Repository navigation
219 lines (202 loc) · 8.28 KB
/
Copy pathrelease.yml
File metadata and controls
219 lines (202 loc) · 8.28 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
name: Release
# Rebuilds every image every two hours (Wolfi publishes fixes continuously) and on every change to main, and
# publishes the images whose content changed (see build.yml). Each published image gets a GitHub
# release with its changelog and scan results, and its status (STIG score, vulnerabilities, latest
# tag) is written to GitHub Pages for the README badges. Five versions per image are kept, in GHCR
# and as GitHub releases.
on:
schedule:
- cron: '17 */2 * * *'
push:
branches:
- main
paths:
- 'apko/**'
# Python upstream from python.org: Renovate's version updates land here
- 'melange/**'
- 'keys/**'
- 'scripts/**'
- '.github/workflows/build.yml'
- '.github/workflows/release.yml'
workflow_dispatch:
# Releases run one after another, never in parallel or cancelled half-way
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
permissions: {}
jobs:
build:
permissions:
contents: read
packages: write
id-token: write
attestations: write
uses: ./.github/workflows/build.yml
with:
publish: true
secrets: inherit
# Runs even when some images failed to publish, so the ones that did are released. Only records
# whose release tag exists on the recorded digest are released; a re-run of the failed jobs
# finishes the rest.
release:
needs: build
if: ${{ !cancelled() }}
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: write
packages: read
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Download Release Records
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
pattern: release-*
path: records
# Unchanged images: today's status only (no release, no archive)
- name: Download Status Records
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
pattern: status-*
path: records
- name: Install oras
uses: oras-project/setup-oras@5e2f2482b30b0cb3e14e67f89a80c88c659a7c63 # v2.0.3
with:
version: 1.3.6
# A record is released only if its tag was created on its digest (tags are the last step)
- name: Published Images
id: any
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
printf '%s' "$GH_TOKEN" | oras login ghcr.io -u "$GITHUB_ACTOR" --password-stdin
for dir in records/release-*; do
[ -f "$dir/record.json" ] || continue
ref="$(jq -r '"\(.image):\(.release)"' "$dir/record.json")"
want="$(jq -r .digest "$dir/record.json")"
if [ "$(oras resolve "$ref" 2>/dev/null || true)" != "$want" ]; then
echo "::warning::$ref is not tagged on $want; not releasing it"
rm -rf "$dir"
fi
done
if compgen -G "records/release-*/record.json" >/dev/null; then
echo "published=true" >> "$GITHUB_OUTPUT"
else
echo "::notice::No image changed; nothing to release"
echo "published=false" >> "$GITHUB_OUTPUT"
fi
# A release creates a git tag on the commit the images were built from. When main has moved
# on and its workflow files differ from that commit, GitHub treats the new tag as a workflow
# change, which GITHUB_TOKEN may never make. The repository's GitHub App can.
- name: App Token
id: app-token
if: steps.any.outputs.published == 'true'
continue-on-error: true
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
with:
client-id: ${{ vars.RENOVATE_APP_CLIENT_ID }}
private-key: ${{ secrets.RENOVATE_APP_PRIVATE_KEY }}
permission-contents: write
permission-workflows: write
- name: GitHub Releases
if: steps.any.outputs.published == 'true'
env:
GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
run: |
for dir in records/release-*; do
tag="$(jq -r .release "$dir/record.json")"
python3 scripts/release.py notes "$dir/record.json" > "$dir/notes.md"
if gh release view "$tag" >/dev/null 2>&1; then
echo "::notice::GitHub release $tag already exists"
continue
fi
gh release create "$tag" --target "$GITHUB_SHA" --title "$tag" \
--notes-file "$dir/notes.md" \
"$dir/record.json" "$dir/stig-x86_64.html" "$dir/stig-aarch64.html" \
"$dir/sbom-x86_64.spdx.json" "$dir/sbom-aarch64.spdx.json"
done
# The README badges and the image pages read these files. Published images get their page and
# an archive entry; unchanged images get their status refreshed with today's scan.
- name: Status Pages
id: pages
run: |
mkdir -p site
for dir in records/release-*; do
[ -f "$dir/record.json" ] && python3 scripts/release.py pages "$dir" site
done
for dir in records/status-*; do
[ -f "$dir/record.json" ] && python3 scripts/release.py pages "$dir" site --no-archive
done
python3 scripts/release.py index site
if [ -n "$(ls -A site)" ]; then echo "any=true" >> "$GITHUB_OUTPUT"; fi
- name: Publish Status Pages
if: steps.pages.outputs.any == 'true'
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./site
keep_files: true
# Keeps the five newest versions of every image and nothing else: in GHCR only the release tags of
# each family are counted (never `3.12`, `latest` or the sha256-* referrer tags), so a kept version
# keeps its platform manifests, signatures and attestations; untagged leftovers of failed runs are
# deleted. The GitHub releases of each family follow the same rule. Never fails the release.
cleanup:
needs: [build, release]
if: ${{ !cancelled() && needs.build.result == 'success' }}
runs-on: ubuntu-24.04
timeout-minutes: 30
continue-on-error: true
permissions:
contents: write
packages: write
strategy:
max-parallel: 1
matrix:
image: ${{ fromJSON(needs.build.outputs.images) }}
env:
FAMILY: ${{ matrix.image.family }}
steps:
- name: Release Tag Pattern
id: pattern
run: |
major="${FAMILY%%-*}"
if [ "$FAMILY" = "$major" ]; then
echo "regex=^${major}\\.[0-9]+-[0-9]{8}\\.[0-9]+\$" >> "$GITHUB_OUTPUT"
else
echo "regex=^${major}\\.[0-9]+-dev-[0-9]{8}\\.[0-9]+\$" >> "$GITHUB_OUTPUT"
fi
- name: Keep the Five Newest Versions in GHCR
uses: dataaxiom/ghcr-cleanup-action@d52806a0dc70b430571a37da1fde39733ffd640f # v1.2.2
with:
package: python
use-regex: true
delete-tags: ${{ steps.pattern.outputs.regex }}
keep-n-tagged: 5
delete-untagged: true
delete-orphaned-images: true
delete-partial-images: true
validate: true
- name: App Token
id: app-token
continue-on-error: true
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
with:
client-id: ${{ vars.RENOVATE_APP_CLIENT_ID }}
private-key: ${{ secrets.RENOVATE_APP_PRIVATE_KEY }}
permission-contents: write
permission-workflows: write
- name: Keep the Five Newest GitHub Releases
env:
GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
REGEX: ${{ steps.pattern.outputs.regex }}
run: |
gh release list --limit 500 --json tagName,createdAt |
jq -r --arg re "$REGEX" 'map(select(.tagName | test($re))) | sort_by(.createdAt) | reverse | .[5:] | .[].tagName' |
while read -r tag; do
echo "deleting release and tag $tag"
gh release delete "$tag" --yes --cleanup-tag
done