Reputation: 44334
I have a variable defined as,
variable "ssh_permit" {
type = list(object({ name = string, ip = string }))
default = [
{
name = "alice"
ip = "1.1.1.1"
},
{
name = "bob"
ip = "2.2.2.2"
},
]
}
I'm attempting to use a for
loop, to post-process this value into an object that can be assigned to the security_rule
attribute on an Azure NSG.
The docs seem to indicate that this should be possible.
However, I get:
Error: Incorrect attribute value type
on test.tf line 81, in resource "azurerm_network_security_group" "foo_nsg":
81: security_rule = [for idx, rule in var.ssh_permit: {
82: name = "allow-${rule.name}"
83: priority = 100 + idx
84: direction = "Inbound"
85: access = "Allow"
86: protocol = "TCP"
87: source_address_prefix = rule.ip
88: source_port_range = "*"
89: destination_address_prefix = "*"
90: destination_port_range = "22"
91: } ]
|----------------
| var.ssh_permit is list of object with 2 elements
Inappropriate value for attribute "security_rule": element 0: attributes
"description", "destination_address_prefixes",
"destination_application_security_group_ids", "destination_port_ranges",
"source_address_prefixes", "source_application_security_group_ids", and
"source_port_ranges" are required.
It is like Terraform is ignoring the fact that there's a for loop there. Yes, var.ssh_permit
is a list of object w/ 2 elements, but it gets transformed into the appropriate object for security_element
.
(I've gotten this to work with the dynamic block syntax, but I'd like to ignore that for the purposes of the question, since I'm trying to also learn the TF syntax for for
loops. It appears that it should work here, so I'd like to understand why not.)
Upvotes: 0
Views: 1721
Reputation: 74694
This failure is reporting that the first element value you've provided for security_rule
doesn't meet the type constraint for this argument.
Based on the error message, it seems like this argument requires a list of objects with various attributes, including all of the ones listed in the error message. One way to make this result conform to the type, then, would be to set all of those extra attributes to null
so that the object has a compatible type but still leaves those attributes unpopulated:
security_rule = [
for idx, rule in var.ssh_permit: {
name = "allow-${rule.name}"
priority = 100 + idx
direction = "Inbound"
access = "Allow"
protocol = "TCP"
source_address_prefix = rule.ip
source_port_range = "*"
destination_address_prefix = "*"
destination_port_range = "22"
description = null
destination_address_prefixes = null
destination_application_security_group_ids = null
destination_port_ranges = null
source_address_prefixes = null
source_application_security_group_ids = null
source_port_ranges = null
}
]
The above expression should make the result have a suitable type, although the provider is free to impose additional validation constraints beyond type checking and so it may not consider null
to be a suitable value for all of these attributes.
You mentioned dynamic
blocks and so I expect you have an underlying question here about why you don't typically need to set arguments to null
in blocks but yet you do here in this expression.
This is because a block in the Terraform language is a special language structure rather than a normal value, and so it has some extra capabilities that differentiate it from values, including the idea that particular arguments can be optional, and that a block can contain nested blocks. Nested blocks are a language construct somewhat unique to Terraform, due to its intent to use a declarative style that reads like a description of a desired result rather than like a typical computer program.
However, whenever you see the security_rule =
syntax you are assigning a value to an argument. Values in Terraform are a lot more like the typical idea of values in general-purpose programming languages, and in particular each value has a type. By using the { ... }
syntax here, you've constructed an object-typed value. Notice that "object" isn't actually a type itself, but rather it's a subcategory of types that have attributes, similar to how in some general-purpose programming languages "object" just means an instance of any class, and the class itself is the type.
A less-conventional characteristic of the Terraform language, though, is that it has a structural type system, which means that object types are defined entirely by their shape (what attributes they have) and not by a "class name", as you might see in other languages. Resource arguments have type constraints, which state which types of values are allowed to be assigned there.
The definition of this security_rule
argument in the provider seems to declare it as if it had the following type constraint (although provider-defined type constraints are not visible directly in the language itself):
list(object({
name = string
priority = number
direction = string
access = string
protocol = string
source_address_prefix = string
source_port_range = string
destination_address_prefix = string
destination_port_range = string
description = string
destination_address_prefixes = list(string)
destination_application_security_group_ids = list(string)
destination_port_ranges = list(string)
source_address_prefixes = list(string)
source_application_security_group_ids = list(string)
source_port_ranges = list(string)
}))
(I might not have got this exactly right; I just guessed based on what we've discussed so far in this question, rather than referring to the documentation.)
Terraform decides if an object type meets an object type constraint by checking that it has at least the attributes defined in the type constraint. Type checking failed in your case because the object you provided was lacking some of the attributes from the type constraint.
I think there's an extra detail worth mentioning here for this security_rule
argument in particular. Although the provider developers seem to have forgotten to document it as such, I think this argument is marked in the provider schema as using the legacy Attributes as Blocks mode, which is a special shim that Terraform supports to allow backward compatibility with some situations where provider designs were relying on configuration validation bugs in Terraform v0.11 that have since been fixed.
I won't include all of the special cases about Attributes as Blocks mode here because they only apply to some special old provider designs and they're already described in detail in the documentation page, but I wanted to mention it since I think this argument using that mode contributes to its behavior being a little confusing/inconsistent when compared to a typical resource type argument.
Upvotes: 1
Reputation: 238877
security_rule
is a block, not an attribute. Thus you can't treat is as an attribute using
security_rule =
It must be defined through dynamic blocks, which as you noted, works.
Your second link to example =
is for attribute, not block.
Upvotes: 0