terraform use count index in module [duplicate] - terraform

This question already has answers here:
Variance in attributes based on count.index in terraform
(3 answers)
Closed 3 years ago.
I want to use the count.index in the terraform module for my aws ec2 instance to name the instance in increment order
file: ec2/main.tf
resource "aws_instance" "instance"{
ami = "ami-xxx"
tags {
Name = "var.instance"
}
count = "var.count"
}
file: ec2instance.tf
module "ec2"{
source = "./ec2"
count = 3
instance_name = "firsttypeinstance-${count.index+1}"
}
module "ec20"{
source = "./ec2"
count = 2
instance_name = "secondtype-${count.index+1}"
}
I want the instance name to be populated as
firsttypeinstance-1
firsttypeinstance-2
firsttypeinstance-3
secondtype-1
secondtype-2
but i get the error that i cannot use count index in the module

From terraform doc:
In addition to the above, the argument names count, for_each and lifecycle are not currently used by Terraform but are reserved for planned future features.
however, you could create the my_count variable in your module and use it on resources inside your module
module ec2
resource "aws_instance" "instance"{
ami = "ami-xxx"
tags {
Name = "var.instance-${count.index}"
}
count = "var.my_count"
}
module main
module "ec2"{
source = "./ec2"
my_count = 3
instance_name = "firsttypeinstance" ## actually instance prefix
}

Related

condition on terraform module

Trying to run modules conditionally.
Expectation : Run module only when env is not equal to prd
module "database_diagnostic_eventhub_setting" {
count = var.env != "prd" ? 1 : 0 // run block if condition is satisfied
source = "git::https://git_url//modules/...."
target_ids = [
"${data.terraform_remote_state.database.outputs.server_id}"
]
environment = "${var.environment}-database-eventhub"
destination = data.azurerm_eventhub_namespace_authorization_rule.event_hub.id
eventhub_name = var.eventhub_name
logs = [
"PostgreSQLLogs",
"QueryStoreWaitStatistics"
]
}
Error:
The name "count" is reserved for use in a future version of Terraform.
You need to use Terraform v0.13 or later in order to use count or for_each inside a module block.
If you can't upgrade from Terraform v0.12 then the old approach, prior to support for module repetition, was to add a variable to your module to specify the object count:
variable "instance_count" {
type = number
}
...and then inside your module add count to each of the resources:
resource "example" "example" {
count = var.instance_count
}
However, if you are able to upgrade to Terraform v0.13 now then I would strongly suggest doing so rather than using the above workaround, because upgrading to use module-level count later, with objects already created, is quite a fiddly process involving running terraform state mv for each of your resource in that module.

Conditionally create a single module in Terraform

I have been trying to conditionally use a module from the root module, so that for certain environments this module is not created. Many people claim that by setting the count in the module to either 0 or 1 using a conditional does the trick.
module "conditionally_used_module" {
source = "./modules/my_module"
count = (var.create == true) ? 1 : 0
}
However, this changes the type of conditionally_used_module: instead of an object (or map) we will have a list (or tuple) containing a single object. Is there another way to achieve this, that does not imply changing the type of the module?
To conditionally create a module you can use a variable, lets say it's called create_module in the variables.tf file of the module conditionally_used_module.
Then for every resource inside the conditionally_used_module module you will use the count to conditionally create or not that specific resource.
The following example should work and provide you with the desired effect.
# Set a variable to know if the resources inside the module should be created
module "conditionally_used_module" {
source = "./modules/my_module"
create_module = var.create
}
# Inside the conditionally_used_module file
# ( ./modules/my_module/main.tf ) most likely
# for every resource inside use the count to create or not each resource
resource "resource_type" "resource_name" {
count = var.create_module ? 1 : 0
... other resource properties
}
I used this in conjunction with workspaces to build a resource only for certain envs. The advantage is for me that I get a single terraform.tfvars file to control the all the environments structure for a project.
Inside main.tf:
workspace = terraform.workspace
#....
module "gcp-internal-lb" {
source = "../../modules/gcp-internal-lb"
# Deploy conditionally based on deploy_internal_lb variable
count = var.deploy_internal_lb[local.workspace] == true ? 1 : 0
# module attributes here
}
Then in variables.tf
variable "deploy_internal_lb" {
description = "Set to true if you want to create an internal LB"
type = map(string)
}
And in terraform.tfvars:
deploy_internal_lb = {
# DEV
myproject-dev = false
# QA
myproject-qa = false
# PROD
myproject-prod = true
}
I hope it helps.

