I am using the following resource
resource "aws_cloudwatch_metric_alarm" "example" {
for_each = toset(var.myenvironments_map[var.environment])
...
db_instance_identifier_key = each.key
dimensions = {
DBInstanceIdentifier = db_instance_identifier
}
}
and wanted to do something like the following
locals {
`db_instance_identifier` = var.db_instance_identifier_key == "myKey" ?
"${avariable}" : "${aMap[var.db_instance_identifier_key].identifier}"
}
variable "db_instance_identifier_key" {
type = string
}
but when I run terraform plan I get: "The root module input variable "db_instance_identifier_key" is not set, and
│ has no default value. Use a -var or -var-file command line argument to
│ provide a value for this variable."
Basically I want to compute db_instance_identifier based on each.key from the for_each.
This is a local variable declaration:
locals {
`db_instance_identifier` = var.db_instance_identifier_key == "myKey" ?
"${avariable}" : "${aMap[var.db_instance_identifier_key].identifier}"
}
This is a module input variable declaration:
variable "db_instance_identifier_key" {
type = string
}
You need to use one or the other, not both.
Related
I am working within terraform and I am trying to combine variables.
I have been able to do this previously in the format of
name = "${var.name}-${var.environment}"
or something like
domain = "${var.environment}.${var.domain}"
Now I am trying to accomplish something similar but my module that I would like to do this with is utilizing a for_each.
I am trying to specify a host using variables that would represent utility.environment.domain.
Module:
module "aws_alb_listener_rule" {
depends_on = [
module.aws_lb_target_group,
module.aws_alb_listener_https
]
source = "../../terraform/modules/aws_lb_listener_rule_https"
listener_rule = module.aws_alb_listener__https.lb_listener
for_each = var.target_group_listener_rule_values
listener_rule_host_header = "${each.value.host_header}.${var.environment}.${var.domain}"
#listener_rule_host_header = each.value["host_header"]
listener_rule_target_group = module.aws_lb_target_group[each.key].arn
listener_rule_action_type = each.value["action_type"]
Where the first host header is what I was hoping to include but the commented out host header is how I currently have it.
My variables:
variable "target_group_listener_rule_values" {
description = "Specify the target group and listener rule settings and it will create on a 1:1 ratio"
type = map(object({
/*--- Listener Rule ---*/
host_header = list(string)
#host_header = string
target_group = string
action_type = string
my tfvars
listener_rule_values = {
Utility1 = {
/*--- Listener Rule ---*/
"host_header" = ["utility"],
#"host_header" = ["utility.environment.domain"],
"action_type" = "forward",
},
Utility2 = {
/*--- Listener Rule ---*/
"host_header" = ["utility"],
#"host_header" = ["utility.environment.domain"],
"action_type" = "forward",
},
Where the first host header is what I was hoping to include but the commented out host header is how I currently have it.
Child module:
resource "aws_lb_listener_rule" "static" {
listener_arn = var.listener_rule
action {
type = var.listener_rule_action_type
target_group_arn = var.listener_rule_target_group
}
condition {
host_header {
values = var.listener_rule_host_header
}
}
}
What I would like to do is shift this over to use variables instead as I am trying to remove the environment from being entered anywhere in the config except for the "environment" variable.
listener_rule_host_header = "${each.value["host_header"]}.${var.environment}.${var.domain}"
the error I am seeing is
Error: Invalid template interpolation value
│
│ on main.tf line 181, in module "module":
│ 181: listener_rule_host_header = "${each.value["host_header"]}.${var.environment}.${var.domain}"
│ ├────────────────
│ │ each.value["host_header"] is list of string with 1 element
│
│ Cannot include the given value in a string template: string required.
you are very close and your syntax for interpolation is correct. What is not correct is the fact that your each.value["host_header"] is a list, so Terraform complains about that.
I don't know why you want a list, and You did not give your full code, so let me show you a working example with map, that I imagine you want to accomplish:
variable "environment" {
default = "stage"
}
variable "domain" {
default = "example.com"
}
variable "sites" {
type = map(any)
default = {
"utils" = {
"host_header" = "utility"
}
"main" = {
"host_header" = "www"
}
}
}
resource "local_file" "hosts" {
for_each = var.sites
filename = "${each.value.host_header}.${var.environment}.${var.domain}"
content = each.key
}
You can adjust your code based on this example, I hope.
Now, if you insist on using lists, this would look like this:
variable "sites_with_list" {
type = map(any)
default = {
"utils" = {
"host_headers" = ["utility"]
}
"main" = {
"host_headers" = ["www"]
}
}
}
resource "local_file" "hosts_list" {
for_each = var.sites_with_list
filename = "${each.value.host_headers[0]}.${var.environment}.${var.domain}"
content = each.key
}
I did not know how you want to utilize this, so I used a local_file just as an example that will work when copied to empty project.
Terraform v1.0.0
Provider: aws v3.49.0
I created dynamic AWS subnets resources with a for_each from a module.
The resources creation is working fine, however being able to output dynamically created resources is not working and cannot find proper documentation for it.
The subnet module is
resource "aws_subnet" "generic" {
vpc_id = var.vpc_id
cidr_block = var.cidr_block
map_public_ip_on_launch = var.public_ip_on_launch
tags = {
Name = var.subnet_tag_name
Environment = var.subnet_environment
}
}
With simple module output defined
output "subnet_id" {
value = aws_subnet.generic.id
}
Then from root module, I am creating a for_each loop over a list variable to create multiple dynamic resources from the module
module "subnets" {
source = "../modules/networking/subnet"
for_each = var.subnets
vpc_id = "vpc-09d6d4c17544f3a49"
cidr_block = each.value["cidr_block"]
public_ip_on_launch = var.public_ip_on_launch
subnet_environment = var.subnet_environment
subnet_tag_name = each.value["subnet_tag_name"]
}
When I run this without defining outputs in the root module, things get created normally. The problem comes when I try to define the outputs
output "subnets" {
value = module.subnets.*.id
description = "Imported VPC ID"
}
It comes up with this error
│ Error: Unsupported attribute
│
│ on output.tf line 2, in output "subnets":
│ 2: value = module.subnets.*.id
│
│ This object does not have an attribute named "id".
I tried different output definitions. Would appreciate guidance on how to properly define outputs of instances dynamically created with a for_each module.
Per the Terraform documentation, the "splat" operator (*) can only be used with lists, and since you're using for_each your output will be a map.
You need to use map/list comprehension to achieve what you want.
For an output that is a map of key/value pairs (note that I've changed the output description to something that makes more sense):
output "subnets" {
value = {
for k, v in module.subnets:
k => v.subnet_id
}
description = "Subnet IDs"
}
For a list that only contains the subnet IDs:
output "subnets" {
value = [
for k, v in module.subnets:
v.subnet_id
]
description = "Subnet IDs"
}
I am optimizing my terraform code by using modules. When i create a resource group module it works perfectly well but it creates two resource groups
i.e.
Temp-AppConfiguration-ResGrp
Temp-AppServices-ResGrp
instead it should only create
Temp-AppConfiguration-ResGrp
Code Resourcegroup.tf.
resource "azurerm_resource_group" "resource" {
name = "${var.environment}-${var.name_apptype}-ResGrp"
location = var.location
tags = {
environment = var.environment
}
}
output "resource_group_name" {
value = "${var.environment}-${var.name_apptype}-ResGrp"
}
output "resource_group_location" {
value = var.location
}
Variable.tf
variable "name_apptype" {
type = string
default = "AppServices"
}
variable "environment" {
type = string
default = "Temp"
}
variable "location" {
type = string
default = "eastus"
}
Main.tf
module "resourcegroup" {
source = "../Modules"
name_apptype = "AppConfiguration"
}
I want to pass name_apptype in main.tf when calling resource group module. So that i don't need to update variable.tf every time.
Any suggestions
where i am doing wrong. Plus i am also unable to output the value, i need it so that i could pass resource group name in the next module i want to create.
Thanks
You need to do that in the Main.tf
module "resourcegroup" {
source = "../Modules"
name_apptype = "AppConfiguration"
}
module "resourcegroup-appservices" {
source = "../Modules"
name_apptype = "AppServices"
}
These create a 2 resources groups with the values that you need, additionally you can remove the default value from the name_apptype variable.
If you want to create with the same module both resource groups you need to use count to iterate over an array of names
I'm getting the below error while running terraform plan and apply
on main.tf line 517, in resource "aws_lb_target_group_attachment" "ecom-tga":
│ 517: for_each = local.service_instance_map
│ ├────────────────
│ │ local.service_instance_map will be known only after apply
│
│ The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will
│ be created. To work around this, use the -target argument to first apply only the resources that the for_each depends on.
My configuration file is as below
variable "instance_count" {
type = string
default = 3
}
variable "service-names" {
type = list
default = ["valid","jsc","test"]
}
locals {
helper_map = {for idx, val in setproduct(var.service-names, range(var.instance_count)):
idx => {service_name = val[0]}
}
}
resource "aws_instance" "ecom-validation-service" {
for_each = local.helper_map
ami = data.aws_ami.ecom.id
instance_type = "t3.micro"
tags = {
Name = "${each.value.service_name}-service"
}
vpc_security_group_ids = [data.aws_security_group.ecom-sg[each.value.service_name].id]
subnet_id = data.aws_subnet.ecom-subnet[each.value.service_name].id
}
data "aws_instances" "ecom-instances" {
for_each = toset(var.service-names)
instance_tags = {
Name = "${each.value}-service"
}
instance_state_names = ["running", "stopped"]
depends_on = [
aws_instance.ecom-validation-service
]
}
locals {
service_instance_map = merge([for env, value in data.aws_instances.ecom-instances:
{
for id in value.ids:
"${env}-${id}" => {
"service-name" = env
"id" = id
}
}
]...)
}
resource "aws_lb_target_group_attachment" "ecom-tga" {
for_each = local.service_instance_map
target_group_arn = aws_lb_target_group.ecom-nlb-tgp[each.value.service-name].arn
port = 80
target_id = each.value.id
depends_on = [aws_lb_target_group.ecom-nlb-tgp]
}
Since i'm passing count as var and its value is 3,i thought terraform will predict as it needs to create 9 instances.But it didn't it seems and throwing error as unable to predict.
Do we have anyway to by pass this by giving some default values for instances count prediction or for that local service_instance_map?
Tried try function but still no luck
Error: Invalid for_each argument
│
│ on main.tf line 527, in resource "aws_lb_target_group_attachment" "ecom-tga":
│ 527: for_each = try(local.service_instance_map,[])
│ ├────────────────
│ │ local.service_instance_map will be known only after apply
│
│ The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will
│ be created. To work around this, use the -target argument to first apply only the resources that the for_each depends on.
My requirement got changed and now i have to create 3 instances in 3 subnets available in that region.I changed the locals as like below But same prediction issue
locals {
merged_subnet_svc = try(flatten([
for service in var.service-names : [
for subnet in aws_subnet.ecom-private.*.id : {
service = service
subnet = subnet
}
]
]), {})
variable "azs" {
type = list(any)
default = ["ap-south-1a", "ap-south-1b", "ap-south-1c"]
}
variable "private-subnets" {
type = list(any)
default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
}
resource "aws_instance" "ecom-instances" {
for_each = {
for svc in local.merged_subnet_svc : "${svc.service}-${svc.subnet}" => svc
}
ami = data.aws_ami.ecom.id
instance_type = "t3.micro"
tags = {
Name = "ecom-${each.value.service}-service"
}
vpc_security_group_ids = [aws_security_group.ecom-sg[each.value.service].id]
subnet_id = each.value.subnet
}
}
In your configuration you've declared that data "aws_instances" "ecom-instances" depends on aws_instance.ecom-validation-service. Since that other object won't exist yet on your first run, Terraform must therefore wait until the apply step to read data.aws_instances.ecom-instances because otherwise it would fail to honor the dependency you've declared, because aws_instance.ecom-validation-service wouldn't exist yet.
To avoid the error message you saw here, you need to make sure that for_each only refers to values that Terraform will know before any objects are actually created. Because EC2 assigns instance ids only once the instance is created, it's not correct to use an EC2 instance id as part of a for_each instance key.
Furthermore, there's no need for a data "aws_instances" block to retrieve instance information here because you already have the relevant instance information as a result of the resource "aws_instance" "ecom-validation-service" block.
With all of that said, let's start from your input variables and build things up again while making sure that we only build instance keys only from values we'll know during planning. The variables you have stay essentially the same; I've just tweaked the type constraints a little to match how we're using each one:
variable "instance_count" {
type = string
default = 3
}
variable "service_names" {
type = set(string)
default = ["valid", "jsc", "test"]
}
I understand from the rest of your example that you are intending to create var.instance_count instances for each distinct element of var.service_names. Your setproduct to produce all of the combinations of those is also good, but I'm going to tweak it to assign the instances unique keys that include the service name:
locals {
instance_configs = tomap({
for pair in setproduct(var.service_names, range(var.instance_count)) :
"${pair[0]}${pair[1]}" => {
service_name = pair[0]
}
})
}
This will produce a data structure like the following:
{
valid0 = { service_name = "valid" }
valid1 = { service_name = "valid" }
valid2 = { service_name = "valid" }
jsc0 = { service_name = "jsc" }
jsc1 = { service_name = "jsc" }
jsc2 = { service_name = "jsc" }
test0 = { service_name = "test" }
test1 = { service_name = "test" }
test2 = { service_name = "test" }
}
This matches the shape that for_each expects, so we can use it directly to declare nine aws_instance instances:
resource "aws_instance" "ecom-validation-service" {
for_each = local.instance_configs
instance_type = "t3.micro"
ami = data.aws_ami.ecom.id
subnet_id = data.aws_subnet.ecom-subnet[each.value.service_name].id
vpc_security_group_ids = [
data.aws_security_group.ecom-sg[each.value.service_name].id,
]
tags = {
Name = "${each.value.service_name}-service"
Service = each.value_service_name
}
}
So far this has been mostly the same as what you shared. But this is the point where I'm going to go in a totally different direction: rather than now trying to read back the instances this declared using a separate data resource, I'll just gather the same data directly from the aws_instance.ecom-validation-service resource. It's generally best for a Terraform configuration to either manage a particular object or read it, not both at the same time, because this way the necessary dependency ordering is revealed automatically be the references.
Notice that I included an extra tag Service on each of the instances to give a more convenient way to get the service name back. If you can't do that then you could get the same information by trimming the -service suffix from the Name tag, but I prefer to keep things direct where possible.
It seemed like your goal then was to have a aws_lb_target_group_attachment instance per instance, with each one connected to the appropriate target group based on the service name. Because that aws_instance resource has for_each set, aws_instance.ecom-validation-service in expressions elsewhere is a map of objects where the keys are the same as the keys in var.instance_configs. That means that value is also compatible with the requirements for for_each and so we can use it directly to declare the target group attachments:
resource "aws_lb_target_group_attachment" "ecom-tga" {
for_each = aws_instance.ecom-validation-service
target_group_arn = aws_lb_target_group.ecom-nlb-tgp[each.value.tags.Service].arn
port = 80
target_id = each.value.id
}
I relied on the extra Service tag from earlier to easily determine which service each instance belongs to in order to look up the appropriate target group ARN. each.value.id works here because each.value is always an aws_instance object, which exports that id attribute.
The result of this is two sets of instances that each have keys matching those in local.instance_configs:
aws_instance.ecom-validation-service["valid0"]
aws_instance.ecom-validation-service["valid1"]
aws_instance.ecom-validation-service["valid2"]
aws_instance.ecom-validation-service["jsc0"]
aws_instance.ecom-validation-service["jsc1"]
aws_instance.ecom-validation-service["jsc2"]
...
aws_lb_target_group_attachment.ecom-tga["valid0"]
aws_lb_target_group_attachment.ecom-tga["valid1"]
aws_lb_target_group_attachment.ecom-tga["valid2"]
aws_lb_target_group_attachment.ecom-tga["jsc0"]
aws_lb_target_group_attachment.ecom-tga["jsc1"]
aws_lb_target_group_attachment.ecom-tga["jsc2"]
...
Notice that all of these keys contain only information specified directly in the configuration, and not any information decided by the remote system. That means we avoid the "Invalid for_each argument" error even though each instance still has an appropriate unique key. If you were to add a new element to var.service_names or increase var.instance_count later then Terraform will also see from the shape of these instance keys that it should just add new instances of each resource, rather than renaming/renumbering any existing instances.
I am trying to pass a variable from the root module to a child module with the following syntax and i'm unable to do that:
└── Terraform
├── main.tf
├── variable.tf
└── module
├──main.tf
├── variable.tf
Terraform Version:
Terraform v0.11.11
+ provider.openstack v1.15.0
Terraform Configuration Files
/Terraform/main.tf:
provider "openstack" {
openstack_user_name = "${var.openstack_user_name}"
openstack_tenant_name = "${var.openstack_tenant_name}"
openstack_password = "${var.openstack_password}"
openstack_auth_url = "${var.openstack_auth_url}"
domain_name = "${var.domain_name}"
}
module "testMod" {
name = "${var.name}"
imageId = "${var.imageId}"
flavor_name = "${var.flavor_name}"
openstack_keypair = "${var.openstack_keypair}"
tenant_network_id = "${var.tenant_network_id}"
tenant_network = "${var.tenant_network}"
source = "./modules"
}
/Terraform/variable.tf:
variable "name" {default = "XXX"}
variable "imageId" {default = "11-22-33"}
variable "flavor_name"{default = "flavor"}
...
/Terraform/modules/main.tf:
resource "openstack_compute_instance_v2" "test" {
name = "${var.name}"
imageId = "${var.imageId}"
flavor_name = "${var.flavor_name}"
openstack_keypair = "${var.openstack_keypair}"
security_groups = ["default"]
network {
tenant_network_id = "${var.tenant_network_id}"
tenant_network = "${var.tenant_network}"
}
}
/Terraform/modules/variable.tf:
variable "name" {}
variable "imageId" {}
variable "flavor_name" {}
variable "openstack_keypair" {}
variable "tenant_network_id"{}
variable "tenant_network" {}
Actual Behavior
Error: module.testMod.openstack_compute_instance_v2.test: : invalid or unknown key: imageId
Steps to Reproduce
terraform init
terraform apply
Unsure what is going wrong here
The error is alerting you to the unknown keyimageId. This message is accurate as, in fact, the key should be image_id. You can check the Terraform openstack_compute_instance_v2 resource documentation and note the presence of the image_id argument.
Your code would then look like:
resource "openstack_compute_instance_v2" "test" {
name = "${var.name}"
image_id = "${var.imageId}"
flavor_name = "${var.flavor_name}"
openstack_keypair = "${var.openstack_keypair}"
security_groups = ["default"]
}
I have this requirement to access variables from root variable.tf inside module/myservice/main.tf
How can we achive that ? By using var.environmentdoes not work
e.g:
root/variable.tf
variable "environment" {
type = string
description = "Resource environment e.g dev, test or prod."
default = "dev"
}
How to access above variable var.environment in root/module/myservice/main.tf using ${var.environment} does not work.
root/module/myservice/main.tf
locals {
name = "${var.environment}.myInstance"
}
resource "aws_network_interface" "foo" {
name = local.name
.
.
.
}