How to append to a list in Terraform? - list

I have some code in the general form:
variable "foo" {
type = "list"
default = [ 1,2,3 ]
}
resource "bar_type" "bar" {
bar_field = "${var.foo}"
}
I want to append an addition value to bar_field without modifying foo. How can I do this? I don't see any sort of contacting or appending functions in their docs.
This is 0.11.x Terraform

You can use the concat function for this. Expanding upon the example in your question:
variable "foo" {
type = "list"
default = [ 1,2,3 ]
}
# assume a value of 4 of type number is the additional value to be appended
resource "bar_type" "bar" {
bar_field = "${concat(var.foo, [4])}"
}
which appends to the value assigned to bar_field while ensuring var.foo remains unchanged.

Adding new item to the list if not present:
locals {
oldlist = ["a", "b", "c"]
newitem = "d"
newlist = (contains(local.oldlist, local.newitem) == false ? concat(local.oldlist, [local.newitem]) : local.oldlist)
}
output "newlist" {
value = local.newlist
}

var1 = ["string1","string2"]
var2 = "string3"
var3 = concat(var1, formatlist(var2))

Related

Terraform - Copy AWS SSM Parameters

longtime lurker first time poster
Looking for some guidance from you all. I'm trying to replicate the aws command to essentially get the parameters (ssm get-parameters-by-path) then loop through the parameters and get them
then loop through and put them into a new parameter (ssm put-parameter)
I understand there's a for loop expression in TF but for the life of me I can't put together how I would achieve this.
so thanks to the wonderful breakdown below, I've gotten closer! But have this one issue. Code below:
provider "aws" {
region = "us-east-1"
}
data "aws_ssm_parameters_by_path" "parameters" {
path = "/${var.old_env}"
recursive = true
}
output "old_params_by_path" {
value = data.aws_ssm_parameters_by_path.parameters
sensitive = true
}
locals {
names = toset(data.aws_ssm_parameters_by_path.parameters.names)
}
data "aws_ssm_parameter" "old_param_name" {
for_each = local.names
name = each.key
}
output "old_params_names" {
value = data.aws_ssm_parameter.old_param_name
sensitive = true
}
resource "aws_ssm_parameter" "new_params" {
for_each = local.names
name = replace(data.aws_ssm_parameter.old_param_name[each.key].name, var.old_env, var.new_env)
type = data.aws_ssm_parameter.old_param_name[each.key].type
value = data.aws_ssm_parameter.old_param_name[each.key].value
}
I have another file like how the helpful poster mentioned and created the initial dataset. But what's interesting is that after you create the set after the second set, it overwrites the first set! The idea is that I would be able to tell terraform, I have this current set of SSM parameters and I want you to copy that info (values, type) and create a brand new set of parameters (and not destroy anything that's already there).
Any and all help would be appreciated!
I understand, It's not easy at the beginning. I will try to elaborate step-by-step on how I achieve that.
Anyway, it's nice to include any code, that you tried before, even if doesn't work.
So, firstly I create some example parameters:
# create_parameters.tf
resource "aws_ssm_parameter" "p" {
count = 3
name = "/test/${count.index}/p${count.index}"
type = "String"
value = "test-${count.index}"
}
Then I try to view them:
# example.tf
data "aws_ssm_parameters_by_path" "parameters" {
path = "/test/"
recursive = true
}
output "params_by_path" {
value = data.aws_ssm_parameters_by_path.parameters
sensitive = true
}
As an output I received:
terraform output params_by_path
{
"arns" = tolist([
"arn:aws:ssm:eu-central-1:999999999999:parameter/test/0/p0",
"arn:aws:ssm:eu-central-1:999999999999:parameter/test/1/p1",
"arn:aws:ssm:eu-central-1:999999999999:parameter/test/2/p2",
])
"id" = "/test/"
"names" = tolist([
"/test/0/p0",
"/test/1/p1",
"/test/2/p2",
])
"path" = "/test/"
"recursive" = true
"types" = tolist([
"String",
"String",
"String",
])
"values" = tolist([
"test-0",
"test-1",
"test-2",
])
"with_decryption" = true
}
aws_ssm_parameters_by_path is unusable without additional processing, so we need to use another data source, to get a suitable object for a copy of provided parameters. n the documentation I found aws_ssm_parameter. However, to use it, I need the full name of the parameter.
List of the parameter names I retrieved in the previous stage, so now only needed is to iterate through them:
# example.tf
locals {
names = toset(data.aws_ssm_parameters_by_path.parameters.names)
}
data "aws_ssm_parameter" "param" {
for_each = local.names
name = each.key
}
output "params" {
value = data.aws_ssm_parameter.param
sensitive = true
}
And as a result, I get:
terraform output params
{
"/test/0/p0" = {
"arn" = "arn:aws:ssm:eu-central-1:999999999999:parameter/test/0/p0"
"id" = "/test/0/p0"
"name" = "/test/0/p0"
"type" = "String"
"value" = "test-0"
"version" = 1
"with_decryption" = true
}
"/test/1/p1" = {
"arn" = "arn:aws:ssm:eu-central-1:999999999999:parameter/test/1/p1"
"id" = "/test/1/p1"
"name" = "/test/1/p1"
"type" = "String"
"value" = "test-1"
"version" = 1
"with_decryption" = true
}
"/test/2/p2" = {
"arn" = "arn:aws:ssm:eu-central-1:999999999999:parameter/test/2/p2"
"id" = "/test/2/p2"
"name" = "/test/2/p2"
"type" = "String"
"value" = "test-2"
"version" = 1
"with_decryption" = true
}
}
Each parameter object has been retrieved, so now it is possible to create new parameters - which can be done like this:
# example.tf
resource "aws_ssm_parameter" "new_param" {
for_each = local.names
name = "/new_path${data.aws_ssm_parameter.param[each.key].name}"
type = data.aws_ssm_parameter.param[each.key].type
value = data.aws_ssm_parameter.param[each.key].value
}

