Detecting and resolving configuration drift between Terraform state and actual Microsoft 365 resources
Configuration drift occurs when the actual state of Microsoft 365 resources diverges from what Terraform expects based on your configuration and state file. This guide explains how to detect drift, understand its causes, and implement strategies to prevent and resolve it.
The most common method - compares configuration to actual state:
$ terraform planmicrosoft365_graph_beta_groups_group.engineering: Refreshing state... [id=12345678-1234-1234-1234-123456789abc]Note: Objects have changed outside of TerraformTerraform detected the following changes made outside of Terraform since the last "terraform apply": # microsoft365_graph_beta_groups_group.engineering has changed ~ resource "microsoft365_graph_beta_groups_group" "engineering" { id = "12345678-1234-1234-1234-123456789abc" ~ description = "Engineering department" -> "Engineering team - updated manually" display_name = "Engineering Team" # (10 unchanged attributes hidden) }No changes. Your infrastructure matches the configuration.Your configuration already matches the changes detected above.
Explicitly check for drift without planning changes:
$ terraform plan -refresh-onlymicrosoft365_graph_beta_groups_group.engineering: Refreshing state... [id=12345678-1234-1234-1234-123456789abc]This is a refresh-only plan, so Terraform will not take any actions to undo these.If you were expecting these changes then you can apply this plan to record the updated values in the Terraform state. # microsoft365_graph_beta_groups_group.engineering has changed ~ resource "microsoft365_graph_beta_groups_group" "engineering" { id = "12345678-1234-1234-1234-123456789abc" ~ description = "Engineering department" -> "Engineering team - updated manually" display_name = "Engineering Team" }Plan: 0 to add, 0 to change, 0 to destroy.
terraform refresh is deprecated in favor of terraform apply -refresh-only. The standalone refresh command updates state without showing you what changed first.
Manual change: Admin changes state to “enabledForReportingButNotEnforced”Detection:
$ terraform plan # microsoft365_graph_beta_identity_and_access_conditional_access_policy.mfa has changed ~ resource "microsoft365_graph_beta_identity_and_access_conditional_access_policy" "mfa" { ~ state = "enabledForReportingButNotEnforced" -> "enabled" }
Re-enabling a conditional access policy through Terraform could lock out users if not carefully planned. Always test policy changes in report-only mode first.
Revert the resource to match your Terraform configuration:
# Apply configuration, reverting manual changesterraform applyPlan: 0 to add, 1 to change, 0 to destroy.Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yesmicrosoft365_graph_beta_groups_group.engineering: Modifying... [id=12345678-1234-1234-1234-123456789abc]microsoft365_graph_beta_groups_group.engineering: Modifications complete after 2s
Accept drift and update state without changing configuration:
# Review driftterraform plan -refresh-only# Accept drift and update stateterraform apply -refresh-onlyDo you want to perform these actions? Only 'yes' will be accepted to approve. Enter a value: yesmicrosoft365_graph_beta_groups_group.engineering: Refreshing state... [id=12345678-1234-1234-1234-123456789abc]Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
This creates permanent drift between configuration and state. Use sparingly and document why configuration doesn’t match reality.
# Remove broad permissions from admins# Grant only read access to Terraform-managed resourcesaz role assignment create \ --role "Directory Readers" \ --assignee[email protected] \ --scope /# Grant full permissions only to Terraform service principalaz role assignment create \ --role "Directory.ReadWrite.All" \ --assignee[email protected] \ --scope /
# Development environment for testingresource "microsoft365_graph_beta_groups_group" "dev_engineering" { display_name = "Engineering Team - Dev" # Test changes here first}# Production environmentresource "microsoft365_graph_beta_groups_group" "prod_engineering" { display_name = "Engineering Team" # Deploy after successful dev testing}