Why resource provides has two labels - terraform - terraform

I am currently learning terraform, still i can't understand, why resource provider block needs two labels as below ? What are the use cases for two labels ?
resource "aws_instance" "example"
Regards,
Chin

You are telling Terraform that you are declaring a resource of type aws_instance with name example. Only one of those things is a label, the other is the type of resource you want Terraform to create and manage for you.

I am assuming that you are familiar with OOP concepts. So, I am gonna use java to explain.
Lets say you have a java class box defined.
class box {
double length;
double width;
double height;
}
Now you usually need to create an object to use that class or to refer that class. You can have multiple object of the same class. right ?
new myBox1 = box(length=11,width=7,height=5)
new myBox2 = box(length=9,width=4,height=8)
Now lets try to understand the terraform.
There are three components in resource "aws_instance" "example"
resource : It tells the terraform core engine that you want to use a terraform resource. In your case its an aws_instance. It could have also been any other resource like aws_s3 or aws_rds_cluster etc. Imagine it's your keyword class.
aws_instance : It's your selected resource you wanna use. Imagine it's your class box.
example : It's the identifier you are gonna use to store your terraform state. The instances may have separate values. One can be t2.micro and another can be something else. Imagine it as your object myBox1 or myBox2.
Terraform stores the current state of your infrastructure in a state file be it local or remote. And, in practice you will have multiple aws instances. How can you or terraform differentiate between them. It identifies each of your aws_instace by the name you provide. For instance,
resource "aws_instance" "example1"
resource "aws_instance" "example2"
aws_instance.example1 and aws_instance.example2 are two different object or two different instances. For the same reason you can not have the same identifier. (If your terraform state is same).
In short, think of that as class and object. :)

Related

Terraform best practise for near identical iam_policy_document's for each environment to avoid duplication

What’s the best practise for handling policy documents that are entirely the same for each environment apart from an ID within them?
Initially, the codebase I was using simply duplicated these policies
in the iam.tf file with the ID changed in each environments resource
definition. It’s a single workspace monolithic repo that I can’t
change.
I then refactored it to be a module which creates the policy
with the ID as a variable.
I then found out about templatefiles in
terraform so I refactored it to instead be a policy .tftpl file in a
subdirectory and then I call templatefile() with the different
variable for each environment.
I’m aware that the recommended convention for policy documents is to implement them as a data object, but my understanding is I can’t then parameterise it to prevent entire policy documents being repeated save for a single variable (unless I modularise it like I did initially).
Does anyone have any advice on the best practise for this scenario?
You can definitely parameterize the aws_iam_policy_document data source.
data "aws_iam_policy_document" "this" {
for_each = toset(["bucket-a", "bucket-b"])
statement {
actions = ["s3:*"]
resources = ["arn:aws:s3:::${each.key}"]
}
}
You can follow this pattern for attachment too:
resource "aws_iam_policy" "this" {
for_each = toset(["bucket-a", "bucket-b"])
name_prefix = each.key
policy = data.aws_iam_policy_document.this[each.key].json
}
resource "aws_iam_policy_attachment" "this" {
for_each = toset(["bucket-a", "bucket-b"])
name = "${each.key}-attachment"
policy_arn = aws_iam_policy.this[each.key].arn
# things to attach to
}

Creating a list of existing aws_instance resources in Terraform

I'm currently trying to find a way to create a list of aws_instance type resources.
The ec2s are already configured. I imported them using Terraform, but I would like to have them as a single list, so I could perform actions like remote-exec on all of them at the same time.
I'm just not sure how can I declare a list type variable to include all existing aws_instance resources.
Any help would be much appreciated! Cheers.
EDIT:
As asked, I'm going to add some of the HCL:
As I stated, each instance is imported from an existing aws configuration.
This means I already have aws_instance blocks for each ec2.
resource "aws_instance" "ec2_1" {
*truncated*
}
I was wondering if there was a way to take these resources, and append them into a list.
I would like to create this list in order to perform actions on all instances at once, using the Provisioner remote-exec.
I tried creating a variable, but I'm afraid it doesn't function that way:
variable "ec2_list" {
type = list
default = [aws_instance.ec2_1, aws_instance.ec2_2,...]
}
But variables from main.tf cannot be used for variables.
I'm just curious if you can make a general resource to create a list under.
If you know anything, please let me know.

Declare multiple providers for a list of regions