Terraform : How to loop over aws_instance N times as defined within object

I have the following variable
variable "instance_types" {
default = {
instances : [
{
count = 1
name = "control-plane"
ami = "ami-xxxxx"
instance_type = "t2.large"
iam_instance_profile = "xxx-user"
subnet_id = "subnet-xxxxx"
},
{
count = 3
name = "worker"
ami = "ami-xxxxx"
instance_type = "t2.large"
iam_instance_profile = "xxx-user"
subnet_id = "subnet-xxxxx"
}
]
}
}
With the following instance declaration (that I'm attempting to iterate)
resource "aws_instance" "k8s-node" {
# Problem here : How to turn an array of 2 objects into 4 (1 control_plane, 3 workers)
for_each = {for x in var.instance_types.instances: x.count => x}
ami = lookup(each.value, "ami")
instance_type = lookup(each.value, "instance_type")
iam_instance_profile = lookup(each.value, "iam_instance_profile")
subnet_id = lookup(each.value, "subnet_id")
tags = {
Name = lookup(each.value, "name")
Type = each.key
}
}
Goal: Get the aws_instance to iterate 4 times (1 control_plane + 3 workers) and populate the values the index of instance_types.
Problem : Cannot iterate the over the object array correctly with desired result. In a typical programming language this would be achieved in a double for loop.
This can be solved easier with a data type of map(object)) for your input variable. The transformed data structure appears like:
variable "instance_types" {
...
default = {
"control-plane" = {
count = 1
ami = "ami-xxxxx"
instance_type = "t2.large"
iam_instance_profile = "xxx-user"
subnet_id = "subnet-xxxxx"
},
"worker" = {
count = 3
ami = "ami-xxxxx"
instance_type = "t2.large"
iam_instance_profile = "xxx-user"
subnet_id = "subnet-xxxxx"
}
}
}
Note the name key in the object is subsumed into the map key for efficiency and cleanliness.
If the resources are split between the control plane and worker nodes, then we are finished and can immediately leverage this variable's value in a for_each meta-argument. However, combining the resources now requires a data transformation:
locals {
instance_types = flatten([ # need this for final structure type
for instance_key, instance in var.instance_types : [ # iterate over variable input objects
for type_count in range(1, instance.count + 1) : { # sub-iterate over objects by "count" value specified; use range function and begin at 1 for human readability
new_key = "${instance_key} ${type_count}" # for resource uniqueness
type = instance_key # for easier tag value later
ami = instance.ami # this and below retained from variable inputs
instance_type = instance.instance_type
iam_instance_profile = instance.iam_instance_profile
subnet_id = instance.subnet_id
}
]
])
}
Now we can iterate within the resource with the for_each meta-argument, and utilize the for expression to reconstruct the input for suitable usage within the resource.
resource "aws_instance" "k8s-node" {
# local.instance_types is a list of objects, and we need a map of objects with unique resource keys
for_each = { for instance_type in local.instance_types : instance_type.new_key => instance_type }
ami = each.value.ami
instance_type = each.value.instance_type
iam_instance_profile = each.value.iam_instance_profile
subnet_id = each.value.subnet_id
tags = {
Name = each.key
Type = each.value.type
}
}
This will give you the behavior you desire, and you can modify it for style preferences or different uses as the need arises.
Note the lookup functions are removed since they are only useful when default values are specified as a third argument, and that is not possible in object types within variable declarations except as an experimental feature in 0.14.
The absolute namespace for these resources' exported resource attributes would be:
(module?.<declared_module_name>?.)<resource_type>.<resource_name>[<resource_key>].<attribute>
For example, given an intra-module resource, first worker node, and private ip address exported attribute:
aws_instance.k8s-node["worker 1"].private_ip
Note you can also access all resources' exported attributes by terminating the namespace at <resource_name> (retaining the map of all resources instead of accessing a singular resource value). Then you could also use a for expression in an output declaration to create a custom aggregate output for all of the similar resources and their identical exported attribute(s).
{ for node_key, node in aws_instance.k8s-node : node_key => node.private_ip }

