How to generate a parameter array - dml-lang

I am now developing a management unit that controls several IPs through corresponding control-fsm. These IPs are grouped in several group, and each group are controlled by one control-fsm. Thus I have a control_fsm_template and a ip_template, and in each control_fsm there is a parameter ip_list to record all IPs controlled by this FSM:
template control_fsm_template {
param ip_list : sequence (ip_template);
param ip_list default undefined;
method gm1() {
foreach ip in ip_list {
if (ip.p1) ip.m1();
}
}
}
template ip_template {
param p1;
param p2;
connect c1;
port c2;
method m1() {}
method m2() {}
}
group ip_1 is (ip_template);
group ip_2 is (ip_template);
group control_fsm1 is (control_fsm_template) {
param ip_list = [ip_1, ip_2];
}
group ip_3 is (ip_template);
group ip_4 is (ip_template);
group control_fsm2 is (control_fsm_template) {
param ip_list = [ip_3, ip_4];
}
DMLC reports error:
error: not a value: [ip_1, ip_2]
And then I changed the code to:
template ip_group1_template is (ip_template);
template ip_group2_template is (ip_template);
group ip_1 is (ip_group1_tempalte);
group ip_2 is (ip_group1_template);
group control_fsm1 is (control_fsm_template) {
param ip_list = each ip_group1_template in (dev);
}
group ip_3 is (ip_group2_tempalte);
group ip_4 is (ip_group2_template);
group control_fsm2 is (control_fsm_template) {
param ip_list = each ip_group2_template in (dev);
}
Then DMLC reports error:
error: wrong type in assignment
got: list of trait ip_group1_template
expect: list of trait ip_template

The problem in your first attempt is that [] lists are not true values; they are purely compile-time entities that only support three operations: #foreach, #select, and direct indexing with a constant; therefore you cannot assign them to a typed parameter. The only way to construct values of type sequence(X) is currently each .. in .. expressions, as you correctly realized in your second attempt. The problem there is instead that DML doesn't offer a way to convert a sequence(X) into a sequence(Y), even if template Y is X. This would be a valid feature request.
The simplest solution is to use nested groups, so you can say collect all instances within a subhierarchy instead of dev:
template ip_group1_template is (ip_template);
template ip_group2_template is (ip_template);
group fsm1 {
group ip_1 is (ip_group1_tempalte);
group ip_2 is (ip_group1_template);
}
group control_fsm1 is (control_fsm_template) {
param ip_list = each ip_template in (fsm1);
}
group fsm2 {
group ip_3 is (ip_group2_tempalte);
group ip_4 is (ip_group2_template);
}
group control_fsm2 is (control_fsm_template) {
param ip_list = each ip_template in (fsm2);
}

Related

Using for_each on property of a counted resource

