if condition in terraform in count - terraform

I am adding autoscale settings in the Azure cosmosdb database, My problem is not all our db requires autoscale only a selection of database require autoscalse rest are manual. I will not be able to specify the autoscalse block also the throughout in the same resource as there are conflicts between those two. so I thought of using the count but I will be not be able to run the resouece block for only one of the DB. for the below example
variable
variable "databases" {
description = "The list of Cosmos DB SQL Databases."
type = list(object({
name = string
throughput = number
autoscale = bool
max_throughput = number
}))
default = [
{
name = "testcoll1"
throughput = 400
autoscale = false
max_throughput = 0
},
{
name = "testcoll2"
throughput = 400
autoscale = true
max_throughput = 1000
}
]
}
For the first I dont need autoscale and next one I need. My main.tf code
resource "azurerm_cosmosdb_mongo_database" "database_manual" {
count = length(var.databases)
name = var.databases[count.index].name
resource_group_name = azurerm_cosmosdb_account.cosmosdb.resource_group_name
account_name = local.account_name
throughput = var.databases[count.index].throughput
}
resource "azurerm_cosmosdb_mongo_database" "database_autoscale" {
count = length(var.databases)
name = var.databases[count.index].name
resource_group_name = azurerm_cosmosdb_account.cosmosdb.resource_group_name
account_name = local.account_name
autoscale_settings {
max_throughput = var.databases[count.index].max_throughput
}
}
First I thought of running two blocks one with scale and on without, but I will not be able to proceed because it requires the count numbers
count = var.autoscale_required == true ? len(databases) : 0
at the start but in my case I will only know at the time of iteration. I have tried to use dynamic within the block but errored out.
*Update
I have switched to foreach and able to run the condition but still it requires 2 blocks
resource "azurerm_cosmosdb_mongo_database" "database_autoscale"
resource "azurerm_cosmosdb_mongo_database" "database_manual"
resource "azurerm_cosmosdb_mongo_database" "database_autoscale" {
for_each = {
for key, value in var.databases : key => value
if value.autoscale_required == true }
name = each.value.name
resource_group_name = azurerm_cosmosdb_account.cosmosdb.resource_group_name
account_name = local.account_name
autoscale_settings {
max_throughput = each.value.max_throughput
}
}

If I understand correctly, I think you could do what you want using the following:
resource "azurerm_cosmosdb_mongo_database" "database_autoscale" {
count = length(var.databases)
name = var.databases[count.index].name
resource_group_name = azurerm_cosmosdb_account.cosmosdb.resource_group_name
account_name = local.account_name
throughput = var.databases[count.index].autoscale == false ? var.databases[count.index].throughput : null
dynamic "autoscale_settings" {
for_each = var.databases[count.index].autoscale == false ? [] : [1]
content {
max_throughput = var.databases[count.index].max_throughput
}
}
}

Related

auto scale azure spring app URI with terraform