How do I assign unique "Name" tag to the EC2 instance(s)?

I am using Terraform 0.12. I am trying to build EC2 in bulk for a project and instead of sequentially naming the ec2's I will to name the instances by providing unique names.
I think of using dynamic tags, however, not quite sure how to incorporate in the code.
resource "aws_instance" "tf_server" {
count = var.instance_count
instance_type = var.instance_type
ami = data.aws_ami.server_ami.id
associate_public_ip_address = var.associate_public_ip_address
##This provides sequential name.
tags = {
Name = "tf_server-${count.index +1}"
}
key_name = "${aws_key_pair.tf_auth.id}"
vpc_security_group_ids = ["${var.security_group}"]
subnet_id = "${element(var.subnets, count.index)}"
}
If I understand your requirement correctly, you can pass the list of VM names as a terraform variable and use count.index to get the name from a specific position in the list based on the count.
# variables.tf
# Length of list should be the same as the count of instances being created
variable "instance_names" {
default = ["apple", "banana", "carrot"]
}
#main.tf
resource "aws_instance" "tf_server" {
count = var.instance_count
instance_type = var.instance_type
ami = data.aws_ami.server_ami.id
associate_public_ip_address = var.associate_public_ip_address
##This provides names as per requirement from the list.
tags = {
Name = "${element(var.instance_names, count.index)}"
}
key_name = "${aws_key_pair.tf_auth.id}"
vpc_security_group_ids = ["${var.security_group}"]
subnet_id = "${element(var.subnets, count.index)}"
}
Would the following be similar to what you are after?
Define a list of name prefixes as a variable and then cycle through the naming prefixes using the element function.
variable "name_prefixes" {
default = ["App", "Db", "Web"]
}
...
##This provides sequential name.
tags = {
Name = "${element(var.name_prefixes, count.index)}${count.index + 1}"
}
...
The result would be App1, Db2, Web3, App4, Db5... The numbering is not ideal, but at least you would have a distinct name per instance.
The only way I can think of naming them sequentially (e.g. App1, App2, Db1, Db2 etc.) would require an individual resource for each type of instance and then just use count.index on the name like your original code.

Terraform using count.index in a resource name

using terraform i'm trying to include the count in the name of my resource using count.index, but unable to get the count to display. I'm basically looking to add the count to the resource name so that the resource can be found, otherwise the resource is unknown.
count = 3
autoscaling_group_name = "${aws_autoscaling_group.exampleautoscaling-("count.index")-example.name}"
ERROR
resource variables must be three parts: TYPE.NAME.ATTR in:
expected is : exampleautoscaling-1-example.name,exampleautoscaling-2-example.name,exampleautoscaling-3-example.name
My suggestion will be to add tags and use name_prefix arguments. But specific to your question
Here are some snippets from the documentation what you can try
"${var.hostnames[count.index]}"
OR
resource "aws_instance" "web" {
# ...
count = "${var.count}"
# Tag the instance with a counter starting at 1, ie. web-001
tags {
Name = "${format("web-%03d", count.index + 1)}"
}
}
Providing the link here. Look under the Math section.
Your syntax is incorrect. You are trying to insert count into the middle of a resource name. You need to change it to be the following:
count = 3
autoscaling_group_name = "${aws_autoscaling_group.exampleautoscaling.name}-${count.index}"

Resources