Convert locals to string with literal curly braces - amazon-web-services

I'm trying to convert a locals variable to string.
locals {
env_vars = {
STAGE = "${var.environment}"
ENVIRONMENT = "${var.environment}"
REGION = "${var.region}"
AWS_DEFAULT_REGION = "${var.region}"
}
}
container_definitions = templatefile("${path.module}/task-definitions/ecs-web-app.json", {
# This works just fine:
#ENVIRONMENT_VARS = <<EOT
# {"name":"STAGE","value":"${var.environment}"},
# {"name":"ENVIRONMENT","value":"${var.environment}"},
# {"name":"REGION","value":"${var.region}"},
# {"name":"AWS_DEFAULT_REGION","value":"${var.region}"}
# EOT
# But I'm trying to get this to work instead:
ENVIRONMENT_VARS = join(",", [for key, value in local.env_vars : "{{\"name\":\"${key}\",\"value\":\"${value}}}\""])
}
Where foo.json is:
[
{
"environment":[
${ENVIRONMENT_VARS}
]
}
]
The error I get is:
Error: ECS Task Definition container_definitions is invalid: Error decoding JSON: invalid character '{' looking for the beginning of object key string
So in a nutshell, I'm trying to convert:
locals {
env_vars = {
STAGE = "${var.environment}"
ENVIRONMENT = "${var.environment}"
REGION = "${var.region}"
AWS_DEFAULT_REGION = "${var.region}"
}
}
To a string that looks like this:
{"name":"STAGE","value":"dev"},
{"name":"ENVIRONMENT","value":"dev"},
{"name":"REGION","value":"us-west-2"},
{"name":"AWS_DEFAULT_REGION","value":"us-west-2"}
I'm almost 100% sure the curly braces are throwing me off, but I can't figure out what I'm doing wrong.
Any help or pointers would be greatly appreciated.

You have one too many curly brackets. It should be:
container_definitions = templatefile("foo.json", {
ENVIRONMENT_VARS = join(",", [for key, value in local.env_vars:
"{\"name\":\"${key}\",\"value\":\"${value}}\""
])
})

Related

How to join terraform subnet variable

variable.tf
variable "private_subnets" {
type = list
default = ["subnet-abc1,subnet-abc2"]
}
main.tf
resource "aws_db_subnet_group" "rds_subnet_group" {
name = var.cluster_name
subnet_ids = "${var.private_subnets}"
tags = {
Name = var.cluster_name,
environment = var.environment
}
}
This the present code i want use varable.tf for subnet this is way i want achive like this subnet-abc1,subnet-abc2
Considering that your variable has a wrongly defined default value, the first thing to fix is that:
variable "private_subnets" {
type = list(string)
default = ["subnet-abc1", "subnet-abc2"]
}
The way you are currently defining the default value, i.e, ["subnet-abc1,subnet-abc2"], is a list, however, it is a list with one element. Each element in a list of strings needs to start and end with double quotes, i.e. "some value". You can read more about lists in [1].
Then, you would just need to fix the code in the main.tf to look like this:
resource "aws_db_subnet_group" "rds_subnet_group" {
name = var.cluster_name
subnet_ids = var.private_subnets
tags = {
Name = var.cluster_name,
environment = var.environment
}
}
The syntax for subnets in the main.tf file is the old terraform syntax, so this will work without double quotes and ${}.
[1] https://developer.hashicorp.com/terraform/language/expressions/types#list
i have sort issue with split this is the answer
variable.tf
variable "private_subnets" {
default = "subnet-abc1,subnet-abc2"
}
main.tf
resource "aws_db_subnet_group" "rds_subnet_group" {
name = var.cluster_name
subnet_ids = "${split(",", var.private_subnets)}"
tags = {
Name = var.cluster_name,
environment = var.environment
}

Terraform split all object in a list while using count.index

I have a terraform code like below.
locals {
org_sub_accounts = [
"111111111111",
"222222222222,
"333333333333",
]
role_arns = [
"arn:aws:iam::111111111111:role/DataConnector1",
"arn:aws:iam::222222222222:role/DataConnector2",
"arn:aws:iam::333333333333:role/DataConnector3",
]
}
resource "aws_cloudformation_stack_set_instance" "stack" {
count = length(local.org_sub_accounts)
account_id = local.org_sub_accounts[count.index]
region = "ap-east-1"
parameter_overrides = {
RoleName = local.role_arns[count.index]
}
stack_set_name = aws_cloudformation_stack_set.stackset.name
}
My problem is my RoleName should be DataConnector potion (after /) but not the entire ARN in the aws_cloudformation_stack_set_instance. How can I pass the RoleName DataConnector* within each index?
Note, here I defined the variables in the locals to show my use case. But actually those comes from other resource outputs.
This can be achieved by using the split built-in function:
locals {
role_names = [for arn in local.role_arns : split("/", arn)[1]]
}
With this part split("/", arn)[1] you are splitting the IAM role ARN into two parts (before and after the /) and with the index [1] you are effectively getting the second part of that list. Then, you would have to change the code to reflect that with:
resource "aws_cloudformation_stack_set_instance" "stack" {
count = length(local.org_sub_accounts)
account_id = local.org_sub_accounts[count.index]
region = "ap-east-1"
parameter_overrides = {
RoleName = local.role_names[count.index]
}
stack_set_name = aws_cloudformation_stack_set.stackset.name
}
[1] https://developer.hashicorp.com/terraform/language/functions/split

Terraform: How can I pass variables to user_data init script

I have been hanging around with this problem for some time now and I can't solve it.
I'm launching an EC2 instance that runs a bash script and installs a few things.
At the same time, I am also launching an RDS instance, but I need to be able to pass the value from the RDS endpoint to the EC2 instance to configure the connection.
I'm trying to do this using templatefile, like this
resource "aws_rds_cluster_instance" "cluster_instances" {
count = 1
identifier = "rds-prod-ddbb-${count.index}"
cluster_identifier = aws_rds_cluster.default.id
instance_class = "db.r5.large"
engine = "aurora"
engine_version = "5.6.mysql_aurora.1.22.5"
publicly_accessible = "true"
}
resource "aws_rds_cluster" "default" {
cluster_identifier = "aws-rds-ddbb-cluster"
availability_zones = ["us-west-2b"]
db_subnet_group_name = "default-vpc-003d3ab296c"
skip_final_snapshot = "true"
backup_retention_period = 30
vpc_security_group_ids = [aws_security_group.ddbb.id]
}
data "template_file" "RDSs" {
template = file("init.sh")
vars = {
rds = aws_rds_cluster.default.endpoint
}
depends_on = [
aws_rds_cluster.default,
aws_rds_cluster_instance.cluster_instances,
]
}
resource "aws_instance" "web_01" {
ami = "ami-0477c9562acb09"
instance_type = "t2.micro"
subnet_id = "subnet-0d0558d99ec3cd3"
key_name = "web-01"
user_data_base64 = base64encode(data.template_file.RDSs.rendered)
vpc_security_group_ids = [aws_security_group.ddbb.id]
ebs_block_device {
device_name = "/dev/sda1"
volume_type = "gp2"
volume_size = 20
}
tags = {
Name = "Web01"
}
depends_on = [
aws_rds_cluster.default,
aws_rds_cluster_instance.cluster_instances,
]
}
And then, my init.sh is like this:
#!/bin/bash
echo "rds = $rds" > /var/tmp/rds
But I get nothing in /var/tmp/rds, so it looks like the variable $rds is empty.
Your help will be greatly appreciated.
Ps: I have outputs configured like this:
outputs.tf
output "rds_endpoint" {
value = aws_rds_cluster.default.endpoint
}
And that is working fine, when the apply is complete, it shows me the value of rds endpoint.
The variable is not a shell variable but a templated variable — so terraform will parse the file, regardless of its type and replace terraform variables in the said file.
Knowing this, $rds is not a terraform variable interpolation, while ${rds} is.
So, your bash script should rather be:
#!/bin/bash
echo "rds = ${rds}" > /var/tmp/rds
Since Terraform 0.12 There is a more elegant way of passing variable to user_data
Instead of using the data "template_file" prefer using the new templatefile function in terraform
locals {
WEB_SERVER_UNAME = "your_username"
WEB_SERVER_PASS = "your_password"
}
resource "aws_instance" "web_01" {
....
user_data_base64 = base64encode("${templatefile("${path.module}/user_data_script.sh", {
WEB_SERVER_UNAME = local.WEB_SERVER_UNAME
WEB_SERVER_PASS = local.WEB_SERVER_PASS
})}")
....
}
By using $rds you are refering to variables inside your shell script or env vars, that is the reason why it is displaying nothing.
To use the template variables you need to interpolate in the following way ${variable}
Refer this for further details :- https://www.terraform.io/language/expressions/strings#string-templates

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]

