-
Notifications
You must be signed in to change notification settings - Fork 369
315 lines (304 loc) 路 16.4 KB
/
Copy pathbuild-test-distribute.yaml
File metadata and controls
315 lines (304 loc) 路 16.4 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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
name: "build-test-distribute"
on:
push:
branches: ["master", "release-*", "!*-merge-master"]
tags: ["*"]
pull_request:
branches: ["master", "release-*"]
workflow_dispatch: # Allows manual trigger from GitHub Actions UI or via REST call
permissions:
contents: read
env:
KUMA_DIR: "."
# To keep CI tools out of the SBOM, we use a `.ci_tools` directory in the parent
# of the code checkout path (typically /home/runner/work/<repo-name>/<repo-name>
# on the runner).
CI_TOOLS_DIR: "/home/runner/work/kuma/.ci_tools"
concurrency:
group: ${{ format('{0}-{1}-{2}', github.workflow, github.event_name, github.event_name == 'push' && github.sha || github.event_name == 'pull_request' && github.event.pull_request.number || github.event_name == 'workflow_dispatch' && github.ref_name) }}
cancel-in-progress: ${{ github.event_name == 'push' && false || true }}
jobs:
release_sha_gate:
timeout-minutes: 5
runs-on: ${{ vars.RUNS_ON_RELEASE_2_12_AMD64 || vars.RUNS_ON_AMD64 || 'ubuntu-24.04' }}
permissions:
actions: read
contents: read
steps:
- name: "Verify tagged SHA has a trusted green push run"
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
SHA: ${{ github.sha }}
REF_TYPE: ${{ github.ref_type }}
run: |
set -euo pipefail
if [[ "${REF_TYPE}" != "tag" ]]; then
echo "Not a tag push; skipping release SHA gate."
exit 0
fi
WORKFLOW="build-test-distribute.yaml"
runs_ndjson="$(mktemp)"
if ! gh api --method GET "/repos/${REPO}/actions/workflows/${WORKFLOW}/runs" \
-f head_sha="${SHA}" -f status=completed -f event=push -f per_page=100 --paginate \
--jq '.workflow_runs[] | {id, conclusion, created_at, html_url}' \
> "${runs_ndjson}" 2>gate_runs_err.log; then
echo "::error title=release_sha_gate::gate could not query GitHub Actions for prior runs of ${WORKFLOW} on SHA ${SHA}: $(cat gate_runs_err.log)"
exit 1
fi
if ! selected_run="$(jq -s -r '
map(select(.conclusion == "success"))
| sort_by(.created_at)
| last
// empty
' "${runs_ndjson}" 2>gate_runs_jq_err.log)"; then
echo "::error title=release_sha_gate::gate could not parse GitHub Actions run data for ${WORKFLOW} on SHA ${SHA}: $(cat gate_runs_jq_err.log)"
exit 1
fi
if [[ -z "${selected_run}" ]]; then
echo "::error title=release_sha_gate::No successful completed push run of ${WORKFLOW} found for tagged SHA ${SHA}. Every tag push requires a prior green branch push CI run for the tagged commit. Recovery: re-run the branch push CI on this SHA (re-tests and re-publishes the preview), then re-tag."
exit 1
fi
run_id="$(jq -r '.id' <<<"${selected_run}")"
run_url="$(jq -r '.html_url' <<<"${selected_run}")"
jobs_ndjson="$(mktemp)"
if ! gh api --method GET "/repos/${REPO}/actions/runs/${run_id}/jobs" -f per_page=100 --paginate \
--jq '.jobs[] | {name, conclusion}' \
> "${jobs_ndjson}" 2>gate_jobs_err.log; then
echo "::error title=release_sha_gate::gate could not query GitHub Actions for jobs of run ${run_url}: $(cat gate_jobs_err.log)"
exit 1
fi
if ! missing="$(jq -s -r '
def has_success($jobs; $pattern):
($jobs | any(.[]; (.name | test($pattern)) and .conclusion == "success"));
. as $jobs
| [
{label: "test / test_unit", pattern: "^test / test_unit$"},
{label: "test / e2e ...", pattern: "^test / (test_)?e2e"},
{label: "build_publish / digest-images", pattern: "^build_publish / digest-images$"},
{label: "build_publish / build-binaries", pattern: "^build_publish / build-binaries$"},
{label: "build_publish / build-images ...", pattern: "^build_publish / build-images"},
{label: "build_publish / publish-helm", pattern: "^build_publish / publish-helm$"}
]
| map(select(has_success($jobs; .pattern) | not))
| map(.label)
| join(", ")
' "${jobs_ndjson}" 2>gate_jobs_jq_err.log)"; then
echo "::error title=release_sha_gate::gate could not parse GitHub Actions job data for run ${run_url}: $(cat gate_jobs_jq_err.log)"
exit 1
fi
if [[ -n "${missing}" ]]; then
echo "::error title=release_sha_gate::Selected run ${run_url} is missing required successful job(s): ${missing}. Recovery: re-run the branch push CI on SHA ${SHA}, then re-tag."
exit 1
fi
echo "Trusted source run: ${run_url}"
check:
needs: ["release_sha_gate"]
permissions:
contents: write # needed to upload SBOM assets to GitHub releases
checks: write # needed for golangci/golangci-lint-action to add code annotations in PRs
timeout-minutes: 40
runs-on: ${{ vars.RUNS_ON_RELEASE_2_12_AMD64 || vars.RUNS_ON_AMD64 || 'ubuntu-24.04' }}
env:
FULL_MATRIX: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'ci/run-full-matrix') }}
ALLOW_PUSH: ${{ github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'ci/force-publish') }}
BUILD: ${{ github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'ci/run-build') || contains(github.event.pull_request.labels.*.name, 'ci/force-publish') }}
FORCE_PUBLISH_FROM_FORK: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'ci/force-publish') && github.event.pull_request.head.repo.full_name != github.repository }}
outputs:
FULL_MATRIX: ${{ env.FULL_MATRIX }}
ALLOW_PUSH: ${{ env.ALLOW_PUSH }}
BUILD: ${{ env.BUILD }}
IMAGES: ${{ steps.metadata.outputs.images }}
REGISTRY: ${{ steps.metadata.outputs.registry }}
VERSION_NAME: ${{ steps.metadata.outputs.version }}
NOTARY_REPOSITORY: ${{ (contains(steps.metadata.outputs.version, 'preview') && 'notary-internal') || 'notary' }}
CLOUDSMITH_REPOSITORY: ${{ steps.metadata.outputs.distribution_repository }}
steps:
- name: "Fail when 'ci/force-publish' label is present on PRs from forks"
if: ${{ fromJSON(env.FORCE_PUBLISH_FROM_FORK) }}
run: |
echo "::error title=Label 'ci/force-publish' cannot be used on PRs from forks::To prevent accidental exposure of secrets, CI won't use repository secrets on pull requests from forks"
exit 1
- name: "Free up disk space for the Runner"
uses: endersonmenezes/free-disk-space@6c4664f43348c8c7011b53488d5ca65e9fc5cd1a # v3.0.0
with:
remove_android: true
remove_dotnet: true
remove_haskell: true
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
- name: Set GOMEMLIMIT dynamically based on available memory
id: set-gomemlimit
run: |
# Get total memory in bytes
set -e
mem_total_kb=$(grep MemTotal /proc/meminfo | awk '{print $2}')
mem_total_bytes=$((mem_total_kb * 1024))
gomemlimit=$((mem_total_bytes * 8 / 10))
echo "GOMEMLIMIT=${gomemlimit}" >> $GITHUB_ENV
echo "Setting GOMEMLIMIT to $(numfmt --to=iec $gomemlimit)"
- uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0
env:
GITHUB_TOKEN: ${{ github.token }}
MISE_DISABLE_TOOLS: "golangci-lint,skaffold"
- uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
if: ${{ github.ref_type != 'tag' }}
env:
GOGC: "80"
with:
args: --fix=false --verbose
version: v2.11.3 # TODO: automate this version update via Renovate
# Lint and "make check" are redundant on a tag: release_sha_gate already
# requires a green branch push run for this exact tree/SHA. We
# intentionally keep metadata and SBOM/SCA on tag refs so release tags
# continue publishing security assets.
- if: ${{ github.ref_type != 'tag' }}
run: |
make clean
- if: ${{ github.ref_type != 'tag' }}
run: |
make check
- name: "Set metadata for downstream jobs"
id: metadata
run: |
echo "images=$(make images/info/release/json)" >> $GITHUB_OUTPUT
echo "registry=$(make docker/info/registry)" >> $GITHUB_OUTPUT
echo "version=$(make build/info/version)" >> $GITHUB_OUTPUT
echo "distribution_repository=$(make build/info/cloudsmith_repository)" >> $GITHUB_OUTPUT
# "make check" puts binaries in ./build/tools (see mk/generate.mk: $(POLICY_GEN)
# and $(RESOURCE_GEN)), which are unnecessarily included in the SBOM.
# Running the SCA step before "make check" might seem like a solution, but it
# generates report files (e.g., sbom.spdx.json, cve-report.json) in the working
# directory. These files cause "make check" to fail, as it checks if no files
# in the repository were modified, deleted, or added after its process.
# It doesn't recognize that the SBOM and CVE report files were added earlier
# and should be ignored. Since we currently can't change the working directory
# for the SCA step, it must run after "make check." Instead, we clean ./build
# after "make check" to exclude tool binaries from the SBOM.
- run: |
make clean/build
- name: "Generate SBOM and CVE report (Software Composition Analysis)"
id: sca-project
uses: Kong/public-shared-actions/security-actions/sca@e33f6f6d5ccdaa8af245f29896a51fada48c5d7e # v4.1.4
env:
SYFT_SOURCE_NAME: ${{ github.repository }}
SYFT_SOURCE_VERSION: ${{ steps.metadata.outputs.version }}
with:
dir: .
config: .syft.yaml
upload-sbom-release-assets: true
test:
needs: ["check"]
uses: ./.github/workflows/_test.yaml
with:
FULL_MATRIX: ${{ needs.check.outputs.FULL_MATRIX }}
IS_RELEASE_TAG: ${{ github.ref_type == 'tag' }}
# Per-arch lookup (each side independent):
# 1. fork PR -> free GitHub-hosted runners (security)
# 2. vars.RUNS_ON_RELEASE_2_12_<ARCH> -> per-branch + per-arch override
# 3. vars.RUNS_ON_<ARCH> -> global per-arch override
# 4. default -> the standard self-hosted Kong pool
# Branch slug is hardcoded because GitHub var names can't contain '-' or '.',
# and inline expressions can't sanitize `github.ref_name`.
RUNNERS_BY_ARCH: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && '{"amd64":"ubuntu-24.04","arm64":"ubuntu-24.04-arm"}' || format('{{"amd64":"{0}","arm64":"{1}"}}', vars.RUNS_ON_RELEASE_2_12_AMD64 || vars.RUNS_ON_AMD64 || 'ubuntu-latest-kong', vars.RUNS_ON_RELEASE_2_12_ARM64 || vars.RUNS_ON_ARM64 || 'ubuntu-latest-arm64-kong') }}
secrets: inherit
build_publish:
permissions:
contents: write # needed to upload SBOM assets to GitHub releases
id-token: write # Required for image signing
needs: ["check", "test"]
uses: ./.github/workflows/_build_publish.yaml
if: ${{ fromJSON(needs.check.outputs.BUILD) }}
with:
FULL_MATRIX: ${{ needs.check.outputs.FULL_MATRIX }}
ALLOW_PUSH: ${{ needs.check.outputs.ALLOW_PUSH }}
IMAGE_ARTIFACT_NAME: "image_artifacts"
BINARY_ARTIFACT_NAME: "binary_artifacts"
IMAGES: ${{ needs.check.outputs.IMAGES }}
REGISTRY: ${{ needs.check.outputs.REGISTRY }}
NOTARY_REPOSITORY: ${{ needs.check.outputs.NOTARY_REPOSITORY }}
VERSION_NAME: ${{ needs.check.outputs.VERSION_NAME }}
secrets: inherit
provenance:
needs: ["check", "build_publish"]
if: ${{ github.ref_type == 'tag' }}
uses: ./.github/workflows/_provenance.yaml
secrets: inherit
permissions:
actions: read # For getting workflow run info to build provenance
contents: write # To add assets to a release
id-token: write # For using token to sign images
packages: write # Required for publishing provenance. Issue: https://github.com/slsa-framework/slsa-github-generator/tree/main/internal/builders/container#known-issues
with:
BINARY_ARTIFACTS_HASH_AS_FILE: ${{ needs.build_publish.outputs.BINARY_ARTIFACT_DIGEST_BASE64 }}
IMAGES: ${{ needs.check.outputs.IMAGES }}
REGISTRY: ${{ needs.check.outputs.REGISTRY }}
NOTARY_REPOSITORY: ${{ needs.check.outputs.NOTARY_REPOSITORY }}
IMAGE_DIGESTS: ${{ needs.build_publish.outputs.IMAGE_DIGESTS }}
distributions:
needs: ["release_sha_gate", "build_publish", "check", "test", "provenance"]
permissions:
id-token: write
timeout-minutes: 10
if: ${{ always() }}
runs-on: ${{ vars.RUNS_ON_RELEASE_2_12_AMD64 || vars.RUNS_ON_AMD64 || 'ubuntu-24.04' }}
env:
SECURITY_ASSETS_DOWNLOAD_PATH: "${{ github.workspace }}/security-assets"
SECURITY_ASSETS_PACKAGE_NAME: "security-assets" # Cloudsmith package for hosting security assets
steps:
- name: "Halt due to previous failures"
run: |-
echo "results: ${{ toJson(needs.*.result) }}"
# for some reason, GH Action will always trigger a downstream job even if there are errors in an dependent job
# so we manually check it here. An example could be found here: https://github.com/kumahq/kuma/actions/runs/7044980149
[[ ${{ contains(needs.*.result, 'failure')|| contains(needs.*.result, 'cancelled') }} == "true" ]] && exit 1
echo "All dependent jobs succeeded"
- name: "Download all SBOM assets"
id: collect_sbom
if: ${{ needs.build_publish.result == 'success' }}
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
path: ${{ env.SECURITY_ASSETS_DOWNLOAD_PATH }}
pattern: "*sbom.{cyclonedx,spdx}.json"
merge-multiple: true
- name: "Download binary artifact provenance"
if: ${{ needs.provenance.result == 'success' && github.ref_type == 'tag' }}
id: collect_provenance
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
path: ${{ env.SECURITY_ASSETS_DOWNLOAD_PATH }}
pattern: ${{ github.event.repository.name }}.intoto.jsonl
merge-multiple: true
- name: "Generate security assets TAR"
if: ${{ needs.build_publish.result == 'success' }}
id: security_assets_metadata
run: |
cd ${{ env.SECURITY_ASSETS_DOWNLOAD_PATH }}
find . -maxdepth 1 -type f \( -name '*sbom.*.json' -o -name '*.intoto.jsonl' \) -print | tar -cvzf ${{ env.SECURITY_ASSETS_PACKAGE_NAME }}.tar.gz -T -
ls -alR .
# Publish aggregated zip file of SBOMs and/or Binary Provenance to artifact regstry
- name: Get Cloudsmith OIDC token
id: cloudsmith_token
if: ${{ needs.provenance.result == 'success' || needs.build_publish.result == 'success' }}
uses: cloudsmith-io/cloudsmith-cli-action@18665afcce9f859312b61989671af9af7402e4fb # v2.0.2
with:
oidc-namespace: kong
oidc-service-slug: ${{ contains(needs.check.outputs.VERSION_NAME, 'preview') && vars.CLOUDSMITH_PREVIEW_SERVICE_ACCOUNT || vars.CLOUDSMITH_PROD_SERVICE_ACCOUNT }}
oidc-auth-only: true
- name: Push security assets to cloudsmith
id: push_security_assets
if: ${{ needs.provenance.result == 'success' || needs.build_publish.result == 'success' }}
uses: cloudsmith-io/action@7af394e0f8add4867bce109385962dafecad1b8d # v0.6.14
with:
api-key: ${{ steps.cloudsmith_token.outputs.oidc-token }}
command: "push"
format: "raw"
owner: "kong"
repo: "${{ needs.check.outputs.CLOUDSMITH_REPOSITORY }}"
version: "${{ needs.check.outputs.VERSION_NAME }}"
file: "${{ env.SECURITY_ASSETS_DOWNLOAD_PATH }}/${{ env.SECURITY_ASSETS_PACKAGE_NAME }}.tar.gz"
name: "${{ env.SECURITY_ASSETS_PACKAGE_NAME }}"
summary: "SLSA security artifacts for ${{ github.repository }}"
description: "SBOM and Binary artifact Provenance for ${{ github.repository }}"
use-executable: "false"