Terraform resource with for_each depending on a boolean variable - terraform

Given the following map and its implementation:
variable "tunnel_service_maps" {
default = {}
type = map(object({
target_service = string
create_service_token = bool
}))
tunnel_service_maps = {
tunnel1 = {
target_service = "http://tunnel1"
create_service_token = true
}
tunnel2 = {
target_service = "http://tunnel2"
create_service_token = false
}
tunnel3 = {
target_service = "http://tunnel3"
create_service_token = true
}
}
I want to create the following resource exclusively if create_service_token == true:
resource "example_resource" "example" {
for_each = var.tunnel_service_maps # <- row to change
name = "allow-service-token-${each.value.target_service}"
}
The expected results will be 2 example resources created

That should be easy to achieve:
resource "example_resource" "example" {
for_each = { for k, v in var.tunnel_service_maps : k => v if v.create_service_token }
name = "allow-service-token-${each.value.target_service}"
}

Related

Terraform: Object List Value Has No Attributes

Getting an error on Terraform Plan saying my object has no attributes for the name value. We are deploying about 7 private dns zones and many of them live in the same resource group. some may live in others, but most live in the same one.
Error: Unsupported attribute
on Modules/privatednszone/main.tf line 4, in data "azurerm_resource_group" "this":
name = each.value.name
This value does not have any attributes.
MAIN
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "2.83.0"
}
}
}
provider "azurerm" {
features {}
}
variable "private_dns_zones" {
type = map(object({
dns_zone_name = string
resource_group_name = string
tags = map(string)
vnet_links = list(object({
zone_to_vnet_link_name = string
vnet_name = string
networking_resource_group = string
zone_to_vnet_link_exists = bool
vnet_link_rg_name = string
}))
zone_exists = bool
registration_enabled = bool
}))
description = "Map containing Private DNS Zone Objects"
default = {}
}
data "azurerm_resource_group" "this" {
# read from local variable, index is resource_group_name
for_each = local.rgs_map
name = each.value.name
}
locals {
rgs_map = {
for n in var.private_dns_zones :
n.resource_group_name => {
name = n.resource_group_name
}
}
}
output "rgs_map" {
value = local.rgs_map
}
output "rg_data" {
value = data.azurerm_resource_group.this
}
TFVARS
Code below is a sample of two dns zones, but there are additional ones.
private_dns_zones = {
zone1 = {
dns_zone_name = "privatelink.vaultcore.azure.net"
resource_group_name = "Terraform1"
tags = {
iac = "Terraform"
syntax = "zone1"
}
zone_exists = false
vnet_links = [
{
zone_to_vnet_link_name = "vaultcore-vnet-eastus2-01"
vnet_name = "vnet-eastus2-01"
networking_resource_group = "Terraform1"
zone_to_vnet_link_exists = false
vnet_link_rg_name = "Terraform1"
}
]
registration_enabled = false
},
zone2 = {
dns_zone_name = "privatelink.monitor.azure.com"
resource_group_name = "Terraform1"
tags = {
iac = "Terraform"
syntax = "zone2"
}
zone_exists = false
vnet_links = [
{
zone_to_vnet_link_name = "monitor-vnet-eastus2-01"
vnet_name = "vnet-eastus2-01"
networking_resource_group = "Terraform1"
zone_to_vnet_link_exists = false
vnet_link_rg_name = "Terraform1"
}
]
registration_enabled = false
}
}
You code seems to work fine only if I use different resource group names. As you are using duplicate values of resource group names which is your requirement creating a map "rgs_map" with your code is not possible as it will error out with below :
So , in order to resolve the above error , I used something like below :
locals {
rgs_map = {
for i,n in var.private_dns_zones : "${i}" =>{
name = n.resource_group_name
}
}
}
Complete code:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "2.83.0"
}
}
}
provider "azurerm" {
features {}
}
variable "private_dns_zones" {
type = map(object({
dns_zone_name = string
resource_group_name = string
tags = map(string)
vnet_links = list(object({
zone_to_vnet_link_name = string
vnet_name = string
networking_resource_group = string
zone_to_vnet_link_exists = bool
vnet_link_rg_name = string
}))
zone_exists = bool
registration_enabled = bool
}))
description = "Map containing Private DNS Zone Objects"
default = {}
}
data "azurerm_resource_group" "this" {
# read from local variable, index is resource_group_name
for_each = local.rgs_map
name = each.value.name
}
locals {
rgs_map = {
for i,n in var.private_dns_zones : "${i}" =>{
name = n.resource_group_name
}
}
}
output "rgs_map" {
value = local.rgs_map
}
output "rg_data" {
value = data.azurerm_resource_group.this
}
Output:

Using dynamic values for Kubernetes namespace labels

