When managing Azure infrastructure with Terraform — or any Infrastructure-as-Code tool for that matter — one of the more frequent ClickOps issues you’ll run into involves role assignments.

A particularly common scenario plays out when Terraform attempts to assign a role that has already been manually created through the Azure portal or CLI, resulting in a 409 Conflict error. The typical error looks like this:

│ Error: unexpected status 409 (409 Conflict) with error: RoleAssignmentExists: The role assignment already exists. │ with azurerm_role_assignment.terraform_user_assigned_storage_contributor_data, │ on storage.tf line 32, in resource “azurerm_role_assignment” “terraform_user_assigned_storage_contributor_data”: │ 32: resource “azurerm_role_assignment” “terraform_user_assigned_storage_contributor_data” {

As one might suspect, this error indicates that the role assignment Terraform is trying to create already exists…. but why?!?!

Usually, this happens because someone manually added the role through the Azure Portal or CLI in an effort to quickly resolve a permissions issue — a classic example of ClickOps.

Why This Happens

Terraform maintains a state file that records the resources it manages. If a resource — like a role assignment — was created outside of Terraform, it’s not represented in this state. So when Terraform tries to create what it believes is a new role assignment, Azure returns a 409 Conflict because that exact assignment already exists.

Two Ways to Resolve the Conflict

The good news? Its a relatively simple fix. Once you’re in this situation, you have two paths forward:

Option 1: Delete the Role Assignment

You can delete the existing role assignment manually and then re-run terraform apply. This allows Terraform to create the resource and add it to the state as intended.

However, this might not be ideal in all situations—especially in environments where you don’t want to temporarily remove access or disrupt existing permissions. Think — PRODUCTION!!!

Not a great look. The loss of a critical role assignment could result in hundreds of thousands of transactions failing even if you just remove and re-add it as quickly as you possibly can. Sorry, you’re not that fast…we are but mere mortals are we not?

Option 2: Import the Existing Role Assignment

A more graceful solution is to import the existing role assignment into Terraform. This tells Terraform to treat the manually created resource as if it were managed by Terraform all along.

Here’s an example of an import block for a role assignment:

import {
  id = "/subscriptions/<subscription-id>/resourceGroups/<resource-group-name>/providers/Microsoft.Storage/storageAccounts/<storage-account-name>/providers/Microsoft.Authorization/roleAssignments/<role-assignment-id>"
  to = azurerm_role_assignment.terraform_user_assigned_storage_contributor_data
}

Replace the placeholders with your actual values. This block will bring the manually created role assignment under Terraform’s control.

Cleaning Up the Import Block

Import blocks are environment-specific. That means they’re not portable and shouldn’t remain in your codebase long-term. Once you’ve run the import and Terraform has recognized the resource, you should remove the import block to prevent confusion or future state mismatches.

Final Thoughts

ClickOps can cause unexpected conflicts when using Terraform to manage Azure resources, especially when manual fixes or “drift” are introduced outside of the infrastructure-as-code workflow.

Understanding how to resolve role assignment conflicts — either by removing the manually created resource or importing it into Terraform — is a basic skill for maintaining clean and reliable infrastructure deployments with Terraform or any infrastructure-as-code tool. When using Terraform, you should always strive to keep your state in sync with reality to avoid these kinds of surprises down the line. “Plan” early, “plan” often.