Having trouble assigning a list variable to SSM Parameter as a stringList - terraform-provider-aws

I receive an error, value must be a string, when trying to set an ssm parameter with type=stringlist to a variable of type list using terraform.
resource "aws_ssm_parameter" "customer_list_stg" {
name = "/corp/stg/customer_list"
type = "StringList"
value = var.customer_list
tags = {
environment = var.environment
}
}
customer_list = ["sar", "smi", "heath","first","human","stars","ther","ugg","stars","well"]
terraform apply: expecting an ssm parameter with a list of 10 strings
received an error: Inappropriate value for attribute "value": string required.
I have tried tostring, jsonencode and flatten without success.

Just use list as the type, StringList isn't a valid HCL type. Please see the documentation here:
[https://developer.hashicorp.com/terraform/language/expressions/types][1]

Regardless of the attribute type = "StringList", the value attribute of aws_ssm_parameter always expects a string literal.
Please refer to the AWS API docs
Hence the correct code would be as follow
resource "aws_ssm_parameter" "customer_list_stg" {
name = "/corp/stg/customer_list"
type = "StringList"
value = join(",", var.customer_list) ## if you want to use list(string) input
# value = "sar,smi,heath,first,human,stars,ther,ugg,stars,well" ## as a string literal (you can put this value in a variable too)
tags = {
environment = var.environment # define this variable in your config too.
}
}
variable "customer_list" {
type = list(string)
description = "(optional) Customer List"
default = ["sar", "smi", "heath", "first", "human", "stars", "ther", "ugg", "stars", "well"]
}

Related

Facing error with count.index in terraform

I am preparing to deploy Aurora MySQL Cluster, I have defined the following in the “variable.tf” under root:
variable "db_subnet_id" {
type = list
description = `DB Subnet IDs`
default = [“subnet-02ddc8565555aaa9b”,“subnet-1d30f3a41ce19635d”] #db-sub-eu-central-1a , db-sub-eu-central-1b
}
variable `db-sg` {
type = list
description = “List of standard security groups”
default = [`“sg-00cfed28101ea95ab”,“sg-08017f86bc12348e8”,“sg-0e8c67c7a3cd79a79”`]
}
variable `db_az` {
type = list
description = “Frankfurt Availability Zones”
default = [“eu-central-1a”,“eu-central-1b”]
}
how can I use those variables with the following parameters:
availability_zones
db_subnet_group_name
vpc_security_group_ids
I have tried to use this vailability_zones = var.db_az[count.index] but the following error shows up:
The “count” object can only be used in “module”, “resource”, and “data” blocks, and only when the “count” argument is set.
please advise, how can I use those variables with those parameters ?
Thanks

Any Better Alternative to Using Conditional Count Statements in Terraform?

I am interested to see if anyone knows of any better alternative to using conditional count statements in Terraform. By "conditional count statement", I mean a statement where depending a condition like a variable input, count will evaluate to create either 0 or 1 of a resource.
A simple example:
resource "xxxxxxxx" "example" {
count = var.example != null ? 1 : 0
}
Here, the resource will only be created if var.example has a value (is not null), otherwise it will not get created.
Conditional counts usually work ok in practice, but sometimes in more complex uses than the one above it introduces a risk of getting an error during Terraform Plan where it cannot evaluate the result of the count pre-apply.
The "count" 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 count depends on.
Is there any better way to achieve the same effect of creating resources on a conditional basis in Terraform?
Keeping in mind that your terraform config not shown I've used a general example of for_each instead of count.
This is an example of how 2 CNAME records will be created using & in this example terraform modules are used but the same can be done directly on terraform resources.
locals {
cname_records = {
"email" = ["email.domain.net."]
"imap" = ["imap.domain.net."]
}
}
module "aws_route53_record_CNAME" {
source = "app.terraform.io/terraform-cloud-org/route53-record/aws"
version = "1.0.0"
for_each = local.cname_records
records = each.value
name = each.key
zone_id = "YOUR-HOSTED-ZONE-ID"
type = "CNAME"
ttl = "300"
}
It varies by case, but there are some cases where you need to get around this. There is a clever trick, but it seems obtuse in the single item (binary exists or not) case. But in a case where there are actually multiple items, this should help.
This is a completely contrived example but I actually use the method quite a bit. In short, a list of things not known until apply, can be replaced by a list of objects with a key you don't care about. The purpose is that for_each doesn't mind if the value is unknown at plan-time, only if the key is.
Consider the following root module with these four modules.
main.tf
resource "aws_s3_bucket" "this" {
bucket = "h4s-test-bucket"
}
# module "with_count" { # cannot be determined until apply
# source = "./with-count"
# x = aws_s3_bucket.this.id
# }
module "with_for_each_over_item" { # handy workaround
source = "./with-for-each-over-item"
x = aws_s3_bucket.this.id
}
output "with_for_each_over_item" {
value = module.with_for_each_over_item
}
# module "with_for_each_list" { # cannot be determined until apply
# source = "./with-for-each-list"
# x = [aws_s3_bucket.this.id]
# }
module "with_for_each_list_better" { # handy workaround
source = "./with-for-each-list-better"
x = [{ y = aws_s3_bucket.this.id }]
}
output "with_for_each_list_better" {
value = module.with_for_each_list_better
}
module "with_for_each_list_best" { # handier workaround
source = "./with-for-each-list-best"
x = [aws_s3_bucket.this.id]
}
output "with_for_each_list_best" {
value = module.with_for_each_list_best
}
with-count/main.tf (problematic)
variable "x" {
type = string
default = null
}
resource "null_resource" "this" {
count = var.x != null ? 1 : 0
}
output "this" {
value = null_resource.this
}
with-for-each-over-item/main.tf (handy workaround)
variable "x" {
type = string
default = null
}
resource "null_resource" "this" {
for_each = { for i, v in [var.x] : i => v }
}
output "this" {
value = null_resource.this
}
with-for-each-list/main.tf (problematic)
variable "x" {
type = list(string)
default = []
}
resource "null_resource" "this" {
for_each = toset(var.x)
}
output "this" {
value = null_resource.this
}
with-for-each-list-better/main.tf (handy workaround)
variable "x" {
type = list(object({ y = string }))
default = []
}
resource "null_resource" "this" {
for_each = { for i, v in var.x : i => v }
}
output "this" {
value = null_resource.this
}
with-for-each-list-best/main.tf (handiest workaround)
variable "x" {
type = list(string)
default = []
}
resource "null_resource" "this" {
for_each = { for i, v in var.x : i => v }
}
output "this" {
value = null_resource.this
}
Summary
In cases where the variable has a value not known at plan-time, consider using an object where the key is known.

Terraform: pass variable list to container environment variables

I am trying to find a way how to pass/map the list variable to kubernetes_deployment container environment variables. I run an asp.net core application in the container and I trying to modify the app with setting env variables to override the app settings.json. It is not a problem to override single values from settings.json, the problem is if I need to define/override the whole array there.
I have variable list like this in terrafrom:
variable "allowed_cars" {
type = list(
object({
manufacturer = string
model = string
})
)
}
Then, I have resource definition of kubernetes_deployment with containers.
I believe it could work if I set the env variables for the container like this:
env {
name = "App__AllowedCars__0__Manufacturer"
value = "xxx"
}
env {
name = "App__AllowedCars__0__Model"
value = "xxx"
}
env {
name = "App__AllowedCars__1__Manufacturer"
value = "xxx"
}
env {
name = "App__AllowedCars__1__Model"
value = "xxx"
}
...
Is there a way how to pass these env variables to the container in a dynamic way based on allowed_cars terraform variable? I don't know how many items will be defined for each environments, etc...
Thank you very much.
Something of this definition (used example of azurerm_container_group in azure)
environment_variables = "${merge(var.env_vars,var.secure_env_vars,local.master_env)}"
and then variables can be passed in
variable "env_vars" {
type = "map"
description = "envvaars"
default = {
WEB_USER = "locust"
HATCH_RATE = 25
LOCUST_COUNT = 50
LOCUST_FILE = "/locust/locustfile.py"
ATTACKED_HOST = "https://api-perf.yrdy.com"
}
}
variable "secure_env_vars" {
type = "map"
description = "secure env vars"
default = {
WEB_PASSWORD = "dummy"
API_KEY = "test"
}
}
As #ydaetskcoR mentioned, the dynamic block was solution for me.
https://www.terraform.io/docs/language/expressions/dynamic-blocks.html
dynamic "env" {
for_each = var.allowed_cars
content {
name = "App__AllowedCars__0__${env.key}__Manufacturer"
value = env.value["manufacturer"]
}
}

How to add any symbols after prefix_?

is there any solution? e.g. I have data in Map with key favorites_ prefix and values _suffix (for example: favorites_jeans, favorites_suit,...,). I want to by dint of loop get that values and set in List, because of it I must give keys of map, right?
I want to know how can I get values of myMap["favorites_*"] (* - after the favorites_ any symbols).
List<String> favoritesStrings = ['favorite_name','favorite_jeans',];
Map<String,dynamic> myMap = {
favoritesStrings[0]:'0',
favoritesStrings[1]:'1',
'someKey':'2',
'anotherKey':'3',
};
favoritesStrings.forEach((favorite)=>print(myMap[favorite]));//prints 0 1
As per what I understood, you want to fetch value from map using "favorites_" + a dynamic value from list as key.
You just have to use String templates and use $ to insert suffix variable to build key dynamically:
List<String> suffixList = ["jeans", "suit", "shirt"];
for(String suffix in suffixList) {
var item = myMap["favorites_$suffix"];
// Do something with item
}
Hope it helps

Linq2XML missing element

How do I modify the query below to properly handle the case where the "Summary" element is missing from one of the articles? Now when that happens I get an "Object reference not set to an instance of an object."
var articles = from article in xmlDoc.Descendants("Article")
select new {
articleId = article.Attribute("ID").Value,
heading = article.Element("Heading").Value,
summary = article.Element("Summary").Value,
contents = article.Element("Contents").Value,
cats = from cat in article.Elements("Categories")
select new {
category = cat.Element("Category").Value
}
};
The problem is that article.Element("Summary") returns null if the element is not found, so you get a NullReferenceException when you try to get the Value property.
To solve this, note that XElement also has an explicit conversion to string. This won't throw if the XElement is null - you will just get a null string reference.
So to solve your problem you can change this:
summary = article.Element("Summary").Value,
to this:
summary = (string)article.Element("Summary")

Resources