Description
Currently, the RequiresReplace planmodifier doesn't respect StringSemanticEquals.
...this may not be a bug, it may be working as intended. If so, would be nice to have a new plan modifier - something like stringplanmodifier.RequiresReplaceIfSemanticallyDifferent()
Thankyou!
Module version
github.com/hashicorp/terraform-plugin-framework v1.15.0
Relevant provider source code
I have defined a custom value TrimmedStringValue
which is essentially identical to StringValue
, but it ignores leading/trailing whitespace when semantic equality is checked.
// trimmed_string_custom_type.go
var _ basetypes.StringValuable = TrimmedStringValue{}
var _ basetypes.StringValuableWithSemanticEquals = TrimmedStringValue{}
type TrimmedStringValue struct {
basetypes.StringValue
}
// ...
func (v TrimmedStringValue) StringSemanticEquals(ctx context.Context, newValuable basetypes.StringValuable) (bool, diag.Diagnostics) {
var diags diag.Diagnostics
// The framework should always pass the correct value type, but always check
newValue, ok := newValuable.(TrimmedStringValue)
if !ok {
diags.AddError("Semantic Equality Check Error", "...")
return false, diags
}
// ignore leading/training whitespace
priorString := strings.TrimSpace(v.StringValue.ValueString())
newString := strings.TrimSpace(newValue.ValueString())
// If the strings are equivalent, keep the prior value
return priorString == newString, diags
}
I then implement in in my schema with CustomType: TrimmedStringType{}
and plan modifier stringplanmodifier.RequiresReplace()
.
// ssh_key_resource.go
func (r *sshKeyResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = resources.SshKeyResourceSchema(ctx)
// ...
publicKeyDescription := "The public key in OpenSSH \"authorized_keys\" format. This should be a valid public key, " +
"such as one generated by `ssh-keygen`."
resp.Schema.Attributes["public_key"] = schema.StringAttribute{
Required: true,
Description: publicKeyDescription,
MarkdownDescription: publicKeyDescription,
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
CustomType: TrimmedStringType{}, // Ignore leading/trailing whitespace
}
}
Terraform Configuration Files
I then test this in my unit tests
First test step:
resource "binarylane_ssh_key" "test" {
name = "tf-test-key-resource-test"
public_key = "` + publicKey + `"
}
Second test step:
resource "binarylane_ssh_key" "test" {
name = "tf-test-key-resource-test"
# Test public key with a newline to ensure it does not get recreated
public_key = <<EOT
` + publicKey + `
EOT
}`
I am expecting an empty plan, with
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectEmptyPlan(),
},
},
Debug Output
Not a full debug output, but should sufficient explain the issue:
=== NAME TestSshKeyResource
ssh_key_resource_test.go:22: Step 4/4 error: Pre-apply plan check(s) failed:
expected empty plan, but binarylane_ssh_key.test has planned action(s): [delete create]
Expected Behavior
Plan is empty due to semantic equality.
Actual Behavior
Plan wants to delete & create resource.
Steps to Reproduce
- Implement custom type and value with
StringSemanticEquals
- Define schema with plan modifier
RequiresReplace
- Modify string while maintaining semantic equality