pre-prod | pds-adapter | Terraform Apply? = false #13
Workflow file for this run
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: Deploy - Stack | |
| run-name: "${{ inputs.environment }} | ${{ inputs.stack }} | Terraform Apply? = ${{ inputs.is_deployment }}" | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| environment: | |
| default: "dev" | |
| description: "Environment" | |
| required: true | |
| type: choice | |
| options: | |
| - dev | |
| - pre-prod | |
| - prod | |
| stack: | |
| description: "Stack" | |
| required: true | |
| type: choice | |
| options: | |
| - ehr-repo | |
| - ehr-repo-db-roles | |
| - ehr-out-service | |
| - ehr-transfer-service | |
| - re-registration-service | |
| - suspension-service | |
| - nems-event-processor | |
| - deductions | |
| - deductions-backup | |
| - deductions-cross-account | |
| - deductions-dashboard | |
| - mhs | |
| - gp2gp-messenger | |
| - pds-adapter | |
| - mesh-forwarder | |
| - base-infra | |
| is_deployment: | |
| default: false | |
| type: boolean | |
| description: "Terraform Apply?" | |
| workflow_call: | |
| inputs: | |
| environment: | |
| default: dev | |
| description: "Environment to deploy to" | |
| type: string | |
| stack: | |
| description: "Stack to deploy - stack folder name" | |
| type: string | |
| backend_key_alias: | |
| description: "Alias for the stack - i.e. deductions is referred to as deductions-infra" | |
| type: string | |
| required: false | |
| ecr_alias: | |
| description: "ECR Repository Alias" | |
| type: string | |
| required: false | |
| is_deployment: | |
| default: false | |
| type: boolean | |
| description: "Terraform Apply?" | |
| ci_account: | |
| default: false | |
| description: "Are we deploying to the CI account?" | |
| type: boolean | |
| lambdas_to_build: | |
| default: false | |
| description: "Do we need to build any lambdas before deploying?" | |
| type: boolean | |
| permissions: | |
| pull-requests: write | |
| id-token: write # This is required for requesting the JWT | |
| contents: read # This is required for actions/checkout | |
| jobs: | |
| promote-images-from-source-ecr-account: | |
| environment: ${{ inputs.environment }} | |
| name: Promote images from source to current environment | |
| if: ${{ inputs.is_deployment && inputs.environment != 'dev' && inputs.ecr_alias }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| id: checkout | |
| uses: actions/checkout@v6 | |
| - name: Configure AWS Credentials | |
| id: creds | |
| uses: aws-actions/configure-aws-credentials@v8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0 | |
| with: | |
| role-to-assume: ${{ secrets.IAM_ROLE }} | |
| aws-region: ${{ vars.AWS_REGION }} | |
| role-skip-session-tagging: true | |
| mask-aws-account-id: true | |
| # Needs the AmazonEC2ContainerRegistryPowerUser role | |
| - name: Login to ECR | |
| uses: aws-actions/amazon-ecr-login@v2 | |
| with: | |
| registries: "${{ steps.creds.outputs.aws-account-id }},${{ secrets.AWS_SOURCE_ECR_ACCOUNT_ID }}" | |
| - name: Get Most Recent ECR Image SHA Tag From Lower Environment | |
| id: get-image-tag | |
| env: | |
| ECR_ALIAS: ${{ inputs.ecr_alias }} | |
| run: | | |
| LATEST_IMAGE_SHA=$(aws ecr describe-images \ | |
| --registry-id ${{ secrets.AWS_SOURCE_ECR_ACCOUNT_ID }} \ | |
| --repository-name $ECR_ALIAS \ | |
| --output json | | |
| jq -r ' | |
| .imageDetails | |
| | map(select(.imageTags != null and (.imageTags|length>0))) | |
| | sort_by(.imagePushedAt) | |
| | last | |
| | .imageTags[] | |
| | select(test("^[0-9a-f]{40}$")) | |
| ' | head -n 1) | |
| echo "All image tags for $ECR_ALIAS: $LATEST_IMAGE_SHA" | |
| echo "image_sha=$LATEST_IMAGE_SHA" >> $GITHUB_OUTPUT | |
| - name: ECR Copy | |
| id: ecr-copy | |
| env: | |
| IMAGE_SHA: "${{ steps.get-image-tag.outputs.image_sha }}" | |
| run: | | |
| source_repo=${{ secrets.AWS_SOURCE_ECR_ACCOUNT_ID }}.dkr.ecr.eu-west-2.amazonaws.com/${{ inputs.ecr_alias }} | |
| destination_repo=${{ steps.creds.outputs.aws-account-id }}.dkr.ecr.eu-west-2.amazonaws.com/${{ inputs.ecr_alias }} | |
| docker pull $source_repo:$IMAGE_SHA | |
| docker tag $source_repo:$IMAGE_SHA $destination_repo:$IMAGE_SHA | |
| docker tag $source_repo:$IMAGE_SHA $destination_repo:${{ github.ref_name }} | |
| docker push $destination_repo:$IMAGE_SHA | |
| docker push $destination_repo:${{ github.ref_name }} | |
| deploy_stack: | |
| environment: ${{ !inputs.ci_account && inputs.environment || 'ci_account' }} | |
| env: | |
| GITHUB_ENV: ${{ !inputs.ci_account && inputs.environment || 'ci_account' }} | |
| runs-on: ubuntu-latest | |
| needs: [promote-images-from-source-ecr-account] | |
| if: always() && (needs.promote-images-from-source-ecr-account.result == 'skipped' || needs.promote-images-from-source-ecr-account.result == 'success') | |
| defaults: | |
| run: | |
| working-directory: ./stacks/${{ inputs.stack }}/terraform | |
| steps: | |
| - name: Check out Repo | |
| uses: actions/checkout@v6 | |
| with: | |
| repository: NHSDigital/orphaned-record-continuity-infrastructure | |
| fetch-depth: 0 | |
| ref: ${{ github.ref }} | |
| - name: Configure AWS Credentials (ReadWrite) | |
| uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0 | |
| id: creds | |
| with: | |
| role-to-assume: ${{ secrets.IAM_ROLE }} | |
| aws-region: ${{ vars.AWS_REGION }} | |
| mask-aws-account-id: true | |
| role-skip-session-tagging: true | |
| - name: Setup Terraform variables for Deductions Cross Account | |
| if: inputs.stack == 'deductions-cross-account' | |
| env: | |
| DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} | |
| DOCKERHUB_ACCESS_TOKEN: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} | |
| PROMOTION_IAM_ARN: ${{ secrets.PROMOTION_IAM_ARN }} | |
| run: | | |
| cat > pipeline-deductions-cross-account.auto.tfvars <<EOF | |
| dockerhub_username = "${DOCKERHUB_USERNAME}" | |
| dockerhub_access_token = "${DOCKERHUB_ACCESS_TOKEN}" | |
| promotion_iam_arn = "${PROMOTION_IAM_ARN}" | |
| EOF | |
| - name: Get Most Recent ECR Image Tag | |
| id: get-image-tag | |
| if: inputs.ecr_alias | |
| env: | |
| ECR_ALIAS: ${{ inputs.ecr_alias }} | |
| run: | | |
| LATEST_IMAGE_SHA=$(aws ecr describe-images \ | |
| --repository-name $ECR_ALIAS \ | |
| --output json | | |
| jq -r ' | |
| .imageDetails | |
| | map(select(.imageTags != null and (.imageTags|length>0))) | |
| | sort_by(.imagePushedAt) | |
| | last | |
| | .imageTags[] | |
| | select(test("^[0-9a-f]{40}$")) | |
| ' | head -n 1) | |
| echo "Found tag: $LATEST_IMAGE_SHA" | |
| echo "Most recent image tag for $ECR_ALIAS: $LATEST_IMAGE_SHA" | |
| echo "image_tag=$LATEST_IMAGE_SHA" >> $GITHUB_OUTPUT | |
| - name: Setup Terraform variables | |
| id: vars | |
| env: | |
| ECR_ALIAS: ${{ inputs.ecr_alias }} | |
| IMAGE_TAG: ${{ steps.get-image-tag.outputs.image_tag || '' }} | |
| run: | | |
| COMMON_ACCOUNT_ID=$(aws ssm get-parameter --name /repo/ci/user-input/external/aws-account-id --with-decryption | jq -r .Parameter.Value) | |
| cat > pipeline.auto.tfvars <<EOF | |
| common_account_id=$COMMON_ACCOUNT_ID | |
| common_account_role="CiReadOnly" | |
| EOF | |
| # Only add task_image_tag if stack uses an ECR image | |
| if [ -n "$ECR_ALIAS" ] && [ -n "$IMAGE_TAG" ]; then | |
| echo "task_image_tag = \"${IMAGE_TAG}\"" >> pipeline.auto.tfvars | |
| fi | |
| - name: Build lambdas with additional requirements | |
| if: inputs.lambdas_to_build | |
| run: | | |
| cd ../../../ | |
| ./tasks build_lambdas | |
| - name: Setup Terraform | |
| uses: hashicorp/setup-terraform@v3 | |
| with: | |
| terraform_version: latest | |
| # - name: Terraform Format | |
| # id: fmt | |
| # run: | | |
| # terraform fmt | |
| - name: Terraform Init | |
| id: init | |
| run: | | |
| # compute backend key: prefer alias, otherwise replace '-dev-' with the chosen environment | |
| STACK="${{ inputs.stack }}" | |
| ENV="${{ inputs.environment }}" | |
| ALIAS="${{ inputs.backend_key_alias || '' }}" | |
| if [ -n "$ALIAS" ]; then | |
| KEY="$ALIAS" | |
| else | |
| if [[ "$STACK" == *"-dev-"* ]]; then | |
| KEY="${STACK/-dev-/-$ENV-}" | |
| else | |
| KEY="${STACK}-$ENV" | |
| fi | |
| fi | |
| echo "Using backend key: $KEY/terraform.tfstate" | |
| terraform init -no-color \ | |
| -backend-config="key=${KEY}/terraform.tfstate" \ | |
| -backend-config="bucket=${{ secrets.TF_BACKEND_BUCKET }}" \ | |
| -backend-config="dynamodb_table=${{ secrets.TF_BACKEND_TABLE }}" | |
| shell: bash | |
| - name: Terraform Validate | |
| id: validate | |
| run: terraform validate -no-color | |
| - name: Terraform Plan | |
| id: plan | |
| run: | | |
| terraform plan -no-color -input=false -var-file="${{ vars.AWS_ENVIRONMENT }}.tfvars" -out "${{ vars.AWS_ENVIRONMENT }}.tfplan" | |
| terraform show -no-color ${{ vars.AWS_ENVIRONMENT }}.tfplan > ${{ vars.AWS_ENVIRONMENT }}.tfplan.txt | |
| echo "summary=$(grep -E 'Plan: [0-9]+ to add, [0-9]+ to change, [0-9]+ to destroy\.|No changes\. Your infrastructure matches the configuration\.' ${{ vars.AWS_ENVIRONMENT }}.tfplan.txt | sed 's/.*No changes\. Your infrastructure matches the configuration/Plan: no changes/g' | sed 's/.*Plan: //g' | sed 's/\..*//g')" >> $GITHUB_OUTPUT | |
| shell: bash | |
| - name: Truncate Plan Output | |
| id: plan-truncated | |
| if: success() || failure() | |
| env: | |
| LENGTH: 64512 | |
| run: | | |
| PLAN_FULL=$(grep -v 'Refreshing state...' <<'EOF' | |
| ${{ steps.plan.outputs.stdout }} | |
| ${{ steps.plan.outputs.stderr }} | |
| EOF | |
| ) | |
| # Optionally redact sensitive strings in the PLAN_FULL variable | |
| PLAN_FULL=$(echo "$PLAN_FULL" | sed -E 's#arn:aws:iam::[0-9]{12}:role/[a-zA-Z0-9_-]+#[REDACTED_IAM_ROLE_ARN]#g') | |
| PLAN_FULL=$(echo "$PLAN_FULL" | sed -E 's/[0-9]{12}/[REDACTED_AWS_ACCOUNT_ID]/g') | |
| PLAN_FULL=$(echo "$PLAN_FULL" | sed -E 's#https://[a-zA-Z0-9.-]+\.lambda\.amazonaws\.com/[a-zA-Z0-9/._-]+#[REDACTED_LAMBDA_URL]#g') | |
| PLAN_FULL=$(echo "$PLAN_FULL" | sed -E 's#https://[a-zA-Z0-9.-]+\.execute-api\.[a-zA-Z0-9.-]+\.amazonaws\.com/[a-zA-Z0-9/._-]*#[REDACTED_API_GATEWAY_URL]#g') | |
| PLAN_FULL=$(echo "$PLAN_FULL" | sed -E '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/s/.*/[REDACTED_PEM_CERT]/') | |
| echo "PLAN<<EOF" >> $GITHUB_ENV | |
| echo "${PLAN_FULL::$LENGTH}" >> $GITHUB_ENV | |
| [ ${#PLAN_FULL} -gt $LENGTH ] && echo "(truncated - see workflow logs for full output)" >> $GITHUB_ENV | |
| echo "EOF" >> $GITHUB_ENV | |
| shell: bash | |
| - name: Add PR comment | |
| uses: actions/github-script@v8 | |
| if: github.event_name == 'pull_request' && (success() || failure()) | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| // 1. Retrieve existing bot comments for the PR | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }) | |
| const botComment = comments.find(comment => { | |
| return comment.user.type === 'Bot' && comment.body.includes('Report for environment: ndr-dev') | |
| }) | |
| // 2. Prepare format of the comment | |
| const output = `### Report for ${{ inputs.stack }} | |
| #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\` | |
| <details><summary>Initialization Output</summary> | |
| \`\`\`\n | |
| ${{ steps.init.outputs.stdout }} | |
| \`\`\` | |
| </details> | |
| #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\` | |
| <details><summary>Validation Output</summary> | |
| \`\`\`\n | |
| ${{ steps.validate.outputs.stdout }} | |
| \`\`\` | |
| </details> | |
| #### Terraform Plan 📖\`${{ steps.plan.outcome }}\` | |
| <details><summary>Show Plan (${{ steps.plan.outputs.summary }})</summary> | |
| \`\`\`\n | |
| ${{ env.PLAN }} | |
| \`\`\` | |
| </details>`; | |
| // 3. If we have a comment, update it, otherwise create a new one | |
| if (botComment) { | |
| github.rest.issues.deleteComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body: output | |
| }) | |
| } | |
| github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: output | |
| }) | |
| - name: Terraform Apply | |
| if: inputs.is_deployment | |
| run: terraform apply -auto-approve -input=false ${{ vars.AWS_ENVIRONMENT }}.tfplan |