Search code examples
terraformazureservicebusterraform-provider-azure

terraform code reporting error when using a dependent variable


terraform-azurerm-servciebusnamespace code:

main.tf

resource "azurerm_servicebus_namespace" "servicebus_namespace" {
  name                          = regex("^[-\\w\\._\\(\\)]+$", substr("sbns-${var.environment_name}-${var.application_name}-${var.location}", 0, 50))
  resource_group_name           = var.resource_group_name
  location                      = var.location
  sku                           = var.sku
  capacity                      = var.capacity
  tags                          = local.tags
  public_network_access_enabled = false
  minimum_tls_version           = "1.2"
  local_auth_enabled            = false
  identity                      = var.identity
  customer_managed_key {
    key_vault_key_id = var.key_vault_key_id
    identity_id      = var.identity_id
  }
}

outputs.tf

output "id" {
  value = azurerm_servicebus_namespace.servicebus_namespace.id
}
output "endpoint" {
  value = azurerm_servicebus_namespace.servicebus_namespace.endpoint
}

variables.tf

variable "location" {
  type        = string
  description = "Location of the resource group"
}

variable "environment_name" {
  type        = string
  description = <<EOT
    (Optional)  The name of the environment where the resources will be deployed.

    Options:
      - dev
      - uat
      - test
      - prod

    Default: dev
  EOT

  default = "dev"

  validation {
    condition     = can(regex("(dev|uat|test|prod)", var.environment_name))
    error_message = "Err: environment name is not valid."
  }
}
variable "department" {
  type        = string
  description = "Name of the department"
}
variable "cost_centre" {
  type        = string
  description = "Cost Centre for the Resource or Resource Group"

}
variable "application_name" {
  type        = string
  description = "Name of the application"

}

variable "resource_group_name" {
  type        = string
  description = "Name of the Resource Group where service plan is to be deployed"

}

variable "sku" {
  type        = string
  description = "(Required) Defines which tier to use. Options are Basic, Standard or Premium. Please note that setting this field to Premium will force the creation of a new resource."

  validation {
    condition     = can(index(["Basic", "Standard", "Premium"], var.sku))
    error_message = "Invalid value for my_variable. Allowed values are value1, value2, or value3."
  }
}
variable "capacity" {
  type        = number
  description = "(Optional) Specifies the capacity. When sku is Premium, capacity can be 1, 2, 4, 8, or 16. When sku is Basic or Standard, capacity can be 0 only."

  default = 0 # Set a default value, assuming Basic or Standard sku

  validation {
    condition     = var.sku == "Premium" ? can(index([1, 2, 4, 8, 16], var.capacity)) : var.capacity == 0
    error_message = "Invalid value for capacity. When sku is Premium, capacity can be 1, 2, 4, 8, or 16. When sku is Basic or Standard, capacity can be 0 only."
  }
}
variable "zone_redundant" {
  type        = bool
  description = "(Optional) Whether or not this resource is zone redundant. sku needs to be Premium. Changing this forces a new resource to be created."

  default = false # Set a default value

  validation {
    condition     = var.sku == "Premium" ? !var.zone_redundant : var.zone_redundant
    error_message = "Invalid value for zone_redundant. When sku is Premium, zone_redundant should be true. When sku is Basic or Standard, zone_redundant should be false."
  }
}
variable "identity" {
  type    = any
  default = null
}

locals.tf

locals {
  tags = {
    "Environment" = var.environment_name
    "Department"  = var.department
    "Managed By"  = "Terraform Open Source"
    "Cost Centre" = var.cost_centre
  }
}

I am now calling the code in a module as below, unfortunately, it reports errors below

module "servicebus_namespace" {
  source              = "./.."
  location            = "australiaeast"
  environment_name    = "dev"
  department          = "Data Services"
  cost_centre         = "ABC101"
  application_name    = "demo"
  resource_group_name = "rg-dev-demo-australiaeast"

}

Error:

terraform init

Initializing the backend...
Initializing modules...
There are some problems with the configuration, described below.

The Terraform configuration must be valid before initialization so that
Terraform can determine which modules and providers need to be installed.
╷
│ Error: Invalid reference in variable validation
│ 
│   on ../variables.tf line 64, in variable "capacity":
│   64:     condition     = var.sku == "Premium" ? can(index([1, 2, 4, 8, 16], var.capacity)) : var.capacity == 0
│ 
│ The condition for variable "capacity" can only refer to the variable itself, using var.capacity.
╵

╷
│ Error: Invalid reference in variable validation
│ 
│   on ../variables.tf line 75, in variable "zone_redundant":
│   75:     condition     = var.sku == "Premium" ? !var.zone_redundant : var.zone_redundant
│ 
│ The condition for variable "zone_redundant" can only refer to the variable itself, using var.zone_redundant.

Unable to understand how to resolve this error as I want capacity dependent on what user chooses SKU. Same situation for zone_redundant thing.


