Repository navigation
216 lines (209 loc) · 10.9 KB
/
Copy pathrelease-chart.yml
File metadata and controls
216 lines (209 loc) · 10.9 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
---
name: Release chart
# A decdn-node-X.Y.Z tag releases the `decdn-node` Helm chart on its own version; the
# `decdn.node` and `decdn.publisher` collections have their own workflow
# (release-collection.yml). See RELEASING.md.
#
# `build` always runs: it gates the tag (Chart.yaml and the chart changelog must agree
# on X.Y.Z), re-runs the chart checks, packages the chart, and uploads it with
# SHA256SUMS as a workflow artifact. `publish` makes it public
# (oci://ghcr.io/decdn/charts, cosign-signed keyless, with build-provenance attestations
# for the pushed chart and the packaged .tgz, plus the Artifact Hub repository metadata;
# and a GitHub Release) ONLY when the repository variable PUBLISH_ENABLED is 'true'.
# Until then a tag push is a dry run. A manual dispatch is always a dry run.
#
# This file's name and the tag shape are both part of the chart's cosign certificate
# identity (release-chart.yml@refs/tags/decdn-node-X.Y.Z) and of its attestations'
# signer workflow and source ref (RELEASING.md § Verifying a release): never rename
# this file or the tag shape once a chart is published.
on:
push:
tags: ['decdn-node-[0-9]+.[0-9]+.[0-9]+']
workflow_dispatch:
inputs:
tag:
description: Version to dry-run, e.g. decdn-node-0.1.0 (nothing is published)
required: true
permissions:
contents: read
concurrency:
# Per workflow: a manual dry run of each, both from main, must not queue behind
# (or cancel) the other.
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false # never cut a half-published release short
env:
TAG: ${{ github.event_name == 'push' && github.ref_name || inputs.tag }}
CHART_REGISTRY: oci://ghcr.io/decdn/charts
# `bash -eo pipefail`, not the default `bash -e`, so a failure anywhere in a pipe
# fails the step.
defaults:
run:
shell: bash
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Gate the tag against the chart and its changelog
# The gate accepts either artifact's tag; a manual dispatch could pass the other
# one, so refuse it here, before anything is built.
run: |
[[ "$TAG" == decdn-node-* ]] || { echo "::error::$TAG is not a chart tag (decdn-node-X.Y.Z)"; exit 1; }
scripts/check-release-version.sh "$TAG" --notes release-notes.md
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
# Helm is pinned five times: ci.yml's helm, kics and scripts jobs, and both jobs
# here. Bump all five together.
- uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
with:
version: v4.3.0
- name: Chart lint and render tests
run: make lint-helm
# Artifact Hub reads a release's changes from Chart.yaml, not CHANGELOG.md, so
# generate the annotation from the changelog section the gate just checked. The
# package carries it, the commit never does; lint ran on the chart as committed.
# tests/scripts-test.sh runs this step and the check below by name.
- name: Add the artifacthub.io/changes annotation
run: |
CHANGES="$(scripts/chart-artifacthub-changes.py charts/decdn-node/CHANGELOG.md "${TAG#decdn-node-}")"
export CHANGES
yq -i '.annotations["artifacthub.io/changes"] = strenv(CHANGES)' charts/decdn-node/Chart.yaml
- name: Package the chart and write checksums
run: |
mkdir -p dist
helm package charts/decdn-node --destination dist
(cd dist && sha256sum -- * > SHA256SUMS)
# Not release assets, so outside SHA256SUMS: the notes become the Release
# body, the Artifact Hub metadata is pushed next to the chart.
cp release-notes.md charts/decdn-node/artifacthub-repo.yml dist/
cat dist/SHA256SUMS
- name: Check the packaged artifacthub.io/changes
run: |
helm show chart dist/decdn-node-"${TAG#decdn-node-}".tgz \
| yq -o=json '.annotations["artifacthub.io/changes"] | from_yaml' \
| jq -e 'type == "array" and length > 0 and all(.[];
(.kind | IN("added", "changed", "deprecated", "removed", "fixed", "security"))
and (.description | type == "string" and length > 0))' >/dev/null
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: release-${{ env.TAG }}
path: dist/
if-no-files-found: error
publish:
needs: build
if: vars.PUBLISH_ENABLED == 'true' && github.event_name == 'push'
runs-on: ubuntu-latest
timeout-minutes: 20
# Protect this environment with required reviewers in the repository settings.
environment: release
permissions:
contents: write # the GitHub Release
packages: write # the chart on ghcr.io
id-token: write # keyless cosign signature and attestations (Sigstore)
attestations: write # build-provenance attestations
steps:
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: release-${{ env.TAG }}
path: dist
- name: Verify the artifacts are the ones build checksummed
working-directory: dist
run: sha256sum --check SHA256SUMS
# SLSA build provenance (no predicate input), verifiable with `gh attestation
# verify` (RELEASING.md § Verifying a release). The package's attestation needs
# nothing pushed, so it comes first: a Sigstore or attestations API outage then
# fails the job before the chart is public. Repeatable: a re-run adds another
# attestation for the same digest.
- name: Attest the chart package
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-path: dist/decdn-node-*.tgz
- uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
with:
version: v4.3.0
- uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
# Pinned, not the installer's default: RELEASING.md tells users which cosign
# verifies the signature, and the self-check below reads v3's JSON output.
cosign-release: v3.0.6
- uses: oras-project/setup-oras@005458ad77f1c8facd38a094e4af2e69e5607ff4 # v2.0.2
with:
version: 1.3.4
- name: Push and sign the chart
id: push
env:
GH_TOKEN: ${{ github.token }}
run: |
# Helm and cosign keep separate credential stores (Helm's registry config
# vs Docker's config.json), so log both in: cosign pushes the signature.
echo "$GH_TOKEN" | helm registry login ghcr.io --username "$GITHUB_ACTOR" --password-stdin
echo "$GH_TOKEN" | cosign login ghcr.io --username "$GITHUB_ACTOR" --password-stdin
# Capture and print on both paths: under `bash -e` a bare out="$(…)" would
# exit on failure before the error text was shown.
if ! out="$(helm push dist/decdn-node-"${TAG#decdn-node-}".tgz "$CHART_REGISTRY" 2>&1)"; then
echo "$out"
echo "::error::helm push failed"
exit 1
fi
echo "$out"
digest="$(sed -nE 's/^Digest: (sha256:[0-9a-f]{64})$/\1/p' <<<"$out")"
[ -n "$digest" ] || { echo "::error::no digest in helm push output"; exit 1; }
cosign sign --yes "ghcr.io/decdn/charts/decdn-node@$digest"
echo "digest=$digest" >> "$GITHUB_OUTPUT"
echo "chart: ${CHART_REGISTRY}/decdn-node:${TAG#decdn-node-} ($digest)" >> "$GITHUB_STEP_SUMMARY"
# The pushed chart's attestation needs its digest, so it follows the push. Its
# bundle is also attached to the chart on ghcr.io (push-to-registry, which reads
# Docker's config.json: the cosign login above wrote it). No storage record, so
# no artifact-metadata permission.
- name: Attest the pushed chart
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-name: ghcr.io/decdn/charts/decdn-node
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
create-storage-record: false
# Verify what was just published, as RELEASING.md § Verifying a release tells
# users to, before the Release page appears. cosign v3 reads every Sigstore bundle
# attached to the chart, so the attestation above (same signer identity) could
# satisfy a bare `cosign verify`: the check requires an entry of cosign's own
# signature type, so a missing signature fails here.
- name: Verify the published chart
env:
GH_TOKEN: ${{ github.token }}
DIGEST: ${{ steps.push.outputs.digest }}
run: |
chart="ghcr.io/decdn/charts/decdn-node@$DIGEST"
cosign verify "$chart" \
--certificate-identity "https://github.com/$GITHUB_REPOSITORY/.github/workflows/release-chart.yml@refs/tags/$TAG" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com --output json \
| jq -e 'any(.[]; .critical.type == "https://sigstore.dev/cosign/sign/v1")' >/dev/null \
|| { echo "::error::no cosign signature on $chart"; exit 1; }
attest=(--repo "$GITHUB_REPOSITORY" --source-ref "refs/tags/$TAG"
--signer-workflow "$GITHUB_REPOSITORY/.github/workflows/release-chart.yml")
gh attestation verify "dist/decdn-node-${TAG#decdn-node-}.tgz" "${attest[@]}"
gh attestation verify "oci://$chart" "${attest[@]}"
# The copy attached on ghcr.io, which the GitHub API check above never reads.
gh attestation verify "oci://$chart" "${attest[@]}" --bundle-from-oci
# Repeatable: oras push just moves the artifacthub.io tag.
- name: Push the Artifact Hub repository metadata
working-directory: dist
env:
GH_TOKEN: ${{ github.token }}
run: |
echo "$GH_TOKEN" | oras login ghcr.io --username "$GITHUB_ACTOR" --password-stdin
oras push ghcr.io/decdn/charts/decdn-node:artifacthub.io \
--config /dev/null:application/vnd.cncf.artifacthub.config.v1+yaml \
artifacthub-repo.yml:application/vnd.cncf.artifacthub.repository-metadata.layer.v1.yaml
# Last, so the Release page only appears once the chart is live. Not marked
# Latest: the repo's Latest release follows the decdn.node collection, the
# primary path.
- name: Create the GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "$TAG" --repo "$GITHUB_REPOSITORY" \
--title "decdn-node chart ${TAG#decdn-node-}" --latest=false \
--notes-file dist/release-notes.md \
dist/decdn-node-*.tgz dist/SHA256SUMS