Zum Inhalt

Trigger policy remediation only on assignment change

Context and Problem Statement

Every terraform apply destroys and recreates all policy remediation resources (~1000+) because a null_resource with timestamp() feeds into replace_triggered_by. This dominates plan and apply output, slows down runs, and obscures meaningful changes.

Decision Drivers

  • Apply output is dominated by remediation destroy/create cycles, hiding real infrastructure changes.
  • Hundreds of unnecessary Azure API calls per apply increase run time and risk throttling.
  • Policy remediations are one-shot tasks; re-creating them when nothing changed adds no value.
  • Azure Policy evaluates compliance on its own cycle and can trigger remediation independently.

Considered Options

  • Remove replace_triggered_by entirely and rely on Terraform for_each diff plus Azure Policy evaluation.
  • Replace timestamp() trigger with replace_triggered_by referencing the policy assignment resource.
  • Create a single remediation per assignment without policy_definition_reference_id.
  • Move remediation to a separate Terraform configuration or CI step.

Decision Outcome

Chosen option: "Replace timestamp() trigger with replace_triggered_by referencing the policy assignment resource", because it eliminates unnecessary churn while re-triggering remediations whenever the assignment is updated or replaced.

Per the Terraform replace_triggered_by documentation, a reference to a single resource instance triggers replacement when that instance is planned for update or replace -- not only on replacement. This means in-place changes to parameters, enforce, not_scopes, etc. also re-trigger all remediations for that assignment.

Consequences

  • Good, because it eliminates ~1000+ destroy/create cycles on every apply.
  • Good, because remediations are re-triggered when the assignment changes, including in-place updates (parameters, enforcement, scopes).
  • Good, because new policy definitions added to a set are handled by Terraform's for_each diff without needing the trigger.
  • Neutral, because Azure Policy's own evaluation cycle continues to handle ongoing compliance independently.
  • Bad, because new non-compliant resources appearing without a config change do not receive an immediate Terraform-triggered remediation.

Validation

  • Terraform plan on an unchanged environment shows zero remediation changes.
  • Terraform plan after modifying a policy assignment (e.g. parameters or enforce) shows remediation replacements for that assignment only.
  • Azure Portal confirms remediation tasks are created when assignments change.

Pros and Cons of the Options

Remove replace_triggered_by entirely

  • Good, because it is the simplest change.
  • Good, because Terraform for_each diff still handles added or removed policy definitions in the set.
  • Bad, because modifying assignment parameters (enforcement, scopes) would not re-trigger existing remediations.

Replace timestamp() trigger with replace_triggered_by referencing the policy assignment resource

  • Good, because it ties remediation lifecycle directly to the assignment that owns it.
  • Good, because it covers both in-place updates and replacements of the assignment.
  • Good, because blast radius is limited to the changed assignment.
  • Neutral, because it still replaces all remediations for a changed assignment, even if only one parameter changed.

Create a single remediation per assignment without policy_definition_reference_id

  • Good, because it would reduce resource count from N-per-set to 1-per-assignment.
  • Bad, because Azure requires policy_definition_reference_id for policy set definition remediations; omitting it is not supported.

Move remediation to a separate Terraform configuration or CI step

  • Good, because it decouples remediation lifecycle from infrastructure apply.
  • Bad, because it adds operational complexity and a second state file to manage.
  • Bad, because it breaks the co-location of assignment and remediation logic.