Terraform deploy module with a condition - azure

what am I trying to accomplish?
I am trying to deploy a terraform module only if a input variable is greater than 0
what have I tried?
var.blocks > 0 ? module.diagnostic_mssql_db.target_resource_id : 1,
what was the result?
Error: Unsupported attribute
on ..\mssql-tf-docs\modules\db\main.tf line 27, in module "diagnostic_mssql_db":
27: "Blocks" = var.blocks > 0 ? module.diagnostic_mssql_db.target_resource_id : 1,
|----------------
| module.diagnostic_mssql_db is object with no attributes
This object does not have an attribute named "target_resource_id".
where is your example code?
notice in the part below "main.tf"
section "ds_log_api_endpoints"
main.tf
# SQL Server Database within a SQL Server Server
resource "azurerm_mssql_database" "db" {
name = var.name
server_id = var.server_id
collation = var.collation
license_type = var.license_type
sku_name = var.sku_name
max_size_gb = var.max_size_gb
zone_redundant = var.zone_redundant
read_scale = var.read_scale
read_replica_count = var.read_replica_count
auto_pause_delay_in_minutes = var.sku_name == "GP_S_Gen5_1" ? var.auto_pause_delay_in_minutes : 0
min_capacity = var.sku_name == "GP_S_Gen5_1" ? var.min_capacity : 0
}
# Diagnostic setting
module "diagnostic_mssql_db" {
source = "github.com/faraday23/terraform-azurerm-monitor-diagnostic-setting.git"
storage_account = var.sa_storage_account
sa_resource_group = var.storage_account_resource_group
target_resource_id = azurerm_mssql_database.db.id
target_resource_name = azurerm_mssql_database.db.name
ds_allmetrics_rentention_days = var.metric
ds_log_api_endpoints = { "AutomaticTuning" = var.automatic_tuning > 0 ? module.diagnostic_mssql_db.target_resource_id : 1
"Blocks" = var.blocks > 0 ? module.diagnostic_mssql_db.target_resource_id : 1,
"DatabaseWaitStatistics" = var.database_wait_statistics > 0 ? module.diagnostic_mssql_db.target_resource_id : 1,
"Deadlocks" = var.deadlocks > 0 ? module.diagnostic_mssql_db.target_resource_id : 1,
"Errors" = var.error_log > 0 ? module.diagnostic_mssql_db.target_resource_id : 1,
"Timeouts" = var.timeouts > 0 ? module.diagnostic_mssql_db.target_resource_id : 1,
"QueryStoreRuntimeStatistics" = var.query_store_runtime_statistics > 0 ? module.diagnostic_mssql_db.target_resource_id : 1,
"QueryStoreWaitStatistics" = var.query_store_wait_statistics > 0? module.diagnostic_mssql_db.target_resource_id : 1,
"SQLinsights" = var.sql_insights > 0 ? module.diagnostic_mssql_db.target_resource_id : 1
}
}
variables.tf
#######
# Name
#######
variable "name" {
description = "The name of the Ms SQL Database. Changing this forces a new resource to be created."
type = string
}
############
# Server Id
############
variable "server_id" {
description = "The id of the Ms SQL Server on which to create the database. Changing this forces a new resource to be created."
type = string
default = ""
}
#######
# Flags
#######
# Audit Log Enabled
variable "audit_log_enabled" {
description = "The audit log enabled of the resource."
type = bool
default = false
}
variable "log_retention_days" {
description = "Specifies the number of days to keep in the Threat Detection audit logs"
type = number
default = 7
}
variable "collation" {
description = "Specifies the collation of the database. Changing this forces a new resource to be created."
type = string
default = "SQL_Latin1_General_CP1_CI_AS"
}
variable "license_type" {
description = "Specifies the license type applied to this database. Possible values are LicenseIncluded and BasePrice."
type = string
default = "LicenseIncluded"
}
variable "sku_name" {
description = "Specifies the name of the sku used by the database. Changing this forces a new resource to be created. For example, GP_S_Gen5_2,HS_Gen4_1,BC_Gen5_2, ElasticPool, Basic,S0, P2 ,DW100c, DS100."
type = string
default = "GP_S_Gen5_1"
validation {
condition = can(regex("GP_S_Gen5_1|GP_S_Gen5_2|HS_Gen4_1|BC_Gen5_2|ElasticPool|Basic|S0|P2|DW100c|DS100", var.sku_name))
error_message = "Sku name invalid. Must be GP_S_Gen5_1,GP_S_Gen5_2,HS_Gen4_1,BC_Gen5_2, ElasticPool, Basic,S0, P2 ,DW100c, DS100."
}
}
variable "min_capacity" {
type = number
default = 0.5
}
variable "auto_pause_delay_in_minutes" {
type = number
default = -1
}
variable "max_size_gb" {
type = number
default = 4
}
variable "zone_redundant" {
type = bool
default = false
}
variable "read_scale" {
type = bool
default = false
}
variable "read_replica_count" {
type = number
default = 0
}
##
# Diagnostic setting variable
##
variable "sa_storage_account" {
description = "This blob storage will hold all Threat Detection audit logs. Required if state is Enabled."
type = string
default = ""
}
variable "storage_account_resource_group" {
description = "Azure resource group where the storage account resides."
type = string
}
variable "automatic_tuning" {
description = "Retention only applies to storage account. Retention policy ranges from 1 to 365 days. If you do not want to apply any retention policy and retain data forever, set retention (days) to 0."
type = string
}
variable "database_wait_statistics" {
description = "Retention only applies to storage account. Retention policy ranges from 1 to 365 days. If you do not want to apply any retention policy and retain data forever, set retention (days) to 0."
type = string
}
variable "query_store_runtime_statistics" {
description = "Retention only applies to storage account. Retention policy ranges from 1 to 365 days. If you do not want to apply any retention policy and retain data forever, set retention (days) to 0."
type = string
}
variable "query_store_wait_statistics" {
description = "Retention only applies to storage account. Retention policy ranges from 1 to 365 days. If you do not want to apply any retention policy and retain data forever, set retention (days) to 0."
type = string
}
variable "error_log" {
description = "Retention only applies to storage account. Retention policy ranges from 1 to 365 days. If you do not want to apply any retention policy and retain data forever, set retention (days) to 0."
type = string
}
variable "sql_insights" {
description = "Retention only applies to storage account. Retention policy ranges from 1 to 365 days. If you do not want to apply any retention policy and retain data forever, set retention (days) to 0."
type = string
}
variable "deadlocks" {
description = "Retention only applies to storage account. Retention policy ranges from 1 to 365 days. If you do not want to apply any retention policy and retain data forever, set retention (days) to 0."
type = string
}
variable "timeouts" {
description = "Retention only applies to storage account. Retention policy ranges from 1 to 365 days. If you do not want to apply any retention policy and retain data forever, set retention (days) to 0."
type = string
}
variable "metric" {
description = "Retention only applies to storage account. Retention policy ranges from 1 to 365 days. If you do not want to apply any retention policy and retain data forever, set retention (days) to 0."
type = string
}
variable "blocks" {
description = "Retention only applies to storage account. Retention policy ranges from 1 to 365 days. If you do not want to apply any retention policy and retain data forever, set retention (days) to 0."
type = string
}

