Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Terraform - How to use conditionally created resource's output in conditional operator?

I have a case where I have to create an aws_vpc resource if the user does not provide vpc id. After that I am supposed to create resources with that VPC.

Now, I am applying conditionals while creating an aws_vpc resource. For example, only create VPC if existing_vpc is false:

count                = "${var.existing_vpc ? 0 : 1}"

Next, for example, I have to create nodes in the VPC. If the existing_vpc is true, use the var.vpc_id, else use the computed VPC ID from aws_vpc resource.

But, the issue is, if existing_vpc is true, aws_vpc will not create a new resource and the ternary condition is anyways trying to check if the aws_vpc resource is being created or not. If it doesn't get created, terraform errors out.

An example of the error when using conditional operator on aws_subnet:

Resource 'aws_subnet.xyz-subnet' not found for variable 'aws_subnet.xyz-subnet.id'

The code resulting in the error is:

subnet_id = "${var.existing_vpc ? var.subnet_id : aws_subnet.xyz-subnet.id}"

If both things are dependent on each other, how can we create conditional resources and assign values to other configuration based on them?

like image 720
Shantanu Deshpande Avatar asked May 22 '19 12:05

Shantanu Deshpande


People also ask

How do you do an if statement in Terraform?

Terraform doesn't support if-statements, so this code won't work. However, you can accomplish the same thing by using the count parameter and taking advantage of two properties: If you set count to 1 on a resource, you get one copy of that resource; if you set count to 0, that resource is not created at all.

How do you use output variables in Terraform?

There are several uses for output variables. Here are some examples: We can use outputs to expose a subset of a child module's resource attributes to a parent module. We can use outputs to print values from a root module in the CLI output after running terraform apply .

How do you conditionally create a resource Terraform?

You can use count block with Conditional Expressions (condition ? true_val : false_val) to create a resource based on variable value. description = "default condition value is true."

How do you write a loop in Terraform?

Using the count meta-argument The count meta-argument is the simplest of the looping constructs within Terraform. By either directly assigning a whole number or using the length function on a list or map variable, Terraform creates this number of resources based on the resource block it is assigned to.


Video Answer


3 Answers

You can access dynamically created modules and resources as follows

output "vpc_id" {
  value = length(module.vpc) > 0 ? module.vpc[*].id : null
}
  • If count = 0, output is null
  • If count > 0, output is list of vpc ids

If count = 1 and you want to receive a single vpc id you can specify:

output "vpc_id" {
  value = length(module.vpc) > 0 ? one(module.vpc).id : null
}
like image 163
Ondřej Trojan Avatar answered Nov 08 '22 17:11

Ondřej Trojan


The following example shows how to optionally specify whether a resource is created (using the conditional operator), and shows how to handle returning output when a resource is not created. This happens to be done using a module, and uses an object variable's element as a flag to indicate whether the resource should be created or not.

But to specifically answer your question, you can use the conditional operator as follows:


    output "module_id" {
       value = var.module_config.skip == true ? null : format("%v",null_resource.null.*.id)
    }

And access the output in the calling main.tf:

module "use_conditionals" {
    source = "../../scratch/conditionals-modules/m2" # << Change to your directory
    a = module.skipped_module.module_id # Doesn't exist, so might need to handle that.
    b = module.notskipped_module.module_id
    c = module.default_module.module_id
}

Full example follows. NOTE: this is using terraform v0.14.2


# root/main.tf
provider "null" {}

module "skipped_module" {
    source = "../../scratch/conditionals-modules/m1" # << Change to your directory
    module_config = { 
        skip = true         # explicitly skip this module.
        name = "skipped" 
    }
}

module "notskipped_module" {
    source = "../../scratch/conditionals-modules/m1" # << Change to your directory
    module_config = { 
        skip = false        # explicitly don't skip this module.
        name = "notskipped"
    }
}

module "default_module" {
    source = "../../scratch/conditionals-modules/m1" # << Change to your directory
    # The default position is, don't skip. see m1/variables.tf
}

module "use_conditionals" {
    source = "../../scratch/conditionals-modules/m2" # << Change to your directory
    a = module.skipped_module.module_id
    b = module.notskipped_module.module_id
    c = module.default_module.module_id
}

# root/outputs.tf
output skipped_module_name_and_id {
    value = module.skipped_module.module_name_and_id
}

output notskipped_module_name_and_id {
    value = module.notskipped_module.module_name_and_id
}

output default_module_name_and_id {
    value = module.default_module.module_name_and_id
}

the module


# m1/main.tf
resource "null_resource" "null" {
    count = var.module_config.skip ? 0 : 1 # If skip == true, then don't create the resource.

    provisioner "local-exec" {
        command = <<EOT
        #!/usr/bin/env bash
        echo "null resource, var.module_config.name: ${var.module_config.name}"
EOT
    }
}

# m1/variables.tf
variable "module_config" {
    type = object ({ 
        skip = bool, 
        name = string 
    })
    default = {
        skip = false
        name = "<NAME>"
        }
}

# m1/outputs.tf
output "module_name_and_id" {
   value = var.module_config.skip == true ? "SKIPPED" : format(
      "%s id:%v",
      var.module_config.name, 
      null_resource.null.*.id
   )
}

output "module_id" {
      value = var.module_config.skip == true ? null : format("%v",null_resource.null.*.id)
}

like image 34
P Burke Avatar answered Nov 08 '22 17:11

P Burke


The current answers here are helpful when you are working with more modern versions of terraform, but as noted by OP here they do not work when you are working with terraform < 0.12 (If you're like me and still dealing with these older versions, I am sorry, I feel your pain.)

See the relevant issue from the terraform project for more info on why the below is necessary with the older versions.

but to avoid link rot, I'll use the OPs example subnet_id argument using the answers in the github issue.

subnet_id = "${element(compact(concat(aws_subnet.xyz-subnet.*.id, list(var.subnet_id))),0)}"

From the inside out:

  1. concat will join the splat output list to list(var.subnet_id) -- per the background link 'When count = 0, the "splat syntax" expands to an empty list'
  2. compact will remove the empty item
  3. element will return your var.subnet_id only when compact recieves the empty splat output.
like image 29
Paul Avatar answered Nov 08 '22 16:11

Paul