New to Kestra?
Use blueprints to kickstart your first workflows.
Detect infrastructure drift on a schedule with tofu plan and -detailed-exitcode in Kestra, alerting Discord only when live state diverges from the code.
Drift is what happens between applies: a console hotfix, a deleted resource, a provider-side change. This blueprint runs tofu plan -detailed-exitcode on a schedule through io.kestra.plugin.opentofu.cli.OpenTofuCLI and converts the exit code into structured flow data. Exit code 0 means no changes, 2 means the live state diverges from the configuration, and 1 means the plan itself broke. The shell captures the code without failing the task, emits a drift boolean through Kestra's output protocol, and an If task pings Discord only when drift exists, so the channel stays silent on clean days.
check_drift receives main.tf through inputFiles and runs tofu init followed by tofu plan -detailed-exitcode. The plan command is wrapped so a non-zero exit is captured into a shell variable instead of failing the task, exit code 1 still fails the task because the check itself broke, and the final echo emits {"outputs": {"drift": ..., "plan_exit_code": ...}} through Kestra's output protocol.alert_if_drift (io.kestra.plugin.core.flow.If) reads {{ outputs.check_drift.vars.drift }} and runs the Discord alert only when it is true.errors block sends a distinct message when the plan errored, so an unknown drift status is never confused with a clean one.Schedule trigger runs the check daily at 05:00.The -detailed-exitcode flag is only useful if something interprets the exit code, and cron plus a shell script gives you no history, no structured outputs, and no branching. Kestra turns the code into flow data: the boolean drives an If task today and can drive an automated remediation apply tomorrow, every check is an execution you can audit, and the schedule, the secrets, and the alerting live in one YAML file.
local_file provider and starts with no persisted state, so the first run always reports drift. That is intentional: it demonstrates the alert path end to end. For real drift detection, point main.tf at your remote state backend and pass backend credentials through task env from {{ secret('...') }}.DISCORD_WEBHOOK_URL: Discord incoming webhook URL.DISCORD_WEBHOOK_URL secret to your Kestra namespace.main.tf with your real configuration and a remote backend, then set disabled: false on the daily trigger.outputFiles and attach it to the alert message for one-click review.ForEach, one working directory per environment.