hashicorp / terraform-provider-azurerm

Terraform provider for Azure Resource Manager
https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs
Mozilla Public License 2.0
4.6k stars 4.65k forks source link

Inconsistent state for `azurerm_firewall` resource after creation #15387

Open krowlandson opened 2 years ago

krowlandson commented 2 years ago

Community Note

Terraform (and AzureRM Provider) Version

Terraform v1.1.5
on windows_amd64
+ provider registry.terraform.io/hashicorp/azurerm v2.96.0

Affected Resource(s)

Terraform Configuration Files

This is being observed as part of a test deployment located within the caf-enterprise-scale module, but can be reproduced outside the module.

See the following lines of code where we configure a deployment of the module where we observe this issue: https://github.com/krowlandson/terraform-azurerm-caf-enterprise-scale/blob/89d31d4dddeb5d4bd5c9f948871111b66c9e3aeb/tests/modules/test_003_add_mgmt_conn/main.tf#L117-L146

Debug Output

Panic Output

Expected Behaviour

When creating an azurerm_firewall resource using Terraform, the correct state should be stored upon resource creation.

Actual Behaviour

When running terraform plan or terraform apply after initial creation of an azurerm_firewall resource, Terraform reports that Objects have changed outside of Terraform:

Terraform detected the following changes made outside of Terraform since the last "terraform apply":

  # module.test_connectivity.azurerm_firewall.connectivity["/subscriptions/<redacted>/resourceGroups/12345-connectivity-northeurope/providers/Microsoft.Network/azureFirewalls/12345-fw-northeurope"] has changed
  ~ resource "azurerm_firewall" "connectivity" {
      + dns_servers         = []
        id                  = "/subscriptions/<redacted>/resourceGroups/12345-connectivity-northeurope/providers/Microsoft.Network/azureFirewalls/12345-fw-northeurope"
        name                = "12345-fw-northeurope"
      + private_ip_ranges   = []
        tags                = {}
        # (6 unchanged attributes hidden)

        # (1 unchanged block hidden)
    }

As can be seen from the above, the following attributes appear to be affected:

Attempting to set these values to [] instead of null (which is the module default) to ensure the code matches the configuration, results in the following errors:

╷
│ Error: Not enough list items
│
│   with module.test_connectivity.azurerm_firewall.connectivity["/subscriptions/<redacted>/resourceGroups/12345-connectivity-northeurope/providers/Microsoft.Network/azureFirewalls/12345-fw-northeurope"],
│   on ..\..\..\resources.connectivity.tf line 256, in resource "azurerm_firewall" "connectivity":
│  256:   dns_servers        = []
│
│ Attribute requires 1 item minimum, but config has only 0 declared.
╵
╷
│ Error: Not enough list items
│
│   with module.test_connectivity.azurerm_firewall.connectivity["/subscriptions/<redacted>/resourceGroups/12345-connectivity-northeurope/providers/Microsoft.Network/azureFirewalls/12345-fw-northeurope"],
│   on ..\..\..\resources.connectivity.tf line 257, in resource "azurerm_firewall" "connectivity":
│  257:   private_ip_ranges  = []
│
│ Attribute requires 1 item minimum, but config has only 0 declared.
╵

Steps to Reproduce

  1. Download a copy of the caf-enterprise-scale module from this pre-release branch: https://github.com/krowlandson/terraform-azurerm-caf-enterprise-scale/tree/refactor-tests
  2. Change directory to the ./tests/modules/test_003_add_mgmt_conn sub folder
  3. Optionally update to the latest provider version in terraform.tf
  4. Run terraform init
  5. Run terraform apply and confirm deployment
  6. Upon completion of deployment, run terraform apply a second time and observe the changes

Important Factoids

References

none found

magodo commented 2 years ago

The diff above is showing the difference between what is stored in the state and the remote state of the resource.

In your first apply, due to how Terraoform works, it evaluate the final state to be stored based on the planned new state (see the resource instance change lifecycle for details). In this case, the planned new state is affected by the TF configuration in effect. Specificially, for those list typed properties that is missing in the config, they'll end up with a null in the state (i.e. in your case the private_ip_ranges and dns_servers).

This can be fixed by a follow-up terraform apply -refresh-only. In this case, terraform don't care about the configuration, but only refresh what is recorded in the state to match its current remote state. This will honestly refresh everything regardless the TF configuration. So those list typed properties will become either empty list [] or list with elements, but not a null.

This Objects have changed outside of Terraform issue exists for lots of resources. E.g. an empty resource group with no tags defined will also cause this issue complaining that the tags will be changed from null to {} in a refresh. IMO, this is something need to fix in the hashicorp/terraform-plugin-sdk, rather than the provider side. This might be fixed once hashicorp/terraform-plugin-sdk#261 is addressed, or we've migrated to the terraform-plugin-framework someday.

Reference

andyghc commented 2 years ago

This can be fixed by a follow-up terraform apply -refresh-only. In this case, terraform don't care about the configuration, but only refresh what is recorded in the state to match its current remote state. This will honestly refresh everything regardless the TF configuration.

So is the fix to always run a "terraform apply -refresh-only" after every "terraform apply" is run? And is this normal/safe?

cf my similar issue here https://github.com/hashicorp/terraform-provider-azurerm/issues/15499

dkuzmenok commented 2 years ago

@magodo If there any working solution to this issue with null -> [] changes on apply? We use lots of Idempotent tests, and they fail, of course, due to planned changes. Is there any known way it can be solved with configuration or provider code on the resource level?

magodo commented 2 years ago

@dkuzmenok Unfortunately, the only workaround I know is to do a refresh.