I’m writing a Terraform provider using the Terraform Plugin Framework for an API that merges provided map values with its own set of default map values for a particular attribute when a resource is created. These are not fixed, but can differ per-resource, so the provider can’t know ahead of time what the API-provided defaults will be, nor can it know which keys were user-provided and which were API-provided after the resource is created. The attribute is also immutable for each resource instance, so changes to the user-provided value require the resource to be replaced, and the API-provided defaults will never change on subsequent reads.
For example, if I made a POST
request that writes the following value to the attribute:
{
"user-provided-key": "example"
}
A subsequent GET
request might return the following value:
{
"user-provided-key": "example",
"api-provided-key": "default"
}
Importantly, user-provided values always supersede API-provided defaults.
Naturally, if I try to naively model this in my Terraform provider by defining the attribute as an Optional
Computed
MapAttribute
, then I get inconsistency errors, since the Read()
value will contain keys that the planned value did not have.
Right now I’m modeling this by storing only the user-provided keys in the Terraform state in my Create()
implementation, and taking the intersection of the keys present in the current state and the keys returned from the API in my Read()
and Update()
implementations (to allow the resource to be imported without immediately requiring replacement, if the user-provided values already match those returned by the API).
This is less than ideal though, since the user has no way to retrieve the values of API-provided defaults:
resource "example" "resource" {
attribute = {
user_provided_key = "example"
}
}
# Error: Invalid index
output "api_provided_key" {
value = example.resource.attribute.api_provided_key
}
Is the only alternative to provide a separate read-only Computed
attribute for the merged attribute value? For example:
resource "example" "resource" {
attribute = {
user_provided_key = "example"
}
}
# api_provided_key = "default"
output "api_provided_key" {
value = example.resource.attribute_merged.api_provided_key
}
# user_provided_key = "example"
output "user_provided_key" {
value = example.resource.attribute_merged.user_provided_key
}
I don’t particularly like this solution, since it’s not very ergonomic or intuitive for users, and my resource will have more than one attribute that follows this pattern, causing a lot of duplication of attributes.