I like to do a dynamic function_assocation to an AWS CloudFront resource. Instead of defining each function, I did something like the below.
resource "aws_cloudfront_function" "functions" {
for_each = var.cf_lambda_functions
name = each.value.name
comment = each.value.description
runtime = each.value.runtime
publish = each.value.publish
code = each.value.code
}
and for the function_association, I did something like the below.
dynamic "function_association" {
for_each = aws_cloudfront_function.functions
content {
event_type = "viewer-request"
function_arn = each.value.arn
}
}
this gives me an error: each.value cannot be used in this context. How do you do this by passing multiple ARN of functions?
In dynamic blocks you can't use each. Instead it should be function_association in your case:
dynamic "function_association" {
for_each = aws_cloudfront_function.functions
content {
event_type = "viewer-request"
function_arn = function_association.value.arn
}
}
Related
I have the following code which works fine.
resource "aws_ses_email_identity" "main_from_email" {
email = "some#email.com"
}
data "aws_iam_policy_document" "main_from_email_policy_document" {
statement {
actions = ["SES:SendEmail", "SES:SendRawEmail"]
resources = [aws_ses_email_identity.main_from_email.arn]
principals {
identifiers = ["*"]
type = "AWS"
}
}
}
resource "aws_ses_identity_policy" "email_notif_policy" {
identity = aws_ses_email_identity.main_from_email.arn
name = "${local.namespace}-ses_main_from_email_policy"
policy = data.aws_iam_policy_document.main_from_email_policy_document.json
}
The above code is working fine. But the email is hard coded. I would like to have the (email) resources to be created based on some configuration provided in terraform.tfvars as follows:
clientemails = {
"client1" = { "email" = "client1#gmail.com" }
"client2" = { "email" = "client2#gmail.com" }
}
I modified main resource as follows:
resource "aws_ses_email_identity" "main_from_email_map" {
for_each = var.clientemails
email = each.value.email
}
But, I don't know how I can modify "aws_iam_policy_document" and "aws_ses_identity_policy" to follow "aws_ses_email_identity".
How do I modify my terraform script to honor "clientemails" configuration?
This should be relatively straight forward to do:
resource "aws_ses_email_identity" "main_from_email_map" {
for_each = var.clientemails
email = each.value.email
}
data "aws_iam_policy_document" "main_from_email_policy_document" {
statement {
actions = ["SES:SendEmail", "SES:SendRawEmail"]
resources = values(aws_ses_email_identity.main_from_email_map)[*].arn
principals {
identifiers = ["*"]
type = "AWS"
}
}
}
resource "aws_ses_identity_policy" "email_notif_policy" {
for_each = aws_ses_email_identity.main_from_email_map
identity = each.value.arn
name = "${local.namespace}-ses_main_from_email_policy"
policy = data.aws_iam_policy_document.main_from_email_policy_document.json
}
Here, there will be a single policy which will allow all the same actions for all the SES email identity ARNs. To achieve this, since the SES email identity resource was created using for_each meta-argument, the values built-in function [1] was used to fetch all the values of the ARN attribute for all the keys (hence the [*] part).
There could be a way to modify the data source with for_each if needed (i.e., to create a policy per client email):
data "aws_iam_policy_document" "main_from_email_policy_document" {
for_each = aws_ses_email_identity.main_from_email_map
statement {
actions = ["SES:SendEmail", "SES:SendRawEmail"]
resources = [each.value.arn]
principals {
identifiers = ["*"]
type = "AWS"
}
}
}
Then, you would also have to fix the SES identity policy:
resource "aws_ses_identity_policy" "email_notif_policy" {
for_each = aws_ses_email_identity.main_from_email_map
identity = each.value.arn
name = "${local.namespace}-ses_main_from_email_policy"
policy = data.aws_iam_policy_document.main_from_email_policy_document[each.key].json
}
The code where for_each meta-argument is used with the resources (e.g., aws_ses_email_identity.main_from_email_map) is called for_each chaining [2].
[1] https://developer.hashicorp.com/terraform/language/functions/values
[2] https://developer.hashicorp.com/terraform/language/meta-arguments/for_each#chaining-for_each-between-resources
Using a module to create multiple IAM roles with a for_each block I am trying to pass into the policy a rendered data output and the key from the for_each loop. The name of this policy will be slightly different for each role
module "sso_roles" {
source = "git::ssh://git#gitlab.com/iam/role?ref=1.1.0"
for_each = local.roles
policy = "${data.template_file}.${each.key}_policy".rendered
role_name = each.key
assume_role_policy_def = data.template_file.testing_role.rendered
}
These are the locals its looping through:
locals {
roles = {
"test_Read_Only" = ["arn:aws:iam::*:role/testReadOnly", "]
"test_OS_Only" = ["arn:aws:iam::*:role/testSigninOSOnly"]
}
}
what I need terraform to see when its running are these two:
${data.template_file.test_Read_Only_policy.rendered}
${data.template_file.test_OS_Only_policy.rendered}
But there is something not right with the syntax I have. The error I get says "The "data" object must be followed by two attribute names: the data source type and the resource name."
I don't know how to combine the each.key into the rendered data template file
What I would suggest is:
To use the data source with for_each and use the same variable
To switch to the templatefile built-in function and pass the value as a variable.
To achieve the first, you would do something like:
module "sso_roles" {
source = "git::ssh://git#gitlab.com/iam/role?ref=1.1.0"
for_each = local.roles
policy = data.template_file.policy[each.key].rendered
role_name = each.key
assume_role_policy_def = data.template_file.testing_role.rendered
}
data "template_file" "policy" {
for_each = local.roles
...
}
The second one is probably a bit more convenient and it's using a newer and better templatefile function [1]:
module "sso_roles" {
source = "git::ssh://git#gitlab.com/iam/role?ref=1.1.0"
for_each = local.roles
policy = templatefile("${path.module}/path/to/template/file.tpl", {
iam_role = each.value
})
role_name = each.key
assume_role_policy_def = data.template_file.testing_role.rendered
}
With more information about the template file you are using I would be able to adjust the second example.
[1] https://www.terraform.io/language/functions/templatefile
I have created 2 modules. One for SQS and another for SSM. My SQS creates 4 queues and i am trying to create corresponding entries in the parameter store for their url and arn. I am importing the SSM module inside my SQS module such that it creates the parameters right after SQS creation is done.
This is what my SQS module looks like :-
resource "aws_sqs_queue" "FirstStandardSQSQueue" {
name = "sqs-${var.stage}-one"
message_retention_seconds = var.SQSMsgRetention
redrive_policy = jsonencode({
deadLetterTargetArn = aws_sqs_queue.FirstDeadLetterQueue.arn
maxReceiveCount = 2
})
}
resource "aws_sqs_queue" "FirstDeadLetterQueue" {
name = "sqs-dead-letter-${var.stage}-one"
receive_wait_time_seconds = var.SQSRecvMsgWaitTime
}
resource "aws_sqs_queue" "SecondStandardSQSQueue" {
name = "sqs-${var.stage}-two"
message_retention_seconds = var.SQSMsgRetention
redrive_policy = jsonencode({
deadLetterTargetArn = aws_sqs_queue.SecondDeadLetterQueue.arn
maxReceiveCount = 3
})
}
resource "aws_sqs_queue" "SecondDeadLetterQueue" {
name = "sqs-dead-letter-${var.stage}-two"
receive_wait_time_seconds = var.SQSRecvMsgWaitTime
}
module "ssm" {
source = "../ssm/"
// How do i create a dynamic map???
for_each = var.Queues
name = "/new/${var.stage}/${each.key}"
type = "SecureString"
value = "${each.value}"
}
My SSM module looks like this :-
resource "aws_ssm_parameter" "main" {
name = var.name
type = var.type
value = var.value
}
I am trying to create either a map or somehow dynamically be able to pass values in my ssm module using for_each? I tried setting up something like this :-
variable "Queues" {
type = map
default = {
"FirstStandardSQSQueueUrl" = aws_sqs_queue.FirstStandardSQSQueue.url
"FirstStandardSQSQueueArn" = aws_sqs_queue.FirstStandardSQSQueue.arn
"SecondStandardSQSQueueUrl" = aws_sqs_queue.SecondStandardSQSQueue.url
"SecondStandardSQSQueueArn" = aws_sqs_queue.SecondStandardSQSQueue.arn
}
}
But this is invalid because i keep running into
Error: Variables not allowed line 40: Variables may not be used here.
Can someone suggest a better/right way to do it? Thank you.
As the error msg writes, you can't have dynamic variables. locals should be used instead.
locals {
Queues = {
"FirstStandardSQSQueueUrl" = aws_sqs_queue.FirstStandardSQSQueue.url
"FirstStandardSQSQueueArn" = aws_sqs_queue.FirstStandardSQSQueue.arn
"SecondStandardSQSQueueUrl" = aws_sqs_queue.SecondStandardSQSQueue.url
"SecondStandardSQSQueueArn" = aws_sqs_queue.SecondStandardSQSQueue.arn
}
}
Then you refer to the value as local.Queues in other parts of your code.
I am creating AWS ECR repositories via terraform
resource "aws_ecr_repository" "repo1" {
name = "repo1"
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
}
resource "aws_ecr_repository" "repo2" {
name = "repo2"
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
}
Now I want to attach a policy to all ECR repositories.
Question is, is there a dynamic way to create a list of all the resources (of type ECR) created using the terraform script? If yes then we can have a for_each on that list and attach a policy.
Or is there any better way to do it?
P.S. I know I can attach policy by writing the following for each. I want to avoid duplication and avoid a case where policy is not attached if the block is missed by someone
resource "aws_ecr_lifecycle_policy" "insights_repository_policy" {
repository = aws_ecr_repository.insights_repository.name
policy = local.ecr_cleanup_policy
}
Edit: Question 2
There are some accounts I want to give access to. If I use list of repositories to create and then I want to assign policies for each account then it would make nested for loops. Is there a cleaner solution for that?
local {
accounts = {test=account_id_123, prod=account_id_456}
}
resource "aws_ecr_repository_policy" "access-permission" {
for_each = local.accounts
policy = <<POLICY
...
POLICY
repository = aws_ecr_repository.repo_template.name
}
Not in your form. It would be better if you used for_each or count. For example:
variable "repos" {
default = ["repo1", "repo2"]
}
resource "aws_ecr_repository" "repo" {
for_each = to_set(var.repos)
name = each.key
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
}
then you can do:
resource "aws_ecr_lifecycle_policy" "insights_repository_policy" {
for_each = aws_ecr_repository.repo
repository = each.value.name
policy = local.ecr_cleanup_policy
}
I'm trying to terraform WAF ACL and associated rules. The terraform stack I'm working on is identical in DEV, QA , and PROD, differences are all handled using different variables. So my idea is to store a list of CIDRs in a variable, and automatically create ALLOW rules for each. My limited knowledge is slowing me down though. It creates the ipsets perfectly, but the rules and ACL complain,
variable cloud_allowed_cidr_list = {type="list" default=["1.2.3.4/32","4.3.2.1/32"]}
resource "aws_waf_ipset" "ipset" {
count = "${length(var.cloud_allowed_cidr_list)}"
name = "ipset-${count.index}"
ip_set_descriptors {
type = "IPV4"
value = "${element(var.cloud_allowed_cidr_list, count.index)}"
}
}
resource "aws_waf_rule" "matchIPrule" {
count = "${length(var.cloud_allowed_cidr_list)}"
depends_on = ["aws_waf_ipset.ipset"]
name = "matchMancIPrule${count.index}"
metric_name = "matchMancIPrule${count.index}"
predicates {
data_id = "${aws_waf_ipset.ipset.*.id}"
negated = false
type = "IPMatch"
}
}
resource "aws_waf_web_acl" "waf_acl" {
depends_on = ["aws_waf_ipset.ipset", "aws_waf_rule.matchIPrule"]
name = "mancACL${count.index}"
metric_name = "mancACL${count.index}"
default_action {
type = "BLOCK"
}
rules {
action {
type = "ALLOW"
}
priority = "${count.index}"
rule_id = "${aws_waf_rule.matchIPrule.id}"
type = "REGULAR"
}
}
It fell apart when I realised that rules have multiple predicates, and the ACL has multiple rules .....how do you create that dynamically ? If anyone has any examples of doing something similar I'd be very grateful.
Since the release of 0.12 you can now do this using dynamic blocks.
No need to use count to iterate over your array.
resource "aws_waf_ipset" "ipset" {
name = "youripset"
dynamic "ip_set_descriptors" {
iterator = ip
for_each = var.cloud_allowed_cidr_list
content {
type = "IPV4"
value = ip.value
}
}
}