Terraform- How to iterate through a list of maps without making duplicates?

I have a list that contains the names (strings) of the secret scopes that I want to create
Example:
ss-list = ["name1", "name2"]
I have a map that contains 2 objects that are applied to each name in my ss-list.
Example:
keyvault-map = {
kv1 = {
id = "abc",
dns-name = "123"
},
kv2 = {
id = "def",
dns-name = "456"
}
}
So for each item in ss-list I need to pull the 2 items inside the map and append to make 1 list.
The end result list that I desire to be returned is...
[
{
"keyvault-id" = "abc"
"keyvault-uri" = "123"
"name" = "name1"
},
{
"keyvault-id" = "def"
"keyvault-uri" = "456"
"name" = "name2"
}
]
I'm looking for how I can iterate through this list and map and return my desired list (with no duplicates).

Terraform Variable looping to generate properties

I have to admit, this is the first time I have to ask something that I dont even myself know how to ask for it or explain, so here is my code.
It worth explains that, for specific reasons I CANNOT change the output resource, this, the metadata sent to the resource has to stay as is, otherwise it will cause a recreate and I dont want that.
currently I have a terraform code that uses static/fixed variables like this
user1_name="Ed"
user1_Age ="10"
user2_name="Mat"
user2_Age ="20"
and then those hard typed variables get used in several places, but most importanly they are passed as metadata to instances, like so
resource "google_compute_instance_template" "mytemplate" {
...
metadata = {
othervalues = var.other
user1_name = var.user1_name
user1_Age = var.user1_Age
user2_name = var.user2_name
user2_Age = var.user2_Age
}
...
}
I am not an expert on terraform, thus asking, but I know for fact this is 100% ugly and wrong, and I need to use lists or array or whatever, so I am changing my declarations to this:
users = [
{ "name" : "yo", "age" : "10", "last" : "other" },
{ "name" : "El", "age" : "20", "last" : "other" }
]
but then, how do i get around to generate the same result for that resource? The resulting resource has to still have the same metadata as shown.
Assuming of course that the order of the users will be used as the "index" of the value, first one gets user1_name and so on...
I assume I need to use a for_each loop in there but cant figure out how to get around a loop inside properties of a resource
Not sure if I make myself clear on this, probably not, but didn't found a better way to explain.
From your example it seems like your intent is for these to all ultimately appear as a single map with keys built from two parts.
Your example doesn't make clear what the relationship is between user1 and Ed, though: your first example shows that "user1's" name is Ed, but in your example of the data structure you want to create there is only one "name" and it isn't clear to me whether that name would replace "user1" or "Ed" from your first example.
Instead, I'm going to take a slightly different variable structure which still maintains both the key like "user1" and the name attribute, like this:
variable "users" {
type = map(object({
name = string
age = number
})
}
locals {
# First we'll transform the map of objects into a
# flat set of key/attribute/value objects, because
# that's easier to work with when we generate the
# flattened map below.
users_flat = flatten([
for key, user in var.users : [
for attr, value in user : {
key = key
attr = attr
value = value
}
]
])
}
resource "google_compute_instance_template" "mytemplate" {
metadata = merge(
{
othervalues = var.other
},
{
for vo in local.users_flat : "${vo.key}_${vo.attr}" => vo.value
}
)
}
local.users_flat here is an intermediate data structure that flattens the two-level heirarchy of keys and object attributes from the input. It would be shaped something like this:
[
{ key = "user1", attr = "name", value = "Ed" },
{ key = "user1", attr = "age", value = 10 },
{ key = "user2", attr = "name", value = "Mat" },
{ key = "user2", attr = "age", value = 20 },
]
The merge call in the metadata argument then merges a directly-configured mapping of "other values" with a generated mapping derived from local.users_flat, shaped like this:
{
"user1_name" = "Ed"
"user1_age" = 10
"user2_name" = "Mat"
"user2_age" = 20
}
From the perspective of the caller of the module, the users variable should be defined with the following value in order to get the above results:
users = {
user1 = {
name = "Ed"
age = 10
}
user2 = {
name = "Mat"
age = 20
}
}
metadata is not a block, but a regular attribute of type map. So you can do:
# it would be better to use map, not list for users:
variable "users"
default {
user1 = { "name" : "yo", "age" : "10", "last" : "other" },
user2 = { "name" : "El", "age" : "20", "last" : "other" }
}
}
resource "google_compute_instance_template" "mytemplate" {
for_each = var.users
metadata = each.value
#...
}

