Hello,
I’m working on a provider using the Plugin Framework. I have a specific resource that stores a date as a string in ISO8601 (format accepted and returned by the API), with the following schema:
"expiration_date": schema.StringAttribute{
Computed: true,
Optional: true,
Validators: []validator.String{
fwvalidators.DateValidator(),
},
PlanModifiers: []planmodifier.String{
fwmodifyplan.CheckExpirationDate(),
},
},
For this reason, I’ve made a custom plan modifier to not detect a difference when the date is semantically equivalent:
func (m datePlanModify) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
if req.StateValue.IsNull() || req.StateValue.ValueString() == "" || req.ConfigValue.IsUnknown() ||
req.ConfigValue.IsNull() || req.ConfigValue.ValueString() == "" {
return
}
configDate, err := iso8601.Parse([]byte(req.ConfigValue.ValueString()))
if err != nil {
resp.Diagnostics.AddError(
m.Description(ctx),
err.Error(),
)
}
stateDate, err := iso8601.Parse([]byte(req.StateValue.ValueString()))
if err != nil {
resp.Diagnostics.AddError(
m.Description(ctx),
err.Error(),
)
}
if resp.Diagnostics.HasError() {
return
}
if configDate.Equal(stateDate) {
resp.PlanValue = req.StateValue
} else if configDate.Before(stateDate) {
resp.Diagnostics.AddError(
m.Description(ctx),
"...",
)
}
}
During debugging, I observed that my test correctly triggers the equality check and updates the PlanValue to match the StateValue. However, terraform still detects a difference and forces an update. After the update is applied, terraform continues to detect a difference because the state value remains unchanged and the config value hasn’t changed either, resulting in an infinite update loop.
If I’ve understood correctly the Plan Modifier, this case should not trigger an update since the attribute PlanValue is set to the StateValue.
The log shows that terraform is detecting a change for the expiration_date attribute:
Full logs
2025-12-10T14:28:06.371Z [DEBUG] provider.terraform-provider-outscale: Value switched to prior value due to semantic equality logic: tf_rpc=ReadResource @module=sdk.framework tf_attribute_path=last_modification_date tf_mux_provider="*proto6server.Server" tf_provider_addr=registry.terraform.io/outscale/outscale tf_req_id=397f05fd-d29a-395f-cc44-4227c5e96578 tf_resource_type=outscale_access_key @caller=/home/outscale/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.14.1/internal/fwschemadata/value_semantic_equality.go:91 timestamp=2025-12-10T14:28:06.371Z
2025-12-10T14:28:06.371Z [DEBUG] provider.terraform-provider-outscale: Value switched to prior value due to semantic equality logic: @module=sdk.framework tf_attribute_path=access_key_id tf_mux_provider="*proto6server.Server" tf_provider_addr=registry.terraform.io/outscale/outscale tf_req_id=397f05fd-d29a-395f-cc44-4227c5e96578 tf_resource_type=outscale_access_key tf_rpc=ReadResource @caller=/home/outscale/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.14.1/internal/fwschemadata/value_semantic_equality.go:91 timestamp=2025-12-10T14:28:06.371Z
2025-12-10T14:28:06.372Z [DEBUG] provider.terraform-provider-outscale: Value switched to prior value due to semantic equality logic: tf_resource_type=outscale_access_key tf_rpc=ReadResource tf_provider_addr=registry.terraform.io/outscale/outscale @caller=/home/outscale/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.14.1/internal/fwschemadata/value_semantic_equality.go:91 @module=sdk.framework tf_attribute_path=state tf_mux_provider="*proto6server.Server" tf_req_id=397f05fd-d29a-395f-cc44-4227c5e96578 timestamp=2025-12-10T14:28:06.371Z
2025-12-10T14:28:06.372Z [DEBUG] provider.terraform-provider-outscale: Value switched to prior value due to semantic equality logic: @module=sdk.framework tf_mux_provider="*proto6server.Server" tf_req_id=397f05fd-d29a-395f-cc44-4227c5e96578 tf_resource_type=outscale_access_key tf_rpc=ReadResource @caller=/home/outscale/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.14.1/internal/fwschemadata/value_semantic_equality.go:91 tf_attribute_path=expiration_date tf_provider_addr=registry.terraform.io/outscale/outscale timestamp=2025-12-10T14:28:06.372Z
2025-12-10T14:28:06.372Z [DEBUG] provider.terraform-provider-outscale: Value switched to prior value due to semantic equality logic: @module=sdk.framework tf_mux_provider="*proto6server.Server" tf_req_id=397f05fd-d29a-395f-cc44-4227c5e96578 tf_resource_type=outscale_access_key @caller=/home/outscale/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.14.1/internal/fwschemadata/value_semantic_equality.go:91 tf_attribute_path=id tf_provider_addr=registry.terraform.io/outscale/outscale tf_rpc=ReadResource timestamp=2025-12-10T14:28:06.372Z
2025-12-10T14:28:06.372Z [DEBUG] provider.terraform-provider-outscale: Value switched to prior value due to semantic equality logic: @caller=/home/outscale/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.14.1/internal/fwschemadata/value_semantic_equality.go:91 tf_attribute_path=creation_date tf_mux_provider="*proto6server.Server" tf_provider_addr=registry.terraform.io/outscale/outscale tf_req_id=397f05fd-d29a-395f-cc44-4227c5e96578 tf_rpc=ReadResource @module=sdk.framework tf_resource_type=outscale_access_key timestamp=2025-12-10T14:28:06.372Z
2025-12-10T14:28:06.374Z [DEBUG] provider.terraform-provider-outscale: Detected value change between proposed new state and prior state: tf_req_id=d1ccd145-1124-39a9-6aae-b8cb995c11ac tf_resource_type=outscale_access_key @caller=/home/outscale/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.14.1/internal/fwserver/server_planresourcechange.go:220 tf_attribute_path=expiration_date tf_mux_provider="*proto6server.Server" tf_provider_addr=registry.terraform.io/outscale/outscale tf_rpc=PlanResourceChange @module=sdk.framework timestamp=2025-12-10T14:28:06.374Z
2025-12-10T14:28:06.374Z [DEBUG] provider.terraform-provider-outscale: Marking Computed attributes with null configuration values as unknown (known after apply) in the plan to prevent potential Terraform errors: tf_mux_provider="*proto6server.Server" tf_req_id=d1ccd145-1124-39a9-6aae-b8cb995c11ac @caller=/home/outscale/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.14.1/internal/fwserver/server_planresourcechange.go:229 @module=sdk.framework tf_provider_addr=registry.terraform.io/outscale/outscale tf_resource_type=outscale_access_key tf_rpc=PlanResourceChange timestamp=2025-12-10T14:28:06.374Z
2025-12-10T14:28:06.374Z [DEBUG] provider.terraform-provider-outscale: marking computed attribute that is null in the config as unknown: tf_rpc=PlanResourceChange @caller=/home/outscale/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.14.1/internal/fwserver/server_planresourcechange.go:480 @module=sdk.framework tf_attribute_path="AttributeName(\"secret_key\")" tf_mux_provider="*proto6server.Server" tf_provider_addr=registry.terraform.io/outscale/outscale tf_req_id=d1ccd145-1124-39a9-6aae-b8cb995c11ac tf_resource_type=outscale_access_key timestamp=2025-12-10T14:28:06.374Z
2025-12-10T14:28:06.374Z [DEBUG] provider.terraform-provider-outscale: marking computed attribute that is null in the config as unknown: @module=sdk.framework tf_attribute_path="AttributeName(\"creation_date\")" tf_mux_provider="*proto6server.Server" tf_req_id=d1ccd145-1124-39a9-6aae-b8cb995c11ac tf_resource_type=outscale_access_key @caller=/home/outscale/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.14.1/internal/fwserver/server_planresourcechange.go:480 tf_provider_addr=registry.terraform.io/outscale/outscale tf_rpc=PlanResourceChange timestamp=2025-12-10T14:28:06.374Z
2025-12-10T14:28:06.374Z [DEBUG] provider.terraform-provider-outscale: marking computed attribute that is null in the config as unknown: tf_req_id=d1ccd145-1124-39a9-6aae-b8cb995c11ac @caller=/home/outscale/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.14.1/internal/fwserver/server_planresourcechange.go:480 @module=sdk.framework tf_resource_type=outscale_access_key tf_rpc=PlanResourceChange tf_attribute_path="AttributeName(\"access_key_id\")" tf_mux_provider="*proto6server.Server" tf_provider_addr=registry.terraform.io/outscale/outscale timestamp=2025-12-10T14:28:06.374Z
2025-12-10T14:28:06.374Z [DEBUG] provider.terraform-provider-outscale: marking computed attribute that is null in the config as unknown: tf_mux_provider="*proto6server.Server" tf_req_id=d1ccd145-1124-39a9-6aae-b8cb995c11ac @module=sdk.framework tf_attribute_path="AttributeName(\"request_id\")" tf_provider_addr=registry.terraform.io/outscale/outscale tf_resource_type=outscale_access_key tf_rpc=PlanResourceChange @caller=/home/outscale/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.14.1/internal/fwserver/server_planresourcechange.go:480 timestamp=2025-12-10T14:28:06.374Z
2025-12-10T14:28:06.374Z [DEBUG] provider.terraform-provider-outscale: marking computed attribute that is null in the config as unknown: tf_mux_provider="*proto6server.Server" tf_provider_addr=registry.terraform.io/outscale/outscale tf_req_id=d1ccd145-1124-39a9-6aae-b8cb995c11ac tf_resource_type=outscale_access_key tf_rpc=PlanResourceChange @caller=/home/outscale/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.14.1/internal/fwserver/server_planresourcechange.go:480 @module=sdk.framework tf_attribute_path="AttributeName(\"last_modification_date\")" timestamp=2025-12-10T14:28:06.374Z
2025-12-10T14:28:06.374Z [DEBUG] provider.terraform-provider-outscale: marking computed attribute that is null in the config as unknown: tf_req_id=d1ccd145-1124-39a9-6aae-b8cb995c11ac tf_resource_type=outscale_access_key @caller=/home/outscale/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.14.1/internal/fwserver/server_planresourcechange.go:480 tf_attribute_path="AttributeName(\"id\")" tf_mux_provider="*proto6server.Server" tf_rpc=PlanResourceChange @module=sdk.framework tf_provider_addr=registry.terraform.io/outscale/outscale timestamp=2025-12-10T14:28:06.374Z
2025-12-10T14:28:06.376Z [DEBUG] provider.stdio: received EOF, stopping recv loop: err="rpc error: code = Unavailable desc = error reading from server: EOF" 2025-12-10T14:28:06.377Z [INFO] provider: plugin process exited: plugin=.terraform/providers/registry.terraform.io/outscale/outscale/1.0.0-dev/linux_amd64/terraform-provider-outscale id=627994
2025-12-10T14:28:06.377Z [DEBUG] provider: plugin exited
2025-12-10T14:28:06.377Z [DEBUG] building apply graph to check for errors
2025-12-10T14:28:06.377Z [DEBUG] ProviderTransformer: "outscale_access_key.access_key01" (*terraform.NodeApplyableResourceInstance) needs provider["registry.terraform.io/outscale/outscale"]
2025-12-10T14:28:06.377Z [DEBUG] ProviderTransformer: "outscale_access_key.access_key01 (expand)" (*terraform.nodeExpandApplyableResource) needs provider["registry.terraform.io/outscale/outscale"]
2025-12-10T14:28:06.377Z [DEBUG] pruning unused provider["registry.terraform.io/scottwinkler/shell"]
2025-12-10T14:28:06.377Z [DEBUG] ReferenceTransformer: "outscale_access_key.access_key01 (expand)" references: []
2025-12-10T14:28:06.377Z [DEBUG] ReferenceTransformer: "var.region" references: []
2025-12-10T14:28:06.377Z [DEBUG] ReferenceTransformer: "var.image_id" references: []
2025-12-10T14:28:06.377Z [DEBUG] ReferenceTransformer: "var.vm_type" references: []
2025-12-10T14:28:06.377Z [DEBUG] ReferenceTransformer: "outscale_access_key.access_key01" references: []
2025-12-10T14:28:06.377Z [DEBUG] ReferenceTransformer: "provider[\"registry.terraform.io/outscale/outscale\"]" references: []
2025-12-10T14:28:06.377Z [INFO] backend/local: plan operation completed
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:
# outscale_access_key.access_key01 will be updated in-place
~ resource "outscale_access_key" "access_key01" {
~ access_key_id = "..." -> (known after apply)
~ creation_date = "2025-12-10T13:42:18.616+0000" -> (known after apply)
~ id = "..." -> (known after apply)
~ last_modification_date = "2025-12-10T13:52:27.167+0000" -> (known after apply)
~ request_id = "4d12e358-607f-4ab5-a56a-eb76652c81de" -> (known after apply)
~ secret_key = "..." -> (known after apply)
# (2 unchanged attributes hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.