Skip to content

Commit ab5d065

Browse files
perf(ci): reduce verification latency and runner usage
1 parent dd328ec commit ab5d065

138 files changed

Lines changed: 3914 additions & 2883 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/huge-seals-do.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
---
3+
4+
No release note: this changes CI execution only.

.github/actions/setup-caches/action.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ runs:
1313
using: "composite"
1414
steps:
1515
- name: Set up JDK 21
16-
if: contains(fromJSON('["application-server-unit", "application-server-arch", "application-server-integration", "application-server-quality", "webapp-e2e", "openapi-validation", "database-validation"]'), inputs.cache-type)
16+
if: startsWith(inputs.cache-type, 'application-server-') || contains(fromJSON('["webapp-e2e", "server-contracts"]'), inputs.cache-type)
1717
uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
1818
with:
1919
distribution: "temurin"
@@ -26,7 +26,7 @@ runs:
2626
# then activates on the present directory and skips codegen, causing compile
2727
# failures because the cached classes don't match the current sources. Codegen
2828
# runs in ~5s — much cheaper than that failure mode.
29-
if: contains(fromJSON('["application-server-unit", "application-server-arch", "application-server-integration", "application-server-quality", "webapp-e2e", "openapi-validation", "database-validation"]'), inputs.cache-type)
29+
if: startsWith(inputs.cache-type, 'application-server-') || contains(fromJSON('["webapp-e2e", "server-contracts"]'), inputs.cache-type)
3030
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
3131
with:
3232
path: |

.github/workflows/ci-docker-build.yml

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,21 @@ on:
2525
required: false
2626
type: string
2727
default: "true"
28+
release_pin_fetcher_changed:
29+
description: "Whether release-pin-fetcher image inputs changed"
30+
required: false
31+
type: string
32+
default: "true"
33+
postgres_image_changed:
34+
description: "Whether the PostgreSQL image inputs changed"
35+
required: false
36+
type: string
37+
default: "true"
2838

2939
outputs:
3040
application-server-published:
3141
description: "Whether an application-server image exists at this commit"
32-
value: ${{ jobs.application-server-build.result == 'success' }}
42+
value: ${{ jobs.application-server-build.outputs.manifest-digest != '' || jobs.tag-unchanged-images.outputs.application-server-published == 'true' }}
3343

