Issues with Terraform creating a string of IP addresses - terraform

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.

Related

Conditionals in for_each loop around elastic IPs

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.

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
}

Terraform on Azure - Deploy multiple subnet

I'm trying to implement a Terraform script to create multiple subnets.
resource "azurerm_subnet" "test_subnet" {
name = "testSUBNET"
resource_group_name = "${local.resource_group_name}"
virtual_network_name = "${azurerm_virtual_network.lab_vnet.name}"
address_prefix = "10.0.1.0/24"
}
Is there a way to do a for-each or a loop on a variable in order to create them at the same time?
You can achieve this using a variable and count index as follows:
variable "subnet_prefix" {
type = "list"
default = [
{
ip = "10.0.1.0/24"
name = "subnet-1"
},
{
ip = "10.0.2.0/24"
name = "subnet-2"
}
]
}
resource "azurerm_subnet" "test_subnet" {
name = "${lookup(element(var.subnet_prefix, count.index), "name")}"
count = "${length(var.subnet_prefix)}"
resource_group_name = "${local.resource_group_name}"
virtual_network_name = "${azurerm_virtual_network.lab_vnet.name}"
address_prefix = "${lookup(element(var.subnet_prefix, count.index), "ip")}"
}
There is also preview feature available for-each in the new version
If you are using Terraform 12 this can be achieved using the for-each capability or the count capability
count should be used if you are looking to create almost identical resources.
for-each should be used to create multiple of each instance based on a different map or set of values.
Using an list of strings and the toset() function to convert this is a neat way to achieve this
variable "subnet_ids" {
type = list(string)
}
resource "aws_instance" "server" {
for_each = toset(var.subnet_ids)
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
subnet_id = each.key # note: each.key and each.value are the same for a set
tags = {
Name = "Server ${each.key}"
}
}
Or you could achieve this by using something like the below:
resource "azurerm_resource_group" "rg" {
for_each = {
a_group = "eastus"
another_group = "westus2"
}
name = each.key
location = each.value
}
If you are looking to achieve this with Terraform 11 the count and variable capabilities are the only way other than code duplication. (Rajat Arora has mentioned)
I would strongly recommended using Terraform 12 as the providers for Terraform 11 will be unsupported in the not to far future and if you can save yourself from refactoring now, you should!

terraform setting hostname on multiple aws instances

I'm wanting to understand how user data can be used to set hostnames for 2 or more ec2 instances that terrafrom creates. Below is my instance.tf which creates 2 instances.
resource "aws_instance" "example" {
count = 2
ami = "${lookup(var.AMIS, var.aws_region)}"
instance_type = "t2.micro"
tags = {Name = "rb-${count.index}"}
# the VPC subnet
subnet_id = "${aws_subnet.dee-main-public-1.id}"
# the security group
vpc_security_group_ids = ["${aws_security_group.allow-ssh.id}"]
# the public SSH key
key_name = "${aws_key_pair.mykeypair.key_name}"
}
resource "aws_key_pair" "mykeypair" {
key_name = "mykeypair"
public_key = "${file("${var.PATH_TO_PUBLIC_KEY}")}"
}
How do I set the hostnames for those 2 instances. i.e web1.example.com, web2.example.com
I understand cloudinit or remote-exec can be used for this but struggling to come up with the code as I'm still a beginner. Really appreciate If I can get some help to come up to speed. Many thanks in advance.
-B
You can do it with data resource for template file which allows you to use count function. The below syntax will allow you use the hostname variable with the count function.
data "template_file" "init" {
count = 2
template = file("$path")
vars = {
hostname = "web${count.index}.example.com"
}
}
resource "aws_instance" "master" {
ami = "$ami"
count = 2
instance_type = t2.medium
user_data = data.template_file.init[count.index].rendered
}

Resources