Skip to content

Merge pull request #583 from ministryofjustice/dependabot/github_acti… #370

Merge pull request #583 from ministryofjustice/dependabot/github_acti…

Merge pull request #583 from ministryofjustice/dependabot/github_acti… #370

Workflow file for this run

---
name: base
# This pipeline works across multiple terragrunt projects within a team
# directory with locking (turnstyle) and PR comment functionality.
#
# The init step figures out which projects to plan/apply
# The plan/apply steps run across each project as a matrix
#
# It allow both plan/apply via manual dispatch.
# For CI/CD, it runs a plan when PR raised and apply when merged.
#
on:
workflow_dispatch:
inputs:
projects:
description: 'One or more project to plan/apply, space separated'
required: true
default: ''
action:
description: 'Set to plan or apply'
required: true
default: 'plan'
pull_request:
types:
- opened
- edited
- synchronize
- reopened
branches:
- main
paths:
- commonimages/base/**
- modules/imagebuilder/**
- .github/workflows/base.yml
- ansible/**
push:
branches:
- main
paths:
- commonimages/base/**
- modules/imagebuilder/**
- .github/workflows/base.yml
- ansible/**
permissions:
id-token: write # This is required for requesting the JWT
contents: read # This is required for actions/checkout
pull-requests: write # For posting comments to PR
env:
AWS_REGION: "eu-west-2"
ENVIRONMENT_MANAGEMENT: ${{ secrets.MODERNISATION_PLATFORM_ENVIRONMENTS }}
# set branch to main on pull requests so plan changes are same as push-to-main
TF_IN_AUTOMATION: true
TF_VAR_BRANCH_NAME: ${{ github.event_name == 'pull_request' && 'main' || (github.head_ref || github.ref_name) }}
TF_VAR_GH_ACTOR_NAME: ${{ github.actor}}
TF_ENV: production
BASE_DIR: commonimages/base
PROJECT_DIR_DEPTH: 3
DEFAULT_TERRAFORM_VERSION: 1.3.3
TERRAGRUNT_VERSION: v0.36.1
defaults:
run:
shell: bash
jobs:
init:
runs-on: ubuntu-latest
outputs:
action: ${{ steps.parseinput.outputs.action }}
projects: ${{ steps.parseinput.outputs.projects }}
pr_number: ${{ steps.parseinput.outputs.pr_number }}
matrix: ${{ steps.setupproject.outputs.matrix }}
steps:
- name: Checkout the code
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- name: Set Account Number
run: echo "ACCOUNT_NUMBER=$(jq -r -e '.modernisation_platform_account_id' <<< $ENVIRONMENT_MANAGEMENT)" >> $GITHUB_ENV
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@8c3f20df09ac63af7b3ae3d7c91f105f857d8497 # v4.0.0
with:
role-to-assume: "arn:aws:iam::${{ env.ACCOUNT_NUMBER }}:role/github-actions"
role-session-name: githubactionsrolesession
aws-region: ${{ env.AWS_REGION }}
# get PR number: for posting PR comments and figuring out changed files
- name: Get PR number on push
id: get_pr_number
if: ${{ github.event_name == 'push' }}
run: |
pr_number=$(curl -sS \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
https://api.github.com/repos/${{ github.repository }}/commits/${{ github.sha }}/pulls \
| jq -r '.[0].number')
if [[ $pr_number == "null" ]]; then
echo "Could not find PR number for commit=${{ github.sha }}"
exit 1
fi
echo "pr=${pr_number}" >> $GITHUB_OUTPUT
# for workflow dispatch, check valid project specified
- name: Validate workflow dispatch
id: projects_workflow_dispatch
if: ${{ github.event_name == 'workflow_dispatch' }}
run: |
echo "Validate [projects=${{ github.event.inputs.projects }}] [action=${{ github.event.inputs.action }}]"
set +o pipefail
projects=${{ github.event.inputs.projects }}
if [[ ${projects} == "all" ]]; then
projects=$(find "${BASE_DIR}" -name terragrunt.hcl | grep -v '/.terra' | sed -r 's|/[^/]+$||' | cut -d/ -f${PROJECT_DIR_DEPTH}- | sort -u)
else
for project in ${{ github.event.inputs.projects }}; do
if [[ ! -e "${BASE_DIR}/${project}/terragrunt.hcl" ]]; then
echo "Project not found ${BASE_DIR}/${project}/terragrunt.hcl" >&2
exit 1
fi
done
fi
action=${{ github.event.inputs.action }}
if [[ $action != "plan" && $action != "apply" && $action != "driftcheck" ]]; then
echo "Unexpected value for $action [$action], must be plan/apply/driftcheck" >&2
exit 1
fi
echo "action=[$action]; list of projects" [${projects}]
echo "projects=${projects}" >> $GITHUB_OUTPUT
echo "action=${action}" >> $GITHUB_OUTPUT
- name: Get list of committed files
id: files
if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }}
run: |
if [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then
pr_number="${{ github.event.pull_request.number }}"
elif [[ "${GITHUB_EVENT_NAME}" == "push" ]]; then
pr_number="${{ steps.get_pr_number.outputs.pr }}"
else
echo "Unexpected github event name ${GITHUB_EVENT_NAME}"
exit 1
fi
echo "pr_number=${pr_number}" >> $GITHUB_OUTPUT
pr_files=$(curl -sS \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
https://api.github.com/repos/${{ github.repository }}/pulls/${pr_number}/files \
| jq .[].filename \
| sed 's/"//g' \
| tr '\n' ' ')
echo "file_list=${pr_files}" >> $GITHUB_OUTPUT
- name: Detect pull/push projects
id: projects_pull_push
if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }}
run: |
updated_files="${{ steps.files.outputs.file_list }}"
echo "Updated files: ${updated_files[@]}"
set +o pipefail
allprojects=$(find ${BASE_DIR} -name terragrunt.hcl | grep -v '/.terra' | grep -v "${BASE_DIR}/terragrunt.hcl" | sed -r "s;${BASE_DIR}/([^/]+)/.+;\1;" | sort -u)
echo "all projects: ${allprojects[@]}"
changed=$(echo "${{ steps.files.outputs.file_list }}" | tr "[:space:]" "\n" | grep -v "${BASE_DIR}/components/" | sed -r 's|/[^/]+$||' | grep "${BASE_DIR}/" | sed -r "s;${BASE_DIR}/([^/]+);\1;g" | sort -u)
workflow_path=.github/workflows/${GITHUB_WORKFLOW}.yml
common_files=( $(ls -p ${BASE_DIR} | grep -v /) ) # base dir files
common_files=( "${common_files[@]/#/${BASE_DIR}\/}" ) # prefix with base dir
common_files+=("${workflow_path}") # add workflow to common files
common_files+=("modules/imagebuilder/.+")
echo "common files: ${common_files[@]}"
projects=$(comm -12 <(echo "${changed}") <(echo "${allprojects}"))
for file in ${common_files[@]}; do
if [[ " ${updated_files[*]} " =~ " ${file} " ]]; then
projects=${allprojects[@]}
fi
done
echo List of projects [ ${projects} ]
echo projects=${projects} >> $GITHUB_OUTPUT
- name: Parse inputs
id: parseinput
run: |
echo "Parsing input parameters event=${GITHUB_EVENT_NAME}"
action="plan"
pr_number=""
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
projects="${{ steps.projects_workflow_dispatch.outputs.projects }}"
action="${{ steps.projects_workflow_dispatch.outputs.action }}"
elif [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then
projects="${{ steps.projects_pull_push.outputs.projects }}"
pr_number="${{ github.event.pull_request.number }}"
elif [[ "${GITHUB_EVENT_NAME}" == "push" ]]; then
projects="${{ steps.projects_pull_push.outputs.projects }}"
pr_number="${{ steps.get_pr_number.outputs.pr }}"
action="apply"
else
echo "Unsupported event ${GITHUB_EVENT_NAME}"
exit 1
fi
echo "Set action=${action} pr_number=${pr_number} project=${projects}"
echo "projects=${projects}" >> $GITHUB_OUTPUT
echo "action=${action}" >> $GITHUB_OUTPUT
echo "pr_number=${pr_number}" >> $GITHUB_OUTPUT
- name: Setup project matrix
id: setupproject
env:
projects: ${{ steps.parseinput.outputs.projects }}
run: |
echo "Setup ansible actions for projects [${projects}]"
echo -n "matrix={\"include\":[" >> $GITHUB_OUTPUT
delimiter=""
for project in ${projects}; do
echo -n "${delimiter}" >> $GITHUB_OUTPUT
echo -n '{"project":"'${project}'"}' >> $GITHUB_OUTPUT
delimiter=","
done
echo "]}" >> $GITHUB_OUTPUT
- name: Hide previous PR comments
if: ${{ github.event_name == 'pull_request' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMENT_BODY_CONTAINS: "**`${{ env.BASE_DIR }}/"
PR_NUMBER: "${{ github.event.pull_request.number }}"
run: |
cd ${GITHUB_WORKSPACE}/scripts/minimise-comments
go build
./minimise-comments
plan:
needs: init
if: ${{ needs.init.outputs.projects != '' }}
strategy:
matrix: ${{ fromJson(needs.init.outputs.matrix) }}
fail-fast: false
runs-on: ubuntu-latest
steps:
- name: Checkout the code
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- name: Set Account Number
run: echo "ACCOUNT_NUMBER=$(jq -r -e '.modernisation_platform_account_id' <<< $ENVIRONMENT_MANAGEMENT)" >> $GITHUB_ENV
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@8c3f20df09ac63af7b3ae3d7c91f105f857d8497 # v4.0.0
with:
role-to-assume: "arn:aws:iam::${{ env.ACCOUNT_NUMBER }}:role/github-actions"
role-session-name: githubactionsrolesession
aws-region: ${{ env.AWS_REGION }}
- name: Get terraform version
id: discover
run: |
echo "Get terraform version for ${{ matrix.project }}"
set +o pipefail
required_version=$(grep ^terraform "${BASE_DIR}/${{ matrix.project }}"/*.tf -A 20 | grep -w required_version | cut -d\" -f2)
if [[ -z "$required_version" ]]; then
echo "Using default terraform version specified in pipeline $DEFAULT_TERRAFORM_VERSION" >&2
required_version="=${DEFAULT_TERRAFORM_VERSION}"
fi
if [[ ! "$required_version" =~ =[0-9]+.[0-9]+.[0-9]+ ]]; then
echo "Unexpected terraform required_version format, expect '=x.y.z': $required_version" >&2
exit 1
fi
version=$(echo "$required_version" | cut -d= -f2)
echo "version=${version}" >> $GITHUB_OUTPUT
- name: Setup Terraform
uses: hashicorp/setup-terraform@633666f66e0061ca3b725c73b2ec20cd13a8fdd1 # v2.0.3
with:
terraform_version: ${{ steps.discover.outputs.version }}
terraform_wrapper: false
- name: Setup Terragrunt
run: |
wget -O /usr/local/bin/terragrunt https://github.com/gruntwork-io/terragrunt/releases/download/${TERRAGRUNT_VERSION}/terragrunt_linux_amd64
chmod u+x /usr/local/bin/terragrunt
- name: Init
working-directory: "${{ env.BASE_DIR }}/${{ matrix.project }}"
run: |
terragrunt init
terragrunt workspace select "core-shared-services-${TF_ENV}" || terragrunt workspace new "core-shared-services-${TF_ENV}"
- name: Plan
id: plan
working-directory: "${{ env.BASE_DIR }}/${{ matrix.project }}"
run: |
exitcode=0
chmod +x ${GITHUB_WORKSPACE}/scripts/redact-output.sh
terragrunt plan -no-color -detailed-exitcode | ${GITHUB_WORKSPACE}/scripts/redact-output.sh | tee tfplan.txt || exitcode=$?
echo "terragrunt plan exit code = $exitcode"
echo "exitcode=${exitcode}" >> $GITHUB_OUTPUT
(( exitcode == 1 )) && exit 1 || exit 0
- name: Create Plan PR message
if: ${{ (github.event_name == 'pull_request' || github.event_name == 'push') && steps.plan.outputs.exitcode == '2' }}
working-directory: "${{ env.BASE_DIR }}/${{ matrix.project }}"
run: |
comment() {
url="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
len=$(cat tfplan.txt | wc -c)
echo '**`${{ env.BASE_DIR }}/${{ matrix.project }}`** terragrunt plan on `${{ github.event_name }}` event [#${{ github.run_number }}](${url})'
echo
echo '```'
head -c 65476 tfplan.txt
echo
echo '```'
if [[ $len -gt 65476 ]]; then
echo "** Truncated output. See $url for the rest **"
fi
}
echo 'TF_PLAN_OUT<<EOF' >> $GITHUB_ENV
comment >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
- name: Post Plan to PR
env:
message: "${{ env.TF_PLAN_OUT }}"
pr_number: "${{ needs.init.outputs.pr_number }}"
run: |
escaped_message=$(echo "$message" | jq -Rsa .)
curl -sS -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
"https://api.github.com/repos/${{ github.repository }}/issues/${pr_number}/comments" \
-d '{"body":'"${escaped_message}"'}'
apply:
needs:
- init
- plan
if: ${{ needs.init.outputs.projects != '' && needs.init.outputs.action == 'apply' }}
strategy:
matrix: ${{ fromJson(needs.init.outputs.matrix) }}
fail-fast: false
runs-on: ubuntu-latest
environment:
name: core-shared-services
steps:
- name: Checkout the code
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- name: Set Account Number
run: echo "ACCOUNT_NUMBER=$(jq -r -e '.modernisation_platform_account_id' <<< $ENVIRONMENT_MANAGEMENT)" >> $GITHUB_ENV
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@8c3f20df09ac63af7b3ae3d7c91f105f857d8497 # v4.0.0
with:
role-to-assume: "arn:aws:iam::${{ env.ACCOUNT_NUMBER }}:role/github-actions"
role-session-name: githubactionsrolesession
aws-region: ${{ env.AWS_REGION }}
- name: Get terraform version
id: discover
run: |
echo "Get terraform version for ${{ matrix.project }}"
set +o pipefail
required_version=$(grep ^terraform "${BASE_DIR}/${{ matrix.project }}"/*.tf -A 20 | grep -w required_version | cut -d\" -f2)
if [[ -z "$required_version" ]]; then
echo "Using default terraform version specified in pipeline $DEFAULT_TERRAFORM_VERSION" >&2
required_version="=${DEFAULT_TERRAFORM_VERSION}"
fi
if [[ ! "$required_version" =~ =[0-9]+.[0-9]+.[0-9]+ ]]; then
echo "Unexpected terraform required_version format, expect '=x.y.z': $required_version" >&2
exit 1
fi
version=$(echo "$required_version" | cut -d= -f2)
echo "version=${version}" >> $GITHUB_OUTPUT
- name: Setup Terraform
uses: hashicorp/setup-terraform@633666f66e0061ca3b725c73b2ec20cd13a8fdd1 # v2.0.3
with:
terraform_version: ${{ steps.discover.outputs.version }}
terraform_wrapper: false
- name: Setup Terragrunt
run: |
wget -O /usr/local/bin/terragrunt https://github.com/gruntwork-io/terragrunt/releases/download/${TERRAGRUNT_VERSION}/terragrunt_linux_amd64
chmod u+x /usr/local/bin/terragrunt
- name: Init
working-directory: "${{ env.BASE_DIR }}/${{ matrix.project }}"
run: |
terragrunt init
terragrunt workspace select "core-shared-services-${TF_ENV}" || terragrunt workspace new "core-shared-services-${TF_ENV}"
- name: Plan
id: plan
working-directory: "${{ env.BASE_DIR }}/${{ matrix.project }}"
run: |
exitcode=0
chmod +x ${GITHUB_WORKSPACE}/scripts/redact-output.sh
terragrunt plan -detailed-exitcode -no-color -out=tf.plan | ${GITHUB_WORKSPACE}/scripts/redact-output.sh || exitcode=$?
echo "terragrunt plan exit code = $exitcode"
echo "exitcode=${exitcode}" >> $GITHUB_OUTPUT
(( exitcode == 1 )) && exit 1 || exit 0
- name: Apply
if: ${{ steps.plan.outputs.exitcode == '2' }}
working-directory: "${{ env.BASE_DIR }}/${{ matrix.project }}"
run: |
terragrunt apply tf.plan -no-color | ${GITHUB_WORKSPACE}/scripts/redact-output.sh | tee tfapply.txt
- name: Create Apply PR message
if: ${{ github.event_name == 'push' && steps.plan.outputs.exitcode == '2' }}
working-directory: "${{ env.BASE_DIR }}/${{ matrix.project }}"
run: |
comment() {
url="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
len=$(cat tfapply.txt | wc -c)
echo '**`${{ env.BASE_DIR }}/${{ matrix.project }}`** terraform apply on `${{ github.event_name }}` event [#${{ github.run_number }}](${url})'
echo
echo '```'
head -c 65476 tfapply.txt
echo
echo '```'
if [[ $len -gt 65476 ]]; then
echo "** Truncated output. See $url for the rest **"
fi
}
echo 'TF_APPLY_OUT<<EOF' >> $GITHUB_ENV
comment >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
- name: Post Apply to PR
if: ${{ github.event_name == 'push' && steps.plan.outputs.exitcode == '2' }}
env:
message: "${{ env.TF_APPLY_OUT }}"
pr_number: "${{ needs.init.outputs.pr_number }}"
run: |
escaped_message=$(echo "$message" | jq -Rsa .)
curl -sS -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
"https://api.github.com/repos/${{ github.repository }}/issues/${pr_number}/comments" \
-d '{"body":'"${escaped_message}"'}'