passing variable to terraform dynamic block v12

I am trying to use the code in this repo https://github.com/jmgreg31/terraform-aws-cloudfront/
but getting a hard time in setting the variables.
My variables.tf has this value, but somehow its not working :
variable "dynamic_s3_origin_config" {
default =
[
{
domain_name = "domain.s3.amazonaws.com"
origin_id = "S3-domain-cert"
origin_access_identity = "origin-access-identity/cloudfront/1234"
},
{
domain_name = "domain2.s3.amazonaws.com"
origin_id = "S3-domain2-cert"
origin_access_identity = "origin-access-identity/cloudfront/1234"
origin_path = ""
}
]
}
variable definition in module looks like:
variable dynamic_s3_origin_config {
description = "Configuration for the s3 origin config to be used in dynamic block"
type = list(map(string))
default = []
}
can someone help me to understand what I am doing wrong here?
terraform plan
Error: Invalid expression
on variables.tf line 65, in variable "dynamic_s3_origin_config":
65:
66:
Expected the start of an expression, but found an invalid expression token.
You can't have a newline between default = and the start of the expression. Try changing your block to:
variable "dynamic_s3_origin_config" {
default = [
{
domain_name = "domain.s3.amazonaws.com"
origin_id = "S3-domain-cert"
origin_access_identity = "origin-access-identity/cloudfront/1234"
},
{
domain_name = "domain2.s3.amazonaws.com"
origin_id = "S3-domain2-cert"
origin_access_identity = "origin-access-identity/cloudfront/1234"
origin_path = ""
}
]
}