hashicorp / hashicorp/terraform-plugin-framework
inability to prevent recreation on empty Map in `Plan` when state on field is `null`
- Dominant language
- Go
- Stars
- 384
- Forks
- 107
- Avg merge
- 3m
- Merged PRs (30d)
- 1
Description
When reviewing the following fix for `inconsistent result after apply`, i noticed that there isn't a way to handle a case where users explicitly set an empty map on a field when the state is set as `null` without a recreation step occurring.
- https://github.com/GoogleCloudPlatform/magic-modules/pull/17200#discussion_r3157110842
a recreation step shouldn't be necessary if the provider finds that `{}` and `null` are semantically the same on the specific field of the resource.
### Module version
```
v1.17.0
```
### Terraform Configuration Files
```hcl
resource "google_storage_notification" "notification" {
bucket = google_storage_bucket.bucket.name
payload_format = "JSON_API_V1"
topic = google_pubsub_topic.topic.id
event_types = ["OBJECT_FINALIZE"]
custom_attributes = {}
depends_on = [google_pubsub_topic_iam_binding.binding]
}
```
`terraform apply` when `tf.state` is:
```hcl
{
"mode": "managed",
"type": "google_storage_notification",
"name": "notification",
"provider": "provider[\"registry.terraform.io/hashicorp/google\"]",
"instances": [
{
"schema_version": 1,
"attributes": {
"bucket": "testing-custom-attributes",
"custom_attributes": {},
"event_types": [
"OBJECT_FINALIZE"
],
...
```
### Debug Output
### Expected Behavior
We should be able to create a planModifier that handles an empty map as the same as null
### Actual Behavior
When attempting to handle this in a planModifier I get the following:
```hcl
│ Error: Provider produced invalid plan
│
│ Provider "registry.terraform.io/hashicorp/google" planned an invalid value for google_storage_notification.notification.custom_attributes: planned value cty.NullVal(cty.Map(cty.String))
│ does not match config value cty.MapValEmpty(cty.String).
```
## planModifier implementation attempt
```go
func EmptyMapAsNull() planmodifier.Map {
return emptyMapAsNullModifier{}
}
type emptyMapAsNullModifier struct{}
func (m emptyMapAsNullModifier) Description(_ context.Context) string {
return "Treats an empty map ({}) as null."
}
func (m emptyMapAsNullModifier) MarkdownDescription(ctx context.Context) string {
return m.Description(ctx)
}
func (m emptyMapAsNullModifier) PlanModifyMap(ctx context.Context, req planmodifier.MapRequest, resp *planmodifier.MapResponse) {
if req.PlanValue.IsNull() || req.PlanValue.IsUnknown() {
return
}
if len(req.PlanValue.Elements()) == 0 {
resp.PlanValue = basetypes.NewMapNull(basetypes.NewDynamicNull().Type(ctx))
}
}
```
### Steps to Reproduce
1. apply plan modifier to a MapAttribute field
2. provision resource to have map be `null` in `tf.state`
3. apply empty map `field_name = {}`
4. `terraform apply`
### References
Contributor guide
Assessment
This issue has not been assessed yet.