Terraform dynamic blocks with nested list - terraform

I need to create an escalation policy in Pagerduty using Terraform. I want to dynamically create rule blocks and then within them target blocks with values from rule. I am not sure how to make the second call inside target block to make it dynamic.
I have a list of teams within a list.
locals {
teams = [
[data.pagerduty_schedule.ce_ooh_schedule.id, data.pagerduty_schedule.pi_office_hours_schedule.id],
[data.pagerduty_schedule.delivery_managers_schedule.id]
]
}
resource "pagerduty_escalation_policy" "policy" {
name = var.policy_name
num_loops = var.num_loops
teams = [var.policy_teams]
dynamic "rule" {
for_each = local.teams
escalation_delay_in_minutes = var.escalation_delay
dynamic "target" {
for_each = ??????
content {
type = var.target_type
id = ??????
}
}
}
}
???? are the points I'm not sure about.
I need to create a rule for each item in a list(so [team1, team2] and [escalation_team]) and then for each item within those lists I need to create a target for each of the teams(so rule 1 will have two targets - team1 and team2 and rule 2 will have one target which is escalation_team).
Any idea how I could approach this?
I'm using TF v0.12.20
Here's my config after updating:
resource "pagerduty_escalation_policy" "policy" {
name = var.policy_name
num_loops = var.num_loops
teams = [var.policy_teams]
dynamic "rule" {
for_each = local.teams
escalation_delay_in_minutes = var.escalation_delay
dynamic "target" {
for_each = rule.value
content {
type = var.target_type
id = target.value
}
}
}
}
Edit: Changed locals.teams to local.teams

If I'm reading your question correctly, I believe you want something like the following
resource "pagerduty_escalation_policy" "policy" {
name = var.policy_name
num_loops = var.num_loops
teams = [var.policy_teams]
dynamic "rule" {
for_each = locals.teams
content {
escalation_delay_in_minutes = var.escalation_delay
dynamic "target" {
for_each = rule.value
content {
type = var.target_type
id = target.value
}
}
}
}
}
Note the following
Each dynamic block must have a matching content block
dynamic blocks introduce new names that have .key and .value which can be used to access properties of what's being looped over.
I can't actually run this so if it's still wrong let me know and I'll update.

Related

adding multiple destinations to new relic workflows using terraform

I am trying to create a newrelic workflow using terraform modules. I am fine with creating a workflow with signle destination. But, I am trying to create a workflow with more than one destination.
slack channel ids
variable "channel_ids" {
type = set(string)
default = ["XXXXXXXXXX","YYYYYYYYY"]
}
creating notification channels using slack channel ids
resource "newrelic_notification_channel" "notification_channel" {
for_each = var.channel_ids
name = "test" # will modify if required
type = "SLACK" # will parameterize this
destination_id = "aaaaaaaaa-bbbbb-cccc-ddddd-eeeeeeeeee"
product = "IINT"
property {
key = "channelId"
value = each.value
}
}
Now I want to create something like below (two destinations)
resource "newrelic_workflow" "newrelic_workflow" {
name = "my-workflow"
muting_rules_handling = "NOTIFY_ALL_ISSUES"
issues_filter {
name = "Filter-name"
type = "FILTER"
predicate {
attribute = "accumulations.policyName"
operator = "EXACTLY_MATCHES"
values = [ "policy_name" ]
}
}
destination {
channel_id = newrelic_notification_channel.notification_channel.id
}
destination {
channel_id = newrelic_notification_channel.notification_channel.id
}
}
I tried using for_each and for loop but no luck. Any idea on how to get my desired output?
Is it possible to loop through and create multiple destinations within the same resource, like attaching multiple destination to a single workflow?
I was able to achieve this by using a dynamic block, which produces a dynamic number of destination blocks based on the number of elements of newrelic_notification_channel.notification_channel.
resource "newrelic_workflow" "newrelic_workflow" {
name = "my-workflow"
muting_rules_handling = "NOTIFY_ALL_ISSUES"
issues_filter {
name = "Filter-name"
type = "FILTER"
predicate {
attribute = "accumulations.policyName"
operator = "EXACTLY_MATCHES"
values = [ "policy_name" ]
}
}
dynamic "destination" {
for_each = newrelic_notification_channel.notification_channel
content {
channel_id = destination.value.id
}
}
}

