hashicorp / hashicorp/terraform-plugin-framework

inability to prevent recreation on empty Map in `Plan` when state on field is `null`

Open
#1,298 1 comment 0 reactions 0 assignees View on GitHub
bug
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.