Terraform validation for optional variables and for list of object variables - terraform-provider-azure

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.

Related

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

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

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.

Attempting to edit array that contains specific key

So I am trying to edit my config list where it has to edit robbed to true when entity is equal to entity in the list (entities get generated when my script is starting)
Config file
Config.location = {
[1] = {
x = 24.39,
y = -1345.776,
z = 29.49,
h = 267.58,
robbed = false,
entity = nil
},
[2] = {
x = -47.7546,
y = -1759.276,
z = 29.421,
h = 48.035,
robbed = false,
entity = nil
},
}
So this list gets loaded - When [1] has been robbed it should change robbed in [1] if the entity matches.
I would imagine i should do a for loop but i'm still clueless.
As Config.list is a sequence with positive integer keys starting from 1 you can conveniently use the iparis iterator in combination with a generic for loop to check every entry in your list.
for i,v in ipairs(Config.location) do
v.robbed = v.entity == someOtherEntity and true or false
end
Of course your entity entries shouldn't be nil as this wouldn't make sense.

How to use prepare_analogy_questions and check_analogy_accuracy functions in text2vec package?

Following code:
library(text2vec)
text8_file = "text8"
if (!file.exists(text8_file)) {
download.file("http://mattmahoney.net/dc/text8.zip", "text8.zip")
unzip ("text8.zip", files = "text8")
}
wiki = readLines(text8_file, n = 1, warn = FALSE)
# Create iterator over tokens
tokens <- space_tokenizer(wiki)
# Create vocabulary. Terms will be unigrams (simple words).
it = itoken(tokens, progressbar = FALSE)
vocab <- create_vocabulary(it)
vocab <- prune_vocabulary(vocab, term_count_min = 5L)
# Use our filtered vocabulary
vectorizer <- vocab_vectorizer(vocab)
# use window of 5 for context words
tcm <- create_tcm(it, vectorizer, skip_grams_window = 5L)
RcppParallel::setThreadOptions(numThreads = 4)
glove_model = GloVe$new(word_vectors_size = 50, vocabulary = vocab, x_max = 10, learning_rate = .25)
word_vectors_main = glove_model$fit_transform(tcm, n_iter = 20)
word_vectors_context = glove_model$components
word_vectors = word_vectors_main + t(word_vectors_context)
causes error:
qlst <- prepare_analogy_questions("questions-words.txt", rownames(word_vectors))
> Error in (function (fmt, ...) :
invalid format '%d'; use format %s for character objects
File questions-words.txt from word2vec sources https://github.com/nicholas-leonard/word2vec/blob/master/questions-words.txt
This was a small bug in information message formatting (after introduction of futille.logger). Just fixed it and pushed to github.
You can install updated version of the package with devtools::install_github("dselivanov/text2vec"

Can't match the right function

Here's my codes:
-record(user,{id,name,group,age}).
adult_section(U=#user{}) when U#user.age >=18 -> "allowed";
adult_section(_) -> "no permission".
it can be worked with:
>records_new:adult_section(#user{id=1,name='Ray',group=admin,age=10}).
output: "no permission"
but when I try this:
> records_new:adult_section(#user{}).
"allowed"
I didn't give the user's age in the second order, why it can be worked, why is not "no permission".
Since your record does not have a default value for age, if you create the record without specifying a value for age, it defaults to the atom undefined.
1> #user{}.
#user{id = undefined,name = undefined,group = undefined,
age = undefined}
In Erlang, atoms compare greater than integers:
2> undefined > 18.
true
3> foo > 100000000.
true
If you specify a default value of age to be e.g. 0, your function works as you expect:
-record(user,{id,name,group,age = 0}).
1> #user{}.
#user{id = undefined,name = undefined,group = undefined,
age = 0}
2> a:adult_section(#user{}).
"no permission"
You should look into Dogbert answer. His explanation is spot-on.
I would like to only add that you could protect yourself from such "bad-data" injection by expanding your guard.
adult_section(U=#user{}) when
is_integer(U#user.age) andalso
U#user.age >=18 ->
"allowed";

Resources