a slack app that turns your terraform plans into a proper approval workflow. π shaaame π
Review request lands in your channel
Review has been approved, and an apply action has been triggered |
Full plan review with one-click approval |
when terraform runs and detects a change in the plan, this app:
- just found out the plans changed again
- posts a message to the
#prod-infra-reviewschannel - lets you approve/reject, publicly, proudly
- stores the decision so you can pretend you actually reviewed it later
- π’ low risk: "oh look, you're adding a tag. how brave."
- π‘ medium risk: "you're changing some stuff. i'm watching you."
β οΈ high risk: "you're touching iam or security groups. i hope you know what you're doing."- π΄ critical risk: "love that for you"
sequenceDiagram
participant CI as CI/CD Pipeline
participant Artifacts as GitHub Artifacts
participant Webhook as Webhook
participant Slack as Slack App
participant User as Human Reviewer
participant Datastore as Slack Datastore
participant GitHub as GitHub API
CI->>CI: Run Terraform Plan
alt Plan has changes
CI->>Artifacts: Upload tfplan.json artifact
CI->>Webhook: POST review metadata (artifact name, counts)
Webhook->>Slack: Trigger approval workflow
Slack->>Datastore: Store review metadata
Slack->>User: Post message with buttons
User->>Slack: Click "Review & decide"
Slack->>Artifacts: Fetch tfplan.json (via GitHub API)
Slack->>User: Open review modal with plan details
User->>Slack: Approve/Reject in modal
alt Approve
Slack->>GitHub: POST repository_dispatch
GitHub->>CI: Trigger workflow (approve)
CI->>CI: Apply changes
Slack->>User: Update: β
Approved
else Reject
Slack->>GitHub: POST repository_dispatch
GitHub->>CI: Trigger workflow (reject)
CI->>CI: Cancel pipeline
Slack->>User: Update: β Rejected
end
else No changes
CI->>CI: Continue pipeline
end
built with:
- deno slack cli
- slack ui components
- a slack workspace (duh)
- a github repository with terraform (also duh)
- a ci/cd pipeline that can make http requests (github actions, gitlab ci, etc.)
- the ability to read documentation (you're doing great so far!)
more below.
- clone this repo (you already did this, good job!) β’ install the slack cli if you haven't:
brew install --cask slack-cli- link your app:
slack login- run the app:
slack run- get your webhook url from the slack app settings
add this to your ci/cd pipeline (github actions example):
- name: Upload Terraform Plan Artifact
uses: actions/upload-artifact@v4
with:
name: tfplan.json
path: infrastructure/tfplan.json
retention-days: 7
- name: Notify Slack about Terraform Plan
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK_URL }} \
-H "Content-Type: application/json" \
-d '{
"id": "${{ github.run_id }}",
"slackChannelId": "C1234567890",
"repositoryFullName": "${{ github.repository }}",
"runId": "${{ github.run_id }}",
"runNumber": "${{ github.run_number }}",
"commitHash": "${{ github.sha }}",
"terraformAdds": "5",
"terraformChanges": "2",
"terraformDestroys": "0",
"commitMessage": "${{ github.event.head_commit.message }}",
"commitTimeEpoch": "${{ github.event.head_commit.timestamp }}",
"author": "${{ github.event.head_commit.author.name }}",
"branch": "${{ github.ref_name }}",
"organizationName": "${{ github.repository_owner }}",
"repositoryName": "${{ github.event.repository.name }}",
"friendlyName": "company/infrastructure",
"artifactName": "tfplan.json"
}'note: the terraform plan JSON is stored as a GitHub artifact and fetched on-demand when opening the review modal. this avoids webhook size limits and slack datastore constraints.
using Slack CLI
slack env set GH_TOKEN xxxwhich has permission to dispatch events and read artifacts from the repositoryslack env set REVIEW_TRIGGER_URL xxxwhich is the url to dispatch the apply pipeline
- webhook trigger: our infra cd pipeline hits the webhook with terraform plan data
- workflow execution: slack starts the approval workflow
- latest claim: if there's already a review for this repo, it gets marked as stale and the new one becomes "latest"
- message posting: a beautiful message appears in your channel with:
- summary of changes (adds/changes/destroys)
- risk assessment (because we care about your job security)
- links to the full plan
- approve/reject buttons
- decision time: someone (hopefully not an intern) clicks a button
- github integration: the app calls github's api to approve/reject the run
- audit trail: everything gets stored for when the lawyers come calling
the app has a SopHisTicAteED algorithm to determine risk levels:
- iam changes: high risk (because permissions are scary)
- security groups: high risk (because security is important)
- database instances: high risk (because data is money)
- eks clusters: medium risk (because kubernetes is complex)
- s3 buckets: low risk (because storage is cheap)
to prevent chaos and ensure only the latest changes get applied:
- latest claims: when a new terraform plan comes in, it becomes the "latest" and any existing reviews are marked as stale
- modal blocking: only the latest review can open the approval modal - stale reviews show an error message
- no confusion: no more wondering which review is the "real" one
this app is weird, it's part of slack's new function platform.
it owns logs, deploying, running, the lot. nice.
# To run a dev version locally
slack run
# To deploy to production
slack deploy
# To view logs
slack logs# install dependencies (there aren't any, it's deno!)
# but you can run tests (there aren't any, yoloΒ±)
deno task test
# format code (because we're civilized)
deno fmt
# lint code (because we care about quality)
deno lintβββ functions/ # the main logic (where the magic happens)
βββ workflows/ # essentially proxies traffic through to the "function" layer
βββ triggers/ # a webhook from our ci pipeline with infra review data
βββ datastores/ # stores the review data, avoid slacks interactivity shit
βββ types/ # π΅
βββ utils/tf/ # terraform parsing and whatnot
βββ assets/ # πΌοΈ


