Terraform config file : Using variable inside a variable - terraform

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.

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
}

Why resource provides has two labels - 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. :)

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.

Terraform chicken/egg problem using aws_vpc data source in root module

I have a root Terraform module that declares a VPC module and other modules such as an EC2 instance that is to launch in the VPC.
In the EC2 module, I read the VPC using the aws_vpc type:
data "aws_vpc" "vpc" {
filter {
name = "tag:Name"
values = [var.name_tag]
}
}
Now this works fine if I declare the modules independently.
But when declaring a root module that declares these other modules separately, I get this failure:
▶ terraform apply
module.cloudwatch.data.aws_ami.ami: Refreshing state...
module.backend.data.aws_vpc.vpc: Refreshing state...
module.backend.data.aws_ami.ami: Refreshing state...
Error: no matching VPC found
on .terraform/modules/backend/main.tf line 1, in data "aws_vpc" "vpc":
1: data "aws_vpc" "vpc" {
So there is a chicken/egg problem here.
I am confused. How can this ever work? If a root module cannot both declare a VPC and then use the aws_vpc data source later to read it into other modules, what is the use of these data sources? I would appreciate advice on the best practice here. Should I simply not use aws_vpc and instead read in the VPC ID as an output elsewhere?
To me this sounds like you are declaring both a resource like
resource "aws_vpc" "example" {}
AND data-provider like
data "aws_vpc" "example" {}
in order to access something from the data like data.aws_vpc.example.arn. This is not needed and in fact is causing your error. If both is in the same terraform state, you can simply drop the data "aws_vpc" "example" {} and refer to the resource by e.g. resource.aws_vpc.example.arn.
The data provider is actually only needed in cases in which you are referring to a resource that is created somewhere else like something created manually, by a different provisioning engine (or also by terraform, but in a different layer).
You have not mentioned your query very specifically.
As far as I understand from your question, you have declared VPC in root module and want to use it's id or arn, etc from this, right?
So, in this scenario, you must have specify the perfect path of your vpc module in (.) format (e.g. module.root.aws_vpc.vpc_name.id) and also you can use
depends_on = [your vpc ] in your data resource declaration.
NOTE: I don't have required reputation here to suggest you these things as in comments, I may also loos my reputations here by answering you as a answer here.
Request: Please mention more details here, also paste your module tree structure for better understanding.
you can refer this link : https://www.terraform.io/docs/providers/aws/d/vpc.html
The data provider is actually only needed in cases in which you are referring to a resource that is created somewhere else
An example code that is correct.
provider "aws" {
region = "us-east-2"
}
data "aws_vpc" "example" {
filter {
name = "tag:Name"
values = ["TRIAL"]
}
}
output "show-me-vpc" {
value = data.aws_vpc.example.arn
}

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