1- # Build and publish a Docker image .
1+ # Build and publish Docker images .
22#
33# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a local
44# artifacts job within `cargo-dist`.
55#
66# Adapted from https://github.com/astral-sh/ty/blob/main/.github/workflows/build-docker.yml
7- name : " Build Docker image "
7+ name : " Build Docker images "
88
99on :
1010 workflow_call :
1515
1616env :
1717 PREK_BASE_IMG : ghcr.io/${{ github.repository_owner }}/prek
18+ ALPINE_VERSION : " 3.24"
1819
19- permissions :
20- contents : read
21- packages : write # zizmor: ignore[excessive-permissions]
20+ permissions : {}
2221
2322jobs :
2423 plan :
2524 runs-on : ubuntu-slim
2625 outputs :
27- publish : ${{ steps.plan.outputs.publish }}
26+ push : ${{ steps.plan.outputs.push }}
27+ tag : ${{ steps.plan.outputs.tag }}
28+ base-tag : ${{ steps.plan.outputs.base-tag }}
29+ floating-tags : ${{ steps.plan.outputs.floating-tags }}
30+ action : ${{ steps.plan.outputs.action }}
2831 steps :
2932 - name : Plan
3033 id : plan
3134 shell : bash
3235 env :
33- HAS_PLAN : ${{ inputs.plan != '' }}
34- HAS_IMPLICIT_TAG : ${{ inputs.plan != '' && fromJson(inputs.plan).announcement_tag_is_implicit }}
36+ DRY_RUN : ${{ inputs.plan == '' || fromJson(inputs.plan).announcement_tag_is_implicit }}
37+ PRERELEASE : ${{ inputs.plan != '' && fromJson(inputs.plan).announcement_is_prerelease }}
38+ TAG : ${{ inputs.plan != '' && fromJson(inputs.plan).announcement_tag }}
3539 run : |
36- [[ "$HAS_PLAN" == "true" && "$HAS_IMPLICIT_TAG" != "true" ]] && publish=1
37-
38- if [[ "$publish" ]]; then
39- echo "publish=true" >> "$GITHUB_OUTPUT"
40+ if [[ "$DRY_RUN" == "false" ]]; then
41+ version="${TAG#v}"
42+ echo "push=true" >> "$GITHUB_OUTPUT"
43+ echo "tag=${version}" >> "$GITHUB_OUTPUT"
44+ echo "base-tag=${version}" >> "$GITHUB_OUTPUT"
45+ echo "action=Build and publish" >> "$GITHUB_OUTPUT"
46+ if [[ "$PRERELEASE" == "true" ]]; then
47+ echo "floating-tags=false" >> "$GITHUB_OUTPUT"
48+ else
49+ echo "floating-tags=true" >> "$GITHUB_OUTPUT"
50+ fi
4051 else
41- echo "publish=false" >> "$GITHUB_OUTPUT"
52+ echo "push=false" >> "$GITHUB_OUTPUT"
53+ echo "tag=dry-run" >> "$GITHUB_OUTPUT"
54+ echo "base-tag=latest" >> "$GITHUB_OUTPUT"
55+ echo "floating-tags=false" >> "$GITHUB_OUTPUT"
56+ echo "action=Build" >> "$GITHUB_OUTPUT"
4257 fi
4358
44- docker-build :
45- name : Build Docker image ghcr.io/j178/prek for ${{ matrix.platform }}
59+ docker-publish-base :
60+ name : ${{ needs.plan.outputs.action }} Docker image
4661 needs : plan
4762 runs-on : ubuntu-latest
4863 environment :
4964 name : release
50- strategy :
51- fail-fast : false
52- matrix :
53- platform :
54- - linux/amd64
55- - linux/arm64
65+ permissions :
66+ contents : read
67+ packages : write
68+ id-token : write
69+ attestations : write
5670 steps :
5771 - uses : actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
5872 with :
@@ -61,19 +75,19 @@ jobs:
6175 - uses : docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
6276
6377 - uses : docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
64- if : ${{ needs.plan.outputs.publish == 'true' }}
78+ if : ${{ needs.plan.outputs.push == 'true' }}
6579 with :
6680 registry : ghcr.io
6781 username : ${{ github.repository_owner }}
6882 password : ${{ secrets.GITHUB_TOKEN }}
6983
7084 - name : Check tag consistency
71- if : ${{ needs.plan.outputs.publish == 'true' }}
85+ if : ${{ needs.plan.outputs.push == 'true' }}
7286 env :
73- TAG : ${{ inputs .plan != '' && fromJson(inputs.plan).announcement_tag || 'dry-run' }}
87+ TAG : ${{ needs .plan.outputs.tag }}
7488 run : |
7589 version=$(grep -m 1 "^version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
76- if [ "${TAG}" != "v ${version}" ]; then
90+ if [ "${TAG}" != "${version}" ]; then
7791 echo "The input tag does not match the version from pyproject.toml:" >&2
7892 echo "${TAG}" >&2
7993 echo "${version}" >&2
@@ -91,114 +105,120 @@ jobs:
91105 images : ${{ env.PREK_BASE_IMG }}
92106 # Defining this makes sure the org.opencontainers.image.version OCI label becomes the actual release version and not the branch name
93107 tags : |
94- type=raw,value=dry-run,enable=${{ needs.plan.outputs.publish != 'true' }}
95- type=semver,pattern={{ raw }},value=${{ inputs.plan != '' && fromJson(inputs.plan).announcement_tag || 'dry-run' }},enable=${{ needs.plan.outputs.publish == 'true' }}
108+ type=raw,value=dry-run,enable=${{ needs.plan.outputs.push == 'false' }}
109+ type=semver,pattern={{ raw }},value=${{ needs.plan.outputs.tag }},enable=${{ needs.plan.outputs.push == 'true' }}
110+ type=semver,pattern={{ major }}.{{ minor }},value=${{ needs.plan.outputs.tag }},enable=${{ needs.plan.outputs.push == 'true' }}
96111
97- - name : Normalize Platform Pair (replace / with -)
98- run : |
99- platform=${{ matrix.platform }}
100- echo "PLATFORM_TUPLE=${platform//\//-}" >> "$GITHUB_ENV"
101-
102- # Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
103- - name : Build and push by digest
112+ - name : Build and push
104113 id : build
105114 uses : docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
106115 with :
107116 context : .
108- platforms : ${{ matrix.platform }}
109- cache-from : type=gha,scope=prek-${{ env.PLATFORM_TUPLE }}
110- cache-to : type=gha,mode=min,scope=prek-${{ env.PLATFORM_TUPLE }}
117+ platforms : linux/amd64,linux/arm64
118+ cache-from : type=gha,scope=prek
119+ cache-to : type=gha,mode=min,scope=prek
120+ push : ${{ needs.plan.outputs.push }}
121+ tags : ${{ steps.meta.outputs.tags }}
111122 labels : ${{ steps.meta.outputs.labels }}
112- outputs : type=image,name=${{ env.PREK_BASE_IMG }},push-by-digest=true,name-canonical=true,push=${{ needs.plan.outputs.publish == 'true' }}
113-
114- - name : Export digests
115- env :
116- digest : ${{ steps.build.outputs.digest }}
117- run : |
118- mkdir -p /tmp/digests
119- touch "/tmp/digests/${digest#sha256:}"
123+ annotations : ${{ steps.meta.outputs.annotations }}
124+ provenance : mode=max
125+ sbom : true
120126
121- - name : Upload digests
122- uses : actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
127+ - name : Generate artifact attestation
128+ if : ${{ needs.plan.outputs.push == 'true' }}
129+ uses : actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1
123130 with :
124- name : digests-${{ env.PLATFORM_TUPLE }}
125- path : /tmp/digests/*
126- if-no-files-found : error
127- retention-days : 1
131+ subject-name : ${{ env.PREK_BASE_IMG }}
132+ subject-digest : ${{ steps.build.outputs.digest }}
133+ push-to-registry : true
128134
129- docker-publish :
130- name : Publish Docker image (ghcr.io/j178/prek)
135+ docker-publish-alpine :
136+ name : ${{ needs.plan.outputs.action }} Alpine Docker image
131137 runs-on : ubuntu-latest
132138 environment :
133139 name : release
134140 needs :
135141 - plan
136- - docker-build
142+ - docker-publish-base
137143 permissions :
138144 contents : read
139145 packages : write
140146 id-token : write
141147 attestations : write
142- if : ${{ needs.plan.outputs. publish == 'true ' }}
148+ if : ${{ needs.plan.result == 'success' && needs.docker- publish-base.result == 'success ' }}
143149 steps :
144- - name : Download digests
145- uses : actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
150+ - uses : actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
146151 with :
147- path : /tmp/digests
148- pattern : digests-*
149- merge-multiple : true
150-
151- - uses : docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
152+ persist-credentials : false
152153
153- - name : Extract metadata (tags, labels) for Docker
154- id : meta
155- uses : docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
156- env :
157- DOCKER_METADATA_ANNOTATIONS_LEVELS : index
154+ # Installing Alpine packages executes target-architecture binaries.
155+ - uses : docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
158156 with :
159- images : ${{ env.PREK_BASE_IMG }}
160- # Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version
161- tags : |
162- type=raw,value=${{ fromJson(inputs.plan).announcement_tag }}
163- type=semver,pattern=v{{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }}
157+ platforms : arm64
158+
159+ - uses : docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
164160
165161 - uses : docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
162+ if : ${{ needs.plan.outputs.push == 'true' }}
166163 with :
167164 registry : ghcr.io
168165 username : ${{ github.repository_owner }}
169166 password : ${{ secrets.GITHUB_TOKEN }}
170167
171- # Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
172- - name : Create manifest list and push
173- working-directory : /tmp/digests
174- # The jq command expands the docker/metadata json "tags" array entry to `-t tag1 -t tag2 ...` for each tag in the array
175- # The printf will expand the base image with the `<PREK_BASE_IMG>@sha256:<sha256> ...` for each sha256 in the directory
176- # The final command becomes `docker buildx imagetools create -t tag1 -t tag2 ... <PREK_BASE_IMG>@sha256:<sha256_1> <PREK_BASE_IMG>@sha256:<sha256_2> ...`
168+ - name : Generate Alpine Dockerfile
169+ env :
170+ PREK_IMAGE : ${{ format('{0}:{1}', env.PREK_BASE_IMG, needs.plan.outputs.base-tag) }}
177171 run : |
178- # shellcheck disable=SC2046
179- readarray -t lines <<< "$DOCKER_METADATA_OUTPUT_ANNOTATIONS"; annotations=(); for line in "${lines[@]}"; do annotations+=(--annotation "$line"); done
172+ cat <<EOF > Dockerfile.alpine
173+ FROM ${PREK_IMAGE} AS prek
174+ FROM alpine:${ALPINE_VERSION}
175+
176+ RUN apk add --no-cache \
177+ ca-certificates \
178+ git
180179
181- docker buildx imagetools create \
182- "${annotations[@]}" \
183- $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
184- $(printf "${PREK_BASE_IMG}@sha256:%s " *)
180+ COPY --from=prek /prek /usr/local/bin/prek
181+ WORKDIR /io
182+ ENTRYPOINT ["/usr/local/bin/prek"]
183+ EOF
185184
186- - name : Export manifest digest
187- id : manifest-digest
185+ - name : Extract metadata (tags, labels) for Docker
186+ id : meta
187+ uses : docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
188188 env :
189- IMAGE : ${{ env.PREK_BASE_IMG }}
190- VERSION : ${{ steps.meta.outputs.version }}
191- run : |
192- digest="$(
193- docker buildx imagetools inspect \
194- "${IMAGE}:${VERSION}" \
195- --format '{{json .Manifest}}' \
196- | jq -r '.digest'
197- )"
198- echo "digest=${digest}" >> "$GITHUB_OUTPUT"
189+ DOCKER_METADATA_ANNOTATIONS_LEVELS : index
190+ with :
191+ images : ${{ env.PREK_BASE_IMG }}
192+ flavor : latest=false
193+ tags : |
194+ type=raw,value=dry-run-alpine,enable=${{ needs.plan.outputs.push == 'false' }}
195+ type=semver,pattern={{ raw }},suffix=-alpine${{ env.ALPINE_VERSION }},value=${{ needs.plan.outputs.tag }},enable=${{ needs.plan.outputs.push == 'true' }}
196+ type=semver,pattern={{ major }}.{{ minor }},suffix=-alpine${{ env.ALPINE_VERSION }},value=${{ needs.plan.outputs.tag }},enable=${{ needs.plan.outputs.push == 'true' }}
197+ type=raw,value=alpine${{ env.ALPINE_VERSION }},enable=${{ needs.plan.outputs.floating-tags == 'true' }}
198+ type=semver,pattern={{ raw }},suffix=-alpine,value=${{ needs.plan.outputs.tag }},enable=${{ needs.plan.outputs.push == 'true' }}
199+ type=semver,pattern={{ major }}.{{ minor }},suffix=-alpine,value=${{ needs.plan.outputs.tag }},enable=${{ needs.plan.outputs.push == 'true' }}
200+ type=raw,value=alpine,enable=${{ needs.plan.outputs.floating-tags == 'true' }}
201+
202+ - name : Build and push
203+ id : build
204+ uses : docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
205+ with :
206+ context : .
207+ file : Dockerfile.alpine
208+ platforms : linux/amd64,linux/arm64
209+ cache-from : type=gha,scope=prek-alpine
210+ cache-to : type=gha,mode=min,scope=prek-alpine
211+ push : ${{ needs.plan.outputs.push }}
212+ tags : ${{ steps.meta.outputs.tags }}
213+ labels : ${{ steps.meta.outputs.labels }}
214+ annotations : ${{ steps.meta.outputs.annotations }}
215+ provenance : mode=max
216+ sbom : true
199217
200218 - name : Generate artifact attestation
219+ if : ${{ needs.plan.outputs.push == 'true' }}
201220 uses : actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1
202221 with :
203222 subject-name : ${{ env.PREK_BASE_IMG }}
204- subject-digest : ${{ steps.manifest-digest.outputs.digest }}
223+ subject-digest : ${{ steps.build.outputs.digest }}
224+ push-to-registry : true
0 commit comments