I want to create a resource for many regions, environments, apps, etc at once.
I'd like to do something like this:
param apps array = [
'app1'
'app2'
'app3'
]
param environments array = [
'alpha'
'beta'
]
param regions array = [
'ne'
'we'
'uks'
]
resource origin_group 'Microsoft.Cdn/profiles/origingroups#2021-06-01' = [ for region in regions: {
[ for env in environments: {
[ for app in apps: {
parent: profiles_global_fd_name_resource
name: '${env}-${region}-${app}-origin-group'
properties: {
loadBalancingSettings: {
sampleSize: 4
successfulSamplesRequired: 3
additionalLatencyInMilliseconds: 50
}
healthProbeSettings: {
probePath: '/'
probeRequestType: 'HEAD'
probeProtocol: 'Http'
probeIntervalInSeconds: 100
}
sessionAffinityState: 'Disabled'
}
}]
}]
}]
All docs mentioning nested loops talk about looping inside a resource to create many sub-resources. Not what I'm after. Perhaps another way would be to somehow merge all these arrays into a single array of objects of every possible iteration. Not sure where to start with that either.
Any help much appreciated.
This is not supported for the moment but it will (see Is there plans to support nested loop on resources?).
Using a little bit of math, you could achieve what you'd like (Not sure if you should):
param environments array = [ 'alpha', 'beta' ]
param regions array = [ 'ne', 'we', 'uks' ]
param apps array = [ 'app1', 'app2', 'app3' ]
// Setting some variables for clarity
var envCount = length(environments)
var regionCount = length(regions)
var appCount = length(apps)
// Setting the total number of combination
var originGroupCount = envCount * regionCount * appCount
// Iterate all possible combinations
output originGroupNames array = [for i in range(0, originGroupCount): {
name: '${environments[i / (regionCount * appCount) % envCount]}-${regions[i / appCount % regionCount]}-${apps[i % appCount]}-origin-group'
}]
this will output all possible combinations (I think) for origin group name.
Related
I have an issue where I want to pass a list of vpc_ids to aws_route53_zone while getting the id from a couple of module calls and iterating it from the state file.
The out put format I am using is:
output "development_vpc_id" {
value = [for vpc in values(module.layout)[*] : vpc.id if vpc.environment == "development"]
description = "VPC id for development env"
}
where I get the output like:
"development_vpc_id": {
"value": [
"xxxx"
],
"type": [
"tuple",
[
"string"
]
]
},
instead I want to achieve below:
"developmemt_vpc_id": {
"value": "xxx",
"type": "string"
},
Can someone please help me with the same.
There isn't any automatic way to "convert" a sequence of strings into a single string, because you need to decide how you want to represent the multiple separate strings once you've reduced it into only a single string.
One solution would be to apply JSON encoding so that your output value is a string containing JSON array syntax:
output "development_vpc_id" {
value = jsonencode([
for vpc in values(module.layout)[*] : vpc.id
if vpc.environment == "development"
])
}
Another possibility is to concatenate all of the strings together with a particular character as a marker to separate each one, such as a comma:
output "development_vpc_id" {
value = join(",", [
for vpc in values(module.layout)[*] : vpc.id
if vpc.environment == "development"
])
}
If you expect that this list will always contain exactly one item -- that is, if each of your objects has a unique environment value -- then you could also tell Terraform about that assumption using the one function:
output "development_vpc_id" {
value = one([
for vpc in values(module.layout)[*] : vpc.id
if vpc.environment == "development"
])
}
In this case, Terraform will either return the one element of this sequence or will raise an error saying there are too many items in the sequence. The one function therefore acts as an assertion to help you detect if there's a bug which causes there to be more than one item in this list, rather than just silently discarding some of the items.
Is it possible to construct a data structure given the input variable below, where you append the name value to each value in the stages list;
environments = [
{
name = "preprod"
stages = ["blue", "green"]
},
{
name = "qat"
stages = ["blue", "green"]
}
]
so that I end up with a data structure that looks like this once flattened:
local.transformed = [
"preprod-blue",
"preprod-green",
"prod-blue",
"prod-green"
]
You can use flatten:
locals {
transformed = flatten([for val in var.environments: [
for color in val.stages:
"${val.name}-${color}"
]
])
}
I am trying to create a predefined set of IAM roles.
locals {
default_iam_roles = {
group1 = {
name = "group:group1-group#mydomain.com"
roles = toset([
"roles/viewer"
])
}
group2 = {
name = "group:group2-group#mydomain.com"
roles = toset([
"roles/owner"
])
}
}
formatted_iam_roles = [ for member in local.default_iam_roles : setproduct([member.name], member.roles) ]
}
If I print local.formatted_iam_roles I get the following:
[
toset([
[
"group:group1-group#mydomain.com",
"roles/viewer",
],
]),
toset([
[
"group:group2-group#mydomain.com",
"roles/owner",
],
]),
]
Now I want to create a single set which contains all of the combinations contained in the list, so that I can feed it to a resource on a for_each statement, but I am not able to find the logic for this.
The expected output would be:
toset([
[
"group:group1-group#mydomain.com",
"roles/viewer",
],
[
"group:group2-group#mydomain.com",
"roles/owner",
]
])
The operation of taking multiple sets and deriving a new set which contains all of the elements across all of the input sets is called union and so Terraform's function for it is called setunion to follow that.
locals {
all_iam_roles = setunion(local.formatted_iam_roles...)
}
The ... symbol after the argument tells Terraform that it should use each element of local.formatted_iam_roles as a separate argument to setunion, because that function is defined as taking an arbitrary number of arguments that are all sets, rather than as taking a list of sets.
It might be helpful to think of setunion as being a similar sort of function as concat. The concat function joins multiple lists together, preserving each list's element order and the order the lists are given. Set elements don't have any particular order and contain each distinct value only once, and so the behavior of setunion is different but its purpose is related.
Your solution in the question was close, you just have to apply a flatten function to the setproduct output.
locals {
# ...
formatted_iam_roles = [for member in local.default_iam_roles : flatten(setproduct([member.name], member.roles))]
}
The output will be the following:
formatted_iam_roles = [
[
"group:group1-group#mydomain.com",
"roles/viewer",
],
[
"group:group2-group#mydomain.com",
"roles/owner",
],
]
Now, if you really want it to make a set for the final result, you can use toset, most like it will be pointless:
formatted_iam_roles = toset([for member in local.default_iam_roles : flatten(setproduct([member.name], member.roles))])
I finally faced the problem using maps instead of lists, because I needed to use a for_each argument and it doesn't accept a set of tuples as value.
If anyone knows a better approach, post it and I will be pleased of marking it as correct.
locals {
formatted_iam_roles = merge([ for member in local.default_iam_roles :
{ for pair in setproduct([member.name], member.roles) :
"${pair[0]}${pair[1]}" => { "name": pair[0], "role": pair[1] }
}
]...)
}
resource "google_project_iam_member" "team_access" {
for_each = local.formatted_iam_roles
project = var.project_id
member = each.value["name"]
role = each.value["role"]
}
I was trying to create a dynamic cloud-config files using templatefile(file, vars). In my template I invoke the jsonencode(var) function in order to render valid yaml. As a mre :
#cloud-init
${jsonencode({
"groups": [ for g in groups :
{
g.name
}
],
"users": [ for u in users :
{
"name": u.name,
"gecos": u.gecos,
"homedir": u.homedir,
}
})}
locals {
json-user-data = templatefile(
"${path.module}/templates/cloud-init.tpl",
{
groups = var.groups,
users = var.users
}
}
What I'd like to obtain is something like :
groups:
- ubuntu: [root,sys]
- cloud-users
Where the groups member attribute is optional. I'm having a hard time achiving the result also because Terraform does not like mixed objects. Is there a way to achive something similar?
Thanks for helping, and let me know how to improve my question.
I have a yaml file where I can specify n number of options:
---
solr:
- dev
- test
I then call them into a puppet variable using heira:
if $solr_values == undef {
$solr_values = hiera('solr', false)
}
if count($solr_values) > 0 {
class { solr:
cores => [ $solr_values ],
}
}
However $solr_values is coming through as 'devtest' and not 'dev'. 'test' as I'd expect given that it's a list in yaml. Can someone advise on the best approach here?
The expression [ $solr_values ] gives you an array of arrays, e.g. [ [ 'dev', 'test', ... ] ], which is likely not what you want.
I suggest plain
cores => $solr_values,
Note that when you use the array variable $core/$solr_values in a string, e.g.
$debug = "VALUES: '$solr_values'"
Puppet will coerce the array into a string by simply concatenating the values, so you will still end up with 'devtest...'
What you want to do is make use of the join function from the stdlib module, e.g.
cores => join($solr_values, ','),