Terraform 0.13 introduced support for for_each and count to modules.
See https://www.terraform.io/docs/configuration/modules.html#multiple-instances-of-a-module for more details and examples.
For example:
module "diagnostic_mssql_db" {
count = var.blocks > 0 ? 1 : 0
. . .
}
This will give you 0 or 1 module.diagnostic_mssql_db resource depending on the value of var.blocks.

This issue looks a bit confused, but if you think from a different perspective, then it's easy to solve.
It seems you want to create a MySQL database and other resources in the module. But the other resources depend on the condition. Think, all the resources in the Terraform have a count property, and if the count equal to 0. It means you only need to set the count variable for all the condition resources. Inside the module, use the variable target_resource_id directly.

Related

How can i set a count in for_each in terraform

I'm learning terraform by building a template to create my infrastructure in the hetzner cloud. For this purpose I'm using the hcloud provider.
I create a map variable hosts to create >1 server with different configuration.
variable "hosts" {
type = map(object({
name = string
serverType = string
serverImage = string
serverLocation = string
serverKeepDisk = bool
serverBackup = bool
ip = string
}))
}
This is working fine. But I need to configure also volumes. I need only for 2 servers additional volumes and terraform has to check if variable volume is true or not. If true a new volume with given details should be created and attached to the server.
For this I edit my variable hosts:
variable "hosts" {
type = map(object({
name = string
serverType = string
serverImage = string
serverLocation = string
serverKeepDisk = bool
serverBackup = bool
ip = string
volume = bool
volumeName = string
volumeSize = number
volumeFormat = string
volumeAutomount = bool
volumeDeleteProtection = bool
}))
}
in the main.tf the volume block looks like this, but it doesnt work because for_each and count cant be used together. How can I get what I'm looking for? Is that possible?
resource "hcloud_volume" "default" {
for_each = var.hosts
count = each.value.volume ? 1 : 0
name = each.value.volumeName
size = each.value.volumeSize
server_id = hcloud_server.default[each.key].id
automount = each.value.volumeAutomount
format = each.value.volumeFormat
delete_protection = each.value.volumeDeleteProtection
}
The former iterative meta-argument count will not provide you with the functionality you need here, as you need to access the volume bool type on a per var.hosts iteration in the map. To that end, you can add a conditional in a for expression within the for_each meta-argument.
for_each = { for host, values in var.hosts: host => values if values.volume }
This will construct a map for the value of the for_each meta-argument. It will contain every key value pair of var.hosts for which the volume object key is true.
It would seem like this would be a good fit for a collect or map method or function which transforms list and map types and exist in many other languages, but these do not yet exist in Terraform. Therefore, we use a for expression lambda equivalent.

