Skip to content

build-test-distribute #34440

build-test-distribute

build-test-distribute #34440

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"