Conditionals in for_each loop around elastic IPs - terraform

I have the following code and I would like to have a conditional that will only create an elastic IP if the instance is part of a public subnet (or based off of a boolean value if needed). This is the code that I currently have that works, but I want it to not create elastic IPs for resources on the private subnets:
locals {
instances_beta = {
my-ec2 = {
name = "myec2",
ami = "ami-029e27fb2fc8ce9d8",
instancetype = "t3.xlarge"
environment = "Beta",
securitygroups = [var.mysg],
subnetid = var.public-a,
elasticip = true
}
}
}
resource "aws_instance" "beta-instance" {
for_each = local.instances_beta
ami = each.value.ami
instance_type = each.value.instancetype
subnet_id = each.value.subnetid
key_name = "mykey"
vpc_security_group_ids = each.value.securitygroups
tags = {
Name = each.value.name
Environment = each.value.environment
}
}
resource "aws_eip" "beta-eip" {
for_each = local.instances_beta
instance = aws_instance.beta-instance[each.key].id
vpc = true
}
It sounds like count is the best way to do something like that, but I cannot do that as I am already using a for_each to achieve the resource creation. I was trying to do this with a nested for loop, but I cannot quite figure out how to get the syntax correct or if this is the best way to do it. For reference , the best resource I found on it was here around for_each conditionals: https://blog.gruntwork.io/terraform-tips-tricks-loops-if-statements-and-gotchas-f739bbae55f9

You can use for loop to create filtered map, for example:
for_each = {
for key, value in local.instances_beta: key => value if value.subnetid == var.public-a
}
It will filter local.instances_beta and leave items where subnetid equals var.public-a. You can adjust condition according to your needs.
More details in terraform documentation.

Related

Issues with Terraform creating a string of IP addresses

I would like to create EC2 VMs with the following resource declaration using Terraform.
resource "aws_instance" "wurststand" {
for_each = toset(["bratwurst-01", "bratwurst-02", "bratwurst-03", "currywurst-01", "currywurst-02" , "hotdog-01"])
ami = "ami-0c9354388bb36c088"
instance_type = "t2.micro"
key_name = "wurststand"
tags = {
name = each.key
}
}
Now I need the public_ip addresses of all Bratwurst instances in one string. To create a group_vars file for ansible that fills a JINJA2 template again. How do I do this. I already tried to solder this with join but somehow Terraform doesn't like it.
The final result should look like this: 127.0.0.1, 127.0.0.2, 127.0.0.3.
Thank you for your help.
You need to use a for expression to extract the list of IPs from the set of aws_instance resources, and then pass that to join:
join(", ", [for i in aws_instance.wurststand : i.private_ip])
So this is my solution with which it does what I want.
resource "aws_instance" "wurststand" {
for_each = toset(["bratwurst-01", "bratwurst-02", "currywurst-01", "currywurst-02", "currywurst-03", "hotdog-01", "logstash-01"])
ami = "ami-0c9354388bb36c088"
instance_type = "t2.micro"
key_name = "wurststand"
tags = {
Name = each.key
}
}
resource "local_file" "inventory" {
content = templatefile("templates/inventory.tftpl", {
elastic = tomap({
for instance in aws_instance.wurststand:
instance.tags.Name => instance.public_ip
})
})
filename = format("%s/%s", abspath(path.root), "../ansible/inventory/inventory.cfg")
}
resource "local_file" "bratwurst_hosts_for_group_vars" {
content = templatefile("templates/bratwurst_groupvars.tftpl", {
hosts = join("\" , \"", [for i in aws_instance.wurststand: i.public_ip if contains("bratwurst", i.tags.Name)])
})
filename = format("%s/%s", abspath(path.root), "../ansible/group_vars/Bratwurst.yml")
}
Thanks a lot for your help.

Nested Dynamic block on counted resource not working as expected