Terrraform list of objects syntax

I'm using a module that references a central module used to build a Puppet server in terraform. There is one variable in the root module that allows additional tags to be used with the ASG however I can't seem to get the syntax right. This is the information in the core repository:
variable "additional_asg_tags" {
description = "A map of additional tags to add to the puppet server ASG."
type = list(object({ key = string, value = string, propagate_at_launch = bool }))
default = []
}
I've tried everything I can think of to call this but it always errors with messages like "incorrect list element type: string required." or "This default value is not compatible with the variable's type constraint: list of object required."
I'm trying to call the above with something like;
variable "additional_asg_tags" {
description = "A map of additional tags to add to ASG."
type = list(object({ key = string, value = string, propagate_at_launch = bool }))
default = { key = "Name", value = "Puppet-nonprod", propagate_at_launch = "true"
}
}
I've removed the square braces around this as that was causing errors also but I may need to add these back in.
Can someone help please in what is the correct way to reference a list of objects with these values
The correct default value for your additional_asg_tags is a list:
variable "additional_asg_tags" {
description = "A map of additional tags to add to ASG."
type = list(object({
key = string,
value = string,
propagate_at_launch = bool
}))
default = [{
key = "Name",
value = "Puppet-nonprod",
propagate_at_launch = "true"
}]
}
You can reference individual elements as follows (some examples):
var.additional_asg_tags[0]["key"]
var.additional_asg_tags[0].value
# to get list
var.additional_asg_tags[*].propagate_at_launch

Terraform map to string value

How do you parse a map variable to a string in a resource value with Terraform12?
I have this variable:
variable "tags" {
type = map
default = {
deployment_tool = "Terraform"
code = "123"
}
}
And want this: {deployment_tool=Terraform, code=123}
I've tried the following without success:
resource "aws_ssm_parameter" "myparamstore" {
***
value = {
for tag in var.tags:
join(",",value, join("=",tag.key,tag.values))
}
}
Replacing ":" with "=" is not a perfect solution, just consider a map with such a value: https://example.com - it becomes https=//example.com. That's not good.
So here is my solution:
environment_variables = join(",", [for key, value in var.environment_variables : "${key}=${value}"])
Your requested output is just malformed JSON string. So you can convert your variable to json using jsonencode, and then remove " and change : into =:
value = replace(replace(jsonencode(var.tags), "\"", ""), ":", "=")