OpenTofuCLI icon
If icon
DiscordIncomingWebhook icon
Schedule icon

Detect Infrastructure Drift with Scheduled OpenTofu Plans

Detect infrastructure drift on a schedule with tofu plan and -detailed-exitcode in Kestra, alerting Discord only when live state diverges from the code.

Categories
Infrastructure

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.

How it works

  1. 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.
  2. 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.
  3. The errors block sends a distinct message when the plan errored, so an unknown drift status is never confused with a clean one.
  4. A disabled-by-default Schedule trigger runs the check daily at 05:00.

What you get

  • Drift as a first-class boolean output, usable by conditions, alerts, or downstream remediation tasks.
  • Alert-on-divergence semantics: no noise when the plan is empty, a ping when it is not.
  • Three distinct outcomes (clean, drift, broken check) mapped to three distinct behaviors.
  • A pattern that works for any stack by swapping the configuration and the state backend.

Who it's for

  • SREs who find out about console hotfixes weeks later during an unrelated apply.
  • Platform teams that want drift visibility without granting anyone apply permissions.
  • Anyone running OpenTofu who wants day 2 checks with the same tool that provisions.

Why orchestrate this with Kestra

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.

Prerequisites

  • A Discord incoming webhook for drift alerts.
  • The demo config uses the 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('...') }}.

Secrets

  • DISCORD_WEBHOOK_URL: Discord incoming webhook URL.

Quick start

  1. Add the DISCORD_WEBHOOK_URL secret to your Kestra namespace.
  2. Execute the flow once and confirm Discord receives the drift alert, since the demo has no prior state.
  3. Replace main.tf with your real configuration and a remote backend, then set disabled: false on the daily trigger.

How to extend

  • Chain a remediation apply behind a human approval when drift is detected, as shown in the OpenTofu approval gate blueprint.
  • Store the rendered plan with outputFiles and attach it to the alert message for one-click review.
  • Fan the check out across multiple stacks with ForEach, one working directory per environment.
  • Escalate repeated drift by counting consecutive detections in a KV store and alerting a different channel past a threshold.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.