I need enable auto scale for an spring app hosted by spring app services.I am used below terraform code.
resource "azurerm_monitor_autoscale_setting" "spring_apps_app_carrier_events" {
name = "default_auto_scale"
enabled = true
resource_group_name = module.rg.resource_group_name
location = module.rg.resource_group_location
target_resource_id = module.spring_apps_app_carrier_events.app_identities[0].principal_id
profile {
name = "defaultProfile"
capacity {
default = 1
minimum = 1
maximum = 2
}
It return errors:
Error: Can not parse "target_resource_id" as a resource id: Cannot parse Azure ID: parse "290dc6bd-1895-4e52-bac2-a34e63a138a9": invalid URI for request
It seems it need a uri. May u know how can I get the uri of a spring app?
Thanks in advance
I tried to reproduce the same in my environment.
Received the same error:
│ Error: Can not parse "target_resource_id" as a resource id: Cannot parse Azure ID: parse "xxxxx": invalid URI for request
│ with azurerm_monitor_autoscale_setting.spring_apps_app_carrier_events,
The target_resource_id should not be in just number id form,
It has to be something like /subscriptions/xxxxxc/resourceGroups/<myrg>/providers/Microsoft.xxx/xx/sxx
In your case,
target_resource_id = module.spring_apps_app_carrier_events.app_identities[0].principal_id
gives the principal Id which is in “23434354544466” format which is not correct.
I tried below code with targetid being, resourceId : /subscriptions/xxx/resourceGroups/ <myrg>/providers/Microsoft.AppPlatform/spring/springcloudappkavya/apps/kaexamplspringcloudapp/deployments/kavyadeploy1
Code:
resource "azurerm_spring_cloud_service" "example" {
name = "springcloudappkavya"
location =data.azurerm_resource_group.example.location
resource_group_name = data.azurerm_resource_group.example.name
sku_name = "S0"
config_server_git_setting {
uri = "https://github.com/Azure-Samples/piggymetrics"
label = "config"
search_paths = ["dir1", "dir2"]
}
trace {
connection_string = azurerm_application_insights.example.connection_string
sample_rate = 10.0
}
tags = {
Env = "staging"
}
}
resource "azurerm_spring_cloud_app" "example" {
name = "kaexamplspringcloudapp"
resource_group_name = data.azurerm_resource_group.example.name
service_name = azurerm_spring_cloud_service.example.name
identity {
type = "SystemAssigned"
}
}
resource "azurerm_spring_cloud_java_deployment" "test" {
name = "kavyadeploy1"
spring_cloud_app_id = azurerm_spring_cloud_app.example.id
instance_count = 2
jvm_options = "-XX:+PrintGC"
quota {
cpu = "2"
memory = "4Gi"
}
runtime_version = "Java_11"
environment_variables = {
"Foo" : "Bar"
"Env" : "Staging"
}
}
resource "azurerm_monitor_autoscale_setting" "spring_apps_app_carrier_events" {
name = "default_auto_scale"
enabled = true
resource_group_name = data.azurerm_resource_group.example.name
location = data.azurerm_resource_group.example.location
target_resource_id = azurerm_spring_cloud_java_deployment.test.id
// target_resource_id = .spring_apps_app_carrier_events.app_identities[0].principal_id
// target_resource_id = "18xxxxxe2"
profile {
name = "metricRules"
capacity {
default = 1
minimum = 1
maximum = 2
}
rule {
metric_trigger {
dimensions {
name = "AppName"
operator = "Equals"
values = [azurerm_spring_cloud_app.example.name]
}
dimensions {
name = "Deployment"
operator = "Equals"
values = [azurerm_spring_cloud_java_deployment.test.name]
}
metric_name = "AppCpuUsage"
metric_namespace = "microsoft.appplatform/spring"
metric_resource_id = azurerm_spring_cloud_service.example.id
time_grain = "PT1M"
statistic = "Average"
time_window = "PT5M"
time_aggregation = "Average"
operator = "GreaterThan"
threshold = 75
}
scale_action {
direction = "Increase"
type = "ChangeCount"
value = 1
cooldown = "PT1M"
}
}
}
}
Could execute without errors.
Portal view of Autoscale settings for spring apps.
Reference : An Azure Spring Cloud Update: Managed Virtual Network and Autoscale are now generally available in Azure Spring Cloud

Terraform - dynamic block within resource with "count" doesn't work

I'm using the AWS provider for creating CloudWatch Metric Alarms. I created a module which takes in a variable that is a list of instance IDs, and the resource it has uses the "count" functionality to create an alarm per an Instance ID from that variable.
The "aws_cloudwatch_metric_alarm" resource can take in multiple "metric_query" blocks, and my plan was to do this as dynamic block to be able to define as many as needed in the root module.
Issue I'm experiencing is with accessing the "for_each" iterator values.
The high-level end solution should be something among these lines: Use 3 metric blocks, two are available metrics and a third one for an expression on top of the other two, and create this alarm for every instance that is provided in the instance list.
Resource definition, module code:
resource "aws_cloudwatch_metric_alarm" "alarm" {
count = length(var.dimension_values)
alarm_name = "${var.alarm_name}_${var.dimension_values[count.index]}"
comparison_operator = var.comparison_operator
evaluation_periods = var.evaluation_periods
threshold = var.threshold
actions_enabled = var.actions_enabled
alarm_actions = var.alarm_actions
dynamic "metric_query" {
for_each = var.metric_queries
content {
id = metric_queries.value.id
return_data = metric_queries.value.return_data
expression = metric_queries.value.expression
label = metric_queries.value.label
metric {
namespace = metric_queries.value.namespace
metric_name = metric_queries.value.metric_name
period = metric_queries.value.period
stat = metric_queries.value.stat
dimensions = {
"${metric_queries.value.dimension_name}" = var.dimension_values[count.index]
}
}
}
}
tags = merge(
var.common_tags,
{
Name = "${var.alarm_name}_${var.dimension_values[count.index]}"
}
)
}
Module variables (only metric_queries pasted):
variable "metric_queries" {
type = list(object({
id = string
return_data = bool
expression = string
label = string
namespace = string
metric_name = string
period = number
stat = string
dimension_name = string
}))
description = "Metric query for the CloudWatch alarm"
default = []
}
And finally, the root module:
module "cpu_alarms" {
source = "../../Modules/cloudwatch_metric_alarm/"
common_tags = local.common_tags
# Metrics
alarm_name = "EC2_CPU_80_PERCENT"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = 3
threshold = 80
actions_enabled = true
alarm_actions = ["redacted"]
dimension_values = local.all_ec2_instance_ids
metric_queries = [
{
id = "m1"
return_data = true
expression = null
label = "CPU utilization"
namespace = "AWS/EC2"
metric_name = "CPUUtilization"
period = 60
stat = "Average"
dimension_name = "InstanceId"
}
]
}
I'm getting two separate errors with this approach depending on how I'm referring to the "for_each" iterator object.
When using "each" as reference to the iterator the error is:
A reference to "each.value" has been used in a context in which it unavailable, such as when the configuration no longer contains the value in its "for_each" expression. Remove this reference to each.value in your configuration to work │ around this error.
When using "metric_queries" as reference to the iterator the error is:
A managed resource "metric_queries" "value" has not been declared in module.cpu_alarms.
What could be the root cause of this?
Please see the documentation on dynamic blocks. You are trying to use the syntax for the resource level for_each meta-argument, not the syntax for dynamic blocks. It's confusing that they have different syntax, but since a dynamic block could exist inside a resource with for_each, the syntax has to be different to prevent name clashes.
For dynamic blocks the name of the variable is what you put after the dynamic key word, in your case "metric_query". So your code should look like this:
dynamic "metric_query" {
for_each = var.metric_queries
content {
id = metric_query.value.id
return_data = metric_query.value.return_data
expression = metric_query.value.expression
label = metric_query.value.label
metric {
namespace = metric_query.value.namespace
metric_name = metric_query.value.metric_name
period = metric_query.value.period
stat = metric_query.value.stat
dimensions = {
"${metric_query.value.dimension_name}" = var.dimension_values[count.index]
}
}
}
}

Terraform ignore sub-block changes in AWS metric_query

Am in the process of trying to configure IOPS alerting on EBS volumes as we move them to GP3. The plan is to configure the alarms in TF but to shift the setting of the target to a lambda that can keep the alarm up-to-date based on lifecycle changes to the ASG. For GP2 volumes I was able to get this configured cleanly and have ignore_changes on the dimensions block of each alert but now that I have moved to several metric_query blocks I cannot seem to find a way to address the nested dimension config.
resource "aws_cloudwatch_metric_alarm" "foobar" {
count = length(data.aws_availability_zones.available.names)
alarm_name = "${local.env_short}_app_volume_IOPS_${data.aws_availability_zones.available.names[count.index]}"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = "5"
threshold = "2700"
alarm_description = "IOPS in breach of 90% of provisioned"
insufficient_data_actions = []
actions_enabled = "true"
datapoints_to_alarm = "5"
alarm_actions = [aws_sns_topic.app_alert.arn]
ok_actions = [aws_sns_topic.app_alert.arn]
metric_query {
id = "e1"
expression = "(m1+m2)/PERIOD(m1)"
label = "IOPSCalc"
return_data = "true"
}
metric_query {
id = "m1"
metric {
metric_name = "VolumeWriteOps"
namespace = "AWS/EBS"
period = "60"
stat = "Sum"
dimensions = {}
}
}
metric_query {
id = "m2"
metric {
metric_name = "VolumeReadOps"
namespace = "AWS/EBS"
period = "60"
stat = "Sum"
dimensions = {}
}
}
lifecycle {
ignore_changes = [metric_query.1.metric.dimensions]
}
}
I have tried various iterations of the ignore_changes block and so far have only succeeded if I set the value to [metric_query] but that then ignores the whole thing whereas I am trying just to target the metric_query.metric.dimensions piece. Anyone have any clever ideas around addressing this block?

Terraform refactoring for azurerm_dns_txt_record use in a module record block

I've got the following code in a terraform module:
locals {
txt_records = flatten(
[
for i in var.DnsZones :
{
zone_name = i.ZoneName
resource_group_name = i.ResourceGroup
name = "#"
ttl = i.Ttl
records = i.TxtRecords
}
]
)
}
resource "azurerm_dns_zone" "zone" {
count = length(var.DnsZones)
name = var.DnsZones[count.index].ZoneName
resource_group_name = var.DnsZones[count.index].ResourceGroup
}
resource "azurerm_dns_txt_record" "record-txt" {
count = length(local.txt_records)
resource_group_name = local.txt_records[count.index].resource_group_name
zone_name = local.txt_records[count.index].zone_name
ttl = local.txt_records[count.index].ttl
name = local.txt_records[count.index].name
record {
value = local.txt_records[count.index].records[0]
}
record {
value = local.txt_records[count.index].records[1]
}
record {
value = local.txt_records[count.index].records[2]
}
record {
value = local.txt_records[count.index].records[3]
}
record {
value = local.txt_records[count.index].records[4]
}
record {
value = local.txt_records[count.index].records[5]
}
record {
value = local.txt_records[count.index].records[6]
}
depends_on = [azurerm_dns_zone.zone]
}
It doesn't seem like a very clean way of adding record blocks, but i can't find a better way of doing it.
I've tried refactoring it in this way:
resource "azurerm_dns_txt_record" "record-txt" {
count = length(local.txt_records)
resource_group_name = local.txt_records[count.index].resource_group_name
zone_name = local.txt_records[count.index].zone_name
ttl = local.txt_records[count.index].ttl
name = local.txt_records[count.index].name
dynamic "record" {
for_each = local.txt_records[count.index].records
iterator = i
content {
value = i.value
}
}
depends_on = [azurerm_dns_zone.zone]
}
but unfortunately, this results in a single MX record in our DNS where we should have 7 items inserted. It seems that each item gets inserted on top of the previous one.
As you can see each of the record blocks needs to be separated in the resource: MX record resource
Can anyone think of a better way of structuring this terraform?
Your dynamic block should be:
dynamic "record" {
for_each = local.txt_records[count.index].records
content {
value = record.value
}
}
not
dynamic "record" {
for_each = local.txt_records[count.index].records
content {
value = i.value
}
}

Terraform's random_shuffle don't shuffle as expected

I have this configuration :
variable "sub_list" {
type = "map"
default = {
"data.dev" = ["data1", "data2", "data3", "data4"]
"data.dev2" = ["data1", "data2", "data3", "data4"]
}
}
resource "random_shuffle" "az" {
input = "${var.sub_list[local.data]}"
result_count = "${length(var.VM_count)}"
}
data "vsphere_sub" "sub" {
count = "${length(var.VM_count)}"
name = "${random_shuffle.az.result[count.index]}"
}
resource "vsphere_virtual_machine" "VM" {
name = "${var.VM_name}
folder = "${var.folder}"
count = "${length(var.VM_count)}"
sub_id = "${element(data.vsphere_sub.sub.*.id, (count.index)%length(data.vsphere_sub.sub.id))}"
num_cpus = "${var.VM_vcpu}"
memory = "${var.VM_memory}"
}
When I launch with VM_count=2 for example, I expect to have a subnet for every VM but it creates the 2 VMs in the same subnet, and it shuffles just one time and not 2. How we could select randomly an item from a map based on the number of VMs to be created ?
Thank you for your help
A couple issues. You cannot get the length of a number so this
count = length(var.VM_count)
should be
count = var.VM_count
Unsure what this line's intention is but this
sub_id = element(data.vsphere_sub.sub.*.id, (count.index)%length(data.vsphere_sub.sub.id))
should be this if we want a different subnet
sub_id = element(data.vsphere_sub.sub.*.id, count.index)
so the final result would be
resource "random_shuffle" "az" {
input = "${var.sub_list[local.data]}"
result_count = "${var.VM_count}"
}
data "vsphere_sub" "sub" {
count = "${var.VM_count}"
name = "${random_shuffle.az.result[count.index]}"
}
resource "vsphere_virtual_machine" "VM" {
name = "${var.VM_name}"
folder = "${var.folder}"
count = "${var.VM_count}"
sub_id = "${element(data.vsphere_sub.sub.*.id, count.index)}"
num_cpus = "${var.VM_vcpu}"
memory = "${var.VM_memory}"
}
Now when you apply with a VM_count=2, it should grab 2 random subnets from sub_list and create 2 VMs with each having a different subnet.

Resources