Terraform Deploy Workflow Implementation Plan¶
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Create GitHub Actions workflow that triggers Windmill flows when Terraform module changes merge to main.
Architecture: A reusable GitHub Actions workflow detects changes in tf/vault, tf/grafana, or tf/authentik directories and triggers a generic parameterized Windmill flow via API. The flow accepts module and ref parameters, replacing the hardcoded deploy_vault.flow.
Tech Stack: GitHub Actions, Windmill flows (YAML), curl for API calls
Task 1: Create Generic Windmill Flow¶
Files:
- Create: windmill/f/terraform/deploy_terraform.flow/flow.yaml
Step 1: Create flow directory
mkdir -p windmill/f/terraform/deploy_terraform.flow
Step 2: Write the flow file
Create windmill/f/terraform/deploy_terraform.flow/flow.yaml:
summary: Deploy Terraform module with approval
description: |
Generic deployment flow for any Terraform module:
1. Clone repository at specified ref
2. Initialize Terraform for specified module
3. Run plan and check for changes
4. If changes: send Discord notification, wait for approval, apply
5. If no changes: complete silently
Inputs:
- module: Terraform module path (e.g., tf/vault)
- ref: Git ref to checkout (commit SHA or branch)
schema:
$schema: 'https://json-schema.org/draft/2020-12/schema'
type: object
required:
- module
- ref
properties:
module:
type: string
description: Terraform module path (e.g., tf/vault, tf/grafana, tf/authentik)
ref:
type: string
description: Git ref to checkout (commit SHA or branch name)
value:
same_worker: true
modules:
- id: git_clone
value:
type: script
input_transforms:
branch:
type: javascript
expr: flow_input.ref
github:
type: javascript
expr: resource('f/resources/github')
repository:
type: static
value: fzymgc-house/selfhosted-cluster
workspace_dir:
type: static
value: /tmp/terraform-workspace
path: f/terraform/git_clone
- id: terraform_init
value:
type: script
input_transforms:
module_path:
type: javascript
expr: flow_input.module
s3:
type: javascript
expr: resource('f/resources/s3')
s3_bucket_prefix:
type: javascript
expr: variable('g/all/s3_bucket_prefix')
tfc_token:
type: javascript
expr: variable('g/all/tfc_token')
workspace_path:
type: javascript
expr: results.git_clone.workspace_path
path: f/terraform/terraform_init
- id: terraform_plan
value:
type: script
input_transforms:
module_dir:
type: javascript
expr: results.terraform_init.module_dir
tfc_token:
type: javascript
expr: variable('g/all/tfc_token')
vault_addr:
type: static
value: 'https://vault.fzymgc.house'
vault_token:
type: javascript
expr: variable('g/all/vault_terraform_token')
path: f/terraform/terraform_plan
- id: check_changes
value:
type: branchone
branches:
- summary: Has changes - request approval and apply
expr: results.terraform_plan.has_changes
modules:
- id: notify_approval
value:
type: script
input_transforms:
discord:
type: javascript
expr: resource('f/bots/terraform_discord_bot_configuration')
discord_bot_token:
type: javascript
expr: resource('f/bots/terraform_discord_bot_token_configuration')
module:
type: javascript
expr: flow_input.module
plan_details:
type: javascript
expr: results.terraform_plan.plan_details
plan_summary:
type: javascript
expr: results.terraform_plan.plan_summary
path: f/terraform/notify_approval
suspend:
required_events: 1
timeout: 86400
- id: terraform_apply
value:
type: script
input_transforms:
module_dir:
type: javascript
expr: results.terraform_init.module_dir
tfc_token:
type: javascript
expr: variable('g/all/tfc_token')
vault_addr:
type: static
value: 'https://vault.fzymgc.house'
vault_token:
type: javascript
expr: variable('g/all/vault_terraform_token')
path: f/terraform/terraform_apply
- id: notify_success
value:
type: script
input_transforms:
approval_message_id:
type: javascript
expr: results.notify_approval?.message_id
details:
type: static
value: Terraform apply completed successfully
discord:
type: javascript
expr: resource('f/bots/terraform_discord_bot_configuration')
discord_bot_token:
type: javascript
expr: resource('f/bots/terraform_discord_bot_token_configuration')
module:
type: javascript
expr: flow_input.module
status:
type: static
value: success
path: f/terraform/notify_status
default: []
failure_module:
id: notify_failure
value:
type: script
input_transforms:
approval_message_id:
type: javascript
expr: results.notify_approval?.message_id
details:
type: javascript
expr: error.message
discord:
type: javascript
expr: resource('f/bots/terraform_discord_bot_configuration')
discord_bot_token:
type: javascript
expr: resource('f/bots/terraform_discord_bot_token_configuration')
module:
type: javascript
expr: flow_input.module
status:
type: static
value: failed
path: f/terraform/notify_status
Step 3: Validate YAML syntax
python3 -c "import yaml; yaml.safe_load(open('windmill/f/terraform/deploy_terraform.flow/flow.yaml'))" && echo "YAML valid"
Expected: YAML valid
Step 4: Commit the new flow
git add windmill/f/terraform/deploy_terraform.flow/
git commit -m "feat(windmill): Add generic deploy_terraform flow
Parameterized flow accepting module and ref inputs, replacing
module-specific flows. Supports tf/vault, tf/grafana, tf/authentik.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>"
Task 2: Create GitHub Actions Workflow¶
Files:
- Create: .github/workflows/terraform-deploy.yml
Step 1: Write the workflow file
Create .github/workflows/terraform-deploy.yml:
# Triggers Windmill terraform deployment flows when tf/* modules change
name: Terraform Deploy
on:
push:
branches: [main]
paths:
- 'tf/vault/**'
- 'tf/grafana/**'
- 'tf/authentik/**'
workflow_dispatch:
inputs:
module:
description: 'Terraform module to deploy'
required: true
type: choice
options:
- tf/vault
- tf/grafana
- tf/authentik
jobs:
detect-changes:
runs-on: ubuntu-latest
if: github.event_name == 'push'
outputs:
vault: ${{ steps.filter.outputs.vault }}
grafana: ${{ steps.filter.outputs.grafana }}
authentik: ${{ steps.filter.outputs.authentik }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
vault:
- 'tf/vault/**'
grafana:
- 'tf/grafana/**'
authentik:
- 'tf/authentik/**'
deploy:
runs-on: fzymgc-house-cluster-runners
needs: [detect-changes]
if: always() && (needs.detect-changes.result == 'success' || needs.detect-changes.result == 'skipped')
strategy:
fail-fast: false
matrix:
module: [tf/vault, tf/grafana, tf/authentik]
steps:
- name: Check if should deploy
id: should-deploy
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
if [ "${{ github.event.inputs.module }}" == "${{ matrix.module }}" ]; then
echo "deploy=true" >> $GITHUB_OUTPUT
else
echo "deploy=false" >> $GITHUB_OUTPUT
fi
else
case "${{ matrix.module }}" in
tf/vault)
echo "deploy=${{ needs.detect-changes.outputs.vault }}" >> $GITHUB_OUTPUT
;;
tf/grafana)
echo "deploy=${{ needs.detect-changes.outputs.grafana }}" >> $GITHUB_OUTPUT
;;
tf/authentik)
echo "deploy=${{ needs.detect-changes.outputs.authentik }}" >> $GITHUB_OUTPUT
;;
esac
fi
- name: Trigger Windmill flow
if: steps.should-deploy.outputs.deploy == 'true'
env:
WMILL_TOKEN: ${{ secrets.WMILL_TOKEN }}
run: |
echo "Triggering deploy for ${{ matrix.module }} at ${{ github.sha }}"
response=$(curl -sf -X POST \
-H "Authorization: Bearer $WMILL_TOKEN" \
-H "Content-Type: application/json" \
-d '{"module": "${{ matrix.module }}", "ref": "${{ github.sha }}"}' \
"https://windmill.fzymgc.house/api/w/terraform-gitops-prod/jobs/run/p/f/terraform/deploy_terraform")
echo "Windmill job started: $response"
Step 2: Validate YAML syntax
python3 -c "import yaml; yaml.safe_load(open('.github/workflows/terraform-deploy.yml'))" && echo "YAML valid"
Expected: YAML valid
Step 3: Commit the workflow
git add .github/workflows/terraform-deploy.yml
git commit -m "feat(ci): Add terraform-deploy workflow
Triggers Windmill deploy_terraform flow when tf/* modules change.
Supports push to main and manual dispatch.
- Uses dorny/paths-filter for change detection
- Runs on self-hosted runner for Windmill API access
- Matrix strategy handles all three modules
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>"
Task 3: Delete Old Flow¶
Files:
- Delete: windmill/f/terraform/deploy_vault.flow/flow.yaml
Step 1: Remove the old flow directory
rm -rf windmill/f/terraform/deploy_vault.flow
Step 2: Verify deletion
ls windmill/f/terraform/deploy_vault.flow 2>&1 | grep -q "No such file" && echo "Deleted successfully"
Expected: Deleted successfully
Step 3: Commit the deletion
git add -A windmill/f/terraform/deploy_vault.flow
git commit -m "chore(windmill): Remove deploy_vault.flow
Replaced by generic deploy_terraform.flow which accepts module
and ref parameters.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>"
Task 4: Update Documentation¶
Files:
- Modify: docs/windmill-migration.md
Step 1: Add note about new workflow
Add the following section after the "Operational Workflows" section in docs/windmill-migration.md:
### Terraform GitOps Trigger
The `terraform-deploy.yml` GitHub Actions workflow triggers Windmill flows when Terraform modules change:
- **Trigger:** Push to `main` with changes in `tf/vault/**`, `tf/grafana/**`, or `tf/authentik/**`
- **Manual:** workflow_dispatch with module selector
- **Flow:** `f/terraform/deploy_terraform` (generic parameterized flow)
- **Runner:** `fzymgc-house-cluster-runners` (self-hosted for Windmill API access)
Step 2: Commit documentation update
git add docs/windmill-migration.md
git commit -m "docs: Document terraform-deploy workflow
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>"
Task 5: Push and Create PR¶
Step 1: Push branch
git push -u origin feat/terraform-deploy-workflow
Step 2: Create PR
gh pr create \
--title "feat: Add terraform-deploy workflow for GitOps automation" \
--body "$(cat <<'EOF'
## Summary
Adds GitHub Actions workflow to trigger Windmill flows when Terraform module changes are merged to main.
- Creates generic `deploy_terraform.flow` accepting `module` and `ref` parameters
- Creates `terraform-deploy.yml` workflow with change detection
- Removes module-specific `deploy_vault.flow`
## Changes
- `.github/workflows/terraform-deploy.yml` - New workflow
- `windmill/f/terraform/deploy_terraform.flow/flow.yaml` - New generic flow
- `windmill/f/terraform/deploy_vault.flow/` - Deleted (replaced)
- `docs/windmill-migration.md` - Updated documentation
## Testing
1. Merge this PR
2. Manually trigger workflow via Actions tab to test
3. Make a small tf/vault change to test push trigger
## Design
See `docs/plans/2025-12-14-terraform-deploy-workflow-design.md`
🤖 Generated with [Claude Code](https://claude.ai/code)
EOF
)"
Verification Checklist¶
After PR is merged:
- [ ] Manually trigger workflow from GitHub Actions → should start Windmill job
- [ ] Check Windmill UI for
deploy_terraformflow execution - [ ] Verify Discord notification appears (if changes detected)
- [ ] Make small tf/vault change → verify automatic trigger