Some of this was discussed out of band, so posting some findings for future travelers that may have similar issues.
Problem
It’s common to run into a problem while migrating from SDKv2 to Plugin Framework as Terraform core handles SDKv2 data differently in certain situations compared to Plugin Framework. This problem is briefly summarized here: Document Mitigation for Empty String to Null Plans for SDK Migrations · Issue #510 · hashicorp/terraform-plugin-framework · GitHub. Running into this problem can prompt Terraform to detect changes to a resource due to the proper data handling of null
by Plugin Framework.
Amplifying the confusion when experiencing this migration issue, is a bug with Terraform CLI that hides the attributes that are causing a resource to be updated, that looks like below:
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# random_password.test will be updated in-place
~ resource "random_password" "test" {
id = "none"
# (12 unchanged attributes hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
In the example above, there is actually a change being made (due to the migration now handling null
values properly, however Terraform is not correctly displaying the output to the human-readable plan output.
Debugging this problem
In the acceptance tests for your provider, you can use a Plan Check to output the machine-readable plan output, which will have the correct information about the attributes that are planned for change (before
and after
).
A custom debug plan check can be added to your tests that will output the machine readable plan output, which can be useful in determining what values are changing during migration. The documentation of this machine-readable plan output is here.
Implementing a simple debug plan check and using it
package plancheck_test
import (
"context"
"encoding/json"
"fmt"
"testing"
r "github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/plancheck"
)
var _ plancheck.PlanCheck = debugPlan{}
type debugPlan struct{}
func (e debugPlan) CheckPlan(ctx context.Context, req plancheck.CheckPlanRequest, resp *plancheck.CheckPlanResponse) {
rd, err := json.Marshal(req.Plan)
if err != nil {
fmt.Println("error marshalling machine-readable plan output:", err)
}
fmt.Printf("req.Plan - %s\n", string(rd))
}
func DebugPlan() plancheck.PlanCheck {
return debugPlan{}
}
func Test_DebugPlan(t *testing.T) {
t.Parallel()
r.Test(t, r.TestCase{
ExternalProviders: map[string]r.ExternalProvider{
"random": {
Source: "registry.terraform.io/hashicorp/random",
},
},
Steps: []r.TestStep{
{
Config: `resource "random_string" "one" {
length = 16
}`,
PlanOnly: true,
ConfigPlanChecks: r.ConfigPlanChecks{
PostApplyPreRefresh: []plancheck.PlanCheck{
DebugPlan(),
},
},
},
},
})
}
Output
req.Plan - {"format_version":"1.1","terraform_version":"1.4.1","planned_values":{"root_module":{"resources":[{"address":"random_string.one","mode":"managed","type":"random_string","name":"one","provider_name":"registry.terraform.io/hashicorp/random","schema_version":2,"values":{"keepers":null,"length":16,"lower":true,"min_lower":0,"min_numeric":0,"min_special":0,"min_upper":0,"number":true,"numeric":true,"override_special":null,"special":true,"upper":true},"sensitive_values":{}}]}},"resource_changes":[{"address":"random_string.one","mode":"managed","type":"random_string","name":"one","provider_name":"registry.terraform.io/hashicorp/random","change":{"actions":["create"],"before":null,"after":{"keepers":null,"length":16,"lower":true,"min_lower":0,"min_numeric":0,"min_special":0,"min_upper":0,"number":true,"numeric":true,"override_special":null,"special":true,"upper":true},"after_unknown":{"id":true,"result":true},"before_sensitive":false,"after_sensitive":{}}}],"configuration":{"provider_config":{"random":{"name":"random","full_name":"registry.terraform.io/hashicorp/random"}},"root_module":{"resources":[{"address":"random_string.one","mode":"managed","type":"random_string","name":"one","provider_config_key":"random","expressions":{"length":{"constant_value":16}},"schema_version":2}]}}}
In this specific post, we found a string attribute that was being changed from ""
in SDKv2, to null
with Plugin Framework. This information was hidden from the Terraform CLI due to the bug, but was present in the machine readable output.