Help to create a condition to configure resources

Hi, everyone!

I’m writing a terraform module to Storage Account at the Azure provider.

I’m need write in the same module, Storage Account , blob Storage, File Share, monitor, alert and group

resource "azurerm_monitor_diagnostic_setting" "example" {
  name               = "example"
  target_resource_id = data.azurerm_key_vault.example.id
  storage_account_id = data.azurerm_storage_account.example.id

  log {
    category = "AuditEvent"
    enabled  = false

    retention_policy {
      enabled = false
    }
  }

  metric {
    category = "AllMetrics"

    retention_policy {
      enabled = false
    }
  }
}
resource "azurerm_monitor_metric_alert" "example" {
  name                = "example-metricalert"
  resource_group_name = azurerm_resource_group.example.name
  scopes              = [azurerm_storage_account.to_monitor.id]
  description         = "Action will be triggered when Transactions count is greater than 50."

  criteria {
    metric_namespace = "Microsoft.Storage/storageAccounts"
    metric_name      = "Transactions"
    aggregation      = "Total"
    operator         = "GreaterThan"
    threshold        = 50

    dimension {
      name     = "ApiName"
      operator = "Include"
      values   = ["*"]
    }
  }

  action {
    action_group_id = azurerm_monitor_action_group.main.id
  }
}

For create a blob storage and file share, I use variable type map

variable "blob_storage" {
  type      = map(any)
  default = {}
}
variable "file_share" {
  type      = map(any)
  default = {}
}

I want to create a condition, where I could enable or disable the resources (group, diagnostic and alert), using variables blob_storage and file_share, an example in non-productive environmet I could disable this resources and in productions environmente enable.

For this I’m tryed below codes, but this dosen’t works, sincerely I’don’t have sure if this idea this is codable, but I would like sugestions if possible.

resource "azurerm_monitor_metric_alert" "example" {
 for_each            = length(var.blob_storage) || length(var.file_share)
  name                = "example-metricalert"
  resource_group_name = azurerm_resource_group.example.name
  scopes              = [azurerm_storage_account.to_monitor.id]
  description         = "Action will be triggered when Transactions count is greater than 50."

  criteria {
    metric_namespace = "Microsoft.Storage/storageAccounts"
    metric_name      = "Transactions"
    aggregation      = "Total"
    operator         = "GreaterThan"
    threshold        = 50

    dimension {
      name     = "ApiName"
      operator = "Include"
      values   = ["*"]
    }
  }

  action {
    action_group_id = azurerm_monitor_action_group.main.id
  }
}

Hi @leandro,

Because your azurerm_monitor_metric_alert resource doesn’t seem to actually make use of either of these variables apart from this conditional declaration, I think the simplest approach would be to use conditional count rather than conditional for_each, like this:

  count = (length(var.blob_storage) > 0 || length(var.file_share) > 0) ? 1 : 0

The for_each argument is more appropriate for situations where you need to create one instance of a resource for each element of a collection, where each instance uses data from one element of that collection. For the example you showed there seems to be only zero or one metric alerts regardless of how many blob storage or file share elements there are (as long as there’s at least one of either) so count is the better fit for this situation.

Hi @apparentlymart

Thank you for help, but I hava another doubt

Would be possible, to combine yout tip and get a configuration parameters as name or resource_group_name, from a map:

