I am trying to develop a provider. One the attributes that I am trying to define accepts different types.
Following is an example of the data that I am trying to define the Schema for. As it is visible, value attribute accepts different types, such as list(string), list(int64), or an object:
"criteria": {
"filters": [
{
"type": "TYPE_1",
"value": [
"Str1",
"Str2"
]
},
{
"type": "TYPE_2",
"value": [
123,
456
],
"dataset": null
},
{
"type": "TYPE_3",
"value": {
"value": 0,
"from": 10,
"to": 100
}
}
]
}
Following is a sample code snippet of the Schema that I have trying to write down:
func (d *mitigationPolicyDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
"criteria": schema.SetNestedAttribute{
Required: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"filters": schema.ListNestedAttribute{
Required: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"value": DONT_KNOW_HOW_TO_DEFINE_THIS,
"type": schema.StringAttribute{
Required: true,
},
},
},
},
},
},
}
}
I don’t know how to achieve this. Any help is highly appreciated
HI @Masoud-CSIRT,
In Terraform, all elements of a collection must always be of the same type, so there is not a way to define an attribute that accepts different types. Terraform will always try to find a single type to represent an attribute. The Terraform Type Constraints documentation will give you more details on this.
Instead, I would suggest splitting up the types up into their own attributes, like splitting the filters
attribute into stringFilters
, int64Filters
, and so on.
A possible variation on this would be to define “filters” as being a list of an object type which has one attribute for each possible filter type. Using Terraform’s own type constraint notation, that could be:
filters = list(object({
type_1 = optional(list(string))
type_2 = optional(list(number))
type_3 = optional(object({
value = number
from = number
to = number
}))
}))
(To write this in a provider you’d need to describe it using the plugin framework’s API to describe the type in Go, rather than using Terraform’s syntax, but I’m using Terraform syntax here just to illustrate it more concisely.)
You can then add a validation rule, implemented as Go code in your provider, which raises an error unless exactly one of the attributes is set. This then makes the object type behave equivalently to a tagged union, where the author’s chosen attribute type acts as the “tag”.
When writing a value of this type in a Terraform module, an author would therefore use the attribute name to indicate which type they are intending to use for each element of the list:
filters = [
{
type_1 = ["Str1", "Str2"]
},
{
type_2 = [123, 456]
},
{
type_3 = {
value = 0
from = 10
to = 100
}
},
]
The plugin framework doesn’t directly support this pattern as a built-in, so currently you would need to implement it yourself. One way to implement it would be to write a type that implements the interface attr.Type
, performing the validation that checks that exactly one attribute is set in the ValueFromTerraform
method (returning an error if not), and making TerraformType
return the framework’s representation of the object type constraint I wrote at the start of this comment.