I have local variable:
locals {
bucket = {
firstBucket = {
sse = true
lifecycle_rules = [
{
id = "firstBucket"
enabled = true
expiration = {
days = 7
}
}
]
}
secondBucket = {
sse = false
lifecycle_rules = [
{
id = "secondBucket"
enabled = true
expiration = {
days = 7
}
}
]
}
}
}
I want first bucket to be encrypted (sse=true) and the second one should be encrypted (sse=false)
Then I try to create two s3 buckets using module. I want to use sse field defined in a local variable to set security options:
module "gitlab_bucket" {
for_each = local.bucket
/* some stuff */
server_side_encryption_configuration = lookup(each.value, "sse", null) ? var.security_cofig : {}
}
But it returns error The given key does not identify an element in this collection value
The syntax seems okay, but the default value(when sse attribute is missing) will have to be a boolean value (either true or false, so can't be null) for conditional expression.
I tested the below code in terraform 13.5, and it gave the expected result.
locals {
bucket = {
firstBucket = {
sse = true
lifecycle_rules = [
{
id = "firstBucket"
enabled = true
expiration = {
days = 7
}
}
]
}
secondBucket = {
#sse = false
lifecycle_rules = [
{
id = "secondBucket"
enabled = true
expiration = {
days = 7
}
}
]
}
}
}
resource "random_pet" "example" {
for_each = local.bucket
keepers = {
sse = lookup(each.value, "sse", false) ? jsonencode({x = "yes"}) : jsonencode({})
}
}
Below was the plan result:
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# random_pet.example["firstBucket"] will be created
+ resource "random_pet" "example" {
+ id = (known after apply)
+ keepers = {
+ "sse" = jsonencode(
{
+ x = "yes"
}
)
}
+ length = 2
+ separator = "-"
}
# random_pet.example["secondBucket"] will be created
+ resource "random_pet" "example" {
+ id = (known after apply)
+ keepers = {
+ "sse" = jsonencode({})
}
+ length = 2
+ separator = "-"
}
Plan: 2 to add, 0 to change, 0 to destroy.
Related
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}"
}
getting stuck with problem.
Need a terraform expert help. I want to create VPS in GCP with count using module. How to correct create and attach google_compute_address and google_compute_disk to each VPS with different names
Any help, please
Module code:
resource "google_compute_instance" "vps" {
count = var.server_count
name = var.server_count > 1 ? "${var.server_name}-${count.index}" : var.server_name
description = var.server_description
machine_type = var.server_type
zone = var.server_datacenter
deletion_protection = var.server_delete_protection
labels = var.server_labels
metadata = var.server_metadata
tags = var.server_tags
boot_disk {
auto_delete = false
initialize_params {
size = var.boot_volume_size
type = var.boot_volume_type
image = var.boot_volume_image
labels = var.boot_volume_labels
}
}
dynamic "attached_disk" {
for_each = var.volumes
content {
source = attached_disk.value["volume_name"]
}
}
dynamic "network_interface" {
for_each = var.server_network
content {
subnetwork = network_interface.value["subnetwork_name"]
network_ip = network_interface.value["subnetwork_ip"]
dynamic "access_config" {
for_each = network_interface.value.nat_ip ? [1] : []
content {
nat_ip = google_compute_address.static_ip.address
}
}
}
}
}
resource "google_compute_disk" "volume" {
for_each = var.volumes
name = each.value["volume_name"]
type = each.value["volume_type"]
size = each.value["volume_size"]
zone = var.server_datacenter
labels = each.value["volume_labels"]
}
resource "google_compute_address" "static_ip" {
count = var.server_count
name = var.server_count > 1 ? "${var.server_name}-${count.index}" : var.server_name
region = var.server_region
}
Using example:
module "vps-test" {
source = "../module"
credentials_file = "../../../../main/vault/prod/.tf/terraform-bb-prod-ground.json"
server_count = 2
server_name = "example-vps"
server_description = "simple vps for module testing"
server_type = "e2-small"
server_region = "europe-west4"
server_datacenter = "europe-west4-c"
server_labels = {
project = "terraform"
environment = "test"
}
server_metadata = {
groups = "parent_group.child_group"
}
boot_volume_image = "debian-cloud/debian-11"
boot_volume_size = 30
boot_volume_labels = {
environment = "production"
project = "v3"
type = "system"
}
server_tags = ["postgres", "production", "disable-gce-firewall"]
server_delete_protection = true
server_network = {
common_network = {
subnetwork_name = "${data.terraform_remote_state.network.outputs.subnetwork_vpc_production_common_name}"
subnetwork_ip = ""
nat_ip = true
} # },
# custom_network = {
# subnetwork_name = (data.terraform_remote_state.network.outputs.subnetwork_vpc_production_k8s_name)
# subnetwork_ip = ""
# nat_ip = false
# }
}
volumes = {
volume_data1 = {
volume_name = "v3-postgres-saga-import-test-storage"
volume_size = "40"
volume_type = "pd-ssd"
volume_labels = {
environment = "production"
project = "v3"
type = "storage"
}
},
volume_data2 = {
volume_name = "volume-vpstest2"
volume_size = "20"
volume_type = "pd-ssd"
volume_labels = {
environment = "production"
project = "v2"
type = "storage"
}
}
}
}
Now error is: Because google_compute_address.static_ip has "count" set, its attributes must be accessed on specific instances And i know, error with same disk name will come
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!
I've config that need to use count and for_each at the same time.
here's the config block
variable "all_zone" {
type = list(any)
default = ["asia-southeast1-a", "asia-southeast1-b", "asia-southeast1-c"]
}
variable "backends" {
description = "Map backend indices to list of backend maps."
type = map(object({
neg_name = string
}))
}
data "google_compute_network_endpoint_group" "get_neg" {
for_each = var.backends
count = length(var.all_zone)
zone = var.all_zone[count.index]
name = lookup(each.value, "neg_name")
}
resource "google_compute_backend_service" "default" {
. . .
dynamic "backend" {
for_each = [for b in data.google_compute_network_endpoint_group.get_neg[*].id : b]
content {
group = backend.value
}
}
}
is there anyway to do this?
Update: here's sample var.backends
backends = {
default = {
neg_name = 'name-1'
}
}
Update: Thanks for #marcin for the solution. but I've another problem to acessing this data.
Before I use helper_map
data "google_compute_network_endpoint_group" "get_neg" {
count = length(var.all_zone)
zone = var.all_zone[count.index]
name = 'name-1'
}
and here structure off the output data:
neg = [
+ {
+ id = "projects/k8s-playground-public/zones/asia-southeast1-a/networkEndpointGroups/name-1"
+ name = "name-1"
+ zone = "https://www.googleapis.com/compute/v1/projects/k8s-playground-public/zones/asia-southeast1-a"
},
+ {
+ id = "projects/k8s-playground-public/zones/asia-southeast1-b/networkEndpointGroups/name-1"
+ name = "name-1"
+ zone = "https://www.googleapis.com/compute/v1/projects/k8s-playground-public/zones/asia-southeast1-b"
},
+ {
+ id = "projects/k8s-playground-public/zones/asia-southeast1-c/networkEndpointGroups/name-1"
+ name = "name-1"
+ zone = "https://www.googleapis.com/compute/v1/projects/k8s-playground-public/zones/asia-southeast1-c"
},
]
here's I accessing the data
for_each = [for b in data.google_compute_network_endpoint_group.get_neg[*].id : b]
after use map_helper
neg = [
+ {
+ default-asia-southeast1-a = {
+ id = "projects/k8s-playground-public/zones/asia-southeast1-a/networkEndpointGroups/k8s1-e051d246-default-gclb-poc-8080-ef51ff1c"
+ name = "name-1"
+ zone = "https://www.googleapis.com/compute/v1/projects/k8s-playground-public/zones/asia-southeast1-a"
}
+ default-asia-southeast1-b = {
+ id = "projects/k8s-playground-public/zones/asia-southeast1-b/networkEndpointGroups/name-1"
+ name = "name-1"
+ zone = "https://www.googleapis.com/compute/v1/projects/k8s-playground-public/zones/asia-southeast1-b"
}
+ default-asia-southeast1-c = {
+ id = "projects/k8s-playground-public/zones/asia-southeast1-c/networkEndpointGroups/name-1"
+ name = "name-1"
+ zone = "https://www.googleapis.com/compute/v1/projects/k8s-playground-public/zones/asia-southeast1-c"
}
},
]
and how i access this id of data.
I'm not sure exactly what you want to achieve with your data structures, but one way to overcome your issue, would be to create local helper variable, which would be the combination of your backends and all_zone. For example:
variable "backends" {
description = "Map backend indices to list of backend maps."
type = map(object({
neg_name = string
}))
default = {
default = {
neg_name = "name-1"
}
}
}
variable "all_zone" {
type = list(any)
default = ["asia-southeast1-a", "asia-southeast1-b", "asia-southeast1-c"]
}
locals {
helper_map = merge([
for backend_key, backend_value in var.backends:
{
for zone in var.all_zone:
"${backend_key}-${zone}" => {
backend_value = backend_value.neg_name
zone = zone
}
}
]...)
}
which gives:
{
"default-asia-southeast1-a" = {
"backend_value" = "name-1"
"zone" = "asia-southeast1-a"
}
"default-asia-southeast1-b" = {
"backend_value" = "name-1"
"zone" = "asia-southeast1-b"
}
"default-asia-southeast1-c" = {
"backend_value" = "name-1"
"zone" = "asia-southeast1-c"
}
}
Then, you can easily iterate over this join structure (example only):
data "google_compute_network_endpoint_group" "get_neg" {
for_each = local.helper_map
zone = each.value.zone
name = each.value.backend_value
}
The above will probably require further adjustment to match your data structures, but the general idea of using local helper variable remains same.
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"
}
}