Conditional variable question regarding module outputs

Hello,

I am attempting to use a module output as a variable value my main.tf to be passed to a module. The module is set with a default value of null, so that if it doesn’t get said value, it moves along. However the output might not be available. How can I get TF to accept a null/missing output value in the module variable definition?

So essentially, if module_A is run, it is to output a result to be ingested by module_B with a variable definition of var_name = module.module_A.output. If module_A has not been run, that output will not be available.

I would like module_B to set var_name to null in the absence of the output.

Is this possible? If so, what would it look like?

Thanks,
Chris

The ternary operator or try() work.

module "B" {
  var_name = condition_controlling_A_use ? module.module_A.output : null
  var_name = try(module.module_A.output , null)
}

Thanks for the response Jeremy, but it looks like that doesn’t work.

When I set the condition_controlling_A_use to false, it still runs module A and then passes a null. When set to true, it runs module A and passes the output.

Try does the same thing.

My idea to let it run Module A, even when I don’t want it to run, and then destroy Module A results in TF wanting to destroy the Module B resources.

My actual use case is Dedicated Hosts or not. With the controlling variable set to false, it builds the DHs and then doesn’t put the instance on the DH. With it set to true, it creates the DHs and then puts the instance on the DH. Setting it to false and building the instances stand alone (Not on the DH) and then destroying the DHG/DHs wants to delete the stand alone instances…

I’m afraid you’ve lost me.

It would help a lot to have a code sample in addition to the natural language explanation.

module "region_a_dedicated_host_group_01" {
  source = "./modules/dedicated_host_group/"

  azure_client_id = var.azure_client_id
  azure_client_secret = var.azure_client_secret
  azure_dhg_fault_domain_count = var.azure_dhg_fault_domain_count_region_a
  azure_dhg_name = var.azure_dhg01_name_region_a
  azure_environment = var.azure_environment
  azure_location = var.azure_location_region_a
  azure_rg_name = var.azure_rg_name_region_a
  azure_subscription_id = var.azure_subscription_id
  azure_tenant_id = var.azure_tenant_id
  azure_availability_zone = "1"
}
module "region_a_cns_nva_dedicated_host_01" {
  source = "./modules/dedicated_host/"
  azure_client_id = var.azure_client_id
  azure_client_secret = var.azure_client_secret
  azure_cns_dedicated_host_group_id = module.region_a_dedicated_host_group_01.dedicated_host_group_id
  azure_cns_dedicated_host_name = join ("", list(var.azure_cns_nva_dedicated_hostname, "-01", ".", var.azure_pod_a, ".service-now.com"))
  azure_cns_dedicated_host_sku = var.azure_cns_nva_dedicated_host_sku
  azure_environment = var.azure_environment
  azure_fault_domain = "0"
  azure_location = var.azure_location_region_a
  azure_subscription_id = var.azure_subscription_id
  azure_tenant_id = var.azure_tenant_id
}
module "cisco_asa_nva_region_a" {
  source = "./modules/azure_network_asa_nva"
  azure_asa1_hostname = var.asa1_hostname
  azure_asa1_management_offset = var.asa1_management_offset
  azure_asa1_storage_account_name = var.asa1_storage_account_name
  azure_asa2_hostname = var.asa2_hostname
  azure_asa2_management_offset = var.asa2_management_offset
  azure_asa2_storage_account_name = var.asa2_storage_account_name
  azure_asa_hostname = var.asa_hostname
  azure_asa_inside_subnet_id = module.infrastructure.subnet_id_03
  azure_asa_inside_subnet_prefix = module.infrastructure.subnet_address_space_03_0
  azure_asa_management_subnet_id = module.infrastructure.subnet_id_01
  azure_asa_management_subnet_prefix = module.infrastructure.subnet_address_space_01_0
  azure_asa_offer = var.asa_offer
  azure_asa_outside_subnet_id = module.infrastructure.subnet_id_02
  azure_asa_outside_subnet_prefix = module.infrastructure.subnet_address_space_02_0
  azure_asa_publisher = var.asa_publisher
  azure_asa_sku = var.asa_sku
  azure_asa_vm_size = var.asa_vm_size
  azure_client_id = var.azure_client_id
  azure_client_secret = var.azure_client_secret
  azure_environment = var.azure_environment
  azure_infra_pod = var.infra_pod_a
  azure_location = module.infrastructure.resource_group_location
#  azure_ptr_zone = var.azure_ptr_zone_region_a
  azure_rg = module.infrastructure.resource_group_name
  azure_subscription_id = var.azure_subscription_id
  azure_tenant_id = var.azure_tenant_id
  azure_uhoh_secret = var.azure_uhoh_secret
#  dns_key_name = var.dns_key_name
#  dns_key_secret = var.dns_key_secret
#  dns_server_address = var.dns_server_address
#  asa1_zone = try(module.region_a_dedicated_host_group_01.dedicated_host_group_zone[0] , null)
#  asa2_zone = try(module.region_a_dedicated_host_group_02.dedicated_host_group_zone[0] , null)
  asa1_zone = var.use_dh ? module.region_a_dedicated_host_group_01.dedicated_host_group_zone : null
  asa2_zone = var.use_dh ? module.region_a_dedicated_host_group_02.dedicated_host_group_zone : null
  dedicated_host_id_01 = var.use_dh ? module.region_a_cns_nva_dedicated_host_01.dedicated_host_id : null
  dedicated_host_id_02 = var.use_dh ? module.region_a_cns_nva_dedicated_host_02.dedicated_host_id : null
}

I have an infrastructure module that builds the RG/vNets/Subnets/NSGs, etc.

So I run a terraform apply -target=module.infrastructure to build that…

If I then run the Cisco module with terraform apply -target=module.cisco_asa_nva_region_a

Since the cisco_asa_nva_region_a module uses a module output to define a variable, it runs the DHG and DH modules.

It runs those modules with var.use_dh set to true or false. When set to true, the ASAs are built on the DHs. When set to false, the DHs are built and the ASAs are built as stand alone instances.

Sorry for the large font. Those are commented out in the code… I guess a pound sign means bold here…

I think I understand the intent now.
To rephrase, given:

  • Module A
  • Module B with an optional dependency on A, via passing a variable from A

The goal is to only instantiate A if B will use it.

In that case, no, try() and ?: will not help.

Prior to terraform 0.13, the techniques that occur to me are

  • generate the configuration, possibly copying from an external source
  • conditionally point module A’s source to a dummy module that accepts the same inputs but does nothing. This seems to require rerunning terraform init each time a module’s source location changes.
module "A" {
  source = var.use_A ? "./module_A" : "./module_A_dummy"
}

With terraform 0.13, setting count to 0 will work. Using count (or for_each) likely means making changes to deal with the instance index and relocating nested providers.

Hey Jeremy,

I think I’m getting it. And in module_a_dummy, I would need to get the output to be null?

output “dedicated_host_id” {
value = null
}

Thanks,
Chris

That could work. Having the dummy supply a null output would eliminate the need for try() when used with the other module.