Skip to content

Commit

Permalink
Add GHA workflow to integrate and deploy OpenToFu plan
Browse files Browse the repository at this point in the history
Signed-off-by: Benoit Donneaux <[email protected]>
  • Loading branch information
btlogy committed Jan 21, 2025
1 parent 3de89d5 commit 3a689c4
Show file tree
Hide file tree
Showing 2 changed files with 233 additions and 0 deletions.
200 changes: 200 additions & 0 deletions .github/workflows/_tf.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# Re-usable workflow to continuously integrate and deploy OpenToFu plan

on:
workflow_call:
inputs:
tf_version:
description: 'Version of OpenToFu runtime to use'
required: false
type: string
default: '1.9.0'
tf_dir:
description: 'Path to the OpenToFu plan to use'
required: false
type: string
default: './'
gh_runner_version:
description: 'Version of the GitHub runner to use'
required: false
type: string
default: 'ubuntu-22.04'
auto_comment:
description: 'Enable automatic comment on GitHub pull request'
required: false
type: boolean
default: true
apply_on_branch:
description: 'Automaticaly apply plan when on a specific branch'
required: false
type: string
default: ''
aws_default_region:
description: 'AWS default region'
required: false
type: string
default: 'eu-central-1'
secrets:
aws_access_key_id:
description: 'AWS access key id'
required: false
aws_secret_access_key:
description: 'AWS secret access key'
required: false
hcloud_token:
description: 'API token for Hetzner Cloud'
required: false

jobs:
tf:
name: OpenToFu
runs-on: ${{ inputs.gh_runner_version }}
defaults:
run:
working-directory: ${{ inputs.tf_dir }}
env:
AWS_ACCESS_KEY_ID: ${{ secrets.aws_access_key_id }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.aws_secret_access_key }}
AWS_DEFAULT_REGION: ${{ inputs.aws_default_region }}
TF_VAR_hcloud_token: ${{ secrets.hcloud_token }}
permissions:
pull-requests: write
contents: read

steps:
- name: Checkout
id: checkout
uses: actions/checkout@v4

- name: Setup
id: setup
uses: opentofu/setup-opentofu@v1
with:
tofu_version: ${{ inputs.tf_version }}

- name: Format
id: fmt
run: tofu fmt -no-color -check -diff

- name: Init
id: init
run: tofu init -no-color -input=false

- name: Validate
id: validate
run: tofu validate -no-color

- name: Plan
id: plan
run: tofu plan -no-color -input=false -compact-warnings -out tf_plan.out

- name: Post Setup
id: post_setup
# Only to avoid wrapper to mess up outputs in later steps
uses: opentofu/setup-opentofu@v1
with:
tofu_version: ${{ inputs.tf_version }}
tofu_wrapper: false

- name: Verify
id: verify
run: |
# Process the plan to verify the presence of some data
# which can be used later to make additional checks
terraform show -no-color tf_plan.out > tf_plan.log 2> >(tee tf_plan.err >&2) && ret=0 || ret=$?
# Export the plan in json too
terraform show -json tf_plan.out > tf_plan.json
# Extract current state from the plan for later comparison
unzip tf_plan.out tfstate
# Extract data from temp files and export them as outputs for next steps
# - changes made, if any
echo "changes<<terraform_verify_changes" >> $GITHUB_OUTPUT
awk '/the following actions/,0' tf_plan.log >> $GITHUB_OUTPUT
echo "terraform_verify_changes" >> $GITHUB_OUTPUT
# - summary of the change, if any
echo "summary<<terraform_verify_summary" >> $GITHUB_OUTPUT
awk '/(Plan: |No changes. )/,1' tf_plan.log | sed -e 's/Plan: /change(s): /' >> $GITHUB_OUTPUT
echo "terraform_verify_summary" >> $GITHUB_OUTPUT
# - stderr describing errors, if any
echo "stderr<<terraform_verify_stderr" >> $GITHUB_OUTPUT
cat tf_plan.err >> $GITHUB_OUTPUT
echo "terraform_verify_stderr" >> $GITHUB_OUTPUT
# Exit with failure if any
exit $ret
- name: Comment
id: update
if: ${{ always() && github.event_name == 'pull_request' && inputs.auto_comment }}
uses: actions/github-script@v7
with:
github-token: ${{ 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('pr-auto-comment-${{ inputs.tf_dir }}')
})
// 2. Prepare format of the comment, using toJSON to escape any special char
const changes = [
${{ toJSON(steps.verify.outputs.changes) }},
]
const errors = [
${{ toJSON(steps.fmt.outputs.stdout) }},
${{ toJSON(steps.init.outputs.stderr) }},
${{ toJSON(steps.validate.outputs.stderr) }},
${{ toJSON(steps.plan.outputs.stderr) }},
${{ toJSON(steps.verify.outputs.stderr) }},
]
const output = `
<!-- pr-auto-comment-${{ inputs.tf_dir }} -->
### ${{ github.workflow }}
| Step | Outcome |
| ---- | ------- |
| :pencil2: **Format** | \`${{ steps.fmt.outcome }}\` |
| :wrench: **Init** ️| \`${{ steps.init.outcome }}\` |
| :mag: **Validate** | \`${{ steps.validate.outcome }}\` |
| :page_facing_up: **Plan** | \`${{ steps.plan.outcome }}\` |
| :passport_control: **Verify** | \`${{ steps.verify.outcome }}\` |
| :point_right: **Result** | ${{ ( steps.plan.outcome == 'success' && steps.verify.outcome == 'success' && steps.verify.outputs.summary ) || 'with error(s) - see below' }} |
<details><summary>show change(s)</summary>
\`\`\`
${ changes.filter(function(entry) { return /\S/.test(entry); }).join('\n') }
\`\`\`
</details>
<details><summary>show error(s)</summary>
\`\`\`
${ errors.filter(function(entry) { return /\S/.test(entry); }).join('\n') }
\`\`\`
</details>
*Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*
*Workflow: \`${{ github.workflow_ref }}\`*`;
// 3. If we have a comment, update it, otherwise create a new one
const comment_data = {
owner: context.repo.owner,
repo: context.repo.repo,
body: output
}
if (botComment) {
comment_data.comment_id = botComment.id
github.rest.issues.updateComment(comment_data)
} else {
comment_data.issue_number = context.issue.number
github.rest.issues.createComment(comment_data)
}
- name: Apply
id: apply
if: ${{ inputs.apply_on_branch != '' && github.ref == format('refs/heads/{0}', inputs.apply_on_branch) }}
run: tofu apply -no-color -input=false -auto-approve tf_plan.out
33 changes: 33 additions & 0 deletions .github/workflows/tf-core.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Workflow to continuously integrate and deploy OpenToFu plan for
# the core resources of Tahoe-LAFS (e.g.: DNS, self-hosted vps, ...)
name: ToFu - core
concurrency: tf-core_state

on:
push:
branches:
- main
paths:
- '.github/workflows/_tf.yml'
- '.github/workflows/tf-core.yml'
- 'tf/core/**'
pull_request:
paths:
- '.github/workflows/_tf.yml'
- '.github/workflows/tf-core.yml'
- 'tf/core/**'
workflow_dispatch:

jobs:
call-workflow-passing-data:
# Call the re-usable ToFu workflow
uses: ./.github/workflows/_tf.yml
with:
tf_version: '1.9.0'
tf_dir: tf/core
apply_on_branch: 'main'
aws_default_region: 'eu-central-1'
secrets:
aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
hcloud_token: ${{ secrets.HCLOUD_TOKEN }}

0 comments on commit 3a689c4

Please sign in to comment.