trigger-release-tasks #823
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: "trigger-release-tasks" | |
| # When a version's release-tag build (build-test-distribute) completes, fan out | |
| # the per-version post-build release tasks in parallel: | |
| # - smoke: the downstream enterprise smoke-test workflow (cross-repo dispatch) | |
| # - verify: this repo's own release workflow with check=true (same-repo) | |
| # - installer: runs this repo's own installer.sh end-to-end for the version | |
| # (kuma.io here; developer.konghq.com/mesh once synced into kong-mesh) | |
| # Per-branch/event-driven, not a batch fan-out over all versions. A single guard | |
| # job decides "is this a real release tag?" once; all three tasks read it. | |
| on: | |
| workflow_run: | |
| workflows: ["build-test-distribute"] | |
| types: [completed] | |
| # cuts the dominant noise source: every master/release-* push completion | |
| # otherwise creates a (skipped) run before the guard job's `if` even | |
| # evaluates. Tags aren't filterable here (GitHub doesn't expose tag | |
| # matching for workflow_run), so tag-shape validation still lives in guard. | |
| branches-ignore: ["master", "release-*"] | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Product version to release-task, e.g. 2.14.1 or v2.14.1" | |
| required: true | |
| type: string | |
| run_smoke: | |
| description: "Dispatch the downstream smoke tests" | |
| required: false | |
| type: boolean | |
| default: true | |
| run_verify: | |
| description: "Dispatch artifact verification (release check=true)" | |
| required: false | |
| type: boolean | |
| default: true | |
| run_installer: | |
| description: "Run the installer.sh verification" | |
| required: false | |
| type: boolean | |
| default: true | |
| permissions: {} | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch || inputs.version }} | |
| cancel-in-progress: true | |
| jobs: | |
| guard: | |
| # workflow_run has no reliable tag-level filter at the trigger layer (GitHub | |
| # only supports `branches`/`branches-ignore`, and those are documented as | |
| # branch-only - unreliable/silently-broken for tag-triggered runs). So this | |
| # `if` is the earliest point we can filter, and it excludes the two branch | |
| # names that account for the overwhelming majority of build-test-distribute | |
| # completions (master, release-*) before a runner is even allocated. Actual | |
| # tag-shape validation (vX.Y.Z / 2.9's bare X.Y.Z) still happens in-job below. | |
| if: >- | |
| github.event_name == 'workflow_dispatch' || | |
| ( | |
| github.event.workflow_run.conclusion == 'success' && | |
| github.event.workflow_run.event == 'push' && | |
| github.event.workflow_run.head_branch != 'master' && | |
| !startsWith(github.event.workflow_run.head_branch, 'release-') | |
| ) | |
| timeout-minutes: 5 | |
| runs-on: ${{ vars.RUNS_ON_MASTER_AMD64 || vars.RUNS_ON_AMD64 || 'ubuntu-24.04' }} | |
| permissions: | |
| contents: read # to confirm the ref is really a tag, via the git refs API | |
| outputs: | |
| should_dispatch: ${{ steps.version.outputs.should_dispatch }} | |
| product_version: ${{ steps.version.outputs.product_version }} | |
| steps: | |
| - name: "Resolve version" | |
| id: version | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| MANUAL_VERSION: ${{ inputs.version }} | |
| HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} | |
| HEAD_SHA: ${{ github.event.workflow_run.head_sha }} | |
| run: | | |
| set -euo pipefail | |
| # v-prefix optional: release-2.9 never got the v-prefix backport and | |
| # tags bare; manual re-runs also conventionally use the bare form. | |
| RAW="${MANUAL_VERSION:-${HEAD_BRANCH}}" | |
| if [[ "${RAW}" =~ ^v?([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then | |
| PRODUCT_VERSION="${BASH_REMATCH[1]}" | |
| else | |
| echo "'${RAW}' is not a recognized release version/tag; skipping release-task dispatch." | |
| echo "should_dispatch=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if [[ -n "${MANUAL_VERSION}" ]]; then | |
| # deliberate operator override; may target an archived version | |
| echo "should_dispatch=true" >> "$GITHUB_OUTPUT" | |
| echo "product_version=${PRODUCT_VERSION}" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # confirm head_branch is this tag, not a same-named branch, and it | |
| # still points at the built commit, not one it moved to since | |
| TAG="${RAW}" | |
| # keep stderr in the run log: a missing tag (404) is the intended | |
| # clean skip, but a rate-limit/network/auth failure must not vanish | |
| # silently behind the same should_dispatch=false line. | |
| if ! TAG_REF_JSON="$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG}" 2>/tmp/gh_tag_ref_err)"; then | |
| echo "'${TAG}' is not an existing tag ref (or the lookup failed); skipping release-task dispatch." >&2 | |
| cat /tmp/gh_tag_ref_err >&2 || true | |
| echo "should_dispatch=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| OBJ_TYPE="$(jq -r '.object.type' <<< "${TAG_REF_JSON}")" | |
| OBJ_SHA="$(jq -r '.object.sha' <<< "${TAG_REF_JSON}")" | |
| # annotated tags point at a tag object, not the commit directly | |
| if [[ "${OBJ_TYPE}" == "tag" ]]; then | |
| OBJ_SHA="$(gh api "repos/${GITHUB_REPOSITORY}/git/tags/${OBJ_SHA}" --jq '.object.sha')" | |
| fi | |
| if [[ "${OBJ_SHA}" != "${HEAD_SHA}" ]]; then | |
| echo "'${TAG}' does not currently point at built commit ${HEAD_SHA}; skipping release-task dispatch." | |
| echo "should_dispatch=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "should_dispatch=true" >> "$GITHUB_OUTPUT" | |
| echo "product_version=${PRODUCT_VERSION}" >> "$GITHUB_OUTPUT" | |
| smoke: | |
| needs: guard | |
| # workflow_run carries no dispatch inputs, so run_smoke is empty there and | |
| # must not gate the auto path; only the manual path honours the toggle. | |
| if: >- | |
| needs.guard.outputs.should_dispatch == 'true' && | |
| (github.event_name != 'workflow_dispatch' || inputs.run_smoke) | |
| timeout-minutes: 5 | |
| runs-on: ${{ vars.RUNS_ON_MASTER_AMD64 || vars.RUNS_ON_AMD64 || 'ubuntu-24.04' }} | |
| permissions: {} | |
| steps: | |
| - name: "Trigger downstream smoke test" | |
| env: | |
| # smoke.yaml has no repository_dispatch trigger, so this dispatches | |
| # via repository_dispatch to kong-mesh-smoke instead, which listens | |
| # for KUMA_SMOKE_TEST via dispatch-kuma-smoke.yaml and forwards to | |
| # smoke.yaml with the default GITHUB_TOKEN (same-repo, no cross-org | |
| # auth needed on that side). Needs a classic PAT with `repo` scope, | |
| # SSO-authorized for the Kong org - fine-grained PATs aren't enabled | |
| # there for cross-org repo selection. | |
| GH_TOKEN: ${{ secrets.KONG_MESH_SMOKE_DISPATCH_TOKEN }} | |
| NOTIFY_OWNER: ${{ secrets.NOTIFY_OWNER }} | |
| SMOKE_REPO: kong-mesh-smoke | |
| PRODUCT_VERSION: ${{ needs.guard.outputs.product_version }} | |
| run: | | |
| set -euo pipefail | |
| # product_name is the built edition, which is the repo we run in. | |
| case "${GITHUB_REPOSITORY}" in | |
| */kong-mesh) PRODUCT_NAME=kong-mesh ;; | |
| *) PRODUCT_NAME=kuma ;; | |
| esac | |
| jq -n \ | |
| --arg product_name "${PRODUCT_NAME}" \ | |
| --arg product_version "${PRODUCT_VERSION}" \ | |
| '{ | |
| event_type: "KUMA_SMOKE_TEST", | |
| client_payload: { | |
| product_name: $product_name, | |
| product_version: $product_version, | |
| k8s_provider_k3d: true, | |
| k8s_provider_gke: true, | |
| k8s_provider_eks: true, | |
| uni_provider_gcp: true, | |
| uni_provider_aws: true, | |
| run_tf_provider_tests: false | |
| } | |
| }' | gh api "repos/${NOTIFY_OWNER}/${SMOKE_REPO}/dispatches" --input - | |
| verify: | |
| needs: guard | |
| # see smoke job: run_verify only gates the manual path. | |
| if: >- | |
| needs.guard.outputs.should_dispatch == 'true' && | |
| (github.event_name != 'workflow_dispatch' || inputs.run_verify) | |
| timeout-minutes: 5 | |
| runs-on: ${{ vars.RUNS_ON_MASTER_AMD64 || vars.RUNS_ON_AMD64 || 'ubuntu-24.04' }} | |
| permissions: | |
| # same-repo dispatch needs no cross-org token, unlike smoke; the default | |
| # GITHUB_TOKEN only gains workflow_dispatch rights with actions:write. | |
| actions: write | |
| steps: | |
| - name: "Trigger artifact verification" | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PRODUCT_VERSION: ${{ needs.guard.outputs.product_version }} | |
| DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} | |
| run: | | |
| set -euo pipefail | |
| # same display name ("release") both sides, but a different filename in | |
| # the enterprise fork; dispatch by filename to stay unambiguous. | |
| case "${GITHUB_REPOSITORY}" in | |
| */kong-mesh) RELEASE_WORKFLOW=kuma-release.yaml ;; | |
| *) RELEASE_WORKFLOW=release.yaml ;; | |
| esac | |
| gh workflow run "${RELEASE_WORKFLOW}" \ | |
| --repo "${GITHUB_REPOSITORY}" \ | |
| --ref "${DEFAULT_BRANCH}" \ | |
| --field release="${PRODUCT_VERSION}" \ | |
| --field check=true | |
| installer: | |
| needs: guard | |
| # see smoke job: run_installer only gates the manual path. | |
| if: >- | |
| needs.guard.outputs.should_dispatch == 'true' && | |
| (github.event_name != 'workflow_dispatch' || inputs.run_installer) | |
| timeout-minutes: 5 | |
| runs-on: ${{ vars.RUNS_ON_MASTER_AMD64 || vars.RUNS_ON_AMD64 || 'ubuntu-24.04' }} | |
| permissions: {} | |
| steps: | |
| - name: "Run installer.sh for the released version" | |
| env: | |
| PRODUCT_VERSION: ${{ needs.guard.outputs.product_version }} | |
| run: | | |
| set -euo pipefail | |
| # developer.konghq.com/mesh/installer.sh is a thin wrapper that pipes | |
| # back through kuma.io/installer.sh with PRODUCT_NAME/REPO overrides, | |
| # so hitting the right host is what actually exercises each product's | |
| # own published binaries. | |
| case "${GITHUB_REPOSITORY}" in | |
| */kong-mesh) INSTALLER_URL="https://developer.konghq.com/mesh/installer.sh" ;; | |
| *) INSTALLER_URL="https://kuma.io/installer.sh" ;; | |
| esac | |
| # installer.sh itself exits non-zero (via its `err` helper) when the | |
| # version's binary isn't published, so a clean run is the assertion; | |
| # still grep its "has been downloaded!" line as a positive check that | |
| # it actually reached and confirmed PRODUCT_VERSION rather than | |
| # exiting 0 some other way. | |
| if ! OUTPUT="$(curl --fail --silent --show-error --location "${INSTALLER_URL}" | VERSION="${PRODUCT_VERSION}" sh - 2>&1)"; then | |
| echo "${OUTPUT}" | |
| echo "installer.sh failed for version ${PRODUCT_VERSION} via ${INSTALLER_URL}" >&2 | |
| exit 1 | |
| fi | |
| echo "${OUTPUT}" | |
| if ! grep -qF "${PRODUCT_VERSION} has been downloaded" <<< "${OUTPUT}"; then | |
| echo "installer.sh did not confirm ${PRODUCT_VERSION} was downloaded via ${INSTALLER_URL}" >&2 | |
| exit 1 | |
| fi |