Passing multiple values to a variable in Terraform - terraform

I have a question about passing multiple values to a variable in Terraform. I can't find the answer anywhere and I am not sure if this is even possible. In our environment when we create AWS resources such VPC and add a tag name to it like project-environment-VPC e.g. cvs-production-VPC. How would I do the same when I am trying to create the resource using Terraform? I tried the below approach and it didn't work:
resource "aws_vpc" "main" {
cidr_block = var.aws_cidr
instance_tenancy = "default"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = ${var.project}-${var.environment}-${"VPC"}
}
}
If it is not possible - perhaps there is a workaround? Thanks in advance for any replies.

The current Name tag value causes Invalid Character error using Terraform 0.14.6.
Change the Name tag value as below to resolve the issue.
Name = "${var.project}-${var.environment}-${"VPC"}"

Related

Terraform count length of variables

we have a standard naming convention within azure, but in order to sometimes be able to make an exception, it must be possible to provide a name yourself when calling the module
How can be indicated within count which variable Var.Log Name or Local.ComponetName should be used and how can we pass this to the name of the resource
resource "azurerm_log_analytics_workspace" "LOG" {
count = length(var.LOG_Name) == "" ? length(local.ComponentNames) : null
name = var.LOG_Name[count.index] == "" ? local.ComponentNames[count.index] : null
resource_group_name = element(var.resourcegroup_name[*], count.index)
location = var.location
sku = var.LOG_Sku
retention_in_days = var.LOG_RetentionPeriod
}
What you are actually looking for are loops. Within loops you can reference the name of the resource and in case there is no such resource available it won't create them, which seems to be what you tried to indicate when mentioning null.
Here is a great link regarding loops in terraform that thoroughly explains the different types of loops and how to use them: https://blog.gruntwork.io/terraform-tips-tricks-loops-if-statements-and-gotchas-f739bbae55f9

Terraform: Conditional creation of a resource based on a variable in .tfvars

