-
Notifications
You must be signed in to change notification settings - Fork 1
275 lines (264 loc) · 13.1 KB
/
Copy pathci.yml
File metadata and controls
275 lines (264 loc) · 13.1 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
name: ci
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
jobs:
test:
name: vet + unit tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
- run: go vet ./...
- name: gofmt
run: |
unformatted=$(gofmt -l .)
if [ -n "$unformatted" ]; then
echo "::error::these files need gofmt:"; echo "$unformatted"; exit 1
fi
# -race: the ASH sampler and store writer run concurrently; the race
# detector must stay green (it needs CGO, which the ubuntu runner has).
- run: go test -race ./... # DB-dependent tests self-skip without PGBOT_TEST_DSN
# The query scrubber is a privacy boundary — fuzz it briefly so a regression
# that leaks an email/uuid/number shape fails CI, not a customer.
- name: fuzz the query scrubber
run: go test ./internal/conn/ -run '^$' -fuzz 'FuzzScrubQueryText' -fuzztime 30s
lint:
name: golangci-lint + govulncheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
# Pinned to the version validated locally so a golangci-lint release can't
# change the gate out from under a green PR.
- name: golangci-lint
run: |
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.13.2
"$(go env GOPATH)/bin/golangci-lint" run ./...
# A security-positioned tool must scan its own dependency tree.
- name: govulncheck
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
"$(go env GOPATH)/bin/govulncheck" ./...
action-smoke:
name: github action smoke
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
- name: build pgbot onto PATH (so the action uses it — no release needed)
run: |
mkdir -p "$HOME/.local/bin"
CGO_ENABLED=0 go build -o "$HOME/.local/bin/pgbot" ./cmd/pgbot
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: start PostgreSQL with a pg_monitor role
run: |
docker run -d --name pg -p 5432:5432 -e POSTGRES_PASSWORD=pw \
postgres:17 -c shared_preload_libraries=pg_stat_statements
for i in $(seq 1 60); do docker exec pg pg_isready -U postgres -h 127.0.0.1 -q && break; sleep 1; done
docker exec pg psql -U postgres -q \
-c "CREATE EXTENSION pg_stat_statements;" \
-c "CREATE ROLE pgbot_ro LOGIN PASSWORD 'ro'; GRANT pg_monitor TO pgbot_ro; GRANT CONNECT ON DATABASE postgres TO pgbot_ro;"
- name: run the composite action (fail-on=none so findings don't fail the smoke)
uses: ./
with:
dsn: postgres://pgbot_ro:ro@127.0.0.1:5432/postgres
format: sarif
fail-on: none
upload-sarif: "false"
- name: the action's SARIF is valid
run: |
python3 -c "import json,sys; d=json.load(open('pgbot-report.sarif')); assert d['version']=='2.1.0' and d['runs'], 'bad SARIF'; print('valid SARIF,', len(d['runs'][0]['results']), 'results')"
# D3-3: exercise the schema profile through the action, capture a base
# report, then re-run with base-report — the migration-PR wiring end to end.
- name: action profile=schema → base report
uses: ./
with:
dsn: postgres://pgbot_ro:ro@127.0.0.1:5432/postgres
profile: schema
format: json
fail-on: none
upload-sarif: "false"
- run: cp pgbot-report.json base.json
- name: action profile=schema with --fail-on-new (no change → passes)
uses: ./
with:
dsn: postgres://pgbot_ro:ro@127.0.0.1:5432/postgres
profile: schema
base-report: base.json
fail-on: warn
upload-sarif: "false"
# action-smoke pre-builds pgbot onto PATH, so it never runs install.sh. This job
# deliberately does NOT, so the action must install via `install.sh` with the
# default version 'latest' — the path the "latest is a literal tag → 404" bug
# broke and the entry point for the agent-push / CI-check workflow.
action-install-smoke:
name: github action install path (latest)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: start PostgreSQL with a pg_monitor role
run: |
docker run -d --name pg -p 5432:5432 -e POSTGRES_PASSWORD=pw postgres:17
for i in $(seq 1 60); do docker exec pg pg_isready -U postgres -h 127.0.0.1 -q && break; sleep 1; done
docker exec pg psql -U postgres -q \
-c "CREATE ROLE pgbot_ro LOGIN PASSWORD 'ro'; GRANT pg_monitor TO pgbot_ro; GRANT CONNECT ON DATABASE postgres TO pgbot_ro;"
- name: run the action with no pgbot on PATH (default version=latest)
uses: ./
with:
dsn: postgres://pgbot_ro:ro@127.0.0.1:5432/postgres
format: text
fail-on: none
- name: the action installed pgbot and it runs
run: pgbot --version
static-check:
name: no EXPLAIN ANALYZE
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# pgbot must never execute a query it inspects. EXPLAIN ANALYZE runs the
# statement — it must not appear anywhere in the tree.
- name: forbid EXPLAIN ANALYZE
run: |
# Exclude the guard test itself — it names the banned phrase in its own
# doc/error strings, exactly as the in-tree Go guard (TestNoExplainAnalyze)
# skips its own file.
if grep -rniE 'explain[[:space:]]*(\(|[[:space:]])[^)]*analyze' \
--include='*.go' --include='*.sql' --exclude='no_explain_analyze_test.go' . ; then
echo "::error::EXPLAIN ANALYZE found — pgbot must never execute inspected queries"
exit 1
fi
echo "clean: no EXPLAIN ANALYZE"
build:
name: build ${{ matrix.goos }}/${{ matrix.goarch }}
runs-on: ubuntu-latest
strategy:
matrix:
goos: [linux, darwin]
goarch: [amd64, arm64]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
# CGO must stay off so the binary is static (catches a cgo regression from
# a new dependency).
- run: CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build ./cmd/pgbot
integration:
name: integration PG${{ matrix.pg }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
pg: [14, 15, 16, 17, 18]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
- name: start PostgreSQL ${{ matrix.pg }} (with pg_stat_statements)
run: |
docker run -d --name pg -p 5432:5432 -e POSTGRES_PASSWORD=pw \
postgres:${{ matrix.pg }} -c shared_preload_libraries=pg_stat_statements
# TCP check (-h 127.0.0.1), not the default socket: the postgres image's
# initdb-phase server is socket-only, so a socket pg_isready can report
# ready during init and then the real-server start briefly closes it.
# TCP only answers once the real server is up — no shutdown-window race.
for i in $(seq 1 60); do docker exec pg pg_isready -U postgres -h 127.0.0.1 -q && break; sleep 1; done
docker exec pg psql -U postgres -q \
-c "CREATE EXTENSION pg_stat_statements;" \
-c "CREATE TABLE people(id serial primary key, email text);" \
-c "INSERT INTO people(email) SELECT 'user'||g||'@example.com' FROM generate_series(1,2000) g;" \
-c "CREATE ROLE pgbot_ro LOGIN PASSWORD 'ro'; GRANT pg_monitor TO pgbot_ro; GRANT CONNECT ON DATABASE postgres TO pgbot_ro;"
- name: run integration suite (full pipeline + PII + rates under load)
env:
PGBOT_TEST_DSN: postgres://pgbot_ro:ro@127.0.0.1:5432/postgres
# Superuser dsn for the doc-verify guard: it creates a small fixture
# (tables + pgstattuple) the read-only role can't, then runs every
# "How to verify" query from the catalogue read-only (PG18 job only).
PGBOT_TEST_SUPERUSER_DSN: postgres://postgres:pw@127.0.0.1:5432/postgres
PGBOT_TEST_LOAD: "1"
run: |
# concurrent write load so the stats-caching / rate guards are meaningful
docker exec pg psql -U postgres -q -c \
"DO \$\$ BEGIN FOR i IN 1..200000 LOOP INSERT INTO people(email) VALUES('l@example.com'); IF i%50=0 THEN COMMIT; END IF; END LOOP; END \$\$;" &
go test ./internal/collect/ -run Integration -v
# cmd/pgbot integration: the advisor's read-only-blocks-a-write proof and
# the MCP explain_plan/schema_of tools, against the same server.
go test ./cmd/pgbot/ -run Integration -v
- name: degrade cleanly without pg_stat_statements
run: |
docker rm -f pg
docker run -d --name pg2 -p 5433:5432 -e POSTGRES_PASSWORD=pw postgres:${{ matrix.pg }}
for i in $(seq 1 60); do docker exec pg2 pg_isready -U postgres -h 127.0.0.1 -q && break; sleep 1; done
docker exec pg2 psql -U postgres -q -c "CREATE TABLE t(id serial primary key);"
CGO_ENABLED=0 go build -o pgbot ./cmd/pgbot
# a no-pgss run must exit on findings (not error) and mark Queries unavailable
./pgbot inspect "postgres://postgres:pw@127.0.0.1:5433/postgres" --no-store --json \
| grep -q '"exactness": "unavailable"' \
&& echo "queries correctly unavailable" || (echo "::error::no-pgss path broken"; exit 1)
pooler:
name: transaction pooler (rates stay correct)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
- name: PgBouncer transaction mode in front of Postgres
run: |
docker network create n
docker run -d --name pg --network n -e POSTGRES_PASSWORD=pw \
postgres:17 -c shared_preload_libraries=pg_stat_statements
# TCP check (-h 127.0.0.1), not the default socket: the postgres image's
# initdb-phase server is socket-only, so a socket pg_isready can report
# ready during init and then the real-server start briefly closes it.
# TCP only answers once the real server is up — no shutdown-window race.
for i in $(seq 1 60); do docker exec pg pg_isready -U postgres -h 127.0.0.1 -q && break; sleep 1; done
docker exec pg psql -U postgres -q -c "CREATE TABLE t(id serial primary key, note text);"
# publish on 6543 so the known-endpoint detection also fires
docker run -d --name pgb --network n -p 6543:5432 \
-e DB_HOST=pg -e DB_USER=postgres -e DB_PASSWORD=pw -e DB_NAME=postgres \
-e POOL_MODE=transaction -e AUTH_TYPE=scram-sha-256 edoburu/pgbouncer
sleep 5
- name: assert rates stay correct behind the pooler
env:
PGBOT_POOLER_DSN: postgres://postgres:pw@127.0.0.1:6543/postgres
PGBOT_TEST_LOAD: "1"
run: |
docker exec pg psql -U postgres -q -c \
"DO \$\$ BEGIN FOR i IN 1..200000 LOOP INSERT INTO t(note) VALUES('x'); IF i%50=0 THEN COMMIT; END IF; END LOOP; END \$\$;" &
go test ./internal/collect/ -run Integration_poolerRatesStayCorrect -v
npm:
name: npm wrapper (packaging)
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 20
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
# DoD 9: exit codes and signals pass through the wrapper unmodified — a 2
# (critical) arrives as 2. Pure-Node, no DB.
- name: wrapper unit tests
run: node --test npm/test/*.test.mjs
# DoD 10: `npm install --ignore-scripts` produces a working install. Builds
# the real binary, packs the wrapper + this platform's package, installs the
# tarballs with --ignore-scripts, and runs `pgbot --version` through it.
- name: pack + --ignore-scripts install smoke
run: bash npm/test/pack-smoke.sh