hashicorp / terraform

Terraform enables you to safely and predictably create, change, and improve infrastructure. It is a source-available tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned.
https://www.terraform.io/
Other
42.75k stars 9.56k forks source link

Terraform does not see a specific output attribute from remote state on destroy. #35930

Closed t3ranservice closed 1 week ago

t3ranservice commented 1 week ago

Terraform Version

Terraform v1.8.5
on darwin_arm64
+ provider registry.terraform.io/hashicorp/azurerm v3.107.0

Terraform Configuration Files

**In main.tf:**
module "app-service-windows" {
  source = "../../modules/app-service"

  resource_group_name       = resource.azurerm_resource_group.connect-application.name
  location                  = local.location
  environment               = var.environment
  naming_prefix             = "${local.location_abbreviations[local.location]}-${var.environment}"
  stack_name                = local.stack_name
  app_settings              = var.app_settings
  site_config               = var.site_config
  default_site_config       = local.default_site_config
  virtual_network_subnet_id = module.connect-vnet.app_services_subnet_id
  connection_strings        = local.connection_strings
  app_service_logs_settings = var.app_service_logs_settings
  tags                      = local.tags
  os_type                   = var.os_type
  sku_name                  = var.sku_name
  app_names                 = local.app_names
  team_name                 = local.team_name
  pe_subnet_id              = module.connect-vnet.private_endpoints_subnet_id
  key_vault_id              = data.terraform_remote_state.data.outputs.data_keyvault.resource_id
  tenant_id                 = data.terraform_remote_state.data.outputs.data_keyvault.tenant_id
  workspace_id              = data.terraform_remote_state.data.outputs.connect_log_analytics_workspace.id

  private_dns_zone_ids = [
    module.connect-vnet.azurewebsites_privatelink_dns_zone_id,
    data.terraform_remote_state.techops.outputs.techops_azurewebsites_privatelink_dns_zone_id
  ] #what's the proper pDNS zone id?

  providers = {
    azurerm.techops = azurerm.techops
  }
}

In data stack:

resource "azurerm_log_analytics_workspace" "connect-la" {
  location            = local.location
  name                = "${local.location_abbreviations[local.location]}-${var.environment}-${local.team_name}-connect-la-001"
  resource_group_name = azurerm_resource_group.data-rg.name
}

output "connect_log_analytics_workspace" {
  value = {
    id = azurerm_log_analytics_workspace.connect-la.id
  }
}

Debug Output

╷ │ Error: Unsupported attribute │ │ on main.tf line 231, in module "app-service-windows": │ 231: workspace_id = data.terraform_remote_state.data.outputs.connect_log_analytics_workspace.id │ ├──────────────── │ │ data.terraform_remote_state.data.outputs is object with 6 attributes │ │ This object does not have an attribute named "connect_log_analytics_workspace".

The output from 'data':

data "terraform_remote_state" "data" { backend = "azurerm" config = { container_name = "data" key = "modified" resource_group_name = "modified" storage_account_name = "modified" subscription_id = "modified" } outputs = { connect_log_analytics_workspace = { id = "modified" } connect_servicebus = { connection_string_reference = "modified" resource_id = "modified" } connect_sql_server = { connection_string_reference = "modified" resource_id = "modified" } .... other

Expected Behavior

The resource previously provisioned with terraform apply with help of reference to outputs of remote 'data' state should get destroyed as well.

Actual Behavior

On destroy, Terraform does not see the attribute in outputs of terraform_remote_state.data while it's clearly there (confirmed with terraform output in data stack and provided above). I've checked the state file itself and I can clearly see an attribute there

Steps to Reproduce

terraform init -backend-config="key=modified.tfstate" terraform apply -var environment=preprod terraform desotry

Additional Context

No response

References

No response

t3ranservice commented 1 week ago

Just noticed that everything works if I pass the same environment variable to destroy as to apply (-var environment=preprod). It does not make sense to me since it looks into state files?

liamcervante commented 1 week ago

Hi @t3ranservice, thanks for filing this. The destroy operation performs an internal refresh first, to ensure that the plan output contains the most up-to-date information about exactly what will be destroyed. I think this explains the behaviour you are seeing.

I wonder if running terraform destroy -refresh=false might work for you without specifying the variable? I believe this is working as intended so I will close this ticket. Thanks again!

t3ranservice commented 1 week ago

@liamcervante Hello Liam, thank you for quick response. I don't believe this is normal behavior. Why would it fail to retrieve an attribute from outputs of different Terraform configuration during refresh while the attribute is there and can be seen while viewing the .tfstate file manually?

t3ranservice commented 1 week ago

It fails specifically during refresh as part of destroy, not during destroy itself

jbardin commented 1 week ago

The values stored in the state for data sources are never actually used by Terraform, they are there as a legacy artifact for compatibility and debugging. Data sources must always be read again during execution in order to be used (-refresh=false does not affect data sources, that only applies to managed resources). If the remote state data source is reading new data during the destroy operation without the connect_log_analytics_workspace attribute, that is what will be evaluated at that time.

liamcervante commented 1 week ago

Just a quick follow up, specifically the clarification that terraform destroy -var environment=preprod does work while terraform destroy on its own does not suggests that the specific value for that variable affects which data is being loaded during the operation and would explain why the outputs from the remote_data object are different.

t3ranservice commented 1 week ago

Sorry for my ignorance, but I am not following your ideas. My remote state does have the attribute. If you are saying data sources are always read and latest information is pulled from there, then my reference data.terraform_remote_state.data.outputs.connect_log_analytics_workspace.resource_id should get resolved and I should not see an error, shouldn't it?

t3ranservice commented 1 week ago

So my data source is pulled like that:

data "terraform_remote_state" "data" { backend = "azurerm"

config = { subscription_id = "modified" resource_group_name = "modiifed" storage_account_name = "modified" container_name = "modified" key = "${var.environment}.tfstate" } }

The environment defaults to "dev" and I am currently working with "preprod" environment. Maybe while it does not actually use that value during refresh or destroy, it still evaluates it and fails because the dev.tfstate does not indeed have it, only preprod.tfstate has. Think that's the issue?

t3ranservice commented 1 week ago

I think that's what the Liam said in his follow-up. Isn't it a bit confusing though? It should perform the destroy successfully based on the state file only, but it fails and I now must pass a variable to destroy so it can pass the evaluation of that data.terraform_remote_state variable. Or my environments just have to be identical

t3ranservice commented 1 week ago

Thanks for your help, the issue turned out to be pretty obvious

liamcervante commented 1 week ago

I think what might be missing from the picture you have is that variable values are not stored in state files. They need to be specified for every operation, including destroy and refresh.

So in the failing case, that data source is being loaded with key = dev.tfstate which doesn't contain the required outputs. Then anything that references that data source tries to read the outputs and finds the output they expected doesn't exist, and we see the error.

In the passing case, the data source is loaded with key = preprod.tfstate and contains all the necessary outputs, and we see things working.

t3ranservice commented 1 week ago

Yep, that's all making sense now, thank you