3444
jobs:
3545
webapp-build:
@@ -51,6 +61,7 @@ jobs:
5161
tags: |
5262
${{ github.ref_name }}
5363
${{ github.sha }}
64+
${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || '' }}
5465
ci-${{ github.run_number }}
5566
${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || '' }}
5667
labels: |
@@ -62,12 +73,9 @@ jobs:
6273
6374
application-server-build:
6475
name: "App Server"
65-
# Every code PR receives an immutable app-server tag. If server code did not change, this is
66-
# intentionally a cached rebuild of main; previews can then use SOURCE_COMMIT uniformly instead
67-
# of racing on a global IMAGE_TAG or failing because the commit tag does not exist.
6876
if: >-
6977
inputs.should_skip != 'true' &&
70-
(inputs.application_server_changed == 'true' || github.event_name == 'pull_request')
78+
(inputs.application_server_changed == 'true' || github.event_name != 'pull_request')
7179
uses: ./.github/workflows/reusable-docker-build.yml
7280
with:
7381
image-name: "ls1intum/hephaestus/application-server"
@@ -127,7 +135,9 @@ jobs:
127135
128136
release-pin-fetcher-build:
129137
name: "Release Pin Fetcher"
130-
if: inputs.should_skip != 'true'
138+
if: >-
139+
inputs.should_skip != 'true' &&
140+
(inputs.release_pin_fetcher_changed == 'true' || github.event_name != 'pull_request')
131141
uses: ./.github/workflows/reusable-docker-build.yml
132142
with:
133143
image-name: "ls1intum/hephaestus/release-pin-fetcher"
@@ -139,6 +149,7 @@ jobs:
139149
tags: |
140150
${{ github.ref_name }}
141151
${{ github.sha }}
152+
${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || '' }}
142153
ci-${{ github.run_number }}
143154
${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || '' }}
144155
labels: |
@@ -150,7 +161,9 @@ jobs:
150161
151162
postgres-build:
152163
name: "Postgres (pg_partman)"
153-
if: inputs.should_skip != 'true'
164+
if: >-
165+
inputs.should_skip != 'true' &&
166+
(inputs.postgres_image_changed == 'true' || github.event_name != 'pull_request')
154167
uses: ./.github/workflows/reusable-docker-build.yml
155168
with:
156169
image-name: "ls1intum/hephaestus/postgres"
@@ -162,6 +175,7 @@ jobs:
162175
tags: |
163176
${{ github.ref_name }}
164177
${{ github.sha }}
178+
${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || '' }}
165179
ci-${{ github.run_number }}
166180
${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || '' }}
167181
labels: |
@@ -170,3 +184,71 @@ jobs:
170184
org.opencontainers.image.vendor=AET TUM
171185
org.opencontainers.image.licenses=MIT
172186
hephaestus.component=postgres
187+
188+
# Preview stacks use one commit tag for every component. Point unchanged components at their
189+
# verified base-commit digest instead of rebuilding identical content under a new name.
190+
tag-unchanged-images:
191+
name: "Tag unchanged images"
192+
if: >-
193+
inputs.should_skip != 'true' && github.event_name == 'pull_request' &&
194+
(inputs.webapp_changed != 'true' || inputs.application_server_changed != 'true' ||
195+
inputs.agent_images_changed != 'true' || inputs.release_pin_fetcher_changed != 'true' ||
196+
inputs.postgres_image_changed != 'true')
197+
runs-on: ubuntu-latest
198+
outputs:
199+
application-server-published: ${{ steps.tag.outputs.application-server-published }}
200+
permissions:
201+
packages: write
202+
attestations: read
203+
contents: read
204+
steps:
205+
- name: Log in to Container Registry
206+
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
207+
with:
208+
registry: ghcr.io
209+
username: ${{ github.actor }}
210+
password: ${{ secrets.GITHUB_TOKEN }}
211+
212+
- name: Verify and tag unchanged images
213+
id: tag
214+
env:
215+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
216+
WEBAPP_CHANGED: ${{ inputs.webapp_changed }}
217+
APPLICATION_SERVER_CHANGED: ${{ inputs.application_server_changed }}
218+
AGENT_IMAGES_CHANGED: ${{ inputs.agent_images_changed }}
219+
RELEASE_PIN_FETCHER_CHANGED: ${{ inputs.release_pin_fetcher_changed }}
220+
POSTGRES_IMAGE_CHANGED: ${{ inputs.postgres_image_changed }}
221+
PR_NUMBER: ${{ github.event.number }}
222+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
223+
run: |
224+
set -euo pipefail
225+
226+
tag_image() {
227+
local image="ghcr.io/ls1intum/hephaestus/$1"
228+
local source="$image:$BASE_SHA"
229+
local ref_tag="${GITHUB_REF_NAME//\//-}"
230+
local digest
231+
digest=$(docker buildx imagetools inspect --format '{{.Manifest.Digest}}' "$source")
232+
if [[ ! "$digest" =~ ^sha256:[a-f0-9]{64}$ ]]; then
233+
echo "::error::Invalid digest for $source: ${digest:-<empty>}"
234+
return 1
235+
fi
236+
local verified="$image@$digest"
237+
gh attestation verify "oci://$verified" --owner "${{ github.repository_owner }}"
238+
docker buildx imagetools create \
239+
--tag "$image:$ref_tag" \
240+
--tag "$image:${{ github.sha }}" \
241+
--tag "$image:${{ github.event.pull_request.head.sha }}" \
242+
--tag "$image:ci-${{ github.run_number }}" \
243+
--tag "$image:pr-$PR_NUMBER" \
244+
"$verified"
245+
}
246+
247+
[ "$WEBAPP_CHANGED" = "true" ] || tag_image webapp
248+
if [ "$APPLICATION_SERVER_CHANGED" != "true" ]; then
249+
tag_image application-server
250+
echo "application-server-published=true" >> "$GITHUB_OUTPUT"
251+
fi
252+
[ "$AGENT_IMAGES_CHANGED" = "true" ] || tag_image agent-pi
253+
[ "$RELEASE_PIN_FETCHER_CHANGED" = "true" ] || tag_image release-pin-fetcher
254+
[ "$POSTGRES_IMAGE_CHANGED" = "true" ] || tag_image postgres

