I have a terraform map of set objects as given below
variable "BName" {
type = string
}
variable "type_names" {
type = map(object({
name = string
type = string
stream = string
grp = optional(string)
}))
}
I'm also trying to use the type_name in the following resource. How we can refer the value from map of sets during the resource creation
resource "azure_resource" "reso" {
id = az_resource.B_Name.id
name = var.type_names.name
}
resource "az_resource" "B_Name" {
name = var.BName
}
How I can pass this value in terraform.tfvars file to create following resource. Now I'm trying to pass the following way
BName = "Test"
type_names = {
name = "cooper"
type = "senior"
stream= "developer"
grp = "Mid"
}
{
name = "Mike"
type = "Junior"
stream= "tester"
grp = "entry"
}
For creating multiple resources with a single definition with a map(object) variable input, there exists a general algorithm and pattern. First, we need the variable structure definition from the declaration. For your example, we have:
variable "type_names" {
type = map(object({
name = string
type = string
stream = string
grp = optional(string)
}))
}
where I assume the use of the experimental feature for optional object keys given the appearance of the keyword optional. In this situation, it is helpful for the resource to have a descriptive identifier. That would most likely be the name, so we can remove it from the object value and refactor it to be the map key. Updated definition with an example default that conforms to the type definition would be:
variable "type_names" {
type = map(object({
type = string
stream = string
grp = optional(string)
}))
default = {
"cooper" = {
type = "senior"
stream = "developer"
grp = "Mid"
},
"Mike" = {
type = "Junior"
stream = "tester"
grp = "entry"
}
}
}
We can then easily use this to manage multiple resources with a single definition and input variable. Since the example resource in the question is using different keys than the provided variable possesses, we will modify the example resource slightly:
resource "azure_resource" "this" {
for_each = var.type_names
# value is the object, so we access object values with standard `.key` syntax
name = each.key
type = each.value.type
stream = each.value.stream
grp = each.value.grp
}
and the namespace of each resource will be azure_resource.this["<key>"] e.g. azure_resource.this["Mike"], and you can access its attributes normally.
Related
I want to allow any key to be set within a dictionary object and require Name to be set. Im passing this object into a variable that forces Name to be set but its ignoring all the other keys
tags = {
"Name" = "EC2_Name_Value" # Required
"AnyKey1" = "value1"
"AnyKey2" = "value2"
...
}
variable "tags" {
type = object({
Name = string
})
}
> var.tags
{
"Name" = "EC2_Name_Value"
}
I know that I'm able to use key = optional(string) however, i want to accept all extra keys and not have to define only the keys i want to accept.
What I would suggest is leaving the tags variable as is (maybe renaming it), and then using either an additional variable or local variables, e.g.:
variable "required_tag" {
type = object({
Name = string
})
}
variable "additional_tags" {
type = object(any)
}
Then, you would use the merge built-in function [1] to bring them all together:
tags = merge(var.required_tag, var.additional_tags)
Alternatively, since you know you will always need that one tag, you could switch it up a bit and remove the required_tag (or tags in your example) variable, and do something like:
tags = merge({ Name = "EC2_Name_Value" }, var.additional_tags)
Last, but not the least, there is an option to use default_tags on the provider level [2], but I am not sure if that fits your case.
[1] https://developer.hashicorp.com/terraform/language/functions/merge
[2] https://registry.terraform.io/providers/hashicorp/aws/latest/docs/guides/resource-tagging#propagating-tags-to-all-resources
I am trying to copy contents of a list and map into a file in terraform.
Here is my variables.tf file.
variable "fruits" {
type = list
description = "List Of Fruits"
default = [ "Apple" , "Banana" , "Mango" ]
}
variable "animals" {
type = map
description = "Map of animals"
default = {
herbivores = "sheep"
carnovore = "lion"
omnivore = "bear"
}
}
variable "birds" {
type = string
description = "A string for Bird"
default = "parrot"
}
variable "bool" {
default = "true"
}
variable "filenames" {
default = "Terraform_Basics.txt"
}
I am trying to create a resource type local_file which will have contents of the map and list values. Able to copy single value but not able to capture all the values at once to be copied to the file.
resource "local_file" "pet" {
filename = var.filenames
content = var.fruits[0]
}
I am new to terraform, can someone help in how I can achieve this.
To do that, there needs to be one very important change. Even if you manage to get it to work for all the elements, you would get the following error:
var.fruits is a list of dynamic, known only after apply
var.pets is a map of dynamic, known only after apply
For these kinds of tests, I usually suggest using local variables [1]. Their values are not dynamic and are known. So for the first part, I would change variable definitions to local variables:
locals {
fruits = ["Apple", "Banana", "Mango"]
pets = {
herbivores = "sheep"
carnovore = "lion"
omnivore = "bear"
}
filenames = "Terraform_Basics.txt"
}
Now, another thing to note is that you gave the file in the example you posted a logical name of pets while you were trying to output the values of fruits, which could lead to a lot of confusion down the road. Here is how your resource blocks should look like:
resource "local_file" "fruits" {
...
}
resource "local_file" "pets" {
...
}
I will explain the content that needs to be added to the resource blocks. You want to fetch all the elements of a map/list. For that, you can use the splat expression [2].If it is a list, then you can simply do:
resource "local_file" "fruits" {
filename = local.filenames
content = join(", ", local.fruits[*])
}
For a map, there is a slightly different syntax as you would use the values built-in function [3] which also returns a list, hence why the splat expression is required in this case as well:
resource "local_file" "pets" {
filename = local.filenames
content = join(", ", values(local.pets)[*])
}
Note that there is an additional built-in function you need to use and that is join [4]. If you were to try to pass only the local variable value in the content, you would get the following error:
Inappropriate value for attribute "content": string required.
If you are looking to further improve the code, my suggestion would be to create a different filename for pets and fruits because otherwise it will get overridden constantly:
locals {
fruits = ["Apple", "Banana", "Mango"]
pets = {
herbivores = "sheep"
carnovore = "lion"
omnivore = "bear"
}
filename_pets = "Terraform_Basics_Pets.txt"
filename_fruits = "Terraform_Basics_Fruits.txt"
}
And then, in the resources blocks:
resource "local_file" "fruits" {
filename = local.filename_fruits
content = join(", ", local.fruits[*])
}
resource "local_file" "pets" {
filename = local.filename_pets
content = join(", ", values(local.pets)[*])
}
[1] https://www.terraform.io/language/values/locals
[2] https://www.terraform.io/language/expressions/splat
[3] https://www.terraform.io/language/functions/values
[4] https://www.terraform.io/language/functions/join
I have a list of accounts declared under variable "acct". I need to create as many ARNs as the accounts below using this list. How to do it? The below code gives me attribute error:
A reference to a resource type must be followed by at least one attribute access, specifying the resource name.
variable "acct"{
type = list(string)
default = ["111111111111","22222222222",.....]
}
data "aws_arn" "SrcArn" {
account = [for acc in var.acct : acc ]
arn = ["arn:aws:logs:us-east-1:${account}:*"]
}
I need to create a list of arns which then can be used further down in the code. Can these then be referenced below like this:
condition {
test = "ArnLike"
values = data.aws_arn.SrcArn
variable = "aws:SourceArn"
}
If you want to use aws_arn, you can use to this.
variable "acct"{
type = list(string)
default = ["111111111111","22222222222",.....]
}
data "aws_arn" "SrcArn" {
count = var.acct
arn = ["arn:aws:logs:us-east-1:${var.acct[count.index]}:*"]
}
And if you wanna make arn_list,
locals {
# only use to variable.
# not use to data "aws_arn"
arn_list_only_use_to_variable = [for account in var.acct: "arn:aws:logs:us-east-1:${account}:*"]
# use to data "aws_arn"
src_arn = data.aws_arn.SrcArn
arn_list_2_use_to_data_aws_arn = [for account in src_arn: account]
}
...
condition {
test = "ArnLike"
values = local.arn_list_only_use_to_variable
variable = "aws:SourceArn"
}
...
I am trying to get the value of function name from local.tf but I am not able to get it. I have terraform,tfvars in which I am giving the function name then it is passed to variable.tf. From varibale.tf I pass it to local.tf then to main.tf. I am not able to get the function name in main.tf. Any help would be appreciated.
terraform.tfvars
config = {
s3= {
//s3 configurations
}
s3_notifications = {
function_name = "test-lambda-mary"
}
}
variable.tf
variable "config" {
type = any
description = "S3 configuration block"
}
local.tf
function_name = {
for k, v in var.config :
k => lookup(v, "function_name", "")
}
module "all_notifications" {
source = "terraform-aws-modules/s3-bucket/aws//modules/notification"
for_each = var.config
bucket = module.s3_bucket[each.key].this_s3_bucket_id
lambda_notifications = {
lambda = {
function_name = local.function_name[each.key]
function_arn = "arn:aws:lambda:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:function:${local.function_name[each.key]}"
events = ["s3:ObjectCreated:*"]
}
}
}
error
"function_name" doesn't comply with restrictions ("^(arn:[\\w-]+:lambda:)?([a-z]{2}-(?:[a-z]+-){1,2}\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?$"): ""
│
│ with module.all_notifications["s3"].aws_lambda_permission.allow["lambda"],
│ on .terraform/modules/all_notifications/modules/notification/main.tf line 63, in resource "aws_lambda_permission" "allow":
│ 63: function_name = each.value.function_name
If I put the function_name inside s3 braces it works absolutely fine but I need to have the fucntion name in s3_notification
That looks like a great hint. You're iterating over var.config which has 2 keys and only 1 of them has function_name defined. So when module is requested with s3 as a key, the function_value for that key will be empty string and AWS will fail the request as expected.
You can filter for_each = var.config to exclude such case, something like:
for_each = { for k, v in var.config: k => v if local.function_name[each.key] != ""}
Little nitpick: seems like the source of the module could be incorrectly written. Instead of terraform-aws-modules/s3-bucket/aws//modules/notification potentially it should be terraform-aws-modules/terraform-aws-s3-bucket//modules/notification. See https://github.com/terraform-aws-modules/terraform-aws-s3-bucket
A stab in the dark as I haven't used that module before.
But by looking at your error message:
"function_name" doesn't comply with restrictions ("^(arn:[\\w-]+:lambda:)?
it looks like for function_name you should pass Lambda's ARN, not name (contrary to what the variable name says).
BTW, is function_arn even a parameter here?
This error is coming since terraform module is expecting a valid and mandatory function name to create [aws_lambda_permission][1] resource using terraform-aws-modules/s3-bucket/aws//modules/notification module.
In your case you are looping the module on var.config which consist s3 and s3_notifications part and while first iteration its getting function name as null therefore its throwing this error.
"function_name" doesn't comply with restrictions ("^(arn:[\\w-]+:lambda:)?([a-z]{2}-(?:[a-z]+-){1,2}\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?$"): ""
Better split the variable specific to s3_notification as list and iterate the module as below using count.
variable "s3_config" {
type = any
default = {
s3 = {
}
}
}
variable "s3_notify_lambda_func" {
type = list
default = ["test-lambda-mary","test"]. #N Numbers of functions
}
data "aws_region" "current" {}
data "aws_caller_identity" "current" {}
module "all_notifications" {
source = "terraform-aws-modules/s3-bucket/aws//modules/notification"
count = length(var.s3_notify_lambda_func) > 0 ? length(var.s3_notify_lambda_func) : 0
bucket = module.s3_bucket[count.index].this_s3_bucket_id
lambda_notifications = {
lambda = {
function_name = var.s3_notify_lambda_func[count.index]
function_arn = "arn:aws:lambda:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:function:${var.s3_notify_lambda_func[count.index]}"
events = ["s3:ObjectCreated:*"]
}
}
}
I am having a hard time figuring out how to make an output for each target group resource that this code creates.
I'd like to be able to reference each one individually in other modules. It sounds like for_each stores it as a map, so my question is how would I get the arn for targetgroup1 and targetgroup2?
Terraform normally refers to outputs by resource name, so I am struggling with that in this scenario and also how to refer to these individual arns.
Would I also need to work the outputs into the for_each or could I drop it into the output.tf file?
locals {
target_groups_beta = {
targetgroup1 = {
name = "example",
path = "/",
environment = "Beta"
}
targetgroup2 = {
name = "example2",
path = "/",
environment = "Beta"
}
}
}
resource "aws_lb_target_group" "target-group" {
for_each = local.target_groups_beta
name = "example-${each.value.name}-"
port = 80
protocol = "HTTP"
vpc_id = var.vpc-id
deregistration_delay = 5
tags = {
Environment = "${each.value.environment}"
}
health_check{
healthy_threshold = 2
unhealthy_threshold = 2
timeout = 10
interval = 15
path = each.value.path
}
}
I receive the following error when trying to do it in the output.tf file without a key value, but when I input one such as value = "${aws_lb_target_group.target-group[0].arn}" it says it's invalid. Error without key value below:
Error: Missing resource instance key
on modules\targetgroups\output.tf line 2, in output "tg_example_beta":
2: value = "${aws_lb_target_group.target-group.arn}"
Because aws_lb_target_group.target-group has "for_each" set, its attributes
must be accessed on specific instances.
For example, to correlate with indices of a referring resource, use:
aws_lb_target_group.target-group[each.key]
The aws_lb_target_group.target-group generated will be a map, with key values of targetgroup2 and targetgroup1.
Therefore, to get the individual target group details you can do:
output "target-group1-arn" {
value = aws_lb_target_group.target-group["targetgroup1"].arn
}
To return both as a map:
output "target-groups-arn-alternatice" {
value = {for k, v in aws_lb_target_group.target-group: k => v.arn}
}
target-groups-arn-alternatice = {
"targetgroup1" = "arn:aws:elasticloadbalancing:us-east-1:xxxx:targetgroup/example-example/285b26e15221b113"
"targetgroup2" = "arn:aws:elasticloadbalancing:us-east-1:xxxx:targetgroup/example-example2/075bd58359e4c4b2"
}
To return both as a list (order will be same as for keys function):
output "target-groups-arn" {
value = values(aws_lb_target_group.target-group)[*].arn
}
target-groups-arn = [
"arn:aws:elasticloadbalancing:us-east-1:xxxx:targetgroup/example-example/285b26e15221b113",
"arn:aws:elasticloadbalancing:us-east-1:xxxx:targetgroup/example-example2/075bd58359e4c4b2",
]