diff --git a/.github/actions/release/action.yml b/.github/actions/release/action.yml index 662802f11..7208dab5f 100644 --- a/.github/actions/release/action.yml +++ b/.github/actions/release/action.yml @@ -9,14 +9,11 @@ inputs: bucket-name: description: S3 bucket name required: true - prod-deployment-hook-token: - description: Token for the webhook that triggers the deployment to production + promotions-token: + description: GitHub App token used to dispatch the production promotion workflow required: true - prod-deployment-hook-url: - description: URL of the webhook that triggers the deployment to production - required: true - react-app-rpc-token: - description: Infura RPC API token + promotions-repo: + description: Repository hosting the gated production promotion workflows (owner/name) required: true react-app-tenderly-org-name: description: Tenderly simulation organization name @@ -59,8 +56,8 @@ runs: git tag -f last-release shell: bash env: - PROD_DEPLOYMENT_HOOK_TOKEN: ${{ inputs.prod-deployment-hook-token }} - PROD_DEPLOYMENT_HOOK_URL: ${{ inputs.prod-deployment-hook-url }} + GH_TOKEN: ${{ inputs.promotions-token }} + PROMOTIONS_REPO: ${{ inputs.promotions-repo }} BUCKET_NAME: ${{ inputs.bucket-name }} GITHUB_TOKEN: ${{ inputs.github-token }} REACT_APP_TENDERLY_ORG_NAME: ${{ inputs.react-app-tenderly-org-name }} diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index e0e064d0c..6b68d3e4d 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -140,12 +140,26 @@ jobs: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME_STAGING }} aws-region: ${{ secrets.AWS_DEFAULT_REGION }} + # Minted here because composite-action steps cannot read secrets directly. + # Installation tokens live 60 minutes and the dispatch is the last nx target; + # historical release runs complete in 3-9 minutes, so expiry is not a + # practical concern. + - name: Generate promotions repo token + id: promotions_token + continue-on-error: true + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + app-id: ${{ secrets.PROMOTIONS_APP_ID }} + private-key: ${{ secrets.PROMOTIONS_APP_PRIVATE_KEY }} + repositories: ${{ secrets.PROMOTIONS_REPO }} + permission-actions: write + - name: 'Deploy production' uses: ./.github/actions/release with: github-token: ${{ secrets.GITHUB_TOKEN }} - prod-deployment-hook-token: ${{ secrets.PROD_DEPLOYMENT_HOOK_TOKEN }} - prod-deployment-hook-url: ${{ secrets.PROD_DEPLOYMENT_HOOK_URL }} + promotions-token: ${{ steps.promotions_token.outputs.token }} + promotions-repo: ${{ github.repository_owner }}/${{ secrets.PROMOTIONS_REPO }} bucket-name: ${{ secrets.STAGING_BUCKET_NAME }}/releases react-app-tenderly-org-name: ${{ secrets.REACT_APP_TENDERLY_ORG_NAME }} react-app-tenderly-project-name: ${{ secrets.REACT_APP_TENDERLY_PROJECT_NAME }} diff --git a/scripts/prepare_production_deployment.sh b/scripts/prepare_production_deployment.sh index b41651476..eec2a5e01 100755 --- a/scripts/prepare_production_deployment.sh +++ b/scripts/prepare_production_deployment.sh @@ -3,17 +3,33 @@ set -ev # Only: -# - Security env variables are available. -if [ -n "$PROD_DEPLOYMENT_HOOK_TOKEN" ] && [ -n "$PROD_DEPLOYMENT_HOOK_URL" ] +# - GH_TOKEN and PROMOTIONS_REPO are available. +if [ -n "$GH_TOKEN" ] && [ -n "$PROMOTIONS_REPO" ] then APP_NAME="$(basename $(pwd))" PACKAGE_VERSION=$(sed -nr 's/^\s*\"version": "([0-9]{1,}\.[0-9]{1,}.*)",$/\1/p' package.json) - curl --silent --output /dev/null --write-out "%{http_code}" -X POST \ - -F token="$PROD_DEPLOYMENT_HOOK_TOKEN" \ - -F ref=master \ - -F "variables[TRIGGER_RELEASE_APP_NAME]=$APP_NAME" \ - -F "variables[TRIGGER_RELEASE_COMMIT_TAG]=$PACKAGE_VERSION" \ - $PROD_DEPLOYMENT_HOOK_URL + # tx-builder promotes via safe-tx-builder-tf (built from safe-wallet-monorepo + # releases) since 2026-03; dispatching it here would only trip the promotion + # workflow's app guard. + if [ "$APP_NAME" = "tx-builder" ] + then + echo "Skipping tx-builder: it deploys via safe-tx-builder-tf" + exit 0 + fi + # --ref is required: without it gh resolves the default branch via GraphQL, + # which the app token (actions:write, metadata:read only) is not allowed to do. + # A failed dispatch must not fail this script: it runs mid nx-chain, before + # `git tag -f last-release`, and a hard exit would leave the release half-done. + # The error annotation stays visible on the run; the promotion can be + # dispatched manually from safe-production-promotions. + if ! gh workflow run react-apps-production.yml \ + --repo "$PROMOTIONS_REPO" \ + --ref main \ + -f "app=$APP_NAME" \ + -f "tag=$PACKAGE_VERSION" + then + echo "::error::Failed to dispatch production deployment for $APP_NAME $PACKAGE_VERSION" + fi else - echo "[ERROR] Production deployment could not be prepared" + echo "::warning::Production deployment could not be prepared: GH_TOKEN or PROMOTIONS_REPO missing" fi