build-test-distribute #34440
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |