New to Kestra?
Use blueprints to kickstart your first workflows.
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.
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.
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.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.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.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.confirm reports the completed apply, and the errors block alerts on any failure in either half.plan.txt) attached to the execution, not buried in CI logs.local_file provider, runnable with no cloud credentials.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.
env from {{ secret('...') }} in both OpenTofu tasks.SLACK_WEBHOOK_URL: Slack incoming webhook URL.SLACK_WEBHOOK_URL secret to your Kestra namespace.plan.txt in the plan task outputs.main.tf copies with your real configuration, keeping the provider versions pinned so the saved plan stays valid.timeout to the Pause task so unreviewed plans expire instead of waiting forever.namespaceFiles so the gate reviews exactly what is on the branch.tofu plan -detailed-exitcode first, as shown in the OpenTofu drift detection blueprint.