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
Related
I have the following variable that I try to parse :
variable.tf
variable "rbac_roles" {
type = object(
{
view = list(object({
group_name = string,
group_id = string,
namespaces = list(string)
})),
edit = list(object({
group_name = string,
group_id = string,
namespaces = list(string)
})),
admin = list(object({
group_name = string,
group_id = string,
namespaces = list(string)
}))
}
)
}
variable.tfvars
rbac_roles = {
view = [
{
group_name = "group1",
group_id = "123",
namespaces = ["default", "namespace1"]
},
{
group_name = "group2",
group_id = "456",
namespaces = ["namespace2"]
}
],
edit = [
{
group_name = "group1",
group_id = "123",
namespaces = ["namespace2"]
}
],
admin = [
{
group_name = "group3",
group_id = "789",
namespaces = ["default, namespace1, namespace2"]
},
]
}
I try to create the following resources :
resource "kubernetes_role_binding" "view_cluster_role_binding" {
metadata {
name = ${group}-${namespace}-viewer-binding
namespace = ${namespace}
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "Role"
name = "view"
}
subject {
kind = "Group"
name = ${group}
api_group = "rbac.authorization.k8s.io"
}
}
resource "kubernetes_role_binding" "edit_cluster_role_binding" {
metadata {
name = ${group}-${namespace}-viewer-binding
namespace = ${namespace}
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "Role"
name = "edit"
}
subject {
kind = "Group"
name = ${group}
api_group = "rbac.authorization.k8s.io"
}
}
resource "kubernetes_role_binding" "admin_cluster_role_binding" {
metadata {
name = ${group}-${namespace}-viewer-binding
namespace = ${namespace}
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "Role"
name = "admin"
}
subject {
kind = "Group"
name = ${group}
api_group = "rbac.authorization.k8s.io"
}
}
So far I have tried to flatten() my list and to loop over it with for and foreach but I haven't been successful yet. From what I understand I need to use a locals{} to reach my goal, but I can't get the right syntax. Any help will be appreciated !
First this is wrong ["default, namespace1, namespace2"]. It should be ["default", "namespace1", "namespace2"]. Once you fix that, you can flatten your data structure as follows:
locals {
flat_rbac_roles = merge([
for role, groups in var.rbac_roles:
merge([
for group_idx, group in groups:
{
for namespace_idx, namespace in group["namespaces"]:
"${role}-${group_idx}-${namespace_idx}" => {
role_name = role
group_name = group["group_name"]
group_id = group["group_id"]
namespace = namespace
}
}
]...)
]...)
}
which gives:
{
"admin-0-0" = {
"group_id" = "789"
"group_name" = "group3"
"namespace" = "default"
"role_name" = "admin"
}
"admin-0-1" = {
"group_id" = "789"
"group_name" = "group3"
"namespace" = "namespace1"
"role_name" = "admin"
}
"admin-0-2" = {
"group_id" = "789"
"group_name" = "group3"
"namespace" = "namespace2"
"role_name" = "admin"
}
"edit-0-0" = {
"group_id" = "123"
"group_name" = "group1"
"namespace" = "namespace2"
"role_name" = "edit"
}
"view-0-0" = {
"group_id" = "123"
"group_name" = "group1"
"namespace" = "default"
"role_name" = "view"
}
"view-0-1" = {
"group_id" = "123"
"group_name" = "group1"
"namespace" = "namespace1"
"role_name" = "view"
}
"view-1-0" = {
"group_id" = "456"
"group_name" = "group2"
"namespace" = "namespace2"
"role_name" = "view"
}
}
Using the flatten() approach :
resource "kubernetes_role_binding" "default_roles_binding" {
for_each = {
for binding in flatten([
for role_name, groups in var.rbac_roles : [
for group in groups : [
for ns in group.namespaces : [
{
binding_name = lower("${ns}-${group.group_name}-${role_name}")
role = role_name
group_id = group.group_id
group_name = group.group_name
ns = ns
}
]
]
]]) : binding.binding_name => binding }
metadata {
namespace = each.value.ns
name = each.value.binding_name
annotations = { "group_name" : each.value.group_name }
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "Role"
name = each.value.role
}
subject {
kind = "Group"
name = each.value.group_id
}
depends_on = [
azurerm_kubernetes_cluster.aks
]
}
I am trying to pick up both elements in my query_string tuple. However, only the last element is being picked up. Below, is my test/main.tf file:
terraform {
required_version = ">= 0.13.0"
required_providers {
aws = ">= 3.58"
}
}
provider "aws" {
region = "us-east-1"
}
module "test_module" {
source = "../"
lb = [
{
name = "lb-name"
enable_deletion_protection = true
internal = false
load_balancer_type = "application"
lb_listener = [
{
port = "8080"
protocol = "HTTP"
ssl_policy = "ELBSecurityPolicy-2016-08"
default_action = {
type = "redirect"
order = 1
redirect = {
status_code = "HTTP_302"
}
}
lb_listener_rule = [
{
action = {
type = "fixed-response"
fixed_response = {
content_type = "text/plain"
message_body = "This is a message body."
}
}
condition = {
query_string = [
{
key = "health"
value = "check"
},
{
value = "bar"
}
]
}
}
]
}
]
}
]
}
Here is my lb.tf file:
variable "lb" {
description = <<DESCRIPTION
The AWS Elastic Load Balancing v2 module.
DESCRIPTION
type = any
default = []
}
locals {
lb_listener_flat_list = flatten([
for lb in var.lb : [
for lb_listener in lookup(lb, "lb_listener", []) : {
lb_name = lb.name
lb_listener_load_balancer_arn = aws_lb.lb[lb.name].arn
lb_listener_port = lookup(lb, "load_balancer_type", "application") != "gateway" ? lookup(lb_listener, "port", null) : null
lb_listener_protocol = lookup(lb, "load_balancer_type", "application") != "gateway" || lookup(lb, "ip_address_type", null) != "dualstack" ? lookup(lb_listener, "protocol", null) : null
lb_listener_ssl_policy = lookup(lb_listener, "protocol", null) != "HTTPS" || lookup(lb_listener, "protocol", null) != "TLS" ? lb_listener.ssl_policy : lookup(lb_listener, "ssl_policy", null)
lb_listener_default_action_type = lb_listener.default_action.type
lb_listener_default_action_order = lookup(lb_listener.default_action, "order", null)
lb_listener_default_action_redirect = lookup(lb_listener.default_action, "redirect", {}) != {} ? {
lb_listener_default_action_redirect_status_code = lb_listener.default_action.redirect.status_code
} : null
}
]
])
lb_listener_rule_flat_list = flatten([
for lb in var.lb : [
for lb_listener in lookup(lb, "lb_listener", []) : [
for lb_listener_rule in lookup(lb_listener, "lb_listener_rule", []) : [
# I've recently added the line below in an attempte to loop through the list of maps for query_string
for index in range(length(lookup(lb_listener_rule.condition, "query_string", []))) : {
lb_name = lb.name
lb_listener_rule_index = index
lb_listener_rule_listener_arn = aws_lb_listener.lb_listener[lb.name].arn
lb_listener_rule_action_type = lb_listener_rule.action.type
lb_listener_rule_action_fixed_response = lookup(lb_listener_rule.action, "fixed_response", {}) != {} ? {
lb_listener_rule_action_fixed_response_content_type = lb_listener_rule.action.fixed_response.content_type
lb_listener_rule_action_fixed_response_message_body = lookup(lb_listener_rule.action.fixed_response, "message_body", null)
} : null
lb_listener_rule_condition_query_string = lookup(lb_listener_rule.condition, "query_string", {}) != {} ? {
lb_listener_rule_condition_query_string_key = lookup(lb_listener_rule.condition.query_string[index], "key", null)
lb_listener_rule_condition_query_string_value = lb_listener_rule.condition.query_string[index].value
} : null
}
]
]
]
])
lb_map = { for lb in var.lb : lb.name => lb }
lb_listener_map = { for lb_listener in local.lb_listener_flat_list : lb_listener.lb_name => lb_listener }
lb_listener_rule_map = { for lb_listener_rule in local.lb_listener_rule_flat_list : "${lb_listener_rule.lb_name}-[${lb_listener_rule.lb_listener_rule_index}]" => lb_listener_rule }
}
resource "aws_lb_listener" "lb_listener" {
for_each = local.lb_listener_map
load_balancer_arn = each.value.lb_listener_load_balancer_arn
port = each.value.lb_listener_port
protocol = each.value.lb_listener_protocol
ssl_policy = each.value.lb_listener_ssl_policy
default_action {
type = each.value.lb_listener_default_action_type
order = each.value.lb_listener_default_action_order
dynamic "redirect" {
for_each = each.value.lb_listener_default_action_redirect != null ? [{}] : []
content {
status_code = each.value.lb_listener_default_action_redirect.lb_listener_default_action_redirect_status_code
}
}
}
}
resource "aws_lb_listener_rule" "lb_listener_rule" {
for_each = local.lb_listener_rule_map
listener_arn = each.value.lb_listener_rule_listener_arn
action {
type = each.value.lb_listener_rule_action_type
dynamic "fixed_response" {
for_each = each.value.lb_listener_rule_action_fixed_response != null ? [{}] : []
content {
content_type = each.value.lb_listener_rule_action_fixed_response.lb_listener_rule_action_fixed_response_content_type
message_body = each.value.lb_listener_rule_action_fixed_response.lb_listener_rule_action_fixed_response_message_body
}
}
}
condition {
dynamic "query_string" {
for_each = each.value.lb_listener_rule_condition_query_string != null ? [{}] : []
content {
key = each.value.lb_listener_rule_condition_query_string.lb_listener_rule_condition_query_string_key
value = each.value.lb_listener_rule_condition_query_string.lb_listener_rule_condition_query_string_value
}
}
}
}
resource "aws_lb" "lb" {
for_each = local.lb_map
name = lookup(each.value, "name", null)
enable_deletion_protection = lookup(each.value, "enable_deletion_protection", null)
internal = lookup(each.value, "internal", null)
ip_address_type = lookup(each.value, "ip_address_type", null)
load_balancer_type = lookup(each.value, "load_balancer_type", null)
}
Here is how the condition block looks when I run a terraform plan:
condition {
query_string {
value = "bar"
}
}
Here is how I would like the condition block to look:
condition {
query_string {
key = "health"
value = "check"
}
query_string {
value = "bar"
}
}
Your code, as complex as it is creates the two query_string correctly:
The code, in the form posted in the question has number of issues and it will not work. But then as you wrote that "My original files are very long. ", I guess that your real code actually works. Thus I only focused on its verification, rather then fixing all possible errors as I don't know what is your factual code.
I am trying to create an aws_ecs_task_definition with multiple portMappings from an array of objects. When I use a for statement it prints out an empty array of objects instead of adding the values that I expect it to add. Any idea what I am doing wrong?
Additional context:
In my actual setup the aws_ecs_task_definition is in a module and the variables are passed in when the module is called which is why I am using the for loop in the first place.
Simple as Possible Example:
variable "test" { default = [{ container_port = 80 }, { container_port = 3333 }] }
resource "aws_ecs_task_definition" "default" {
family = "foo"
container_definitions = jsonencode([
{
name = "foo"
image = "foo"
portMappings = [
for item in var.test :
{ container_port = item.container_port }
]
}
])
}
Expected (simplified for brevity):
+ resource "aws_ecs_task_definition" "default" {
+ container_definitions = jsonencode(
[
+ {
+ portMappings = [
+ {
+ container_port = 80
+ },
+ {
+ container_port = 3333
+ },
]
},
]
)
}
Actual (simplified for brevity):
+ resource "aws_ecs_task_definition" "default" {
+ container_definitions = jsonencode(
[
+ {
+ portMappings = [
+ {},
+ {},
]
},
]
)
}
It should be:
{ containerPort = item.container_port }
not
{ container_port = item.container_port }
I am writing terraform script to automate the provision of acm for domains, the issue that I am facing is how can I merge the domain and subject_alternative_names like it should pick first domain from domain_name and merge it with first block in subject_alternative_name and go on.
Variable.tf
variable "domain_name" {
description = "Configuration for alb settings"
default = [
"domain.com",
"helloworld.com",
"helloworld2.com",
]
}
variable "subject_alternative_names" {
description = "subject_alternative_names"
default = [ {
domain.com = {
"domain.com",
"domain2.com",
"domain3.com",
},
helloworld.com = {
"helloworld1.com",
"helloworld2.com"
},
hiworld.com = {
"hiworld1.com",
"hiworld2.com"
}
}]
}
variable "region" {
description = "name of the region"
default = "us-east-1"
}
variable "validation_method" {
description = "name of the region"
default = "DNS"
}
variable "tags" {
description = "name of the region"
default = "Test"
}
working variable.tf
variable "domain_name" {
description = "Configuration for alb settings"
default = [
"domain.com",
"helloworld.com",
"helloworld2.com",
"helloworld1.com",
"helloworld3.com",
]
}
variable "subject_alternative_names"{
description = "subject_alternative_names"
default = [
"domain.com",
"helloworld.com",
"helloworld2.com",
"helloworld1.com",
"helloworld3.com",
]
}
variable "region" {
description = "name of the region"
default = "us-east-1"
}
variable "validation_method" {
description = "name of the region"
default = "DNS"
}
variable "tags" {
description = "name of the region"
default = "Test"
}
main.tf
module "acm" {
count = length(var.domain_name)
source = "./modules/acm"
domain_name = var.domain_name[count.index]
validation_method = var.validation_method
tags = var.tags
subject_alternative_names = var.subject_alternative_names
}
resource.tf
variable "domain_name" {
default = ""
description = "Nmae of the domain"
}
variable "validation_method" {
default = ""
description = "Validation method DNS or EMAIL"
}
variable "tags" {
default = ""
description = "tags for the ACM certificate"
}
variable "subject_alternative_names" {
default = ""
description = "subject_alternative_names"
}
resource "aws_acm_certificate" "acm_cert" {
domain_name = var.domain_name
validation_method = var.validation_method
subject_alternative_names = var.subject_alternative_names
lifecycle {
create_before_destroy = true
}
tags = {
Name = var.tags
}
}
The easiest way would be to use a single map:
variable "domain_name_with_alternate_names" {
default = {
"domain.com" = [
"domain.com",
"domain2.com",
"domain3.com",
],
"helloworld.com" = [
"helloworld1.com",
"helloworld2.com"
],
"hiworld.com" = [
"hiworld1.com",
"hiworld2.com"
],
"hiwodd4.com" = []
}
}
module "acm" {
for_each = var.domain_name_with_alternate_names
source = "./modules/acm"
domain_name = each.key
validation_method = var.validation_method
tags = var.tags
subject_alternative_names = each.value
}
I am using version 2.0.50 of the AWS cli on Linux, and trying to create a v2 AWS WAF. Running the command aws wafv2 create-web-acl --cli-input-json file://waf.json results in the following response:
An error occurred (WAFInvalidParameterException) when calling the CreateWebACL operation: Error reason: Your statement has multiple values set for a field that requires exactly one value., field: RULE, parameter: Rule
Can somebody identify what is wrong with the following JSON, or confirm that they are seeing the same issue?
{
"DefaultAction": {
"Allow": {}
},
"Name": "test-web-acl",
"Rules": [
{
"Name": "rule-one",
"Priority": 1,
"Statement": {
"ManagedRuleGroupStatement": {
"Name": "AWSManagedRulesUnixRuleSet",
"VendorName": "AWS"
}
},
"VisibilityConfig": {
"CloudWatchMetricsEnabled": false,
"MetricName": "rule-one-metric",
"SampledRequestsEnabled": false
}
}
],
"Scope": "REGIONAL",
"VisibilityConfig": {
"CloudWatchMetricsEnabled": false,
"MetricName": "test-web-acl-metric",
"SampledRequestsEnabled": false
}
}
I can't see what is incorrect about the JSON according to the syntax described here CreateWebACL
The answer is that the OverrideAction attribute is missing from the Rule object. When adding "OverrideAction":{"None":{}} to the Rule object, then the ACL was created. The error message is misleading.
# resources.tf
resource "aws_wafv2_ip_set" "ip_whitelist" {
name = var.waf_name
scope = var.waf_scope
ip_address_version = var.waf_ip_address_version
addresses = [var.waf_addresses]
}
resource "aws_wafv2_web_acl" "web_acl" {
name = var.waf_web_acl_name
description = var.waf_description
scope = var.waf_scope
default_action {
allow {}
}
# ipsets
rule {
name = var.waf_name
priority = 0
action {
allow {}
}
statement {
ip_set_reference_statement {
arn = aws_wafv2_ip_set.ip_whitelist.arn
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = var.waf_name
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = var.waf_name
sampled_requests_enabled = true
}
dynamic "rule" {
for_each = var.rules
content {
name = rule.value.name
priority = rule.value.priority
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = rule.value.managed_rule_group_statement_name
vendor_name = rule.value.managed_rule_group_statement_vendor_name
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = rule.value.metric_name
sampled_requests_enabled = true
}
}
}
}
resource "aws_wafv2_web_acl_association" "waf_alb" {
resource_arn = aws_alb.alb.arn
web_acl_arn = aws_wafv2_web_acl.web_acl.arn
}
# variables.tf
# waf ipset name
variable "waf_name" {
type = string
default = "ip-whitelist"
}
variable "waf_scope" {
type = string
default = "REGIONAL"
}
variable "waf_ip_address_version" {
default = "IPV4"
type=string
}
variable "waf_addresses" {
default = "your-ip/32"
type = string
}
# waf details
variable "waf_web_acl_name" {
type=string
default = "waf-rules"
}
variable "waf_description" {
type=string
default = "waf rules"
}
# waf multiple rules
variable "rules" {
type = list(any)
default = [
{
name = "AWS-AWSManagedRulesAdminProtectionRuleSet"
priority = 1
managed_rule_group_statement_name = "AWSManagedRulesAdminProtectionRuleSet"
managed_rule_group_statement_vendor_name = "AWS"
metric_name = "foo_name"
},
{
name = "AWS-AWSManagedRulesPHPRuleSet"
priority = 2
managed_rule_group_statement_name = "AWSManagedRulesPHPRuleSet"
managed_rule_group_statement_vendor_name = "AWS"
metric_name = "foo_name"
},
{
name = "AWS-AWSManagedRulesLinuxRuleSet"
priority = 3
managed_rule_group_statement_name = "AWSManagedRulesLinuxRuleSet"
managed_rule_group_statement_vendor_name = "AWS"
metric_name = "foo_name"
},
{
name = "AWS-AWSManagedRulesAmazonIpReputationList"
priority = 4
managed_rule_group_statement_name = "AWSManagedRulesAmazonIpReputationList"
managed_rule_group_statement_vendor_name = "AWS"
metric_name = "foo_name"
},
{
name = "AWS-AWSManagedRulesSQLiRuleSet"
priority = 5
managed_rule_group_statement_name = "AWSManagedRulesSQLiRuleSet"
managed_rule_group_statement_vendor_name = "AWS"
metric_name = "foo_name"
},
{
name = "AWS-AWSManagedRulesUnixRuleSet"
priority = 6
managed_rule_group_statement_name = "AWSManagedRulesUnixRuleSet"
managed_rule_group_statement_vendor_name = "AWS"
metric_name = "foo_name"
},
{
name = "AWS-AWSManagedRulesCommonRuleSet"
priority = 7
managed_rule_group_statement_name = "AWSManagedRulesCommonRuleSet"
managed_rule_group_statement_vendor_name = "AWS"
metric_name = "foo_name"
}
]
}