What is difference between locals and variables when executing terraform code?

Hello,

I was having trouble when I had a setup like this
main.tf

...
  name                = "${var.prefix}${var.project_name}${var.environment}"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name

  account_tier = "Standard"
  replication  = "LRS"
  access_tier  = "Hot"

  cors_rules = local.api_cors_allowed != [] ? [
    {
      allowed_origins    = local.api_cors_allowed
      allowed_methods    = local.storage_cors_allowed_methods
      allowed_headers    = ["*"]
      exposed_headers    = ["*"]
      max_age_in_seconds = 10
    }
  ] : null
...

locals.tf

api_cors_allowed             = var.environment == "dev" ? ["https://localhost:4200", "http://localhost:4200"] : []
storage_cors_allowed_methods = ["GET", "DELETE", "POST", "PUT", "OPTIONS"]

I was getting this error

Error: updating Azure Storage Account `blob_properties` "noname": storage.BlobServicesClient#SetServiceProperties: Failure responding to request: StatusCode=400 -- Original Error: autorest/azure: Service returned an error. Status=400 Code="ContainerOperationFailure" Message="The value for one of the XML nodes is not in the correct format.\nRequestId:fe79df2d-101e-0043-6b34-501a20000000\nTime:2023-03-06T14:01:33.0828273Z"
│ 
│   with module.main_storage.azurerm_storage_account.main,
│   on .terraform/modules/main_storage/modules/storage_account/main.tf line 1, in resource "azurerm_storage_account" "main":
│    1: resource "azurerm_storage_account" "main" {

When I turned my code into this stile it works
locals.tf

api_cors = var.environment == "dev" ? [
    {
      allowed_origins    = ["https://localhost:4200", "http://localhost:4200"]
      allowed_methods    = ["GET", "DELETE", "POST", "PUT", "OPTIONS"]
      allowed_headers    = ["*"]
      exposed_headers    = ["*"]
      max_age_in_seconds = 10
    }
  ] : []

main. tf

cors_rules = local.api_cors

Also, when I did it like this it worked

main.tf

cors_rules = var.cors_on ? [
    {
      allowed_origins    = local.api_cors_allowed
      allowed_methods    = local.storage_cors_allowed_methods
      allowed_headers    = ["*"]
      exposed_headers    = ["*"]
      max_age_in_seconds = 10
    }
  ] : []

variables.tf

variable "cors_on" {
  type    = bool
  default = false
}

Why do these two ways work, and the first way is giving an error?

Hi @dewa55,

You didn’t include the error message so I’m not certain what was wrong in the first place, but you do have a comparison which is incorrect which may be part of the problem.

The expression local.api_cors_allowed != [] is almost always going to be true, because api_cors_allowed is not an empty tuple type. If you want to check for an non-empty list, use length(local.api_cors_allowed) > 0.

Hi jbardin,

Thank you for pointing it out. I have now included the error.

Can you please explain me a bit this part " because api_cors_allowed is not an empty tuple type "

Why will that not be an empty tuple type?

I think I am missing some knowledge and probably that is the issue.

oh, in this case that comparison probably did work, because you used a literal [] value. That literal produces a value of an empty tuple type, which is only equal to that specific literal value. In most cases users try to compare that to an empty list or set, which never matches because the type is compared first which does not match.

For example:

> [] == []
true
> [] == tolist([])
false

As for the error, the provider is returning something about an invalid XML value format, so you would need to inspect the exact value sent to know for sure. I suspect that the provider’s XML encoding of null vs [] is the key difference there.

Thank you for this explanation. I didn’t know this.