Terraform dynamically generate strings using data sources output

Is there any way I can feed the Terraform data source output to another Terraform file as input
The scenario is, I have a terraform code to fetch the private IP addresses (here 3 IPs 10.1.1.1,10.1.1.2,10.1.1.3) for particular tags(here jenkins) using data source
data "aws_instances" "mytag" {
filter {
name = "tag:Application"
values = ["jenkins"]
}
}
output "output from aws" {
value = data.aws_instances.mytag_private_ips
}
Whenever, I do the terraform apply, the on the pattern section in the
below metric-filter code should be able to fetch the resultant IPs from the above code and make them available in the live state ( aws console )
resource "aws_cloudwatch_log_metric_filter" "test" {
name = "test-metric-filter"
pattern = "[w1,w2,w3,w4!=\"*<IP1>*\"&&w4!=\"*<IP2>*\"&&w4!=\"*<IP3>*\",w5=\"*admin*\"]"
log_group_name = var.test_log_group_name
metric_transformation {
name ="test-metric-filter"
namespace = "General"
}
}
So, the final result of metric pattern in the aws console should be like below
[w1,w2,w3,w4!="*10.1.1.1*"&&w4!="*10.1.1.2*"&&w4!="*10.1.1.3*",w5="*admin*"]
The end goal is whenever if the new IPs are generated, it will get populated to pattern (in aws-console) without changing the metric-filter code.
Any help is appreciated, as I could not find any precise document on terraform allows us to dynamically generate strings using data sources
Not sure why you need two files for something this simple...
Here is what I would do:
provider "aws" {
region = "us-east-1"
}
data "aws_instances" "test" {
filter {
name = "architecture"
values = ["x86_64"]
}
}
resource "aws_cloudwatch_log_metric_filter" "test" {
name = "test-metric-filter"
pattern = "[w1,w2,w3,w4!=\"*${data.aws_instances.test.private_ips[0]}*\",w5=\"*admin*\"]"
log_group_name = "test_log_group_name"
metric_transformation {
name = "test-metric-filter"
namespace = "General"
value = 1
}
}
And a terraform plan will show
Terraform will perform the following actions:
# aws_cloudwatch_log_metric_filter.test will be created
+ resource "aws_cloudwatch_log_metric_filter" "test" {
+ id = (known after apply)
+ log_group_name = "test_log_group_name"
+ name = "test-metric-filter"
+ pattern = "[w1,w2,w3,w4!=\"*172.31.70.170*\",w5=\"*admin*\"]"
+ metric_transformation {
+ name = "test-metric-filter"
+ namespace = "General"
+ unit = "None"
+ value = "1"
}
}
Plan: 1 to add, 0 to change, 0 to destroy.
Concatenating strings is easy: "foo ${var.bar} 123"
and on this case our private_ips is an array so we need the [x]
For more complex concatenations look into the format function:
https://www.terraform.io/docs/language/functions/format.html
I did changed the filter to be able to test on my environment and also used a shorter pattern than yours, but that is the basis for what you need, just add more of make changes to suit your needs.
What you are looking for is string interpolation in Terraform.
I believe you would want to do the following:
pattern = "[w1,w2,w3,w4!=\"*${data.aws_instances.mytag.private_ips[0]}*\"&&w4!=\"*${data.aws_instances.mytag.private_ips[1]}*\"&&w4!=\"*${data.aws_instances.mytag.private_ips[2]}*\",w5=\"*admin*\"]"
I suggest being careful with this statement, because it will fail if you don't have at least 3 instances. You would want to have something dynamic instead.

