Terraform dynamic block syntax - terraform

I have the following terraform code of aws_lb_target_group resource
dynamic "health_check" {
for_each = contains(keys(each.value), "health_check_settings") ? each.value.health_check_settings == true ? ["health_check"] : [] : []
iterator = hc
content {
port = contains(keys(each.value), "health_check_port") ? each.value.health_check_port : "traffic-port"
protocol = contains(keys(each.value), "health_check_protocol") ? each.value.health_check_protocol : "HTTP"
....
}
}
And I don't understand the part of for_each = ["health_check"]
It's just a tuple of one string, not an object. In case of health_check_settings=true how would the data will be passed to the for each.

It's done in order to make this block conditional.
The actual value of for_each doesn't matter because we use each from higher for_each context.

Related

How to add condition with for_each in dynmic block

Here in the below code, I'm planning to achieve that if value is null ,I don't want to execute a block of the code or else yes
dynamic "request_uri_condition" {
for_each = delivery_rule.value.request_uri_condition_operator != "" ?1 : 0
content{
operator = delivery_rule.value.request_uri_condition_operator
match_values = delivery_rule.value.request_uri_condition_match_values
}
}
variable.tf
variable "delivery_rules" {
type = list(object({
cdnendpoint = string
name = string
order = number
request_scheme_conditions_match_values = list(string)
request_scheme_conditions_operator = string
}))
}
error: Cannot use a number value in for_each. An iterable collection is required.
Using the syntax below, determine whether delivery_rules is empty or not.
error: Cannot use a number value in for_each. An iterable collection is required.
It means that a conditional operator should be given as a string rather than a number (?1:0). I verified it in my environment after making the changes, and everything worked as expected.
dynamic "request_uri_condition" {
for_each = var.delivery_rule != "" ? var.delivery_rule : "default"
content{
operator = delivery_rule.value.request_uri_condition_operator
match_values = delivery_rule.value.request_uri_condition_match_values
}
}
terraform initialized successfully with terraform init:
terraform planexecuted with no "type" errors:
Refer terraform conditionals, terraform registry Cdn template

Terraform How to use for each values from one resource in another resource

I am trying to create users in terraform cloud based off a simple csv, structured like so:
email,access_level
test#gmail.com,readonly
test2#gmail.come,owners
I can create users easily enough using the following resource block:
locals {
users_csv = csvdecode(file("${path.module}/users.csv"))
}
resource "tfe_organization_membership" "users_csv" {
for_each = { for r in local.users_csv : r.email => r }
organization = var.ppl_organisation
email = each.value.email
}
However, if I want to add them to teams then I need the "id" output from the above resource into the below resource:
resource "tfe_team_organization_member" "readonly_member" {
for_each = { for r in local.read_only : r.email => r }
team_id = each.value.access_level == "readonly" ? each.value.access_level : local.readonly_id
organization_membership_id = "${tfe_organization_membership.users_csv.id}"
}
Is there a way to pass this?
Thanks in advance.
I think the first thing I'd do here is to factor out the projection of your CSV data from a list to a map, like this:
locals {
users_csv = csvdecode(file("${path.module}/users.csv"))
users = { for r in local.users_csv : r.email => r }
}
By defining local.users like this we can make it more concise to refer to that value multiple times elsewhere in the config:
resource "tfe_organization_membership" "users_csv" {
for_each = local.users
organization = var.ppl_organisation
email = each.value.email
}
resource "tfe_team_organization_member" "readonly_member" {
for_each = local.users
team_id = (
each.value.access_level == "readonly" ?
each.value.access_level :
local.readonly_id
)
organization_membership_id = tfe_organization_membership.users_csv[each.key].id
}
Any resource that has for_each set is represented in expressions as a map of objects whose keys are the same as the for_each input map, so the tfe_organization_membership.users_csv[each.key] expression refers to an object representation of the corresponding instance of the other resource, correlating by the map keys.

How to have conditional resources inside a module with 0.12 for_each

