66 - ' **'
77 workflow_dispatch :
88
9+ env :
10+ DOCKER_METADATA_SET_OUTPUT_ENV : ' true'
11+
912jobs :
1013 build :
11- runs-on : ubuntu-latest
14+ runs-on : ${{ matrix.runner }}
1215 outputs :
13- build-image : ${{ steps.build-meta.outputs.tags }}
16+ image-app-arm64 : ${{ steps.gen-output.outputs.image-app-arm64 }}
17+ image-app-x64 : ${{ steps.gen-output.outputs.image-app-x64 }}
18+ image-dev-arm64 : ${{ steps.gen-output.outputs.image-development-arm64 }}
19+ image-dev-x64 : ${{ steps.gen-output.outputs.image-development-x64 }}
20+ strategy :
21+ fail-fast : false
22+ matrix :
23+ runner :
24+ - ubuntu-24.04
25+ - ubuntu-24.04-arm
26+ target :
27+ - app
28+ - development
1429 steps :
1530 - name : Checkout code
1631 uses : actions/checkout@v4
1732
18- - name : Set up QEMU
19- uses : docker/setup-qemu-action@v3
20-
2133 - name : Set up Docker Buildx
2234 uses : docker/setup-buildx-action@v3
2335
@@ -28,48 +40,58 @@ jobs:
2840 username : ${{ github.actor }}
2941 password : ${{ secrets.GITHUB_TOKEN }}
3042
31- - name : Produce the build image tag
32- id : build- meta
43+ - name : Docker meta
44+ id : meta
3345 uses : docker/metadata-action@v5
3446 with :
3547 images : ghcr.io/${{ github.repository }}
36- tags : type=sha,suffix=-build-${{ github.run_id }}_${{ github.run_attempt }}
48+ # note Specifies a single tag to ensure the default doesn't add more than one.
49+ # The actual tag is not used, this is just used to sanitize the registry name
50+ # and produce labels.
51+ tags : type=sha
52+
53+ - name : Sanitize registry repository name
54+ id : get-reg
55+ run : |
56+ echo "registry=$(echo '${{ steps.meta.outputs.tags }}' | cut -f1 -d:)" | tee -a "$GITHUB_OUTPUT"
3757
38- - name : Build and push the untested image
58+ - name : Build/push the arch-specific image
59+ id : build
3960 uses : docker/build-push-action@v6
4061 with :
41- push : true
42- labels : ${{ steps.build-meta.outputs.labels }}
43- tags : ${{ steps.build-meta.outputs.tags }}
44- provenance : true
45- sbom : true
46- target : development
62+ # @todo GHA caching needs tuning, these tend not to hit. Perhaps switch to type=registry?
4763 cache-from : type=gha
48- cache-to : type=gha
64+ cache-to : type=gha,mode=max
65+ labels : ${{ steps.meta.outputs.labels }}
66+ provenance : mode=max
67+ sbom : true
68+ tags : ${{ steps.get-reg.outputs.registry }}
69+ outputs : type=image,push-by-digest=true,push=true
70+ target : ${{ matrix.target }}
71+
72+ - name : Write arch-specific image digest to outputs
73+ id : gen-output
74+ run : |
75+ echo "image-${{ matrix.target }}-${RUNNER_ARCH,,}=${{ steps.get-reg.outputs.registry }}@${{ steps.build.outputs.digest }}" | tee -a "$GITHUB_OUTPUT"
4976
50- test :
51- runs-on : ubuntu-latest
77+ merge :
78+ runs-on : ubuntu-24.04
5279 needs :
5380 - build
54- strategy :
55- fail-fast : false
56- matrix :
57- test :
58- - mypy .
59- - pydoclint .
60- # pylint returns error codes if the checks fail
61- # https://pylint.readthedocs.io/en/latest/user_guide/usage/run.html#exit-codes
62- - pylint -v .
63- - python -m unittest -v
6481 env :
65- COMPOSE_FILE : docker-compose.yml:docker-compose.ci.yml
66- DOCKER_APP_IMAGE : ${{ needs.build.outputs.build-image }}
82+ DOCKER_APP_IMAGE_ARM64 : ${{ needs.build.outputs.image-app-arm64 }}
83+ DOCKER_APP_IMAGE_X64 : ${{ needs.build.outputs.image-app-x64 }}
84+ DOCKER_DEV_IMAGE_ARM64 : ${{ needs.build.outputs.image-dev-arm64 }}
85+ DOCKER_DEV_IMAGE_X64 : ${{ needs.build.outputs.image-dev-x64 }}
86+ outputs :
87+ app-image : ${{ steps.app-meta.outputs.tags }}
88+ dev-image : ${{ steps.dev-meta.outputs.tags }}
6789 steps :
6890 - name : Checkout code
6991 uses : actions/checkout@v4
7092
71- - name : Set up Docker Compose
72- uses : docker/setup-compose -action@v1
93+ - name : Set up Docker Buildx
94+ uses : docker/setup-buildx -action@v3
7395
7496 - name : Login to GitHub Container Registry
7597 uses : docker/login-action@v3
@@ -78,35 +100,120 @@ jobs:
78100 username : ${{ github.actor }}
79101 password : ${{ secrets.GITHUB_TOKEN }}
80102
81- - name : Copy environment file
82- run : cp env.example .env
83-
84- - name : Set ARTIFACTS_DIR
85- run : echo "ARTIFACTS_DIR=${RUNNER_TEMP}/artifacts" >> $GITHUB_ENV
103+ - name : Generate tag for the dev image
104+ id : dev-meta
105+ uses : docker/metadata-action@v5
106+ with :
107+ images : ghcr.io/${{ github.repository }}
108+ tags : |
109+ type=sha,suffix=-build-${{ github.run_id }}_${{ github.run_attempt }}-dev
86110
87- - name : Create the artifacts directory
88- run : mkdir -p "$ARTIFACTS_DIR"
111+ - name : Push the multi-platform dev image
112+ run : |
113+ docker buildx imagetools create \
114+ --tag "$DOCKER_METADATA_OUTPUT_TAGS" \
115+ "$DOCKER_DEV_IMAGE_ARM64" "$DOCKER_DEV_IMAGE_X64"
89116
90- - name : Record start time
91- run : TEST_START=`date +%s` >> $GITHUB_ENV
117+ - name : Generate tag for the app image
118+ id : app-meta
119+ uses : docker/metadata-action@v5
120+ with :
121+ images : ghcr.io/${{ github.repository }}
122+ tags : |
123+ type=sha,suffix=-build-${{ github.run_id }}_${{ github.run_attempt }}
92124
93- - name : Run the test command
125+ - name : Push the multi-platform app image
126+ run : |
127+ docker buildx imagetools create \
128+ --tag "$DOCKER_METADATA_OUTPUT_TAGS" \
129+ "$DOCKER_APP_IMAGE_ARM64" "$DOCKER_APP_IMAGE_X64"
130+
131+ test-mypy :
132+ runs-on : ubuntu-24.04
133+ needs : merge
134+ container :
135+ image : ${{ needs.merge.outputs.dev-image }}
136+ defaults :
137+ run :
138+ working-directory : /app
139+ steps :
140+ - name : Run mypy
141+ run : mypy .
142+
143+ test-pydoclint :
144+ runs-on : ubuntu-24.04
145+ needs : merge
146+ container :
147+ image : ${{ needs.merge.outputs.dev-image }}
148+ defaults :
149+ run :
150+ working-directory : /app
151+ steps :
152+ - name : Run pydoclint
153+ run : pydoclint .
154+
155+ # pylint returns error codes if the checks fail
156+ # @see https://pylint.readthedocs.io/en/latest/user_guide/usage/run.html#exit-codes
157+ test-pylint :
158+ runs-on : ubuntu-24.04
159+ needs : merge
160+ container :
161+ image : ${{ needs.merge.outputs.dev-image }}
162+ defaults :
163+ run :
164+ working-directory : /app
165+ steps :
166+ - name : Run pylint
167+ run : pylint -v .
168+
169+ test-unittest :
170+ runs-on : ubuntu-24.04
171+ needs : merge
172+ container :
173+ image : ${{ needs.merge.outputs.dev-image }}
174+ defaults :
175+ run :
176+ working-directory : /app
177+ steps :
178+ - name : Run unit tests
94179 run : |
95- docker compose run --no-deps --rm app ${{ matrix.test }}
180+ python -m unittest .
181+
182+ test-startup :
183+ runs-on : ubuntu-24.04
184+ needs : merge
185+ env :
186+ COMPOSE_FILE : docker-compose.yml:docker-compose.ci.yml
187+ DOCKER_APP_IMAGE : ${{ needs.merge.outputs.app-image }}
188+ steps :
189+ - name : Checkout code
190+ uses : actions/checkout@v4
191+
192+ - name : Set up Docker Compose
193+ uses : docker/setup-compose-action@v1
96194
97- - name : Upload test report
98- if : ${{ always() }}
99- uses : actions/upload-artifact@v4
195+ - name : Login to GitHub Container Registry
196+ uses : docker/login-action@v3
100197 with :
101- name : Test Report - ${{ matrix.test }} (${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }})
102- path : ${{ env.ARTIFACTS_DIR }}
103- if-no-files-found : warn
198+ registry : ghcr.io
199+ username : ${{ github.actor }}
200+ password : ${{ secrets.GITHUB_TOKEN }}
201+
202+ - name : Run the test script
203+ run : |
204+ docker compose up --detach --wait
104205
105206 push :
106- runs-on : ubuntu-latest
207+ runs-on : ubuntu-24.04
107208 needs :
108- - build
109- - test
209+ - merge
210+ - test-mypy
211+ - test-pydoclint
212+ - test-pylint
213+ - test-startup
214+ - test-unittest
215+ env :
216+ DOCKER_APP_IMAGE : ${{ needs.merge.outputs.app-image }}
110217 steps :
111218 - name : Checkout code
112219 uses : actions/checkout@v4
@@ -129,10 +236,7 @@ jobs:
129236 type=raw,value=latest,enable={{is_default_branch}}
130237
131238 - name : Retag and push the image
132- uses : docker/build-push-action@v6
133- with :
134- push : true
135- labels : ${{ steps.branch-meta.outputs.labels }}
136- tags : ${{ steps.branch-meta.outputs.tags }}
137- cache-from : type=registry,ref=${{ needs.build.outputs.build-image }}
138- target : app
239+ run : |
240+ docker pull "$DOCKER_APP_IMAGE"
241+ echo "$DOCKER_METADATA_OUTPUT_TAGS" | tr ' ' '\n' | xargs -n1 docker tag "$DOCKER_APP_IMAGE"
242+ docker push --all-tags "$(echo "$DOCKER_APP_IMAGE" | cut -f1 -d:)"
0 commit comments