I am managing my on-prem Kubernetes cluster namespaces with Terraform and want to include some custom labels/annotations on them. This is to make auditing easier and also we have mutating webhooks that rely on labels/annotations.
I am trying to do something like this (pseudo code)
resource "kubernetes_namespace" "namespaces" {
for_each = {for k, v in var.namespaces: k => v}
metadata {
name = each.value.name
annotations = {
"linkerd.io/inject" = each.value.linkerd
{{loop over each.value.custom_annotations}}
}
labels = {
"apps.kubernetes.io/app" = each.value.app
"k8s.domain.co/managed-by" = each.value.managed
"k8s.domain.co/owner" = each.value.owner
{{loop over each.value.custom.labels}}
}
}
}
I have my var.namespaces variable constructed like
description = "List of namespaces controlled by Terraform"
type = list(object({
name = string
linkerd = string
app = string
owner = string
managed = string
custom_annotations = list(object({
label = string
value = string
}))
custom_labels = list(object({
label = string
value = string
}))
}))
I am trying to end up with
namespaces = [
{
name = foo
...
custom_annotations = {
label = "myannotation"
value = "myvalue"
custom_labels = {
label = "mylabel"
value = "myvalue"
}]
resource "kubernetes_namespace" "namespaces" {
for_each = {for k, v in var.namespaces: k => v}
metadata {
name = each.value.name
annotations = {
"linkerd.io/inject" = each.value.linkerd
myannotation = myvalue
}
labels = {
"apps.kubernetes.io/app" = each.value.app
"k8s.domain.co/managed-by" = each.value.managed
"k8s.domain.co/owner" = each.value.owner
mylabel = myvalue
}
}
}
I have a feeling some mix of locals and dynamic blocks would be the solution but I can't seem to pin them together in a way that works
Any advice please?
I managed to get this almost working for myself without using locals or dynamic blocks. However I can't include the default labels and annotations
resource "kubernetes_namespace" "namespaces" {
for_each = { for k, v in var.namespaces: k => v} //loop over the namespaces
metadata {
name = each.value.name
annotations = {
for annotation in each.value.custom_annotations: annotation.label => annotation.value
}
labels = {
for label in each.value.custom_labels: label.label => label.value
}
}
}
With this input
namespaces = [
{
name = "metallb-system"
linkerd = "enabled"
app = "metallb"
owner = "KPE"
managed = "Terraform"
custom_annotations = []
custom_labels = [{label="foo.io/bar", value="foobar"}, {label="bar.io/foo", value="barfoo"}]
},
{ name = "test-ns"
linkerd = "enabled"
app = "myapp"
owner = "Me"
managed = "Terraform"
custom_annotations = [{label="foo.io/annotation", value="test"}]
custom_labels = [{label="app.io/label", value="value"}]
}
]
It gives me this output
Changes to Outputs:
+ namespaces = {
+ 0 = {
+ id = "metallb-system"
+ metadata = [
+ {
+ annotations = {}
+ generate_name = ""
+ generation = 0
+ labels = {
+ "bar.io/foo" = "barfoo"
+ "foo.io/bar" = "foobar"
}
+ name = "metallb-system"
+ resource_version = "410142"
+ uid = "02d6b1e1-707a-49cf-9a2d-3f28c9ce1e5a"
},
]
+ timeouts = null
}
+ 1 = {
+ id = (known after apply)
+ metadata = [
+ {
+ annotations = {
+ "foo.io/annotation" = "test"
}
+ generate_name = null
+ generation = (known after apply)
+ labels = {
+ "app.io/label" = "value"
}
+ name = "test-ns"
+ resource_version = (known after apply)
+ uid = (known after apply)
},
]
+ timeouts = null
}
}
I found a way to add default labels and annotations using setunion
locals {
default_annotations = [{label = "foo", value = "bar"}]
default_labels = [{label = "terraform", value = true}]
}
resource "kubernetes_namespace" "namespaces" {
for_each = { for k, v in var.namespaces: k => v} //loop over the namespaces
metadata {
name = each.value.name
annotations = {
for annotation in setunion(each.value.custom_annotations, local.default_annotations) : annotation.label => annotation.value
}
labels = {
for label in setunion(each.value.custom_labels, local.default_labels) : label.label => label.value
}
}
}
I know this doesn't exactly solve your use case, as you are wanting to read the value from your list of namesapces, however I do think it is one step closer!

How to ignore a block in terraform

I have a terraform code that is creating kubernetes cluster resource in Oracle cloud.
I want to ignore endpoint_config block when the cluster is public and execute this block when the cluster is private. How can I achieve that
resource "oci_containerengine_cluster" "cluster" {
count = var.deploy_oke_cluster ? 1 : 0
compartment_id = var.compartment_id
kubernetes_version = var.cluster_kubernetes_version
name = "oke-${var.environment}"
vcn_id = oci_core_virtual_network.base_vcn.id
endpoint_config {
is_public_ip_enabled = false
subnet_id = oci_core_subnet.snet-apiserver.id
}
options {
add_ons {
is_kubernetes_dashboard_enabled = true
is_tiller_enabled = false
}
kubernetes_network_config {
pods_cidr = var.pods_cidr
services_cidr = var.services_cidr
}
service_lb_subnet_ids = [oci_core_subnet.snet-pub-lb.id]
}
}
You can do this with dynamic blocks:
resource "oci_containerengine_cluster" "cluster" {
count = var.deploy_oke_cluster ? 1 : 0
compartment_id = var.compartment_id
kubernetes_version = var.cluster_kubernetes_version
name = "oke-${var.environment}"
vcn_id = oci_core_virtual_network.base_vcn.id
dynamic "endpoint_config" {
for_each = var.is_public == true ? [1] : []
content {
is_public_ip_enabled = false
subnet_id = oci_core_subnet.snet-apiserver.id
}
}
options {
add_ons {
is_kubernetes_dashboard_enabled = true
is_tiller_enabled = false
}
kubernetes_network_config {
pods_cidr = var.pods_cidr
services_cidr = var.services_cidr
}
service_lb_subnet_ids = [oci_core_subnet.snet-pub-lb.id]
}
}

Terraform Optional Parameter for List of String

Trying to implement Azure WAF policy and associate with http listener the code was working fine until I try to include a new optional parameter called http_listener_ids
Tf code:
variable "http_listener_ids"{
type = "list"
description = "A list of HTTP Listener IDs from an azurerm_application_gateway"
default = []
}
locals {
http_listener_ids ="${var.http_listener_ids}" == [] ? null: "${var.http_listener_ids}"
}
resource "azurerm_web_application_firewall_policy" "example" {
name = "example-wafpolicy"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
http_listener_ids = "${local.http_listener_ids}"
custom_rules {
name = "Rule1"
priority = 1
rule_type = "MatchRule"
match_conditions {
match_variables {
variable_name = "RemoteAddr"
}
operator = "IPMatch"
negation_condition = false
match_values = ["192.168.1.0/24", "10.0.0.0/24"]
}
action = "Block"
}
custom_rules {
name = "Rule2"
priority = 2
rule_type = "MatchRule"
match_conditions {
match_variables {
variable_name = "RemoteAddr"
}
operator = "IPMatch"
negation_condition = false
match_values = ["192.168.1.0/24"]
}
match_conditions {
match_variables {
variable_name = "RequestHeaders"
selector = "UserAgent"
}
operator = "Contains"
negation_condition = false
match_values = ["Windows"]
}
action = "Block"
}
policy_settings {
enabled = true
mode = "Prevention"
request_body_check = true
file_upload_limit_in_mb = 100
max_request_body_size_in_kb = 128
}
managed_rules {
exclusion {
match_variable = "RequestHeaderNames"
selector = "x-company-secret-header"
selector_match_operator = "Equals"
}
exclusion {
match_variable = "RequestCookieNames"
selector = "too-tasty"
selector_match_operator = "EndsWith"
}
managed_rule_set {
type = "OWASP"
version = "3.1"
rule_group_override {
rule_group_name = "REQUEST-920-PROTOCOL-ENFORCEMENT"
disabled_rules = [
"920300",
"920440"
]
}
}
}
}
Error I got is
Error: "http_listener_ids": this field cannot be set
I thought http_listener_ids property is not skipping and try to assign the value of null instead. So i try to implement dynamic block. But the problem is since http_listener_ids a simple list of string and not a block as such . So not sure what to put inside the content
dynamic "http_listener_ids"{
for_each = "${var.http_listener_ids}"
content{
??
}
}
According to a recent GitHub PR, http_listener_ids is read only, and can't be set. Maybe docs haven't been updated yet.
The documentation for the azurerm_web_application_firewall_policy resource is out of date but http_listener_ids and path_based_rule_ids are read only now (as of v2.55.0) so you can't set them and can only read them as an attribute of the resource.

Incrementing tag index in terraform

I am looking for a way to differentiate the tag names in my module, and know that there is a road block there as you can't use count in modules, I would like to increment my tag name, based of the position of the public subnet, how would I do this?
variable "public_subnet_count" {
default = 3
}
variable "public_subnet_name" {
default = {
0 = "cp-net0"
1 = "k8s-net0"
2 = "um-net0"
}
}
module "vpc" {
source = "github.com/terraform-aws-modules/terraform-aws-vpc"
name = "apigee"
cidr = "10.0.0.0/16"
azs = data.aws_availability_zones.available.names
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
assign_generated_ipv6_cidr_block = true
enable_nat_gateway = true
single_nat_gateway = true
public_subnet_tags = {
Name = lookup(var.public_subnet_name, count.index) // will not work
}
tags = {
Owner = "212743998"
Environment = "sandbox"
}
vpc_tags = {
Name = "apigee"
}
}

Resources