Prevent a Terraform resource with for_each from being created depending on a condition - foreach

I'm using the following resources to create a DynamoDB table with items from a JSON file of objects.
module "dynamodb_label" {
source = "./modules/labels"
enabled = var.ENABLE_TABLE
name = var.dynamodb_name
context = module.this.context
}
locals {
json_data = file("./items.json")
items = jsondecode(local.json_data)
}
module "dynamodb_table" {
source = "./aws-dynamodb"
count = var.ENABLE_TABLE ? 1 : 0
hash_key = "schema"
hash_key_type = "S"
autoscale_write_target = 50
autoscale_read_target = 50
autoscale_min_read_capacity = 5
autoscale_max_read_capacity = 1000
autoscale_min_write_capacity = 5
autoscale_max_write_capacity = 1000
enable_autoscaler = true
enable_encryption = true
enable_point_in_time_recovery = true
ttl_enabled = false
dynamodb_attributes = [
{
name = "schema"
type = "S"
}
]
context = module.dynamodb_label.context
}
resource "aws_dynamodb_table_item" "dynamodb_table_item" {
for_each = var.ENABLE_TABLE ? local.items : {}
table_name = module.dynamodb_table.table_name
hash_key = "schema"
item = jsonencode(each.value)
depends_on = [module.dynamodb_table]
}
The JSON file
{
"Item1": {
"schema": {
"S": "https://schema.org/government-documents#id-card"
},
"properties": {
"S": "{\"documentName\":{\"type\":\"string\"},\"dateOfBirth\":{\"type\":\"string\"}}"
}
},
"Item2": {
"schema": {
"S": "https://schema.org/government-documents#drivers-license"
},
"properties": {
"S": "{\"documentName\":{\"type\":\"string\"},\"dateOfBirth\":{\"type\":\"string\"}}"
}
}
}
I'm getting the following error
Error: Inconsistent conditional result types
on dynamodb-table.tf line 173, in resource "aws_dynamodb_table_item" "dynamodb_table_item":
173: for_each = var.ENABLE_TABLE ? local.items : {}
├────────────────
│ local.items is object with 13 attributes
│ var.ENABLE_TABLE is a bool, known only after apply
The true and false result expressions must have consistent types. The given expressions are object and object, respectively.
I've tried many options to pass this error even change the variable type from bool to object. If I remove the condition in for_each and just pass local.items the aws_dynamodb_table_item tries to create regardless of the depends_on and it fails of course to create because table_name is returned empty because of count = module.dynamodb_label.enabled ? 1 : 0 in dynamodb_table module
I want the aws_dynamodb_table_item to be skipped if var.ENABLE_TABLE is set to false
What am I missing here? Any hint is highly appreciated.
EDIT: Tried the following so far all with same error;
for_each = var.ENABLE_TABLE == true ? local.schemas : {}
for_each = var.ENABLE_TABLE ? local.items : {}

Maybe you can try this:
resource "aws_dynamodb_table_item" "dynamodb_table_item" {
for_each = var.ENABLE_TABLE : local.items ? {}
table_name = module.dynamodb_table.table_name
hash_key = "schema"
item = jsonencode(local.for_each)
depends_on = [module.dynamodb_table]
}
EDIT: in this case the variable var.ENABLE_TABLE must be boolean.

After experimenting for 2 days around different types of expressions to get around this issue a kind sir on Reddit referred to this solution and it worked like a charm;
for_each = { for k,v in local.items : k => v if var.ENABLE_TABLE }
For anyone with a similar requirement this seems a gem that I have missed and recommend you to use it.

The true and false result expressions must have consistent types. The given expressions are object and object, respectively.
I think I found what's the issue is
You should've used jsondecode but used jsonencode instead.
locals {
json_data = file("./items.json")
items = jsondecode(local.json_data)
}
Secondly I run into this issue
╷
│ Error: Incorrect attribute value type
│
│ on main.tf line 14, in resource "aws_dynamodb_table_item" "dynamodb_table_item":
│ 14: item = each.value
│ ├────────────────
│ │ each.value is object with 2 attributes
│
│ Inappropriate value for attribute "item": string required.
╵
╷
│ Error: Incorrect attribute value type
│
│ on main.tf line 14, in resource "aws_dynamodb_table_item" "dynamodb_table_item":
│ 14: item = each.value
│ ├────────────────
│ │ each.value is object with 2 attributes
│
│ Inappropriate value for attribute "item": string required.
For that I used this
resource "aws_dynamodb_table_item" "dynamodb_table_item" {
for_each = var.ENABLE_TABLE ? local.items : {}
table_name = module.dynamodb_table.table_name
hash_key = "schema"
item = each.value.properties.S
depends_on = [module.dynamodb_table]
}
Hope this helps you debug.

