OpenTofuCLI icon
SlackIncomingWebhook icon
Pause icon

Gate OpenTofu Applies Behind Human Approval

Gate tofu apply behind human approval in Kestra. Plan once, review the saved plan from Slack, resume the paused execution, and apply exactly that plan.

Categories
Infrastructure

Approval workflows that re-plan after the approval approve nothing, since the plan the human read and the plan that gets applied can differ. This blueprint closes that gap with OpenTofu's saved plan files. io.kestra.plugin.opentofu.cli.OpenTofuCLI writes plan.tfplan plus a human readable plan.txt, Slack receives a review pointer, io.kestra.plugin.core.flow.Pause holds the execution, and after a human resumes it, a second task applies the identical plan.tfplan through inputFiles. The approver approves exactly what runs.

How it works

  1. plan runs tofu init and tofu plan -out=plan.tfplan, then renders the plan to text with tofu show. outputFiles uploads both files to Kestra internal storage.
  2. notify_approver posts to Slack before the pause, so the approver gets the execution ID and knows where to read plan.txt. The notification always precedes the gate.
  3. wait_for_approval (io.kestra.plugin.core.flow.Pause) suspends the execution until someone resumes it from the UI or the API. A paused execution applies nothing.
  4. apply_reviewed_plan receives the binary plan through inputFiles interpolation from the plan task's outputs, reinitializes the pinned provider, and runs tofu apply plan.tfplan. OpenTofu refuses a plan whose dependencies changed, which is exactly the guarantee an approval gate needs.
  5. confirm reports the completed apply, and the errors block alerts on any failure in either half.

What you get

  • A review artifact (plan.txt) attached to the execution, not buried in CI logs.
  • The apply bound to the reviewed plan file, so approval covers the real change set.
  • A paused execution as the approval record, with who resumed it in the audit log.
  • A demo stack using only the local_file provider, runnable with no cloud credentials.

Who it's for

  • Platform teams that require sign-off on production infrastructure changes.
  • Organizations replacing ticket-based approval theater with an enforceable gate.
  • Anyone who has been burned by an apply that diverged from the reviewed plan.

Why orchestrate this with Kestra

OpenTofu provides the saved plan mechanics but no way to hold a run open for a human. Kestra's Pause task turns the approval into flow state: the execution waits indefinitely, the Slack message carries the pointer, and resuming is a logged action. The plan file travels between tasks through internal storage, so the two OpenTofu invocations stay isolated while sharing exactly one artifact.

Prerequisites

  • A Slack incoming webhook for the review and confirmation messages.
  • No cloud credentials are needed for the demo config; for real providers, pass credentials through task env from {{ secret('...') }} in both OpenTofu tasks.

Secrets

  • SLACK_WEBHOOK_URL: Slack incoming webhook URL.

Quick start

  1. Add the SLACK_WEBHOOK_URL secret to your Kestra namespace.
  2. Execute the flow and open the Slack message, then read plan.txt in the plan task outputs.
  3. Resume the paused execution from the UI and confirm Slack reports the completed apply.
  4. Replace both main.tf copies with your real configuration, keeping the provider versions pinned so the saved plan stays valid.

How to extend

  • Add a timeout to the Pause task so unreviewed plans expire instead of waiting forever.
  • Route the review message to a dedicated approvals channel with the plan summary in the text.
  • Load the configuration from Git with namespaceFiles so the gate reviews exactly what is on the branch.
  • Skip the gate for empty plans by checking tofu plan -detailed-exitcode first, as shown in the OpenTofu drift detection blueprint.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.