My input is as below:
list_groups = [
{
dev-api = {
envs = [
"dev-eu-1",
"dev-eu-2",
]
hosts = [
"dev-api-eu1",
"dev-api-eu2",
]
}
},
{
dev-api = {
envs = [
"dev-us-1",
"dev-us-2",
]
hosts = [
"dev-api-us1",
"dev-api-us2",
]
}
},
]
I am using merge as below:
output "map_groups" {
value = merge(var.list_groups...)
}
And the output I get is :
map_groups = {
dev-api = {
envs = [
"dev-us-1",
"dev-us-2",
]
hosts = [
"dev-api-us1",
"dev-api-us2",
]
}
}
But I need the output to be:
map_groups = {
dev-api = {
envs = [
"dev-us-1",
"dev-us-2",
"dev-eu-1",
"dev-eu-2"
]
hosts = [
"dev-api-us1",
"dev-api-us2",
"dev-api-eu1",
"dev-api-eu2"
]
}
}
I understand the merge is eating up one of the entries because the key is the same but if it could somehow merge the entries/elements, that'll be great.
This is a rather complex issue. I think you should concentrate on simplifying your input data, rather than trying to come out with some convoluted TF code to post-fix your input data structures.
Nevertheless, the expansion symbol ... can be used to solve the issue in terraform.
variable "list_groups" {
default = [
{
dev-api = {
envs = [
"dev-eu-1",
"dev-eu-2",
]
hosts = [
"dev-api-eu1",
"dev-api-eu2",
]
}
},
{
dev-api = {
envs = [
"dev-us-1",
"dev-us-2",
]
hosts = [
"dev-api-us1",
"dev-api-us2",
]
}
},
]
}
locals {
api_names = distinct([for api in var.list_groups: keys(api)[0]])
}
output "test" {
value = {
for key, val in {
for api_name in local.api_names:
api_name => {
envs = flatten([
for api in var.list_groups:
api[api_name].envs
])
hosts = flatten([
for api in var.list_groups:
api[api_name].hosts
])
}...
}:
key => val[0]
}
}
Output is:
test = {
"dev-api" = {
"envs" = [
"dev-eu-1",
"dev-eu-2",
"dev-us-1",
"dev-us-2",
]
"hosts" = [
"dev-api-eu1",
"dev-api-eu2",
"dev-api-us1",
"dev-api-us2",
]
}
}
Related
I am trying to parse this variable in terraform. The main goal is to create map of "group" = "member" type. Did i set type in variable correct? Required output provided below.
variable "iam" "this"{
type = map(map(list(string)))
default = {
"admins" = {
"user" = [
"oleh_mykolaishyn#gmail.com",
"yevhen_plaksa#gmail.com"
]
"service_principal" = [
"dpaf2-dev-sa"
]
}
"dev" = {
"user" = [
"oleh_mykolaishyn#gmail.com",
"yevhen_plaksa#gmail.com"
]
"service_principal" = []
}
"ops" = {
"user" = [
"oleksandr_gorkun#gmail.com",
]
"service_principal" = [
"dpaf2-dev-sa",
"dpaf2-iacda-app-id",
]
}
}
}
Required output:
{
"admins" = "oleh_mykolaishyn#gmail.com"
"admins" = "yevhen_plaksa#gmail.com"
"admins" = "dpaf2-dev-sa"
"dev" = "oleh_mykolaishyn#gmail.com"
"dev" = "yevhen_plaksa#gmail.com"
"ops" = "oleksandr_gorkun#gmail.com"
"ops" = "dpaf2-dev-sa"
"ops" = "dpaf2-iacda-app-id"
}
Start with this and manipulate it to suit your exact needs. There are many ways to structure resources using maps, arrays and nesting.
locals {
service_principal = {
admin = ["dpaf2-dev-sa"]
dev = []
ops = [
"dpaf2-dev-sa",
"dpaf2-iacda-app-id",
]
}
users = {
admin = [
"oleh_mykolaishyn#gmail.com",
"yevhen_plaksa#gmail.com"
]
dev = [
"yevhen_plaksa#gmail.com"
]
ops = [
"oleksandr_gorkun#gmail.com"
]
}
result = flatten([for role in keys(local.users) : [for user in local.users[role] :
{ role = role, user = user, principals = local.service_principal[role] }]])
}
resource "null_resource" "users" {
count = length(local.result)
provisioner "local-exec" {
command = "echo user: $USER has role: $ROLE and principals: $SERVICE_PRINCIPAL >> ouput.txt"
environment = {
USER = local.result[count.index].user
ROLE = local.result[count.index].role
SERVICE_PRINCIPAL = join(", ", local.result[count.index].principals)
}
}
}
If you run:
terraform init
terraform plan
terraform apply --auto-approve
Then check your directory for a file named output.txt which will contain
user:oleh_mykolaishyn#gmail.com has role:admin and principals: dpaf2-dev-sa
user:yevhen_plaksa#gmail.com has role:dev and principals:
user:oleksandr_gorkun#gmail.com has role:ops and principals: dpaf2-dev-sa, dpaf2-iacda-app-id
user:yevhen_plaksa#gmail.com has role:admin and principals: dpaf2-dev-sa
I know the text file isn't your desired output but the loop is just a demonstration of how you can iterate over the local variable.
Again, many ways to sort and slice it. I avoided your desired output because maps cannot have duplicate keys so it'll never work.
If you want to see the result in isolation you can do:
terraform console
local.result
And it will appear in the terminal. To exit the console type exit.
I am creating dashboard for ecs but loadbalancer name is getting [] in "[ "mis-app-cluster" ]" because of list of string type in variable.
Is there any by which we can remove [] from the value.
List of string is mandatory.
data -
{
my-dashboard-name = "MIS-Dev-Cloudwatch-Dashboard-ECS"
aws-region = "eu-west-1"
targets = ["mis-app-cluster"]
metrics = ["CPUUtilization", "MemoryUtilization"]
service_name = ["mis-fileserver", "mis-nginx", "mis-odi", "mis-oraclebi", "mis-ords", "mis-restdataservices"]
aws-namespace = "AWS/ECS"
dim = "ClusterName"
stat = "Average"
period = 300
}
module (I have only added for ALB module below)
locals {
widget-def-alb = [for metric in var.metrics :
{
type = "metric",
x = 0,
y = 0
width = 18,
height = 8,
properties = {
metrics = [for targetgroup in var.target_groups : [var.aws-namespace, metric,"TargetGroup", targetgroup, var.dim, var.targets]],
title = "${var.aws-namespace}: ${metric}",
region = var.aws-region,
period = var.period,
stat = var.stat,
view = "timeSeries",
legend = {
position = "right"
}
}
}
]
}
resource "aws_cloudwatch_dashboard" "cw-dashboard-alb" {
count = var.aws-namespace == "AWS/ApplicationELB" ? 1 : 0
dashboard_name = var.dashboard-name
dashboard_body = jsonencode({
start = "-PT9H"
widgets = local.widget-def-alb
})
}
Code -
module "create-dashboard" {
source = "../"
for_each = { for service in local.dashboards : service.my-dashboard-name => service if length(regexall(".*ALB.*", service.my-dashboard-name))
> 0 }
dashboard-name = each.value.my-dashboard-name
aws-region = each.value.aws-region
targets = each.value.targets
metrics = each.value.metrics
aws-namespace = each.value.aws-namespace
dim = each.value.dim
target_groups = each.value.target_groups
stat = each.value.stat
period = each.value.period
}
output
{
"legend": {
"position": "right"
},
"metrics": [
[ "AWS/ECS", "CPUUtilization", "ServiceName", "mis-fileserver", "ClusterName", [ "mis-app-cluster" ] ],
[ ".", "MemoryUtilization", ".", ".", ".", [ "mis-app-cluster" ] ]
],
"period": 300,
"region": "eu-west-1",
"stat": "Average",
"title": "mis-fileserver",
"view": "timeSeries",
"timezone": "UTC"
}
I am working on setting up VPC Service control in GCP using google_access_context_manager_service_perimeter provider. In this resource, within status block, I have to specify a list of google projects as resources value in the format "projects/123456789". I want to put the project numbers in a variable and created something like this.
variable "project_numbers_to_protect" {
type = list(any)
default = [
"123456",
"456789",
"894321"
]
}
I am able to reference the variable as below.
resources = ["projects/${var.project_numbers_to_protect[0]}",
"projects/${var.project_numbers_to_protect[1]}",
"projects/${var.project_numbers_to_protect[2]}"]
But in my production case, I have a large number of projects in the list and I am looking for option to reference it dynamically. I tried count option, but that didn't work.
count = var.project_numbers_to_protect
resources = ["projects/${var.project_numbers_to_protect[count.index]}"]
Error message
vpc-sc-module $ terraform validate
╷
│ Error: Reference to "count" in non-counted context
│
│ on vpc-sc-copy.tf line 16, in resource "google_access_context_manager_service_perimeter" "regular_service_perimeter":
│ 16: resources = ["projects/${var.project_numbers_to_protect[count.index]}"]
│
│ The "count" object can only be used in "module", "resource", and "data" blocks, and only when the "count" argument is set.
╵
Appreciate any help. Thanks.
Full Code
vpc-sc-copy.tf
resource "google_access_context_manager_service_perimeter" "regular_service_perimeter" {
parent = "accessPolicies/${var.access_context_manager_policy_number}"
name = "accessPolicies/${var.access_context_manager_policy_number}/servicePerimeters/${var.perimeter_name}"
perimeter_type = var.perimeter_type
title = var.perimeter_name
use_explicit_dry_run_spec = false
status {
restricted_services = var.restricted_services
## Below two lines works.
# resources = ["projects/${var.project_numbers_to_protect[0]}",
# "projects/${var.project_numbers_to_protect[1]}",]
## Below option doesn't work
count = var.project_numbers_to_protect
resources = ["projects/${var.project_numbers_to_protect[count.index]}"]
ingress_policies {
ingress_from {
identity_type = "ANY_IDENTITY"
sources {
access_level = "*"
}
}
ingress_to {
resources = [
"*"
]
dynamic "operations" {
for_each = var.ingress_rule1_service_name
content {
service_name = operations.value
method_selectors {
method = "*"
}
}
}
}
}
egress_policies {
egress_from {
identities = ["serviceAccount:service-${var.project_number_to_protect}#gcp-sa-aiplatform-cc.iam.gserviceaccount.com"]
}
egress_to {
resources = [
"projects/${var.egress_rule1_project_number}"
]
operations {
service_name = "storage.googleapis.com"
dynamic "method_selectors" {
for_each = var.egress_rule1_methods
content {
method = method_selectors.value
}
}
}
}
}
egress_policies {
egress_from {
identity_type = "ANY_IDENTITY"
}
egress_to {
resources = [
"projects/${var.egress_rule2_project_number}"
]
operations {
service_name = "storage.googleapis.com"
dynamic "method_selectors" {
for_each = var.egress_rule2_methods
content {
method = method_selectors.value
}
}
}
}
}
}
}
Relevant section of vars.tf
variable "project_numbers_to_protect" {
type = list(any)
default = [
"123456",
"456789",
"894321"
]
}
As the error writes, you can't use count the way you want. Instead it should be:
resource "google_access_context_manager_service_perimeter" "regular_service_perimeter" {
parent = "accessPolicies/${var.access_context_manager_policy_number}"
name = "accessPolicies/${var.access_context_manager_policy_number}/servicePerimeters/${var.perimeter_name}"
perimeter_type = var.perimeter_type
title = var.perimeter_name
use_explicit_dry_run_spec = false
status {
restricted_services = var.restricted_services
resources = [for project_number in var.project_numbers_to_protect:
"projects/${project_number}" ]
ingress_policies {
ingress_from {
identity_type = "ANY_IDENTITY"
sources {
access_level = "*"
}
}
ingress_to {
resources = [
"*"
]
dynamic "operations" {
for_each = var.ingress_rule1_service_name
content {
service_name = operations.value
method_selectors {
method = "*"
}
}
}
}
}
egress_policies {
egress_from {
identities = ["serviceAccount:service-${var.project_number_to_protect}#gcp-sa-aiplatform-cc.iam.gserviceaccount.com"]
}
egress_to {
resources = [
"projects/${var.egress_rule1_project_number}"
]
operations {
service_name = "storage.googleapis.com"
dynamic "method_selectors" {
for_each = var.egress_rule1_methods
content {
method = method_selectors.value
}
}
}
}
}
egress_policies {
egress_from {
identity_type = "ANY_IDENTITY"
}
egress_to {
resources = [
"projects/${var.egress_rule2_project_number}"
]
operations {
service_name = "storage.googleapis.com"
dynamic "method_selectors" {
for_each = var.egress_rule2_methods
content {
method = method_selectors.value
}
}
}
}
}
}
}
I have a data structure and I need to extract a list out of a map of lists based on the maps key. Here is the sample data:
locals {
firwall_rules = ["first", "third"] # this is the filter used on firewall_rules_lookup
firewall_rules_lookup = {
type = map
"first" = [ { name ="rule1.1", start_ip="0.0.0.1" , end_ip = "0.0.0.2" },
{ name ="rule1.2", start_ip="0.0.0.4" , end_ip = "0.0.0.5" },
],
"second"= [ { name ="rule2.1", start_ip="0.0.1.1" , end_ip = "0.0.1.2" } ],
"third" = [ { name ="rule3.1", start_ip="0.0.3.1" , end_ip = "0.0.3.2" },
{ name ="rule3.2", start_ip="0.0.3.4" , end_ip = "0.0.3.5" },
]
}
fw_rules = flatten([
for rule_name in local.firewall_rules : {
for r in local.firewall_rules_lookup[rule_name] : {
name = r.name
start_ip = r.start_ip
end_ip = r.end_ip
}
}
])
}
Expected result:
fw_rules=
[ { name ="rule1.1", start_ip="0.0.0.1" , end_ip = "0.0.0.2" },
{ name ="rule1.2", start_ip="0.0.0.4" , end_ip = "0.0.0.5" },
{ name ="rule3.1", start_ip="0.0.3.1" , end_ip = "0.0.3.2" },
{ name ="rule3.2", start_ip="0.0.3.4" , end_ip = "0.0.3.5" }
]
The inner for loop is not working. Terraform gives me an error. I think the for loops
work only with maps. Is there a different solution to this problem?
It should be as follows:
locals {
firewall_rules = ["first", "third"] # this is the filter used on firewall_rules_lookup
firewall_rules_lookup = {
"first" = [ { name ="rule1.1", start_ip="0.0.0.1" , end_ip = "0.0.0.2" },
{ name ="rule1.2", start_ip="0.0.0.4" , end_ip = "0.0.0.5" },
],
"second"= [ { name ="rule2.1", start_ip="0.0.1.1" , end_ip = "0.0.1.2" } ],
"third" = [ { name ="rule3.1", start_ip="0.0.3.1" , end_ip = "0.0.3.2" },
{ name ="rule3.2", start_ip="0.0.3.4" , end_ip = "0.0.3.5" },
]
}
fw_rules = flatten([
for rule_name in local.firewall_rules : [
for r in local.firewall_rules_lookup[rule_name] : {
name = r.name
start_ip = r.start_ip
end_ip = r.end_ip
}
]
])
}
I have this codeblock
launchpad_tmpl = {
apiVersion = "<Link for the API>"
kind = "mke"
spec = {
mke = {
adminUsername = "admin"
adminPassword = var.admin_password
installFlags : [
"--default-node-orchestrator=kubernetes",
"--san=${module.masters.lb_dns_name}",
]
}
hosts = concat(local.managers, local.msrs, local.workers, local.windows_workers)
}
}
}
And I have another codeblock
launchpad_tmpl = {
apiVersion = "<Some link for the API>"
kind = "mke"
spec = {
mke = {
adminUsername = "admin"
adminPassword = var.admin_password
installFlags : [
"--default-node-orchestrator=kubernetes",
"--san=${module.masters.lb_dns_name}",
]
}
msr = {
...some_code
}
hosts = concat(local.managers, local.msrs, local.workers, local.windows_workers)
}
}
}
I want to tell terraform something like
IF msr_count >= 1 - Use second codeblock to run
ELSE use the first one...
Is it possible in Terraform Or I will need to make a wrapper somewhere?
Thanks in Advance.
UPDATE:::
Error: Inconsistent conditional result types
on main.tf line 162, in output "mke_cluster":
162: value = yamlencode(var.msr_count > 0 ? local.msr_launchpad_tmpl : local.mke_launchpad_tmpl)
|----------------
| local.mke_launchpad_tmpl is object with 3 attributes
| local.msr_launchpad_tmpl is object with 3 attributes
| var.msr_count is 1
The true and false result expressions must have consistent types. The given
expressions are object and object, respectively.
This is the output when it placed like this:
mke_launchpad_tmpl = {
apiVersion = "API"
kind = "mke"
spec = {
mke = {
adminUsername = "admin"
adminPassword = var.admin_password
installFlags : [
"--default-node-orchestrator=kubernetes",
"--san=${module.masters.lb_dns_name}",
]
}
hosts = concat(local.managers, local.msrs, local.workers, local.windows_workers)
}
}
msr_launchpad_tmpl = {
apiVersion = "API"
kind = "mke+msr"
spec = {
mke = {
adminUsername = "admin"
adminPassword = var.admin_password
installFlags : [
"--default-node-orchestrator=kubernetes",
"--san=${module.masters.lb_dns_name}",
]
}
msr = {
installFlags : [
"--ucp-insecure-tls",
"--dtr-external-url ${module.msrs.lb_dns_name}",
]
}
hosts = concat(local.managers, local.msrs, local.workers, local.windows_workers)
}
}
launchpad_tmpl = var.msr_count > 0 ? local.msr_launchpad_tmpl : local.mke_launchpad_tmpl
}
output "mke_cluster" {
value = yamlencode(local.launchpad_tmpl)
}
You could use conditional expression. Also for simplicity, you can create the two alternatives as locals, which will simplify the expression.
For example:
locals {
mke_launchpad_tmpl = {
apiVersion = "API"
kind = "mke"
spec = {
mke = {
adminUsername = "admin"
adminPassword = var.admin_password
installFlags : [
"--default-node-orchestrator=kubernetes",
"--san=${module.masters.lb_dns_name}",
]
}
msr = {}
hosts = concat(local.managers, local.msrs, local.workers, local.windows_workers)
}
}
msr_launchpad_tmpl = {
apiVersion = "API"
kind = "mke+msr"
spec = {
mke = {
adminUsername = "admin"
adminPassword = var.admin_password
installFlags : [
"--default-node-orchestrator=kubernetes",
"--san=${module.masters.lb_dns_name}",
]
}
msr = {
installFlags : [
"--ucp-insecure-tls",
"--dtr-external-url ${module.msrs.lb_dns_name}",
]
}
hosts = concat(local.managers, local.msrs, local.workers, local.windows_workers)
}
}
}
Then, in your resource:
launchpad_tmpl = var.msr_count > -1 ? local.second_launchpad_tmpl : local.first_launchpad_tmpl