resource "azurerm_monitor_metric_alert" "example" {
 count = (length(var.blob_storage) > 0 || length(var.file_share) > 0) ? 1 : 0
  name                = each.value["name"]
  resource_group_name = each.value["resource_group_name"]
  scopes              = [azurerm_storage_account.to_monitor.id]
  description         = "Action will be triggered when Transactions count is greater than 50."
...

Hi @leandro,

It would indeed be possible to write something like that but I can’t show a full example because it isn’t clear to me what ["name"] and ["resource_group_name"] are elements of.

The general idea here would be to write an expression that somehow merges your two variables together in a way that means there would be one element per azurerm_monitor_metric_alert object that you want to declare.

If you can show more about where this name and resource group name would be defined then I may be able to share a real code example.

Hi @apparentlymart

Thanks for the answer.

I’m creating a terraform module for Storage Account, blob Storage, monitoring, alert and alert group.

To create the storage account I am using type a variable map(object).

The code below is woking now.

variables.tf

 variable "storage_accounts" {
  type = map(object({
    storage_account_name = string
    resource_group_name  = string
    location             = string
 

    account_kind                     = string
    account_tier                     = string
    access_tier                      = string
    account_replication_type         = string

    cross_tenant_replication_enabled = bool

    enable_https_traffic_only = bool
	
    min_tls_version           = string

    allow_nested_items_to_be_public = any

    shared_access_key_enabled       = bool

    public_network_access_enabled   = any 

    default_to_oauth_authentication = bool

    is_hns_enabled = bool

    nfsv3_enabled = bool

    large_file_share_enabled = any

    queue_encryption_key_type = string

    table_encryption_key_type = string

    infrastructure_encryption_enabled = bool

    soft_delete_enabled = bool

    network_rules = list(object({
      default_action             = string
      bypass                     = list(string)
      ip_rules                   = list(string)
      virtual_network_subnet_ids = list(string)
      private_link_access        = list(string)
    })) 

    blob_properties = list(object({
      versioning_enabled                = bool
      change_feed_enabled               = bool
      change_feed_retention_in_days     = number
      default_service_version           = string
      last_access_time_enabled          = bool
      cors_rule                         = list(any)
      delete_retention_policy           = list(any)
      container_delete_retention_policy = list(any)
    })) 

    queue_properties = list(object({
      cors_rule = list(any)
      logging = list(any)
      minute_metrics = list(any)
      hour_metrics = list(any)
    }))

    share_properties = list(object({
      cors_rule = list(any)
      retention_policy = list(any)
      smb = list(any)
   })) 

    static_website = list(object({
      index_document     = string
      error_404_document = string
    })) 

    routing = list(any) 

    immutability_policy = list(object({
      allow_protected_append_writes = bool
      state                         = string
      period_since_creation_in_days = number
    })) 

    tags = map(any)
  }))

  default     = {}
  description = "(Required) Map of key/value to configure `Storage Accoounts`."
} 

/* CONTAINER STORAGE */
variable "storage_container" {
  type        = map(any)
  description = "(Required) Map of key/value to configure `Storage Containers`."
  default     = {}
} 

/* BLOB STORAGE */
variable "blob_storage" {
  type        = map(any)
  description = "(Optional) Map of key/value to configure `Blob Storage`."
  default     = {}
}

main.tf:

resource "azurerm_storage_account" "default" {
  for_each            = var.storage_accounts
  name                = each.value["storage_account_name"]
  resource_group_name = each.value["resource_group_name"]
  location            = each.value["location"]
  account_kind                     = lookup(each.value, "account_kind", "StorageV2")
  account_tier                     = each.value["account_tier"]
  access_tier                      = lookup(each.value, "access_tier", "Hot")
  account_replication_type         = each.value["account_replication_type"]
  cross_tenant_replication_enabled = lookup(each.value, "cross_tenant_replication_enabled", false)
  enable_https_traffic_only = lookup(each.value, "enable_https_traffic_only", true)
  min_tls_version           = lookup(each.value, "min_tls_version", "TLS1_2")
  allow_nested_items_to_be_public = lookup(each.value, "allow_nested_items_to_be_public", false)
  public_network_access_enabled   = lookup(each.value, "public_network_access_enabled", false)
  shared_access_key_enabled       = lookup(each.value, "shared_access_key_enabled", true)
  default_to_oauth_authentication = lookup(each.value, "default_to_oauth_authentication", false)
  is_hns_enabled = lookup(each.value, "is_hns_enabled", false)
  nfsv3_enabled = lookup(each.value, "nfsv3_enabled", false)
  large_file_share_enabled = lookup(each.value, "large_file_share_enabled", true)
  queue_encryption_key_type = lookup(each.value, "queue_encryption_key_type", "Service")
  table_encryption_key_type = lookup(each.value, "table_encryption_key_type", "Service")
  infrastructure_encryption_enabled = lookup(each.value, "infrastructure_encryption_enabled", false)

  dynamic "custom_domain" {
    for_each = try(each.value["custom_domain"], [])
    content {
      name          = lookup(custom_domain.value, "name", null)
      use_subdomain = lookup(custom_domain.value, "use_subdomain", null)
    }
  } 

  dynamic "customer_managed_key" {
    for_each = try(each.value["customer_managed_key"], [])
    content {
      key_vault_key_id          = lookup(customer_managed_key.value, "key_vault_key_id", null)
      user_assigned_identity_id = lookup(customer_managed_key.value, "user_assigned_identity_id", null)
    }
  } 

  dynamic "identity" {
    for_each = try(each.value["identity"], [])
    content {
      type         = lookup(identity.value, "key_vault_key_id", null)
      identity_ids = lookup(identity.value, "user_assigned_identity_id", null)
    }
  } 

  dynamic "blob_properties" {
    for_each = each.value["blob_properties"]
    content {
      versioning_enabled            = lookup(blob_properties.value, "versioning_enabled", null)
      change_feed_enabled           = lookup(blob_properties.value, "change_feed_enabled", null)
      change_feed_retention_in_days = lookup(blob_properties.value, "change_feed_retention_in_days", null)
      default_service_version       = lookup(blob_properties.value, "default_service_version", null)
      last_access_time_enabled      = lookup(blob_properties.value, "last_access_time_enabled", null) 

      dynamic "cors_rule" {
        for_each = try(blob_properties.value["cors_rule"], [])
        content {
          allowed_headers    = lookup(cors_rule.value, "allowed_headers", ["x-ms-blob-content-type"])
          allowed_methods    = lookup(cors_rule.value, "allowed_methods", ["GET"])
          allowed_origins    = lookup(cors_rule.value, "allowed_origins", [http://www.contoso.com])
          exposed_headers    = lookup(cors_rule.value, "exposed_headers", ["x-ms-*"])
          max_age_in_seconds = lookup(cors_rule.value, "max_age_in_seconds", "5")
        }
      } 

      dynamic "delete_retention_policy" {
        for_each = try(blob_properties.value["delete_retention_policy"], [])
        content {
          days = lookup(delete_retention_policy.value, "days", "7")
        }
      } 

      dynamic "container_delete_retention_policy" {
        for_each = try(blob_properties.value["container_delete_retention_policy"], [])
        content {
          days = lookup(container_delete_retention_policy.value, "days", "7")
        }
      }
    }
  } 

  dynamic "queue_properties" {
    for_each = each.value["queue_properties"]
    content {
      dynamic "cors_rule" {
        for_each = try(queue_properties.value["cors_rule"], [])
        content {
          allowed_headers    = lookup(cors_rule.value, "allowed_headers", ["x-ms-blob-content-type"])
          allowed_methods    = lookup(cors_rule.value, "allowed_methods", ["GET"])
          allowed_origins    = lookup(cors_rule.value, "allowed_origins", [http://www.contoso.com])
          exposed_headers    = lookup(cors_rule.value, "exposed_headers", ["x-ms-*"])
          max_age_in_seconds = lookup(cors_rule.value, "max_age_in_seconds", "5")
        }
      } 

      dynamic "logging" {
        for_each = try(queue_properties.value["logging"], [])
        content {
          delete                = lookup(logging.value, "delete", false)
          read                  = lookup(logging.value, "read", false)
          version               = lookup(logging.value, "version", "1.0")
          write                 = lookup(logging.value, "write", false)
          retention_policy_days = lookup(logging.value, "retention_policy_days", "1")
        }
      }

 

      dynamic "minute_metrics" {
        for_each = try(queue_properties.value["minute_metrics"], [])
        content {
          enabled               = lookup(minute_metrics.value, "enabled", false)
          version               = lookup(minute_metrics.value, "version", "1.0")
          include_apis          = lookup(minute_metrics.value, "include_apis", false)
          retention_policy_days = lookup(minute_metrics.value, "retention_policy_days", "1")
        }
      } 

      dynamic "hour_metrics" {
        for_each = try(queue_properties.value["hour_metrics"], [])
        content {
          enabled               = lookup(hour_metrics.value, "enabled", false)
          version               = lookup(hour_metrics.value, "version", "1.0")
          include_apis          = lookup(hour_metrics.value, "include_apis", false)
          retention_policy_days = lookup(hour_metrics.value, "retention_policy_days", "1")
        }
      }
    }
  } 

  dynamic "share_properties" {
    for_each = each.value["share_properties"]
    content {
     dynamic "cors_rule" {
        for_each = try(share_properties.value["cors_rule"], [])
        content {
          allowed_headers    = lookup(cors_rule.value, "allowed_headers", ["x-ms-blob-content-type"])
          allowed_methods    = lookup(cors_rule.value, "allowed_methods", ["GET"])
          allowed_origins    = lookup(cors_rule.value, "allowed_origins", [http://www.contoso.com])
          exposed_headers    = lookup(cors_rule.value, "exposed_headers", ["x-ms-*"])
          max_age_in_seconds = lookup(cors_rule.value, "max_age_in_seconds", "5")
        }
      }

 

      dynamic "retention_policy" {
        for_each = try(share_properties.value["retention_policy"], [])
        content {
          days = lookup(retention_policy.value, "days", "7")
        }
      } 

      dynamic "smb" {
        for_each = try(share_properties.value["smb"], {})
        content {
          versions                        = lookup(smb.value, "versions", [])
          authentication_types            = lookup(smb.value, "authentication_types", [])
          kerberos_ticket_encryption_type = lookup(smb.value, "kerberos_ticket_encryption_type", [])
          channel_encryption_type         = lookup(smb.value, "channel_encryption_type", [])
          multichannel_enabled            = lookup(smb.value, "multichannel_enabled", false)
        }
      }
    }
  } 

  dynamic "network_rules" {
    for_each = each.value["network_rules"]
    content {
      default_action             = network_rules.value["default_action"]
      bypass                     = network_rules.value["bypass"]
      ip_rules                   = network_rules.value["ip_rules"]
      virtual_network_subnet_ids = network_rules.value["virtual_network_subnet_ids"] 

      dynamic "private_link_access" {
        for_each = try(each.value.network_rules.private_link_access, {})
        content {
          endpoint_resource_id = lookup(private_link_access.value, "endpoint_resource_id", "")
          endpoint_tenant_id   = lookup(private_link_access.value, "endpoint_tenant_id", "")
        }
      }
    }
  }

  dynamic "azure_files_authentication" {
    for_each = try(each.value["azure_files_authentication"], [])
    content {
      directory_type = lookup(azure_files_authentication.value, "directory_type", "AD")
      dynamic "active_directory" {
        for_each = try(azure_files_authentication.value["active_directory"], {})
        content {
          storage_sid         = lookup(active_directory.value, "storage_sid", null)
          domain_name         = lookup(active_directory.value, "domain_name", null
          domain_sid          = lookup(active_directory.value, "domain_sid", null)
          domain_guid         = lookup(active_directory.value, "domain_guid", null)
          forest_name         = lookup(active_directory.value, "forest_name", null)
          netbios_domain_name = lookup(active_directory.value, "netbios_domain_name", null)
        }
      }
    }
  } 

  dynamic "static_website" {
    for_each = try(each.value["static_website"], [])
    content {
      index_document     = lookup(static_website.value, "index_document", "index.html")
      error_404_document = lookup(static_website.value, "error_404_document", "erro_404.html")
    }
  } 

  dynamic "routing" {
    for_each = try(each.value["routing"], [])
    content {
      publish_internet_endpoints  = lookup(routing.value, "publish_internet_endpoints", false)
      publish_microsoft_endpoints = lookup(routing.value, "publish_microsoft_endpoints", false)
      choice                      = lookup(routing.value, "choice", "MicrosoftRouting")
    }
  } 

  dynamic "immutability_policy" {
    for_each = try(each.value["immutability_policy"], [])
    content {
      allow_protected_append_writes = lookup(immutability_policy.value, "allow_protected_append_writes", false)
      state                         = lookup(immutability_policy.value, "state", "Disabled")
      period_since_creation_in_days = lookup(immutability_policy.value, "period_since_creation_in_days", "0")
    }
  }

  tags = each.value["tags"] 

  lifecycle {
    create_before_destroy = true
  }
}

monitoring.tf

resource "azurerm_monitor_action_group" "default" {
  for_each           =  try(var.blob_storage, {})
  name                = ${each.value["blob_name"]-group}
  resource_group_name = each.value["resource_group_name"]
  short_name          = lookup(each.value, "short_name", "shortgrpexpl")

  tags = each.value["tags"]

  depends_on = [
    azurerm_storage_account.default,
    azurerm_storage_blob.default
  ]
}

 resource "azurerm_monitor_diagnostic_setting" "default" {
  for_each           = try(var.blob_storage, {})
  name               = "${lookup(each.value, "storage_account_name")}-diags"
  target_resource_id = "${element(values(azurerm_storage_account.default)[*].id, length(var.storage_accounts))}/blobServices/default"
  storage_account_id = element(values(azurerm_storage_account.default)[*].id, length(var.storage_accounts))
 
  dynamic "metric" {
    for_each = try(each.value["metric"], [])
    content {
      category = lookup(metric.value, "category", "Transaction")
      enabled  = lookup(metric.value, "enabled", true) 

      dynamic "retention_policy" {
        for_each = metric.value["retention_policy"]
        content {
          enabled = lookup(retention_policy.value, "enabled", true)
          days    = lookup(retention_policy.value, "days", "5")
        }
      }
    }
  } 

  dynamic "log" {
    for_each = try(each.value["log"], [])
    content {
      category       = lookup(log.value, "category", "StorageRead")
      category_group = lookup(log.value, "category_group", "null")
      enabled        = lookup(log.value, "enabled", true) 

      dynamic "retention_policy" {
        for_each = log.value["retention_policy"]
        content {
          enabled = lookup(retention_policy.value, "enabled", true)
          days    = lookup(retention_policy.value, "days", "5")
        }
      }
    }
  }

  depends_on = [
    azurerm_storage_blob.default
  ]
}

resource "azurerm_monitor_metric_alert" "default" {
  for_each            = try(var.blob_storage, {})
  name                = each.value["storage_account_name"]
  resource_group_name = each.value["resource_group_name"]
  scopes              = [(element(values(azurerm_storage_account.default)[*].id, length(var.storage_accounts)))]
  description         = "Action will be triggered when Transactions count is greater than 50."
 
  dynamic "criteria" {
    for_each = each.value["criteria"]
    content {
      metric_namespace = lookup(criteria.value, "metric_namespace", "Microsoft.Storage/storageAccounts")
      metric_name      = lookup(criteria.value, "metric_name", "Transactions")
      aggregation      = lookup(criteria.value, "aggregation", "Total")
      operator         = lookup(criteria.value, "operator", "GreaterThan")
      threshold        = lookup(criteria.value, "threshold", "50")

       dynamic "dimension" {
        for_each = criteria.value["dimension"]
        content {
          name     = lookup(dimension.value, "name", "ApiName")
          operator = lookup(dimension.value, "operator", "Include")
          values   = lookup(dimension.value, "values", ["*"])
        }
      }
    }
  } 

  action {
    action_group_id = (element(values(azurerm_monitor_action_group.default)[*].id, length(var.storage_accounts)))
  }

  tags = each.value["tags"] 

  depends_on = [
    azurerm_storage_account.default,
    azurerm_storage_blob.default,
    azurerm_monitor_action_group.default,
    azurerm_data_protection_backup_policy_blob_storage.default
  ]
}

container.tf

 resource "azurerm_storage_container" "example" {
  for_each              = var.storage_container
  name                  = each.value["name"]
  storage_account_name  = element(values(azurerm_storage_account.default)[*].name, length(var.storage_accounts))
  container_access_type = lookup(each.value, "container_access_type", "private")
  
  metadata = lookup(each.value, "container_access_type", {})      
}

For to configure monitoring I used variable blob_storage, map(object), where I provided parameters as, name, resource_group and others need to configure resource.

By default all features are implemented when run.

My idea for code improvement is to configurer in the monitoring resources, a trigger to disable services but to continue using all parameters sent to the blob_storage variable.

Is This possible?

In case it not possible, could you suggest another way.

I hope I was clear in my idea, but if I wasn’t please let me know and I will try explain again.

Hi @leandro,

I’m afraid I’m not sure I do understand what your goal is but one part I did understand is that you want to add an additional condition to azurerm_monitor_metric_alert.example while still keeping the for_each.

One way to achieve that is to use a for expression with an if clause in the for_each argument. For example:

  for_each = tomap({
    for k, s in var.blob_storage : k => s
    if var.condition
  })

The optional if clause in a for expression will filter from the source collection any element where the condition evaluates to false. If you write an expression that produces false for all elements then the result will be an empty map and so you will be declaring zero instances of this resource.

I simplified the above to focus on the for expression in particular but if you do need to preserve the try function in your example then you can write that as for k, s in try(var.blob_storage, {}) with the same effect.

Hopefully that gives you some ideas on how to solve the problem!

@apparentlymart

Sorry by delay in the response, but I tested your sugestion and work well to my code.

Thank very much.