Search code examples
githubgithub-actions

Multiple jobs and deployment protection


I have a workflow with several jobs which perform deployment tasks. I use matrix strategy to deploy them across multiple environments, say dev, tst and stg. So each job has strategy configured as below

    strategy:
      matrix:
        target_env: ${{ fromJson(needs.determine_target_env.outputs.target_env) }}

Also, I need someone to review deployments if the environment it is being deployed to is tst or stg (no review required for dev). I have created environments in GitHub as test-environment and stg-environment and use them as below

environment: >-
        ${{ 
          matrix.target_env == 'tst' && 'test-environment' ||
          matrix.target_env == 'stg' && 'stage-environment' ||
          ''
        }}

This triggers approvals and reviewrs can approve/reject deployment. Here is the issue with this:

  1. Since I have several jobs, it keeps asking for approvals for all of them

  2. If I move the environment to a separate job which executed first (see below), then there is no way of capturing the output of that rejection or approval, because when rejected, the workflow just fails and if I provide a continue-on-error to that job, it marks it as success even if it rejected.

      environment_approval:
        needs: determine_target_env
        runs-on: [default]
        strategy:
          matrix:
            target_env: ${{ fromJson(needs.determine_target_env.outputs.target_env) }}
          fail-fast: false
        continue-on-error: true //If this is not set, rejections marks this job as failure. If it is set, the job is always a success.
        outputs:
          approval_status: ${{ steps.check_approval.outcome }}
        environment: >-
          ${{ 
            matrix.target_env == 'tst' && 'test-environment' ||
            matrix.target_env == 'stg' && 'stage-environment' ||
            ''
          }}
        steps:
          - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
            with:
              fetch-depth: 0
          - name: Check approval status
            continue-on-error: true
            id: check_approval
            run: |
              echo "status=success" >> $GITHUB_OUTPUT
    

Is there a central way of handling deployment protection and applying it to all jobs within a workflow?

I also tried to use

  • Composite actions, which did not help as it did not support environment: context
  • Re-useable action, which is the same as using it inline

Solution

  • OK, I ended up solving it myself. I understand this is a unique case of using environments with deployment protection and then also using strategy:matrix, but here is the solution if anyone is interested .

    Have another task in the same job (environment-approval)job which queries GH API deployments

    Something like this

                DEPLOYMENTS_JSON=$(curl -s -H "Authorization: token ${{ steps.generate-token.outputs.token }}" \
                https://api.github.com/repos/${{ github.repository }}/deployments)
    
                DEPLOYMENT_ID=$(echo "$DEPLOYMENTS_JSON" | jq -r --arg DEPLOYMENT_NAME "$DEPLOYMENT_NAME" 'map(select(.environment == $DEPLOYMENT_NAME)) | sort_by(.created_at) | last | .id // empty')
                if [ -n "$DEPLOYMENT_ID" ]; then
                  DEPLOYMENT_STATUS=$(curl -s -H "Authorization: token ${{ steps.generate-token.outputs.token }}" \
                    https://api.github.com/repos/${{ github.repository }}/deployments/$DEPLOYMENT_ID/statuses | \
                    jq -r '.[0].state') # Get the state of the most recent status
    
                  echo "Deployment ID: $DEPLOYMENT_ID"
                  echo "Deployment Status: $DEPLOYMENT_STATUS"
    
                  if [[ "$DEPLOYMENT_STATUS" == "success" || "$DEPLOYMENT_STATUS" == "in_progress" ]]; then
                    echo "approved" > ${{ matrix.target_env }}_approval_status.txt
                  else
                    echo "rejected" > ${{ matrix.target_env }}_approval_status.txt
                  fi
                else
                  echo "Deployment not found."
                  exit 1
                fi
    

    Output this into a file and have another job following this to process these files into respective environment related variable (You need another job because the latest run overrides the variable set part of strategy:matrix.

    Use this new variable in all subsequent jobs to validate deployment approvals.

    Hope this helps