-
Notifications
You must be signed in to change notification settings - Fork 0
244 lines (232 loc) · 9.83 KB
/
Copy pathrelease.yml
File metadata and controls
244 lines (232 loc) · 9.83 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
235
236
237
238
239
240
241
242
243
244
name: Release
on:
push:
branches: [main]
tags: ['v*']
permissions:
contents: read
concurrency:
group: saola-cli-release-${{ github.ref }}
cancel-in-progress: false
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
with:
go-version-file: go.mod
cache: true
- name: Test release metadata contract
run: bash hack/release-metadata_test.sh
- name: Test Go packages
run: make test
metadata:
runs-on: ubuntu-latest
outputs:
channel: ${{ steps.metadata.outputs.channel }}
version: ${{ steps.metadata.outputs.version }}
commit: ${{ steps.metadata.outputs.commit }}
build_date: ${{ steps.metadata.outputs.build_date }}
source_date_epoch: ${{ steps.metadata.outputs.source_date_epoch }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Derive immutable metadata
id: metadata
env:
EXPECTED_COMMIT: ${{ github.sha }}
GIT_REF: ${{ github.ref }}
REPOSITORY: ${{ github.repository }}
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "${EXPECTED_COMMIT}"
source_date_epoch="$(git show -s --format=%ct "${EXPECTED_COMMIT}")"
metadata="$(SOURCE_DATE_EPOCH="${source_date_epoch}" ./hack/release-metadata.sh "${GIT_REF}" "${EXPECTED_COMMIT}" "${REPOSITORY}")"
printf '%s\n' "${metadata}" >> "${GITHUB_OUTPUT}"
printf 'source_date_epoch=%s\n' "${source_date_epoch}" >> "${GITHUB_OUTPUT}"
build:
needs: [test, metadata]
runs-on: ubuntu-latest
permissions:
actions: read
attestations: write
contents: write
id-token: write
env:
VERSION: ${{ needs.metadata.outputs.version }}
GIT_COMMIT: ${{ needs.metadata.outputs.commit }}
BUILD_DATE: ${{ needs.metadata.outputs.build_date }}
SOURCE_DATE_EPOCH: ${{ needs.metadata.outputs.source_date_epoch }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
with:
go-version-file: go.mod
cache: true
- name: Build official architectures
run: make release-build
- name: Inspect release binaries
run: |
set -euo pipefail
file dist/saola-linux-amd64 dist/saola-linux-arm64
file dist/saola-linux-amd64 | grep -q 'x86-64'
file dist/saola-linux-arm64 | grep -Eq 'ARM aarch64|aarch64'
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
with:
platforms: arm64
- name: Smoke-test both architectures
run: |
set -euo pipefail
amd64_output="$(dist/saola-linux-amd64 version)"
arm64_output="$(dist/saola-linux-arm64 version)"
printf '%s\n' "${amd64_output}"
printf '%s\n' "${arm64_output}"
grep -Fq "Version: ${VERSION}" <<<"${amd64_output}"
grep -Fq "Git Commit: ${GIT_COMMIT}" <<<"${amd64_output}"
grep -Fq "Version: ${VERSION}" <<<"${arm64_output}"
grep -Fq "Git Commit: ${GIT_COMMIT}" <<<"${arm64_output}"
- name: Create checksums
run: make release-checksums
- name: Generate amd64 SBOM
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
with:
file: dist/saola-linux-amd64
format: spdx-json
output-file: dist/saola-linux-amd64.spdx.json
artifact-name: saola-linux-amd64.spdx.json
upload-artifact: false
upload-release-assets: false
- name: Generate arm64 SBOM
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
with:
file: dist/saola-linux-arm64
format: spdx-json
output-file: dist/saola-linux-arm64.spdx.json
artifact-name: saola-linux-arm64.spdx.json
upload-artifact: false
upload-release-assets: false
- name: Attest release artifacts
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4
with:
subject-path: |
dist/saola-linux-amd64
dist/saola-linux-arm64
dist/SHA256SUMS
dist/saola-linux-amd64.spdx.json
dist/saola-linux-arm64.spdx.json
- name: Install Cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: Keyless-sign release artifacts
run: |
set -euo pipefail
for artifact in \
dist/saola-linux-amd64 \
dist/saola-linux-arm64 \
dist/SHA256SUMS \
dist/saola-linux-amd64.spdx.json \
dist/saola-linux-arm64.spdx.json; do
cosign sign-blob --yes --bundle "${artifact}.sigstore.json" "${artifact}"
done
- name: Upload immutable workflow artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: saola-cli-${{ needs.metadata.outputs.version }}
path: dist/
if-no-files-found: error
retention-days: 30
- name: Publish stable tag assets
if: needs.metadata.outputs.channel == 'stable'
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ needs.metadata.outputs.version }}
run: |
set -euo pipefail
release_created=false
if ! release_json="$(gh release view "${RELEASE_TAG}" --json isDraft,assets 2>/dev/null)"; then
gh release create "${RELEASE_TAG}" --verify-tag --draft --generate-notes --title "Saola CLI ${RELEASE_TAG}"
release_created=true
release_json="$(gh release view "${RELEASE_TAG}" --json isDraft,assets)"
fi
is_draft="$(jq -r '.isDraft' <<<"${release_json}")"
[[ "${is_draft}" == "true" || "${is_draft}" == "false" ]] || {
echo "unable to determine release draft state" >&2
exit 1
}
mapfile -t remote_assets < <(jq -r '.assets[].name' <<<"${release_json}")
for remote_asset in "${remote_assets[@]}"; do
[[ "${remote_asset}" == "$(basename "${remote_asset}")" && -f "dist/${remote_asset}" ]] || {
echo "release contains unexpected asset: ${remote_asset}" >&2
exit 1
}
done
verify_dir="$(mktemp -d)"
trap 'rm -rf "${verify_dir}"' EXIT
for artifact in dist/*; do
asset_name="$(basename "${artifact}")"
asset_exists=false
for remote_asset in "${remote_assets[@]}"; do
if [[ "${remote_asset}" == "${asset_name}" ]]; then
asset_exists=true
break
fi
done
if [[ "${asset_exists}" == "false" ]]; then
if [[ "${is_draft}" != "true" ]]; then
echo "published release is missing immutable asset: ${asset_name}" >&2
exit 1
fi
upload_artifact="${artifact}"
if [[ "${asset_name}" == *.spdx.json.sigstore.json ]]; then
sbom_name="${asset_name%.sigstore.json}"
canonical_sbom="${verify_dir}/${sbom_name}/${sbom_name}"
if [[ -f "${canonical_sbom}" ]]; then
mkdir -p "${verify_dir}/canonical-bundles"
upload_artifact="${verify_dir}/canonical-bundles/${asset_name}"
cosign sign-blob --yes --bundle "${upload_artifact}" "${canonical_sbom}"
fi
fi
gh release upload "${RELEASE_TAG}" "${upload_artifact}"
fi
asset_verify_dir="${verify_dir}/${asset_name}"
mkdir -p "${asset_verify_dir}"
gh release download "${RELEASE_TAG}" --pattern "${asset_name}" --dir "${asset_verify_dir}"
if [[ "${asset_name}" == *.sigstore.json ]]; then
signed_artifact="${artifact%.sigstore.json}"
signed_name="$(basename "${signed_artifact}")"
canonical_signed_artifact="${verify_dir}/${signed_name}/${signed_name}"
if [[ -f "${canonical_signed_artifact}" ]]; then
signed_artifact="${canonical_signed_artifact}"
fi
cosign verify-blob \
--bundle "${asset_verify_dir}/${asset_name}" \
--certificate-identity "https://github.com/${GITHUB_REPOSITORY}/.github/workflows/release.yml@refs/tags/${RELEASE_TAG}" \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
"${signed_artifact}"
elif [[ "${asset_name}" == *.spdx.json ]]; then
canonical_sbom="${asset_verify_dir}/${asset_name}"
jq -e '
(.spdxVersion | type == "string" and startswith("SPDX-")) and
(.SPDXID == "SPDXRef-DOCUMENT") and
(.packages | type == "array" and length > 0)
' "${canonical_sbom}" >/dev/null || {
echo "release SBOM is not a valid SPDX document: ${asset_name}" >&2
exit 1
}
else
cmp -s "${artifact}" "${asset_verify_dir}/${asset_name}" || {
echo "release asset differs from local immutable artifact: ${asset_name}" >&2
exit 1
}
fi
done
if [[ "${is_draft}" == "true" ]]; then
gh release edit "${RELEASE_TAG}" --draft=false
elif [[ "${release_created}" == "true" ]]; then
echo "new release unexpectedly lost draft state" >&2
exit 1
fi