.github/workflows/ci-profile.yml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: CI profile
2+
3+
on:
4+
schedule:
5+
- cron: "17 3 * * 1"
6+
workflow_dispatch:
7+
8+
concurrency:
9+
group: ci-profile-${{ github.ref }}
10+
cancel-in-progress: true
11+
12+
jobs:
13+
server-integration:
14+
name: "Server integration profile"
15+
runs-on: ubuntu-latest
16+
timeout-minutes: 30
17+
permissions:
18+
contents: read
19+
steps:
20+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
21+
with:
22+
persist-credentials: false
23+
24+
- uses: ./.github/actions/setup-pnpm-node
25+
26+
- uses: ./.github/actions/setup-caches
27+
with:
28+
cache-type: application-server-integration
29+
os: ${{ runner.os }}
30+
31+
- name: Restore performance history
32+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
33+
with:
34+
path: ci-profile-history
35+
key: ci-profile-history-${{ github.run_id }}
36+
restore-keys: ci-profile-history-
37+
38+
- name: Run integration suite with JFR
39+
run: |
40+
mkdir -p ci-metrics
41+
/usr/bin/time -v -o ci-metrics/server-integration-resource.txt \
42+
pnpm run test:server:integration \
43+
-DargLine=-XX:StartFlightRecording=filename=target/integration-profile.jfr,settings=profile,dumponexit=true \
44+
-Dlogging.level.org.springframework.test.context.cache=DEBUG \
45+
2>&1 | tee ci-metrics/server-integration.log
46+
47+
- name: Summarize integration results
48+
if: always()
49+
continue-on-error: true
50+
run: >-
51+
pnpm run summarize:test-results "Server Integration Profile"
52+
server/target/surefire-reports ci-metrics/server-integration-profile.json
53+
ci-metrics/server-integration.log ci-metrics/server-integration-resource.txt
54+
55+
- name: Enforce performance budgets
56+
if: success()
57+
run: |
58+
node scripts/check-ci-performance.ts ci-metrics/server-integration-profile.json ci-profile-history
59+
mkdir -p ci-profile-history
60+
cp ci-metrics/server-integration-profile.json "ci-profile-history/${{ github.run_id }}.json"
61+
62+
- name: Extract database cleanup timings
63+
if: always()
64+
continue-on-error: true
65+
run: >-
66+
jfr print --events hephaestus.test.DatabaseCleanup
67+
server/target/integration-profile.jfr
68+
> ci-metrics/database-cleanup.jfr.txt
69+
70+
- name: Upload profile
71+
if: always()
72+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
73+
with:
74+
name: server-integration-profile-${{ github.run_id }}
75+
path: |
76+
server/target/integration-profile.jfr
77+
server/target/surefire-reports
78+
ci-metrics/server-integration-profile.json
79+
ci-metrics/server-integration.log
80+
ci-metrics/server-integration-resource.txt
81+
ci-metrics/database-cleanup.jfr.txt
82+
if-no-files-found: warn
83+
retention-days: 14

0 commit comments

Comments
 (0)