Solution

  • The following way of validating one variable inside other is not possible AFAIk:

    variables.tf:

    variable "capacity" {
      type        = number
      description = "(Optional) Specifies the capacity. When sku is Premium, capacity can be 1, 2, 4, 8, or 16. When sku is Basic or Standard, capacity can be 0 only."
    
      default = 0 # Set a default value, assuming Basic or Standard SKU
    
      validation {
        condition     = var.sku == "Premium" ? contains([1, 2, 4, 8, 16], var.capacity) : var.capacity == 0
        error_message = "Invalid .... capacity can be 1, 2, 4, 8, or 16. When SKU is Basic or Standard, capacity can be 0 only."
      }
    }
    

    enter image description here

    Modify variable validation with different value to validate only self value.

    Variables.tf

    variable "sku" {
      type        = string
      description = "(Required) Defines which tier to use. Options are Basic, Standard or Premium. Please note that setting this field to Premium will force the creation of a new resource."
    
      validation {
        condition     = can(index(["Basic", "Standard", "Premium"], var.sku))
        error_message = "Invalid value for my_variable. Allowed values are value1, value2, or value3."
      }
    }
    variable "capacity" {
      type        = number
      description = "(Optional) Specifies the capacity. When sku is Premium, capacity can be 1, 2, 4, 8, or 16. When sku is Basic or Standard, capacity can be 0 only."
        
      validation {
        condition     = can(index([1, 2, 4, 8, 16], var.capacity))
        error_message = "Invalid value for capacity. When sku is Premium, capacity can be 1, 2, 4, 8, or 16. When sku is Basic or Standard, capacity can be 0 only."
      }
    }
    

    Check the following way which worked for me to validate capacity based on sku Using validate_capacity in locals

    Main.tf:

    provider "azurerm" {
    
      features {
        resource_group {
          prevent_deletion_if_contains_resources = false
        }
      }
    }
    
    
    locals {
      validate_capacity = var.sku == "Premium" ? can(index([1, 2, 4, 8, 16], var.capacity)) : var.capacity == 0
    }
    
    Main.tf:
    
    resource "azurerm_servicebus_namespace" "servicebus_namespace" {
      name                          = "mxxxxxbnme"
      resource_group_name           = data.azurerm_resource_group.example.name
      location                      = data.azurerm_resource_group.example.location
      sku                           = var.sku
      capacity                      = var.capacity
      public_network_access_enabled = false
      minimum_tls_version           = "1.2"
      local_auth_enabled            = false
      identity                      = var.identity
       customer_managed_key {
        ...
       }
    }
    
    
        output "id" {
          value = azurerm_servicebus_namespace.servicebus_namespace.id
        }
        output "endpoint" {
          value = azurerm_servicebus_namespace.servicebus_namespace.endpoint
        }
    

    enter image description here

    with terraform apply

    *executed code:*
    
    **`terraform apply`**
    
    *Acquiring state lock. This may take a few moments...*
    
        **var.application_name**
          Name of the application
        
          Enter a value: mynewappn
        
        **var.capacity**
          (Optional) Specifies the capacity. When sku is Premium, capacity can be 1, 2, 4, 8, or 16. When sku is Basic or Standard, capacity can be 0 only.
        
          **Enter a value**: 1
        
        **var.cost_centre**
          Cost Centre for the Resource or Resource Group
        
          Enter a value: premium
        
        ****var.department**
        
          Name of the department**
        
          **Enter a value:** depmuh
        
        var.sku
          (Required) Defines which tier to use. Options are Basic, Standard or Premium. Please note that setting this field to Premium will force the creation of a new resource.
        
          Enter a value: Premium
    
    Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
      + create
    
        **Terraform will perform the following actions:**
    
             azurerm_servicebus_namespace.servicebus_namespace will be created
              resource "azurerm_servicebus_namespace" "servicebus_namespace" {
              + capacity                            = 1
              + default_primary_connection_string   = (sensitive value)
              + default_primary_key                 = (sensitive value)
              + default_secondary_connection_string = (sensitive value)
              + default_secondary_key               = (sensitive value)
              + endpoint                            = (known after apply)
              + id                                  = (known after apply)
              + local_auth_enabled                  = false
              + location                            = "westus2"
              + minimum_tls_version                 = "1.2"
              + name                                = "mysvbnme"
              + public_network_access_enabled       = false
              + resource_group_name                 = "vttre"
              + sku          
        
                           = "Premium"
            }
    
    **Plan: 1 to add, 0 to change, 0 to destroy.**
    
    **Changes to Outputs:**
      + endpoint = (known after apply)
      + id       = (known after apply)
    
    **Do you want to perform these actions?
      Terraform will perform the actions described above.
      Only 'yes' will be accepted to approve.**
    
      **Enter a value:** yes
    
    **azurerm_servicebus_namespace.servicebus_namespace: Creating....**
    

    Successfully, validated the sku and capacity value which is given as 1 is accepted:

    enter image description here

    enter image description here

    if unsupported values are given for capacity in regards with sku, it triggers error as below:

    enter image description here