I am creating aws_workspace using the terraform. I am merging the variable in local.tf with the default values if the values are not provided in the variable. Then passing those to module. So inside resource.tf I want to eliminate the for_each loop and assign the values without any loop. Is it possible to do it?
local.tf
locals {
my_defaults = {
root_volume_encryption_enabled = true
user_volume_encryption_enabled = true
volume_encryption_key = "alias/aws/workspaces"
compute_type_name = "VALUE"
user_volume_size_gib = 10
root_volume_size_gib = 80
running_mode = "AUTO_STOP"
running_mode_auto_stop_timeout_in_minutes = 60
}
final_aws_workspace = { for k, v in var.aws_workspace :
k => merge(local.my_defaults, v)
}
}
Module.tf
variable "aws_workspace" {
default = {
user1 = {
user_name = "john.doe"
root_volume_encryption_enabled = true
user_volume_encryption_enabled = true
volume_encryption_key = "alias/aws/workspaces"
compute_type_name = "VALUE"
user_volume_size_gib = 10
root_volume_size_gib = 80
running_mode = "AUTO_STOP"
running_mode_auto_stop_timeout_in_minutes = 60
},
user2 = {
user_name = "wahaj.akmal"
}
}
description = "aws workspace configuration"
}
variable "tags" {
default = ""
description = "tags for the resouces"
}
variable "region" {
default = ""
description = "region on which aws infra is to be deployed"
}
data "aws_workspaces_bundle" "value_windows_10" {
bundle_id = "wsb-bh8rsxt14"
}
module "aws_workspace" {
source = "./modules/aws_workspace"
aws_workspace = local.final_aws_workspace
tags = {
Name = "cloud"
}
bundle_id = data.aws_workspaces_bundle.value_windows_10.id
directory_id = aws_workspaces_directory.example.id
}
Resource.tf
variable "aws_workspace" {
default = ""
description = "configuration of aws workspaces"
}
variable "tags" {
default = ""
description = "tags of the resources"
}
variable "directory_id" {
default = ""
description = "Id of the directory"
}
variable "bundle_id" {
default = ""
description = "id of the bundle"
}
resource "aws_workspaces_workspace" "this" {
directory_id = var.directory_id
bundle_id = var.bundle_id
for_each = var.aws_workspace
user_name = each.value.user_name
root_volume_encryption_enabled = each.value.root_volume_encryption_enabled
user_volume_encryption_enabled = each.value.user_volume_encryption_enabled
volume_encryption_key = each.value.volume_encryption_key
workspace_properties {
compute_type_name = each.value.compute_type_name
user_volume_size_gib = each.value.user_volume_size_gib
root_volume_size_gib = each.value.root_volume_size_gib
running_mode = each.value.running_mode
running_mode_auto_stop_timeout_in_minutes = each.value.running_mode_auto_stop_timeout_in_minutes
}
tags = var.tags
}
Your aws_workspace is a map with different values for each user. You could only change to count, but this poses its own issues, and for_each is preferred in your case.
So you either use for_each as you do know, change it to count. Or if you totally don't want to use any of that, you have to put for_each in your module instead.
Update
The new aws_workspaces_workspace:
resource "aws_workspaces_workspace" "this" {
directory_id = var.directory_id
bundle_id = var.bundle_id
user_name = var.aws_workspace.user_name
root_volume_encryption_enabled = var.aws_workspace.root_volume_encryption_enabled
user_volume_encryption_enabled = var.aws_workspace.user_volume_encryption_enabled
volume_encryption_key = var.aws_workspace.volume_encryption_key
workspace_properties {
compute_type_name = var.aws_workspace.compute_type_name
user_volume_size_gib = var.aws_workspace.user_volume_size_gib
root_volume_size_gib = var.aws_workspace.root_volume_size_gib
running_mode = var.aws_workspace.running_mode
running_mode_auto_stop_timeout_in_minutes = var.aws_workspace.running_mode_auto_stop_timeout_in_minutes
}
and for module:
module "aws_workspace" {
for_each = local.final_aws_workspace
source = "./modules/aws_workspace"
aws_workspace = each.value
tags = {
Name = "cloud"
}
bundle_id = data.aws_workspaces_bundle.value_windows_10.id
directory_id = aws_workspaces_directory.example.id
}
Related
I'm trying to create a dynamic method to create multiple dynamodb tables with their own attributes. I tried for loops with dynamic blocks, list objects, etc but was not able to iterate attributes for each table. The goal is to have multiple tables with different attributes and global indexes for each table in one go. I have terraform.tfvars and main.tf with the following structure:
Variable declaration:
variable "dynamodb_table" {
type = list(object({
table_name = string
billing_mode = string
read_capacity = optional(number)
write_capacity = optional(string)
hash_key = string
ttl_attribute_name = string
ttl_enabled = string
range_key = optional(string)
attribute = object({
name = string
type = string
})
}))
}
variable "global_secondary_indexes" {
description = "Describe a GSI for the table; subject to the normal limits on the number of GSIs, projected attributes, etc."
type = list(object({
index_name = string
index_projection_type = string
index_range_key = string
index_hash_key = string
index_write_capacity = optional(string)
index_read_capacity = optional(string)
index_non_key_attributes = list(string)
}))
default = []
}
dynamodb_table = [
{
table_name = "devops-test-01",
billing_mode = "PAY_PER_REQUEST",
hash_key = "UserId",
range_key = "GameTitle",
ttl_attribute_name = "ttl_attribute_name",
ttl_enabled = "false"
attribute = [
{
name = "UserId"
type = "S"
},
{
name = "GameTitle"
type = "S"
}
]
},
{
table_name = "devops-test-02",
billing_mode = "PAY_PER_REQUEST",
hash_key = "GameTitle",
ttl_attribute_name = "ttl_attribute_name",
ttl_enabled = "false"
}
]
global_secondary_indexes = [
{
index_name = "TitleIndex"
index_hash_key = "UserId"
index_range_key = "GameTitle"
index_projection_type = "INCLUDE"
index_non_key_attributes = ["Id"]
}
]
default_tags = {
"Environment" = "Dev",
"Owner" = "xxx"
}
resource "aws_dynamodb_table" "basic-dynamodb-table" {
for_each = { for key, value in var.dynamodb_table : key => value }
name = each.value.table_name
billing_mode = each.value.billing_mode
read_capacity = each.value.read_capacity
write_capacity = each.value.write_capacity
hash_key = each.value.hash_key
range_key = each.value.range_key
ttl {
attribute_name = each.value.ttl_attribute_name
enabled = each.value.ttl_enabled
}
dynamic "attribute" {
for_each = { for key, value in var.attributes : key => value }
content {
name = attribute.value.name
type = attribute.value.type
}
}
dynamic "global_secondary_index" {
for_each = var.global_secondary_indexes
content {
name = global_secondary_index.value.index_name
hash_key = global_secondary_index.value.index_hash_key
projection_type = global_secondary_index.value.index_projection_type
range_key = lookup(global_secondary_index.value, "index_range_key", null)
read_capacity = lookup(global_secondary_index.value, "index_read_capacity", null)
write_capacity = lookup(global_secondary_index.value, "index_write_capacity", null)
non_key_attributes = lookup(global_secondary_index.value, "index_non_key_attributes", null)
}
}
tags = merge(
var.default_tags,
{
Name = each.value.table_name
})
}
This code produces the following error:
The given value is not suitable for var.dynamodb_table declared at variable.tf:6,1-26: element 0: attribute │ "attribute": object required
You did not share your attributes variable but I have used attributes in dynamodb_table variable.
Your main problem is attribute property in dynamodb_table variable is requeired but you did not provide any value for it in devops-test-02 table values.
variables.tf
variable "dynamodb_table" {
type = list(object({
table_name = string
billing_mode = string
// read_capacity = optional(number)
//write_capacity = optional(string)
hash_key = string
ttl_attribute_name = string
ttl_enabled = string
//range_key = optional(string)
attribute = list(object({
name = string
type = string
}))
}))
default = [
{
table_name = "devops-test-01",
billing_mode = "PAY_PER_REQUEST",
hash_key = "UserId",
range_key = "GameTitle",
ttl_attribute_name = "ttl_attribute_name",
ttl_enabled = "false"
attribute = [
{
name = "UserId"
type = "S"
},
{
name = "GameTitle"
type = "S"
}
]
},
{
table_name = "devops-test-02",
billing_mode = "PAY_PER_REQUEST",
hash_key = "GameTitle",
ttl_attribute_name = "ttl_attribute_name",
ttl_enabled = "false"
attribute = [
{
name = "UserId"
type = "S"
},
{
name = "GameTitle"
type = "S"
}
]
}
]
}
variable "global_secondary_indexes" {
description = "Describe a GSI for the table; subject to the normal limits on the number of GSIs, projected attributes, etc."
type = list(object({
index_name = string
index_projection_type = string
index_range_key = string
index_hash_key = string
//index_write_capacity = optional(string)
//index_read_capacity = optional(string)
index_non_key_attributes = list(string)
}))
default = [
{
index_name = "TitleIndex"
index_hash_key = "UserId"
index_range_key = "GameTitle"
index_projection_type = "INCLUDE"
index_non_key_attributes = ["Id"]
}
]
}
variable "default_tags" {
default = {
"Environment" = "Dev",
"Owner" = "xxx"
}
}
dynamodb.tf
resource "aws_dynamodb_table" "basic-dynamodb-table" {
for_each = { for key, value in var.dynamodb_table : value.table_name => value }
name = each.value.table_name
billing_mode = each.value.billing_mode
read_capacity = lookup(each.value, "read_capacity", null)
write_capacity = lookup(each.value, "write_capacity", null)
hash_key = each.value.hash_key
range_key = lookup(each.value, "range_key", null)
ttl {
attribute_name = each.value.ttl_attribute_name
enabled = each.value.ttl_enabled
}
dynamic "attribute" {
for_each = { for key, value in each.value.attribute : key => value }
content {
name = attribute.value.name
type = attribute.value.type
}
}
dynamic "global_secondary_index" {
for_each = var.global_secondary_indexes
content {
name = global_secondary_index.value.index_name
hash_key = global_secondary_index.value.index_hash_key
projection_type = global_secondary_index.value.index_projection_type
range_key = lookup(global_secondary_index.value, "index_range_key", null)
read_capacity = lookup(global_secondary_index.value, "index_read_capacity", null)
write_capacity = lookup(global_secondary_index.value, "index_write_capacity", null)
non_key_attributes = lookup(global_secondary_index.value, "index_non_key_attributes", null)
}
}
tags = merge(
var.default_tags,
{
Name = each.value.table_name
})
}
UPDATE 2023-01-17
Add Kinesis streaming destination resource to dynamodb tables.
resource "aws_kinesis_stream" "example" {
for_each = aws_dynamodb_table.basic-dynamodb-table
name = "${each.key}_table_stream"
shard_count = 1
}
resource "aws_dynamodb_kinesis_streaming_destination" "example" {
for_each = aws_dynamodb_table.basic-dynamodb-table
stream_arn = aws_kinesis_stream.example[each.key].arn
table_name = each.key
}
I want to create multiple acm certificates and route53 records for its validation, just cant figure out how can I reference to all acm resources created by for_each in route53 resource block which is looping in acm resource to get all the DNS _validation attributes, code is working fine if I will set one cert in variable and reference it directly with name, but how can I loop to all domain names for referencing it in for loop?
Issue is this line
for dvo in aws_acm_certificate.web[for i in keys(var.certificates) : i]
which is returning
The index operator must end with a closing bracket ("]").
Adding a second bracket like this
for dvo in aws_acm_certificate.web[[for i in keys(var.certificates) : i]]
returns error
│
│ on main.tf line 21, in resource "aws_route53_record" "domain_validation":
│ 21: for dvo in aws_acm_certificate.web[[for i in keys(var.certificates) : i]].domain_validation_options : dvo.domain_name => {
│ ├────────────────
│ │ aws_acm_certificate.web is object with 2 attributes
│ │ var.certificates is object with 2 attributes
│
│ The given key does not identify an element in this collection value: string
│ required.```
resource "aws_acm_certificate" "web" {
for_each = var.certificates
domain_name = "${replace(each.key, var.search_period, var.replace_period)}"
subject_alternative_names = each.value.subject_alternative_names
validation_method = "DNS"
}
resource "aws_route53_record" "domain_validation" {
for_each = var.dns_validation ? {
for dvo in aws_acm_certificate.web[[for i in keys(var.certificates) : i]].domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
} : {}
allow_overwrite = true
name = each.value.name #aws_acm_certificate.web[each.key].domain_validation_options.0.resource_record_name
records = [each.value.record] #aws_acm_certificate.web[each.key].domain_validation_options.0.resource_record_value
ttl = 60
type = each.value.type #aws_acm_certificate.web[each.key].domain_validation_options.0.resource_record_type
zone_id = data.aws_route53_zone.selected[[for i in keys(var.certificates) : i]].zone_id
}
var.certificates looks like this:
certificates = {
"xxx.com" = {
subject_alternative_names = ["*.xxx.com","*.dev.xxx.com","*.stage.xxx.com","*.preprod.xxx.com"]
},
"zzz.com" = {
subject_alternative_names = ["*.dev.zzz.com","*.zzz.com"]
},
}
aws_acm_certificate.web in console looks like this:
```
> aws_acm_certificate.web
{
"test.com" = {
"arn" = "arn:aws:acm:eu-west-1:584637875403:certificate/a6fed-01c6-4f2c-ad87-59c04877bd0b"
"certificate_authority_arn" = ""
"certificate_body" = tostring(null)
"certificate_chain" = tostring(null)
"domain_name" = "test.com"
"domain_validation_options" = toset([
{
"domain_name" = "*.test.com"
"resource_record_name" = "_d5e2266fa07c911501b806f3d19e.test.com."
"resource_record_type" = "CNAME"
"resource_record_value" = "_fcc7913c9269201f77625b7f71ec.ltyvprtsjl.acm-validations.aws."
},
{
"domain_name" = "*.dev.test.com"
"resource_record_name" = "_f2aa63aabaae8cd721bf0143dee6.dev.test.com."
"resource_record_type" = "CNAME"
"resource_record_value" = "_f461eca5849d2a3e218dea91955.ltyvprtsjl.acm-validations.aws."
},
{
"domain_name" = "*.preprod.test.com"
"resource_record_name" = "_2747174805245587c6f9811a1180.preprod.test.com."
"resource_record_type" = "CNAME"
"resource_record_value" = "_7d3ccdf1006b12074ebcbc9c3d1.ltyvprtsjl.acm-validations.aws."
},
{
"domain_name" = "*.stage.test.com"
"resource_record_name" = "_6f571d29f334dcccfe098a2371c.stage.test.com."
"resource_record_type" = "CNAME"
"resource_record_value" = "_9c9657a4839d827d1ff6db0ffd0.ltyvprtsjl.acm-validations.aws."
},
{
"domain_name" = "test.com"
"resource_record_name" = "_d5e2266fa07c91bad71501bd19e.test.com."
"resource_record_type" = "CNAME"
"resource_record_value" = "_fcc7913c926926efc05b7f71ec.ltyvprtsjl.acm-validations.aws."
},
])
"id" = "arn:aws:acm:eu-west-1:584637875403:certificate/a6fed5981c6-4f2c-ad87-59c04877bd0b"
"options" = tolist([
{
"certificate_transparency_logging_preference" = "ENABLED"
},
])
"private_key" = (sensitive)
"status" = "ISSUED"
"subject_alternative_names" = toset([
"*.test.com",
"*.dev.test.com",
"*.preprod.test.com",
"*.stage.test.com",
])
"tags" = tomap({})
"tags_all" = tomap({})
"validation_emails" = tolist([])
"validation_method" = "DNS"
}
}
```
You have to flatten your aws_acm_certificate.web first. For example:
locals {
certificate_web_flat = merge([
for hzone, certs in aws_acm_certificate.web: {
for domain_validation_option in certs.domain_validation_options:
"${hzone}-${domain_validation_option.domain_name}" => {
"hzone" = hzone
"domain_name" = domain_validation_option.domain_name
resource_record_name = domain_validation_option.resource_record_name
resource_record_value = domain_validation_option.resource_record_value
resource_record_type = domain_validation_option.resource_record_type
}
}
]...)
}
then
resource "aws_route53_record" "domain_validation" {
for_each = var.dns_validation ? local.certificate_web_flat : {}
allow_overwrite = true
name = each.value.resource_record_name
records = [each.value.resource_record_value]
ttl = 60
type = each.value.resource_record_type
zone_id = data.aws_route53_zone.selected[each.value.hzone].zone_id
}
Say that I have two modules that require the same source (append_only_history_bq_tables). Their difference is only the from_dwh_core variable which the default set to false
module "dwh_core_append_only_history_tables" {
source = "../append_only_history_bq_tables"
gcp_project = var.gcp_project
bq_location = var.bq_location
dataset_id = google_bigquery_dataset.jago_pagerduty.dataset_id
module_base_path = path.module
tables = {
"list_incident" = { "business_key_columns" = "id", "partition_type" = "DAY" },
"raw_incident_information" = { "business_key_columns" = "id", "partition_type" = "DAY" }
}
}
module "daily_closing_balance" {
source = "../append_only_history_bq_tables"
gcp_project = var.gcp_project
bq_location = var.bq_location
dataset_id = google_bigquery_dataset.dwh_core_dataset.dataset_id
module_base_path = path.module
use_source_ord = false
from_dwh_core = true
tables = {
"daily_closing_balance" = { "business_key_columns" = "full_date,account_number,customer_id", "partition_type" = "DAY" }
}
depends_on = [google_bigquery_dataset.dwh_core_dataset]
}
The append_only_history_bq_tables contains this resource
resource "google_bigquery_table" "dwh_core_snapshot_daily" {
for_each = var.tables
dataset_id = var.dataset_id
project = var.gcp_project
table_id = "${each.key}_snapshot_current_daily"
schema = file("${local.schema_def_folder}/${each.key}.json")
deletion_protection=false
}
How to conditionally call the dwh_core_snapshot_daily resource only if the from_dwh_core variable is set to true?
Thank you for your help
You can use Conditional Expression:
resource "google_bigquery_table" "dwh_core_snapshot_daily" {
for_each = var.from_dwh_core == true ? var.tables : {}
dataset_id = var.dataset_id
project = var.gcp_project
table_id = "${each.key}_snapshot_current_daily"
schema = file("${local.schema_def_folder}/${each.key}.json")
deletion_protection=false
}
I am trying to create aws_workspace_workspaces by passing the values to dynamic block but its not picking up the values from there. The main aim is to automatically distributes the value to the resource. it shows this error
on modules/aws_workspace/main.tf line 4, in resource "aws_workspaces_workspace" "example": │ 4: dynamic "aws_workspace" { │ │ Blocks of type "aws_workspace" are not expected here.
dev.tfvars
aws_workspace={
user_name = "john.doe"
root_volume_encryption_enabled = true
user_volume_encryption_enabled = true
volume_encryption_key = "alias/aws/workspaces"
workspace_properties = {
compute_type_name = "VALUE"
user_volume_size_gib = 10
root_volume_size_gib = 80
running_mode = "AUTO_STOP"
running_mode_auto_stop_timeout_in_minutes = 60
}
}
tags = {
Department = "IT"
}
resource "aws_workspaces_workspace" "example" {
directory_id = var.directory_id
bundle_id = var.bundle_id
dynamic "aws_workspace" {
for_each = var.aws_workspace
content {
user_name = aws_workspace.value.user_name
root_volume_encryption_enabled = aws_workspace.value.root_volume_encryption_enabled
user_volume_encryption_enabled = aws_workspace.value.user_volume_encryption_enabled
volume_encryption_key = aws_workspace.value.volume_encryption_key
workspace_properties {
compute_type_name = aws_workspace.value.compute_type_name
user_volume_size_gib = aws_workspace.value.user_volume_size_gib
root_volume_size_gib = aws_workspace.value.root_volume_size_gib
running_mode = aws_workspace.value.running_mode
running_mode_auto_stop_timeout_in_minutes = aws_workspace.value.running_mode_auto_stop_timeout_in_minutes
}
}
}
tags = {
Department = "IT"
}
}
So If I pass the value to of another user it creates it as well, this is the basic aim that I am trying to achieve
module "my_workspaces" {
source = "./modules/workspaces"
hosts = {
"user1" = {
"user_name" = "user1.last1",
"compute_type_name" = var.compute_type_name
"user_volume_size_gib" = var.user_volume_size_gib,
"root_volume_size_gib" = var.root_volume_size_gib,
"running_mode" = var.running_mode,
"running_mode_auto_stop_timeout_in_minutes" = var.auto_stop_timeout_min
},
"user2" = {
"user_name" = "user2.last2",
"compute_type_name" = var.compute_type_name
"user_volume_size_gib" = var.user_volume_size_gib,
"root_volume_size_gib" = var.root_volume_size_gib,
"running_mode" = var.running_mode,
"running_mode_auto_stop_timeout_in_minutes" = var.auto_stop_timeout_min
},
}
}
There is no such block as aws_workspace in aws_workspaces_workspace. You should use for_each directly:
resource "aws_workspaces_workspace" "example" {
directory_id = var.directory_id
bundle_id = var.bundle_id
for_each = var.aws_workspace
user_name = each.value.user_name
root_volume_encryption_enabled = each.value.root_volume_encryption_enabled
user_volume_encryption_enabled = each.value.user_volume_encryption_enabled
volume_encryption_key = each.value.volume_encryption_key
workspace_properties {
compute_type_name = each.value.compute_type_name
user_volume_size_gib = each.value.user_volume_size_gib
root_volume_size_gib = each.value.root_volume_size_gib
running_mode = each.value.running_mode
running_mode_auto_stop_timeout_in_minutes = each.value.running_mode_auto_stop_timeout_in_minutes
}
tags = {
Department = "IT"
}
}
I am trying to pass the values s3 name and create_user into local block in main.tf so that both of them have the value in list and then I am passing list_of_bucket in local block in module s3 to create the buckets and looping of user_to_create in module s3_user to create the user if the boolean is set to true. All of these values are passed to variable.tf and then to main.tf
dev.tfvars
wea-nonprod = {
services = {
s3 = [
sthree = {
create_user = true,
}
sfour = {
create_user = true,
}
sfive = {
create_user = true,
}
]
}
}
variable.tf
variable "s3_buckets" {
type = list(map)
}
main.tf
locals {
users_to_create = ""
list_of_buckets = ""
}
module "s3" {
source = "../../s3"
name = join("-", [var.name_prefix, "s3"])
tags = merge(var.tags, {Name = join("-", [var.name_prefix, "s3"])})
buckets = list_of_buckets
sse_algorithm = "AES256"
access_log_bucket_name = var.access_log_bucket_name
}
module "s3_user" {
for_each = local.users_to_create
source = "./service-s3-bucket-user"
name = join("-", [var.name_prefix, each.key])
tags = var.tags
bucket_arn = module.s3.bucket_arns[each.key]
depends_on = [module.s3]
}
Just iterate over your wea-nonprod map:
locals {
users_to_create = [ for name in var.wea-nonprod.services.s3 if name.create_user == true ]
list_of_buckets = [ for bucket in var.wea-nonprod.services.s3 ]
}
And a few changes to your module blocks:
module "s3" {
source = "../../s3"
name = "${var.name_prefix}-s3"
tags = merge(var.tags, { Name = "${var.name_prefix}-s3" })
buckets = local.list_of_buckets
sse_algorithm = "AES256"
access_log_bucket_name = var.access_log_bucket_name
}
module "s3_user" {
count = length(local.users_to_create)
source = "./service-s3-bucket-user"
name = "${var.name_prefix}${local.users_to_create[count.index]}"
tags = var.tags
bucket_arn = module.s3.bucket_arns[local.users_to_create[count.index]]
depends_on = [module.s3]
}