I have a resource with a varying count (0 or 1 resources).
resource "aws_acm_certificate" "cert" {
count = local.acm_version_count
domain_name = "${local.deployment_version_name}.example.com"
subject_alternative_names = [
"*.${local.deployment_version_name}.example.com
]
validation_method = "DNS"
}
And now I want to iterate over a property of the optional resource:
resource "aws_route53_record" "acm_validation" {
for_each = {
for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
zone_id = data.aws_route53_zone.r53_zone.zone_id
name = each.value.name
type = each.value.type
ttl = 60
records = [each.value.record]
allow_overwrite = true
}
Of course, because aws_acm_certificate.cert is counted, we must access that at an index (i.e., aws_acm_certificate.cert[0] or aws_acm_certificate.cert[count.index]). However, I can't use a count because it is mutually exclusive from for_each, and I cannot access [0] when the optional resource is not created. Ideally I could add a count on the record itself, so that I ignore the aws_route53_record.acm_validation if there is no aws_acm_certificate
I know about toproduct, so I wonder if that would be useful - but I honestly didn't know enough about it to utilize it and couldn't make it work here
How can I iterate over this optional resource so that I can ignore it if the acm is optionally excluded, but still iterate over those domain_validation_options if there is an acm?
You can produce a flattened list of all of the "domain validation options" across all of the aws_acm_certificate.cert instances like this:
flatten(aws_acm_certificate.cert[*].domain_validation_options)
When you have zero certificates declared, this would be like calling flatten([]) and so it'll just produce an empty list, whereas when count is nonzero it'll capture all of the declared validation options, because aws_acm_certificate.cert[*].domain_validation_options alone would produce a list of lists of objects.
I think it should work to insert that flatten( ... ) call in the same place where you are currently referring directly to an attribute of a singleton resource, and leave everything else unchanged:
for_each = {
for dvo in flatten(aws_acm_certificate.cert[*].domain_validation_options) :
dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
In most cases I'd suggest thinking of a conditional count as "there can be zero or more instances", even if you know that the maximum count is 1 really, because the language features designed for working with lists of objects are often more compact and concise than what you'd need to write to specifically distinguish between zero or one of some resource. It doesn't always work out like that, but in many cases it's not really important whether there's one or more than one instances, including this situation.
You need to check if the resource is created, and pass an empty list to for_each if it isn't. I would add a local variable to make this a bit cleaner.
locals {
dvos = local.acm_version_count == 0 ? [] : aws_acm_certificate.cert[0].domain_validation_options
}
resource "aws_route53_record" "acm_validation" {
for_each = {
for dvo in local.dvos : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
...
}

Add resource argument in loop from list of strings

I'm trying to set up a Digital Ocean Database Firewall, which uses the below syntax:
resource "digitalocean_database_firewall" "example-fw" {
cluster_id = digitalocean_database_cluster.app.id
rule {
type = "ip_addr"
value = "192.168.1.1"
}
rule {
type = "ip_addr"
value = "192.0.2.0"
}
}
I have a variable which is a list of whitelisted IPs that should be added to the firewall, along with the VPC IP block. I first tried to add these using for_each:
# Postgres firewall (only allow connection inside VPC)
resource "digitalocean_database_firewall" "vpc-fw" {
cluster_id = digitalocean_database_cluster.app.id
rule {
type = "ip_addr"
value = digitalocean_vpc.app_vpc.ip_range
}
}
# Postgres firewall (allow connections from whitelisted IPs)
resource "digitalocean_database_firewall" "whitelisted-fw" {
for_each = toset(var.db_allowed_ips)
cluster_id = digitalocean_database_cluster.app.id
rule {
type = "ip_addr"
value = each.key
}
}
However it seems you can only have one firewall resource per cluster as only the last IP is saved and shows on the dashboard.
I also tried using for_each in the rule block but this throws an error that it can only appear in module or resource blocks.
I've also tried passing the list to value directly, but it only supports strings and not lists.
How can I add a rule { } block for each IP in var.db_allowed_ips and digitalocean_vpc.app_vpc.ip_range?
You can achieve this with a dynamic block:
resource "digitalocean_database_firewall" "whitelisted-fw" {
cluster_id = digitalocean_database_cluster.app.id
dynamic "rule" {
for_each = toset(var.db_allowed_ips)
content {
type = "ip_addr"
value = each.value
}
}
}
I believe the for_each meta-argument at the dynamic block scope accepts values of list(string), but nothing is lost by being safe and using the normal type conversion to set(string).

Can you pass blocks as variables in Terraform, referencing the type of a resource's nested block contents?

I am trying to build in Terraform a Web ACL resource
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl
This resource has the nested blocks rule->action->block and rule-> action->count
I would like to have a variable which's type allows me to set the action to either count {} or block{} so that the two following configurations are possible:
With block:
resource "aws_wafv2_web_acl" "example" {
...
rule {
...
action {
block {}
}
...
}
With count:
resource "aws_wafv2_web_acl" "example" {
...
rule {
...
action {
count {}
}
...
}
I can achieve this result with a boolean variable and dynamic blocks in a very non-declarative way so far.
My question is, can the type of a variable reference the type of a nested block, so that the content of the nested block can be passed in a variable?
What I am trying to achieve is something that would look similar to this (non working syntax):
resource "aws_wafv2_web_acl" "example" {
...
rule {
...
action = var.action_block
...
}
}
variable "action_block" {
description = "Action of the rule"
type = <whatever type is accepted by aws_wafv2_web_acl->rule->action>
}
so that it can be passed down in a similar manner to this
module "my_waf" {
source = "../modules/waf"
action_block {
block {}
}
}
For reference, what I am trying to avoid:
dynamic "action" {
for_each = var.block ? [] : [1]
content {
count {}
}
}
dynamic "action" {
for_each = var.block ? [1] : []
content {
block {}
}
}
Thank you so much for your help!
The only marginal improvement I can imagine is to move the dynamic blocks one level deeper, to perhaps make it clear to a reader that the action block will always be present and it's the count or block blocks inside that have dynamic behavior:
action {
dynamic "count" {
for_each = var.block ? [] : [1]
content {}
}
dynamic "block" {
for_each = var.block ? [1] : []
content {}
}
}
There are some other ways you could formulate those two for_each expressions so that the input could have a different shape, but you'll need to write out a suitable type constraint for that variable yourself which matches whatever conditions you want to apply to it.

Terraform dynamic blocks with nested list

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.

terraform: create list based on resource count

We have a bunch of instances (I know... cattle, not pets, but in this case, these are really pets)
resource "aws_instance" "read_00" {
count = "${var.read_00_count}"
resource "aws_instance" "read_01" {
count = "${var.read_01_count}"
And we have an ELB where we want to dynamically add the instances based on their count variable, like so:
resource "aws_elb" "read_slaves" {
instances = ["${aws_instance.read_.*.id}"]
But that doesn't work, of course.
Is it possible to dynamically create a list of instance ids ONLY if their count is not zero?
I know this goes against the grain, but if this is possible, that would be awesome.
With Terraform 0.12 that will be much easier, but for now something like this would do:
[...]
resource "aws_instance" "read_01" {
[...]
count = "${var.read_01_count}"
tags {
Role = "read_slave"
}
}
data "aws_instances" "read-slaves" {
instance_tags {
Role = "read_slave"
}
// optional filters
}
resource "aws_elb" "read_slaves" {
instances = ["${data.aws_instances.read-slaves.ids}"]
listener {
...
}
}
Thus:
tagging each instance which acts as a read slave
collect the list of aws_intances
create the aws_elb based on the collected data

Resources