Related

Terraform validation for optional variables and for list of object variables

I am using the monitor autoscale setting resource for Terraform and I am trying to validate attributes of a complex object.
https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/monitor_autoscale_setting
The "trigger" block has been made optional so I have added a step to check if a value exists before checking for validation. Below is the scaling profile:
`
variable "scaling_profile" {
type = list(object({
name = optional(string, "Profile 1")
capacity = number
min_capacity = optional(number)
max_capacity = optional(number)
trigger = optional(list(object({
metric_name = string
operator = string
threshold = number
scale_action = string
scale_instance_count = number
time_window = number
scale_cooldown = number
})), [])
schedule = list(object({
days = list(string)
start_hour = number
start_minute = number
end_hour = number
end_minute = number
}))
}))
default = [{
capacity = 1
min_capacity = 1
max_capacity = 3
trigger = [
{
metric_name = "Percentage CPU"
operator = "GreaterThan"
threshold = 60
scale_action = "Increase"
scale_instance_count = 1
time_window = 5
scale_cooldown = 5
},
{
metric_name = "Percentage CPU"
operator = "LessThan"
threshold = 20
scale_action = "Decrease"
scale_instance_count = 1
time_window = 5
scale_cooldown = 5
}
]
schedule = []
}]
validation {
condition = ! contains([
for profile in var.scaling_profile :
[for trigger in profile.trigger :
profile.trigger != [] ?
contains(["GreaterThan", "LessThan", "GreaterThanOrEqual", "NotEquals", "Equals", "LessThanOrEqual"], trigger.operator)
: true]
], false)
error_message = "Threshold operator must be one of following values: GreaterThan, LessThan,GreaterThanOrEqual, NotEquals, Equals, LessThanOrEqual."
}
validation {
condition = ! contains([
for profile in var.scaling_profile :
[for trigger in profile.trigger :
profile.trigger != [] ?
contains(["Increase", "Decrease"], trigger.scale_action)
: true]
], false)
error_message = "Scale action must be one of the following: Increase or Decrease."
}
}
`
The module is being called as below. I have taken out the trigger block and I am only accounting for the schedule block. I want to be able to check for the existence of the trigger block before validation.
`
module "autoscale_vmss_linux" {
source = "../../"
resource_group_name = var.resource_group_name
target_resource_id = module.vmss_linux.id
default_scaling_profile = {
capacity = 1
}
scaling_profile = var.scaling_profile_vmss
depends_on = [
module.vmss_linux
]
}
variable "scaling_profile_vmss" {
description = "set of rules used to determine when autoscale triggers"
default = [{
capacity = 3
schedule = [{
days = ["Monday"],
start_hour = 12,
start_minute = 20,
end_hour = 12,
end_minute = 30
}]
}]
}
`
Below is the error I am receiving when running the apply.
╷ │ Error: Unsupported attribute │ │ on ..\..\variables.tf line 70, in variable "default_scaling_profile": │ 70: [for trigger in profile.trigger : │ │ Can't access attributes on a primitive-typed value (number). ╵ ╷ │ Error: Attempt to get attribute from null value │ │ on ..\..\variables.tf line 70, in variable "default_scaling_profile": │ 70: [for trigger in profile.trigger : │ │ This value is null, so it does not have any attributes. ╵ ╷ │ Error: Unsupported attribute │ │ on ..\..\variables.tf line 70, in variable "default_scaling_profile": │ 70: [for trigger in profile.trigger : │ │ Can't access attributes on a list of objects. Did you mean to access an attribute for a specific element of the list, or across all elements of the list? ╵ ╷ │ Error: Unsupported attribute │ │ on ..\..\variables.tf line 80, in variable "default_scaling_profile": │ 80: [for trigger in profile.trigger : │ │ Can't access attributes on a primitive-typed value (number). ╵ ╷ │ Error: Attempt to get attribute from null value │ │ on ..\..\variables.tf line 80, in variable "default_scaling_profile": │ 80: [for trigger in profile.trigger : │ │ This value is null, so it does not have any attributes. ╵ ╷ │ Error: Unsupported attribute │ │ on ..\..\variables.tf line 80, in variable "default_scaling_profile": │ 80: [for trigger in profile.trigger : │ │ Can't access attributes on a list of objects. Did you mean to access an attribute for a specific element of the list, or across all elements of the list?
I've rearranged the validation step several times but I continue to run into the issue of accessing attributes from a list of objects. I've nested the validation in such a way that it should be able to iterate through the trigger options. I have also created an optional default for the trigger block to allow for an empty list such that when the validation step checks for trigger not equal to an empty list, the validation proceeds as normal. However, I have the resulting errors. Any insight would be greatly appreciated.

what is the 'availability_zone' argument in terraform for azure?

I'm trying to create a Virtual network in Azure with a NAT gateway, using terraform.
I'm getting the following warning when I run terraform plan :
Warning: Argument is deprecated
│
│ with azurerm_public_ip.example2,
│ on PublicIPandNAT.tf line 16, in resource "azurerm_public_ip" "example2":
│ 16: zones = ["1"]
│
│ This property has been deprecated in favour of `availability_zone` due to a breaking behavioural change in Azure:
│ https://azure.microsoft.com/en-us/updates/zone-behavior-change/
│
│ (and one more similar warning elsewhere)
╵
Do you want to perform these actions?
But in the azurerm provider documentation on registry.terraform.io, there is no reference to a availability_zone argument in the azurerm_public_ip resource.
Is the terraform documentation out of date? what is the syntax of the availability_zone argument? and what is the risk of using the zones argument?
Trying to create a Virtual network in Azure with a NAT gateway, using
terraform.
To create Virtual network with NAT Gateway using terraform, we have tried at our end with following code and it works fine without any error.
You can use the below code to do the same by adding your required name :-
main.tf:-
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "example" {
name = "nat-gateway-example-rg"
location = "West Europe"
}
resource "azurerm_public_ip" "example" {
name = "nat-gateway-publicIP"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
allocation_method = "Static"
sku = "Standard"
zones = ["1"]
}
resource "azurerm_virtual_network" "example" {
name = "example-network"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
address_space = ["10.0.0.0/16"]
dns_servers = ["10.0.0.4", "10.0.0.5"]
}
resource "azurerm_public_ip_prefix" "example" {
name = "nat-gateway-publicIPPrefix"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
prefix_length = 30
zones = ["1"]
}
resource "azurerm_nat_gateway" "example" {
name = "nat-Gateway"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
sku_name = "Standard"
idle_timeout_in_minutes = 10
zones = ["1"]
}
OUTPUT DETAILS:-
My terraform version :-
what is the syntax of the availability_zone argument? and what is the
risk of using the zones argument?
AFAIK, there is no risk of use availability zone and you can find the reference in aforementioned code .
For more information please refer this HashiCorp| azurerm_nat_gateway

Terraform Map of List input variables-For_each

I am trying to implement for_each for map of list input variables.
What the output should be is a list of subnets should be created for AZ us-east-1a(3 subnets) and us-east-1b (2 subnets).
Need your help for the below:
MAIN.TF
resource "aws_vpc" "name" {
cidr_block = "192.168.0.0/16"
}
resource "aws_subnet" "name" {
for_each = var.subnetcidr
vpc_id = aws_vpc.name.id
availability_zone = each.key
cidr_block = toset(each.value) //need help here
}
TFVARS FILE
subnetcidr = {
"us-east-1a" = ["192.168.1.0/24","192.168.2.0/24","192.168.0.0/24"]
"us-east-1b" = ["192.168.3.0/24","192.168.4.0/24"]
}
Error:
Error: Incorrect attribute value type
on main.tf line 28, in resource "aws_subnet" "name":
28: cidr_block = toset(each.value)
|----------------
| each.value is tuple with 2 elements
Inappropriate value for attribute "cidr_block": string required.
Any help would be appreciated. TIA!
The error seems clear:
Inappropriate value for attribute "cidr_block": string required
Not sure why you are passing a list...
If you need to keep it as a list, an option could be getting the first value:
resource "aws_vpc" "name" {
cidr_block = "192.168.0.0/16"
}
resource "aws_subnet" "name" {
for_each = var.subnetcidr
vpc_id = aws_vpc.name.id
availability_zone = each.key
cidr_block = element(each.value, 0)
}
If you can change the subnetcidr variable, go with something like:
subnetcidr = {
"192.168.0.0/24" = "us-east-1a"
"192.168.1.0/24" = "us-east-1a"
"192.168.2.0/24" = "us-east-1a"
"192.168.3.0/24" = "us-east-1b"
"192.168.4.0/24" = "us-east-1b"
}
As you can see now the unique cidr blocks are the keys and the zones are the values.
Then in your code we do like this:
resource "aws_vpc" "name" {
cidr_block = "192.168.0.0/16"
}
resource "aws_subnet" "name" {
for_each = var.subnetcidr
vpc_id = aws_vpc.name.id
availability_zone = each.value
cidr_block = each.key
}
I was finally able to solve this. Below is how.
resource "aws_vpc" "name" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "name" {
vpc_id = aws_vpc.name.id
for_each = transpose(var.subnet)
availability_zone = each.value[0]
cidr_block = each.key
}
variable subnet {}
subnet = {
"us-east-1a" = ["10.0.0.0/20","10.0.16.0/20","10.0.32.0/20"]
"us-east-1b" =
["10.0.64.0/20","10.0.80.0/20"]
"us-east-1d" = ["10.0.112.0/20","10.0.128.0/20","10.0.144.0/20","10.0.96.0/20"]
}

Metric math alarms: How can I use a for_each expression to loop over metrics within a dynamic block?

I am trying to create dynamic metric math alarms, that are configurable with a JSON.
I am struggling with looping over the metric alarm with a for_each expression as this is a loop within a loop.
Here is an example of what I am trying to do:
resource "aws_cloudwatch_metric_alarm" "Percentage_Alert" {
for_each = var.percentage_error_details
locals { alarm_details = each.value }
alarm_name = "${terraform.workspace}-${each.key}"
comparison_operator = local.alarm_details["Comparison_Operator"]
evaluation_periods = "1"
threshold = local.alarm_details["Threshold"]
metric_query {
id = "e1"
expression = local.alarm_details["Expression"]
label = local.alarm_details["Label"]
return_data = "true"
}
dynamic "metric_query" {
for metric in each.value["Metrics"]{
id = metric.key
metric_name = metric.value
period = local.alarm_details["Period"]
stat = local.alarm_details["Statistic"]
namespace = local.full_namespace
unit = "Count"
}
}
}
And this is the sample JSON
{
"locals": {
"Name": {
"Name": "metric_math",
"Metrics": {
"m1": "Sucess",
"m2": "Failure"
},
"Expression": "100*(m2/(m1+m2))",
"Threshold" : 1,
"Period": 25,
"Priority": "critical",
"Statistic": "Sum",
"Label": "label",
"Comparison_Operator": "GreaterThanOrEqualToThreshold"
}
}
}
And this is the error message i'm getting:
Error: Invalid block definition
On ../modules/cloudwatch/metriclogfilter/main.tf line 89: Either a quoted
string block label or an opening brace ("{") is expected here.
Any help would be much appreciated.

Issues in passing key vault secrets to the Data.tf and referencing in Main.tf

I have created multiple key vault secrets in the Azure portal and trying pass them in terraform Data.tf. But i am not sure how to pass multiple secrets and refer in the main.tf. Can someone help on this.
My requirement is to pass multiple values in the Name attribute and refer in Main.tf
data.tf
data "azurerm_key_vault" "key_vault" {
name = "test-key-vault-cts"
resource_group_name = "gcdmvrlyprd03-30cf06a8"
}
data "azurerm_key_vault_secret" "admin_password" {
name = "admin-password"
key_vault_id = data.azurerm_key_vault.key_vault.id
}
Main.tf
module "location_us-west" {
source = "./Modules"
web_server_location = "westus2"
web_server_rg = "${var.web_server_rg}-us-west"
resource_prefix = "${var.resource_prefix}-us-west"
web_server_address_space = "10.0.0.0/22"
#web_server_address_prefix = "10.0.1.0/24"
web_server_name = var.web_server_name
environment = var.environment
size = var.vm_size
admin_user = var.user
admin_password = data.azurerm_key_vault_secret.admin_password.value
web_server_count = var.web_server_count
web_server_subnets = {
"web-server" = "10.0.1.0/24"
"AzureBastionSubnet" = "10.0.2.0/24"
}
}
If you want to reference a second secret via data, create a second block of code.
data "azurerm_key_vault_secret" "admin_password2" {
name = "admin-password2"
key_vault_id = data.azurerm_key_vault.key_vault.id
}
In my opinion, you can put the data sources together with the module location_us-west in the main.tf file. So that you can quote the data.azurerm_key_vault_secret.admin_password.value directly. I don't recommend you desperate the data sources from the main.tf file if you just want to quote the secrets without any other actions.
And for the secrets, you need to put the names for multiple secrets to get them from the Key vault. So if you want to get multiple secrets only one time, you can change the code like this example:
variable "secret_names" {
type = list
default = [
"test1",
"test2"
]
}
data "azurerm_key_vault" "example" {
name = "key_vault_name"
resource_group_name = "group_name"
}
data "azurerm_key_vault_secret" "example" {
count = length(var.secret_names)
name = element(var.secret_names, count.index)
key_vault_id = data.azurerm_key_vault.example.id
}
Then the output data.azurerm_key_vault_secret.example.*.value will contain all the secret values in a list.

Resources