Creating dynamic resources with Terraform for_each - amazon-web-services

I would like to create AWS SSM Parameters using Terraform, with the parameters being passed in as input variables.
I see there is a for_each feature, but how can this be applied to top level properties within a terraform resource? From the documentation, the use of for_each appears to be restricted to not work on top level properties of a resource, am I misunderstanding?
This is what I am trying to accomplish:
main.tf
resource "aws_ssm_parameter" "ssm_parameters" {
for_each = var.params
content {
name = name.value
type = "String"
overwrite = true
value = paramValue.value
tags = var.tags
lifecycle {
ignore_changes = [
tags,
value
]
}
}
}
variables.tf
variable "params" {
default = [
{
name = "albUrl"
paramValue = "testa"
},
{
name = "rdsUrl1"
paramValue = "testb"
},
{
name = "rdsUrl2"
valparamValueue = "testc"
},
]
}

You can use for each, but you need to modify its syntax and fix syntax in your var.params:
variable "params" {
default = [
{
name = "albUrl"
paramValue = "testa"
},
{
name = "rdsUrl1"
paramValue = "testb"
},
{
name = "rdsUrl2"
paramValue = "testc"
},
]
}
Then to use for each, and create 3 ssm parameters:
resource "aws_ssm_parameter" "ssm_parameters" {
for_each = {for v in var.params: v.name => v.paramValue}
type = "String"
name = each.key
value = each.value
overwrite = true
}
In the above you have to project your list(map) to a map as it is required for for_each.

Related

Terraform: how to create resource from loop through a objects with inner list

I am working with Terraform and I need to create a Glue Workflow. My target is the following schema:
I don't understand how I can use the "nested loops" to create the resouce from a variable object with a list of string:
My main.tf file is:
provider "aws" {
region = "eu-west-1"
profile = "<MY-STAGE>"
}
locals {
workflow_name = "my_example"
first_job = "Job_start"
my_map = [
{
flow = ["JOB-A1", "JOB-A2", "JOB-A3"]
},
{
flow = ["JOB-B1", "JOB-B2", "JOB-B3"]
}
]
}
resource "aws_glue_workflow" "example" {
name = "example"
}
resource "aws_glue_trigger" "example-start" {
name = "trigger-start"
type = "ON_DEMAND"
workflow_name = local.workflow_name
actions {
job_name = "${replace(lower(local.first_job), "_", "-")}"
}
}
resource "aws_glue_trigger" "this" {
for_each = toset(local.my_map)
name = "trigger-inner--${lower(element(each.key, index(local.my_map, each.key)))}"
type = "CONDITIONAL"
workflow_name = aws_glue_workflow.example.name
predicate {
conditions {
job_name = "${replace(lower(element(each.key, index(local.my_map, each.key))), "_", "-")}"
state = "SUCCEEDED"
}
}
actions {
job_name = "${replace(lower(element(each.key, index(local.my_map, each.key) + 1)), "_", "-")}"
}
}
When I try to do the "plan" I get this error:
| each.key is a string, known only after apply
| local.my_map is tuple with 2 elements
| Call to function "element" failed: cannot read elements from string.
Then, how can I get all the rows of the object and scroll through the list elements?
Any help or pointers would be much appreciated!

Terraform create multiple resources using for_each and jsondecode