I have a Terraform module that manages AWS GuardDuty.
In the module, an aws_guardduty_detector resource is declared. The resource allows no specification of region, although I need to configure one of these resources for each region in a list. The region used needs to be declared by the provider, apparently(?).
Lack of module for_each seems to be part of the problem, or, at least, module for_each, if it existed, might let me declare the whole module, once for each region.
Thus, I wonder, is it possible to somehow declare a provider, for each region in a list?
Or, short of writing a shell script wrapper, or doing code generation, is there any other clean way to solve this problem that I might not have thought of?
To support similar processes I have found two approaches to this problem
Declare multiple AWS providers in the Terraform module.
Write the module to use a single provider, and then have a separate .tfvars file for each region you want to execute against.
For the first option, it can get messy having multiple AWS providers in one file. You must give each an alias and then each time you create a resource you must set the provider property on the resource so that Terraform knows which region provider to execute against. Also, if the provider for one of the regions can not initialize, maybe the region is down, then the entire script will not run, until you remove it or the region is back up.
For the second option, you can write the Terraform for what resources you need to set up and then just run the module multiple times, once for each regional .tfvars file.
prod-us-east-1.tfvars
prod-us-west-1.tfvars
prod-eu-west-2.tfvars
My preference is to use the second option as the module is simpler and less duplication. The only duplication is in the .tfvars files and should be more manageable.
EDIT: Added some sample .tfvars
prod-us-east-1.tfvars:
region = "us-east-1"
account_id = "0000000000"
tags = {
env = "prod"
}
dynamodb_read_capacity = 100
dynamodb_write_capacity = 50
prod-us-west-1.tfvars:
region = "us-west-1"
account_id = "0000000000"
tags = {
env = "prod"
}
dynamodb_read_capacity = 100
dynamodb_write_capacity = 50
We put whatever variables might need to be changed for the service or feature based on environment and/or region. For instance in a testing environment, the dynamodb capacity may be lower than in the production environment.

Terraform config file : Using variable inside a variable

I am tring to create a terraform configuration file to create an ec2 instance. I am using the variables.tf file to put all my variables. It works for most of the cases but there are two cases which I am not able to achieve. Any pointers is much appreciated.
1.using variable for aws instance name. Using var.service_name or "${service_name}" does not work.
resource "aws_instance" var.service_name {
ami = "ami-010fae13a16763bb4"
instance_type = "t2.micro"
.....
}
This post explains that resource name cannot be variables. But this is pretty old. Not sure if this is still the case.
2.Using variable inside another variable. For example I have connection parameters defined like this
connection {
host = "${aws_instance.terraformtest.public_ip}"
type = "ssh"
user = "ec2-user"
private_key = "${file("C:/Users/phani/Downloads/microservices.pem")}"
}
This works. I am using the ip generated from aws instance resource. But If i use it like this. It doesn't work
host = "${aws_instance.${service_name}.public_ip}"
Am I missing something or is there a workaround
Resource names in Terraform are required to be constants. In practice this usually isn't a problem, because a resource name only needs to be unique within a given module and so it shouldn't ever be necessary for a caller to customize what a resource is named in a child module. In the case where a particular resource is the primary object declared by a module, a common idiom is to name the resource "main", like resource "aws_instance" "main".
In situations where you need to declare several instances whose definitions share a common configuration, you can use for_each or count to produce multiple instances from a same resource block. In that case, the resource addresses include an additional "index" component, like aws_instance.example["foo"] or aws_instance.example[0], where the index can be a variable to dynamically select one of the instances produced by a single resource block.

Referring to resources named with variables in Terraform

I'm trying to create a module in Terraform that can be instantiated multiple times with different variable inputs. Within the module, how do I reference resources when their names depend on an input variable? I'm trying to do it via the bracket syntax ("${aws_ecs_task_definition[var.name].arn}") but I just guessed at that.
(Caveat: I might be going about this in completely the wrong way)
Here's my module's (simplified) main.tf file:
variable "name" {}
resource "aws_ecs_service" "${var.name}" {
name = "${var.name}_service"
cluster = ""
task_definition = "${aws_ecs_task_definition[var.name].arn}"
desired_count = 1
}
resource "aws_ecs_task_definition" "${var.name}" {
family = "ecs-family-${var.name}"
container_definitions = "${template_file[var.name].rendered}"
}
resource "template_file" "${var.name}_task" {
template = "${file("task-definition.json")}"
vars {
name = "${var.name}"
}
}
I'm getting the following error:
Error loading Terraform: Error downloading modules: module foo: Error loading .terraform/modules/af13a92c4edda294822b341862422ba5/main.tf: Error reading config for aws_ecs_service[${var.name}]: parse error: syntax error
I was fundamentally misunderstanding how modules worked.
Terraform does not support interpolation in resource names (see the relevant issues), but that doesn't matter in my case, because the resources of each instance of a module are in the instance's namespace. I was worried about resource names colliding, but the module system already handles that.
The picture below shows what is going on.
The terraform documentation does not make their use of "NAME" clear versus the "name" values that are used for the actual resources created by the infrastructure vender (like, AWS or Google Cloud).
Additionally, it isn't always "name=, but sometimes, say, "endpoint= or even "resource_group_name= or whatever.
And there are a couple of ways to generate multiple "name" values -- using count, variables, etc., or inside tfvar files and running terraform apply -var-file=foo.tfvars

Resources