hashicorp / hashicorp/terraform-plugin-framework

Non-empty plans when migrating existing optional+computed lists to Terraform Plugin Framework

Open
#883 10 comments 3 reactions 0 assignees View on GitHub
bug
Dominant language
Go
Stars
384
Forks
107
Avg merge
3m
Merged PRs (30d)
1

Description

### Module version

```
1.4.2
```
We are currently migrating existing Plugin-SDK based resources to Plugin Framework (Plugin protocol v6). In below resource, we have some **optional+computed** list block attributes where certain nested attributes _if not configured/partially configured_ by the user in the config, the API/provider will still return those attributes. That response is currently persisted in the state.

In order to migrate these blocks to the Plugin Framework, we have tried using schema.ListNestedBlock but the returned plan after upgrade to the Framework-migrated resource is always non-empty. **This will be a breaking change for our users.**

**What is the recommended non-breaking way to migrate list attributes such as these?**

### Relevant provider source code

Terraform Plugin SDK based schema for "advanced_configuration" block:
```go
func ClusterAdvancedConfigurationSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Optional: true,
Computed: true,
ConfigMode: schema.SchemaConfigModeAttr,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"default_read_concern": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"default_write_concern": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"fail_index_key_too_long": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"javascript_enabled": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"minimum_enabled_tls_protocol": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"no_table_scan": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"oplog_size_mb": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"oplog_min_retention_hours": {
Type: schema.TypeInt,
Optional: true,
},
........
},
},
}
}
```

Terraform Plugin Framework migrated schema:
```go
func clusterRSAdvancedConfigurationSchemaBlock() schema.ListNestedBlock {
return schema.ListNestedBlock{
NestedObject: schema.NestedBlockObject{
Attributes: map[string]schema.Attribute{
"default_read_concern": schema.StringAttribute{
Optional: true,
Computed: true,
},
"default_write_concern": schema.StringAttribute{
Optional: true,
Computed: true,
},
"fail_index_key_too_long": schema.BoolAttribute{
Optional: true,
Computed: true,
},
"javascript_enabled": schema.BoolAttribute{
Optional: true,
Computed: true,
},
"minimum_enabled_tls_protocol": schema.StringAttribute{
Optional: true,
Computed: true,
},
"no_table_scan": schema.BoolAttribute{
Optional: true,
Computed: true,
},
"oplog_min_retention_hours": schema.Int64Attribute{
Optional: true,
},
"oplog_size_mb": schema.Int64Attribute{
Optional: true,
Computed: true,
},
..........
},
},
Validators: []validator.List{
listvalidator.SizeAtMost(1),
},
PlanModifiers: []planmodifier.List{
listplanmodifier.UseStateForUnknown(),
},
}
}
```

### Terraform Configuration Files

Use-case 1 (no blocks configured):
```hcl
main.tf:
resource "mongodbatlas_cluster" "cluster-no-blocks" {
project_id = mongodbatlas_project.project-tf.id
provider_name = "AWS"
name = "tfCluster1"
backing_provider_name = "AWS"
provider_region_name = "US_EAST_1"
provider_instance_size_name = "M10"
auto_scaling_compute_enabled = false
auto_scaling_compute_scale_down_enabled = false
provider_auto_scaling_compute_min_instance_size = "M10"
provider_auto_scaling_compute_max_instance_size = "M20"
}

terraform.tfstate (including only concerned blocks here due to large resource config):
"advanced_configuration": [
{
"default_read_concern": "",
"default_write_concern": "",
"fail_index_key_too_long": false,
"javascript_enabled": true,
"minimum_enabled_tls_protocol": "TLS1_2",
"no_table_scan": false,
"oplog_min_retention_hours": 0,
"oplog_size_mb": 0,
"sample_refresh_interval_bi_connector": 0,
"sample_size_bi_connector": 0,
"transaction_lifetime_limit_seconds": 0
}
],
"bi_connector_config": [
{
"enabled": false,
"read_preference": "secondary"
}
],
```