terraform count index and length

Trying to create multiple private DNS zone
non prod --> Dev,QA,UAT.
prod --> Prd,DR
Resource should be created only if is_nonprod is set to 1. emphasized text(boolean value).
Idea is to use count twice in resource block : once for boolean and once for length function.
resource "azurerm_private_dns_zone" "example" {
count = var.is_nonprod ? 1 : 0 && length(var.env)
name = var.env[count.index].npr
resource_group_name = "examplerg"
}
variable file:
variable "env" {
description = "List of routes to be added to the route table"
default = []
type = list(map(any))
}
variable "is_nonprod " {
default = true
}
tfvars
env = [
{ npr = "qa" },
{ npr = "uat" },
{ npr = "dev" }
]
Error :
The true and false result expressions must have consistent types. The given
expressions are number and bool, respectively.
workaround :
resource "azurerm_private_dns_zone" "example" {
count = var.is_nonprod ? 1 : 0
count = length(var.env)
name = var.env[count.index].npr
resource_group_name = "examplerg"
}
Error :
The argument "count" was already set at main.tf:96,3-8. Each argument may be
set only once.
var.is_nonprod ? 1 : 0 && length(var.env) does not look like the logic you want here based on what you described in the question. It seems like you really want var.is_nonprod ? length(var.env) : 0, which is also syntactically valid. The && operator inputs and returns booleans, which are not valid as an input type to the count metaparameter. count takes a number as input (typically the number of resources you want to manage), and not true or false.
Seems like datatype conflicts. You can directly use count = var.is_nonprod ? length(var.env) : 0 and it should give you expected results.

Terraform: Conditional creation of a resource based on a variable in .tfvars