I have resources defined in .tf files that are generic to several applications. I populate many of the fields via a .tfvars file. I need to omit some of the resources entirely based on variables in the .tfvars.
For example if I have a resource like:
resource "cloudflare_record" "record" {
zone_id = "${data.cloudflare_zones.domain.zones[0].id}"
name = "${var.subdomain}"
value = "${var.origin_server}"
type = "CNAME"
ttl = 1
proxied = true
}
But then I declare something like cloudflare = false in my .tfvars file I'd like to be able to do something like this:
if var.cloudflare {
resource "cloudflare_record" "record" {
zone_id = "${data.cloudflare_zones.domain.zones[0].id}"
name = "${var.subdomain}"
value = "${var.origin_server}"
type = "CNAME"
ttl = 1
proxied = true
}
}
I've looked at dynamic blocks but that looks like you can only use those to edit fields and blocks within a resource. I need to be able to ignore an entire resource.
Add a count parameter with a ternary conditional using the variable declared in .tfvars like this:
resource "cloudflare_record" "record" {
count = var.cloudflare ? 1 : 0
zone_id = "${data.cloudflare_zones.domain.zones[0].id}"
name = "${var.subdomain}"
value = "${var.origin_server}"
type = "CNAME"
ttl = 1
proxied = true
}
In this example var.cloudflare is a boolean declared in the .tfvars file. If it is true a count of 1 record will be created. If it is false a count of 0 record will be created.
After the count apply the resource becomes a group, so later in the reference use 0-index of the group:
cloudflare_record.record[0].some_field
Expanding on #Joel Guerra's answer, after you use count to determine whether to deploy the resource or not, you can use the one() function to refer to the resource without an index (i.e. without having to use [0]).
For example, after defining the resource like below
resource "cloudflare_record" "record" {
count = var.cloudflare ? 1 : 0
}
Define a local variable like below
locals {
cloudflare_record_somefield = one(cloudflare_record.record[*].some_field)
}
Now instead of cloudflare_record.record[0].some_field, you can use
local.cloudflare_record_somefield
If the count is 0 (e.g. var.cloudflare is false and the resource wasn't created) then local.cloudflare_record_somefield would return null (instead of returning an error when indexing using [0]).
Reference: https://developer.hashicorp.com/terraform/language/functions/one
An issue i'm seeing this with is if the resource your trying to create is already using a for_each then you can't use both count and for_each in the resource. I'm still trying to find an answer on this will update if I find something better.

proper way to use nested variables in terraform

In my terraform script, I have
resource "azuread_application" "main" {
count = "${length(var.sp_names)}"
name = "${sp_prefix}-${var.sp_names[count.index]}"
available_to_other_tenants = false
}
resource "azuread_service_principal" "main" {
count = "${length(var.sp_names)}"
application_id = "${azuread_application.main.["${sp_prefix}"-"${var.sp_names[count.index]}"].application_id}"
}
when I ran terraform init I get the following error:
An attribute name is required after a dot.
what is the right way to use nested variables and a list object?
In order for a resource to be represented as a map of instances rather than a list of instances, you need to use for_each instead of count:
resource "azuread_application" "main" {
for_each = { for n in var.sp_names : n => "${var.sp_prefix}-${n}" }
name = each.value
available_to_other_tenants = false
}
The for_each expression above is a for expression that transforms your list or set of names into a mapping from the given names to the prefixed names. In the other expressions in that block, each.key would therefore produce the original given name and each.value the prefixed name.
You can then similarly use for_each to declare the intent "create one service principal per application" by using the application resource's map itself as the for_each expression for the service principal resource:
resource "azuread_service_principal" "main" {
for_each = azuread_application.main
application_id = each.value.application_id
}
In this case, the azuread_application.main value is a map from unprefixed names to objects representing each of the declared applications. Therefore each.key in this block is the unprefixed name again, but each.value is the corresponding application object from which we can access the application_id value.
If your var.sp_names had a string "example" in it, then Terraform would interpret the above as a request to create two objects named azuread_application.main["example"] and azuread_service_principal.main["example"], identifying these instances by the var.sp_names values. This is different to count where the instances would have addresses like azuread_application.main[0] and azuread_service_principal.main[0]. By using for_each, we ensure that adding and removing items from var.sp_names will add and remove corresponding instances from those resources, rather than updating existing ones that happen to share the same numeric indices.
I am assuming you are using a version older that 0.12.x. If not the answer from Martin is the best one.
You need to leverage the splatting.
resource "azuread_service_principal" "main" {
count = "${length(var.sp_names)}"
application_id = "${azuread_application.main.*.application_id}"
}

When/where does the data aws_availability_zones is initialised or injected?

I am reading the terraform code here https://github.com/linuxacademy/content-terraform/blob/master/course/terraform-aws/networking/main.tf and here are the code I don't quite understand.
data "aws_availability_zones" "available" {}
....
resource "aws_subnet" "tf_public_subnet" {
count = 2
vpc_id = "${aws_vpc.tf_vpc.id}"
cidr_block = "${var.public_cidrs[count.index]}"
map_public_ip_on_launch = true
availability_zone = "${data.aws_availability_zones.available.names[count.index]}"
tags {
Name = "tf_public_${count.index + 1}"
}
}
I don't understand when the data.aws_availability_zones is populated.
data.aws_availability_zones is data source, different from resource (the next code you pasted)
Please go through this url terraform data source to understand how data sources work
If you don't understand how data.aws_availability_zones works, go through this url Data Source: aws_availability_zones
So from its Attributes Reference, we know it will help to get current account's availability zones.
zone_ids - A list of the Availability Zone IDs available to the account.
Its attribute name is alias of zone_ids, their outputs are same.

terraform - how to update route_table attribute conditionally

any ideas how to conditionally insert 'propagating_vgws = some_vgw' ??
I see lots of ways to conditionally create a resource, but what about a resources optional attribute?
resource "aws_route_table" "internal" {
count = "${length(var.internal_subnets)}"
vpc_id = "${aws_vpc.main.id}"
# propagating_vgws = ["${aws_vpn_gateway.main.id}"]
tags {
Name = "${var.name}-${format("internal-%03d", count.index+1)}"
}
}
the route_table is created by a vpc module and I need to conditionally place that vpc on a direct-connect virtual interface or not.
thanks for any feedback.
Maybe this will help you. (scroll to the conditinals section).
The idea is that you can do a conditional interpolation, and you can create an empty list rendering the attribute empty, thus in a way optional (has meaningful value or not):
propagating_vgws = "${var.decide_based_on_this_value == var.something ?
var.list_in_case_yes : list() }"
${list()} returns an empty list, for more refer to the above linked page, at the bottom there are many built-in functions like lists...
I hope it will solve your problem.

Resources