Skip to content

pre-prod | pds-adapter | Terraform Apply? = false #13

pre-prod | pds-adapter | Terraform Apply? = false

pre-prod | pds-adapter | Terraform Apply? = false #13

Workflow file for this run

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