-
Notifications
You must be signed in to change notification settings - Fork 0
228 lines (207 loc) · 9.25 KB
/
Copy pathrelease.yml
File metadata and controls
228 lines (207 loc) · 9.25 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
name: release
# Tag-driven release: build + smoke-test the PHP matrix, push the images to
# GHCR, then cut a GitHub Release with notes lifted from CHANGELOG.md.
# Trigger: git tag -a vX.Y.Z && git push --follow-tags
on:
push:
tags: ["v*"]
# Least privilege at the top; the jobs that need more opt in explicitly.
permissions:
contents: read
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
env:
REGISTRY: ghcr.io
jobs:
# Every job carries a timeout-minutes cap: a hung docker push or Sigstore/
# Rekor call would otherwise hold the runner for GitHub's 6-hour default.
#
# Single source for the PHP matrix: the Makefile's PHP_VERSIONS (see ci.yml).
php-matrix:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
php: ${{ steps.read.outputs.php }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: read the php matrix from the Makefile
id: read
run: echo "php=$(make print-php-matrix)" >> "$GITHUB_OUTPUT"
images:
needs: php-matrix
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read # checkout
packages: write # push to GHCR via the built-in GITHUB_TOKEN
id-token: write # keyless (OIDC) signing for the attestations below
attestations: write # record the provenance + SBOM attestations
strategy:
fail-fast: false
matrix:
php: ${{ fromJSON(needs.php-matrix.outputs.php) }}
env:
PHP: ${{ matrix.php }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
# Single-source the release coordinates from the Dockerfile ARGs (same as
# the Makefile) so this workflow needs no edit on a version bump. GHCR
# requires a lowercase image path, hence the owner is downcased.
- name: derive image coordinates
run: |
owner=${GITHUB_REPOSITORY_OWNER,,}
{
echo "IMAGE=${REGISTRY}/${owner}/freeunit-drupal"
echo "VERSION=${GITHUB_REF_NAME#v}"
echo "BASE_TAG=$(sed -n 's/^ARG BASE_TAG=\([^[:space:]]*\).*/\1/p' Dockerfile)"
echo "DEFAULT_PHP=$(sed -n 's/^ARG PHP_VER=\([^[:space:]]*\).*/\1/p' Dockerfile)"
} >> "$GITHUB_ENV"
- name: set up buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
# Reuses the Makefile target: builds php$PHP with the floating tag, then
# runs the end-to-end smoke test against it. Wire buildx's GitHub Actions
# layer cache read-only: ci.yml populated scope=php<ver> on the merge
# commit minutes earlier (default-branch caches are readable from a tag
# ref), so release reuses those layers instead of a cold rebuild. --load
# drops the image into the daemon for the smoke test + tag/push that
# follow. No --cache-to: release shouldn't compete with CI for cache writes
# and a miss just falls back to a full build.
- name: build + smoke test
env:
DOCKER_BUILD_EXTRA: >-
--load
--cache-from type=gha,scope=php${{ matrix.php }}
run: make test DEFAULT_PHP="$PHP" IMAGE="$IMAGE"
- name: log in to GHCR
uses: ./.github/actions/ghcr-login
with:
username: ${{ github.actor }}
token: ${{ secrets.GITHUB_TOKEN }}
# Push the floating tag make produced, add a release-version tag, and on
# the default PHP line also publish the bare :$VERSION and :latest so
# `docker pull $IMAGE` and `:$VERSION` resolve.
# The sequence of pushes is not atomic: a mid-step failure leaves a
# partial tag set in GHCR. Recovery is to re-run this workflow from the
# same git tag -- every push is idempotent (same image, same digest).
# :latest goes LAST so the floating "current release" pointer only
# advances once every other tag of the set is already live.
- name: tag + push
run: |
floating="$IMAGE:${BASE_TAG}-php${PHP}"
versioned="$IMAGE:${VERSION}-php${PHP}"
docker tag "$floating" "$versioned"
docker push "$floating"
docker push "$versioned"
if [ "$PHP" = "$DEFAULT_PHP" ]; then
docker tag "$floating" "$IMAGE:${VERSION}"
docker tag "$floating" "$IMAGE:latest"
docker push "$IMAGE:${VERSION}"
docker push "$IMAGE:latest"
fi
# All tags for this PHP leg point at one image; resolve its registry
# digest once so the attestations below bind to the manifest, not a
# mutable tag. Export the local ref too for the SBOM scan.
digest=$(docker buildx imagetools inspect "$floating" --format '{{ .Manifest.Digest }}')
{
echo "IMAGE_DIGEST=$digest"
echo "IMAGE_REF=$floating"
} >> "$GITHUB_ENV"
# Scan the just-built image into an SPDX SBOM (from the local daemon, so no
# re-pull). Artifact upload is off: the SBOM is published as an attestation
# below, and parallel matrix legs would collide on a shared artifact name.
- name: generate SBOM
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
image: ${{ env.IMAGE_REF }}
format: spdx-json
output-file: sbom.spdx.json
upload-artifact: false
# Keyless (OIDC) provenance + SBOM attestations, pushed to GHCR as OCI
# referrers of the image manifest. Verify with:
# gh attestation verify oci://$IMAGE@$DIGEST --owner <owner>
# Both write a Sigstore public-good Rekor transparency-log entry, which
# intermittently times out ("error creating tlog entry"). GitHub Actions
# has no native retry for `uses:` steps, so each attestation runs once with
# continue-on-error and is re-run by a guarded step only when the first
# attempt failed — riding out a transient tlog flake without failing the
# whole release.
- name: attest build provenance
id: provenance
continue-on-error: true
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-name: ${{ env.IMAGE }}
subject-digest: ${{ env.IMAGE_DIGEST }}
push-to-registry: true
- name: attest build provenance (retry)
if: steps.provenance.outcome == 'failure'
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-name: ${{ env.IMAGE }}
subject-digest: ${{ env.IMAGE_DIGEST }}
push-to-registry: true
# actions/attest-sbom is deprecated; actions/attest is its successor and
# accepts the same inputs (sbom-path creates the SBOM attestation).
- name: attest SBOM
id: sbom
continue-on-error: true
uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1
with:
subject-name: ${{ env.IMAGE }}
subject-digest: ${{ env.IMAGE_DIGEST }}
sbom-path: sbom.spdx.json
push-to-registry: true
- name: attest SBOM (retry)
if: steps.sbom.outcome == 'failure'
uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1
with:
subject-name: ${{ env.IMAGE }}
subject-digest: ${{ env.IMAGE_DIGEST }}
sbom-path: sbom.spdx.json
push-to-registry: true
- name: log out of GHCR
if: always()
run: docker logout "$REGISTRY"
release:
needs: images
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write # create the GitHub Release
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
# Pull just this version's section out of CHANGELOG.md for the release body
# (everything between this `## [x.y.z]` heading and the next `## [`).
- name: extract changelog section
run: |
version=${GITHUB_REF_NAME#v}
# Grab the section between this version's `## [x.y.z]` heading and the
# next `## [`, skipping link-reference definitions (`[x.y.z]: https://...`)
# that sit at the file's foot and would otherwise tail the last section.
awk -v v="$version" '
$0 ~ "^## \\[" v "\\]" { grab = 1; next }
grab && /^## \[/ { exit }
grab && /^\[[^][]*\]:[[:space:]]/ { next }
grab { print }
' CHANGELOG.md > release-notes.md
if [ ! -s release-notes.md ]; then
echo "ERROR: no CHANGELOG.md section for $version" >&2
exit 1
fi
# Gate on the Russian changelog carrying the same version, so the
# translated sibling can't silently fall out of sync at release time.
if ! grep -q "^## \[${version}\]" CHANGELOG.ru.md; then
echo "ERROR: no CHANGELOG.ru.md section for $version (translate it before tagging)" >&2
exit 1
fi
- name: create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: gh release create "$GITHUB_REF_NAME" --title "$GITHUB_REF_NAME" --notes-file release-notes.md