I'm passing my modules a list and it's going to create EC2 instances and eips and attach.
I'm using for_each so users can reorder the list and Terraform won't try to destroy anything.
But how do I use conditional resources now? Do I still use count? If so how, because you can't use count with for_each?
This is my module now:
variable "mylist" {
type = set(string)
description = "Name used for tagging, AD, and chef"
}
variable "createip" {
type = bool
default = true
}
resource "aws_instance" "sdfsdfsdfsdf" {
for_each = var.mylist
user_data = data.template_file.user_data[each.key].rendered
tags = each.value
...
#conditional for EIP
resource "aws_eip" "public-ip" {
for_each = var.mylist
// I can't use this anymore!
// how can I say if true create else don't create
#count = var.createip ? 0 : length(tolist(var.mylist))
instance = aws_instance.aws-vm[each.key].id
vpc = true
tags = each.value
}
I also need to get the value of the mylist item for eip too because I use that to tag the eip. So I think I need to index into the foreach loop somehow and also be able to use count or another list to determine if it's created or not - is that correct?
I think I got it but I don't want to accept until it's confirmed this is not the wrong way (not as a matter of opinion but improper usage that will cause actual problems).
variable "mylist" {
type = set(string)
description = "Name used for tagging, AD, and chef"
}
variable "createip" {
type = bool
default = true
}
locals {
// set will-create-public-ip to empty array if false
// otherwise use same mylist which module uses for creating instances
will-create-public-ip = var.createip ? var.mylist : []
}
resource "aws_instance" "sdfsdfsdfsdf" {
for_each = var.mylist
user_data = data.template_file.user_data[each.key].rendered
tags = each.value
...
resource "aws_eip" "public-ip" {
// will-create-public-ip set to mylist or empty to skip this resource creatation
for_each = will-create-public-ip
instance = aws_instance.aws-vm[each.key].id
vpc = true
tags = each.value
}

How to use null with a different value in place of default value

I know that null can be used like this to set default behavior if var not specified:
variable "override_private_ip" {
type = string
default = null
}
resource "aws_instance" "example" {
# ... (other aws_instance arguments) ...
private_ip = var.override_private_ip
}
But I want to set my own default behavior if it's not specified.
I'm doing this:
#### if user sets an id use that but if not get id from data source
resource "aws_instance" "myserver" {
ami = var.ami_id != null ? var.ami_id : data.aws_ami.getami.id
This seems to work but is this the correct way? I want to make sure I'm not missing a feature for this. I tried just var.ami_id ? var.ami_id : data.aws_ami.getami.id but null is not converted to a bool so did not work.
A conditional expression like the one you showed is indeed the right way to express this in Terraform. As you've seen, Terraform does not consider null to be a boolean false.
It doesn't seem like it would be necessary for this particular situation, but if you have input variables that are used in many places where all uses would need the same normalization/preparation logic then you can factor out the expression into a local value to use it many times:
variable "ami_id" {
type = string
default = null
}
data "aws_ami" "example" {
count = var.ami_id == null ? 1 : 0
# ...
}
locals {
ami_id = var.ami_id != null ? var.ami_id : data.aws_ami.example[0].id
}
resource "aws_instance" "example" {
# ... (other aws_instance arguments) ...
ami = local.ami_id
}
You could then use local.ami_id many times in the module without duplicating the logic that handles the default value lookup.

Create nested resource parameter blocks based on conditional in terraform

I am trying to create a terraform module that creates a compute instance. I want the resource to have an attached disk if and only if I have a variable attached_disk_enabled set to true during module invocation. I have this:
resource "google_compute_disk" "my-disk" {
name = "data"
type = "pd-ssd"
size = 20
count = var.attached_disks_enabled ? 1 : 0
}
resource "google_compute_instance" "computer" {
name = "computer"
boot_disk {
...
}
// How do I make this disappear if attached_disk_enabled == false?
attached_disk {
source = "${google_compute_disk.my-disk.self_link}"
device_name = "computer-disk"
mode = "READ_WRITE"
}
}
Variables have been declared for the module in vars.tf. Module invocation is like this:
module "main" {
source = "../modules/computer"
attached_disk_enabled = false
...
}
I know about dynamic blocks and how to use for loop to iterate over a list and set multiple blocks, but I'm not sure how to exclude a block from a resource using this method:
dynamic "attached-disk" {
for_each in var.disk_list
content {
source = "${google_compute_disk.my-disk.*.self_link}"
device_name = "computer-disk-${count.index}"
mode = "READ_WRITE"
}
}
I want if in place of for_each. Is there a way to do this?
$ terraform version
Terraform v0.12.0
Because your disk resource already has the conditional attached to it, you can use the result of that resource as your iterator and thus avoid specifying the conditional again:
dynamic "attached_disk" {
for_each = google_compute_disk.my-disk
content {
source = attached_disk.value.self_link
device_name = "computer-disk-${attached_disk.key}"
mode = "READ_WRITE"
}
}
To answer the general question: if you do need a conditional block, the answer is to write a conditional expression that returns either a single-item list or an empty list:
dynamic "attached_disk" {
for_each = var.attached_disk_enabled ? [google_compute_disk.my-disk[0].self_link] : []
content {
source = attached_disk.value
device_name = "computer-disk-${attached_disk.key}"
mode = "READ_WRITE"
}
}
However, in your specific situation I'd prefer the former because it describes the intent ("attach each of the disks") more directly.

Resources