I want to create multiple resource(GCP's multiple cloudSQL instances) and this is what I have:
locals {
psql_settings = [
{ "name" : "psql1", "location" : "us-central1", "zone" : "us-central1-c" },
{ "name" : "psql2", "location" : "us-east1", "zone" : "us-east1-b" }
]
}
I have to use them in json format because this will be stored in consul for dynamic changes.
Using this locals value, how I can create multiple resources. I am trying:
module "postgresql-db" {
depends_on = [
module.vpc
]
source = "../modules/postgres"
for_each = local.psql_settings[0]
name = each.value.name
random_instance_name = true
database_version = "POSTGRES_13"
project_id = "xyz-project
zone = each.value.zone
region = each.value.location
...
...
It should be:
for_each = {for idx,val in local.psql_settings: idx => val}
The code changes your list of maps, into map of maps, which is required by for_each.

Terraform multiple AWS route53 MX records

I have locals in tf to push MX records to cloudflare and it works exactly I want it to. The same locals I want to use for Route53 but for route53 the records should be combined to one. How I can use blow locals to push to Route53 using terraform.
locals {
MXRecordSets = [
{
Name = "example.com.",
Type = "MX",
TTL = 3600,
MXRecords = [
{
"Value" = "1 aspmx.l.google.com"
},
{
"Value" = "5 alt1.aspmx.l.google.com"
}
]
}
]
}
locals {
FlatMXRecordSets = merge([
for idx, MXRecordSet in local.MXRecordSets:
{
for MXRecord in MXRecordSet.MXRecords:
"${idx}-${MXRecord.Value}" => {
MXRecordSet = MXRecordSet
MXRecord = MXRecord["Value"]
}
}
]...)
}
and finally I am using below aws route53 module to push changes to AWS, but its not working:
resource "aws_route53_record" "mx_records" {
for_each =local.FlatMXRecordSets
zone_id = aws_route53_zone.carfeine_com.id
name = each.value["MXRecordSet"].Name
type = each.value["MXRecordSet"].Type
records = [ each.value["MXRecord"] ]
ttl = 1
}
The error comes as [Tried to create resource record set [name='example.com.', type='MX'] but it already exists] because I am using for_each and I should not use that. Any other way ?
Your MXRecordSets is already flat, thus it does not require flattening. But Its also a list of maps, which can lead to more issues, as a list depends on order of items. Thus its better to use just a map of maps, if possible:
locals {
MXRecordSets = {
"mail.mwotest.xyz." = {
Type = "MX",
TTL = 3600,
MXRecords = [
{
"Value" = "1 aspmx.l.google.com"
},
{
"Value" = "5 alt1.aspmx.l.google.com"
}
]
}
}
}
then
resource "aws_route53_record" "mx_records" {
for_each = local.MXRecordSets
zone_id = aws_route53_zone.carfeine_com.id
name = each.key
type = each.value.Type
records = [ for key,record in each.value["MXRecords"]: record["Value"] ]
ttl = 1
}
in the above, the
[ for key,record in each.value["MXRecords"]: record["Value"] ]
will produce correct records for MX as:
records = ["1 aspmx.l.google.com", "5 alt1.aspmx.l.google.com"]

How use values of list terraform variable in aws_policy.arn

main.tf
module "iam_assumable_role" {
for_each = var.service_accounts
source = "../../../../../../modules/iam-assumable-role-with-oidc/"
create_role = true
role_name = each.value.name
provider_url = replace(module.eks.cluster_oidc_issuer_url, "https://", "")
// role_policy_arns = [for i in each.value.policies : "aws_iam_policy.${i}.arn"]
oidc_fully_qualified_subjects = each.value.wildcard == "" ? ["system:serviceaccount:${each.value.namespace}:${each.value.name}"] : []
oidc_subjects_with_wildcards = each.value.wildcard != "" ? ["system:serviceaccount:${each.value.namespace}:${each.value.wildcard}"] : []
tags = var.tags
}
resource "aws_iam_policy" "dev-policy1" {
name_prefix = "dev-policy"
description = "some description"
policy = data.aws_iam_policy_document.dev-policy1.json
}
variable "service_accounts" {
type = map(object({
name = string
namespace = string
wildcard = string
policies = list(any)
}))
}
tfvars
service_accounts = {
"dev-sa" = {
"name" = "dev-sa",
"namespace" = "dev",
"wildcard" = "*",
"policies" = ["dev-policy1", "dev-policy2"]
},
"qa-sa" = {
"name" = "qa-sa",
"namespace" = "qa",
"wildcard" = "*",
"policies" = ["qa-policy1", "qa-policy2"]
}
}
My code is iterating over service_accounts variable and creates appropriate resources. The problem is that in the commented line I cannot get the list of aws_iam_policy.arn s for the provided policy names (policy names are provided through service_account variable). My current code returns the aws_iam_policy.PolicyName.arn as string and not the actual value. Note that dev-policy1 resource s just one of the all policy resources. All policy documents exist as well. module itself is working correctly when I provide policy list directly and not through variable.
Is it possible to achieve the desired in terraform at all?
You have to use for_each, to create your policies, as you can't dynamically references individual resources the way you are trying to do:
# get all policy names. Your names are unique, so its fine to use list
locals {
policy_names = flatten(values(var.service_accounts)[*]["policies"])
}
# create policy for each name in `policy_names`
resource "aws_iam_policy" "policy" {
for_each = local.policy_names
name_prefix = "dev-policy"
description = "some description"
# similar must be done below
# policy = data.aws_iam_policy_document.dev-policy1.json
}
Then you refer to them as:
role_policy_arns = [for i in each.value.policies: aws_iam_policy[${i}].arn]

Terraform count within for_each loop

I'm trying to create GCP SQL DBs by iterating a list of string using Terraform's for_each and count parameter and the other loop is for the map keys (maindb & replicadb).
Unfortunately, I get the error that appears below.
Is it possible to do this is Terraform?
variables.tf
variable "sql_var" {
default = {
"maindb" = {
"db_list" = ["firstdb", "secondsdb", "thirddb"],
"disk_size" = "20",
},
"replicadb" = {
"db_list" = ["firstdb"],
"disk_size" = "",
}
}
}
main.tf
resource "google_sql_database_instance" "master_sql_instance" {
...
}
resource "google_sql_database" "database" {
for_each = var.sql_var
name = "${element(each.value.db_list, count.index)}"
instance = "${google_sql_database_instance.master_sql_instance[each.key].name}"
count = "${length(each.value.db_list)}"
}
Error Message
Error: Invalid combination of "count" and "for_each"
on ../main.tf line 43, in resource
"google_sql_database" "database": 43: for_each =
var.sql_var
The "count" and "for_each" meta-arguments are mutually-exclusive, only
one should be used to be explicit about the number of resources to be
created.
What the error message tells you is that you cannot use count and for_each together. It looks like you are trying to create 3 main databases and 1 replica database am I correct? What I would do is create your 2 master instances and then transform your map variable to create the databases.
terraform {
required_version = ">=0.13.3"
required_providers {
google = ">=3.36.0"
}
}
variable "sql_instances" {
default = {
"main_instance" = {
"db_list" = ["first_db", "second_db", "third_db"],
"disk_size" = "20",
},
"replica_instance" = {
"db_list" = ["first_db"],
"disk_size" = "20",
}
}
}
locals {
databases = flatten([
for key, value in var.sql_instances : [
for item in value.db_list : {
name = item
instance = key
}
]
])
sql_databases = {
for item in local.databases :
uuid() => item
}
}
resource "google_sql_database_instance" "sql_instance" {
for_each = var.sql_instances
name = each.key
settings {
disk_size = each.value.disk_size
tier = "db-f1-micro"
}
}
resource "google_sql_database" "sql_database" {
for_each = local.sql_databases
name = each.value.name
instance = each.value.instance
depends_on = [
google_sql_database_instance.sql_instance,
]
}
Then, first run terraform apply -target=google_sql_database_instance.sql_instance and after this run terraform apply.