Need to understand terraform resource structure - terraform

I am reading terraform and found below code:
resource "aws_vpc" "vpc_main" {
cidr_block = "10.0.0.0/16"
tags {
Name = "Main VPC"
}
}
Here I could not understand what vpc_main stands for in the resource definition. Could somebody explain?

It's a user-defined name for the resource - without this you wouldn't be able to distinguish multiple instances of the same resource type.
See the docs for more details.

Variable types and names in other programming languages are a good analogy. For example, in Java, you can declare a variable as follows:
String foo
The type of the variable is String and the name of the variable is foo. You must specify a name so you can (a) distinguish it from other variables of the same type and (b) refer to it later.
In Terraform, it's essentially the same idea:
resource "aws_instance" "foo" { ... }
Here, the type of the resource is aws_instance and the name is foo. You must specify a name so you can (a) distinguish it from other resources of the same type and (b) refer to it later. For example, to output the public IP address of that Instance, you could do the following:
output "public_ip" {
value = "${aws_instance.foo.public_ip}"
}

Related

Terraform: pass a list of security groups

I try to pass a list of security groups to create ec2 instance.
variables.tf
variable "parameters" {
type = map(any)
}
terraform.tfvars.json
{
"parameters": {
"ami": "ami1234",
"vpc_security_group_ids": "sg-1234,sg-wxyz"
}
}
Note, teffaform does not allow to use list for security groups as it requires all element type of map be the same. So I have to use comma-separated string.
resource "aws_instance" "worker" {
...
vpc_security_group_ids = ["${split(",",var.parameters.vpc_security_group_ids)}"]
}
I copy some online code to split the string, but terraform complains that the variable is only known after apply.
As I think you've already understood, the any in map(any) represents asking Terraform to automatically infer the element type of the map, and so Terraform will study the given value and automatically choose a single concrete type to replace any. In the case of your example here, the result would be map(string) because all of the values in your map are strings.
However, the example you've described here appears to me to more suited to be an object type rather than a map. Maps are intended for arbitrary key/value pairs where all of the elements represent the same kind of thing and can therefore be of the same type. Object types are for describing a single thing that has multiple different properties of different types.
Therefore I would suggest rewriting your type constraint to properly describe the data structure you're expecting, which seems to be the following based on context:
variable "parameters" {
type = object({
ami = string
vpc_security_group_ids = set(string)
})
}
set(string) matches the provider's type constraint for vpc_security_group_ids in aws_instance, since security groups don't have any meaningful ordering when associated with an EC2 instance and so it wouldn't make sense to use a list.
With this data type in place then you should be able to just assign the variable values directly, because they will already be of the expected types:
resource "aws_instance" "worker" {
ami = var.parameters.ami
vpc_security_group_ids = var.parameters.vpc_security_group_ids
}

Terraform resource as a module input variable

When developing a terraform module, I sometimes find myself in the need to define different input variables for the same resources. For example, right now I need the NAME and ARN of the same AWS/ECS cluster for my module, so, I defined two variables in my module: ecs_cluster_arn and ecs_cluster_name.
For the sake of DRY, it would be very nice if I could just define the input variable ecs_cluster of type aws_ecs_cluster and the just use whatever I need inside my module.
I can't seem to find a way to do this. Does anyone know if it's possible?
You can define an input variable whose type constraint is compatible with the schema of the aws_ecs_cluster resource type. Typically you'd write a subset type constraint that contains only the attributes the module actually needs. For example:
variable "ecs_cluster" {
type = object({
name = string
arn = string
})
}
Elsewhere in the module, you can use var.ecs_cluster.name and var.ecs_cluster.arn to refer to those attributes. The caller of the module can pass in anything that's compatible with that type constraint, which includes a whole instance of the aws_ecs_cluster resource type, but would also include a literal object containing just those two attributes:
module "example" {
# ...
ecs_cluster = aws_ecs_cluster.example
}
module "example" {
# ...
ecs_cluster = {
name = "blah"
arn = "arn:aws:yada-yada:blah"
}
}
In many cases this would also allow passing the result of the corresponding data source instead of the managed resource type. Unfortunately for this pairing in particular the data source for some reason uses the different attribute name cluster_name and therefore isn't compatible. That's unfortunate, and not the typical design convention for pairs of managed resource type and data source with the same name; I assume it was a design oversight.
module "example" {
# ...
# This doesn't actually work for the aws_ecs_cluster
# data source because of a design quirk, but this would
# be possible for most other pairings such as
# the aws_subnet managed resource type and data source.
ecs_cluster = data.aws_ecs_cluster.example
}

What is the iterator feature for in Terraform's for_each?