snowflake terraform create multiple table in one tf files

I am trying to create multiple table through tf in snowflake.
Below are the sample code.
resource "snowflake_table" "table" {
database = "AMAYA"
schema = "public"
name = "info"
comment = "A table."
column {
name = "id"
type = "int"
nullable = true
default {
sequence = snowflake_sequence.sequence.fully_qualified_name
}
}
column {
name = "identity"
type = "NUMBER(38,0)"
nullable = true
identity {
start_num = 1
step_num = 3
}
}
resource "snowflake_table" "table" {
database = "AMAYA"
schema = "public"
name = "arch_info"
comment = "A table."
column {
name = "id"
type = "int"
nullable = true
default {
sequence = snowflake_sequence.sequence.fully_qualified_name
}
}
column {
name = "identity"
type = "NUMBER(38,0)"
nullable = true
identity {
start_num = 1
step_num = 3
}
}
}
When I run this script I get the error.
A snowflake_procedure resource named "table" was already declared at str.tf:16,1-38. Resource names must be unique per type in each module.
The only solution I have tried and worked is to create different files for different table. however I have 100 of tables to create, and was wondering if there is simpler way of putting all in one file and run the script
You can't use the same name for a resource more than once, like tablebelow:
resource "snowflake_table" "table" {
Use different names:
resource "snowflake_table" "table_1" {
You should look into for_each and dynamic functions when needing to create lots of the same resource with different parameters:
Terraform for_each
Terraform dynamic
With those, you can create complex maps that are defined on input and automatically create the required amount of resources, something like below (just an example with a couple of parameters):
locals {
snowflake_tables = {
info = {
database = "AMAYA"
...
columns = {
identity = {
type = "NUMBER(38,0)"
nullable = true
...
}
}
}
}
}
resource "snowflake_table" "table" {
for_each = local.snowflake_tables
name = each.key # info
database = each.value.database # AMAYA
...
dynamic "column" {
for_each = each.value.columns
content {
name = setting.key
type = setting.value["type"]
nullable = setting.value["nullable"]
...
}
}
}
With this technique, all you do is add more objects to the map for tables and columns. I've set the example in locals but you could have this as a variable input instead in a .tfvars file etc.

Terraform How to use for each values from one resource in another resource

I am trying to create users in terraform cloud based off a simple csv, structured like so:
email,access_level
test#gmail.com,readonly
test2#gmail.come,owners
I can create users easily enough using the following resource block:
locals {
users_csv = csvdecode(file("${path.module}/users.csv"))
}
resource "tfe_organization_membership" "users_csv" {
for_each = { for r in local.users_csv : r.email => r }
organization = var.ppl_organisation
email = each.value.email
}
However, if I want to add them to teams then I need the "id" output from the above resource into the below resource:
resource "tfe_team_organization_member" "readonly_member" {
for_each = { for r in local.read_only : r.email => r }
team_id = each.value.access_level == "readonly" ? each.value.access_level : local.readonly_id
organization_membership_id = "${tfe_organization_membership.users_csv.id}"
}
Is there a way to pass this?
Thanks in advance.
I think the first thing I'd do here is to factor out the projection of your CSV data from a list to a map, like this:
locals {
users_csv = csvdecode(file("${path.module}/users.csv"))
users = { for r in local.users_csv : r.email => r }
}
By defining local.users like this we can make it more concise to refer to that value multiple times elsewhere in the config:
resource "tfe_organization_membership" "users_csv" {
for_each = local.users
organization = var.ppl_organisation
email = each.value.email
}
resource "tfe_team_organization_member" "readonly_member" {
for_each = local.users
team_id = (
each.value.access_level == "readonly" ?
each.value.access_level :
local.readonly_id
)
organization_membership_id = tfe_organization_membership.users_csv[each.key].id
}
Any resource that has for_each set is represented in expressions as a map of objects whose keys are the same as the for_each input map, so the tfe_organization_membership.users_csv[each.key] expression refers to an object representation of the corresponding instance of the other resource, correlating by the map keys.

How to have conditional resources inside a module with 0.12 for_each

I'm passing my modules a list and it's going to create EC2 instances and eips and attach.
I'm using for_each so users can reorder the list and Terraform won't try to destroy anything.
But how do I use conditional resources now? Do I still use count? If so how, because you can't use count with for_each?
This is my module now:
variable "mylist" {
type = set(string)
description = "Name used for tagging, AD, and chef"
}
variable "createip" {
type = bool
default = true
}
resource "aws_instance" "sdfsdfsdfsdf" {
for_each = var.mylist
user_data = data.template_file.user_data[each.key].rendered
tags = each.value
...
#conditional for EIP
resource "aws_eip" "public-ip" {
for_each = var.mylist
// I can't use this anymore!
// how can I say if true create else don't create
#count = var.createip ? 0 : length(tolist(var.mylist))
instance = aws_instance.aws-vm[each.key].id
vpc = true
tags = each.value
}
I also need to get the value of the mylist item for eip too because I use that to tag the eip. So I think I need to index into the foreach loop somehow and also be able to use count or another list to determine if it's created or not - is that correct?
I think I got it but I don't want to accept until it's confirmed this is not the wrong way (not as a matter of opinion but improper usage that will cause actual problems).
variable "mylist" {
type = set(string)
description = "Name used for tagging, AD, and chef"
}
variable "createip" {
type = bool
default = true
}
locals {
// set will-create-public-ip to empty array if false
// otherwise use same mylist which module uses for creating instances
will-create-public-ip = var.createip ? var.mylist : []
}
resource "aws_instance" "sdfsdfsdfsdf" {
for_each = var.mylist
user_data = data.template_file.user_data[each.key].rendered
tags = each.value
...
resource "aws_eip" "public-ip" {
// will-create-public-ip set to mylist or empty to skip this resource creatation
for_each = will-create-public-ip
instance = aws_instance.aws-vm[each.key].id
vpc = true
tags = each.value
}

Create nested resource parameter blocks based on conditional in terraform

I am trying to create a terraform module that creates a compute instance. I want the resource to have an attached disk if and only if I have a variable attached_disk_enabled set to true during module invocation. I have this:
resource "google_compute_disk" "my-disk" {
name = "data"
type = "pd-ssd"
size = 20
count = var.attached_disks_enabled ? 1 : 0
}
resource "google_compute_instance" "computer" {
name = "computer"
boot_disk {
...
}
// How do I make this disappear if attached_disk_enabled == false?
attached_disk {
source = "${google_compute_disk.my-disk.self_link}"
device_name = "computer-disk"
mode = "READ_WRITE"
}
}
Variables have been declared for the module in vars.tf. Module invocation is like this:
module "main" {
source = "../modules/computer"
attached_disk_enabled = false
...
}
I know about dynamic blocks and how to use for loop to iterate over a list and set multiple blocks, but I'm not sure how to exclude a block from a resource using this method:
dynamic "attached-disk" {
for_each in var.disk_list
content {
source = "${google_compute_disk.my-disk.*.self_link}"
device_name = "computer-disk-${count.index}"
mode = "READ_WRITE"
}
}
I want if in place of for_each. Is there a way to do this?
$ terraform version
Terraform v0.12.0
Because your disk resource already has the conditional attached to it, you can use the result of that resource as your iterator and thus avoid specifying the conditional again:
dynamic "attached_disk" {
for_each = google_compute_disk.my-disk
content {
source = attached_disk.value.self_link
device_name = "computer-disk-${attached_disk.key}"
mode = "READ_WRITE"
}
}
To answer the general question: if you do need a conditional block, the answer is to write a conditional expression that returns either a single-item list or an empty list:
dynamic "attached_disk" {
for_each = var.attached_disk_enabled ? [google_compute_disk.my-disk[0].self_link] : []
content {
source = attached_disk.value
device_name = "computer-disk-${attached_disk.key}"
mode = "READ_WRITE"
}
}
However, in your specific situation I'd prefer the former because it describes the intent ("attach each of the disks") more directly.

Resources