Dynamic Block - Blocks of type "xxx" are not expected here - terraform

I try to use a Dynamic block with the following ressource: https://registry.terraform.io/providers/netascode/iosxe/latest/docs/resources/interface_nve
"vnis" have to be dynamic and the expected result must be like this
resource "iosxe_interface_nve" "nve1" {
device = var.leaf_name
name = 1
description = "VTEP"
shutdown = false
host_reachability_protocol_bgp = true
source_interface_loopback = 1
vnis = [
{
vni_range = "10123"
ingress_replication = true
},
{
vni_range = "11234"
ingress_replication = true
}
]
}
The syntax i imagined is the following one:
resource "iosxe_interface_nve" "nve1" {
device = var.leaf_name
name = 1
description = "VTEP"
shutdown = false
host_reachability_protocol_bgp = true
source_interface_loopback = 1
dynamic "vnis" {
for_each = var.l2vni
content {
vni_range = each.value["vni"]
ingress_replication = true
}
}
}
I'm stuck with the following error:
│ Error: Unsupported block type
│
│ on modules\terraform-iosxe-nve\main.tf line 32, in resource "iosxe_interface_nve" "nve1":
│ 32: dynamic "vnis" {
│
│ Blocks of type "vnis" are not expected here.
I wonder if i missed something regarding the dynamic block syntax as soon as it's the first time i need it. Is it possible that dynamic block are not supported in any resource?
Thanks a lot for you help! (my first post i hope it's not so bad explained..)

vnis isn't a block, it's a list of objects. Note how vnis = [ has a square bracket [ which indicates a list, not a brace {.
You simply need to build a list dynamically, and assign it to the vnis attribute of your resource. You could use a for expression for that.

Thanks to Mark here is the code modified and working as expected.
locals {
l2vni_list = [
for key, l2vni in var.l2vni : {
vni_range = l2vni.vni
ingress_replication = true
}
]
}
resource "iosxe_interface_nve" "nve1" {
device = var.leaf_name
name = 1
description = "VTEP"
shutdown = false
host_reachability_protocol_bgp = true
source_interface_loopback = 1
vnis = local.l2vni_list
}

Related

Using Terraform 1.1.x, nested block inputs are not being iterated, but just once instead

I am working on automating some PagerDuty resources and first started on service dependencies. What I thought was a fairly straight forward solve has proven not to be true. When we create service dependencies, we want to be able to attach many dependencies either that the service USES or that the service is USED BY. With this in mind, I created the following resource block in Terraform.
resource "pagerduty_service_dependency" "this" {
count = var.create_dependencies ? 1 : 0
dynamic "dependency" {
for_each = var.dependency
content {
type = dependency.value.type
dynamic "dependent_service" {
for_each = dependency.value.dependent_service
content {
id = dependent_service.value.id
type = dependent_service.value.type
}
}
dynamic "supporting_service" {
for_each = dependency.value.supporting_service
content {
id = supporting_service.value.id
type = supporting_service.value.type
}
}
}
}
}
With this block as the variable
variable "dependency" {
description = "value"
type = any
default = []
}
And using this block for inputs (Using Terragrunt 0.36.x)
// Service Dependencies
create_dependencies = true
dependency = [{
type = "service"
dependent_service = [
{
id = "DD4V04U" // The service we are applying the dependency to.
type = "service"
}
]
supporting_service = [
{
id = "BBF2LHB" // The service that is being used by "DD4V04U"
type = "service"
},
{
id = "AAILTY1" // Another service being used by "DD4V04U"
type = "service"
}
]
}]
The Terraform will apply the change but will only create the last dependency in the list(object). Subsequent additions to this list of objects result in all other dependencies before it being destroyed except for the last object in the list. This applies for both dependent and supporting service lists of objects.
The original path I went down in terms of providing inputs looked like this:
// Service Dependencies
create_dependencies = true
dependency = [
{
type = "service"
dependent_service = [{
id = "DD4V04U"
type = "service"
}],
supporting_service = [{
id = "BBF2LHB"
type = "service"
}]
},
{
type = "service"
dependent_service = [{
id = "DD4V04U"
type = "service"
}],
supporting_service = [{
id = "BBF2LHB"
type = "service"
}]
}
]
...and the API reponds
╷
│ Error: Too many dependency blocks
│
│ on main.tf line 160, in resource "pagerduty_service_dependency" "this":
│ 160: content {
│
│ No more than 1 "dependency" blocks are allowed
╵
Which seems to go against what the API documentation states can be done.
Any insight here as to what I am missing would be highly appreciated. Thanks for looking.
Cheers

Combining/merging variables in terraform

I am working within terraform and I am trying to combine variables.
I have been able to do this previously in the format of
name = "${var.name}-${var.environment}"
or something like
domain = "${var.environment}.${var.domain}"
Now I am trying to accomplish something similar but my module that I would like to do this with is utilizing a for_each.
I am trying to specify a host using variables that would represent utility.environment.domain.
Module:
module "aws_alb_listener_rule" {
depends_on = [
module.aws_lb_target_group,
module.aws_alb_listener_https
]
source = "../../terraform/modules/aws_lb_listener_rule_https"
listener_rule = module.aws_alb_listener__https.lb_listener
for_each = var.target_group_listener_rule_values
listener_rule_host_header = "${each.value.host_header}.${var.environment}.${var.domain}"
#listener_rule_host_header = each.value["host_header"]
listener_rule_target_group = module.aws_lb_target_group[each.key].arn
listener_rule_action_type = each.value["action_type"]
Where the first host header is what I was hoping to include but the commented out host header is how I currently have it.
My variables:
variable "target_group_listener_rule_values" {
description = "Specify the target group and listener rule settings and it will create on a 1:1 ratio"
type = map(object({
/*--- Listener Rule ---*/
host_header = list(string)
#host_header = string
target_group = string
action_type = string
my tfvars
listener_rule_values = {
Utility1 = {
/*--- Listener Rule ---*/
"host_header" = ["utility"],
#"host_header" = ["utility.environment.domain"],
"action_type" = "forward",
},
Utility2 = {
/*--- Listener Rule ---*/
"host_header" = ["utility"],
#"host_header" = ["utility.environment.domain"],
"action_type" = "forward",
},
Where the first host header is what I was hoping to include but the commented out host header is how I currently have it.
Child module:
resource "aws_lb_listener_rule" "static" {
listener_arn = var.listener_rule
action {
type = var.listener_rule_action_type
target_group_arn = var.listener_rule_target_group
}
condition {
host_header {
values = var.listener_rule_host_header
}
}
}
What I would like to do is shift this over to use variables instead as I am trying to remove the environment from being entered anywhere in the config except for the "environment" variable.
listener_rule_host_header = "${each.value["host_header"]}.${var.environment}.${var.domain}"
the error I am seeing is
Error: Invalid template interpolation value
│
│ on main.tf line 181, in module "module":
│ 181: listener_rule_host_header = "${each.value["host_header"]}.${var.environment}.${var.domain}"
│ ├────────────────
│ │ each.value["host_header"] is list of string with 1 element
│
│ Cannot include the given value in a string template: string required.
you are very close and your syntax for interpolation is correct. What is not correct is the fact that your each.value["host_header"] is a list, so Terraform complains about that.
I don't know why you want a list, and You did not give your full code, so let me show you a working example with map, that I imagine you want to accomplish:
variable "environment" {
default = "stage"
}
variable "domain" {
default = "example.com"
}
variable "sites" {
type = map(any)
default = {
"utils" = {
"host_header" = "utility"
}
"main" = {
"host_header" = "www"
}
}
}
resource "local_file" "hosts" {
for_each = var.sites
filename = "${each.value.host_header}.${var.environment}.${var.domain}"
content = each.key
}
You can adjust your code based on this example, I hope.
Now, if you insist on using lists, this would look like this:
variable "sites_with_list" {
type = map(any)
default = {
"utils" = {
"host_headers" = ["utility"]
}
"main" = {
"host_headers" = ["www"]
}
}
}
resource "local_file" "hosts_list" {
for_each = var.sites_with_list
filename = "${each.value.host_headers[0]}.${var.environment}.${var.domain}"
content = each.key
}
I did not know how you want to utilize this, so I used a local_file just as an example that will work when copied to empty project.

Produce repeating blocks inside a terraform resource

I am fairly new to terraform and trying to create a google_compute_backend_service using terraform and there are multiple backend blocks inside the resource as shown below:
resource "google_compute_backend_service" "app-backend" {
log_config {
enable = "true"
sample_rate = "1"
}
name = "app-backend"
port_name = "http-34070"
project = "my-project"
protocol = "HTTP"
session_affinity = "NONE"
timeout_sec = "30"
backend {
group = "insatnce-group1"
}
backend {
group = "instance-group2"
}
backend {
group = "instance-group3"
}
health_checks = [google_compute_http_health_check.app-http-l7.name]
}
As seen in the code block above the backend block repeats multiple times. I want to make it dynamic so I do not have to write multiple blocks manually.
I tried the following:
Created a variable in the variables.tf file that contains all the instance groups:
variable "groups" {
type = list(object({
name = string
}))
default = [{ name = "instance-group1"},
{ name = "instance-group2"},
{ name = "instance-group3"}]
}
And modified my resource block to this:
resource "google_compute_backend_service" "app-backend" {
log_config {
enable = "true"
sample_rate = "1"
}
name = "app-backend"
port_name = "http-34070"
project = "my-project"
protocol = "HTTP"
session_affinity = "NONE"
timeout_sec = "30"
dynamic "backend" {
for_each = var.groups
iterator = item
group = item.value.name
}
health_checks = [google_compute_http_health_check.app-http-l7.name]
}
However, when I execute terraform plan I get the following error:
Error: Unsupported argument
│
│ on backend_service.tf line 15, in resource "google_compute_backend_service" "app-backend":
│ 15: group = item.value.name
│
│ An argument named "group" is not expected here.
Where am I going wrong? Is there a better way to achieve this?
You can check the dynamic blocks documentation for the syntax. Otherwise, you had the right idea.
dynamic "backend" {
for_each = var.groups
content {
group = backend.value.name
}
}
You can also simplify the variable structure to make this even easier.
variable "groups" {
type = set(string)
default = ["instance-group1", "instance-group2", "instance-group3"]
}
dynamic "backend" {
for_each = var.groups
content {
group = backend.value
}
}

Because data.zabbix_template.template has "for_each" set, its attributes must be accessed on specific instances

I'm tring to create a zabbix template with applications defined and trigger.
I can create the template, import my hosts and associate to it.
Now when I try to add the trigger to the template, I receive the error in the object.
this is my
data.tf
data "zabbix_hostgroup" "group" {
name = "Templates"
}
data "zabbix_template" "template" {
for_each = {
common_simple = { name = "Common Simple" }
common_snmp = { name = "Common SNMP" }
class_template = { name = var.class_names[var.class_id] }
}
name = each.value.name
}
data "zabbix_proxy" "proxy" {
for_each = {
for inst in var.instances :
"${inst.instance}.${inst.site}" => inst.site
}
#host = "zabpxy01.${each.value}.mysite.local"
host = "mon-proxy1.${each.value}.mtsite.local"
}
and this is my hosts.tf:
# create host group for specific to service
resource "zabbix_hostgroup" "hostgroup" {
name = var.class_names[var.class_id]
}
# create template
resource "zabbix_template" "template" {
host = var.class_id
name = var.class_names[var.class_id]
description = var.class_names[var.class_id]
groups = [
data.zabbix_hostgroup.group.id
]
}
# create application
resource "zabbix_application" "application" {
hostid = data.zabbix_template.template.id
name = var.class_names[var.class_id]
}
# create snmp disk_total item
resource "zabbix_item_snmp" "disk_total_item" {
hostid = data.zabbix_template.template.id
key = "snmp_disk_root_total"
name = "Disk / total"
valuetype = "unsigned"
delay = "1m"
snmp_oid="HOST-RESOURCES-MIB::hrStorageSize[\"index\", \"HOST-RESOURCES-MIB::hrStorageDescr\", \"/\"]"
depends_on = [
data.zabbix_template.template
]
}
# create snmp disk_used item
resource "zabbix_item_snmp" "disk_used_item" {
hostid = data.zabbix_template.template.id
key = "snmp_disk_root_used"
name = "Disk / used"
valuetype = "unsigned"
delay = "1m"
snmp_oid="HOST-RESOURCES-MIB::hrStorageUsed[\"index\", \"HOST-RESOURCES-MIB::hrStorageDescr\", \"/\"]"
depends_on = [
data.zabbix_template.template
]
}
# create trigger > 75%
resource "zabbix_trigger" "trigger" {
name = "Disk Usage 75%"
expression = "({${data.zabbix_template.template.host}:${zabbix_item_snmp.disk_used_item.key}.last()} / {${data.zabbix_template.template.host}:${zabbix_item_snmp.disk_total_item.key}.last()}) * 100 >= 75"
priority = "warn"
enabled = true
multiple = false
recovery_none = false
manual_close = false
}
# create hosts
resource "zabbix_host" "host" {
for_each = {
for inst in var.instances : "${var.class_id}${format("%02d", inst.instance)}.${inst.site}" => inst
}
host = var.ip_addresses[var.class_id][each.value.site][each.value.instance]["hostname"]
name = var.ip_addresses[var.class_id][each.value.site][each.value.instance]["hostname"]
enabled = false
proxyid = data.zabbix_proxy.proxy["${each.value.instance}.${each.value.site}"].id
groups = [
zabbix_hostgroup.hostgroup.id
]
templates = concat ([
data.zabbix_template.template["common_simple"].id,
data.zabbix_template.template["common_snmp"].id,
zabbix_template.template.id
])
# add SNMP interface
interface {
type = "snmp"
ip = var.ip_addresses[var.class_id][each.value.site][each.value.instance]["mgmt0"]
main = true
port = 161
}
# Add Zabbix Agent interface
interface {
type = "agent"
ip = var.ip_addresses[var.class_id][each.value.site][each.value.instance]["mgmt0"]
main = true
port = 10050
}
macro {
name = "{$INTERFACE_MONITOR}"
value = var.ip_addresses[var.class_id][each.value.site][each.value.instance]["mgmt0"]
}
macro {
name = "{$SNMP_COMMUNITY}"
value = var.ip_addresses[var.class_id][each.value.site][each.value.instance]["snmp"]
}
depends_on = [
zabbix_hostgroup.hostgroup,
data.zabbix_template.template,
data.zabbix_proxy.proxy,
]
}
output "class_template_id" {
value = zabbix_template.template.id
description = "Template ID of created class template for items"
}
When I run "Terraform plan" I receive the error:
Error: Missing resource instance key │ │ on hosts/hosts.tf line 26,
in resource "zabbix_application" "application": │ 26: hostid =
data.zabbix_template.template.id │ │ Because
data.zabbix_template.template has "for_each" set, its attributes must
be accessed on specific instances. │ │ For example, to correlate with
indices of a referring resource, use: │
data.zabbix_template.template[each.key]
Where is my error?
Thanks for the support
UPDATE
I tried to use
output "data_zabbix_template" {
value = data.zabbix_template.template
}
but I don't see any output when I run terraform plan
I tried to modify in:
hostid = data.zabbix_template.template.class_template.id
but I continue to receive the same error:
Error: Missing resource instance key on hosts/hosts.tf line 27, in
resource "zabbix_application" "application": 27: hostid =
data.zabbix_template.template.class_template.id Because
data.zabbix_template.template has "for_each" set, its attributes must
be accessed on specific instances.
For example, to correlate with indices of a referring resource, use:
data.zabbix_template.template[each.key]
Error: Unsupported attribute on hosts/hosts.tf line 27, in resource
"zabbix_application" "application": 27: hostid =
data.zabbix_template.template.class_template.id This object has no
argument, nested block, or exported attribute named "class_template".
UPDATE:
My script for each host taht I'll add, set two existing template ("Common Simple" and "Common SNMP") and create a new template as below:
# module.mytemplate-servers_host.zabbix_template.template will be created
+ resource "zabbix_template" "template" {
+ description = "mytemplate-servers"
+ groups = [
+ "1",
]
+ host = "mytemplate-servers"
+ id = (known after apply)
+ name = "mytemplate-servers"
}
Now my scope is to add on this template an application and set two items and one trigger
When you use for_each in a data source or resource, the output of that data source or resource is a map, where the keys in the map are the same as the keys in the for_each and the values are the regular output of that data/resource for the given input value with that key.
Try using:
output "data_zabbix_template" {
value = data.zabbix_template.template
}
And you'll see what I mean. The output will look something like:
data_zabbix_template = {
common_simple = {...}
common_snmp = {...}
class_template = {...}
}
So in order to use this data source (on the line where the error is being thrown), you need to do:
hostid = data.zabbix_template.template.common_simple.id
And replace common_simple in that line with whichever key in the for_each you want to use. You'll need to do this everywhere that you use data.zabbix_template.template.

Passing Terraform variables between Modules within Modules?

I have an issue I have gotten stuck on building a module from the Kubespray project.
I have the following folder structure:
terraform/
modules/
kubespray/
modules/
compute/
ips/
network/
main.tf
variables.tf
roles/
kubespray/
terraform_setup/
main.tf
Within the kubespray/main.tf module there are variables being passed around:
module "ips" {
source = "./modules/ips"
number_of_k8s_masters = "${var.number_of_k8s_masters}"
number_of_k8s_masters_no_etcd = "${var.number_of_k8s_masters_no_etcd}"
number_of_k8s_nodes = "${var.number_of_k8s_nodes}"
floatingip_pool = "${var.floatingip_pool}"
number_of_bastions = "${var.number_of_bastions}"
external_net = "${var.external_net}"
network_name = "${var.network_name}"
router_id = "${module.network.router_id}"
}
module "compute" {
source = "./modules/compute"
...
k8s_master_fips = "${module.ips.k8s_master_fips}"
k8s_master_no_etcd_fips = "${module.ips.k8s_master_no_etcd_fips}"
k8s_node_fips = "${module.ips.k8s_node_fips}"
bastion_fips = "${module.ips.bastion_fips}"
...
}
output "private_subnet_id" {
value = "${module.network.subnet_id}"
}
output "floating_network_id" {
value = "${var.external_net}"
}
output "router_id" {
value = "${module.network.router_id}"
}
output "k8s_master_fips" {
value = "${concat(module.ips.k8s_master_fips, module.ips.k8s_master_no_etcd_fips)}"
}
output "k8s_node_fips" {
value = "${module.ips.k8s_node_fips}"
}
output "bastion_fips" {
value = "${module.ips.bastion_fips}"
}
If I run my terraform init/apply from inside the submodule in modules/kubespray/ It works fine
If I run from my role/kubespray that has
module "kubespray" {
source = "../../../modules/kubespray"
providers = {
openstack.src = openstack
}
}
it fails with
Error: Invalid index
on ../../../modules/kubespray/modules/compute/main.tf line 670, in
resource "openstack_compute_floatingip_associate_v2" "k8s_node": 670:
floating_ip = "${var.k8s_node_fips[count.index]}"
|----------------
| count.index is 0
| var.k8s_node_fips is empty list of dynamic
Help would be extremely appreicated
When you run terraform, the directory becomes an implicit "root module". As it's explained in the Input Variables documentation, root modules can access TF inputs from the CLI or environment, but all other called modules need to have these passed in.
I'm assuming what's happening could be that your ~kubespray/variables.tf input variables have default values assigned which are taking effect. If that's the case, then these should be copied over to ~terraform_setup/variables.tf and passed into the calling module:
module "kubespray" {
source = "../../../modules/kubespray"
providers = {
openstack.src = openstack
}
}
Without asking for further information, my guess here is that you have an empty array being passed in (${var.k8s_node_fips[count.index]}) with a value greater than 0 for var.node_root_volume_size_in_gb and var.number_of_k8s_nodes_no_floating_ip, causing an error when trying to index an empty array for a string that the resource declaration requires.
Error: Invalid index
on ../../../modules/kubespray/modules/compute/main.tf line 670, in resource "openstack_compute_floatingip_associate_v2" "k8s_node": 670: floating_ip = "${var.k8s_node_fips[count.index]}" |---------------- | count.index is 0 | var.k8s_node_fips is empty list of dynamic
According to the error, it seems this may be a interpolation error at ${var.k8s_node_fips[count.index]}.
What it seems like is happening is that the variable is empty, but the count is set to a non-zero number, thereby causing TF to error out.
Looking at the original project code, seems like the count variable depends on var.node_root_volume_size_in_gb to be 0 to be set to 0; otherwise will set the count to the value of var.number_of_k8s_nodes_no_floating_ip.
Snippet that the error seems to be occuring at:
resource "openstack_compute_instance_v2" "k8s_node_no_floating_ip_custom_volume_size" {
name = "${var.cluster_name}-k8s-node-nf-${count.index+1}"
count = "${var.node_root_volume_size_in_gb > 0 ? var.number_of_k8s_nodes_no_floating_ip : 0}"
availability_zone = "${element(var.az_list, count.index)}"
image_name = "${var.image}"
flavor_id = "${var.flavor_k8s_node}"
key_pair = "${openstack_compute_keypair_v2.k8s.name}"
block_device {
uuid = "${data.openstack_images_image_v2.vm_image.id}"
source_type = "image"
volume_size = "${var.node_root_volume_size_in_gb}"
boot_index = 0
destination_type = "volume"
delete_on_termination = true
}
Hope this helps

Resources