From 79932f2bb909d82ac3e8f806a3a6131ca3d3b598 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Thu, 3 Oct 2024 14:27:48 -0400 Subject: [PATCH 01/10] Update `terraform-plugin-framework` dependency --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 1ebd9191..d42c3222 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,8 @@ toolchain go1.22.7 require ( github.com/google/go-cmp v0.6.0 - github.com/hashicorp/terraform-plugin-framework v1.12.0 - github.com/hashicorp/terraform-plugin-go v0.24.0 + github.com/hashicorp/terraform-plugin-framework v1.12.1-0.20241003175841-23a025bad206 + github.com/hashicorp/terraform-plugin-go v0.24.1-0.20240926180758-adf4d559d7d4 ) require ( @@ -19,5 +19,5 @@ require ( github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/go.sum b/go.sum index 3521a4c7..0eb0cc29 100644 --- a/go.sum +++ b/go.sum @@ -7,10 +7,10 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/terraform-plugin-framework v1.12.0 h1:7HKaueHPaikX5/7cbC1r9d1m12iYHY+FlNZEGxQ42CQ= -github.com/hashicorp/terraform-plugin-framework v1.12.0/go.mod h1:N/IOQ2uYjW60Jp39Cp3mw7I/OpC/GfZ0385R0YibmkE= -github.com/hashicorp/terraform-plugin-go v0.24.0 h1:2WpHhginCdVhFIrWHxDEg6RBn3YaWzR2o6qUeIEat2U= -github.com/hashicorp/terraform-plugin-go v0.24.0/go.mod h1:tUQ53lAsOyYSckFGEefGC5C8BAaO0ENqzFd3bQeuYQg= +github.com/hashicorp/terraform-plugin-framework v1.12.1-0.20241003175841-23a025bad206 h1:ZDrQ1nD2MOdkJ8MMCK4hf9SXLTG4UZrgPohns4XlK3A= +github.com/hashicorp/terraform-plugin-framework v1.12.1-0.20241003175841-23a025bad206/go.mod h1:FJCkncD8LZN2VNEKar09gElTKT5u6s9lRaMEn9jYwsg= +github.com/hashicorp/terraform-plugin-go v0.24.1-0.20240926180758-adf4d559d7d4 h1:IrFJDqDFuJ7Soy0n/M2oiAn/oH9Cvfi+MMKJh1s2M6I= +github.com/hashicorp/terraform-plugin-go v0.24.1-0.20240926180758-adf4d559d7d4/go.mod h1:zoSM9LyEFI4iVkDeKXgwBHV0uTuIIXydtK1fy9J4wBA= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -35,8 +35,8 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 1a904011ff59b1ca4a6e6d24f117b4e31f0e8a56 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Mon, 7 Oct 2024 16:25:38 -0400 Subject: [PATCH 02/10] Implement `PreferWriteOnlyAttribute` resource validator --- .../prefer_write_only_attribute.go | 80 ++++++++++ ...refer_write_only_attribute_example_test.go | 23 +++ .../prefer_write_only_attribute_test.go | 149 ++++++++++++++++++ 3 files changed, 252 insertions(+) create mode 100644 resourcevalidator/prefer_write_only_attribute.go create mode 100644 resourcevalidator/prefer_write_only_attribute_example_test.go create mode 100644 resourcevalidator/prefer_write_only_attribute_test.go diff --git a/resourcevalidator/prefer_write_only_attribute.go b/resourcevalidator/prefer_write_only_attribute.go new file mode 100644 index 00000000..812340c7 --- /dev/null +++ b/resourcevalidator/prefer_write_only_attribute.go @@ -0,0 +1,80 @@ +package resourcevalidator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +// PreferWriteOnlyAttribute returns a warning if the Terraform client supports +// write-only attributes, and the old attribute value is not null. +func PreferWriteOnlyAttribute(oldAttribute path.Expression, writeOnlyAttribute path.Expression) resource.ConfigValidator { + return preferWriteOnlyAttributeValidator{ + oldAttribute: oldAttribute, + writeOnlyAttribute: writeOnlyAttribute, + } +} + +var _ resource.ConfigValidator = preferWriteOnlyAttributeValidator{} + +// preferWriteOnlyAttributeValidator implements the validator. +type preferWriteOnlyAttributeValidator struct { + oldAttribute path.Expression + writeOnlyAttribute path.Expression +} + +// Description describes the validation in plain text formatting. +func (v preferWriteOnlyAttributeValidator) Description(ctx context.Context) string { + return fmt.Sprintf("The write-only attribute %s should be prefered over the regular attribute %s", v.writeOnlyAttribute, v.oldAttribute) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v preferWriteOnlyAttributeValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateResource performs the validation. +func (v preferWriteOnlyAttributeValidator) ValidateResource(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + + if !req.ClientCapabilities.WriteOnlyAttributesAllowed { + return + } + + oldAttributePaths, oldAttributeDiags := req.Config.PathMatches(ctx, v.oldAttribute) + if oldAttributeDiags.HasError() { + resp.Diagnostics.Append(oldAttributeDiags...) + return + } + + _, writeOnlyAttributeDiags := req.Config.PathMatches(ctx, v.writeOnlyAttribute) + if writeOnlyAttributeDiags.HasError() { + resp.Diagnostics.Append(writeOnlyAttributeDiags...) + return + } + + for _, mp := range oldAttributePaths { + // Get the value + var matchedValue attr.Value + diags := req.Config.GetAttribute(ctx, mp, &matchedValue) + resp.Diagnostics.Append(diags...) + if diags.HasError() { + continue + } + + if matchedValue.IsUnknown() { + return + } + + if matchedValue.IsNull() { + continue + } + + resp.Diagnostics.AddAttributeWarning(mp, + "Available Write-Only Attribute Alternative", + fmt.Sprintf("The attribute has a WriteOnly version %s available. "+ + "Use the WriteOnly version of the attribute when possible.", v.writeOnlyAttribute.String())) + } +} diff --git a/resourcevalidator/prefer_write_only_attribute_example_test.go b/resourcevalidator/prefer_write_only_attribute_example_test.go new file mode 100644 index 00000000..6e61bde6 --- /dev/null +++ b/resourcevalidator/prefer_write_only_attribute_example_test.go @@ -0,0 +1,23 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resourcevalidator_test + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + + "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" +) + +func ExamplePreferWriteOnlyAttribute() { + // Used inside a resource.Resource type ConfigValidators method + _ = []resource.ConfigValidator{ + // Throws a warning diagnostic if the resource supports write-only + // attributes and the oldAttribute has a known value. + resourcevalidator.PreferWriteOnlyAttribute( + path.MatchRoot("oldAttribute"), + path.MatchRoot("writeOnlyAttribute"), + ), + } +} diff --git a/resourcevalidator/prefer_write_only_attribute_test.go b/resourcevalidator/prefer_write_only_attribute_test.go new file mode 100644 index 00000000..6d635b26 --- /dev/null +++ b/resourcevalidator/prefer_write_only_attribute_test.go @@ -0,0 +1,149 @@ +package resourcevalidator_test + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" +) + +func TestPreferWriteOnlyAttribute(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + validators []resource.ConfigValidator + req resource.ValidateConfigRequest + expected *resource.ValidateConfigResponse + }{ + "valid-warning-diag": { + validators: []resource.ConfigValidator{ + resourcevalidator.PreferWriteOnlyAttribute( + path.MatchRoot("oldAttribute1"), + path.MatchRoot("writeOnlyAttribute1"), + ), + resourcevalidator.PreferWriteOnlyAttribute( + path.MatchRoot("oldAttribute2"), + path.MatchRoot("writeOnlyAttribute2"), + ), + }, + req: resource.ValidateConfigRequest{ + ClientCapabilities: resource.ValidateConfigClientCapabilities{WriteOnlyAttributesAllowed: true}, + Config: tfsdk.Config{ + Schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "oldAttribute1": schema.StringAttribute{ + Optional: true, + }, + "writeOnlyAttribute1": schema.StringAttribute{ + Optional: true, + }, + "oldAttribute2": schema.StringAttribute{ + Optional: true, + }, + "writeOnlyAttribute2": schema.StringAttribute{ + Optional: true, + }, + }, + }, + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "oldAttribute1": tftypes.String, + "writeOnlyAttribute1": tftypes.String, + "oldAttribute2": tftypes.String, + "writeOnlyAttribute2": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "oldAttribute1": tftypes.NewValue(tftypes.String, nil), + "writeOnlyAttribute1": tftypes.NewValue(tftypes.String, nil), + "oldAttribute2": tftypes.NewValue(tftypes.String, "test-value"), + "writeOnlyAttribute2": tftypes.NewValue(tftypes.String, nil), + }, + ), + }, + }, + expected: &resource.ValidateConfigResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewAttributeWarningDiagnostic(path.Root("oldAttribute2"), + "Available Write-Only Attribute Alternative", + "The attribute has a WriteOnly version writeOnlyAttribute2 available. "+ + "Use the WriteOnly version of the attribute when possible."), + }, + }, + }, + "valid-no-client-capabilities": { + validators: []resource.ConfigValidator{ + resourcevalidator.PreferWriteOnlyAttribute( + path.MatchRoot("oldAttribute1"), + path.MatchRoot("writeOnlyAttribute1"), + ), + resourcevalidator.PreferWriteOnlyAttribute( + path.MatchRoot("oldAttribute2"), + path.MatchRoot("writeOnlyAttribute2"), + ), + }, + req: resource.ValidateConfigRequest{ + Config: tfsdk.Config{ + Schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "oldAttribute1": schema.StringAttribute{ + Optional: true, + }, + "writeOnlyAttribute1": schema.StringAttribute{ + Optional: true, + }, + "oldAttribute2": schema.StringAttribute{ + Optional: true, + }, + "writeOnlyAttribute2": schema.StringAttribute{ + Optional: true, + }, + }, + }, + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "oldAttribute1": tftypes.String, + "writeOnlyAttribute1": tftypes.String, + "oldAttribute2": tftypes.String, + "writeOnlyAttribute2": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "oldAttribute1": tftypes.NewValue(tftypes.String, nil), + "writeOnlyAttribute1": tftypes.NewValue(tftypes.String, nil), + "oldAttribute2": tftypes.NewValue(tftypes.String, "test-value"), + "writeOnlyAttribute2": tftypes.NewValue(tftypes.String, nil), + }, + ), + }, + }, + expected: &resource.ValidateConfigResponse{}, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := &resource.ValidateConfigResponse{} + + resourcevalidator.AnyWithAllWarnings(testCase.validators...).ValidateResource(context.Background(), testCase.req, got) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} From f48686118e5e8bda8d37fc0fc0149c68836b03d7 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Mon, 7 Oct 2024 17:32:35 -0400 Subject: [PATCH 03/10] Implement `PreferWriteOnlyAttribute` schema validator and attribute validator implementations --- boolvalidator/prefer_write_only_attribute.go | 28 ++ ...refer_write_only_attribute_example_test.go | 34 +++ .../prefer_write_only_attribute.go | 28 ++ ...refer_write_only_attribute_example_test.go | 34 +++ .../prefer_write_only_attribute.go | 28 ++ ...refer_write_only_attribute_example_test.go | 34 +++ int32validator/prefer_write_only_attribute.go | 28 ++ ...refer_write_only_attribute_example_test.go | 34 +++ int64validator/prefer_write_only_attribute.go | 28 ++ ...refer_write_only_attribute_example_test.go | 34 +++ .../prefer_write_only_attribute.go | 250 ++++++++++++++++++ .../prefer_write_only_attribute_test.go | 190 +++++++++++++ listvalidator/prefer_write_only_attribute.go | 28 ++ ...refer_write_only_attribute_example_test.go | 37 +++ mapvalidator/prefer_write_only_attribute.go | 28 ++ ...refer_write_only_attribute_example_test.go | 36 +++ .../prefer_write_only_attribute.go | 28 ++ ...refer_write_only_attribute_example_test.go | 34 +++ .../prefer_write_only_attribute.go | 28 ++ ...refer_write_only_attribute_example_test.go | 34 +++ setvalidator/prefer_write_only_attribute.go | 28 ++ ...refer_write_only_attribute_example_test.go | 36 +++ .../prefer_write_only_attribute.go | 28 ++ ...refer_write_only_attribute_example_test.go | 34 +++ 24 files changed, 1129 insertions(+) create mode 100644 boolvalidator/prefer_write_only_attribute.go create mode 100644 boolvalidator/prefer_write_only_attribute_example_test.go create mode 100644 float32validator/prefer_write_only_attribute.go create mode 100644 float32validator/prefer_write_only_attribute_example_test.go create mode 100644 float64validator/prefer_write_only_attribute.go create mode 100644 float64validator/prefer_write_only_attribute_example_test.go create mode 100644 int32validator/prefer_write_only_attribute.go create mode 100644 int32validator/prefer_write_only_attribute_example_test.go create mode 100644 int64validator/prefer_write_only_attribute.go create mode 100644 int64validator/prefer_write_only_attribute_example_test.go create mode 100644 internal/schemavalidator/prefer_write_only_attribute.go create mode 100644 internal/schemavalidator/prefer_write_only_attribute_test.go create mode 100644 listvalidator/prefer_write_only_attribute.go create mode 100644 listvalidator/prefer_write_only_attribute_example_test.go create mode 100644 mapvalidator/prefer_write_only_attribute.go create mode 100644 mapvalidator/prefer_write_only_attribute_example_test.go create mode 100644 numbervalidator/prefer_write_only_attribute.go create mode 100644 numbervalidator/prefer_write_only_attribute_example_test.go create mode 100644 objectvalidator/prefer_write_only_attribute.go create mode 100644 objectvalidator/prefer_write_only_attribute_example_test.go create mode 100644 setvalidator/prefer_write_only_attribute.go create mode 100644 setvalidator/prefer_write_only_attribute_example_test.go create mode 100644 stringvalidator/prefer_write_only_attribute.go create mode 100644 stringvalidator/prefer_write_only_attribute_example_test.go diff --git a/boolvalidator/prefer_write_only_attribute.go b/boolvalidator/prefer_write_only_attribute.go new file mode 100644 index 00000000..712c0a91 --- /dev/null +++ b/boolvalidator/prefer_write_only_attribute.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package boolvalidator + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" +) + +// PreferWriteOnlyAttribute returns a warning if the Terraform client supports +// write-only attributes, and the attribute that the validator is applied to has a value. +// It takes in a path.Expression that represents the write-only attribute schema location, +// and the warning message will indicate that the write-only attribute should be preferred. +// +// This validator should only be used for resource attributes as other schema types do not +// support write-only attributes. +// +// This implements the validation logic declaratively within the schema. +// Refer to [resourcevalidator.PreferWriteOnlyAttribute] +// for declaring this type of validation outside the schema definition. +func PreferWriteOnlyAttribute(writeOnlyAttribute path.Expression) validator.Bool { + return schemavalidator.PreferWriteOnlyAttribute{ + WriteOnlyAttribute: writeOnlyAttribute, + } +} diff --git a/boolvalidator/prefer_write_only_attribute_example_test.go b/boolvalidator/prefer_write_only_attribute_example_test.go new file mode 100644 index 00000000..05b2ac0c --- /dev/null +++ b/boolvalidator/prefer_write_only_attribute_example_test.go @@ -0,0 +1,34 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package boolvalidator_test + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator" +) + +func ExamplePreferWriteOnlyAttribute() { + // Used within a Schema method of a Resource + _ = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attr": schema.BoolAttribute{ + Optional: true, + Validators: []validator.Bool{ + // Throws a warning diagnostic encouraging practitioners to use + // write_only_attr if example_attr has a value. + boolvalidator.PreferWriteOnlyAttribute( + path.MatchRoot("write_only_attr"), + ), + }, + }, + "write_only_attr": schema.BoolAttribute{ + WriteOnly: true, + Optional: true, + }, + }, + } +} diff --git a/float32validator/prefer_write_only_attribute.go b/float32validator/prefer_write_only_attribute.go new file mode 100644 index 00000000..b5ff8670 --- /dev/null +++ b/float32validator/prefer_write_only_attribute.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package float32validator + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" +) + +// PreferWriteOnlyAttribute returns a warning if the Terraform client supports +// write-only attributes, and the attribute that the validator is applied to has a value. +// It takes in a path.Expression that represents the write-only attribute schema location, +// and the warning message will indicate that the write-only attribute should be preferred. +// +// This validator should only be used for resource attributes as other schema types do not +// support write-only attributes. +// +// This implements the validation logic declaratively within the schema. +// Refer to [resourcevalidator.PreferWriteOnlyAttribute] +// for declaring this type of validation outside the schema definition. +func PreferWriteOnlyAttribute(writeOnlyAttribute path.Expression) validator.Float32 { + return schemavalidator.PreferWriteOnlyAttribute{ + WriteOnlyAttribute: writeOnlyAttribute, + } +} diff --git a/float32validator/prefer_write_only_attribute_example_test.go b/float32validator/prefer_write_only_attribute_example_test.go new file mode 100644 index 00000000..50754649 --- /dev/null +++ b/float32validator/prefer_write_only_attribute_example_test.go @@ -0,0 +1,34 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package float32validator_test + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/float32validator" +) + +func ExamplePreferWriteOnlyAttribute() { + // Used within a Schema method of a Resource + _ = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attr": schema.Float32Attribute{ + Optional: true, + Validators: []validator.Float32{ + // Throws a warning diagnostic encouraging practitioners to use + // write_only_attr if example_attr has a value. + float32validator.PreferWriteOnlyAttribute( + path.MatchRoot("write_only_attr"), + ), + }, + }, + "write_only_attr": schema.Float32Attribute{ + WriteOnly: true, + Optional: true, + }, + }, + } +} diff --git a/float64validator/prefer_write_only_attribute.go b/float64validator/prefer_write_only_attribute.go new file mode 100644 index 00000000..8882cf73 --- /dev/null +++ b/float64validator/prefer_write_only_attribute.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package float64validator + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" +) + +// PreferWriteOnlyAttribute returns a warning if the Terraform client supports +// write-only attributes, and the attribute that the validator is applied to has a value. +// It takes in a path.Expression that represents the write-only attribute schema location, +// and the warning message will indicate that the write-only attribute should be preferred. +// +// This validator should only be used for resource attributes as other schema types do not +// support write-only attributes. +// +// This implements the validation logic declaratively within the schema. +// Refer to [resourcevalidator.PreferWriteOnlyAttribute] +// for declaring this type of validation outside the schema definition. +func PreferWriteOnlyAttribute(writeOnlyAttribute path.Expression) validator.Float64 { + return schemavalidator.PreferWriteOnlyAttribute{ + WriteOnlyAttribute: writeOnlyAttribute, + } +} diff --git a/float64validator/prefer_write_only_attribute_example_test.go b/float64validator/prefer_write_only_attribute_example_test.go new file mode 100644 index 00000000..e3c2e541 --- /dev/null +++ b/float64validator/prefer_write_only_attribute_example_test.go @@ -0,0 +1,34 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package float64validator_test + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/float64validator" +) + +func ExamplePreferWriteOnlyAttribute() { + // Used within a Schema method of a Resource + _ = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attr": schema.Float64Attribute{ + Optional: true, + Validators: []validator.Float64{ + // Throws a warning diagnostic encouraging practitioners to use + // write_only_attr if example_attr has a value. + float64validator.PreferWriteOnlyAttribute( + path.MatchRoot("write_only_attr"), + ), + }, + }, + "write_only_attr": schema.Float64Attribute{ + WriteOnly: true, + Optional: true, + }, + }, + } +} diff --git a/int32validator/prefer_write_only_attribute.go b/int32validator/prefer_write_only_attribute.go new file mode 100644 index 00000000..c064220d --- /dev/null +++ b/int32validator/prefer_write_only_attribute.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int32validator + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" +) + +// PreferWriteOnlyAttribute returns a warning if the Terraform client supports +// write-only attributes, and the attribute that the validator is applied to has a value. +// It takes in a path.Expression that represents the write-only attribute schema location, +// and the warning message will indicate that the write-only attribute should be preferred. +// +// This validator should only be used for resource attributes as other schema types do not +// support write-only attributes. +// +// This implements the validation logic declaratively within the schema. +// Refer to [resourcevalidator.PreferWriteOnlyAttribute] +// for declaring this type of validation outside the schema definition. +func PreferWriteOnlyAttribute(writeOnlyAttribute path.Expression) validator.Int32 { + return schemavalidator.PreferWriteOnlyAttribute{ + WriteOnlyAttribute: writeOnlyAttribute, + } +} diff --git a/int32validator/prefer_write_only_attribute_example_test.go b/int32validator/prefer_write_only_attribute_example_test.go new file mode 100644 index 00000000..3ada7a75 --- /dev/null +++ b/int32validator/prefer_write_only_attribute_example_test.go @@ -0,0 +1,34 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int32validator_test + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" +) + +func ExamplePreferWriteOnlyAttribute() { + // Used within a Schema method of a Resource + _ = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attr": schema.Int32Attribute{ + Optional: true, + Validators: []validator.Int32{ + // Throws a warning diagnostic encouraging practitioners to use + // write_only_attr if example_attr has a value. + int32validator.PreferWriteOnlyAttribute( + path.MatchRoot("write_only_attr"), + ), + }, + }, + "write_only_attr": schema.Int32Attribute{ + WriteOnly: true, + Optional: true, + }, + }, + } +} diff --git a/int64validator/prefer_write_only_attribute.go b/int64validator/prefer_write_only_attribute.go new file mode 100644 index 00000000..3161328f --- /dev/null +++ b/int64validator/prefer_write_only_attribute.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int64validator + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" +) + +// PreferWriteOnlyAttribute returns a warning if the Terraform client supports +// write-only attributes, and the attribute that the validator is applied to has a value. +// It takes in a path.Expression that represents the write-only attribute schema location, +// and the warning message will indicate that the write-only attribute should be preferred. +// +// This validator should only be used for resource attributes as other schema types do not +// support write-only attributes. +// +// This implements the validation logic declaratively within the schema. +// Refer to [resourcevalidator.PreferWriteOnlyAttribute] +// for declaring this type of validation outside the schema definition. +func PreferWriteOnlyAttribute(writeOnlyAttribute path.Expression) validator.Int64 { + return schemavalidator.PreferWriteOnlyAttribute{ + WriteOnlyAttribute: writeOnlyAttribute, + } +} diff --git a/int64validator/prefer_write_only_attribute_example_test.go b/int64validator/prefer_write_only_attribute_example_test.go new file mode 100644 index 00000000..9c894abb --- /dev/null +++ b/int64validator/prefer_write_only_attribute_example_test.go @@ -0,0 +1,34 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int64validator_test + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" +) + +func ExamplePreferWriteOnlyAttribute() { + // Used within a Schema method of a Resource + _ = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attr": schema.Int64Attribute{ + Optional: true, + Validators: []validator.Int64{ + // Throws a warning diagnostic encouraging practitioners to use + // write_only_attr if example_attr has a value. + int64validator.PreferWriteOnlyAttribute( + path.MatchRoot("write_only_attr"), + ), + }, + }, + "write_only_attr": schema.Int64Attribute{ + WriteOnly: true, + Optional: true, + }, + }, + } +} diff --git a/internal/schemavalidator/prefer_write_only_attribute.go b/internal/schemavalidator/prefer_write_only_attribute.go new file mode 100644 index 00000000..aa097f7e --- /dev/null +++ b/internal/schemavalidator/prefer_write_only_attribute.go @@ -0,0 +1,250 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schemavalidator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" +) + +// This type of validator must satisfy all types. +var ( + _ validator.Bool = PreferWriteOnlyAttribute{} + _ validator.Float32 = PreferWriteOnlyAttribute{} + _ validator.Float64 = PreferWriteOnlyAttribute{} + _ validator.Int32 = PreferWriteOnlyAttribute{} + _ validator.Int64 = PreferWriteOnlyAttribute{} + _ validator.List = PreferWriteOnlyAttribute{} + _ validator.Map = PreferWriteOnlyAttribute{} + _ validator.Number = PreferWriteOnlyAttribute{} + _ validator.Object = PreferWriteOnlyAttribute{} + _ validator.Set = PreferWriteOnlyAttribute{} + _ validator.String = PreferWriteOnlyAttribute{} +) + +// PreferWriteOnlyAttribute is the underlying struct implementing ExactlyOneOf. +type PreferWriteOnlyAttribute struct { + WriteOnlyAttribute path.Expression +} + +type PreferWriteOnlyAttributeRequest struct { + ClientCapabilities validator.ValidateSchemaClientCapabilities + Config tfsdk.Config + ConfigValue attr.Value + Path path.Path + PathExpression path.Expression +} + +type PreferWriteOnlyAttributeResponse struct { + Diagnostics diag.Diagnostics +} + +func (av PreferWriteOnlyAttribute) Description(ctx context.Context) string { + return av.MarkdownDescription(ctx) +} + +func (av PreferWriteOnlyAttribute) MarkdownDescription(_ context.Context) string { + return fmt.Sprintf("The write-only attribute %s should be prefered over this attribute", av.WriteOnlyAttribute) +} + +func (av PreferWriteOnlyAttribute) Validate(ctx context.Context, req PreferWriteOnlyAttributeRequest, resp *PreferWriteOnlyAttributeResponse) { + if !req.ClientCapabilities.WriteOnlyAttributesAllowed { + return + } + + oldAttributePaths, oldAttributeDiags := req.Config.PathMatches(ctx, req.PathExpression) + if oldAttributeDiags.HasError() { + resp.Diagnostics.Append(oldAttributeDiags...) + return + } + + _, writeOnlyAttributeDiags := req.Config.PathMatches(ctx, av.WriteOnlyAttribute) + if writeOnlyAttributeDiags.HasError() { + resp.Diagnostics.Append(writeOnlyAttributeDiags...) + return + } + + for _, mp := range oldAttributePaths { + // Get the value + var matchedValue attr.Value + diags := req.Config.GetAttribute(ctx, mp, &matchedValue) + resp.Diagnostics.Append(diags...) + if diags.HasError() { + continue + } + + if matchedValue.IsUnknown() { + return + } + + if matchedValue.IsNull() { + continue + } + + resp.Diagnostics.AddAttributeWarning(mp, + "Available Write-Only Attribute Alternative", + fmt.Sprintf("This attribute has a WriteOnly version %s available. "+ + "Use the WriteOnly version of the attribute when possible.", av.WriteOnlyAttribute.String())) + } +} + +func (av PreferWriteOnlyAttribute) ValidateBool(ctx context.Context, req validator.BoolRequest, resp *validator.BoolResponse) { + validateReq := PreferWriteOnlyAttributeRequest{ + Config: req.Config, + ConfigValue: req.ConfigValue, + Path: req.Path, + PathExpression: req.PathExpression, + } + validateResp := &PreferWriteOnlyAttributeResponse{} + + av.Validate(ctx, validateReq, validateResp) + + resp.Diagnostics.Append(validateResp.Diagnostics...) +} + +func (av PreferWriteOnlyAttribute) ValidateFloat32(ctx context.Context, req validator.Float32Request, resp *validator.Float32Response) { + validateReq := PreferWriteOnlyAttributeRequest{ + Config: req.Config, + ConfigValue: req.ConfigValue, + Path: req.Path, + PathExpression: req.PathExpression, + } + validateResp := &PreferWriteOnlyAttributeResponse{} + + av.Validate(ctx, validateReq, validateResp) + + resp.Diagnostics.Append(validateResp.Diagnostics...) +} + +func (av PreferWriteOnlyAttribute) ValidateFloat64(ctx context.Context, req validator.Float64Request, resp *validator.Float64Response) { + validateReq := PreferWriteOnlyAttributeRequest{ + Config: req.Config, + ConfigValue: req.ConfigValue, + Path: req.Path, + PathExpression: req.PathExpression, + } + validateResp := &PreferWriteOnlyAttributeResponse{} + + av.Validate(ctx, validateReq, validateResp) + + resp.Diagnostics.Append(validateResp.Diagnostics...) +} + +func (av PreferWriteOnlyAttribute) ValidateInt32(ctx context.Context, req validator.Int32Request, resp *validator.Int32Response) { + validateReq := PreferWriteOnlyAttributeRequest{ + Config: req.Config, + ConfigValue: req.ConfigValue, + Path: req.Path, + PathExpression: req.PathExpression, + } + validateResp := &PreferWriteOnlyAttributeResponse{} + + av.Validate(ctx, validateReq, validateResp) + + resp.Diagnostics.Append(validateResp.Diagnostics...) +} + +func (av PreferWriteOnlyAttribute) ValidateInt64(ctx context.Context, req validator.Int64Request, resp *validator.Int64Response) { + validateReq := PreferWriteOnlyAttributeRequest{ + Config: req.Config, + ConfigValue: req.ConfigValue, + Path: req.Path, + PathExpression: req.PathExpression, + } + validateResp := &PreferWriteOnlyAttributeResponse{} + + av.Validate(ctx, validateReq, validateResp) + + resp.Diagnostics.Append(validateResp.Diagnostics...) +} + +func (av PreferWriteOnlyAttribute) ValidateList(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) { + validateReq := PreferWriteOnlyAttributeRequest{ + Config: req.Config, + ConfigValue: req.ConfigValue, + Path: req.Path, + PathExpression: req.PathExpression, + } + validateResp := &PreferWriteOnlyAttributeResponse{} + + av.Validate(ctx, validateReq, validateResp) + + resp.Diagnostics.Append(validateResp.Diagnostics...) +} + +func (av PreferWriteOnlyAttribute) ValidateMap(ctx context.Context, req validator.MapRequest, resp *validator.MapResponse) { + validateReq := PreferWriteOnlyAttributeRequest{ + Config: req.Config, + ConfigValue: req.ConfigValue, + Path: req.Path, + PathExpression: req.PathExpression, + } + validateResp := &PreferWriteOnlyAttributeResponse{} + + av.Validate(ctx, validateReq, validateResp) + + resp.Diagnostics.Append(validateResp.Diagnostics...) +} + +func (av PreferWriteOnlyAttribute) ValidateNumber(ctx context.Context, req validator.NumberRequest, resp *validator.NumberResponse) { + validateReq := PreferWriteOnlyAttributeRequest{ + Config: req.Config, + ConfigValue: req.ConfigValue, + Path: req.Path, + PathExpression: req.PathExpression, + } + validateResp := &PreferWriteOnlyAttributeResponse{} + + av.Validate(ctx, validateReq, validateResp) + + resp.Diagnostics.Append(validateResp.Diagnostics...) +} + +func (av PreferWriteOnlyAttribute) ValidateObject(ctx context.Context, req validator.ObjectRequest, resp *validator.ObjectResponse) { + validateReq := PreferWriteOnlyAttributeRequest{ + Config: req.Config, + ConfigValue: req.ConfigValue, + Path: req.Path, + PathExpression: req.PathExpression, + } + validateResp := &PreferWriteOnlyAttributeResponse{} + + av.Validate(ctx, validateReq, validateResp) + + resp.Diagnostics.Append(validateResp.Diagnostics...) +} + +func (av PreferWriteOnlyAttribute) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { + validateReq := PreferWriteOnlyAttributeRequest{ + Config: req.Config, + ConfigValue: req.ConfigValue, + Path: req.Path, + PathExpression: req.PathExpression, + } + validateResp := &PreferWriteOnlyAttributeResponse{} + + av.Validate(ctx, validateReq, validateResp) + + resp.Diagnostics.Append(validateResp.Diagnostics...) +} + +func (av PreferWriteOnlyAttribute) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + validateReq := PreferWriteOnlyAttributeRequest{ + Config: req.Config, + ConfigValue: req.ConfigValue, + Path: req.Path, + PathExpression: req.PathExpression, + } + validateResp := &PreferWriteOnlyAttributeResponse{} + + av.Validate(ctx, validateReq, validateResp) + + resp.Diagnostics.Append(validateResp.Diagnostics...) +} diff --git a/internal/schemavalidator/prefer_write_only_attribute_test.go b/internal/schemavalidator/prefer_write_only_attribute_test.go new file mode 100644 index 00000000..e5cdd570 --- /dev/null +++ b/internal/schemavalidator/prefer_write_only_attribute_test.go @@ -0,0 +1,190 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schemavalidator_test + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" +) + +func TestPreferWriteOnlyAttribute(t *testing.T) { + t.Parallel() + + type testCase struct { + req schemavalidator.PreferWriteOnlyAttributeRequest + in path.Expression + expWarnings int + expErrors int + } + + testCases := map[string]testCase{ + "base": { + req: schemavalidator.PreferWriteOnlyAttributeRequest{ + ClientCapabilities: validator.ValidateSchemaClientCapabilities{WriteOnlyAttributesAllowed: true}, + ConfigValue: types.StringValue("oldAttribute value"), + Path: path.Root("oldAttribute"), + PathExpression: path.MatchRoot("oldAttribute"), + Config: tfsdk.Config{ + Schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "writeOnlyAttribute": schema.StringAttribute{WriteOnly: true}, + "oldAttribute": schema.StringAttribute{}, + }, + }, + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "writeOnlyAttribute": tftypes.String, + "oldAttribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "writeOnlyAttribute": tftypes.NewValue(tftypes.String, nil), + "oldAttribute": tftypes.NewValue(tftypes.String, "oldAttribute value"), + }), + }, + }, + in: path.MatchRoot("writeOnlyAttribute"), + expWarnings: 1, + }, + "no-write-only-capability": { + req: schemavalidator.PreferWriteOnlyAttributeRequest{ + ConfigValue: types.StringValue("oldAttribute value"), + Path: path.Root("oldAttribute"), + PathExpression: path.MatchRoot("oldAttribute"), + Config: tfsdk.Config{ + Schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "writeOnlyAttribute": schema.StringAttribute{WriteOnly: true}, + "oldAttribute": schema.StringAttribute{}, + }, + }, + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "writeOnlyAttribute": tftypes.String, + "oldAttribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "writeOnlyAttribute": tftypes.NewValue(tftypes.String, nil), + "oldAttribute": tftypes.NewValue(tftypes.String, "oldAttribute value"), + }), + }, + }, + in: path.MatchRoot("writeOnlyAttribute"), + }, + "old-attribute-is-null": { + req: schemavalidator.PreferWriteOnlyAttributeRequest{ + ConfigValue: types.StringNull(), + Path: path.Root("oldAttribute"), + PathExpression: path.MatchRoot("oldAttribute"), + Config: tfsdk.Config{ + Schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "writeOnlyAttribute": schema.StringAttribute{WriteOnly: true}, + "oldAttribute": schema.StringAttribute{}, + }, + }, + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "writeOnlyAttribute": tftypes.String, + "oldAttribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "writeOnlyAttribute": tftypes.NewValue(tftypes.String, nil), + "oldAttribute": tftypes.NewValue(tftypes.String, nil), + }), + }, + }, + in: path.MatchRoot("writeOnlyAttribute"), + }, + "old-attribute-is-unknown": { + req: schemavalidator.PreferWriteOnlyAttributeRequest{ + ClientCapabilities: validator.ValidateSchemaClientCapabilities{WriteOnlyAttributesAllowed: true}, + ConfigValue: types.StringUnknown(), + Path: path.Root("oldAttribute"), + PathExpression: path.MatchRoot("oldAttribute"), + Config: tfsdk.Config{ + Schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "writeOnlyAttribute": schema.StringAttribute{WriteOnly: true}, + "oldAttribute": schema.StringAttribute{}, + }, + }, + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "writeOnlyAttribute": tftypes.String, + "oldAttribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "writeOnlyAttribute": tftypes.NewValue(tftypes.String, nil), + "oldAttribute": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + }), + }, + }, + in: path.MatchRoot("writeOnlyAttribute"), + }, + "matches-no-attribute-in-schema": { + req: schemavalidator.PreferWriteOnlyAttributeRequest{ + ClientCapabilities: validator.ValidateSchemaClientCapabilities{WriteOnlyAttributesAllowed: true}, + ConfigValue: types.StringValue("oldAttribute value"), + Path: path.Root("oldAttribute"), + PathExpression: path.MatchRoot("oldAttribute"), + Config: tfsdk.Config{ + Schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "writeOnlyAttribute": schema.StringAttribute{WriteOnly: true}, + "oldAttribute": schema.StringAttribute{}, + }, + }, + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "writeOnlyAttribute": tftypes.String, + "oldAttribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "writeOnlyAttribute": tftypes.NewValue(tftypes.String, nil), + "oldAttribute": tftypes.NewValue(tftypes.String, "oldAttribute value"), + }), + }, + }, + in: path.MatchRoot("writeOnlyAttribute2"), + expErrors: 1, + }, + } + + for name, test := range testCases { + name, test := name, test + t.Run(name, func(t *testing.T) { + t.Parallel() + res := &schemavalidator.PreferWriteOnlyAttributeResponse{} + + schemavalidator.PreferWriteOnlyAttribute{ + WriteOnlyAttribute: test.in, + }.Validate(context.TODO(), test.req, res) + + if test.expWarnings != res.Diagnostics.WarningsCount() { + t.Fatalf("expected no warining(s), got %d: %v", res.Diagnostics.WarningsCount(), res.Diagnostics) + } + + if test.expWarnings > 0 && test.expWarnings != res.Diagnostics.WarningsCount() { + t.Fatalf("expected %d warning(s), got %d: %v", test.expWarnings, res.Diagnostics.WarningsCount(), res.Diagnostics) + } + + if test.expErrors == 0 && res.Diagnostics.HasError() { + t.Fatalf("expected no error(s), got %d: %v", res.Diagnostics.WarningsCount(), res.Diagnostics) + } + + if test.expErrors > 0 && test.expErrors != res.Diagnostics.ErrorsCount() { + t.Fatalf("expected %d error(s), got %d: %v", test.expErrors, res.Diagnostics.Errors(), res.Diagnostics) + } + }) + } +} diff --git a/listvalidator/prefer_write_only_attribute.go b/listvalidator/prefer_write_only_attribute.go new file mode 100644 index 00000000..50aba3b4 --- /dev/null +++ b/listvalidator/prefer_write_only_attribute.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package listvalidator + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" +) + +// PreferWriteOnlyAttribute returns a warning if the Terraform client supports +// write-only attributes, and the attribute that the validator is applied to has a value. +// It takes in a path.Expression that represents the write-only attribute schema location, +// and the warning message will indicate that the write-only attribute should be preferred. +// +// This validator should only be used for resource attributes as other schema types do not +// support write-only attributes. +// +// This implements the validation logic declaratively within the schema. +// Refer to [resourcevalidator.PreferWriteOnlyAttribute] +// for declaring this type of validation outside the schema definition. +func PreferWriteOnlyAttribute(writeOnlyAttribute path.Expression) validator.List { + return schemavalidator.PreferWriteOnlyAttribute{ + WriteOnlyAttribute: writeOnlyAttribute, + } +} diff --git a/listvalidator/prefer_write_only_attribute_example_test.go b/listvalidator/prefer_write_only_attribute_example_test.go new file mode 100644 index 00000000..dc81b3f6 --- /dev/null +++ b/listvalidator/prefer_write_only_attribute_example_test.go @@ -0,0 +1,37 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package listvalidator_test + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" +) + +func ExamplePreferWriteOnlyAttribute() { + // Used within a Schema method of a Resource + _ = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attr": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Validators: []validator.List{ + // Throws a warning diagnostic encouraging practitioners to use + // write_only_attr if example_attr has a value. + listvalidator.PreferWriteOnlyAttribute( + path.MatchRoot("write_only_attr"), + ), + }, + }, + "write_only_attr": schema.ListAttribute{ + ElementType: types.StringType, + WriteOnly: true, + Optional: true, + }, + }, + } +} diff --git a/mapvalidator/prefer_write_only_attribute.go b/mapvalidator/prefer_write_only_attribute.go new file mode 100644 index 00000000..8343ef41 --- /dev/null +++ b/mapvalidator/prefer_write_only_attribute.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package mapvalidator + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" +) + +// PreferWriteOnlyAttribute returns a warning if the Terraform client supports +// write-only attributes, and the attribute that the validator is applied to has a value. +// It takes in a path.Expression that represents the write-only attribute schema location, +// and the warning message will indicate that the write-only attribute should be preferred. +// +// This validator should only be used for resource attributes as other schema types do not +// support write-only attributes. +// +// This implements the validation logic declaratively within the schema. +// Refer to [resourcevalidator.PreferWriteOnlyAttribute] +// for declaring this type of validation outside the schema definition. +func PreferWriteOnlyAttribute(writeOnlyAttribute path.Expression) validator.Map { + return schemavalidator.PreferWriteOnlyAttribute{ + WriteOnlyAttribute: writeOnlyAttribute, + } +} diff --git a/mapvalidator/prefer_write_only_attribute_example_test.go b/mapvalidator/prefer_write_only_attribute_example_test.go new file mode 100644 index 00000000..a99a4155 --- /dev/null +++ b/mapvalidator/prefer_write_only_attribute_example_test.go @@ -0,0 +1,36 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package mapvalidator_test + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator" +) + +func ExamplePreferWriteOnlyAttribute() { + // Used within a Schema method of a Resource + _ = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attr": schema.MapAttribute{ + ElementType: types.StringType, + Optional: true, + Validators: []validator.Map{ + // Throws a warning diagnostic encouraging practitioners to use + // write_only_attr if example_attr has a value. + mapvalidator.PreferWriteOnlyAttribute( + path.MatchRoot("write_only_attr"), + ), + }, + }, + "write_only_attr": schema.MapAttribute{ + WriteOnly: true, + Optional: true, + }, + }, + } +} diff --git a/numbervalidator/prefer_write_only_attribute.go b/numbervalidator/prefer_write_only_attribute.go new file mode 100644 index 00000000..61285843 --- /dev/null +++ b/numbervalidator/prefer_write_only_attribute.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package numbervalidator + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" +) + +// PreferWriteOnlyAttribute returns a warning if the Terraform client supports +// write-only attributes, and the attribute that the validator is applied to has a value. +// It takes in a path.Expression that represents the write-only attribute schema location, +// and the warning message will indicate that the write-only attribute should be preferred. +// +// This validator should only be used for resource attributes as other schema types do not +// support write-only attributes. +// +// This implements the validation logic declaratively within the schema. +// Refer to [resourcevalidator.PreferWriteOnlyAttribute] +// for declaring this type of validation outside the schema definition. +func PreferWriteOnlyAttribute(writeOnlyAttribute path.Expression) validator.Number { + return schemavalidator.PreferWriteOnlyAttribute{ + WriteOnlyAttribute: writeOnlyAttribute, + } +} diff --git a/numbervalidator/prefer_write_only_attribute_example_test.go b/numbervalidator/prefer_write_only_attribute_example_test.go new file mode 100644 index 00000000..ea1cb436 --- /dev/null +++ b/numbervalidator/prefer_write_only_attribute_example_test.go @@ -0,0 +1,34 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package numbervalidator_test + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/numbervalidator" +) + +func ExamplePreferWriteOnlyAttribute() { + // Used within a Schema method of a Resource + _ = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attr": schema.NumberAttribute{ + Optional: true, + Validators: []validator.Number{ + // Throws a warning diagnostic encouraging practitioners to use + // write_only_attr if example_attr has a value. + numbervalidator.PreferWriteOnlyAttribute( + path.MatchRoot("write_only_attr"), + ), + }, + }, + "write_only_attr": schema.NumberAttribute{ + WriteOnly: true, + Optional: true, + }, + }, + } +} diff --git a/objectvalidator/prefer_write_only_attribute.go b/objectvalidator/prefer_write_only_attribute.go new file mode 100644 index 00000000..71a8ac41 --- /dev/null +++ b/objectvalidator/prefer_write_only_attribute.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package objectvalidator + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" +) + +// PreferWriteOnlyAttribute returns a warning if the Terraform client supports +// write-only attributes, and the attribute that the validator is applied to has a value. +// It takes in a path.Expression that represents the write-only attribute schema location, +// and the warning message will indicate that the write-only attribute should be preferred. +// +// This validator should only be used for resource attributes as other schema types do not +// support write-only attributes. +// +// This implements the validation logic declaratively within the schema. +// Refer to [resourcevalidator.PreferWriteOnlyAttribute] +// for declaring this type of validation outside the schema definition. +func PreferWriteOnlyAttribute(writeOnlyAttribute path.Expression) validator.Object { + return schemavalidator.PreferWriteOnlyAttribute{ + WriteOnlyAttribute: writeOnlyAttribute, + } +} diff --git a/objectvalidator/prefer_write_only_attribute_example_test.go b/objectvalidator/prefer_write_only_attribute_example_test.go new file mode 100644 index 00000000..4546771a --- /dev/null +++ b/objectvalidator/prefer_write_only_attribute_example_test.go @@ -0,0 +1,34 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package objectvalidator_test + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator" +) + +func ExamplePreferWriteOnlyAttribute() { + // Used within a Schema method of a Resource + _ = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attr": schema.ObjectAttribute{ + Optional: true, + Validators: []validator.Object{ + // Throws a warning diagnostic encouraging practitioners to use + // write_only_attr if example_attr has a value. + objectvalidator.PreferWriteOnlyAttribute( + path.MatchRoot("write_only_attr"), + ), + }, + }, + "write_only_attr": schema.ObjectAttribute{ + WriteOnly: true, + Optional: true, + }, + }, + } +} diff --git a/setvalidator/prefer_write_only_attribute.go b/setvalidator/prefer_write_only_attribute.go new file mode 100644 index 00000000..fd8a9388 --- /dev/null +++ b/setvalidator/prefer_write_only_attribute.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" +) + +// PreferWriteOnlyAttribute returns a warning if the Terraform client supports +// write-only attributes, and the attribute that the validator is applied to has a value. +// It takes in a path.Expression that represents the write-only attribute schema location, +// and the warning message will indicate that the write-only attribute should be preferred. +// +// This validator should only be used for resource attributes as other schema types do not +// support write-only attributes. +// +// This implements the validation logic declaratively within the schema. +// Refer to [resourcevalidator.PreferWriteOnlyAttribute] +// for declaring this type of validation outside the schema definition. +func PreferWriteOnlyAttribute(writeOnlyAttribute path.Expression) validator.Set { + return schemavalidator.PreferWriteOnlyAttribute{ + WriteOnlyAttribute: writeOnlyAttribute, + } +} diff --git a/setvalidator/prefer_write_only_attribute_example_test.go b/setvalidator/prefer_write_only_attribute_example_test.go new file mode 100644 index 00000000..df3a6068 --- /dev/null +++ b/setvalidator/prefer_write_only_attribute_example_test.go @@ -0,0 +1,36 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator_test + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" +) + +func ExamplePreferWriteOnlyAttribute() { + // Used within a Schema method of a Resource + _ = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attr": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Validators: []validator.Set{ + // Throws a warning diagnostic encouraging practitioners to use + // write_only_attr if example_attr has a value. + setvalidator.PreferWriteOnlyAttribute( + path.MatchRoot("write_only_attr"), + ), + }, + }, + "write_only_attr": schema.SetAttribute{ + WriteOnly: true, + Optional: true, + }, + }, + } +} diff --git a/stringvalidator/prefer_write_only_attribute.go b/stringvalidator/prefer_write_only_attribute.go new file mode 100644 index 00000000..920546de --- /dev/null +++ b/stringvalidator/prefer_write_only_attribute.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" +) + +// PreferWriteOnlyAttribute returns a warning if the Terraform client supports +// write-only attributes, and the attribute that the validator is applied to has a value. +// It takes in a path.Expression that represents the write-only attribute schema location, +// and the warning message will indicate that the write-only attribute should be preferred. +// +// This validator should only be used for resource attributes as other schema types do not +// support write-only attributes. +// +// This implements the validation logic declaratively within the schema. +// Refer to [resourcevalidator.PreferWriteOnlyAttribute] +// for declaring this type of validation outside the schema definition. +func PreferWriteOnlyAttribute(writeOnlyAttribute path.Expression) validator.String { + return schemavalidator.PreferWriteOnlyAttribute{ + WriteOnlyAttribute: writeOnlyAttribute, + } +} diff --git a/stringvalidator/prefer_write_only_attribute_example_test.go b/stringvalidator/prefer_write_only_attribute_example_test.go new file mode 100644 index 00000000..5ecf8d0c --- /dev/null +++ b/stringvalidator/prefer_write_only_attribute_example_test.go @@ -0,0 +1,34 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator_test + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" +) + +func ExamplePreferWriteOnlyAttribute() { + // Used within a Schema method of a Resource + _ = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attr": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + // Throws a warning diagnostic encouraging practitioners to use + // write_only_attr if example_attr has a value. + stringvalidator.PreferWriteOnlyAttribute( + path.MatchRoot("write_only_attr"), + ), + }, + }, + "write_only_attr": schema.StringAttribute{ + WriteOnly: true, + Optional: true, + }, + }, + } +} From 256e81de6a72b40b63793bf319c79e710c7dc549 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Thu, 6 Feb 2025 11:52:53 -0500 Subject: [PATCH 04/10] Add `PreferWriteOnlyAttribute()` to `dynamicvalidator` package --- .../prefer_write_only_attribute.go | 28 +++++++++++++++ ...refer_write_only_attribute_example_test.go | 34 +++++++++++++++++++ .../prefer_write_only_attribute.go | 14 ++++++++ 3 files changed, 76 insertions(+) create mode 100644 dynamicvalidator/prefer_write_only_attribute.go create mode 100644 dynamicvalidator/prefer_write_only_attribute_example_test.go diff --git a/dynamicvalidator/prefer_write_only_attribute.go b/dynamicvalidator/prefer_write_only_attribute.go new file mode 100644 index 00000000..811d0f5b --- /dev/null +++ b/dynamicvalidator/prefer_write_only_attribute.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package dynamicvalidator + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" +) + +// PreferWriteOnlyAttribute returns a warning if the Terraform client supports +// write-only attributes, and the attribute that the validator is applied to has a value. +// It takes in a path.Expression that represents the write-only attribute schema location, +// and the warning message will indicate that the write-only attribute should be preferred. +// +// This validator should only be used for resource attributes as other schema types do not +// support write-only attributes. +// +// This implements the validation logic declaratively within the schema. +// Refer to [resourcevalidator.PreferWriteOnlyAttribute] +// for declaring this type of validation outside the schema definition. +func PreferWriteOnlyAttribute(writeOnlyAttribute path.Expression) validator.Dynamic { + return schemavalidator.PreferWriteOnlyAttribute{ + WriteOnlyAttribute: writeOnlyAttribute, + } +} diff --git a/dynamicvalidator/prefer_write_only_attribute_example_test.go b/dynamicvalidator/prefer_write_only_attribute_example_test.go new file mode 100644 index 00000000..62491f62 --- /dev/null +++ b/dynamicvalidator/prefer_write_only_attribute_example_test.go @@ -0,0 +1,34 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package dynamicvalidator_test + +import ( + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/dynamicvalidator" +) + +func ExamplePreferWriteOnlyAttribute() { + // Used within a Schema method of a Resource + _ = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attr": schema.DynamicAttribute{ + Optional: true, + Validators: []validator.Dynamic{ + // Throws a warning diagnostic encouraging practitioners to use + // write_only_attr if example_attr has a value. + dynamicvalidator.PreferWriteOnlyAttribute( + path.MatchRoot("write_only_attr"), + ), + }, + }, + "write_only_attr": schema.DynamicAttribute{ + WriteOnly: true, + Optional: true, + }, + }, + } +} diff --git a/internal/schemavalidator/prefer_write_only_attribute.go b/internal/schemavalidator/prefer_write_only_attribute.go index aa097f7e..adc79bf0 100644 --- a/internal/schemavalidator/prefer_write_only_attribute.go +++ b/internal/schemavalidator/prefer_write_only_attribute.go @@ -109,6 +109,20 @@ func (av PreferWriteOnlyAttribute) ValidateBool(ctx context.Context, req validat resp.Diagnostics.Append(validateResp.Diagnostics...) } +func (av PreferWriteOnlyAttribute) ValidateDynamic(ctx context.Context, req validator.DynamicRequest, resp *validator.DynamicResponse) { + validateReq := PreferWriteOnlyAttributeRequest{ + Config: req.Config, + ConfigValue: req.ConfigValue, + Path: req.Path, + PathExpression: req.PathExpression, + } + validateResp := &PreferWriteOnlyAttributeResponse{} + + av.Validate(ctx, validateReq, validateResp) + + resp.Diagnostics.Append(validateResp.Diagnostics...) +} + func (av PreferWriteOnlyAttribute) ValidateFloat32(ctx context.Context, req validator.Float32Request, resp *validator.Float32Response) { validateReq := PreferWriteOnlyAttributeRequest{ Config: req.Config, From c573312153cf7dc90c1b5f3f865811611fa440fd Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Thu, 6 Feb 2025 11:54:46 -0500 Subject: [PATCH 05/10] Fix misspells --- go.sum | 2 -- internal/schemavalidator/prefer_write_only_attribute.go | 2 +- resourcevalidator/prefer_write_only_attribute.go | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/go.sum b/go.sum index 50df47e3..eaba773d 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,6 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/terraform-plugin-framework v1.13.0 h1:8OTG4+oZUfKgnfTdPTJwZ532Bh2BobF4H+yBiYJ/scw= -github.com/hashicorp/terraform-plugin-framework v1.13.0/go.mod h1:j64rwMGpgM3NYXTKuxrCnyubQb/4VKldEKlcG8cvmjU= github.com/hashicorp/terraform-plugin-framework v1.13.1-0.20250204221559-2b72dc0a12ab h1:x1GxE9ztKndo/6CPnkA8yBfmj8z13063U0pY6ayKNFo= github.com/hashicorp/terraform-plugin-framework v1.13.1-0.20250204221559-2b72dc0a12ab/go.mod h1:qBKUqe1lv1NZcsO5pBjiKd5YuNDUeqvV1w8w5df/8WI= github.com/hashicorp/terraform-plugin-go v0.26.0 h1:cuIzCv4qwigug3OS7iKhpGAbZTiypAfFQmw8aE65O2M= diff --git a/internal/schemavalidator/prefer_write_only_attribute.go b/internal/schemavalidator/prefer_write_only_attribute.go index adc79bf0..278f3ba5 100644 --- a/internal/schemavalidator/prefer_write_only_attribute.go +++ b/internal/schemavalidator/prefer_write_only_attribute.go @@ -51,7 +51,7 @@ func (av PreferWriteOnlyAttribute) Description(ctx context.Context) string { } func (av PreferWriteOnlyAttribute) MarkdownDescription(_ context.Context) string { - return fmt.Sprintf("The write-only attribute %s should be prefered over this attribute", av.WriteOnlyAttribute) + return fmt.Sprintf("The write-only attribute %s should be preferred over this attribute", av.WriteOnlyAttribute) } func (av PreferWriteOnlyAttribute) Validate(ctx context.Context, req PreferWriteOnlyAttributeRequest, resp *PreferWriteOnlyAttributeResponse) { diff --git a/resourcevalidator/prefer_write_only_attribute.go b/resourcevalidator/prefer_write_only_attribute.go index 812340c7..22e984c1 100644 --- a/resourcevalidator/prefer_write_only_attribute.go +++ b/resourcevalidator/prefer_write_only_attribute.go @@ -28,7 +28,7 @@ type preferWriteOnlyAttributeValidator struct { // Description describes the validation in plain text formatting. func (v preferWriteOnlyAttributeValidator) Description(ctx context.Context) string { - return fmt.Sprintf("The write-only attribute %s should be prefered over the regular attribute %s", v.writeOnlyAttribute, v.oldAttribute) + return fmt.Sprintf("The write-only attribute %s should be preferred over the regular attribute %s", v.writeOnlyAttribute, v.oldAttribute) } // MarkdownDescription describes the validation in Markdown formatting. From afeec7dd890590a587d68c0ed3c48758fe9861f8 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Thu, 6 Feb 2025 11:55:50 -0500 Subject: [PATCH 06/10] Add copywrite headers --- resourcevalidator/prefer_write_only_attribute.go | 3 +++ resourcevalidator/prefer_write_only_attribute_test.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/resourcevalidator/prefer_write_only_attribute.go b/resourcevalidator/prefer_write_only_attribute.go index 22e984c1..a2dcbec4 100644 --- a/resourcevalidator/prefer_write_only_attribute.go +++ b/resourcevalidator/prefer_write_only_attribute.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package resourcevalidator import ( diff --git a/resourcevalidator/prefer_write_only_attribute_test.go b/resourcevalidator/prefer_write_only_attribute_test.go index 6d635b26..6c1983d0 100644 --- a/resourcevalidator/prefer_write_only_attribute_test.go +++ b/resourcevalidator/prefer_write_only_attribute_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package resourcevalidator_test import ( From 87f8480ab21ac126f5a1c7c1f1e6d08d69453616 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Thu, 6 Feb 2025 12:17:24 -0500 Subject: [PATCH 07/10] Add changelog entries --- .changes/unreleased/FEATURES-20250206-120903.yaml | 5 +++++ .changes/unreleased/FEATURES-20250206-121130.yaml | 5 +++++ .changes/unreleased/FEATURES-20250206-121159.yaml | 5 +++++ .changes/unreleased/FEATURES-20250206-121224.yaml | 5 +++++ .changes/unreleased/FEATURES-20250206-121243.yaml | 5 +++++ .changes/unreleased/FEATURES-20250206-121308.yaml | 5 +++++ .changes/unreleased/FEATURES-20250206-121402.yaml | 5 +++++ .changes/unreleased/FEATURES-20250206-121418.yaml | 5 +++++ .changes/unreleased/FEATURES-20250206-121440.yaml | 5 +++++ .changes/unreleased/FEATURES-20250206-121500.yaml | 5 +++++ .changes/unreleased/FEATURES-20250206-121519.yaml | 5 +++++ .changes/unreleased/FEATURES-20250206-121533.yaml | 5 +++++ .changes/unreleased/FEATURES-20250206-121553.yaml | 5 +++++ 13 files changed, 65 insertions(+) create mode 100644 .changes/unreleased/FEATURES-20250206-120903.yaml create mode 100644 .changes/unreleased/FEATURES-20250206-121130.yaml create mode 100644 .changes/unreleased/FEATURES-20250206-121159.yaml create mode 100644 .changes/unreleased/FEATURES-20250206-121224.yaml create mode 100644 .changes/unreleased/FEATURES-20250206-121243.yaml create mode 100644 .changes/unreleased/FEATURES-20250206-121308.yaml create mode 100644 .changes/unreleased/FEATURES-20250206-121402.yaml create mode 100644 .changes/unreleased/FEATURES-20250206-121418.yaml create mode 100644 .changes/unreleased/FEATURES-20250206-121440.yaml create mode 100644 .changes/unreleased/FEATURES-20250206-121500.yaml create mode 100644 .changes/unreleased/FEATURES-20250206-121519.yaml create mode 100644 .changes/unreleased/FEATURES-20250206-121533.yaml create mode 100644 .changes/unreleased/FEATURES-20250206-121553.yaml diff --git a/.changes/unreleased/FEATURES-20250206-120903.yaml b/.changes/unreleased/FEATURES-20250206-120903.yaml new file mode 100644 index 00000000..41c57026 --- /dev/null +++ b/.changes/unreleased/FEATURES-20250206-120903.yaml @@ -0,0 +1,5 @@ +kind: FEATURES +body: 'boolvalidator: Added `PreferWriteOnlyAttribute` validator' +time: 2025-02-06T12:09:03.733715-05:00 +custom: + Issue: "263" diff --git a/.changes/unreleased/FEATURES-20250206-121130.yaml b/.changes/unreleased/FEATURES-20250206-121130.yaml new file mode 100644 index 00000000..8b43f1c2 --- /dev/null +++ b/.changes/unreleased/FEATURES-20250206-121130.yaml @@ -0,0 +1,5 @@ +kind: FEATURES +body: 'dynamicvalidator: Added `PreferWriteOnlyAttribute` validator' +time: 2025-02-06T12:11:30.572285-05:00 +custom: + Issue: "263" diff --git a/.changes/unreleased/FEATURES-20250206-121159.yaml b/.changes/unreleased/FEATURES-20250206-121159.yaml new file mode 100644 index 00000000..9e283b24 --- /dev/null +++ b/.changes/unreleased/FEATURES-20250206-121159.yaml @@ -0,0 +1,5 @@ +kind: FEATURES +body: 'float32validator: Added `PreferWriteOnlyAttribute` validator' +time: 2025-02-06T12:11:59.137017-05:00 +custom: + Issue: "263" diff --git a/.changes/unreleased/FEATURES-20250206-121224.yaml b/.changes/unreleased/FEATURES-20250206-121224.yaml new file mode 100644 index 00000000..c843d96c --- /dev/null +++ b/.changes/unreleased/FEATURES-20250206-121224.yaml @@ -0,0 +1,5 @@ +kind: FEATURES +body: 'float64validator: Added `PreferWriteOnlyAttribute` validator' +time: 2025-02-06T12:12:24.396457-05:00 +custom: + Issue: "263" diff --git a/.changes/unreleased/FEATURES-20250206-121243.yaml b/.changes/unreleased/FEATURES-20250206-121243.yaml new file mode 100644 index 00000000..f02e8e6b --- /dev/null +++ b/.changes/unreleased/FEATURES-20250206-121243.yaml @@ -0,0 +1,5 @@ +kind: FEATURES +body: 'int32validator: Added `PreferWriteOnlyAttribute` validator' +time: 2025-02-06T12:12:43.521669-05:00 +custom: + Issue: "263" diff --git a/.changes/unreleased/FEATURES-20250206-121308.yaml b/.changes/unreleased/FEATURES-20250206-121308.yaml new file mode 100644 index 00000000..d8f1d584 --- /dev/null +++ b/.changes/unreleased/FEATURES-20250206-121308.yaml @@ -0,0 +1,5 @@ +kind: FEATURES +body: 'int64validator: Added `PreferWriteOnlyAttribute` validator' +time: 2025-02-06T12:13:08.483387-05:00 +custom: + Issue: "263" diff --git a/.changes/unreleased/FEATURES-20250206-121402.yaml b/.changes/unreleased/FEATURES-20250206-121402.yaml new file mode 100644 index 00000000..a9bb8677 --- /dev/null +++ b/.changes/unreleased/FEATURES-20250206-121402.yaml @@ -0,0 +1,5 @@ +kind: FEATURES +body: 'listvalidator: Added `PreferWriteOnlyAttribute` validator' +time: 2025-02-06T12:14:02.302221-05:00 +custom: + Issue: "263" diff --git a/.changes/unreleased/FEATURES-20250206-121418.yaml b/.changes/unreleased/FEATURES-20250206-121418.yaml new file mode 100644 index 00000000..908ef680 --- /dev/null +++ b/.changes/unreleased/FEATURES-20250206-121418.yaml @@ -0,0 +1,5 @@ +kind: FEATURES +body: 'mapvalidator: Added `PreferWriteOnlyAttribute` validator' +time: 2025-02-06T12:14:18.324953-05:00 +custom: + Issue: "263" diff --git a/.changes/unreleased/FEATURES-20250206-121440.yaml b/.changes/unreleased/FEATURES-20250206-121440.yaml new file mode 100644 index 00000000..0b132279 --- /dev/null +++ b/.changes/unreleased/FEATURES-20250206-121440.yaml @@ -0,0 +1,5 @@ +kind: FEATURES +body: 'numbervalidator: Added `PreferWriteOnlyAttribute` validator' +time: 2025-02-06T12:14:40.942642-05:00 +custom: + Issue: "263" diff --git a/.changes/unreleased/FEATURES-20250206-121500.yaml b/.changes/unreleased/FEATURES-20250206-121500.yaml new file mode 100644 index 00000000..bd4d7222 --- /dev/null +++ b/.changes/unreleased/FEATURES-20250206-121500.yaml @@ -0,0 +1,5 @@ +kind: FEATURES +body: 'objectvalidator: Added `PreferWriteOnlyAttribute` validator' +time: 2025-02-06T12:15:00.176757-05:00 +custom: + Issue: "263" diff --git a/.changes/unreleased/FEATURES-20250206-121519.yaml b/.changes/unreleased/FEATURES-20250206-121519.yaml new file mode 100644 index 00000000..d519065a --- /dev/null +++ b/.changes/unreleased/FEATURES-20250206-121519.yaml @@ -0,0 +1,5 @@ +kind: FEATURES +body: 'resourcevalidator: Added `PreferWriteOnlyAttribute` validator' +time: 2025-02-06T12:15:19.618622-05:00 +custom: + Issue: "263" diff --git a/.changes/unreleased/FEATURES-20250206-121533.yaml b/.changes/unreleased/FEATURES-20250206-121533.yaml new file mode 100644 index 00000000..81104d33 --- /dev/null +++ b/.changes/unreleased/FEATURES-20250206-121533.yaml @@ -0,0 +1,5 @@ +kind: FEATURES +body: 'setvalidator: Added `PreferWriteOnlyAttribute` validator' +time: 2025-02-06T12:15:33.908556-05:00 +custom: + Issue: "263" diff --git a/.changes/unreleased/FEATURES-20250206-121553.yaml b/.changes/unreleased/FEATURES-20250206-121553.yaml new file mode 100644 index 00000000..c39aa29a --- /dev/null +++ b/.changes/unreleased/FEATURES-20250206-121553.yaml @@ -0,0 +1,5 @@ +kind: FEATURES +body: 'stringvalidator: Added `PreferWriteOnlyAttribute` validator' +time: 2025-02-06T12:15:53.337804-05:00 +custom: + Issue: "263" From c9a10f4fb77be8340f8ad0f5dd334c5ea3c2936e Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 18 Feb 2025 18:05:01 -0500 Subject: [PATCH 08/10] Add `WriteOnly` field to tests --- resourcevalidator/prefer_write_only_attribute_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/resourcevalidator/prefer_write_only_attribute_test.go b/resourcevalidator/prefer_write_only_attribute_test.go index 6c1983d0..4cff9495 100644 --- a/resourcevalidator/prefer_write_only_attribute_test.go +++ b/resourcevalidator/prefer_write_only_attribute_test.go @@ -46,13 +46,15 @@ func TestPreferWriteOnlyAttribute(t *testing.T) { Optional: true, }, "writeOnlyAttribute1": schema.StringAttribute{ - Optional: true, + Optional: true, + WriteOnly: true, }, "oldAttribute2": schema.StringAttribute{ Optional: true, }, "writeOnlyAttribute2": schema.StringAttribute{ - Optional: true, + Optional: true, + WriteOnly: true, }, }, }, From c36d263d95298714fcd54ccf97d78798144764e7 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 18 Feb 2025 18:07:44 -0500 Subject: [PATCH 09/10] Remove set validator --- .../prefer_write_only_attribute.go | 15 -------- setvalidator/prefer_write_only_attribute.go | 28 --------------- ...refer_write_only_attribute_example_test.go | 36 ------------------- 3 files changed, 79 deletions(-) delete mode 100644 setvalidator/prefer_write_only_attribute.go delete mode 100644 setvalidator/prefer_write_only_attribute_example_test.go diff --git a/internal/schemavalidator/prefer_write_only_attribute.go b/internal/schemavalidator/prefer_write_only_attribute.go index 278f3ba5..fa58406d 100644 --- a/internal/schemavalidator/prefer_write_only_attribute.go +++ b/internal/schemavalidator/prefer_write_only_attribute.go @@ -25,7 +25,6 @@ var ( _ validator.Map = PreferWriteOnlyAttribute{} _ validator.Number = PreferWriteOnlyAttribute{} _ validator.Object = PreferWriteOnlyAttribute{} - _ validator.Set = PreferWriteOnlyAttribute{} _ validator.String = PreferWriteOnlyAttribute{} ) @@ -235,20 +234,6 @@ func (av PreferWriteOnlyAttribute) ValidateObject(ctx context.Context, req valid resp.Diagnostics.Append(validateResp.Diagnostics...) } -func (av PreferWriteOnlyAttribute) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { - validateReq := PreferWriteOnlyAttributeRequest{ - Config: req.Config, - ConfigValue: req.ConfigValue, - Path: req.Path, - PathExpression: req.PathExpression, - } - validateResp := &PreferWriteOnlyAttributeResponse{} - - av.Validate(ctx, validateReq, validateResp) - - resp.Diagnostics.Append(validateResp.Diagnostics...) -} - func (av PreferWriteOnlyAttribute) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { validateReq := PreferWriteOnlyAttributeRequest{ Config: req.Config, diff --git a/setvalidator/prefer_write_only_attribute.go b/setvalidator/prefer_write_only_attribute.go deleted file mode 100644 index fd8a9388..00000000 --- a/setvalidator/prefer_write_only_attribute.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package setvalidator - -import ( - "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" - - "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" -) - -// PreferWriteOnlyAttribute returns a warning if the Terraform client supports -// write-only attributes, and the attribute that the validator is applied to has a value. -// It takes in a path.Expression that represents the write-only attribute schema location, -// and the warning message will indicate that the write-only attribute should be preferred. -// -// This validator should only be used for resource attributes as other schema types do not -// support write-only attributes. -// -// This implements the validation logic declaratively within the schema. -// Refer to [resourcevalidator.PreferWriteOnlyAttribute] -// for declaring this type of validation outside the schema definition. -func PreferWriteOnlyAttribute(writeOnlyAttribute path.Expression) validator.Set { - return schemavalidator.PreferWriteOnlyAttribute{ - WriteOnlyAttribute: writeOnlyAttribute, - } -} diff --git a/setvalidator/prefer_write_only_attribute_example_test.go b/setvalidator/prefer_write_only_attribute_example_test.go deleted file mode 100644 index df3a6068..00000000 --- a/setvalidator/prefer_write_only_attribute_example_test.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package setvalidator_test - -import ( - "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" - - "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" -) - -func ExamplePreferWriteOnlyAttribute() { - // Used within a Schema method of a Resource - _ = schema.Schema{ - Attributes: map[string]schema.Attribute{ - "example_attr": schema.SetAttribute{ - ElementType: types.StringType, - Optional: true, - Validators: []validator.Set{ - // Throws a warning diagnostic encouraging practitioners to use - // write_only_attr if example_attr has a value. - setvalidator.PreferWriteOnlyAttribute( - path.MatchRoot("write_only_attr"), - ), - }, - }, - "write_only_attr": schema.SetAttribute{ - WriteOnly: true, - Optional: true, - }, - }, - } -} From f7fe8fd450fe71ea96d116e2f977b4fa75e39374 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 18 Feb 2025 18:39:06 -0500 Subject: [PATCH 10/10] Resolve linters --- internal/schemavalidator/prefer_write_only_attribute_test.go | 1 - resourcevalidator/prefer_write_only_attribute_test.go | 2 -- 2 files changed, 3 deletions(-) diff --git a/internal/schemavalidator/prefer_write_only_attribute_test.go b/internal/schemavalidator/prefer_write_only_attribute_test.go index e5cdd570..6584fadc 100644 --- a/internal/schemavalidator/prefer_write_only_attribute_test.go +++ b/internal/schemavalidator/prefer_write_only_attribute_test.go @@ -161,7 +161,6 @@ func TestPreferWriteOnlyAttribute(t *testing.T) { } for name, test := range testCases { - name, test := name, test t.Run(name, func(t *testing.T) { t.Parallel() res := &schemavalidator.PreferWriteOnlyAttributeResponse{} diff --git a/resourcevalidator/prefer_write_only_attribute_test.go b/resourcevalidator/prefer_write_only_attribute_test.go index 4cff9495..2256e8ea 100644 --- a/resourcevalidator/prefer_write_only_attribute_test.go +++ b/resourcevalidator/prefer_write_only_attribute_test.go @@ -137,8 +137,6 @@ func TestPreferWriteOnlyAttribute(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase - t.Run(name, func(t *testing.T) { t.Parallel()