Hmm , I think I’ll need to see more of a resource schema to really help investigate further. Maybe I can propose one and we can adjust it to get on the same page.
We’ll start simple and then can adjust to more closely reflect the schema you’re working with:
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"configure_me": schema.StringAttribute{
Required: true,
},
"foo": schema.SingleNestedAttribute{
Computed: true,
Attributes: map[string]schema.Attribute{
"bar": schema.StringAttribute{
Computed: true,
},
},
},
},
}
resource "examplecloud_thing" "this" {
configure_me = "hello"
}
output "foo" {
value = examplecloud_thing.this.foo
}
First run
If I have the above schema, the expected behavior would be:
- User declares resource in config (not yet in state) and runs
terraform apply
- Resource plans to create,
foo
is unknown (no plan modifiers are on the schema)
Terraform will perform the following actions:
# examplecloud_thing.this will be created
+ resource "examplecloud_thing" "this" {
+ configure_me = "hello"
+ foo = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ foo = (known after apply)
Create
method is called, foo
is set to a value, saved to state, all good
Second run
- User changes config, runs
terraform apply
resource "examplecloud_thing" "this" {
configure_me = "hello123"
}
output "foo" {
value = examplecloud_thing.this.foo
}
Read
is called, foo
has changed, prior state is refreshed with the new value.
- Resource plans to update
configure_me
and foo
is marked as unknown (no plan modifiers, it’s computed so the value could change)
Terraform will perform the following actions:
# examplecloud_thing.this will be updated in-place
~ resource "examplecloud_thing" "this" {
~ configure_me = "hello" -> "hello123"
~ foo = {
~ bar = "here is a value!" -> (known after apply)
} -> (known after apply)
}
Plan: 0 to add, 1 to change, 0 to destroy.
Changes to Outputs:
~ foo = {
- bar = "here is a value!"
} -> (known after apply)
Update
method is called, foo
is set to a value, saved to state, all good
Read
influences the prior state that is presented to the plan step, but in the example above, there is no plan modifier to suggest that foo
should be populated, so it’s set to unknown.
All of this being said, the error you presented was complaining about the plan saying that .foo was null
, which for a Computed
value, should only be possible if a planmodifier
or default
sets it to null. If that’s not happening I think it’s a bug.
The only way I’ve been able to recreate your described error is by adding a default
like so:
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"configure_me": schema.StringAttribute{
Required: true,
},
"foo": schema.SingleNestedAttribute{
Computed: true,
Default: objectdefault.StaticValue(types.ObjectNull(map[string]attr.Type{
"bar": types.StringType,
})),
Attributes: map[string]schema.Attribute{
"bar": schema.StringAttribute{
Computed: true,
},
},
},
},
}
Then you’ll get this on your next terraform apply
The Update
method is setting the value of foo
to something that is not null
, which is invalid
Terraform will perform the following actions:
# examplecloud_thing.this will be updated in-place
~ resource "examplecloud_thing" "this" {
- foo = {
- bar = "here is an updated value!" -> null
} -> null
# (1 unchanged attribute hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
Changes to Outputs:
- foo = {
- bar = "here is an updated value!"
} -> null
examplecloud_thing.this: Modifying...
╷
│ Error: Provider produced inconsistent result after apply
│
│ When applying changes to examplecloud_thing.this, provider "provider[\"registry.terraform.io/austinvalle/sandbox\"]" produced an unexpected new value: .foo: was null, but now
│ cty.ObjectVal(map[string]cty.Value{"bar":cty.StringVal("here is an updated value!")}).
│
│ This is a bug in the provider, which should be reported in the provider's own issue tracker.
Apologies, I feel like I’m running us in circles, but hopefully some of this description can help. There is also documentation on how our RPCs map to the functions that you setup in your resources: Plugin Development - Framework: RPCs | Terraform | HashiCorp Developer