I am trying to understand the iterator feature of the for_each in Terraform 0.12. The docs say:
Iterator:
The iterator argument (optional) sets the name of a temporary variable that represents the current element of the complex value. If omitted, the name of the variable defaults to the label of the dynamic block ...
But I can't find any code examples that uses this feature and I can't get my head around what it is for. I have read the Terraform 0.12 preview but it is not mentioned there, and I found some GitHub issues (e.g. this one) but can't find clues there either.
Is it just for improving readability? I would really appreciate a code example and explanation that goes beyond what I can find in the docs.
Basically in various languages like Python, Ruby, C++, Javascript, Groovy, etc. you can establish a temporary variable within a lambda (especially if it is iterative) that stores the temporary value per iteration within the lambda. In some languages (e.g. Groovy), there is a default name for this variable, or you can set one yourself (i.e. default variable name in Groovy is it). For example, in Groovy we have:
strings.each() {
print it
}
would print the content of the string variable assignment (assuming it can be cast to String). The following code has the exact same functionality:
strings.each() { a_string ->
print a_string
}
where we have explicitly named the temporary variable as a_string. This is analogous to the iterator argument in your question. So in Terraform, we see an example in the documentation:
resource "aws_security_group" "example" {
name = "example" # can use expressions here
dynamic "ingress" {
for_each = var.service_ports
content {
from_port = ingress.value
to_port = ingress.value
protocol = "tcp"
}
}
}
According to the documentation:
If omitted, the name of the variable defaults to the label of the dynamic block
and the name above is ingress (notice it is the label specified adjacent to the dynamic block). Sure enough, we see the name of the temporary variable above is ingress and it is being accessed via ingress.value. To utilize the functionality of iterator to rename this temporary variable, we can do something like the below.
resource "aws_security_group" "example" {
name = "example" # can use expressions here
dynamic "ingress" {
for_each = var.service_ports
iterator = "service_port"
content {
from_port = service_port.value
to_port = service_port.value
protocol = "tcp"
}
}
}
thus renaming the temporary variable storing the element of var.service_ports in each iteration within the lambda from default name ingress to service_port. The primary added value I see in this (and likewise when I use it in Groovy for Jenkins Pipeline libraries) is to provide a more clear name for the temporary variable storing the value to improve readability.

How to add a resource using the same module?

Terraform newbie here. I've a module which creates an instance in GCP. I'm using variables and terraform.tfvars to initialize them. I created one instance successfully - say instance-1. But when I modify the .tfvars file to create a second instance (in addition to the first), it says it has to destroy the first instance. How can I run the module to 'add' an instance, instead of 'replacing the instance'? I know the first instance which was created is in terraform.tfstate. But that doesn't explain the inability to 'add' an instance.
Maybe I'm wrong, but I'm looking at 'modules' (and its config files) as functions- such that I can call them anytime with different parameters. That does not appear to be the case.
Terraform will try to maintain the deployed resources matching your resources definition.
If you want two instances at the same time, then you should describe them both in your .tf file.
Ex. same instances, add a count to your definition
resource "some_resource" "example" {
count = 2
name = "example-${count.index}"
}
Ex. two different resources with specific values
resource "some_resource" "example-1" {
name = "example-1"
size = "small"
}
resource "some_resource" "example-2" {
name = "example-2"
size = "big"
}
Better you can set the specific values in tfvars for each resource
resource "some_resource" "example" {
count = 2
name = "example-${count.index}"
size = ${vars.mysize[count.index]}
}
variable mysize {}
with tfvars file:
mysize = ["small", "big"]

declare a variable using `execute` Interpolation in Terraform

I want to declare a a sub-string of a variable to another variable. I tested taking a sub-string using terraform console.
> echo 'element(split (".", "10.250.3.0/24"), 2)' | terraform console
> 3
my subnet is 10.250.3.0/24 and I want my virtual machine to get private IP address within this subnet mask 10.250.3.6. I want this to get automatically assign by looking at subnet address. What I've tried;
test.tf
variable subnet {
type = "string"
default = "10.250.3.0/24"
description = "subnet mask myTestVM will live"
}
variable myTestVM_subnet {
type = "string"
default = "10.250." ${element(split(".", var.trusted_zone_onpremises_subnet), 2)} ".6"
}
And then I test it by
terraform console
>Failed to load root config module: Error parsing /home/anum/test/test.tf: At 9:25: illegal char
I guess its just simple syntax issue. but couldn't figure out what!
As you've seen, you can't interpolate the values of variables in Terraform.
You can, however, interpolate locals instead and use those if you want to avoid repeating yourself anywhere.
So you could do something like this:
variable "subnet" {
type = "string"
default = "10.250.3.0/24"
description = "subnet mask myTestVM will live"
}
locals {
myTestVM_subnet = "10.250.${element(split(".", var.trusted_zone_onpremises_subnet), 2)}.6"
}
resource "aws_instance" "instance" {
...
private_ip = "${local.myTestVM_subnet}"
}
Where the aws_instance is just for demonstration and could be any resource that requires/takes an IP address.
As a better option in this specific use case though you could use the cidrhost function to generate the host address in a given subnet.
So in your case you would instead have something like this:
resource "aws_instance" "instance" {
...
private_ip = "${cidrhost(var.subnet, 6)}"
}
Which would create an AWS instance with a private IP address of 10.250.3.6. This can then make it much easier to create a whole series of machines that increment the IP address used by using the count meta parameter.
Terraform doesn't allows interpolations declaration of variables in default. So I get ;
Error: variable "myTestVM_subnet": default may not contain interpolations
and the syntax error really got fixed after banging my head, so here is what Terraform likes;
private_ip_address = "10.250.${element(split(".", "${var.subnet}"), 2)}.5"

Resources