-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ci: extend workflows with Terraform functionality (#24)
* ci: extend workflows with Terraform functionality * fixup! ci: extend workflows with Terraform functionality * feat(terraform): add GitHub OIDC role * ci: fix orchestrator patterns * ci: add support for readonly OIDC role during plan * ci: add Terraform lock files * ci: fix the GitHub OIDC subjects * fixup! ci: add support for readonly OIDC role during plan * ci: fix steps running on failure * ci: fix orchestrator job * fixup! ci: fix steps running on failure * fixup! ci: fix steps running on failure * fix(terraform): add Dynamodb and S3 permissions to RO role * fix(terraform): import remote state * ci: disable production workflows * ci: improve orchestrator to pickup module changes * fix(terraform): add environment state lock permissions to RO OIDC * feat(terraform): add dev environment state * ci: support empty TF state * fix(terraform): remove `hashicorp/http` * fixup! ci: support empty TF state * ci: allow Terraform env and account to plan in parallel * fix(terraform): remove `prod` related files * fix(terraform): run `fmt` * ci: fix variable name * ci: add image tags to TF plan comment
- Loading branch information
1 parent
c50dd0e
commit a1d4494
Showing
26 changed files
with
1,085 additions
and
10 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
name: Deploy Terraform account | ||
|
||
on: | ||
workflow_dispatch: | ||
inputs: | ||
account: | ||
description: "Account to deploy" | ||
required: true | ||
type: choice | ||
options: | ||
- nonprod | ||
- prod | ||
ref: | ||
description: "The branch or tag ref to checkout" | ||
type: string | ||
required: false | ||
apply: | ||
type: boolean | ||
required: true | ||
description: "Apply the terraform?" | ||
default: false | ||
terraform-args: | ||
type: string | ||
required: false | ||
description: "Additional arguments to pass to terraform" | ||
workflow_call: | ||
inputs: | ||
ref: | ||
description: "The branch or tag ref to checkout" | ||
type: string | ||
required: false | ||
account: | ||
description: "Environment to deploy" | ||
type: string | ||
required: true | ||
apply: | ||
type: boolean | ||
required: true | ||
terraform-args: | ||
type: string | ||
required: false | ||
description: "Additional arguments to pass to terraform" | ||
outputs: | ||
terraform-output: | ||
description: "Terraform output" | ||
value: ${{ jobs.deploy.outputs.terraform-output }} | ||
|
||
permissions: | ||
contents: read | ||
id-token: write | ||
pull-requests: write | ||
|
||
concurrency: | ||
group: terraform-account-${{ inputs.account }} | ||
|
||
jobs: | ||
deploy: | ||
name: ${{ inputs.apply && 'Apply' || 'Plan' }} | ||
runs-on: ubuntu-latest | ||
# As a workaround for: https://github.com/actions/runner/issues/2120 | ||
# Environment will not be defined for non-apply jobs to ensure that deployments are kept accurate in the GitHub UI. | ||
# It is still possible to overwrite variables/secrets in this workflow by using `format('ACCOUNT_{0}_SOME_VAR', inputs.environment)` - e.g. ACCOUNT_nonprod_VAR | ||
environment: ${{ inputs.apply && format('account-{0}', inputs.account) || null }} | ||
outputs: | ||
terraform-output: ${{ steps.terraform-output.outputs.json }} | ||
env: | ||
WORKING_DIR: infra/terraform/accounts/${{ inputs.account }} | ||
AWS_OIDC_ROLE: ${{ vars[format('ACCOUNT_{0}_TF_OIDC{1}_ROLE', inputs.account, (inputs.apply && '' || '_READONLY'))] || vars[format('TF_OIDC{0}_ROLE', (inputs.apply && '' || '_READONLY'))] }} | ||
AWS_REGION: ${{ vars[format('ACCOUNT_{0}_TF_AWS_REGION', inputs.account)] || vars.TF_AWS_REGION }} | ||
defaults: | ||
run: | ||
shell: bash | ||
working-directory: ${{ env.WORKING_DIR }} | ||
steps: | ||
- uses: actions/checkout@v4 | ||
with: | ||
ref: ${{ inputs.ref || null }} | ||
sparse-checkout: infra/terraform | ||
|
||
- name: Setup Terraform | ||
uses: hashicorp/setup-terraform@v3 | ||
|
||
- name: Configure AWS credentials | ||
uses: aws-actions/configure-aws-credentials@v4 | ||
with: | ||
role-to-assume: ${{ env.AWS_OIDC_ROLE }} | ||
aws-region: ${{ env.AWS_REGION }} | ||
|
||
- name: Terraform init | ||
id: init | ||
run: terraform init -no-color -input=false | ||
|
||
- name: Validate | ||
id: validate | ||
run: terraform validate -no-color | ||
|
||
- name: Plan | ||
if: ${{ !inputs.apply }} | ||
id: plan | ||
run: terraform plan -no-color -input=false -out=tfplan ${{ inputs.terraform-args || '' }} && terraform show -no-color tfplan | ||
|
||
- name: Get plan changes | ||
if: ${{ !inputs.apply }} | ||
id: show | ||
run: | | ||
echo "changes=$(terraform-bin show -json -no-color tfplan | jq -r -c '[.resource_changes[] | select(.change.actions[0] != "no-op") | {action: .change.actions[0], address: .address}] | group_by(.action) | map({(.[0].action): map(.address)}) | add')" >> $GITHUB_OUTPUT | ||
- uses: actions/github-script@v7 | ||
if: ${{ always() && !cancelled() && !failure() && !inputs.apply && github.event_name == 'pull_request' }} | ||
env: | ||
PLAN: "${{ steps.plan.outputs.stdout }}" | ||
CHANGES: "${{ steps.show.outputs.changes }}" | ||
with: | ||
retries: 3 | ||
script: | | ||
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('data-gh-workflow="${{ inputs.account }}-account-plan"') | ||
}) | ||
let summary = ""; | ||
const actionIcons = { | ||
create: "π", | ||
read: "π", | ||
update: "π", | ||
delete: "ποΈ", | ||
"no-op": "π«" | ||
}; | ||
let changes = {}; | ||
if (process.env.CHANGES) { | ||
changes = JSON.parse(process.env.CHANGES) || {}; | ||
} | ||
Object.keys(changes).forEach(action => { | ||
summary += `**${actionIcons[action]} ${action.charAt(0).toUpperCase() + action.slice(1)}s**\n\n\`\`\`tf\n`; | ||
changes[action].forEach(change => { | ||
summary += `${change}\n`; | ||
}); | ||
summary += "\`\`\`\n"; | ||
}); | ||
const output = ` | ||
## Terraform plan for account: \`${{ inputs.account }}\` | ||
**Commit:** ${{ github.event.pull_request.head.sha }} | ||
### Plan summary | ||
\`${changes.create?.length || 0} to add, ${changes.update?.length || 0} to change, ${changes.delete?.length || 0} to destroy\` | ||
${summary} | ||
---- | ||
<details data-gh-workflow="${{ inputs.account }}-account-plan"><summary>Show full plan</summary> | ||
\`\`\`tf\n | ||
${process.env.PLAN} | ||
\`\`\` | ||
</details>`; | ||
if (botComment) { | ||
github.rest.issues.updateComment({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
comment_id: botComment.id, | ||
body: output | ||
}) | ||
} else { | ||
github.rest.issues.createComment({ | ||
issue_number: context.issue.number, | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
body: output | ||
}) | ||
} | ||
- name: Apply | ||
id: apply | ||
if: ${{ inputs.apply }} | ||
run: terraform apply -no-color -input=false -auto-approve ${{ inputs.terraform-args || '' }} | ||
|
||
- name: Set outputs | ||
if: ${{ always() && !cancelled() && !failure() }} | ||
id: terraform-output | ||
run: | | ||
echo "json=$(terraform-bin output -json -no-color | jq -r -c 'to_entries | map({(.key): .value.value}) | add')" >> $GITHUB_OUTPUT |
Oops, something went wrong.