Use-case 2 (all blocks configured):
```hcl
main.tf:
resource "mongodbatlas_cluster" "cluster-multi-region-all-blocks" {
project_id = mongodbatlas_project.project-tf.id
name = "cluster-test-multi-region"
num_shards = 1
cloud_backup = true
cluster_type = "REPLICASET"

provider_name = "AWS"
provider_instance_size_name = "M10"

advanced_configuration { # block can be partially configured by user
minimum_enabled_tls_protocol = "TLS1_2"
default_read_concern = "available"
}

bi_connector_config {
enabled = false
read_preference = "secondary"
}

terraform.tfstate (including only concerned blocks here due to large resource config):
"advanced_configuration": [
{
"default_read_concern": "available",
"default_write_concern": "",
"fail_index_key_too_long": false,
"javascript_enabled": true,
"minimum_enabled_tls_protocol": "TLS1_2",
"no_table_scan": false,
"oplog_min_retention_hours": 0,
"oplog_size_mb": 0,
"sample_refresh_interval_bi_connector": 0,
"sample_size_bi_connector": 0,
"transaction_lifetime_limit_seconds": 0
}
],
"bi_connector_config": [
{
"enabled": false,
"read_preference": "secondary"
}
],
```

### Debug Output

### Expected Behavior

After user upgrades to new provider version with the framework-migrated resource, user should not see any planned changes for optional+computed lists/set blocks when running `terraform plan` and not receive errors when running `terraform apply`.

### Actual Behavior

On running `terraform plan` below plan was produced by Terraform:
**Use-case 1 (no blocks configured):**
```
~ update in-place

Terraform will perform the following actions:

# mongodbatlas_cluster.cluster-no-blocks will be updated in-place
~ resource "mongodbatlas_cluster" "cluster-no-blocks" {
id = "Y2x1c3Rlcl9pZA==:NjU2ODhiMmYzZDI3MWYzZjg2MGI0NjQw-Y2x1c3Rlcl9uYW1l:dGZDbHVzdGVyMQ==-cHJvamVjdF9pZA==:NjRlY2MxNTkyNzVmMjM1OWY0Y2FlODEw-cHJvdmlkZXJfbmFtZQ==:QVdT"
name = "tfCluster1"
~ num_shards = 1 -> (known after apply)
# (34 unchanged attributes hidden)

- advanced_configuration {
- javascript_enabled = true -> null
- minimum_enabled_tls_protocol = "TLS1_2" -> null
- no_table_scan = false -> null
- oplog_min_retention_hours = 0 -> null
}

- bi_connector_config {
- enabled = false -> null
- read_preference = "secondary" -> null
}
```

**Use-case 2 (all blocks configured):**
```
~ update in-place

Terraform will perform the following actions:

# mongodbatlas_cluster.cluster-multi-region-all-blocks will be updated in-place
~ resource "mongodbatlas_cluster" "cluster-multi-region-all-blocks" {
id = "Y2x1c3Rlcl9pZA==:NjU2ODhlZTgzZDI3MWYzZjg2MGI0ZTY3-Y2x1c3Rlcl9uYW1l:dGYtbXVsdGktcmVnaW9uLWFsbC1ibG9ja3M=-cHJvamVjdF9pZA==:NjRlY2MxNTkyNzVmMjM1OWY0Y2FlODEw-cHJvdmlkZXJfbmFtZQ==:QVdT"
name = "tf-multi-region-all-blocks"
# (32 unchanged attributes hidden)

~ advanced_configuration {
+ default_write_concern = (known after apply)
+ fail_index_key_too_long = (known after apply)
~ no_table_scan = false -> (known after apply)
- oplog_min_retention_hours = 0 -> null
+ oplog_size_mb = (known after apply)
+ sample_refresh_interval_bi_connector = (known after apply)
+ sample_size_bi_connector = (known after apply)
+ transaction_lifetime_limit_seconds = (known after apply)
# (3 unchanged attributes hidden)
}

```

### Steps to Reproduce

1. run `terraform init` for provider v.X (contains-Plugin-SDK-based-resource-implementation) for above resource
2. run `terraform plan`
4. run `terraform apply`
5. run `terraform plan` again -> plan returns `No changes.`
6. update provider version to v.Y (contains-Plugin-Framework-based-resource-implementation) for above resource, run `terraform init --upgrade`
7. run `terraform plan` -> Plan is not empty

### References

Included advanced_configuration schema in this issue. Other block schemas mentioned in the example are:

- bi_connector_config [Plugin-SDK schema](https://github.com/mongodb/terraform-provider-mongodbatlas/blob/master/internal/service/cluster/resource_cluster.go#L91) - [migrated-Plugin-Framework schema](https://github.com/mongodb/terraform-provider-mongodbatlas/blob/INTMDB-976-migrate-cluster-framework/mongodbatlas/fw_resource_mongodbatlas_cluster.go#L344)

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.