I have lost the flow of this 'flatten' sequence and need help in ensuring that the traffic manager endpoints are properly represented in the flatten sequence. Although I have gotten to a point where terraform plan works, it is creating the endpoints by Index[0] and Index[1] instead of Indexing it by name.
Below is the code and sample input (in json format). The Azure Traffic Manager profile and Traffic Endpoints (Azure endpoint) exist in the same module.
Plan Output
# module.infra.azurerm_traffic_manager_azure_endpoint.tm_azure_endpoint["0"] will be created
+ resource "azurerm_traffic_manager_azure_endpoint" "tm_azure_endpoint" {
+ enabled = true
+ geo_mappings = [
+ "US",
]
+ id = (known after apply)
+ name = "0"
+ priority = 1
+ profile_id = "/subscriptions/3f128ee2-eef5-4ede-ac7e-42cf4a4f8632/resourceGroups/rg-eastus-tmprofile/providers/Microsoft.Network/trafficManagerProfiles/traf-eastus-01"
+ target_resource_id = "/subscriptions/3f128ee2-eef5-4ede-ac7e-42cf4a4f8632/resourceGroups/rg-eastus-tmprofile/providers/Microsoft.Network/publicIPAddresses/pip-core-vng-eus-01"
+ weight = 1
}
# module.infra.azurerm_traffic_manager_azure_endpoint.tm_azure_endpoint["1"] will be created
+ resource "azurerm_traffic_manager_azure_endpoint" "tm_azure_endpoint" {
+ enabled = true
+ geo_mappings = [
+ "US",
]
+ id = (known after apply)
+ name = "1"
+ priority = 1
+ profile_id = "/subscriptions/3f128ee2-eef5-4ede-ac7e-42cf4a4f8632/resourceGroups/rg-eastus-taf-eastus-01"
+ target_resource_id = (known after apply)
+ weight = 1
}
MAIN
locals {
tm_profile = {
for k, v in try(local.inputs.tm_profiles, {}) : k => merge(
{
existing = false
traffic_view_enabled = false //optional feature. $2 per million data points processed. Data points are essentially queries into Traffic Manager.
interval_in_seconds = 30
timeout_in_seconds = 10
tolerated_number_of_failures = 3
},
v,
{
tags = merge(
local.tags,
try(v.tags, {})
)
}
)
}
tmprofile_endpoints = flatten([
for tm_k, tm_v in try(local.inputs.tm_profiles, {}) : [
for tm_endpoints_k, tm_endpoints_v in try(tm_v.tm_endpoints, {}) : merge(
tm_endpoints_v,
{
name = tm_endpoints_k
profile_id = azurerm_traffic_manager_profile.traffic_manager_profile[tm_endpoints_v["tm_profile_name"]].id
target_resource_id = azurerm_public_ip.pub_ips[tm_endpoints_v["pub_ip"]].id
enabled = true
protocol = tm_v.protocol
}
)
]
])
}
resource "azurerm_traffic_manager_profile" "traffic_manager_profile" {
for_each = {
for k, v in local.tm_profile : k => v if !v.existing
}
name = each.key
resource_group_name = each.value.rg
profile_status = each.value.profile_status
traffic_routing_method = each.value.traffic_routing_method
dns_config {
relative_name = each.value.relative_name
ttl = each.value.ttl
}
monitor_config {
protocol = each.value.protocol
port = each.value.port
path = (each.value.protocol != "TCP") ? each.value.path : lookup(each.value, "path", null)
expected_status_code_ranges = try(each.value.expected_status_code_ranges, "200")
dynamic "custom_header" {
for_each = (each.value.protocol == "HTTP" || each.value.protocol == "HTTPS") ? try(each.value.custom_headers, {}) : {}
content {
name = each.value.name
value = each.value.value
}
}
interval_in_seconds = each.value.interval_in_seconds
timeout_in_seconds = each.value.timeout_in_seconds
tolerated_number_of_failures = each.value.tolerated_number_of_failures
}
tags = each.value.tags
depends_on = [
azurerm_resource_group.rgs
]
}
resource "azurerm_traffic_manager_azure_endpoint" "tm_azure_endpoint" {
for_each = {
for k, v in local.tmprofile_endpoints : k => v }
name = each.key
profile_id = each.value.profile_id
target_resource_id = each.value.target_resource_id
weight = each.value.weight
dynamic "custom_header" {
for_each = (each.value.protocol == "HTTP" || each.value.protocol == "HTTPS") ? try(each.value.custom_headers, {}) : {}
content {
name = each.value.name
value = each.value.value
}
}
enabled = each.value.enabled
geo_mappings = each.value.geo_mappings
priority = each.value.priority
depends_on = [
azurerm_traffic_manager_profile.traffic_manager_profile
]
}
/*
output "tmprofile_endpoints_out" {
value = local.tmprofile_endpoints
}
*/
Sample Input
"tm_profiles": {
"traf-eastus-01": {
"rg": "rg-eastus-tmprofile",
"profile_status": "Enabled",
"traffic_routing_method": "Performance",
"relative_name": "azmech",
"ttl": 3600,
"protocol": "HTTPS",
"port": 443,
"path": "/",
"expected_status_code_ranges": [
"200-201"
],
"custom_header": {
"name": "customheader",
"value": null
},
"tags": {
"tm_profile": "tm_test"
},
"tm_endpoints": {
"tfendpoint1": {
"tm_profile_name": "traf-eastus-01",
"weight": "1",
"custom_header": {
"name": "customheader",
"value": null
},
"geo_mappings": [
"US"
],
"priority": "1",
"pub_ip": "pip-core-vng-eus-01"
},
"tfendpoint2": {
"tm_profile_name": "traf-eastus-01",
"weight": "1",
"custom_header": {
"name": "customheader",
"value": null
},
"geo_mappings": [
"US"
],
"priority": "1",
"pub_ip": "pip-core-vng-wus-01"
}
}
}
}
nice code ;)
If I am getting it right, the flatten causes the indexing. Since flatten works with lists, local.tmprofile_endpoints is a list.
I would project it back into a map where each key is unique. You can try to produce a single unique key per instance like this:
local_tmprofile_endpoints_map = {
for endpoint in local.tmprofile_endpoints : "${endpoint.tm_profile_name}" => endpoint
}
..and work on this from now on.
Related
I am doing the following in my terraform module:
data "vault_policy_document" "this" {
dynamic "rule" {
for_each = {
for p in var.policy.policy_content : format("%s-%s-%s", p.path, join(",", p.capabilities)) => p
}
content {
path = rule.value.path
capabilities = rule.value.capabilities
}
}
}
The variable is declared as
variable "policy" {
description = "The policy to be created"
type = map(any)
}
and with a certain confidence, it is of the form
{
+ "policy-test-1" = {
+ policy_content = [
+ {
+ capabilities = [
+ "read",
+ "create",
]
+ path = "/foo/lala"
},
+ {
+ capabilities = [
+ "read",
+ "create",
]
+ path = "/bar/lala"
},
]
}
},
Why is the code failing to access the policy_content element?
Your initial attempt isn't iterating over the keys of the policy map, which you can fix by looping the policy document datasource itself over the map:
data "vault_policy_document" "this" {
for_each = var.policy
dynamic "rule" {
for_each = {
for p in each.value.policy_content : format("%s-%s", p.path, join(",", p.capabilities)) => p
}
content {
path = rule.value.path
capabilities = rule.value.capabilities
}
}
}
resource "vault_policy" "this" {
for_each = var.policy
name = each.key
policy = data.vault_policy_document.this[each.key].hcl
}
This can probably be cleaned up further (like by iterating the policy resource over the data source elements rather than the same policy var again).
But it does produce what looks like the right plan, to me:
# vault_policy.this["policy-test-1"] will be created
+ resource "vault_policy" "this" {
+ id = (known after apply)
+ name = "policy-test-1"
+ policy = <<-EOT
path "/bar/lala" {
capabilities = ["read", "create"]
}
path "/foo/lala" {
capabilities = ["read", "create"]
}
EOT
}
I create my cdn endpoints as with the code below. Also I have one variable "environment" which either has the value "dev" or "prod". The delivery rule "DevRedirectToWWW" should only be applied when the variable has the value "dev". Im looking for a kind of if/else statement which allowes me to do that.
resource "azurerm_cdn_endpoint" "cdn_endpoint" {
name = "${var.environment}-test"
profile_name = azurerm_cdn_profile.cdn_profile.name
location = azurerm_cdn_profile.cdn_profile.location
resource_group_name = data.azurerm_resource_group.rg.name
origin_host_header = azurerm_storage_account.frontend_storage.primary_web_host
is_https_allowed = true
origin {
name = "${var.environment}-origin"
host_name = azurerm_storage_account.frontend_storage.primary_web_host
}
delivery_rule {
name = "EnforceHTTPS"
order = "1"
request_scheme_condition {
operator = "Equal"
match_values = ["HTTP"]
}
url_redirect_action {
redirect_type = "Found"
protocol = "Https"
}
}
delivery_rule {
name = "redirectToIndex"
order = "2"
url_file_extension_condition {
match_values = [
"0",
]
negate_condition = true
operator = "GreaterThan"
transforms = []
}
url_rewrite_action {
destination = "/index.html"
preserve_unmatched_path = false
source_pattern = "/"
}
}
delivery_rule {
name = "DevRedirectToWWW"
order = "3"
request_uri_condition {
operator = "BeginsWith"
match_values = [ "https://dev.test.app/" ]
}
url_redirect_action {
redirect_type = "Moved"
protocol = "Https"
hostname = "www.google.com"
}
}
depends_on = [
azurerm_cdn_profile.cdn_profile
]
}
You can achieve that by using a dynamic block [1] with for_each [2]. The way you would do that would be:
dynamic "delivery_rule" {
for_each = var.environment == "dev" ? [1] : []
content {
name = "DevRedirectToWWW"
order = "3"
request_uri_condition {
operator = "BeginsWith"
match_values = [ "https://dev.test.app/" ]
}
url_redirect_action {
redirect_type = "Moved"
protocol = "Https"
hostname = "www.google.com"
}
}
}
[1] https://developer.hashicorp.com/terraform/language/expressions/dynamic-blocks
[2] https://developer.hashicorp.com/terraform/language/meta-arguments/for_each
I have the following variable
variable "roles" {
type = set(string)
default = [
"A",
"B",
]
}
And I want to create a aws_iam_policy_document with a sts:AssumeRole action for each of those values.
I tried
data "aws_iam_policy_document" "service_role_trust_node_workers" {
statement {
effect = "Allow"
principals {
identifiers = ["ec2.amazon.com"]
type = "Service"
}
actions = ["sts:AssumeRole"]
}
for_each = var.roles
statement {
effect = "Allow"
sid = "${each.key}-${each.value}"
principals {
identifiers = [
each.value
]
type = "AWS"
}
actions = [
"sts:AssumeRole"
]
}
}
But this produces this
json = jsonencode(
{
+ Statement = [
+ {
+ Action = "sts:AssumeRole"
+ Effect = "Allow"
+ Principal = {
+ Service = "ec2.amazon.com"
}
+ Sid = ""
},
+ {
+ Action = "sts:AssumeRole"
+ Effect = "Allow"
+ Principal = {
+ AWS = "B"
}
+ Sid = "B-B"
},
]
+ Version = "2012-10-17"
}
)
So for some reason, A is ignored.
Any suggestions?
Ok, found it :)
dynamic "statement" {
for_each = var.roles
iterator = role
content {
effect = "Allow"
principals {
identifiers = [
role.value
]
type = "AWS"
}
actions = [
"sts:AssumeRole"
]
}
}
I'm trying to create a module for the aws_wafv2_web_acl resource and I can't figure out how to add multiple 'excluded_rule' blocks inside a dynamic block. Is this possible? Here is the resource:
resource "aws_wafv2_web_acl" "web-acl" {
name = var.name
description = ""
scope = "REGIONAL"
default_action {
allow {}
}
dynamic "rule" {
for_each = var.rules
content {
name = rule.value["name"]
priority = rule.value["priority"]
override_action {
count {}
}
statement {
managed_rule_group_statement {
name = rule.value["name"]
vendor_name = "AWS"
excluded_rule {
name = "excluded rule"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = false
sampled_requests_enabled = false
metric_name = rule.value["name"]
}
}
}
visibility_config {
cloudwatch_metrics_enabled = false
sampled_requests_enabled = false
metric_name = "webaclmetric"
}
}
And here are the variables being passed:
name = "test"
rules = [
{"name": "AWSManagedRulesLinuxRuleSet", "priority": 0, "exclusions": "LFI_QUERYARGUMENTS,LFI_URIPATH"},
{"name": "AWSManagedRulesWindowsRuleSet", "priority": 1, "exclusions": "PowerShellCommands_Set1_QUERYARGUMENTS"}
]
it is possible. You may want to take a look at the terraform module I have written for WafV2 web acl -> https://github.com/umotif-public/terraform-aws-waf-webaclv2
Going back to your question you can solve it with the following block:
dynamic "excluded_rule" {
for_each = length(lookup(managed_rule_group_statement.value, "excluded_rule", {})) == 0 ? [] : toset(lookup(managed_rule_group_statement.value, "excluded_rule"))
content {
name = excluded_rule.value
}
}
and then you can pass in the following into your module
managed_rule_group_statement = {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
excluded_rule = [
"SizeRestrictions_QUERYSTRING",
"SizeRestrictions_BODY",
"GenericRFI_QUERYARGUMENTS"
]
}
I have a list of subnets and wish to join them if they have an the enabled flag set to true.
locals {
env_whitelisted_ips = {
default = [
{ subnets = "${data.terraform_remote_state.infra.outputs.subnet_ids}", enabled = "true"},
{ subnets = "${data.terraform_remote_state.infra.outputs.public_subnet_ids}", enabled = "false"},
{ subnets = "${data.terraform_remote_state.infra.outputs.nlb_public_subnet_ids}", enabled = "false"},
{ subnets = "${data.terraform_remote_state.infra.outputs.vpc_cidr_block}", enabled = "false"}
]
}
Now print it the required values, set to true.
resource "null_resource" "default_3" {
for_each = { for k, v in local.env_whitelisted_ips : k => v }
triggers = {
subnet_value = jsonencode(each.value)
account_key = jsonencode(each.key)
}
}
And this currently produces.
+ triggers = {
+ "account_key" = "\"default\""
+ "subnet_value" = jsonencode(
[
+ {
+ enabled = "true"
+ subnets = [
+ "subnet-xxx",
+ "subnet-yyy",
+ "subnet-zzz",
]
}
But what I'm seeking is:
resource "null_resource" "default_3" {
for_each = { for k, v in local.env_whitelisted_ips : k => v.contains["enabled"] == true }
triggers = {
subnet_value = jsonencode(each.value.subnets)
}
}
Producing values of:
+ triggers = {
+ "account_value" = jsonencode(
[
+ {
+ subnets = [
+ "subnet-xxx",
+ "subnet-yyy",
+ "subnet-zzz",
]
}
Any idea would be much appreciated :)
So the fix was to change the local construct like so:
locals {
env_whitelisted_ips = [
{ subnet_block = "${data.terraform_remote_state.infra.outputs.subnet_cidr_block}",
enabled = "true"
},
{ subnet_block = "${data.terraform_remote_state.infra.outputs.public_subnet_cidr_block}",
enabled = "false"
},
{ subnet_block = "${data.terraform_remote_state.infra.outputs.nlb_subnet_cidr_block}",
enabled = "false"
},
{ subnet_block = "${data.terraform_remote_state.infra.outputs.vpc_cidr_block}",
enabled = "false"
}
]
}
And to update the nested loops like so:
resource "null_resource" "get_vpc_cidrs" {
for_each = { for k, v in (local.env_whitelisted_ips) : k => v if v.enabled != "false" }
triggers = {
whitelisted = "${join(",", each.value["subnet_block"])}"
}
}
And the output is:
# null_resource.get_vpc_cidrs["0"] will be created
+ resource "null_resource" "get_vpc_cidrs" {
+ id = (known after apply)
+ triggers = {
+ "whitelisted" = "172.27.128.0/20,172.27.144.0/20,172.27.160.0/20"
}
}