-
-
Notifications
You must be signed in to change notification settings - Fork 4
234 lines (225 loc) · 9.36 KB
/
Copy pathrelease.yml
File metadata and controls
234 lines (225 loc) · 9.36 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
name: Release
on:
push:
branches: [main]
permissions:
contents: read
# One release at a time; do not cancel one mid-flight.
concurrency:
group: release
cancel-in-progress: false
env:
# Derive the image name from the repository so it tracks a rename automatically
# instead of drifting (the hardcoded name once lagged a rename and published under
# the old name). `github.repository` is `owner/repo`, lowercase as GHCR requires.
IMAGE: ghcr.io/${{ github.repository }}
# OCI namespace for the Helm chart, shared across this owner's charts. The chart is
# pushed as `<owner>/charts/git-cache-proxy`.
CHART_REPO: oci://ghcr.io/${{ github.repository_owner }}/charts
jobs:
release:
name: Release
# knope re-pushes its own version-bump commit to main, which retriggers this
# workflow; that rerun would only find no releasable commits and no-op. Skip it
# outright so it does not even spin a runner. Every other push (a merged PR)
# still runs and releases if there are releasable commits. The publish-* jobs
# need this job, so they cascade-skip on the bump commit too.
if: "${{ !startsWith(github.event.head_commit.message, 'chore: prepare release') }}"
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
released: ${{ steps.release.outputs.released }}
version: ${{ steps.release.outputs.version }}
steps:
# The version-bump commit is pushed directly to `main`, which is protected by a
# branch ruleset that requires status checks. The built-in GITHUB_TOKEN cannot be
# granted a ruleset bypass, so mint a token for a GitHub App that is on the
# ruleset's bypass list and push with that instead.
- name: Mint a token for the release app
id: app-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
- uses: actions/checkout@v7
with:
# Full history and tags so knope can read the commits since the last release.
fetch-depth: 0
# Push the release commit as the app so it bypasses the branch ruleset.
token: ${{ steps.app-token.outputs.token }}
- uses: knope-dev/action@v2.1.2
with:
version: 0.23.0
- name: Configure the release identity
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
# knope bumps the version, updates the changelog, tags, and creates the GitHub
# release from the conventional commits since the last tag. The push uses the app
# token, so - unlike the built-in token - it retriggers this workflow; that rerun
# finds no releasable commits and no-ops via `no_release`, so the publish job runs
# only in the run that actually released. `no_release` is a normal no-op, not a
# failure.
- name: Release
id: release
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
set +e
out="$(knope release 2>&1)"
code=$?
echo "$out"
if [ "$code" -eq 0 ]; then
# Read the version knope just bumped in the working tree. Do NOT derive it
# from `git tag`: with [github] configured, knope creates the tag via the
# GitHub API, so it never appears as a local tag in this checkout, and
# `git tag | head` would return the *previous* release instead.
version="$(grep -m1 '^version = ' Cargo.toml | sed -E 's/.*"(.*)".*/\1/')"
echo "released=true" >> "$GITHUB_OUTPUT"
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "::notice::Released v${version}."
elif echo "$out" | grep -q 'no_release'; then
echo "::notice::No releasable commits since the last release; nothing to do."
echo "released=false" >> "$GITHUB_OUTPUT"
else
exit "$code"
fi
# Build each arch on its own native runner - QEMU-emulated musl compiles are
# painfully slow - then push tag-less, digest-only images for `publish-manifest`
# to stitch into one multi-arch tag.
publish:
name: Build image (${{ matrix.platform }})
needs: release
if: needs.release.outputs.released == 'true'
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
- platform: linux/arm64
runner: ubuntu-24.04-arm
steps:
- name: Sanitize the platform for artifact naming
run: |
platform="${{ matrix.platform }}"
echo "PLATFORM_PAIR=${platform//\//-}" >> "$GITHUB_ENV"
- uses: actions/checkout@v7
with:
ref: v${{ needs.release.outputs.version }}
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v7
with:
context: .
file: Dockerfile
platforms: ${{ matrix.platform }}
outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=${{ matrix.platform }}
cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
- name: Export the digest
run: |
mkdir -p "${{ runner.temp }}/digests"
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload the digest
uses: actions/upload-artifact@v7
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
publish-manifest:
name: Publish multi-arch manifest
needs: [release, publish]
if: needs.release.outputs.released == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Download the digests
uses: actions/download-artifact@v8
with:
path: ${{ runner.temp }}/digests
pattern: digests-*
merge-multiple: true
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create the manifest list and push
working-directory: ${{ runner.temp }}/digests
run: |
docker buildx imagetools create \
-t ${{ env.IMAGE }}:${{ needs.release.outputs.version }} \
-t ${{ env.IMAGE }}:latest \
$(printf '${{ env.IMAGE }}@sha256:%s ' *)
- name: Inspect the published manifest
run: docker buildx imagetools inspect ${{ env.IMAGE }}:${{ needs.release.outputs.version }}
# Publish the crate to crates.io (which feeds docs.rs) in the same run that cut the
# release, at the tag knope just created. Uses crates.io trusted publishing: GitHub's
# OIDC token is exchanged for a short-lived crates.io token, so no API token secret is
# stored. One-time bootstrap before this can work: publish once manually to claim the
# name (`cargo publish`), then add a GitHub Actions trusted publisher on the crate's
# crates.io settings for this repo and the Release workflow.
publish-crate:
name: Publish crate
needs: release
if: needs.release.outputs.released == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v7
with:
ref: v${{ needs.release.outputs.version }}
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Authenticate to crates.io
id: auth
uses: rust-lang/crates-io-auth-action@v1
- name: Publish to crates.io
run: cargo publish --locked
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
# Package and push the Helm chart to GHCR as an OCI artifact, in the same run that
# cut the release, at the tag knope just created. The published GHCR package starts
# private; make it public once to allow anonymous `helm pull`.
publish-chart:
name: Publish chart
needs: release
if: needs.release.outputs.released == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v7
with:
ref: v${{ needs.release.outputs.version }}
- uses: azure/setup-helm@v5
# Version the chart in lockstep with the app: package at the released version
# (overriding the static Chart.yaml version) with the matching appVersion, so a
# pulled chart deploys the image it was cut with.
- name: Package and push the chart
run: |
helm package chart -d dist \
--version "${{ needs.release.outputs.version }}" \
--app-version "${{ needs.release.outputs.version }}"
echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u "${{ github.actor }}" --password-stdin
helm push dist/git-cache-proxy-*.tgz "${{ env.CHART_REPO }}"