From c5f7902a39fd06a9cf04f55eaad3a3ecf910bfe4 Mon Sep 17 00:00:00 2001 From: Erik Burton Date: Thu, 10 Sep 2026 16:00:58 -0400 Subject: [PATCH 1/5] feat(setup-github-token): support invoke-gati-v2 --- .changeset/setup-github-token-gati-v2.md | 8 + .../workflows/setup-github-token-validate.yml | 317 ++++++++++++++++++ actions/setup-github-token/README.md | 89 +++++ actions/setup-github-token/action.yml | 66 +++- .../scripts/resolve-gati-version.sh | 80 +++++ 5 files changed, 551 insertions(+), 9 deletions(-) create mode 100644 .changeset/setup-github-token-gati-v2.md create mode 100644 .github/workflows/setup-github-token-validate.yml create mode 100755 actions/setup-github-token/scripts/resolve-gati-version.sh diff --git a/.changeset/setup-github-token-gati-v2.md b/.changeset/setup-github-token-gati-v2.md new file mode 100644 index 000000000..ec1ea701d --- /dev/null +++ b/.changeset/setup-github-token-gati-v2.md @@ -0,0 +1,8 @@ +--- +"setup-github-token": minor +--- + +Support GATI v2 behind a staged rollout. Adds a `profile` input that selects +GATI v2, a `gati-version` input to force either flow, and a `gati-version` +output. `aws-role-arn` and `aws-lambda-url` are now optional, and required only +when `profile` is not set. diff --git a/.github/workflows/setup-github-token-validate.yml b/.github/workflows/setup-github-token-validate.yml new file mode 100644 index 000000000..317469d34 --- /dev/null +++ b/.github/workflows/setup-github-token-validate.yml @@ -0,0 +1,317 @@ +# Exercises actions/setup-github-token across both GATI flows. +# +# This repository is public, so these workflow logs are public. Keep the checks +# below to assertions on emptiness and equality — never print tokens or role +# ARNs. +name: setup-github-token validate + +on: + pull_request: + paths: + - "actions/setup-github-token/**" + - ".github/workflows/setup-github-token-validate.yml" + push: + branches: + - main + paths: + - "actions/setup-github-token/**" + - ".github/workflows/setup-github-token-validate.yml" + workflow_dispatch: + +permissions: {} + +jobs: + decision-logic: + name: Version decision logic + runs-on: ubuntu-latest + permissions: + contents: read + env: + ARN: arn:aws:iam::123456789012:role/example + URL: https://example.lambda-url.us-west-2.on.aws/ + steps: + - name: Checkout repo + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Check the decision table + working-directory: actions/setup-github-token/scripts + run: | + set -uo pipefail + + failures=0 + + # check [NAME=VALUE ...] + check() { + local description="$1" want_exit="$2" want_outputs="$3" + shift 3 + + local output_file got_exit got_outputs + output_file="$(mktemp)" + env GITHUB_OUTPUT="${output_file}" "$@" \ + ./resolve-gati-version.sh >/dev/null 2>&1 && got_exit=0 || got_exit=1 + got_outputs="$(paste -sd' ' "${output_file}")" + + if [[ "${got_exit}" == "${want_exit}" && "${got_outputs}" == "${want_outputs}" ]]; then + echo "ok ${description}" + return + fi + + echo "FAIL ${description}" + echo " want: exit=${want_exit} outputs='${want_outputs}'" + echo " got: exit=${got_exit} outputs='${got_outputs}'" + failures=$((failures + 1)) + } + + # No pins, no rollout: every case below opts into what it exercises. + base=( + GITHUB_REPOSITORY=owner/repository + ROLLOUT_PERCENTAGE=0 + FORCE_V1_REPOSITORIES= + FORCE_V2_REPOSITORIES= + ) + aws=(AWS_ROLE_ARN="${ARN}" AWS_LAMBDA_URL="${URL}") + + check "gati-version v1 selects v1" \ + 0 "version=v1" \ + "${base[@]}" "${aws[@]}" GATI_VERSION=v1 + + check "gati-version v1 with a profile is rejected" \ + 1 "" \ + "${base[@]}" GATI_VERSION=v1 PROFILE=example + + check "gati-version v2 derives a v1 profile from the role ARN" \ + 0 "version=v2 profile=v1/${ARN}" \ + "${base[@]}" "${aws[@]}" GATI_VERSION=v2 + + check "gati-version v2 passes an explicit profile through" \ + 0 "version=v2 profile=example" \ + "${base[@]}" GATI_VERSION=v2 PROFILE=example + + check "an unknown gati-version is rejected" \ + 1 "" \ + "${base[@]}" "${aws[@]}" GATI_VERSION=v3 + + check "a profile alone selects v2, without the aws inputs" \ + 0 "version=v2 profile=presets/example" \ + "${base[@]}" PROFILE=presets/example + + check "the v1 pin wins over the v2 pin" \ + 0 "version=v1" \ + "${base[@]}" "${aws[@]}" ROLLOUT_PERCENTAGE=100 \ + FORCE_V1_REPOSITORIES=owner/repository \ + FORCE_V2_REPOSITORIES=owner/repository + + check "the v2 pin overrides a 0% rollout" \ + 0 "version=v2 profile=v1/${ARN}" \ + "${base[@]}" "${aws[@]}" \ + FORCE_V2_REPOSITORIES="$(printf 'other/repository\nowner/repository')" + + check "the v1 pin overrides a 100% rollout" \ + 0 "version=v1" \ + "${base[@]}" "${aws[@]}" ROLLOUT_PERCENTAGE=100 \ + FORCE_V1_REPOSITORIES="$(printf 'other/repository\nowner/repository')" + + check "a pin matches whole lines only" \ + 0 "version=v1" \ + "${base[@]}" "${aws[@]}" FORCE_V2_REPOSITORIES=owner/repositories + + check "a 0% rollout selects v1" \ + 0 "version=v1" \ + "${base[@]}" "${aws[@]}" + + check "a 100% rollout selects v2" \ + 0 "version=v2 profile=v1/${ARN}" \ + "${base[@]}" "${aws[@]}" ROLLOUT_PERCENTAGE=100 + + check "aws-role-arn is required without a profile" \ + 1 "" \ + "${base[@]}" AWS_LAMBDA_URL="${URL}" + + check "aws-lambda-url is required without a profile" \ + 1 "" \ + "${base[@]}" AWS_ROLE_ARN="${ARN}" + + if ((failures > 0)); then + echo "${failures} case(s) failed." + exit 1 + fi + echo "All cases passed." + + # Sweeping the percentage rather than recomputing the hash keeps this + # independent of how a repository is bucketed. A single flip proves the + # bucket is stable across invocations and that the threshold is a + # one-directional cutover. + - name: Check the rollout threshold is a stable, one-way cutover + working-directory: actions/setup-github-token/scripts + run: | + set -euo pipefail + + output_file="$(mktemp)" + previous="" + flips=0 + + for percentage in $(seq 0 100); do + env GITHUB_OUTPUT="${output_file}" \ + GITHUB_REPOSITORY=owner/repository \ + ROLLOUT_PERCENTAGE="${percentage}" \ + FORCE_V1_REPOSITORIES= \ + FORCE_V2_REPOSITORIES= \ + AWS_ROLE_ARN="${ARN}" \ + AWS_LAMBDA_URL="${URL}" \ + ./resolve-gati-version.sh >/dev/null + version="$(sed -n 's/^version=//p' "${output_file}")" + : >"${output_file}" + + if [[ -n "${previous}" && "${version}" != "${previous}" ]]; then + flips=$((flips + 1)) + echo "Flipped to ${version} at ${percentage}%." + fi + previous="${version}" + + if [[ "${percentage}" == "0" && "${version}" != "v1" ]]; then + echo "Expected v1 at 0%, got ${version}." + exit 1 + fi + done + + if [[ "${previous}" != "v2" ]]; then + echo "Expected v2 at 100%, got ${previous}." + exit 1 + fi + + if [[ "${flips}" != "1" ]]; then + echo "Expected exactly 1 flip across the sweep, got ${flips}." + exit 1 + fi + echo "The rollout crossed from v1 to v2 exactly once." + + v1-forced: + name: GATI v1 (forced) + # Repository secrets are unavailable to pull requests from forks. + if: + github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - name: Checkout repo (needed to reference local action) + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Setup GitHub token + id: setup + uses: ./actions/setup-github-token + with: + gati-version: v1 + aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_ARN_GATI }} + aws-lambda-url: ${{ secrets.AWS_LAMBDA_URL_GATI }} + set-git-config: "true" + + - name: Check v1 issued the token and configured git + env: + ACCESS_TOKEN: ${{ steps.setup.outputs.access-token }} + GATI_VERSION: ${{ steps.setup.outputs.gati-version }} + run: | + set -euo pipefail + + if [[ "${GATI_VERSION}" != "v1" ]]; then + echo "Expected gati-version 'v1', got '${GATI_VERSION}'." + exit 1 + fi + + if [[ -z "${ACCESS_TOKEN}" ]]; then + echo "No access token was returned." + exit 1 + fi + + # The insteadOf key embeds the token, so match without printing it. + if ! git config --global --list --name-only | grep -q "insteadof"; then + echo "set-git-config did not configure git." + exit 1 + fi + echo "GATI v1 issued a token and configured git." + + v2-rollout: + name: GATI v2 (via rollout) + if: + github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - name: Checkout repo (needed to reference local action) + uses: actions/checkout@v7 + with: + persist-credentials: false + + # No profile, so the rollout decides. This repository is pinned to v2. + - name: Setup GitHub token + id: setup + uses: ./actions/setup-github-token + with: + aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_ARN_GATI }} + aws-lambda-url: ${{ secrets.AWS_LAMBDA_URL_GATI }} + + - name: Check v2 issued the token + env: + ACCESS_TOKEN: ${{ steps.setup.outputs.access-token }} + GATI_VERSION: ${{ steps.setup.outputs.gati-version }} + run: | + set -euo pipefail + + if [[ "${GATI_VERSION}" != "v2" ]]; then + echo "Expected gati-version 'v2', got '${GATI_VERSION}'." + exit 1 + fi + + if [[ -z "${ACCESS_TOKEN}" ]]; then + echo "No access token was returned." + exit 1 + fi + echo "GATI v2 issued a token from the role ARN it was given." + + v2-explicit-profile: + name: GATI v2 (explicit profile) + if: + github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - name: Checkout repo (needed to reference local action) + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Setup GitHub token + id: setup + uses: ./actions/setup-github-token + with: + profile: v1/${{ secrets.AWS_OIDC_IAM_ROLE_ARN_GATI }} + + - name: Check v2 issued the token + env: + ACCESS_TOKEN: ${{ steps.setup.outputs.access-token }} + GATI_VERSION: ${{ steps.setup.outputs.gati-version }} + run: | + set -euo pipefail + + if [[ "${GATI_VERSION}" != "v2" ]]; then + echo "Expected gati-version 'v2', got '${GATI_VERSION}'." + exit 1 + fi + + if [[ -z "${ACCESS_TOKEN}" ]]; then + echo "No access token was returned." + exit 1 + fi + echo "GATI v2 issued a token from the profile it was given." diff --git a/actions/setup-github-token/README.md b/actions/setup-github-token/README.md index b45ee56f1..e5b758963 100644 --- a/actions/setup-github-token/README.md +++ b/actions/setup-github-token/README.md @@ -1 +1,90 @@ # setup-github-token action + +Gets a GitHub installation access token from GATI, and optionally configures git +to authenticate with it. + +Requires `id-token: write` on the calling job — v1 uses it to assume the AWS +role, v2 to authenticate to GATI directly. + +## Which flow runs + +- **v1** ([`invoke-gati`](../invoke-gati/)) assumes `aws-role-arn`, then calls + the lambda at `aws-lambda-url`. +- **v2** ([`invoke-gati-v2`](../invoke-gati-v2/)) requests a `profile` over + GitHub OIDC, with no AWS credentials. + +Passing `profile` selects v2. Otherwise a staged rollout decides: the repository +name is hashed into a bucket of 0–99, and uses v2 when that bucket is below the +rollout percentage. Since the bucket never changes, a repository moves to v2 +once and stays there. The percentage and the two pin lists that override it are +the `ROLLOUT_PERCENTAGE`, `FORCE_V2_REPOSITORIES`, and `FORCE_V1_REPOSITORIES` +env values on the `Resolve GATI version` step in [`action.yml`](./action.yml); +changing them needs a release. `gati-version` overrides all of the above. + +## Migrated v1 roles + +GATI v1 role configuration was carried into v2 automatically, keyed by the +role's IAM ARN and addressable as the profile `v1/`. So v2 needs +no new configuration to take over from v1: given no `profile`, it derives one +from the `aws-role-arn` the caller already passes and returns a token with the +same permissions v1 would have. A caller only needs a real `profile` to get +scopes its v1 role does not already have. + +## Inputs + +Either `profile` or the v1 inputs must be provided. + +| Name | Description | Defaulted | +| ---------------- | --------------------------------------------------------- | -------------- | +| `set-git-config` | Configure git to use the token for `https://github.com/`. | ✅ - `"false"` | +| `gati-version` | Force `v1` or `v2`, bypassing the rollout. | | + +### v2 + +| Name | Description | +| --------- | ---------------------------------------------- | +| `profile` | Token profile to request. Selects v2 when set. | + +### v1 + +Also read by v2 to derive a profile, see +[Migrated v1 roles](#migrated-v1-roles). Ignored when `profile` is set. + +| Name | Description | Defaulted | +| --------------------------- | ----------------------------------------------- | -------------------------------- | +| `aws-role-arn` | ARN of the role that can get a token from GATI. | | +| `aws-lambda-url` | URL of the GATI lambda function. | | +| `aws-region` | AWS region. | | +| `aws-role-duration-seconds` | Duration of the assumed role, in seconds. | ✅ - `"900"` | +| `role-session-name` | Session name, truncated to 64 characters. | ✅ - run id, run number, and job | + +## Outputs + +| Name | Description | +| -------------- | --------------------------------------------------------------------------------------------------------------- | +| `access-token` | The token. A v2 token is revoked when its job ends, so request one per job rather than passing it between jobs. | +| `gati-version` | The GATI version that issued the token, `v1` or `v2`. | + +## Example + +```yaml +jobs: + example: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - name: Setup GitHub token + id: setup-github-token + uses: smartcontractkit/.github/actions/setup-github-token@setup-github-token/v1 + with: + aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_ARN_GATI }} + aws-lambda-url: ${{ secrets.AWS_LAMBDA_URL_GATI }} + aws-region: us-west-2 + + - name: Use the token + env: + GH_TOKEN: ${{ steps.setup-github-token.outputs.access-token }} + run: gh repo view smartcontractkit/.github +``` diff --git a/actions/setup-github-token/action.yml b/actions/setup-github-token/action.yml index 269ad47c3..b5a3a1479 100644 --- a/actions/setup-github-token/action.yml +++ b/actions/setup-github-token/action.yml @@ -2,15 +2,27 @@ name: Setup GitHub Token description: Setup a GitHub Token from GATI inputs: + profile: + description: + GATI v2 token profile to request. When set, GATI v2 is used and the aws-* + inputs are ignored. + required: false + gati-version: + description: + Force "v1" or "v2", bypassing the staged rollout. Leave unset to let the + rollout decide. + required: false aws-role-arn: - description: ARN of role capable of getting token from GATI - required: true + description: + ARN of role capable of getting token from GATI. Required unless profile is + set. + required: false aws-lambda-url: - description: URL of GATI lambda function - required: true + description: URL of GATI lambda function. Required unless profile is set. + required: false aws-region: - description: AWS region - required: true + description: AWS region. Required unless profile is set. + required: false aws-role-duration-seconds: description: Duration of role in seconds required: false @@ -26,15 +38,38 @@ inputs: outputs: access-token: - value: ${{ steps.get-gh-token.outputs.access-token }} + value: + ${{ steps.get-gh-token.outputs.access-token || + steps.get-gh-token-v2.outputs.access-token }} description: The github access token that has permissions reflecting the current AWS role value + gati-version: + value: ${{ steps.resolve-gati-version.outputs.version }} + description: The GATI version that issued the token, "v1" or "v2" runs: using: composite steps: + - name: Resolve GATI version + id: resolve-gati-version + shell: bash + env: + PROFILE: ${{ inputs.profile }} + GATI_VERSION: ${{ inputs.gati-version }} + AWS_ROLE_ARN: ${{ inputs.aws-role-arn }} + AWS_LAMBDA_URL: ${{ inputs.aws-lambda-url }} + # Rollout configuration. Repositories not named in either list are + # bucketed by the hash of their name, and use v2 when their bucket is + # below the percentage. + ROLLOUT_PERCENTAGE: "0" + FORCE_V2_REPOSITORIES: | + smartcontractkit/.github + FORCE_V1_REPOSITORIES: "" + run: ${{ github.action_path }}/scripts/resolve-gati-version.sh + - name: Check the role session name lengths and truncate if needed + if: steps.resolve-gati-version.outputs.version == 'v1' shell: bash id: role-session-name env: @@ -47,6 +82,7 @@ runs: fi - name: Assume role capable of getting token from gati + if: steps.resolve-gati-version.outputs.version == 'v1' uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0 with: aws-region: ${{ inputs.aws-region }} @@ -56,16 +92,28 @@ runs: ${{ steps.role-session-name.outputs.role-session-name }} role-to-assume: ${{ inputs.aws-role-arn }} - - name: Get github token from gati + - name: Get github token from gati (v1) id: get-gh-token + if: steps.resolve-gati-version.outputs.version == 'v1' uses: smartcontractkit/.github/actions/invoke-gati@invoke-gati/0.3.0 with: url: ${{ inputs.aws-lambda-url }} + - name: Get github token from gati (v2) + id: get-gh-token-v2 + if: steps.resolve-gati-version.outputs.version == 'v2' + uses: smartcontractkit/.github/actions/invoke-gati-v2@feat/invoke-gati-v2 + with: + profile: ${{ steps.resolve-gati-version.outputs.profile }} + - name: Configure github token if: inputs.set-git-config == 'true' shell: bash + env: + ACCESS_TOKEN: + ${{ steps.get-gh-token.outputs.access-token || + steps.get-gh-token-v2.outputs.access-token }} run: | git config --global \ - url."https://x-access-token:${{ steps.get-gh-token.outputs.access-token }}@github.com/".insteadOf \ + url."https://x-access-token:${ACCESS_TOKEN}@github.com/".insteadOf \ "https://github.com/" diff --git a/actions/setup-github-token/scripts/resolve-gati-version.sh b/actions/setup-github-token/scripts/resolve-gati-version.sh new file mode 100755 index 000000000..e9961f245 --- /dev/null +++ b/actions/setup-github-token/scripts/resolve-gati-version.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +# Selects between GATI v1 (assume an AWS role, then call the v1 lambda) and GATI +# v2 (GitHub OIDC, no AWS credentials), then emits the version and, for v2, the +# profile to request. + +set -euo pipefail + +PROFILE="${PROFILE:-}" +GATI_VERSION="${GATI_VERSION:-}" +AWS_ROLE_ARN="${AWS_ROLE_ARN:-}" +AWS_LAMBDA_URL="${AWS_LAMBDA_URL:-}" + +VERSION="" +REASON="" + +resolve_version() { + if [[ -n "${GATI_VERSION}" ]]; then + if [[ "${GATI_VERSION}" != "v1" && "${GATI_VERSION}" != "v2" ]]; then + echo "::error::gati-version must be 'v1' or 'v2', got '${GATI_VERSION}'." + exit 1 + fi + VERSION="${GATI_VERSION}" + REASON="gati-version input" + return + fi + + if [[ -n "${PROFILE}" ]]; then + VERSION="v2" + REASON="profile input" + return + fi + + # A repository pinned to both lists stays on v1, so that pinning a broken + # repository back to v1 does not require also editing the v2 list. + if grep -qxF "${GITHUB_REPOSITORY}" <<<"${FORCE_V1_REPOSITORIES}"; then + VERSION="v1" + REASON="repository is pinned to v1" + return + fi + + if grep -qxF "${GITHUB_REPOSITORY}" <<<"${FORCE_V2_REPOSITORIES}"; then + VERSION="v2" + REASON="repository is pinned to v2" + return + fi + + local bucket + bucket=$(($(printf '%s' "${GITHUB_REPOSITORY}" | cksum | cut -d ' ' -f 1) % 100)) + REASON="bucket ${bucket}, rollout at ${ROLLOUT_PERCENTAGE}%" + if ((bucket < ROLLOUT_PERCENTAGE)); then + VERSION="v2" + else + VERSION="v1" + fi +} + +resolve_version + +if [[ "${VERSION}" == "v1" && -n "${PROFILE}" ]]; then + echo "::error::profile is only supported by GATI v2, but v1 was selected (${REASON}). Remove the profile input, or set gati-version to 'v2'." + exit 1 +fi + +if [[ -z "${PROFILE}" ]]; then + if [[ -z "${AWS_ROLE_ARN}" || -z "${AWS_LAMBDA_URL}" ]]; then + echo "::error::aws-role-arn and aws-lambda-url are required when profile is not set." + exit 1 + fi + # Only read by the v2 flow, which migrates a v1 role ARN as a v1 profile. + PROFILE="v1/${AWS_ROLE_ARN}" +fi + +echo "Using GATI ${VERSION} (${REASON})." +echo "version=${VERSION}" >>"$GITHUB_OUTPUT" + +if [[ "${VERSION}" == "v2" ]]; then + # Written without tee: the profile can embed an IAM role ARN. + echo "profile=${PROFILE}" >>"$GITHUB_OUTPUT" +fi From f95e83db104b9b1cfeded1ae6c353407dcac1c9d Mon Sep 17 00:00:00 2001 From: Erik Burton Date: Thu, 10 Sep 2026 16:07:05 -0400 Subject: [PATCH 2/5] fix: default aws-region --- actions/setup-github-token/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/actions/setup-github-token/action.yml b/actions/setup-github-token/action.yml index b5a3a1479..216488128 100644 --- a/actions/setup-github-token/action.yml +++ b/actions/setup-github-token/action.yml @@ -23,6 +23,7 @@ inputs: aws-region: description: AWS region. Required unless profile is set. required: false + default: "us-west-2" aws-role-duration-seconds: description: Duration of role in seconds required: false From b6222baf114c2737a416596900a098b3f36281cb Mon Sep 17 00:00:00 2001 From: Erik Burton Date: Thu, 10 Sep 2026 17:10:56 -0400 Subject: [PATCH 3/5] test: .github-internal --- actions/setup-github-token/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/setup-github-token/action.yml b/actions/setup-github-token/action.yml index 216488128..7acb6ab2a 100644 --- a/actions/setup-github-token/action.yml +++ b/actions/setup-github-token/action.yml @@ -103,7 +103,7 @@ runs: - name: Get github token from gati (v2) id: get-gh-token-v2 if: steps.resolve-gati-version.outputs.version == 'v2' - uses: smartcontractkit/.github/actions/invoke-gati-v2@feat/invoke-gati-v2 + uses: smartcontractkit/.github-internal/actions/invoke-gati-v2@chore/invoke-gati-v2-scaffold with: profile: ${{ steps.resolve-gati-version.outputs.profile }} From 80e253bfe7e7d4616899af77a51dd1088a78de35 Mon Sep 17 00:00:00 2001 From: Erik Burton Date: Mon, 14 Sep 2026 17:05:38 -0400 Subject: [PATCH 4/5] fix: point at proper repo/tag --- actions/setup-github-token/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/setup-github-token/action.yml b/actions/setup-github-token/action.yml index 7acb6ab2a..66a1a775b 100644 --- a/actions/setup-github-token/action.yml +++ b/actions/setup-github-token/action.yml @@ -103,7 +103,7 @@ runs: - name: Get github token from gati (v2) id: get-gh-token-v2 if: steps.resolve-gati-version.outputs.version == 'v2' - uses: smartcontractkit/.github-internal/actions/invoke-gati-v2@chore/invoke-gati-v2-scaffold + uses: smartcontractkit/.github/actions/invoke-gati-v2@invoke-gati-v2/0.1.0 with: profile: ${{ steps.resolve-gati-version.outputs.profile }} From b261e5d5b891dadbdeb2c5bc884e1fb89e5c9ddd Mon Sep 17 00:00:00 2001 From: Erik Burton Date: Tue, 15 Sep 2026 11:03:19 -0400 Subject: [PATCH 5/5] remove temporary validation workflow --- .../workflows/setup-github-token-validate.yml | 317 ------------------ 1 file changed, 317 deletions(-) delete mode 100644 .github/workflows/setup-github-token-validate.yml diff --git a/.github/workflows/setup-github-token-validate.yml b/.github/workflows/setup-github-token-validate.yml deleted file mode 100644 index 317469d34..000000000 --- a/.github/workflows/setup-github-token-validate.yml +++ /dev/null @@ -1,317 +0,0 @@ -# Exercises actions/setup-github-token across both GATI flows. -# -# This repository is public, so these workflow logs are public. Keep the checks -# below to assertions on emptiness and equality — never print tokens or role -# ARNs. -name: setup-github-token validate - -on: - pull_request: - paths: - - "actions/setup-github-token/**" - - ".github/workflows/setup-github-token-validate.yml" - push: - branches: - - main - paths: - - "actions/setup-github-token/**" - - ".github/workflows/setup-github-token-validate.yml" - workflow_dispatch: - -permissions: {} - -jobs: - decision-logic: - name: Version decision logic - runs-on: ubuntu-latest - permissions: - contents: read - env: - ARN: arn:aws:iam::123456789012:role/example - URL: https://example.lambda-url.us-west-2.on.aws/ - steps: - - name: Checkout repo - uses: actions/checkout@v7 - with: - persist-credentials: false - - - name: Check the decision table - working-directory: actions/setup-github-token/scripts - run: | - set -uo pipefail - - failures=0 - - # check [NAME=VALUE ...] - check() { - local description="$1" want_exit="$2" want_outputs="$3" - shift 3 - - local output_file got_exit got_outputs - output_file="$(mktemp)" - env GITHUB_OUTPUT="${output_file}" "$@" \ - ./resolve-gati-version.sh >/dev/null 2>&1 && got_exit=0 || got_exit=1 - got_outputs="$(paste -sd' ' "${output_file}")" - - if [[ "${got_exit}" == "${want_exit}" && "${got_outputs}" == "${want_outputs}" ]]; then - echo "ok ${description}" - return - fi - - echo "FAIL ${description}" - echo " want: exit=${want_exit} outputs='${want_outputs}'" - echo " got: exit=${got_exit} outputs='${got_outputs}'" - failures=$((failures + 1)) - } - - # No pins, no rollout: every case below opts into what it exercises. - base=( - GITHUB_REPOSITORY=owner/repository - ROLLOUT_PERCENTAGE=0 - FORCE_V1_REPOSITORIES= - FORCE_V2_REPOSITORIES= - ) - aws=(AWS_ROLE_ARN="${ARN}" AWS_LAMBDA_URL="${URL}") - - check "gati-version v1 selects v1" \ - 0 "version=v1" \ - "${base[@]}" "${aws[@]}" GATI_VERSION=v1 - - check "gati-version v1 with a profile is rejected" \ - 1 "" \ - "${base[@]}" GATI_VERSION=v1 PROFILE=example - - check "gati-version v2 derives a v1 profile from the role ARN" \ - 0 "version=v2 profile=v1/${ARN}" \ - "${base[@]}" "${aws[@]}" GATI_VERSION=v2 - - check "gati-version v2 passes an explicit profile through" \ - 0 "version=v2 profile=example" \ - "${base[@]}" GATI_VERSION=v2 PROFILE=example - - check "an unknown gati-version is rejected" \ - 1 "" \ - "${base[@]}" "${aws[@]}" GATI_VERSION=v3 - - check "a profile alone selects v2, without the aws inputs" \ - 0 "version=v2 profile=presets/example" \ - "${base[@]}" PROFILE=presets/example - - check "the v1 pin wins over the v2 pin" \ - 0 "version=v1" \ - "${base[@]}" "${aws[@]}" ROLLOUT_PERCENTAGE=100 \ - FORCE_V1_REPOSITORIES=owner/repository \ - FORCE_V2_REPOSITORIES=owner/repository - - check "the v2 pin overrides a 0% rollout" \ - 0 "version=v2 profile=v1/${ARN}" \ - "${base[@]}" "${aws[@]}" \ - FORCE_V2_REPOSITORIES="$(printf 'other/repository\nowner/repository')" - - check "the v1 pin overrides a 100% rollout" \ - 0 "version=v1" \ - "${base[@]}" "${aws[@]}" ROLLOUT_PERCENTAGE=100 \ - FORCE_V1_REPOSITORIES="$(printf 'other/repository\nowner/repository')" - - check "a pin matches whole lines only" \ - 0 "version=v1" \ - "${base[@]}" "${aws[@]}" FORCE_V2_REPOSITORIES=owner/repositories - - check "a 0% rollout selects v1" \ - 0 "version=v1" \ - "${base[@]}" "${aws[@]}" - - check "a 100% rollout selects v2" \ - 0 "version=v2 profile=v1/${ARN}" \ - "${base[@]}" "${aws[@]}" ROLLOUT_PERCENTAGE=100 - - check "aws-role-arn is required without a profile" \ - 1 "" \ - "${base[@]}" AWS_LAMBDA_URL="${URL}" - - check "aws-lambda-url is required without a profile" \ - 1 "" \ - "${base[@]}" AWS_ROLE_ARN="${ARN}" - - if ((failures > 0)); then - echo "${failures} case(s) failed." - exit 1 - fi - echo "All cases passed." - - # Sweeping the percentage rather than recomputing the hash keeps this - # independent of how a repository is bucketed. A single flip proves the - # bucket is stable across invocations and that the threshold is a - # one-directional cutover. - - name: Check the rollout threshold is a stable, one-way cutover - working-directory: actions/setup-github-token/scripts - run: | - set -euo pipefail - - output_file="$(mktemp)" - previous="" - flips=0 - - for percentage in $(seq 0 100); do - env GITHUB_OUTPUT="${output_file}" \ - GITHUB_REPOSITORY=owner/repository \ - ROLLOUT_PERCENTAGE="${percentage}" \ - FORCE_V1_REPOSITORIES= \ - FORCE_V2_REPOSITORIES= \ - AWS_ROLE_ARN="${ARN}" \ - AWS_LAMBDA_URL="${URL}" \ - ./resolve-gati-version.sh >/dev/null - version="$(sed -n 's/^version=//p' "${output_file}")" - : >"${output_file}" - - if [[ -n "${previous}" && "${version}" != "${previous}" ]]; then - flips=$((flips + 1)) - echo "Flipped to ${version} at ${percentage}%." - fi - previous="${version}" - - if [[ "${percentage}" == "0" && "${version}" != "v1" ]]; then - echo "Expected v1 at 0%, got ${version}." - exit 1 - fi - done - - if [[ "${previous}" != "v2" ]]; then - echo "Expected v2 at 100%, got ${previous}." - exit 1 - fi - - if [[ "${flips}" != "1" ]]; then - echo "Expected exactly 1 flip across the sweep, got ${flips}." - exit 1 - fi - echo "The rollout crossed from v1 to v2 exactly once." - - v1-forced: - name: GATI v1 (forced) - # Repository secrets are unavailable to pull requests from forks. - if: - github.event_name != 'pull_request' || - github.event.pull_request.head.repo.full_name == github.repository - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - steps: - - name: Checkout repo (needed to reference local action) - uses: actions/checkout@v7 - with: - persist-credentials: false - - - name: Setup GitHub token - id: setup - uses: ./actions/setup-github-token - with: - gati-version: v1 - aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_ARN_GATI }} - aws-lambda-url: ${{ secrets.AWS_LAMBDA_URL_GATI }} - set-git-config: "true" - - - name: Check v1 issued the token and configured git - env: - ACCESS_TOKEN: ${{ steps.setup.outputs.access-token }} - GATI_VERSION: ${{ steps.setup.outputs.gati-version }} - run: | - set -euo pipefail - - if [[ "${GATI_VERSION}" != "v1" ]]; then - echo "Expected gati-version 'v1', got '${GATI_VERSION}'." - exit 1 - fi - - if [[ -z "${ACCESS_TOKEN}" ]]; then - echo "No access token was returned." - exit 1 - fi - - # The insteadOf key embeds the token, so match without printing it. - if ! git config --global --list --name-only | grep -q "insteadof"; then - echo "set-git-config did not configure git." - exit 1 - fi - echo "GATI v1 issued a token and configured git." - - v2-rollout: - name: GATI v2 (via rollout) - if: - github.event_name != 'pull_request' || - github.event.pull_request.head.repo.full_name == github.repository - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - steps: - - name: Checkout repo (needed to reference local action) - uses: actions/checkout@v7 - with: - persist-credentials: false - - # No profile, so the rollout decides. This repository is pinned to v2. - - name: Setup GitHub token - id: setup - uses: ./actions/setup-github-token - with: - aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_ARN_GATI }} - aws-lambda-url: ${{ secrets.AWS_LAMBDA_URL_GATI }} - - - name: Check v2 issued the token - env: - ACCESS_TOKEN: ${{ steps.setup.outputs.access-token }} - GATI_VERSION: ${{ steps.setup.outputs.gati-version }} - run: | - set -euo pipefail - - if [[ "${GATI_VERSION}" != "v2" ]]; then - echo "Expected gati-version 'v2', got '${GATI_VERSION}'." - exit 1 - fi - - if [[ -z "${ACCESS_TOKEN}" ]]; then - echo "No access token was returned." - exit 1 - fi - echo "GATI v2 issued a token from the role ARN it was given." - - v2-explicit-profile: - name: GATI v2 (explicit profile) - if: - github.event_name != 'pull_request' || - github.event.pull_request.head.repo.full_name == github.repository - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - steps: - - name: Checkout repo (needed to reference local action) - uses: actions/checkout@v7 - with: - persist-credentials: false - - - name: Setup GitHub token - id: setup - uses: ./actions/setup-github-token - with: - profile: v1/${{ secrets.AWS_OIDC_IAM_ROLE_ARN_GATI }} - - - name: Check v2 issued the token - env: - ACCESS_TOKEN: ${{ steps.setup.outputs.access-token }} - GATI_VERSION: ${{ steps.setup.outputs.gati-version }} - run: | - set -euo pipefail - - if [[ "${GATI_VERSION}" != "v2" ]]; then - echo "Expected gati-version 'v2', got '${GATI_VERSION}'." - exit 1 - fi - - if [[ -z "${ACCESS_TOKEN}" ]]; then - echo "No access token was returned." - exit 1 - fi - echo "GATI v2 issued a token from the profile it was given."