Need your kind help. I am stuck with the following resource creation. Using Terraform v1.0.6
I need to create appropriate subnets dynamically in two VPCs
variables.tf
vpc_resource_networks = {
pnw-01 = [
[
{
subnet_name = "wb-01"
subnet_ip = "10.58.72.0/25"
description = "WEB01"
index = 0
},
{
subnet_name = "wb-02"
subnet_ip = "10.58.72.128/25"
description = "WEB02"
index = 1
}
],
[
{
subnet_name = "wb-01"
subnet_ip = "10.58.80.0/25"
description = "WEB01"
index = 0
},
{
subnet_name = "web-02"
subnet_ip = "10.58.72.128/25"
description = "WEB02"
index = 1
}
]
]
}
main.tf
locals {
wlb_net = element(keys(var.vpc_resource_networks), 0)
}
resource "aws_subnet" "wlb" {
count = length(module.aws_vpc_app_resource)
vpc_id = element(module.aws_vpc_app_resource.*.vpc_id, count.index)
dynamic "subnet_group" {
for_each = var.vpc_resource_networks[local.wlb_net][count.index]
content {
dynamic "subnet" {
for_each = subnet_group.value
content {
cidr_block = subnet.subnet_ip
availability_zone = element(var.azs, subnet.index)
tags = {
Name = subnet.subnet_name
}
}
}
}
}
I intend to create subnets dynamically which is var.vpc_resource_networks.pnw01[0] should be on one vpc and other index on another VPC.
The above block returns
dynamic “subnet_group” {
Blocks of type “subnet_group” are not expected here.
Please assist
Looking at the resource definition of aws_subnet, I can see that, as the error message suggests, there's no property for a "subnet_group".
There are several different resource types that are subnet groups though for different services; such as DMS, DocumentDB, DAX, Elasticache, MemoryDB, Neptune, RDS, and Redshift. Search the term "subnet_group" on the left panel within the provider page.
Perhaps an AWS expert can comment here but I believe you're trying to do two things in one motion here.
First you should create the subnets and define their ranges, then you should create subnet groups that need access to different subnets for a particular service.
Here's some more information on subnets and subnet groups.

terraform - Iterate over two linked resources

I’m trying to write some code which would take an input structure like this:
projects = {
"project1" = {
namespaces = ["mynamespace1"]
},
"project2" = {
namespaces = ["mynamespace2", "mynamespace3"]
}
}
and provision multiple resources with for_each which would result in this:
resource "rancher2_project" "project1" {
provider = rancher2.admin
cluster_id = module.k8s_cluster.cluster_id
wait_for_cluster = true
}
resource "rancher2_project" "project2" {
provider = rancher2.admin
cluster_id = module.k8s_cluster.cluster_id
wait_for_cluster = true
}
resource "rancher2_namespace" "mynamespace1" {
provider = rancher2.admin
project_id = rancher2_project.project1.id
depends_on = [rancher2_project.project1]
}
resource "rancher2_namespace" "mynamespace2" {
provider = rancher2.admin
project_id = rancher2_project.project2.id
depends_on = [rancher2_project.project2]
}
resource "rancher2_namespace" "mynamespace3" {
provider = rancher2.admin
project_id = rancher2_project.project2.id
depends_on = [rancher2_project.project2]
}
namespaces are dependent on Projects and the generate id needs to be passed into namespace.
Is there any good way of doing this dynamically ? We might have a lot of Projects/namespaces.
Thanks for any help and advise.
The typical answer for systematically generating multiple instances of a resource based on a data structure is resource for_each. The main requirement for resource for_each is to have a map which contains one element per resource instance you want to create.
In your case it seems like you need one rancher2_project per project and then one rancher2_namespace for each pair of project and namespaces. Your current data structure is therefore already sufficient for the rancher2_project resource:
resource "rancher2_project" "example" {
for_each = var.projects
provider = rancher2.admin
cluster_id = module.k8s_cluster.cluster_id
wait_for_cluster = true
}
The above will declare two resource instances with the following addresses:
rancher2_project.example["project1"]
rancher2_project.example["project2"]
You don't currently have a map that has one element per namespace, so it will take some more work to derive a suitable value from your input data structure. A common pattern for this situation is flattening nested structures for for_each using the flatten function:
locals {
project_namespaces = flatten([
for pk, proj in var.projects : [
for nsk in proj.namespaces : {
project_key = pk
namespace_key = ns
project_id = rancher2_project.example[pk].id
}
]
])
}
resource "rancher2_namespace" "example" {
for_each = {
for obj in local.project_namespaces :
"${obj.project_key}.${obj.namespace_key}" => obj
}
provider = rancher2.admin
project_id = each.value.project_id
}
This produces a list of objects representing all of the project and namespace pairs, and then the for_each argument transforms it into a map using compound keys that include both the project and namespace keys to ensure that they will all be unique. The resulting instances will therefore have the following addresses:
rancher2_namespace.example["project1.mynamespace1"]
rancher2_namespace.example["project2.mynamespace2"]
rancher2_namespace.example["project2.mynamespace3"]
This seems to work too:
resource "rancher2_namespace" "example" {
count = length(local.project_namespaces)
provider = rancher2.admin
name = local.project_namespaces[count.index].namespace_name
project_id = local.project_namespaces[count.index].project_id
}

How to pass the templatefile function to user_data argument of EC2 resource in Terraform 0.12?

I need to pass the below templatefile function to user_data in EC2 resource. Thank you
userdata.tf
templatefile("${path.module}/init.ps1", {
environment = var.env
hostnames = {"dev":"devhost","test":"testhost","prod":"prodhost"}
})
ec2.tf
resource "aws_instance" "web" {
ami = "ami-xxxxxxxxxxxxxxxxx"
instance_type = "t2.micro"
# how do I pass the templatefile Funtion here
user_data = ...
tags = {
Name = "HelloWorld"
}
}
Because templatefile is a built-in function, you can call by including it directly in the argument you wish to assign the value to:
resource "aws_instance" "web" {
ami = "ami-xxxxxxxxxxxxxxxxx"
instance_type = "t2.micro"
user_data = templatefile("${path.module}/init.ps1", {
environment = var.env
hostnames = {"dev":"devhost","test":"testhost","prod":"prodhost"}
})
tags = {
Name = "HelloWorld"
}
}
The above is a good approach if the template is defined only for one purpose, as seems to be the case here, and you won't be using that result anywhere else. In situations where you want to use the same template result in multiple locations, you can use a local value to give that result a name which you can then use elsewhere in the module:
locals {
web_user_data = templatefile("${path.module}/init.ps1", {
environment = var.env
hostnames = {"dev":"devhost","test":"testhost","prod":"prodhost"}
})
}
resource "aws_instance" "web" {
ami = "ami-xxxxxxxxxxxxxxxxx"
instance_type = "t2.micro"
user_data = local.web_user_data
tags = {
Name = "HelloWorld"
}
}
With the local value web_user_data defined, you can use local.web_user_data to refer to it elsewhere in the same module, and thus use the template result in multiple locations. However, I would suggest doing that only if you need to use the result in multiple locations; if the template result is only for this specific instance's user_data then putting it inline as in my first example above will keep things simpler and thus hopefully easier to understand for a future reader and maintainer.

how to conditionally create aws_eip and aws_eip_association?

I need to be able to conditionally create an EIP and associate it to an instance:
resource "aws_eip" "gateway" {
vpc = true
tags = {
Name = "${var.project_id}-gateway"
Project = "${var.project_id}"
user = "${var.user}"
}
}
resource "aws_eip_association" "eip_assoc_gateway" {
instance_id = aws_instance.gateway.id
allocation_id = aws_eip.gateway.id
}
resource "aws_instance" "gateway" {
...
}
Unfortunately, aws_eip and aws_eip_association don't appear to support the count attribute, so I'm not clear if this is even possible?
Any ideas?
As mentioned in comment, count is supported by all Terraform primitive resources. Example for aws_eip below:
resource "aws_eip" "eip" {
instance = "${element(aws_instance.manager.*.id,count.index)}"
count = "${var.eip_count}"
vpc = true
}

Resources