I have resources defined in .tf files that are generic to several applications. I populate many of the fields via a .tfvars file. I need to omit some of the resources entirely based on variables in the .tfvars.
For example if I have a resource like:
resource "cloudflare_record" "record" {
zone_id = "${data.cloudflare_zones.domain.zones[0].id}"
name = "${var.subdomain}"
value = "${var.origin_server}"
type = "CNAME"
ttl = 1
proxied = true
}
But then I declare something like cloudflare = false in my .tfvars file I'd like to be able to do something like this:
if var.cloudflare {
resource "cloudflare_record" "record" {
zone_id = "${data.cloudflare_zones.domain.zones[0].id}"
name = "${var.subdomain}"
value = "${var.origin_server}"
type = "CNAME"
ttl = 1
proxied = true
}
}
I've looked at dynamic blocks but that looks like you can only use those to edit fields and blocks within a resource. I need to be able to ignore an entire resource.
Add a count parameter with a ternary conditional using the variable declared in .tfvars like this:
resource "cloudflare_record" "record" {
count = var.cloudflare ? 1 : 0
zone_id = "${data.cloudflare_zones.domain.zones[0].id}"
name = "${var.subdomain}"
value = "${var.origin_server}"
type = "CNAME"
ttl = 1
proxied = true
}
In this example var.cloudflare is a boolean declared in the .tfvars file. If it is true a count of 1 record will be created. If it is false a count of 0 record will be created.
After the count apply the resource becomes a group, so later in the reference use 0-index of the group:
cloudflare_record.record[0].some_field
Expanding on #Joel Guerra's answer, after you use count to determine whether to deploy the resource or not, you can use the one() function to refer to the resource without an index (i.e. without having to use [0]).
For example, after defining the resource like below
resource "cloudflare_record" "record" {
count = var.cloudflare ? 1 : 0
}
Define a local variable like below
locals {
cloudflare_record_somefield = one(cloudflare_record.record[*].some_field)
}
Now instead of cloudflare_record.record[0].some_field, you can use
local.cloudflare_record_somefield
If the count is 0 (e.g. var.cloudflare is false and the resource wasn't created) then local.cloudflare_record_somefield would return null (instead of returning an error when indexing using [0]).
Reference: https://developer.hashicorp.com/terraform/language/functions/one
An issue i'm seeing this with is if the resource your trying to create is already using a for_each then you can't use both count and for_each in the resource. I'm still trying to find an answer on this will update if I find something better.

cloudflare terraform provider firewall creation with loop

I am trying to work around a constraint where firewall creation is split into 2 sections, creating a filter and creating the rule based on the filter. Filter creation exposes a filter id that should be used in the fw rule creation. I cant wrap my head on how to properly iterate through the map that has values for filter and rule and include newly created filter. if i just use a simple map with name and expression, things work, but if i add rule priority things break
here is my map
variable "fw_allowfilters1" {
description = "list of expressions for firewall to be included in the allow rules"
type = map(object({
fvalue = string
priority = number
}))
default = {
"office_filter1" = [
{
fvalue = "ip.geoip.asnum eq 111111"
priority = 1
}
]
"office_filter2" = [
{
fvalue = "ip.src eq 8.8.8.8"
priority = 3
}
]
}
}
now here is my code for both filter and FW
resource "cloudflare_filter" "allow-filters1" {
for_each = var.fw_allowfilters1
zone_id = var.zoneid
expression = each.value.fvalue
description = each.key
//description = [for o in var.fw_allowfilters1: "Filter_${var.fw_allowfilters1.name}"]
//expression = [for o in var.fw_allowfilters1: var.fw_allowfilters1.value]
}
resource "cloudflare_firewall_rule" "whitelist-rule" {
for_each = cloudflare_filter.allow-filters1
action = "allow"
filter_id = tostring(each.value.id)
zone_id = var.zoneid
description = [for p in var.fw_allowfilters1.name: p.name ]
priority = [for p in var.fw_allowfilters1.priority: p.priority ]
}
now if i dont include priority, i can do the for_each on the filter output in firewall creation, using id output from the resource and key for descirption ( cf tf provider uses description as a name) however, if i need to add the key, i need to iterate through the map with values plus the id that is output of the filter creation and I am not sure how to properly map it. code currently does not work.
so i figured it out and it was not easy:) using locals helped me create proper iterators:
resource "cloudflare_filter" "filters1" {
for_each = var.fw_rules
zone_id = var.zoneid
description = "Filter_${tostring(each.key)}"
expression = tostring(each.value[0])
}
locals {
filterids = [for f in cloudflare_filter.filters1 : f.id] //putting filter
IDs into a separate list for concat later
fwvalues = (values(var.fw_rules)) // putting values from the map of fwvalues into
a separate list to use the index position of a particular value as an interator when
creating commong object that has both filterid and fwvalues
fwkeys = (keys(var.fw_rules)) //putting keys into a separate list
//iterating over all elements in the allowfilters1, combining existing lists in the
variable with the ID value and readding the key as an element in the list
withid3 = {for p in var.fw_rules : local.fwkeys[index(local.fwvalues, p.*)] =>
concat(p, list(local.filterids[index(local.fwvalues,
p.*)]),list(local.fwkeys[index(local.fwvalues, p.*)]))} //working version
}
resource "cloudflare_firewall_rule" "fw-rules" {
for_each = local.withid3
action = each.value[2]
filter_id = each.value[4]
paused = each.value[3]
zone_id = var.zoneid
description = "FW-${tostring(each.value[2])}-${tostring(each.key)}"
priority = each.value[1]
}
where varilable is this:
// the syntax is the following: name of the rule(try to be precise = [ expression, priority,action, disabled - boolean] - all values should be strings, make sure to terminate the quotes correctly
allowed values for the action are: block, challenge, js_challenge, allow, log, bypass
list has to be maintained according to the rule priority
variable "fw_rules" {
description = "list of expressions for firewall to be included in therules"
type = map
default = {
office_allow = ["putexpressionhere","1","allow","false"],
office_allow1 = ["putexpressionhere1","2","deny","false"]
}

Resources