-
Notifications
You must be signed in to change notification settings - Fork 11
408 lines (336 loc) · 14.4 KB
/
Copy pathci.yaml
File metadata and controls
408 lines (336 loc) · 14.4 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
name: CI
on:
pull_request:
branches: [main]
merge_group:
workflow_dispatch:
jobs:
frontend-test:
if: (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'release-please--')) || github.event_name == 'merge_group'
runs-on: depot-ubuntu-24.04-8
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@v5
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "24"
cache: "pnpm"
cache-dependency-path: dashboard/pnpm-lock.yaml
- name: Install dependencies
working-directory: ./dashboard
run: pnpm install --frozen-lockfile
- name: Install just
uses: extractions/setup-just@v3
- name: Run frontend CI pipeline
id: ci-step
run: |
START_TIME=$(date +%s)
just ci ts
END_TIME=$(date +%s)
CI_DURATION=$((END_TIME - START_TIME))
echo "ci_duration=$CI_DURATION" >> $GITHUB_OUTPUT
echo "Frontend CI pipeline completed in ${CI_DURATION}s"
# Report metrics to Somnial
curl -X POST "https://charts.somnial.co/doubleword-control-layer/frontend-ci-duration?value=${CI_DURATION}" || true
- name: Extract frontend coverage metrics
id: coverage-step
working-directory: ./dashboard
run: |
# Extract coverage from already generated coverage files (from 'just ci ts')
echo "Checking for coverage files..."
ls -la coverage/ || echo "Coverage directory not found"
if [ -f coverage/lcov.info ]; then
echo "Found lcov.info, extracting coverage..."
# Parse coverage/lcov.info using awk (clean and always available)
COVERAGE=$(awk '
/^LF:/ { lf += $2 }
/^LH:/ { lh += $2 }
END {
if (lf > 0)
printf "%.2f", (lh * 100) / lf
else
print "0"
}
' FS=: coverage/lcov.info)
echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT
echo "Frontend code coverage: ${COVERAGE}%"
# Report coverage to Somnial
curl -X POST "https://charts.somnial.co/doubleword-control-layer/frontend-code-coverage?value=${COVERAGE}" || true
else
echo "No coverage report found at coverage/lcov.info - coverage may not have been generated"
echo "coverage=0" >> $GITHUB_OUTPUT
fi
- name: Calculate bundle metrics
id: build-step
run: |
cd dashboard
# Calculate bundle metrics from the build output
TOTAL_SIZE=$(find dist -name "*.js" -o -name "*.css" | xargs wc -c | tail -1 | awk '{print $1}')
GZIPPED_SIZE=$(find dist -name "*.js" -o -name "*.css" | xargs gzip -c | wc -c)
echo "total_size=$TOTAL_SIZE" >> $GITHUB_OUTPUT
echo "gzipped_size=$GZIPPED_SIZE" >> $GITHUB_OUTPUT
echo "Bundle: ${TOTAL_SIZE} bytes (${GZIPPED_SIZE} gzipped)"
# Report bundle metrics to Somnial
curl -X POST "https://charts.somnial.co/doubleword-control-layer/total-bundle-size?value=${TOTAL_SIZE}" || true
curl -X POST "https://charts.somnial.co/doubleword-control-layer/gzipped-bundle-size?value=${GZIPPED_SIZE}" || true
backend-test:
if: (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'release-please--')) || github.event_name == 'merge_group'
runs-on: depot-ubuntu-24.04-16
# Postgres is started manually below (not via `services:`) so we can pass
# `-c max_connections=300` — the 16-core runner runs `#[sqlx::test]`
# tests with high parallelism, and each one opens its own pools + underway
# PgListener connections, so the default 100 is insufficient.
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Start Postgres
run: |
docker run -d --name postgres \
-e POSTGRES_DB=test \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_HOST_AUTH_METHOD=trust \
-p 5432:5432 \
postgres:latest \
-c max_connections=300
for _ in $(seq 1 30); do
docker exec postgres pg_isready -U postgres && break
sleep 1
done
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools-preview
# used only for caching binary installs
- uses: Swatinem/rust-cache@v2
- name: Install just
uses: extractions/setup-just@v3
- name: Install sqlx-cli and cargo-llvm-cov
run: |
# Pin sqlx-cli to the 0.8.x line to match the `sqlx` library version.
# sqlx-cli 0.9.0 stopped auto-loading the per-crate `.env`, so an
# unpinned install made `cargo sqlx prepare --check` fail to find
# DATABASE_URL. Keep the CLI and library majors in lockstep.
cargo install sqlx-cli --version '^0.8' --no-default-features --features native-tls,postgres --locked
cargo install cargo-llvm-cov --locked
- name: Run backend CI pipeline
id: ci-step
env:
DB_PASS: postgres
run: |
START_TIME=$(date +%s)
just ci rust -- -D warnings
END_TIME=$(date +%s)
CI_DURATION=$((END_TIME - START_TIME))
echo "ci_duration=$CI_DURATION" >> $GITHUB_OUTPUT
echo "Rust CI pipeline completed in ${CI_DURATION}s"
# Report metrics to Somnial
curl -X POST "https://charts.somnial.co/doubleword-control-layer/backend-ci-duration?value=${CI_DURATION}" || true
- name: Extract backend coverage metrics
id: coverage-step
working-directory: ./dwctl
run: |
# Extract coverage from already generated lcov.info file (from 'just ci rust')
echo "Checking for coverage files..."
ls -la lcov.info || echo "lcov.info not found"
if [ -f lcov.info ]; then
echo "Found lcov.info, parsing coverage data..."
# Parse lcov.info using awk (clean and always available)
COVERAGE=$(awk '
/^LF:/ { lf += $2 }
/^LH:/ { lh += $2 }
END {
if (lf > 0)
printf "%.2f", (lh * 100) / lf
else
print "0"
}
' FS=: lcov.info)
echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT
echo "Backend code coverage: ${COVERAGE}%"
# Report coverage to Somnial
curl -X POST "https://charts.somnial.co/doubleword-control-layer/backend-code-coverage?value=${COVERAGE}" || true
else
echo "No coverage report found at lcov.info - coverage may not have been generated"
echo "coverage=0" >> $GITHUB_OUTPUT
fi
build:
if: (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'release-please--')) || github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch'
runs-on: depot-ubuntu-24.04
permissions:
contents: read
packages: write
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v6
env:
DOCKER_METADATA_PR_HEAD_SHA: true
with:
images: ghcr.io/doublewordai/control-layer
sep-tags: "\n"
tags: |
type=sha,prefix=sha-
type=raw,value=manual,enable=${{ github.event_name == 'workflow_dispatch' }}
- name: Build and push all images
id: build-step
run: |
START_TIME=$(date +%s)
echo "start_time=$START_TIME" >> $GITHUB_OUTPUT
echo "Starting Docker build at $(date)"
- name: Build and push with Depot
uses: depot/build-push-action@v1
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
sbom: true
provenance: true
- name: Record build time
id: build-time-step
run: |
END_TIME=$(date +%s)
BUILD_DURATION=$((END_TIME - ${{ steps.build-step.outputs.start_time }}))
echo "build_duration=$BUILD_DURATION" >> $GITHUB_OUTPUT
echo "Docker build completed in ${BUILD_DURATION}s"
# Report build time to Somnial
curl -X POST "https://charts.somnial.co/doubleword-control-layer/docker-build-duration?value=${BUILD_DURATION}" || true
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
security-scan:
if: (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'release-please--')) || github.event_name == 'merge_group'
runs-on: depot-ubuntu-24.04
needs: build
permissions:
contents: read
packages: read
security-events: write # For uploading SARIF results
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Install Grype
run: |
curl -sSfL https://get.anchore.io/grype | sudo sh -s -- -b /usr/local/bin
grype version
- name: Security scan with Grype
id: security-scan
run: |
# Define image tags using the built images
TAG_LIST="${{ needs.build.outputs.image-tag }}"
# Extract just the SHA tag for our scan
TAG=$(echo "$TAG_LIST" | head -n1 | cut -d':' -f2)
CONTROL_LAYER_TAG="ghcr.io/doublewordai/control-layer:$TAG"
echo "Scanning images for security vulnerabilities..."
echo "Tag: $TAG"
# Parse results and calculate metrics
calculate_vulns() {
local file=$1
local severity=$2
jq -r --arg sev "$severity" '[.matches[] | select(.vulnerability.severity == $sev)] | length' "$file"
}
# Scan control-layer image
echo "Scanning control-layer image: $CONTROL_LAYER_TAG"
grype "$CONTROL_LAYER_TAG" --output json --file control-layer-vulnerabilities.json
CONTROL_LAYER_CRITICAL=$(calculate_vulns control-layer-vulnerabilities.json "Critical")
CONTROL_LAYER_HIGH=$(calculate_vulns control-layer-vulnerabilities.json "High")
# Console output
echo "Security scan completed:"
echo " Critical vulnerabilities: $CONTROL_LAYER_CRITICAL"
echo " High vulnerabilities: $CONTROL_LAYER_HIGH"
# Set outputs for metrics reporting
echo "total_critical=$CONTROL_LAYER_CRITICAL" >> $GITHUB_OUTPUT
echo "total_high=$CONTROL_LAYER_HIGH" >> $GITHUB_OUTPUT
- name: Report vulnerability counts to Somnial
if: always()
run: |
# Report high and critical vulnerability counts to Somnial
CRITICAL="${{ steps.security-scan.outputs.total_critical || '0' }}"
HIGH="${{ steps.security-scan.outputs.total_high || '0' }}"
echo "Reporting vulnerability counts to Somnial..."
echo " Critical vulnerabilities: ${CRITICAL}"
echo " High vulnerabilities: ${HIGH}"
curl -X POST "https://charts.somnial.co/doubleword-control-layer/critical-vulnerabilities?value=${CRITICAL}" || echo "Failed to report critical vulnerabilities"
curl -X POST "https://charts.somnial.co/doubleword-control-layer/high-vulnerabilities?value=${HIGH}" || echo "Failed to report high vulnerabilities"
e2e-test-docker:
if: (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'release-please--')) || github.event_name == 'merge_group'
runs-on: depot-ubuntu-24.04
needs: build
permissions:
contents: read
packages: read
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Install just
uses: extractions/setup-just@v3
- name: Install pnpm
uses: pnpm/action-setup@v5
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "24"
cache: "pnpm"
cache-dependency-path: dashboard/pnpm-lock.yaml
- name: Install dashboard dependencies
working-directory: ./dashboard
run: pnpm install --frozen-lockfile
- name: Install Playwright browsers
working-directory: ./dashboard
run: npx playwright install chromium --only-shell
- name: Install hurl
# Pinned: hurl 8.0.0 (2026-04-27) ships an RFC 9535 JSONPath rewrite
# that breaks our existing `count` / `nth` patterns on filter results
# that yield 0 or 1 items (see tests/perms/{models,users,requests,
# transactions}.hurl and tests/authenticated/admin-crud-group.hurl).
# Hold at 7.x until the assertions are migrated to RFC 9535 semantics.
uses: gacts/install-hurl@v1
with:
version: 7.1.0
- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Compose
uses: docker/setup-compose-action@v2
# Run end to end docker-compose tests
- name: Run Docker end-to-end tests
id: e2e-step
run: |
# Extract first image from newline-separated list
IMAGE_LIST="${{ needs.build.outputs.image-tag }}"
export IMAGE=$(echo "$IMAGE_LIST" | head -n1)
echo "Using image: $IMAGE"
START_TIME=$(date +%s)
just test docker
END_TIME=$(date +%s)
E2E_DURATION=$((END_TIME - START_TIME))
echo "e2e_duration=$E2E_DURATION" >> $GITHUB_OUTPUT
echo "Docker E2E tests completed in ${E2E_DURATION}s"
# Report metrics to Somnial
curl -X POST "https://charts.somnial.co/doubleword-control-layer/e2e-docker-duration?value=${E2E_DURATION}" || true