-
Notifications
You must be signed in to change notification settings - Fork 5
1082 lines (1052 loc) · 60.5 KB
/
Copy pathci.yml
File metadata and controls
1082 lines (1052 loc) · 60.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
name: CI
on:
push:
branches:
- master
pull_request:
concurrency:
# Don't let a superseded push's full matrix (dozens of legs, several
# compiling pg_tle from source) keep occupying runner slots once a newer
# push on the same ref supersedes it. Not for master: every push there is
# its own point in history and should get a complete, uncancelled run.
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
env:
PGUSER: postgres
jobs:
# Cheap gate that lets the heavy jobs below skip themselves on commits
# that touch only docs. This must run on every push/pull_request (no
# paths-ignore on the workflow itself), otherwise the required
# all-checks-passed check would never report on doc-only pushes and get
# stuck Pending in branch protection.
#
# It also derives, from a SINGLE set of constants, the supported-PostgreSQL-
# major lists that the test / extension-update / stepwise jobs consume (see
# the "Derive ..." step). Because those lists live here and every heavy job
# already needs this job, adding a PG major is a one-line edit with no new job.
changes:
name: 🔍 Detect changes & derive PG matrix
runs-on: ubuntu-latest
permissions:
# Needed only by "Find the last commit where real code changed" below,
# to query the Actions API for a past run's conclusion/URL.
actions: read
outputs:
docs_only: ${{ steps.diff.outputs.docs_only }}
# Set when docs_only is true: whether there's a real "last tested" commit
# to report at all (false), or this entire PR/push chain has never
# touched anything but docs (true, docs_only_pr) -- see the "Find the
# last commit..." step below for how these are derived.
docs_only_pr: ${{ steps.last_tested.outputs.docs_only_pr }}
last_tested_sha: ${{ steps.last_tested.outputs.last_tested_sha }}
last_tested_conclusion: ${{ steps.last_tested.outputs.last_tested_conclusion }}
last_tested_url: ${{ steps.last_tested.outputs.last_tested_url }}
# Derived PG-major lists (see the "Derive ..." step for their meaning).
supported_pg: ${{ steps.pg.outputs.supported_pg }}
climb_pg: ${{ steps.pg.outputs.climb_pg }}
legacy_pg: ${{ steps.pg.outputs.legacy_pg }}
steps:
- name: Check out the repo
uses: actions/checkout@v7
with:
# Full history needed so BASE and HEAD below are both reachable
# for `git diff`.
fetch-depth: 0
- name: Compute per-push changed files
id: diff
run: |
if [ "${{ github.event_name }}" = "pull_request" ] && \
[ "${{ github.event.action }}" = "synchronize" ] && \
[ -n "${{ github.event.before }}" ]; then
# A push to an already-open PR: before/after give the true
# per-push diff, same as for a branch push.
BASE="${{ github.event.before }}"
HEAD="${{ github.event.after }}"
elif [ "${{ github.event_name }}" = "pull_request" ]; then
# First run for this PR (opened/reopened/etc, or synchronize
# without a usable before): fall back to the whole base...head
# diff.
BASE="${{ github.event.pull_request.base.sha }}"
HEAD="${{ github.event.pull_request.head.sha }}"
else
BASE="${{ github.event.before }}"
HEAD="${{ github.event.after }}"
fi
echo "base=$BASE"
echo "head=$HEAD"
# Fail-safe: a missing HEAD, or an all-zeros BASE (e.g. a new
# branch's first push, where GitHub reports no prior commit),
# means we can't compute a real diff. Run the full matrix rather
# than risk skipping tests.
if [ -z "$HEAD" ] || [ -z "$BASE" ] || [[ "$BASE" =~ ^0+$ ]]; then
echo "docs_only=false" >> "$GITHUB_OUTPUT"
exit 0
fi
CHANGED=$(git diff --name-only "$BASE" "$HEAD" || echo __DIFF_FAILED__)
DOCS_ONLY=true
if [ "$CHANGED" = "__DIFF_FAILED__" ] || [ -z "$CHANGED" ]; then
DOCS_ONLY=false
else
while IFS= read -r f; do
if ! [[ "$f" =~ \.(md|asc)$ ]]; then
DOCS_ONLY=false
break
fi
done <<< "$CHANGED"
fi
echo "changed files:"
echo "$CHANGED"
echo "docs_only=$DOCS_ONLY" >> "$GITHUB_OUTPUT"
- name: Find the last commit where real code changed
# This push is docs-only, so the heavy jobs below are about to skip --
# meaning THIS run's checks don't cover the code currently on this
# branch. Someone reviewing before merge needs to know whether that
# code was actually fully tested somewhere, and whether it was green.
# Walk backward from HEAD (bounded by the PR's base, or a generous
# depth cap for a direct push) to the newest commit whose OWN diff
# from its parent touched something other than docs -- every commit
# strictly after that one only changed docs, so that commit's CI run
# is still the authoritative test of the code on this branch now.
if: steps.diff.outputs.docs_only == 'true'
id: last_tested
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
# actions/checkout defaults to checking out an EPHEMERAL MERGE
# COMMIT for pull_request events (refs/pull/N/merge), not the PR's
# real head commit -- `git rev-parse HEAD` would return that merge
# commit's SHA, and walking `^` from it follows the wrong (base
# branch) first parent, landing on an unrelated commit entirely
# (confirmed hitting exactly this in real CI). Use the actual PR
# head SHA directly instead, same event-type split already used in
# "Compute per-push changed files" above.
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
HEAD_SHA="$PR_HEAD_SHA"
else
HEAD_SHA="${{ github.sha }}"
fi
if [ -n "$PR_BASE_SHA" ] && ! [[ "$PR_BASE_SHA" =~ ^0+$ ]]; then
BOUNDARY=$(git merge-base "$PR_BASE_SHA" "$HEAD_SHA")
else
# Not a PR (e.g. a direct push to master): no PR base to bound
# the walk against, so cap it at a generous depth instead of
# walking arbitrarily far into history.
BOUNDARY=$(git rev-list --max-count=1 --skip=100 "$HEAD_SHA" 2>/dev/null || echo "$HEAD_SHA")
fi
commit="$HEAD_SHA"
last_code_commit=""
while [ "$commit" != "$BOUNDARY" ]; do
parent=$(git rev-parse "$commit^" 2>/dev/null) || break
changed=$(git diff --name-only "$parent" "$commit")
all_docs=true
while IFS= read -r f; do
[ -z "$f" ] && continue
if ! [[ "$f" =~ \.(md|asc)$ ]]; then all_docs=false; break; fi
done <<< "$changed"
if [ "$all_docs" = true ]; then
commit="$parent"
else
last_code_commit="$commit"
break
fi
done
if [ -z "$last_code_commit" ]; then
# Reached the PR's own base (or the depth cap) without ever
# finding a commit that changed code: this whole PR/push chain
# is docs-only, so there is no "last tested" run to point to.
echo "docs_only_pr=true" >> "$GITHUB_OUTPUT"
echo "This entire PR/push contains only documentation changes -- no code testing applies."
exit 0
fi
echo "docs_only_pr=false" >> "$GITHUB_OUTPUT"
echo "last_tested_sha=$last_code_commit" >> "$GITHUB_OUTPUT"
echo "last code-changing commit: $last_code_commit"
# Find that commit's most recent completed run of this same "CI"
# workflow (there can be other workflows -- e.g. Claude Code Review
# -- triggered on the same SHA, hence the name filter).
RUN_JSON=$(gh api "repos/$REPO/actions/runs?head_sha=$last_code_commit&status=completed" --jq \
'[.workflow_runs[] | select(.name == "CI")] | sort_by(.run_started_at) | last // empty' 2>/dev/null || echo "")
if [ -z "$RUN_JSON" ] || [ "$RUN_JSON" = "null" ]; then
echo "last_tested_conclusion=unknown" >> "$GITHUB_OUTPUT"
echo "last_tested_url=" >> "$GITHUB_OUTPUT"
echo "No completed CI run found for $last_code_commit"
else
echo "last_tested_conclusion=$(jq -r '.conclusion' <<<"$RUN_JSON")" >> "$GITHUB_OUTPUT"
echo "last_tested_url=$(jq -r '.html_url' <<<"$RUN_JSON")" >> "$GITHUB_OUTPUT"
fi
- name: Derive the supported-PostgreSQL-major lists
id: pg
run: |
# Spending 20+ lines to replace a handful of version references looks
# silly on the surface, but the point is CONSISTENCY: every job below
# (the `test`/`pg-tle-test` matrix, `legacy-extension-update-test`'s single
# legacy major, and the stepwise climb) derives its PostgreSQL set
# from this ONE source, so they cannot drift onto different version
# lists.
#
# SINGLE SOURCE OF TRUTH for the supported PostgreSQL majors. To add
# or drop a PG major, edit ONLY the three constants below; every job
# derives its version list from them (adding the newest major is a
# one-line NEWEST bump). Do NOT hardcode a supported major in any job
# matrix or loop.
#
# NEWEST -- highest PostgreSQL major cat_tools is tested on.
# CURRENT_FLOOR -- oldest major the CURRENT extension version supports:
# the 0.2.3->0.3.0 update runs ALTER TYPE ... ADD
# VALUE, which cannot run in a pre-PG12 transaction.
# LEGACY_FLOOR -- oldest major the pre-0.2.2 install scripts still
# load on (PG11 added pg_attribute.attmissingval and
# PG12+ exposes the oid system column in SELECT *, both
# of which those old scripts trip over). Only
# legacy-extension-update-test and the stepwise path reach
# back this far.
NEWEST=18
CURRENT_FLOOR=12
LEGACY_FLOOR=10
# supported = CURRENT_FLOOR..NEWEST (newest-first). The FRESH-install
# matrix (test job) runs exactly these.
supported=$(seq "$NEWEST" -1 "$CURRENT_FLOOR")
# climb = LEGACY_FLOOR+1 .. NEWEST (ascending). The stepwise job starts
# one cluster on the legacy floor and binary-pg_upgrades through every
# later major in turn, so its targets are every major above the floor.
climb=$(seq $((LEGACY_FLOOR + 1)) "$NEWEST")
# Emit a JSON array from a list of ints, for the job matrices to
# consume with fromJSON (GitHub evaluates a literal dollar-brace
# expression even inside a run block, so none is written here).
json() { printf '%s\n' "$@" | paste -sd, - | sed 's/^/[/; s/$/]/'; }
echo "supported_pg=$(json $supported)" >> "$GITHUB_OUTPUT"
echo "legacy_pg=$LEGACY_FLOOR" >> "$GITHUB_OUTPUT"
# Space-separated for direct iteration in the stepwise bash loop.
echo "climb_pg=$(echo $climb)" >> "$GITHUB_OUTPUT"
# ===========================================================================
# Test strategy
#
# A cat_tools install can be arrived at several ways, each of which can break
# differently, so each is exercised by its own job below (the per-job
# comments carry the details; this is the big picture):
#
# The `test` job runs two checks, on every supported PostgreSQL major:
# 1. FRESH install -- CREATE EXTENSION at the current version, via
# `make verify-results`. The baseline a brand-new user gets.
# 2. UPDATE TO CURRENT -- bin/test_existing's update-scenario: CREATE
# EXTENSION at the 0.2.2 backward-compat floor, ALTER EXTENSION
# UPDATE to current, structurally compare against a fresh install,
# then run the full suite in existing mode. A planted dependency
# guard blocks a stray non-CASCADE drop throughout, proving the
# update didn't just quietly reinstall. USED to be its own matrix job
# (legacy-extension-update-test's PG12+ leg) -- folded in here since it runs
# on the same majors as (1), and this job's container, checkout, and
# cat_tools install are already there; a separate job would pay for
# all of that again for no added coverage.
#
# The `pg-upgrade-test` job does a BINARY pg_upgrade, SINGLE jump: install
# an OLD version on an OLD major, binary-upgrade the cluster straight to a
# NEWER major (skipping intermediate majors), then update the extension.
# Proves objects created on an old server work when read on a new one.
#
# The `pg-upgrade-stepwise` job does a BINARY pg_upgrade, EVERY major in
# sequence: one cluster climbing 10 -> 11 -> ... -> 18, exercising each
# individual major-to-major transition in turn.
#
# The `legacy-extension-update-test` job is PG10 ONLY now (see the changes job's
# LEGACY_FLOOR comment): the pre-0.2.2 install scripts (0.2.0/0.2.1) load
# on no other major, so this job's only remaining purpose is the
# 0.2.0->0.2.2, 0.2.1->0.2.2, and 0.2.2->0.2.3 (view-rebuild) update
# scripts, on the one major that can still run them. USED to also cover
# the PG12+ update-to-current check across the full matrix -- that leg
# moved into `test` above (see (2) there), since it ran on the same
# majors as `test`'s own fresh-install check and didn't need a separate
# job.
#
# The `pg-tle-test` job covers the SAME majors as `test`, proving cat_tools
# actually works when deployed via pg_tle (AWS's Trusted Language
# Extensions) instead of a filesystem .control file -- the same
# fresh-install and update-path scenarios `test` proves for a filesystem
# install, run again through pg_tle's own registration. That proof only
# means what it claims if pg_tle ISOLATION holds throughout the run (a
# stale filesystem .control file silently wins over a pg_tle registration
# of the same name -- PostgreSQL just resolves from disk instead of
# erroring), so every step here is bracketed by filesystem-cleanliness
# checks -- isolation is the precondition for a trustworthy result, not
# the point of the job. NOT folded into `test`: pg_tle needs
# shared_preload_libraries (mixing pg_tle/non-pg_tle installs on one
# cluster can misbehave), so it needs its own dedicated cluster.
#
# The `pg-tle-upgrade-test` job is the pg_tle-deployed equivalent of
# `pg-upgrade-test`, on the jump pairs within pg_tle's own supported
# PostgreSQL range.
#
# Supported update origins are 0.2.0, 0.2.1 and 0.2.2 (0.1.x is unsupported).
# 0.2.0 and 0.2.1 are BOTH tested as origins because ALTER EXTENSION UPDATE
# takes the shortest path: a 0.2.0 origin updates straight through the
# 0.2.0--0.2.2 script and never touches 0.2.1--0.2.2, so starting at 0.2.1 is
# the only way to exercise the 0.2.1--0.2.2 update script (spelled out at the
# pg-upgrade-test matrix).
#
# Two PostgreSQL-version floors shape the matrices:
# - The pre-0.2.2 install scripts (0.2.0 / 0.2.1) load ONLY on PG10: PG11
# added pg_attribute.attmissingval and PG12+ exposes the oid system column
# in SELECT *, both of which those old scripts trip over. So a 0.2.0 / 0.2.1
# origin can only start on PG10.
# - The current version needs PG12+: the 0.2.3->0.3.0 update runs
# ALTER TYPE ... ADD VALUE, which cannot run in a pre-PG12 transaction (and
# an extension update script is one).
#
# KEY invariant: every pg_upgrade leg CLIMBS to a PostgreSQL that supports the
# current version, then updates to the current version and runs the full suite
# -- no leg stops short. A PG10/11 origin simply HOLDS the extension at 0.2.3
# (the highest version reachable on those majors) until the cluster reaches
# PG12+, where it is updated to the current version.
# ===========================================================================
test:
needs: [changes]
if: needs.changes.outputs.docs_only != 'true'
strategy:
matrix:
# Current-supported majors, from the single source in the changes job.
pg: ${{ fromJSON(needs.changes.outputs.supported_pg) }}
name: 🐘 PostgreSQL ${{ matrix.pg }}
runs-on: ubuntu-latest
container: pgxn/pgxn-tools
steps:
- name: Start PostgreSQL ${{ matrix.pg }}
run: pg-start ${{ matrix.pg }}
- name: Check out the repo
uses: actions/checkout@v7
- name: Install rsync and server headers
# postgresql-server-dev-NN provides catalog/pg_class.h, which
# test/gen-relkinds.sh reads for the relkind drift check in
# test/sql/relation__.sql.
run: apt-get install -y rsync postgresql-server-dev-${{ matrix.pg }}
- name: Test on PostgreSQL ${{ matrix.pg }}
run: |
# Fail if the relkind drift source is empty (headers missing): the
# drift check must run on every version, not pass silently.
make check-relkind-source
# verify-results is the real gate: base.mk declares `verify-results:
# $(TEST_DEPS)`, not `verify-results: test`, because test's own
# recipe now exits non-zero on a regression, which would abort the
# chain before verify-results could inspect and report the diff.
# Either way this runs the suite via installcheck, then checks
# pgtap/regression.diffs. A bare `make test` is redundant here for
# the same reason -- it now also exits non-zero on a regression
# (pgxntool 2.3.0+), but verify-results is the stricter, documented
# check.
make verify-results
# The update-to-current check (see the Test strategy comment
# above). USED to be its own matrix job (legacy-extension-update-test's
# PG12+ leg) -- folded in here since it runs on these same majors,
# and this job's container, checkout, and cat_tools install are
# already there; a separate job would pay for all of that again
# for no added coverage.
#
# NOT equivalent to `make test-update` (install 0.2.2 -> ALTER
# EXTENSION UPDATE -> run the suite, all inside pg_regress's own
# throwaway db). update-scenario creates its own database
# (cat_tools_update, not pg_regress's throwaway db from
# verify-results above -- confirmed no name collision), ALSO
# plants a dependency guard (proving a stray non-CASCADE drop
# stays blocked through the update, not just that the update
# itself ran) and structurally compares the result against a
# fresh install (assert_matches_fresh, via bin/structural_diff) --
# see bin/test_existing's own header for why -- before running
# the full suite against the real updated database in existing
# mode. Reusing the SAME suite and expected output asserts the
# updated database behaves identically to a fresh install. See
# legacy-extension-update-test below for the PG10-only legacy checks
# this does NOT cover (those pre-0.2.2 scripts don't load on
# PG12+ at all).
bin/test_existing update-scenario cat_tools_update 0.2.2
# Style linter (https://github.com/Postgres-Extensions/linter, vendored at
# .vendor/linter). Deliberately checked out WITHOUT submodules -- `make
# lint` is the same command a developer runs locally, and lint.mk
# self-initializes the submodule on first use (see its comment). Using the
# exact same entry point here is what actually proves that self-init works,
# rather than papering over it with a submodules: true checkout. The
# linter's own test suite (fixtures + scanner edge cases) is that repo's
# own CI's job, not this one's. No PostgreSQL needed -- sql-lint is a
# standalone Perl script -- so this doesn't use the pgxn-tools container.
lint:
needs: [changes]
if: needs.changes.outputs.docs_only != 'true'
name: 🧹 SQL Lint
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v7
- name: Lint SQL
# CRITICAL: call `make lint` directly, not some other path (a script, a
# different target, etc). lint.mk's vendored include is guarded on
# `$(wildcard .git)` so a source tarball (no .git) can still build/install
# -- if that guard ever went false here (checkout somehow left no .git),
# `lint` wouldn't exist as a target at all, and `make lint` fails loudly
# ("No rule to make target 'lint'") rather than silently skipping. Any
# wrapper that swallows that exit code or calls a different entry point
# would defeat this safety net.
run: make lint
# cancel-on-close.yml cancels in-flight CI/claude-review runs on PR close by
# joining the SAME concurrency group strings this workflow and
# claude-code-review.yml use -- but it only works because those strings are
# hand-copied there, not referenced. No `if: docs_only` gate here on
# purpose: this is a near-instant metadata check, not worth ever skipping,
# and workflow files aren't "docs" anyway.
verify-cancel-on-close-coupling:
name: 🔗 Verify cancel-on-close.yml coupling
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v7
- name: Confirm ci.yml / claude-code-review.yml still match cancel-on-close.yml's assumptions
run: |
python3 - <<'PY'
import os, sys, yaml
# GitHub Actions pre-substitutes any expression marker (dollar sign
# directly followed by double-open-brace) it finds anywhere in a
# run: block's raw text -- including inside this heredoc -- before
# the script ever runs, regardless of quoting. D keeps that four-
# character sequence from ever appearing contiguously in this file
# (not even in this comment -- deliberately not spelled out here),
# so the expected strings below stay literal text for Python to
# compare against, instead of being evaluated away by Actions
# itself (which would make this check compare each expression's
# live value against itself -- always "equal", catching nothing).
D = "$"
def load(path):
if not os.path.isfile(path):
return None, f"{path} does not exist"
with open(path) as f:
return yaml.safe_load(f), None
errors = []
ci, err = load('.github/workflows/ci.yml')
if err:
errors.append(err)
else:
if ci.get('name') != 'CI':
errors.append(f"ci.yml name is {ci.get('name')!r}, expected 'CI'")
ci_group = (ci.get('concurrency') or {}).get('group')
expected = D + "{{ github.workflow }}-" + D + "{{ github.ref }}"
if ci_group != expected:
errors.append(f"ci.yml concurrency.group is {ci_group!r}, expected {expected!r}")
review, err = load('.github/workflows/claude-code-review.yml')
if err:
errors.append(err)
elif not review.get('jobs', {}).get('claude-review'):
errors.append("claude-code-review.yml no longer has a 'claude-review' job")
else:
review_group = (review.get('concurrency') or {}).get('group')
expected = (
"claude-review-" + D + "{{ github.event.pull_request.number }}"
+ D + "{{ (github.event.action == 'labeled' && github.event.label.name != 'claude-debug')"
+ " && format('-{0}', github.event.label.name) || '' }}"
)
if review_group != expected:
errors.append(f"claude-code-review.yml concurrency.group is {review_group!r}, expected {expected!r}")
if errors:
print("cancel-on-close.yml's assumptions about ci.yml / claude-code-review.yml no longer hold:")
for e in errors:
print(f" - {e}")
print("Update the hardcoded group: strings in .github/workflows/cancel-on-close.yml (or restore the files/fields) to match.")
sys.exit(1)
print("OK: cancel-on-close.yml's group strings still match ci.yml and claude-code-review.yml.")
PY
# Proves cat_tools survives a BINARY pg_upgrade (in-place catalog migration to a
# newer PostgreSQL major), not just an in-place extension update. Each leg is a
# SINGLE jump: install an old cat_tools on an old cluster, plant a dependency
# guard, binary-pg_upgrade STRAIGHT to a newer major (skipping any intermediate
# majors), update the extension to the current version, then run the suite
# against the REAL migrated objects in "existing" mode. Catches view/catalog
# breakage that only surfaces when objects created on an old server are read on a
# new one. Every leg's new_pg supports the current version (PG12+); the
# version-by-version climb through EVERY major is pg-upgrade-stepwise.
pg-upgrade-test:
# Gated behind test+lint (not just changes): this matrix is expensive
# (multiple binary pg_upgrades per leg), and every leg would fail anyway
# on a baseline that's already broken by a failing fresh-install test or
# lint violation. success() is required explicitly once a job's `if:`
# references anything -- GitHub only assumes success() as a default when
# no `if:` is written at all.
needs: [changes, test, lint]
if: success() && needs.changes.outputs.docs_only != 'true'
strategy:
matrix:
include:
# old_pg=10 origins: the FULL real-world journey for a user on old
# PostgreSQL + old cat_tools. old_install is 0.2.0/0.2.1 -- the only
# versions that install on PG10 (PG11 added pg_attribute.attmissingval,
# PG12+ exposes oid in SELECT *, so 0.2.0/0.2.1 install ONLY on PG10).
# The BRIDGE update to 0.2.3, run on PG10 BEFORE pg_upgrade, makes the
# objects pg_upgrade-safe: the shipped 0.2.0->0.2.2 / 0.2.1->0.2.2
# scripts leave fresh-vs-update divergences (relhasoids/relhaspkey never
# stripped from _cat_tools.pg_class_v via the no-op `!= ANY` omit_column),
# and the 0.2.2->0.2.3 update repairs them (its pg_class_v DROP+CREATE
# rebuild strips the dropped columns). 0.2.3 is the highest version a
# PG10 cluster can hold (0.2.3->0.3.0 needs PG12+); the single jump lands
# on a PG12+ major, where the post-upgrade step updates 0.2.3 -> current.
#
# BOTH 0.2.0 and 0.2.1 appear as origins because ALTER EXTENSION UPDATE
# takes the SHORTEST path: a 0.2.0 origin updates straight through the
# 0.2.0--0.2.2 script and never exercises 0.2.1--0.2.2, so starting at
# 0.2.1 is the ONLY way to exercise the 0.2.1--0.2.2 update script.
- old_pg: "10"
new_pg: "18"
old_install: "0.2.0"
bridge_to: "0.2.3"
- old_pg: "10"
new_pg: "18"
old_install: "0.2.1"
bridge_to: "0.2.3"
# old_pg>=11: 0.2.0/0.2.1 do not install on PG11+, and such users are
# already on 0.2.2+. A FRESH 0.2.2 install already strips relhasoids/
# relhaspkey (fixed `!= ALL` omit_column), so it is pg_upgrade-safe with
# no bridge. pg_upgrade to a PG12+ major, then update to the current
# version.
- old_pg: "11"
new_pg: "12"
old_install: "0.2.2"
bridge_to: ""
- old_pg: "11"
new_pg: "18"
old_install: "0.2.2"
bridge_to: ""
- old_pg: "12"
new_pg: "13"
old_install: "0.2.2"
bridge_to: ""
- old_pg: "12"
new_pg: "18"
old_install: "0.2.2"
bridge_to: ""
# Include old_install in the name so matrix legs that differ only by origin
# version (e.g. the two 10 → 18 legs from 0.2.0 and 0.2.1) get distinct,
# unambiguous check names.
name: 🔄 Binary pg_upgrade ${{ matrix.old_pg }} → ${{ matrix.new_pg }} (from ${{ matrix.old_install }})
runs-on: ubuntu-latest
container: pgxn/pgxn-tools
env:
# Both clusters must use the same initdb options so pg_upgrade sees
# consistent settings (checksums, auth) on old and new clusters.
INITDB_OPTS: --data-checksums --auth trust
steps:
- name: Start PostgreSQL ${{ matrix.old_pg }}
run: pg-start ${{ matrix.old_pg }}
- name: Check out the repo
uses: actions/checkout@v7
- name: Install rsync
run: apt-get install -y rsync
- name: Recreate old cluster with data checksums enabled
run: .github/scripts/pg_upgrade_cluster recreate-old ${{ matrix.old_pg }}
- name: Install cat_tools into old cluster
run: make install
- name: Prepare the old cluster (install + bridge + dependency guard)
# prepare-old installs cat_tools at old_install and, when bridge_to is set
# (old_pg=10), ALTER EXTENSION UPDATEs to it BEFORE pg_upgrade -- the
# bridge that makes the views pg_upgrade-safe (see prepare-old in the
# script). It then plants + proves the dependency guard so the later
# existing-mode run cannot silently drop+reinstall and test a fresh
# install instead. The extension is always older than the current version,
# so the post-upgrade ALTER EXTENSION UPDATE always has real work to do.
run: >-
bin/test_existing prepare-old cat_tools_upgrade
"${{ matrix.old_install }}" "${{ matrix.bridge_to }}"
- name: Install PostgreSQL ${{ matrix.new_pg }}
run: apt-get install -y postgresql-${{ matrix.new_pg }} postgresql-server-dev-${{ matrix.new_pg }}
- name: Install cat_tools into new cluster
# PG_CONFIG must be specified explicitly: at this point both old and new
# PostgreSQL are installed, and the default pg_config on PATH may not be
# the new version's.
run: make install PG_CONFIG=/usr/lib/postgresql/${{ matrix.new_pg }}/bin/pg_config
- name: Stop old cluster, binary pg_upgrade to PostgreSQL ${{ matrix.new_pg }}, start new cluster
run: .github/scripts/pg_upgrade_cluster upgrade ${{ matrix.old_pg }} ${{ matrix.new_pg }}
- name: Update the pg_upgraded extension to the current version
# Exercises ALTER EXTENSION UPDATE on genuinely pg_upgraded objects (the
# extension binary pg_upgrade just migrated), running the version-to-version
# scripts up to the current version. The 0.2.3 -> 0.3.0 script uses
# ALTER TYPE ... ADD VALUE, which is why every leg's new_pg is >= 12.
run: bin/test_existing update cat_tools_upgrade
- name: Run the suite against the pg_upgraded database (existing mode)
# run-suite asserts the version, re-proves the dependency guard still
# blocks a non-CASCADE drop (i.e. it survived pg_upgrade), then runs the
# suite against the REAL pg_upgraded + updated database via --use-existing
# (so pg_regress does not drop/recreate it) -- a plain fresh `make test`
# would silently test a fresh install instead of the migrated objects.
run: bin/test_existing run-suite cat_tools_upgrade
# Proves cat_tools survives EVERY individual major-to-major binary pg_upgrade
# transition, not just a single big jump (that is pg-upgrade-test): ONE cluster
# that starts on the oldest origin and climbs through every major in sequence.
# Installs 0.2.0 on PG10, bridges to 0.2.3 (the highest version a PG10 cluster
# can hold -- see the pg-upgrade-test matrix comment), plants the dependency
# guard, then binary-pg_upgrades 10->11->12->...->18 ONE major at a time. The
# extension is HELD at 0.2.3 for the 10->11 and 11->12 steps (PG11 cannot run
# the 0.2.3->0.3.0 update, which needs PG12+); on reaching PG12 it is updated
# 0.2.3 -> the current version, and from PG12 on the full suite runs in existing
# mode at EVERY major (12..18) against the REAL pg_upgraded objects, re-proving
# the guard survived each step. CI-heavy on purpose: it installs PostgreSQL 10
# through 18, performs 8 sequential pg_upgrades, and runs the suite 7 times.
pg-upgrade-stepwise:
# Gated behind test+lint -- see the comment on pg-upgrade-test's needs.
needs: [changes, test, lint]
if: success() && needs.changes.outputs.docs_only != 'true'
name: 🪜 Stepwise pg_upgrade 10 → 18 (from 0.2.0)
runs-on: ubuntu-latest
container: pgxn/pgxn-tools
env:
# Every pg_upgrade in the climb pairs two clusters that must share initdb
# options (checksums, auth), exactly as in pg-upgrade-test.
INITDB_OPTS: --data-checksums --auth trust
DB: cat_tools_stepwise
# Climb targets and starting (legacy) major, from the single source in the
# changes job -- so a new PG major joins the climb with no edit here.
CLIMB_PG: ${{ needs.changes.outputs.climb_pg }}
LEGACY_PG: ${{ needs.changes.outputs.legacy_pg }}
steps:
- name: Start PostgreSQL 10
run: pg-start 10
- name: Recreate the PG10 cluster with data checksums enabled
run: |
pg_ctlcluster 10 test stop
pg_dropcluster 10 test
# -p 5432: force the well-known port (see pg-upgrade-test for why).
pg_createcluster -p 5432 10 test -- $INITDB_OPTS
pg_ctlcluster 10 test start
pg_isready -t 30
- name: Check out the repo
uses: actions/checkout@v7
- name: Install rsync
# Must NOT install a newer server-dev yet: `make install` just below runs
# with the DEFAULT pg_config, which must still resolve to PG10. Each newer
# major's server-dev is installed inside the climb loop, at which point the
# default pg_config advances with it (the final-major suite reads that
# version's catalog header for the relkind drift check; see the test job).
run: apt-get install -y rsync
- name: Install cat_tools into the PG10 cluster
run: make install
- name: Prepare PG10 (install 0.2.0, bridge to 0.2.3, plant guard)
run: bin/test_existing prepare-old "$DB" 0.2.0 0.2.3
- name: Climb every major via binary pg_upgrade (10 → 11 → ... → 18)
# One sequential loop; each iteration binary-pg_upgrades the cluster from
# $old to $new (a single major step). cat_tools is a SQL-only extension, so
# pg_upgrade only needs its scripts present in the NEW cluster's sharedir:
# `make install PG_CONFIG=<new>` before each step (the $old cluster already
# has them, from the previous iteration or the PG10 install above). The
# extension is held at 0.2.3 until PG12, where it is updated to the current
# version; the suite then runs at every major from PG12 on (see the suite
# policy inside the loop). The per-step pg_upgrade invocation and log
# handling mirror pg-upgrade-test.
run: |
old=$LEGACY_PG
for new in $CLIMB_PG; do
echo "=== binary pg_upgrade PostgreSQL $old -> $new ==="
apt-get install -y postgresql-$new postgresql-server-dev-$new
# PG_CONFIG explicit: several majors are installed, so the default
# pg_config on PATH may not be $new's.
make install PG_CONFIG=/usr/lib/postgresql/$new/bin/pg_config
pg_ctlcluster $old test stop
pg_createcluster -p 5432 $new test -- $INITDB_OPTS
# PG17+ writes logs under the new datadir; older versions to CWD. Dump
# both on failure (same handling as pg-upgrade-test).
mkdir -p /tmp/pg_upgrade_logs
chown postgres:postgres /tmp/pg_upgrade_logs
su -c "cd /tmp/pg_upgrade_logs && /usr/lib/postgresql/$new/bin/pg_upgrade \
-b /usr/lib/postgresql/$old/bin \
-B /usr/lib/postgresql/$new/bin \
-d /var/lib/postgresql/$old/test \
-D /var/lib/postgresql/$new/test \
-o '-c config_file=/etc/postgresql/$old/test/postgresql.conf' \
-O '-c config_file=/etc/postgresql/$new/test/postgresql.conf'" postgres \
|| { find /tmp/pg_upgrade_logs \
/var/lib/postgresql/$new/test/pg_upgrade_output.d \
-name '*.log' 2>/dev/null | sort | xargs -r tail -n +1; exit 1; }
pg_ctlcluster $new test start
pg_isready -t 30
# Suite policy across the climb. We unfortunately CANNOT run the suite
# against the OLD version (0.2.3) at the pre-PG12 steps (10->11, 11->12):
# the suite matches the CURRENT version's objects and we do not maintain
# an old-version expected-output set. So those steps only verify that
# 0.2.3 SURVIVES each pg_upgrade -- i.e. the climb completing without
# error. On reaching PG12 (the floor for 0.2.3->0.3.0, which needs
# ALTER TYPE ... ADD VALUE) update once to the current version; from
# PG12 on the extension is at current, so run the full suite in existing
# mode at EVERY major (run-suite asserts the version and re-proves the
# guard survived this step's pg_upgrade).
if [ "$new" = 12 ]; then
bin/test_existing update "$DB"
fi
if [ "$new" -ge 12 ]; then
bin/test_existing run-suite "$DB"
fi
old=$new
done
# Proves the pre-0.2.2 legacy install/update scripts: CREATE EXTENSION at
# 0.2.0/0.2.1 then ALTER EXTENSION UPDATE (no pg_upgrade). PG10 ONLY -- see
# the Test strategy comment above for why, and why the PG12+ update-to-
# current leg lives in the `test` job instead of here.
legacy-extension-update-test:
# Gated behind test+lint -- see the comment on pg-upgrade-test's needs.
needs: [changes, test, lint]
if: success() && needs.changes.outputs.docs_only != 'true'
# Single source of truth for the legacy PG10 floor: needs.changes.outputs.legacy_pg,
# not a hardcoded '10'.
env:
LEGACY_PG: ${{ needs.changes.outputs.legacy_pg }}
name: ⬆️ Legacy extension update test on PostgreSQL ${{ needs.changes.outputs.legacy_pg }}
runs-on: ubuntu-latest
container: pgxn/pgxn-tools
steps:
- name: Start PostgreSQL ${{ env.LEGACY_PG }}
run: pg-start ${{ env.LEGACY_PG }}
- name: Check out the repo
uses: actions/checkout@v7
- name: Install rsync
# No server-dev header package here: this job runs no suite (see the
# job comment above), so nothing needs the relkind drift check.
run: apt-get install -y rsync
- name: Install cat_tools (all versions)
run: make install
- name: Test pre-0.2.2 update scripts + 0.2.2→0.2.3 rebuild
# 0.2.0/0.2.1 install only on PG10; their update scripts target 0.2.2
# (not the current version) and are otherwise never exercised. Both
# origins are checked because ALTER EXTENSION UPDATE takes the
# shortest path (see pg-upgrade-test above): 0.2.0 goes via the
# 0.2.0--0.2.2 script, 0.2.1 via 0.2.1--0.2.2. update-check-version
# asserts each lands on 0.2.2 -- NOT plain update-check: landing on
# 0.2.2 from these origins is a KNOWN divergence from a fresh 0.2.2
# install (trigger__parse and the pg_class_v omit_column bug,
# repaired in 0.2.2->0.2.3; a type-ACL gap, repaired only in
# 0.2.3->0.3.0), and both 0.2.0->0.2.2 / 0.2.1->0.2.2 are
# already-published scripts that cannot be edited to fix it
# directly, so asserting fresh-parity here would fail forever by
# design.
#
# The rebuild_020/rebuild_021 checks exercise the REAL broken path
# end to end from BOTH pre-0.2.2 origins: a 0.2.0 (resp. 0.2.1)
# install leaves relhasoids in _cat_tools.pg_class_v (the buggy
# 0.2.0--0.2.2 / 0.2.1--0.2.2 omit_column no-op), and updating to
# 0.2.3 routes through 0.2.2--0.2.3 (shortest path <origin>--0.2.2
# then 0.2.2--0.2.3), firing the conditional rebuild that strips
# relhasoids and the trigger__parse repair. It stays
# update-check-version, not plain update-check: 0.2.3 is ALSO
# already-published (tagged), so the type-ACL gap above is not (and
# cannot be) fixed until 0.2.3->0.3.0 either -- this landing point
# still diverges from a fresh 0.2.3 install on that ACL alone. 0.2.3
# is the furthest PG10 can reach (0.2.3--0.3.0 needs PG12+). The
# psql assertion fails loudly if the rebuild did not fire (relhasoids
# still present) -- complementing the stronger 10→18 pg_upgrade
# bridge legs. `$$` is escaped as `\$\$` so the shell passes literal
# dollar quotes through to psql.
run: |
bin/test_existing update-check-version cat_tools_from_020 0.2.0 0.2.2
bin/test_existing update-check-version cat_tools_from_021 0.2.1 0.2.2
for origin in 020:0.2.0 021:0.2.1; do
db="cat_tools_rebuild_${origin%%:*}"
from="${origin##*:}"
bin/test_existing update-check-version "$db" "$from" 0.2.3
psql -d "$db" -v ON_ERROR_STOP=1 -c "DO \$\$ BEGIN IF EXISTS (SELECT 1 FROM pg_attribute WHERE attrelid='_cat_tools.pg_class_v'::regclass AND attname='relhasoids' AND NOT attisdropped AND attnum>0) THEN RAISE EXCEPTION 'pg_class_v still exposes relhasoids after update through 0.2.2->0.2.3 -- rebuild did not fire'; END IF; END \$\$"
done
pg-tle-test:
# Gated behind test+lint -- see the comment on pg-upgrade-test's needs.
needs: [changes, test, lint]
if: success() && needs.changes.outputs.docs_only != 'true'
strategy:
matrix:
# Current-supported majors, from the single source in the changes job
# (see its "Derive ..." step) -- same list the `test` job uses. This
# currently happens to line up with pg_tle 1.5.2's own supported range
# (12-18, see pgxntool/pgtle_versions.md) too, but that's a coincidence:
# if either range moves independently in the future, re-check they
# still overlap before assuming this job covers what it claims to.
# 1.5.2 itself is what AWS RDS for PostgreSQL 17 currently ships
# (RDS PG17.7+, our primary deployment target -- see
# https://docs.aws.amazon.com/AmazonRDS/latest/PostgreSQLReleaseNotes/postgresql-versions.html).
pg: ${{ fromJSON(needs.changes.outputs.supported_pg) }}
name: 🧩 pg_tle ${{ matrix.pg }}
runs-on: ubuntu-latest
container: pgxn/pgxn-tools
env:
PG_TLE_RELEASE: "1.5.2"
steps:
# A dedicated cluster, never shared with the other jobs in this
# workflow: pg_tle requires shared_preload_libraries and mixing
# pg_tle/non-pg_tle extension installs on one cluster can misbehave.
- name: Start PostgreSQL ${{ matrix.pg }}
run: pg-start ${{ matrix.pg }}
- name: Check out the repo
uses: actions/checkout@v7
- name: Install rsync
run: apt-get install -y rsync
- name: Install pgtap (test harness dependency)
# pgTAP is a filesystem-installed dependency of the TEST HARNESS, not
# part of what this job proves is pg_tle-only -- it's not being
# deployed via pg_tle here, and never will be. `bin/test_existing`'s
# pg_tle-mode run_suite calls `make testdeps`, which would otherwise
# filesystem-install pgTAP for the first time partway through the job
# and trip the contamination checks below (confirmed happening in a
# real CI run). Installing it explicitly here, before the baseline
# snapshot, makes it part of the accepted starting state -- like any
# other extension already on disk -- instead of needing a hardcoded
# exclude-by-name rule that would erode the whole point of diffing
# against a baseline.
run: make pgtap
- name: Snapshot filesystem extension control files (pre-pg_tle baseline)
# Whatever ships on disk by default (e.g. contrib, and now pgtap)
# before we install pg_tle. bin/assert_fs_clean's later checks diff
# against this, so they flag ANY extension that lands on disk instead
# of being registered via pg_tle -- not just cat_tools -- without
# hardcoding contrib/pgtap names.
run: bin/assert_fs_clean snapshot ${{ matrix.pg }} /tmp/control_baseline.txt
- name: Build and install pg_tle ${{ env.PG_TLE_RELEASE }}
# flex/bison/libkrb5-dev aren't in the pgxn-tools image; pg_tle's build
# needs them (guc-file.l, and clientauth.c includes gssapi.h).
run: |
apt-get install -y flex bison libkrb5-dev
git clone --branch v${{ env.PG_TLE_RELEASE }} --depth 1 https://github.com/aws/pg_tle.git /tmp/pg_tle
make -C /tmp/pg_tle install
- name: Enable pg_tle and restart PostgreSQL ${{ matrix.pg }}
run: |
echo "shared_preload_libraries = 'pg_tle'" >> /etc/postgresql/${{ matrix.pg }}/test/postgresql.conf
pg_ctlcluster ${{ matrix.pg }} test restart
pg_isready -t 30
- name: Register pg_tle + cat_tools against template1
# template1, not the ambient default db: pg_tle's registration catalog
# is per-database, and `createdb` only inherits it because it copies
# template1 by default. Every cat_tools database used below (the
# smoke-test db, and whatever bin/test_existing createdb's) is created
# AFTER this step specifically so it inherits both registrations.
run: |
psql -d template1 -c "CREATE EXTENSION pg_tle"
PGDATABASE=template1 make run-pgtle
- name: Verify no stray extension control files landed on the filesystem
# CRITICAL, and intentionally redundant with the cat_tools-specific
# check in the next step: a filesystem control file silently wins
# over a pg_tle-registered extension of the same name, which would
# make this whole job a false pass without ever raising an error.
# Confirmed hitting exactly this while researching pg_tle version
# compatibility (a stale filesystem cat_tools install shadowed the
# pg_tle-registered one under test). Run again after every step below
# that could plausibly write extension files to disk -- never trust a
# single check to catch everything.
run: bin/assert_fs_clean verify ${{ matrix.pg }} /tmp/control_baseline.txt
- name: Install cat_tools purely via pg_tle (fresh install, no filesystem trace)
# cat_tools is never `make install`ed in this job, so a successful
# CREATE EXTENSION here can only be resolving through pg_tle's
# registration, not a control file on disk. Checked explicitly here
# too (not just via the comprehensive check above) as a guard
# specifically for the extension under test, in case that check's
# exclude-list logic has a bug.
run: |
test ! -e /usr/share/postgresql/${{ matrix.pg }}/extension/cat_tools.control
createdb cat_tools_smoke
psql -d cat_tools_smoke -c "CREATE EXTENSION cat_tools"
- name: Verify cat_tools works when deployed via pg_tle
run: |
INSTALLED=$(psql -d cat_tools_smoke -tAc "SELECT extversion FROM pg_extension WHERE extname = 'cat_tools'")
EXPECTED=$(make -s print-PGXNVERSION 2>/dev/null | sed -n 's/.*set to "\(.*\)"$/\1/p')
echo "installed=$INSTALLED expected=$EXPECTED"
if [ -z "$INSTALLED" ] || [ -z "$EXPECTED" ] || [ "$INSTALLED" != "$EXPECTED" ]; then
echo "FAIL: installed='$INSTALLED' expected='$EXPECTED'"; exit 1
fi
psql -d cat_tools_smoke -v ON_ERROR_STOP=1 -c "SELECT relname FROM cat_tools.pg_class_v LIMIT 1" > /dev/null
- name: Verify no stray extension control files after the fresh-install smoke test
run: bin/assert_fs_clean verify ${{ matrix.pg }} /tmp/control_baseline.txt
- name: Install PostgreSQL ${{ matrix.pg }} server dev headers
# Needed by `make check-relkind-source`, which bin/test_existing's
# run_suite calls -- every other job that reaches that check already
# installs this; pg-tle-test didn't need it until now because the
# smoke test above never runs the full suite.
run: apt-get install -y postgresql-server-dev-${{ matrix.pg }}
- name: Test the update path via pg_tle (full pgTAP suite, --use-existing)
# Exercises the SAME update path the `test` job proves for a
# filesystem install (0.2.2 -> current), but entirely through pg_tle.
# bin/test_existing's update_scenario is UNMODIFIED from the
# filesystem case -- only run_suite's internals differ, gated by this
# one env var (see the TEST_EXISTING_DEPLOY comment in
# bin/test_existing) -- so this reuses the exact same update/upgrade
# code the other jobs use instead of duplicating it for pg_tle.
run: TEST_EXISTING_DEPLOY=pgtle bin/test_existing update-scenario cat_tools_update 0.2.2
- name: Verify no stray extension control files after the pg_tle update test
# Runs AFTER the complete update+full-suite flow, not before: the
# whole point of pg_tle-mode testing is proving NOTHING touched the
# filesystem THROUGHOUT that flow, not merely that the environment
# started clean. A check placed before this point could not catch a
# regression introduced by anything update-scenario itself does.
run: bin/assert_fs_clean verify ${{ matrix.pg }} /tmp/control_baseline.txt
pg-tle-upgrade-test:
# Gated behind test+lint -- see the comment on pg-upgrade-test's needs.
needs: [changes, test, lint]
if: success() && needs.changes.outputs.docs_only != 'true'
strategy:
matrix:
include:
# Mirrors pg-upgrade-test's old_pg=12 legs (the only ones cleanly
# within pg_tle 1.5.2's own supported range, PG12-18) so the SAME PG
# version jumps are proven for both deployment methods. Unlike
# pg-upgrade-test, there is no bridge/old_install complexity here --
# pg_tle has no equivalent of the pre-0.2.2 filesystem install-script
# issues (those are about SELECT * over catalog columns in old SQL
# files, irrelevant to how the extension gets registered).
- old_pg: "12"
new_pg: "13"
- old_pg: "12"
new_pg: "18"
name: 🧩🔄 pg_tle binary pg_upgrade ${{ matrix.old_pg }} → ${{ matrix.new_pg }}
runs-on: ubuntu-latest
container: pgxn/pgxn-tools
env:
# Both clusters must use the same initdb options so pg_upgrade sees
# consistent settings (checksums, auth) on old and new clusters.
INITDB_OPTS: --data-checksums --auth trust
PG_TLE_RELEASE: "1.5.2"
steps:
# A dedicated cluster pair, never shared with the other jobs in this
# workflow -- same reasoning as pg-tle-test.
- name: Start PostgreSQL ${{ matrix.old_pg }}
run: pg-start ${{ matrix.old_pg }}
- name: Check out the repo
uses: actions/checkout@v7
- name: Install rsync
run: apt-get install -y rsync
- name: Recreate old cluster with data checksums enabled
run: .github/scripts/pg_upgrade_cluster recreate-old ${{ matrix.old_pg }}
- name: Snapshot filesystem extension control files (old cluster baseline)
run: bin/assert_fs_clean snapshot ${{ matrix.old_pg }} /tmp/control_baseline_old.txt
- name: Build and install pg_tle ${{ env.PG_TLE_RELEASE }} on the old cluster
# flex/bison/libkrb5-dev aren't in the pgxn-tools image; pg_tle's build
# needs them (guc-file.l, and clientauth.c includes gssapi.h).
run: |
apt-get install -y flex bison libkrb5-dev
git clone --branch v${{ env.PG_TLE_RELEASE }} --depth 1 https://github.com/aws/pg_tle.git /tmp/pg_tle_old
make -C /tmp/pg_tle_old install
- name: Enable pg_tle and restart PostgreSQL ${{ matrix.old_pg }}
run: |
echo "shared_preload_libraries = 'pg_tle'" >> /etc/postgresql/${{ matrix.old_pg }}/test/postgresql.conf
pg_ctlcluster ${{ matrix.old_pg }} test restart
pg_isready -t 30
- name: Register pg_tle + cat_tools against template1 (old cluster)
run: |
psql -d template1 -c "CREATE EXTENSION pg_tle"
PGDATABASE=template1 make run-pgtle
- name: Verify no stray extension control files on the old cluster
run: bin/assert_fs_clean verify ${{ matrix.old_pg }} /tmp/control_baseline_old.txt
- name: Prepare the old cluster (install via pg_tle + dependency guard)
# A fresh 0.2.2 install (no bridge needed -- see pg-upgrade-test's own
# comment on why its old_pg>=11 legs don't need one), created via
# pg_tle only, then the same dependency guard bin/test_existing's
# other subcommands rely on to prove existing-mode never silently
# drops+reinstalls the extension.
run: |
test ! -e /usr/share/postgresql/${{ matrix.old_pg }}/extension/cat_tools.control
createdb cat_tools_tle_upgrade
psql -d cat_tools_tle_upgrade -c "CREATE EXTENSION cat_tools VERSION '0.2.2'"
bin/test_existing plant-guard cat_tools_tle_upgrade
- name: Install PostgreSQL ${{ matrix.new_pg }}
run: apt-get install -y postgresql-${{ matrix.new_pg }} postgresql-server-dev-${{ matrix.new_pg }}
- name: Install pgtap on the new cluster (test harness dependency)
# Must happen BEFORE the new-cluster baseline snapshot below, same
# reasoning as pg-tle-test's "Install pgtap" step: pgTAP is a
# filesystem-installed dependency of the test harness, unrelated to
# cat_tools's own pg_tle-only deployment story, so it needs to be part
# of the accepted starting state rather than flagged as contamination.
# PATH is set explicitly (not just relying on apt's update-alternatives
# switch from installing postgresql-${{ matrix.new_pg }} above) since
# `pgxn install` resolves pg_config via PATH, not via a passed-through
# PG_CONFIG make variable.
run: PATH=/usr/lib/postgresql/${{ matrix.new_pg }}/bin:$PATH make pgtap
- name: Snapshot filesystem extension control files (new cluster baseline)
run: bin/assert_fs_clean snapshot ${{ matrix.new_pg }} /tmp/control_baseline_new.txt
- name: Build and install pg_tle ${{ env.PG_TLE_RELEASE }} on the new cluster
# Only pg_tle's OWN framework is needed on the new binary tree -- NOT
# cat_tools. Binary pg_upgrade's schema-only restore recreates each
# object individually (binary_upgrade_create_empty_extension), and
# pg_tle's own catalog tables (holding cat_tools's registered SQL) are
# carried over as ordinary heap data during the physical copy phase --
# no re-registration needed on the new side. A SEPARATE clone
# (pg_tle_new, not pg_tle_old rebuilt) avoids installing a .so still
# compiled against the old cluster's PostgreSQL headers: pg_tle has C
# code, unlike cat_tools, so switching PG_CONFIG on an already-built
# tree without a fresh build would risk installing the wrong binary.
run: |
git clone --branch v${{ env.PG_TLE_RELEASE }} --depth 1 https://github.com/aws/pg_tle.git /tmp/pg_tle_new
make -C /tmp/pg_tle_new install PG_CONFIG=/usr/lib/postgresql/${{ matrix.new_pg }}/bin/pg_config