diff --git a/.changes/0.17.0.md b/.changes/0.17.0.md new file mode 100644 index 00000000..c7236745 --- /dev/null +++ b/.changes/0.17.0.md @@ -0,0 +1,18 @@ +## 0.17.0 (February 19, 2025) + +FEATURES: + +* boolvalidator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* dynamicvalidator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* float32validator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* float64validator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* int32validator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* int64validator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* listvalidator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* mapvalidator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* numbervalidator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* objectvalidator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* resourcevalidator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* setvalidator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* stringvalidator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) + diff --git a/.github/workflows/ci-github-actions.yml b/.github/workflows/ci-github-actions.yml index 9d7e22dc..8451dde3 100644 --- a/.github/workflows/ci-github-actions.yml +++ b/.github/workflows/ci-github-actions.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 + - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 with: go-version-file: 'go.mod' - run: go install github.com/rhysd/actionlint/cmd/actionlint@latest diff --git a/.github/workflows/ci-go.yml b/.github/workflows/ci-go.yml index 7e2da688..3fa186e5 100644 --- a/.github/workflows/ci-go.yml +++ b/.github/workflows/ci-go.yml @@ -17,11 +17,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 + - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 with: go-version-file: 'go.mod' - run: go mod download - - uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1 + - uses: golangci/golangci-lint-action@2e788936b09dd82dc280e845628a40d2ba6b204c # v6.3.1 test: name: test (Go v${{ matrix.go-version }}) @@ -31,13 +31,13 @@ jobs: go-version: [ '1.23', '1.22' ] steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 + - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 with: go-version: ${{ matrix.go-version }} - run: go mod download - run: go test -coverprofile=coverage.out ./... - run: go tool cover -html=coverage.out -o coverage.html - - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: go-${{ matrix.go-version }}-coverage path: coverage.html diff --git a/.github/workflows/ci-goreleaser.yml b/.github/workflows/ci-goreleaser.yml index 2c0ae1af..2c62e130 100644 --- a/.github/workflows/ci-goreleaser.yml +++ b/.github/workflows/ci-goreleaser.yml @@ -15,9 +15,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 + - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 with: go-version-file: 'go.mod' - - uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0 + - uses: goreleaser/goreleaser-action@026299872805cb2db698e02dd7fb506a4da5122d # v6.2.0 with: args: check diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4899981c..14c71529 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -84,7 +84,7 @@ jobs: ref: ${{ inputs.versionNumber }} fetch-depth: 0 - - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 + - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 with: go-version-file: 'go.mod' @@ -93,7 +93,7 @@ jobs: cd .changes sed -e "1{/# /d;}" -e "2{/^$/d;}" ${{ needs.changelog-version.outputs.version }}.md > /tmp/release-notes.txt - - uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0 + - uses: goreleaser/goreleaser-action@026299872805cb2db698e02dd7fb506a4da5122d # v6.2.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.golangci.yml b/.golangci.yml index f8175fa9..b2c03a23 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -7,7 +7,7 @@ linters: enable: - durationcheck - errcheck - - exportloopref + - copyloopvar - forcetypeassert - gofmt - gosimple @@ -18,7 +18,7 @@ linters: - paralleltest - predeclared - staticcheck - - tenv + - usetesting - unconvert - unparam - unused diff --git a/CHANGELOG.md b/CHANGELOG.md index 18146462..84166193 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +## 0.17.0 (February 19, 2025) + +FEATURES: + +* boolvalidator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* dynamicvalidator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* float32validator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* float64validator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* int32validator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* int64validator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* listvalidator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* mapvalidator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* numbervalidator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* objectvalidator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* resourcevalidator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* setvalidator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) +* stringvalidator: Added `PreferWriteOnlyAttribute` validator ([#263](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/263)) + ## 0.16.0 (December 12, 2024) FEATURES: diff --git a/boolvalidator/equals_test.go b/boolvalidator/equals_test.go index ed2671e1..1798fbd5 100644 --- a/boolvalidator/equals_test.go +++ b/boolvalidator/equals_test.go @@ -8,10 +8,11 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator" "github.com/hashicorp/terraform-plugin-framework/function" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator" ) func TestEqualsValidator(t *testing.T) { @@ -44,7 +45,6 @@ func TestEqualsValidator(t *testing.T) { } for name, test := range testCases { - name, test := name, test t.Run(fmt.Sprintf("ValidateBool - %s", name), func(t *testing.T) { t.Parallel() 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/datasourcevalidator/all_test.go b/datasourcevalidator/all_test.go index 95e3d171..439848d1 100644 --- a/datasourcevalidator/all_test.go +++ b/datasourcevalidator/all_test.go @@ -161,7 +161,6 @@ func TestAllValidatorValidateDataSource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/datasourcevalidator/any_test.go b/datasourcevalidator/any_test.go index aeb607a8..1b801403 100644 --- a/datasourcevalidator/any_test.go +++ b/datasourcevalidator/any_test.go @@ -138,7 +138,6 @@ func TestAnyValidatorValidateDataSource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/datasourcevalidator/any_with_all_warnings_test.go b/datasourcevalidator/any_with_all_warnings_test.go index e342aee4..5ec0bebe 100644 --- a/datasourcevalidator/any_with_all_warnings_test.go +++ b/datasourcevalidator/any_with_all_warnings_test.go @@ -199,7 +199,6 @@ func TestAnyWithAllWarningsValidatorValidateDataSource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/datasourcevalidator/at_least_one_of_test.go b/datasourcevalidator/at_least_one_of_test.go index 1a7b1066..118676b2 100644 --- a/datasourcevalidator/at_least_one_of_test.go +++ b/datasourcevalidator/at_least_one_of_test.go @@ -8,13 +8,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" ) func TestAtLeastOneOf(t *testing.T) { @@ -105,7 +106,6 @@ func TestAtLeastOneOf(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/datasourcevalidator/conflicting_test.go b/datasourcevalidator/conflicting_test.go index 53975b5e..a3e5dc5d 100644 --- a/datasourcevalidator/conflicting_test.go +++ b/datasourcevalidator/conflicting_test.go @@ -8,13 +8,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" ) func TestConflicting(t *testing.T) { @@ -106,7 +107,6 @@ func TestConflicting(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/datasourcevalidator/exactly_one_of_test.go b/datasourcevalidator/exactly_one_of_test.go index 9c28a951..96f19ee1 100644 --- a/datasourcevalidator/exactly_one_of_test.go +++ b/datasourcevalidator/exactly_one_of_test.go @@ -8,13 +8,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" ) func TestExactlyOneOf(t *testing.T) { @@ -106,7 +107,6 @@ func TestExactlyOneOf(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/datasourcevalidator/required_together_test.go b/datasourcevalidator/required_together_test.go index c36505fa..1186a2ee 100644 --- a/datasourcevalidator/required_together_test.go +++ b/datasourcevalidator/required_together_test.go @@ -8,13 +8,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" ) func TestRequiredTogether(t *testing.T) { @@ -106,7 +107,6 @@ func TestRequiredTogether(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() 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/ephemeralvalidator/all_test.go b/ephemeralvalidator/all_test.go index c8b158bf..bbfbc0a9 100644 --- a/ephemeralvalidator/all_test.go +++ b/ephemeralvalidator/all_test.go @@ -161,7 +161,6 @@ func TestAllValidatorValidateEphemeralResource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/ephemeralvalidator/any_test.go b/ephemeralvalidator/any_test.go index aa990286..7d7b1e4d 100644 --- a/ephemeralvalidator/any_test.go +++ b/ephemeralvalidator/any_test.go @@ -138,7 +138,6 @@ func TestAnyValidatorValidateEphemeralResource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/ephemeralvalidator/any_with_all_warnings_test.go b/ephemeralvalidator/any_with_all_warnings_test.go index c57c7547..d7ba93a8 100644 --- a/ephemeralvalidator/any_with_all_warnings_test.go +++ b/ephemeralvalidator/any_with_all_warnings_test.go @@ -199,7 +199,6 @@ func TestAnyWithAllWarningsValidatorValidateEphemeralResource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/ephemeralvalidator/at_least_one_of_test.go b/ephemeralvalidator/at_least_one_of_test.go index 1c40aa7c..0f546d69 100644 --- a/ephemeralvalidator/at_least_one_of_test.go +++ b/ephemeralvalidator/at_least_one_of_test.go @@ -8,13 +8,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework-validators/ephemeralvalidator" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/ephemeral" "github.com/hashicorp/terraform-plugin-framework/ephemeral/schema" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework-validators/ephemeralvalidator" ) func TestAtLeastOneOf(t *testing.T) { @@ -105,7 +106,6 @@ func TestAtLeastOneOf(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/ephemeralvalidator/conflicting_test.go b/ephemeralvalidator/conflicting_test.go index b8044b18..864d5e14 100644 --- a/ephemeralvalidator/conflicting_test.go +++ b/ephemeralvalidator/conflicting_test.go @@ -8,13 +8,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework-validators/ephemeralvalidator" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/ephemeral" "github.com/hashicorp/terraform-plugin-framework/ephemeral/schema" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework-validators/ephemeralvalidator" ) func TestConflicting(t *testing.T) { @@ -106,7 +107,6 @@ func TestConflicting(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/ephemeralvalidator/exactly_one_of_test.go b/ephemeralvalidator/exactly_one_of_test.go index a205ec1f..d873ed2e 100644 --- a/ephemeralvalidator/exactly_one_of_test.go +++ b/ephemeralvalidator/exactly_one_of_test.go @@ -8,13 +8,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework-validators/ephemeralvalidator" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/ephemeral" "github.com/hashicorp/terraform-plugin-framework/ephemeral/schema" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework-validators/ephemeralvalidator" ) func TestExactlyOneOf(t *testing.T) { @@ -106,7 +107,6 @@ func TestExactlyOneOf(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/ephemeralvalidator/required_together_test.go b/ephemeralvalidator/required_together_test.go index 9dd514d2..8609f389 100644 --- a/ephemeralvalidator/required_together_test.go +++ b/ephemeralvalidator/required_together_test.go @@ -8,13 +8,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework-validators/ephemeralvalidator" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/ephemeral" "github.com/hashicorp/terraform-plugin-framework/ephemeral/schema" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework-validators/ephemeralvalidator" ) func TestRequiredTogether(t *testing.T) { @@ -106,7 +107,6 @@ func TestRequiredTogether(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/float32validator/all_test.go b/float32validator/all_test.go index f5f22cbe..6628eec6 100644 --- a/float32validator/all_test.go +++ b/float32validator/all_test.go @@ -55,7 +55,7 @@ func TestAllValidatorValidateFloat32(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Float32Request{ diff --git a/float32validator/any_test.go b/float32validator/any_test.go index 1b5b9a21..111e202d 100644 --- a/float32validator/any_test.go +++ b/float32validator/any_test.go @@ -66,7 +66,7 @@ func TestAnyValidatorValidateFloat32(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Float32Request{ diff --git a/float32validator/any_with_all_warnings_test.go b/float32validator/any_with_all_warnings_test.go index 4bacc5cb..596d4cba 100644 --- a/float32validator/any_with_all_warnings_test.go +++ b/float32validator/any_with_all_warnings_test.go @@ -67,7 +67,7 @@ func TestAnyWithAllWarningsValidatorValidateFloat32(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Float32Request{ diff --git a/float32validator/at_least_test.go b/float32validator/at_least_test.go index ea768381..3d9df1e4 100644 --- a/float32validator/at_least_test.go +++ b/float32validator/at_least_test.go @@ -53,7 +53,6 @@ func TestAtLeastValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateFloat32 - %s", name), func(t *testing.T) { t.Parallel() diff --git a/float32validator/at_most_test.go b/float32validator/at_most_test.go index b3915037..87c31599 100644 --- a/float32validator/at_most_test.go +++ b/float32validator/at_most_test.go @@ -53,7 +53,6 @@ func TestAtMostValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateFloat32 - %s", name), func(t *testing.T) { t.Parallel() diff --git a/float32validator/between_test.go b/float32validator/between_test.go index be0c11d4..defc7994 100644 --- a/float32validator/between_test.go +++ b/float32validator/between_test.go @@ -77,7 +77,6 @@ func TestBetweenValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateFloat32 - %s", name), func(t *testing.T) { t.Parallel() diff --git a/float32validator/none_of_test.go b/float32validator/none_of_test.go index 8fb8a353..569ecad4 100644 --- a/float32validator/none_of_test.go +++ b/float32validator/none_of_test.go @@ -62,7 +62,6 @@ func TestNoneOfValidator(t *testing.T) { } for name, test := range testCases { - name, test := name, test t.Run(fmt.Sprintf("ValidateFloat32 - %s", name), func(t *testing.T) { t.Parallel() diff --git a/float32validator/one_of_test.go b/float32validator/one_of_test.go index e960a040..79a49489 100644 --- a/float32validator/one_of_test.go +++ b/float32validator/one_of_test.go @@ -62,7 +62,6 @@ func TestOneOfValidator(t *testing.T) { } for name, test := range testCases { - name, test := name, test t.Run(fmt.Sprintf("ValidateFloat32 - %s", name), func(t *testing.T) { t.Parallel() 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/all_test.go b/float64validator/all_test.go index 95d8fb2e..ba562374 100644 --- a/float64validator/all_test.go +++ b/float64validator/all_test.go @@ -55,7 +55,7 @@ func TestAllValidatorValidateFloat64(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Float64Request{ diff --git a/float64validator/any_test.go b/float64validator/any_test.go index 2e9a629e..b4e0745b 100644 --- a/float64validator/any_test.go +++ b/float64validator/any_test.go @@ -66,7 +66,7 @@ func TestAnyValidatorValidateFloat64(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Float64Request{ diff --git a/float64validator/any_with_all_warnings_test.go b/float64validator/any_with_all_warnings_test.go index ecb5ba70..0c9b0541 100644 --- a/float64validator/any_with_all_warnings_test.go +++ b/float64validator/any_with_all_warnings_test.go @@ -67,7 +67,7 @@ func TestAnyWithAllWarningsValidatorValidateFloat64(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Float64Request{ diff --git a/float64validator/at_least_test.go b/float64validator/at_least_test.go index d24df495..db59c56b 100644 --- a/float64validator/at_least_test.go +++ b/float64validator/at_least_test.go @@ -53,7 +53,6 @@ func TestAtLeastValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateFloat64 - %s", name), func(t *testing.T) { t.Parallel() diff --git a/float64validator/at_most_test.go b/float64validator/at_most_test.go index 464ef945..7bfb4daf 100644 --- a/float64validator/at_most_test.go +++ b/float64validator/at_most_test.go @@ -53,7 +53,6 @@ func TestAtMostValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateFloat64 - %s", name), func(t *testing.T) { t.Parallel() diff --git a/float64validator/between_test.go b/float64validator/between_test.go index d9dbd4bc..52517141 100644 --- a/float64validator/between_test.go +++ b/float64validator/between_test.go @@ -8,11 +8,12 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-framework-validators/float64validator" "github.com/hashicorp/terraform-plugin-framework/function" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/float64validator" ) func TestBetweenValidator(t *testing.T) { @@ -76,7 +77,6 @@ func TestBetweenValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateFloat64 - %s", name), func(t *testing.T) { t.Parallel() diff --git a/float64validator/none_of_test.go b/float64validator/none_of_test.go index 01ba012a..51419cfe 100644 --- a/float64validator/none_of_test.go +++ b/float64validator/none_of_test.go @@ -62,7 +62,6 @@ func TestNoneOfValidator(t *testing.T) { } for name, test := range testCases { - name, test := name, test t.Run(fmt.Sprintf("ValidateFloat64 - %s", name), func(t *testing.T) { t.Parallel() diff --git a/float64validator/one_of_test.go b/float64validator/one_of_test.go index 3fb6d391..01a0c85f 100644 --- a/float64validator/one_of_test.go +++ b/float64validator/one_of_test.go @@ -62,7 +62,6 @@ func TestOneOfValidator(t *testing.T) { } for name, test := range testCases { - name, test := name, test t.Run(fmt.Sprintf("ValidateFloat64 - %s", name), func(t *testing.T) { t.Parallel() 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/go.mod b/go.mod index 75219a0c..ce5bf3fc 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.13.0 - github.com/hashicorp/terraform-plugin-go v0.25.0 + github.com/hashicorp/terraform-plugin-framework v1.14.0 + github.com/hashicorp/terraform-plugin-go v0.26.0 ) 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.24.0 // indirect + golang.org/x/sys v0.29.0 // indirect ) diff --git a/go.sum b/go.sum index fc181b56..23739929 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.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-go v0.25.0 h1:oi13cx7xXA6QciMcpcFi/rwA974rdTxjqEhXJjbAyks= -github.com/hashicorp/terraform-plugin-go v0.25.0/go.mod h1:+SYagMYadJP86Kvn+TGeV+ofr/R3g4/If0O5sO96MVw= +github.com/hashicorp/terraform-plugin-framework v1.14.0 h1:lsmTJqBlZ4GUabnDxj8Lsa5bmbuUKiUO3Zm9iIKSDf0= +github.com/hashicorp/terraform-plugin-framework v1.14.0/go.mod h1:xNUKmvTs6ldbwTuId5euAtg37dTxuyj3LHS3uj7BHQ4= +github.com/hashicorp/terraform-plugin-go v0.26.0 h1:cuIzCv4qwigug3OS7iKhpGAbZTiypAfFQmw8aE65O2M= +github.com/hashicorp/terraform-plugin-go v0.26.0/go.mod h1:+CXjuLDiFgqR+GcrM5a2E2Kal5t5q2jb0E3D57tTdNY= 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= @@ -37,8 +37,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc 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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.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= diff --git a/helpers/validatordiag/diag_test.go b/helpers/validatordiag/diag_test.go index b50e8ce4..e80fcd0d 100644 --- a/helpers/validatordiag/diag_test.go +++ b/helpers/validatordiag/diag_test.go @@ -34,7 +34,7 @@ func TestCapitalize(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() got := capitalize(test.input) diff --git a/int32validator/all_test.go b/int32validator/all_test.go index ff1266e5..6fe0e881 100644 --- a/int32validator/all_test.go +++ b/int32validator/all_test.go @@ -55,7 +55,7 @@ func TestAllValidatorValidateInt32(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Int32Request{ diff --git a/int32validator/any_test.go b/int32validator/any_test.go index 3bce8de2..2906afbb 100644 --- a/int32validator/any_test.go +++ b/int32validator/any_test.go @@ -66,7 +66,7 @@ func TestAnyValidatorValidateInt32(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Int32Request{ diff --git a/int32validator/any_with_all_warnings_test.go b/int32validator/any_with_all_warnings_test.go index cc84c96f..1e4b95ad 100644 --- a/int32validator/any_with_all_warnings_test.go +++ b/int32validator/any_with_all_warnings_test.go @@ -67,7 +67,7 @@ func TestAnyWithAllWarningsValidatorValidateInt32(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Int32Request{ diff --git a/int32validator/at_least_sum_of_test.go b/int32validator/at_least_sum_of_test.go index 8126c96b..824b515b 100644 --- a/int32validator/at_least_sum_of_test.go +++ b/int32validator/at_least_sum_of_test.go @@ -147,7 +147,7 @@ func TestAtLeastSumOfValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Int32Request{ diff --git a/int32validator/at_least_test.go b/int32validator/at_least_test.go index 9eeb036c..d96272a4 100644 --- a/int32validator/at_least_test.go +++ b/int32validator/at_least_test.go @@ -49,7 +49,6 @@ func TestAtLeastValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateInt32 - %s", name), func(t *testing.T) { t.Parallel() diff --git a/int32validator/at_most_sum_of_test.go b/int32validator/at_most_sum_of_test.go index da63c63a..de889324 100644 --- a/int32validator/at_most_sum_of_test.go +++ b/int32validator/at_most_sum_of_test.go @@ -147,7 +147,7 @@ func TestAtMostSumOfValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Int32Request{ diff --git a/int32validator/at_most_test.go b/int32validator/at_most_test.go index c28a1186..f1c651c8 100644 --- a/int32validator/at_most_test.go +++ b/int32validator/at_most_test.go @@ -49,7 +49,6 @@ func TestAtMostValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateInt32 - %s", name), func(t *testing.T) { t.Parallel() diff --git a/int32validator/between_test.go b/int32validator/between_test.go index b67f45bf..1e6ebe1f 100644 --- a/int32validator/between_test.go +++ b/int32validator/between_test.go @@ -72,7 +72,6 @@ func TestBetweenValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateInt32 - %s", name), func(t *testing.T) { t.Parallel() diff --git a/int32validator/equal_to_product_of_test.go b/int32validator/equal_to_product_of_test.go index 5d6e060f..8f0e9511 100644 --- a/int32validator/equal_to_product_of_test.go +++ b/int32validator/equal_to_product_of_test.go @@ -147,7 +147,7 @@ func TestEqualToProductOfValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Int32Request{ diff --git a/int32validator/equal_to_sum_of_test.go b/int32validator/equal_to_sum_of_test.go index 76a122c2..69484f63 100644 --- a/int32validator/equal_to_sum_of_test.go +++ b/int32validator/equal_to_sum_of_test.go @@ -148,7 +148,7 @@ func TestEqualToSumOfValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Int32Request{ diff --git a/int32validator/none_of_test.go b/int32validator/none_of_test.go index 2ad66c07..29a6b7d0 100644 --- a/int32validator/none_of_test.go +++ b/int32validator/none_of_test.go @@ -62,7 +62,6 @@ func TestNoneOfValidator(t *testing.T) { } for name, test := range testCases { - name, test := name, test t.Run(fmt.Sprintf("ValidateInt32 - %s", name), func(t *testing.T) { t.Parallel() diff --git a/int32validator/one_of_test.go b/int32validator/one_of_test.go index f04444e3..16d7ce54 100644 --- a/int32validator/one_of_test.go +++ b/int32validator/one_of_test.go @@ -8,10 +8,11 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" "github.com/hashicorp/terraform-plugin-framework/function" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" ) func TestOneOfValidator(t *testing.T) { @@ -61,7 +62,6 @@ func TestOneOfValidator(t *testing.T) { } for name, test := range testCases { - name, test := name, test t.Run(fmt.Sprintf("ValidateInt32 - %s", name), func(t *testing.T) { t.Parallel() 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/all_test.go b/int64validator/all_test.go index 55cf6bc7..f444377e 100644 --- a/int64validator/all_test.go +++ b/int64validator/all_test.go @@ -55,7 +55,7 @@ func TestAllValidatorValidateInt64(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Int64Request{ diff --git a/int64validator/any_test.go b/int64validator/any_test.go index f49d00db..25c64b7a 100644 --- a/int64validator/any_test.go +++ b/int64validator/any_test.go @@ -66,7 +66,7 @@ func TestAnyValidatorValidateInt64(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Int64Request{ diff --git a/int64validator/any_with_all_warnings_test.go b/int64validator/any_with_all_warnings_test.go index d27cb667..89969d04 100644 --- a/int64validator/any_with_all_warnings_test.go +++ b/int64validator/any_with_all_warnings_test.go @@ -67,7 +67,7 @@ func TestAnyWithAllWarningsValidatorValidateInt64(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Int64Request{ diff --git a/int64validator/at_least_sum_of_test.go b/int64validator/at_least_sum_of_test.go index 1ac80708..ce22de62 100644 --- a/int64validator/at_least_sum_of_test.go +++ b/int64validator/at_least_sum_of_test.go @@ -147,7 +147,7 @@ func TestAtLeastSumOfValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Int64Request{ diff --git a/int64validator/at_least_test.go b/int64validator/at_least_test.go index d224a5dc..73250a4d 100644 --- a/int64validator/at_least_test.go +++ b/int64validator/at_least_test.go @@ -49,7 +49,6 @@ func TestAtLeastValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateInt64 - %s", name), func(t *testing.T) { t.Parallel() diff --git a/int64validator/at_most_sum_of_test.go b/int64validator/at_most_sum_of_test.go index d85d3051..5d10d2dd 100644 --- a/int64validator/at_most_sum_of_test.go +++ b/int64validator/at_most_sum_of_test.go @@ -147,7 +147,7 @@ func TestAtMostSumOfValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Int64Request{ diff --git a/int64validator/at_most_test.go b/int64validator/at_most_test.go index a94908dc..11ebd08e 100644 --- a/int64validator/at_most_test.go +++ b/int64validator/at_most_test.go @@ -49,7 +49,6 @@ func TestAtMostValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateInt64 - %s", name), func(t *testing.T) { t.Parallel() diff --git a/int64validator/between_test.go b/int64validator/between_test.go index a7f428c8..ca68c79e 100644 --- a/int64validator/between_test.go +++ b/int64validator/between_test.go @@ -72,7 +72,6 @@ func TestBetweenValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateInt64 - %s", name), func(t *testing.T) { t.Parallel() diff --git a/int64validator/equal_to_product_of_test.go b/int64validator/equal_to_product_of_test.go index c5bcd643..321893d9 100644 --- a/int64validator/equal_to_product_of_test.go +++ b/int64validator/equal_to_product_of_test.go @@ -147,7 +147,7 @@ func TestEqualToProductOfValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Int64Request{ diff --git a/int64validator/equal_to_sum_of_test.go b/int64validator/equal_to_sum_of_test.go index fb150b4f..ff34d481 100644 --- a/int64validator/equal_to_sum_of_test.go +++ b/int64validator/equal_to_sum_of_test.go @@ -148,7 +148,7 @@ func TestEqualToSumOfValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.Int64Request{ diff --git a/int64validator/none_of_test.go b/int64validator/none_of_test.go index 53726ba6..48bc0840 100644 --- a/int64validator/none_of_test.go +++ b/int64validator/none_of_test.go @@ -62,7 +62,6 @@ func TestNoneOfValidator(t *testing.T) { } for name, test := range testCases { - name, test := name, test t.Run(fmt.Sprintf("ValidateInt64 - %s", name), func(t *testing.T) { t.Parallel() diff --git a/int64validator/one_of_test.go b/int64validator/one_of_test.go index 37f23cc7..a93e6023 100644 --- a/int64validator/one_of_test.go +++ b/int64validator/one_of_test.go @@ -62,7 +62,6 @@ func TestOneOfValidator(t *testing.T) { } for name, test := range testCases { - name, test := name, test t.Run(fmt.Sprintf("ValidateInt64 - %s", name), func(t *testing.T) { t.Parallel() 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/configvalidator/at_least_one_of_test.go b/internal/configvalidator/at_least_one_of_test.go index 94eeb86f..276ebb5a 100644 --- a/internal/configvalidator/at_least_one_of_test.go +++ b/internal/configvalidator/at_least_one_of_test.go @@ -598,7 +598,6 @@ func TestAtLeastOneOfValidatorValidate(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -704,7 +703,6 @@ func TestAtLeastOneOfValidatorValidateDataSource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -812,7 +810,6 @@ func TestAtLeastOneOfValidatorValidateProvider(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -920,7 +917,6 @@ func TestAtLeastOneOfValidatorValidateResource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1028,7 +1024,6 @@ func TestAtLeastOneOfValidatorValidateEphemeralResource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/internal/configvalidator/conflicting_test.go b/internal/configvalidator/conflicting_test.go index 1f513f27..20f973a9 100644 --- a/internal/configvalidator/conflicting_test.go +++ b/internal/configvalidator/conflicting_test.go @@ -602,7 +602,6 @@ func TestConflictingValidatorValidate(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -709,7 +708,6 @@ func TestConflictingValidatorValidateDataSource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -818,7 +816,6 @@ func TestConflictingValidatorValidateProvider(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -927,7 +924,6 @@ func TestConflictingValidatorValidateResource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1036,7 +1032,6 @@ func TestConflictingValidatorValidateEphemeralResource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/internal/configvalidator/exactly_one_of_test.go b/internal/configvalidator/exactly_one_of_test.go index 805c2ed8..ae8f3de7 100644 --- a/internal/configvalidator/exactly_one_of_test.go +++ b/internal/configvalidator/exactly_one_of_test.go @@ -622,7 +622,6 @@ func TestExactlyOneOfValidatorValidate(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -729,7 +728,6 @@ func TestExactlyOneOfValidatorValidateDataSource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -838,7 +836,6 @@ func TestExactlyOneOfValidatorValidateProvider(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -947,7 +944,6 @@ func TestExactlyOneOfValidatorValidateResource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1056,7 +1052,6 @@ func TestExactlyOneOfValidatorValidateEphemeralResource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/internal/configvalidator/required_together_test.go b/internal/configvalidator/required_together_test.go index fd7f04f7..595d83f9 100644 --- a/internal/configvalidator/required_together_test.go +++ b/internal/configvalidator/required_together_test.go @@ -590,7 +590,6 @@ func TestRequiredTogetherValidatorValidate(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -697,7 +696,6 @@ func TestRequiredTogetherValidatorValidateDataSource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -806,7 +804,6 @@ func TestRequiredTogetherValidatorValidateProvider(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -915,7 +912,6 @@ func TestRequiredTogetherValidatorValidateResource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1024,7 +1020,6 @@ func TestRequiredTogetherValidatorValidateEphemeralResource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/internal/schemavalidator/also_requires_test.go b/internal/schemavalidator/also_requires_test.go index 3ea0d40b..a17ebfa6 100644 --- a/internal/schemavalidator/also_requires_test.go +++ b/internal/schemavalidator/also_requires_test.go @@ -265,7 +265,7 @@ func TestAlsoRequiresValidatorValidate(t *testing.T) { } for name, test := range testCases { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() res := &schemavalidator.AlsoRequiresValidatorResponse{} diff --git a/internal/schemavalidator/at_least_one_of_test.go b/internal/schemavalidator/at_least_one_of_test.go index ce2122cf..f1f26ebf 100644 --- a/internal/schemavalidator/at_least_one_of_test.go +++ b/internal/schemavalidator/at_least_one_of_test.go @@ -298,7 +298,7 @@ func TestAtLeastOneOfValidatorValidate(t *testing.T) { } for name, test := range testCases { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() res := &schemavalidator.AtLeastOneOfValidatorResponse{} diff --git a/internal/schemavalidator/conflicts_with_test.go b/internal/schemavalidator/conflicts_with_test.go index faa45a9c..d7c80f08 100644 --- a/internal/schemavalidator/conflicts_with_test.go +++ b/internal/schemavalidator/conflicts_with_test.go @@ -266,7 +266,7 @@ func TestConflictsWithValidatorValidate(t *testing.T) { } for name, test := range testCases { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() res := &schemavalidator.ConflictsWithValidatorResponse{} diff --git a/internal/schemavalidator/exactly_one_of_test.go b/internal/schemavalidator/exactly_one_of_test.go index cb6dd1b2..1abe55f0 100644 --- a/internal/schemavalidator/exactly_one_of_test.go +++ b/internal/schemavalidator/exactly_one_of_test.go @@ -267,7 +267,7 @@ func TestExactlyOneOfValidator(t *testing.T) { } for name, test := range testCases { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() res := &schemavalidator.ExactlyOneOfValidatorResponse{} diff --git a/internal/schemavalidator/prefer_write_only_attribute.go b/internal/schemavalidator/prefer_write_only_attribute.go new file mode 100644 index 00000000..fa58406d --- /dev/null +++ b/internal/schemavalidator/prefer_write_only_attribute.go @@ -0,0 +1,249 @@ +// 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.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 preferred 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) 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, + 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) 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..6584fadc --- /dev/null +++ b/internal/schemavalidator/prefer_write_only_attribute_test.go @@ -0,0 +1,189 @@ +// 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 { + 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/all_test.go b/listvalidator/all_test.go index 56ce1ded..4615337f 100644 --- a/listvalidator/all_test.go +++ b/listvalidator/all_test.go @@ -68,7 +68,7 @@ func TestAllValidatorValidateList(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.ListRequest{ diff --git a/listvalidator/any_test.go b/listvalidator/any_test.go index 40bebfe4..76cf25b9 100644 --- a/listvalidator/any_test.go +++ b/listvalidator/any_test.go @@ -85,7 +85,7 @@ func TestAnyValidatorValidateList(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.ListRequest{ diff --git a/listvalidator/any_with_all_warnings_test.go b/listvalidator/any_with_all_warnings_test.go index 659530d0..0cbf0855 100644 --- a/listvalidator/any_with_all_warnings_test.go +++ b/listvalidator/any_with_all_warnings_test.go @@ -86,7 +86,7 @@ func TestAnyWithAllWarningsValidatorValidateList(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.ListRequest{ diff --git a/listvalidator/is_required_test.go b/listvalidator/is_required_test.go index 7bde543f..581377d4 100644 --- a/listvalidator/is_required_test.go +++ b/listvalidator/is_required_test.go @@ -7,11 +7,12 @@ import ( "context" "testing" - "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" ) func TestIsRequiredValidator(t *testing.T) { @@ -53,7 +54,7 @@ func TestIsRequiredValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.ListRequest{ 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/listvalidator/size_at_least_test.go b/listvalidator/size_at_least_test.go index ffb17606..c5a0a0f3 100644 --- a/listvalidator/size_at_least_test.go +++ b/listvalidator/size_at_least_test.go @@ -68,7 +68,6 @@ func TestSizeAtLeastValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateList - %s", name), func(t *testing.T) { t.Parallel() diff --git a/listvalidator/size_at_most_test.go b/listvalidator/size_at_most_test.go index a8cad9ac..83b2d9dc 100644 --- a/listvalidator/size_at_most_test.go +++ b/listvalidator/size_at_most_test.go @@ -72,7 +72,6 @@ func TestSizeAtMostValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateList - %s", name), func(t *testing.T) { t.Parallel() diff --git a/listvalidator/size_between_test.go b/listvalidator/size_between_test.go index 1b35c9d8..6902c9c2 100644 --- a/listvalidator/size_between_test.go +++ b/listvalidator/size_between_test.go @@ -111,7 +111,6 @@ func TestSizeBetweenValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateList - %s", name), func(t *testing.T) { t.Parallel() diff --git a/listvalidator/unique_values_test.go b/listvalidator/unique_values_test.go index 51b49ad6..235d2b55 100644 --- a/listvalidator/unique_values_test.go +++ b/listvalidator/unique_values_test.go @@ -9,13 +9,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/function" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" ) func TestUniqueValues(t *testing.T) { @@ -148,7 +149,6 @@ func TestUniqueValues(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(fmt.Sprintf("ValidateList - %s", name), func(t *testing.T) { t.Parallel() diff --git a/listvalidator/value_float32s_are_test.go b/listvalidator/value_float32s_are_test.go index c54049b7..4d7c1dcf 100644 --- a/listvalidator/value_float32s_are_test.go +++ b/listvalidator/value_float32s_are_test.go @@ -125,7 +125,6 @@ func TestValueFloat32sAreValidatorValidateList(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/listvalidator/value_float64s_are_test.go b/listvalidator/value_float64s_are_test.go index 41f8bf75..f7aa5901 100644 --- a/listvalidator/value_float64s_are_test.go +++ b/listvalidator/value_float64s_are_test.go @@ -125,7 +125,6 @@ func TestValueFloat64sAreValidatorValidateList(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/listvalidator/value_int32s_are_test.go b/listvalidator/value_int32s_are_test.go index 4ea0070c..b471cec3 100644 --- a/listvalidator/value_int32s_are_test.go +++ b/listvalidator/value_int32s_are_test.go @@ -120,7 +120,6 @@ func TestValueInt32sAreValidatorValidateList(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/listvalidator/value_int64s_are_test.go b/listvalidator/value_int64s_are_test.go index ef1fc825..7f04ccd9 100644 --- a/listvalidator/value_int64s_are_test.go +++ b/listvalidator/value_int64s_are_test.go @@ -120,7 +120,6 @@ func TestValueInt64sAreValidatorValidateList(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/listvalidator/value_lists_are_test.go b/listvalidator/value_lists_are_test.go index 478c4ce3..a0a27d8e 100644 --- a/listvalidator/value_lists_are_test.go +++ b/listvalidator/value_lists_are_test.go @@ -172,7 +172,6 @@ func TestValueListsAreValidatorValidateList(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/listvalidator/value_maps_are_test.go b/listvalidator/value_maps_are_test.go index 8e761462..2bac8170 100644 --- a/listvalidator/value_maps_are_test.go +++ b/listvalidator/value_maps_are_test.go @@ -173,7 +173,6 @@ func TestValueMapsAreValidatorValidateList(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/listvalidator/value_numbers_are_test.go b/listvalidator/value_numbers_are_test.go index bbc850fa..3c659ad8 100644 --- a/listvalidator/value_numbers_are_test.go +++ b/listvalidator/value_numbers_are_test.go @@ -126,7 +126,6 @@ func TestValueNumbersAreValidatorValidateList(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/listvalidator/value_sets_are_test.go b/listvalidator/value_sets_are_test.go index 2d6cc4d1..677a5a32 100644 --- a/listvalidator/value_sets_are_test.go +++ b/listvalidator/value_sets_are_test.go @@ -173,7 +173,6 @@ func TestValueSetsAreValidatorValidateList(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/listvalidator/value_strings_are_test.go b/listvalidator/value_strings_are_test.go index 360ac2e7..e3049f29 100644 --- a/listvalidator/value_strings_are_test.go +++ b/listvalidator/value_strings_are_test.go @@ -120,7 +120,6 @@ func TestValueStringsAreValidatorValidateList(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/mapvalidator/all_test.go b/mapvalidator/all_test.go index 1a4e69fd..b3887464 100644 --- a/mapvalidator/all_test.go +++ b/mapvalidator/all_test.go @@ -68,7 +68,7 @@ func TestAllValidatorValidateMap(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.MapRequest{ diff --git a/mapvalidator/any_test.go b/mapvalidator/any_test.go index eca5285b..d9fc1085 100644 --- a/mapvalidator/any_test.go +++ b/mapvalidator/any_test.go @@ -85,7 +85,7 @@ func TestAnyValidatorValidateMap(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.MapRequest{ diff --git a/mapvalidator/any_with_all_warnings_test.go b/mapvalidator/any_with_all_warnings_test.go index 9b237150..1bc41d37 100644 --- a/mapvalidator/any_with_all_warnings_test.go +++ b/mapvalidator/any_with_all_warnings_test.go @@ -86,7 +86,7 @@ func TestAnyWithAllWarningsValidatorValidateMap(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.MapRequest{ diff --git a/mapvalidator/keys_are_test.go b/mapvalidator/keys_are_test.go index a21dc980..8147b244 100644 --- a/mapvalidator/keys_are_test.go +++ b/mapvalidator/keys_are_test.go @@ -92,7 +92,7 @@ func TestKeysAreValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.MapRequest{ 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/mapvalidator/size_at_least_test.go b/mapvalidator/size_at_least_test.go index 969b3c8a..171cacff 100644 --- a/mapvalidator/size_at_least_test.go +++ b/mapvalidator/size_at_least_test.go @@ -68,7 +68,6 @@ func TestSizeAtLeastValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateMap - %s", name), func(t *testing.T) { t.Parallel() diff --git a/mapvalidator/size_at_most_test.go b/mapvalidator/size_at_most_test.go index 5886bae6..9c15255f 100644 --- a/mapvalidator/size_at_most_test.go +++ b/mapvalidator/size_at_most_test.go @@ -72,7 +72,6 @@ func TestSizeAtMostValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateMap - %s", name), func(t *testing.T) { t.Parallel() diff --git a/mapvalidator/size_between_test.go b/mapvalidator/size_between_test.go index d95634c7..e57f6b7a 100644 --- a/mapvalidator/size_between_test.go +++ b/mapvalidator/size_between_test.go @@ -111,7 +111,6 @@ func TestSizeBetweenValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateMap - %s", name), func(t *testing.T) { t.Parallel() diff --git a/mapvalidator/value_float32s_are_test.go b/mapvalidator/value_float32s_are_test.go index 45743f95..cd4dc7ce 100644 --- a/mapvalidator/value_float32s_are_test.go +++ b/mapvalidator/value_float32s_are_test.go @@ -110,7 +110,6 @@ func TestValueFloat32sAreValidatorValidateMap(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/mapvalidator/value_float64s_are_test.go b/mapvalidator/value_float64s_are_test.go index 97dd8466..f34aa7c6 100644 --- a/mapvalidator/value_float64s_are_test.go +++ b/mapvalidator/value_float64s_are_test.go @@ -110,7 +110,6 @@ func TestValueFloat64sAreValidatorValidateMap(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/mapvalidator/value_int32s_are_test.go b/mapvalidator/value_int32s_are_test.go index dabb90fa..4184e7b1 100644 --- a/mapvalidator/value_int32s_are_test.go +++ b/mapvalidator/value_int32s_are_test.go @@ -110,7 +110,6 @@ func TestValueInt32sAreValidatorValidateMap(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/mapvalidator/value_int64s_are_test.go b/mapvalidator/value_int64s_are_test.go index c4295862..39f510f5 100644 --- a/mapvalidator/value_int64s_are_test.go +++ b/mapvalidator/value_int64s_are_test.go @@ -110,7 +110,6 @@ func TestValueInt64sAreValidatorValidateMap(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/mapvalidator/value_lists_are_test.go b/mapvalidator/value_lists_are_test.go index eebdb0ad..fed09386 100644 --- a/mapvalidator/value_lists_are_test.go +++ b/mapvalidator/value_lists_are_test.go @@ -146,7 +146,6 @@ func TestValueListsAreValidatorValidateMap(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/mapvalidator/value_maps_are_test.go b/mapvalidator/value_maps_are_test.go index 41ad0cbc..457fe376 100644 --- a/mapvalidator/value_maps_are_test.go +++ b/mapvalidator/value_maps_are_test.go @@ -145,7 +145,6 @@ func TestValueMapsAreValidatorValidateMap(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/mapvalidator/value_numbers_are_test.go b/mapvalidator/value_numbers_are_test.go index a20d3fd9..121529f7 100644 --- a/mapvalidator/value_numbers_are_test.go +++ b/mapvalidator/value_numbers_are_test.go @@ -111,7 +111,6 @@ func TestValueNumbersAreValidatorValidateMap(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/mapvalidator/value_sets_are_test.go b/mapvalidator/value_sets_are_test.go index 527be84e..88912933 100644 --- a/mapvalidator/value_sets_are_test.go +++ b/mapvalidator/value_sets_are_test.go @@ -146,7 +146,6 @@ func TestValueSetsAreValidatorValidateMap(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/mapvalidator/value_strings_are_test.go b/mapvalidator/value_strings_are_test.go index 19ace181..fa494cec 100644 --- a/mapvalidator/value_strings_are_test.go +++ b/mapvalidator/value_strings_are_test.go @@ -110,7 +110,6 @@ func TestValueStringsAreValidatorValidateMap(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/numbervalidator/all_test.go b/numbervalidator/all_test.go index 5ba266b4..9ab71c47 100644 --- a/numbervalidator/all_test.go +++ b/numbervalidator/all_test.go @@ -56,7 +56,7 @@ func TestAllValidatorValidateNumber(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.NumberRequest{ diff --git a/numbervalidator/any_test.go b/numbervalidator/any_test.go index 06e81fd0..8a9c93a4 100644 --- a/numbervalidator/any_test.go +++ b/numbervalidator/any_test.go @@ -67,7 +67,7 @@ func TestAnyValidatorValidateNumber(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.NumberRequest{ diff --git a/numbervalidator/any_with_all_warnings_test.go b/numbervalidator/any_with_all_warnings_test.go index f7836226..c25eefce 100644 --- a/numbervalidator/any_with_all_warnings_test.go +++ b/numbervalidator/any_with_all_warnings_test.go @@ -68,7 +68,7 @@ func TestAnyWithAllWarningsValidatorValidateNumber(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.NumberRequest{ diff --git a/numbervalidator/none_of_test.go b/numbervalidator/none_of_test.go index a381ffa7..a238a65e 100644 --- a/numbervalidator/none_of_test.go +++ b/numbervalidator/none_of_test.go @@ -63,7 +63,6 @@ func TestNoneOfValidator(t *testing.T) { } for name, test := range testCases { - name, test := name, test t.Run(fmt.Sprintf("ValidateNumber - %s", name), func(t *testing.T) { t.Parallel() diff --git a/numbervalidator/one_of_test.go b/numbervalidator/one_of_test.go index a79aed16..2c516bb6 100644 --- a/numbervalidator/one_of_test.go +++ b/numbervalidator/one_of_test.go @@ -63,7 +63,6 @@ func TestOneOfValidator(t *testing.T) { } for name, test := range testCases { - name, test := name, test t.Run(fmt.Sprintf("ValidateNumber - %s", name), func(t *testing.T) { t.Parallel() 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/all_test.go b/objectvalidator/all_test.go index 94df3c24..fbb3e06e 100644 --- a/objectvalidator/all_test.go +++ b/objectvalidator/all_test.go @@ -82,7 +82,7 @@ func TestAllValidatorValidateObject(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.ObjectRequest{ diff --git a/objectvalidator/any_test.go b/objectvalidator/any_test.go index 9280ef09..5850f71a 100644 --- a/objectvalidator/any_test.go +++ b/objectvalidator/any_test.go @@ -125,7 +125,7 @@ func TestAnyValidatorValidateObject(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.ObjectRequest{ diff --git a/objectvalidator/any_with_all_warnings_test.go b/objectvalidator/any_with_all_warnings_test.go index ea98e882..d595ec1f 100644 --- a/objectvalidator/any_with_all_warnings_test.go +++ b/objectvalidator/any_with_all_warnings_test.go @@ -130,7 +130,7 @@ func TestAnyWithAllWarningsValidatorValidateObject(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.ObjectRequest{ diff --git a/objectvalidator/is_required_test.go b/objectvalidator/is_required_test.go index 8d67bd59..5a900c66 100644 --- a/objectvalidator/is_required_test.go +++ b/objectvalidator/is_required_test.go @@ -7,11 +7,12 @@ import ( "context" "testing" - "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator" ) func TestIsRequiredValidator(t *testing.T) { @@ -63,7 +64,7 @@ func TestIsRequiredValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.ObjectRequest{ 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/providervalidator/all_test.go b/providervalidator/all_test.go index a431e131..93836ea4 100644 --- a/providervalidator/all_test.go +++ b/providervalidator/all_test.go @@ -161,7 +161,6 @@ func TestAllValidatorValidateProvider(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/providervalidator/any_test.go b/providervalidator/any_test.go index e5557e49..309d2567 100644 --- a/providervalidator/any_test.go +++ b/providervalidator/any_test.go @@ -138,7 +138,6 @@ func TestAnyValidatorValidateProvider(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/providervalidator/any_with_all_warnings_test.go b/providervalidator/any_with_all_warnings_test.go index e67d05ec..f5d886ce 100644 --- a/providervalidator/any_with_all_warnings_test.go +++ b/providervalidator/any_with_all_warnings_test.go @@ -199,7 +199,6 @@ func TestAnyWithAllWarningsValidatorValidateProvider(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/providervalidator/at_least_one_of_test.go b/providervalidator/at_least_one_of_test.go index 40b62d34..12971e72 100644 --- a/providervalidator/at_least_one_of_test.go +++ b/providervalidator/at_least_one_of_test.go @@ -8,13 +8,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework-validators/providervalidator" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework-validators/providervalidator" ) func TestAtLeastOneOf(t *testing.T) { @@ -105,7 +106,6 @@ func TestAtLeastOneOf(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/providervalidator/conflicting_test.go b/providervalidator/conflicting_test.go index b7a5a57e..456ea42b 100644 --- a/providervalidator/conflicting_test.go +++ b/providervalidator/conflicting_test.go @@ -8,13 +8,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework-validators/providervalidator" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework-validators/providervalidator" ) func TestConflicting(t *testing.T) { @@ -106,7 +107,6 @@ func TestConflicting(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/providervalidator/exactly_one_of_test.go b/providervalidator/exactly_one_of_test.go index 8af6211a..1561466b 100644 --- a/providervalidator/exactly_one_of_test.go +++ b/providervalidator/exactly_one_of_test.go @@ -8,13 +8,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework-validators/providervalidator" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework-validators/providervalidator" ) func TestExactlyOneOf(t *testing.T) { @@ -106,7 +107,6 @@ func TestExactlyOneOf(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/providervalidator/required_together_test.go b/providervalidator/required_together_test.go index db39fae0..e552e3fd 100644 --- a/providervalidator/required_together_test.go +++ b/providervalidator/required_together_test.go @@ -8,13 +8,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework-validators/providervalidator" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework-validators/providervalidator" ) func TestRequiredTogether(t *testing.T) { @@ -106,7 +107,6 @@ func TestRequiredTogether(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/resourcevalidator/all_test.go b/resourcevalidator/all_test.go index a55dfb57..bd91e197 100644 --- a/resourcevalidator/all_test.go +++ b/resourcevalidator/all_test.go @@ -161,7 +161,6 @@ func TestAllValidatorValidateResource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/resourcevalidator/any_test.go b/resourcevalidator/any_test.go index 8e318012..1f0ff6f9 100644 --- a/resourcevalidator/any_test.go +++ b/resourcevalidator/any_test.go @@ -138,7 +138,6 @@ func TestAnyValidatorValidateResource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/resourcevalidator/any_with_all_warnings_test.go b/resourcevalidator/any_with_all_warnings_test.go index 13bbd355..43353a00 100644 --- a/resourcevalidator/any_with_all_warnings_test.go +++ b/resourcevalidator/any_with_all_warnings_test.go @@ -199,7 +199,6 @@ func TestAnyWithAllWarningsValidatorValidateResource(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/resourcevalidator/at_least_one_of_test.go b/resourcevalidator/at_least_one_of_test.go index 952bce5f..2d563688 100644 --- a/resourcevalidator/at_least_one_of_test.go +++ b/resourcevalidator/at_least_one_of_test.go @@ -8,13 +8,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" "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 TestAtLeastOneOf(t *testing.T) { @@ -105,7 +106,6 @@ func TestAtLeastOneOf(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/resourcevalidator/conflicting_test.go b/resourcevalidator/conflicting_test.go index e73f4a58..01148a77 100644 --- a/resourcevalidator/conflicting_test.go +++ b/resourcevalidator/conflicting_test.go @@ -8,13 +8,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" "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 TestConflicting(t *testing.T) { @@ -106,7 +107,6 @@ func TestConflicting(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/resourcevalidator/exactly_one_of_test.go b/resourcevalidator/exactly_one_of_test.go index 7825b54f..9dfeb1f1 100644 --- a/resourcevalidator/exactly_one_of_test.go +++ b/resourcevalidator/exactly_one_of_test.go @@ -8,13 +8,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" "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 TestExactlyOneOf(t *testing.T) { @@ -106,7 +107,6 @@ func TestExactlyOneOf(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/resourcevalidator/prefer_write_only_attribute.go b/resourcevalidator/prefer_write_only_attribute.go new file mode 100644 index 00000000..a2dcbec4 --- /dev/null +++ b/resourcevalidator/prefer_write_only_attribute.go @@ -0,0 +1,83 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +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 preferred 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..2256e8ea --- /dev/null +++ b/resourcevalidator/prefer_write_only_attribute_test.go @@ -0,0 +1,152 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +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, + WriteOnly: true, + }, + "oldAttribute2": schema.StringAttribute{ + Optional: true, + }, + "writeOnlyAttribute2": schema.StringAttribute{ + Optional: true, + WriteOnly: 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 { + 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) + } + }) + } +} diff --git a/resourcevalidator/required_together_test.go b/resourcevalidator/required_together_test.go index 6db54425..7b9a9e48 100644 --- a/resourcevalidator/required_together_test.go +++ b/resourcevalidator/required_together_test.go @@ -8,13 +8,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" "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 TestRequiredTogether(t *testing.T) { @@ -106,7 +107,6 @@ func TestRequiredTogether(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/setvalidator/all_test.go b/setvalidator/all_test.go index b1e372f2..eaafe8b5 100644 --- a/setvalidator/all_test.go +++ b/setvalidator/all_test.go @@ -68,7 +68,7 @@ func TestAllValidatorValidateSet(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.SetRequest{ diff --git a/setvalidator/any_test.go b/setvalidator/any_test.go index e326daad..d609480a 100644 --- a/setvalidator/any_test.go +++ b/setvalidator/any_test.go @@ -85,7 +85,7 @@ func TestAnyValidatorValidateSet(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.SetRequest{ diff --git a/setvalidator/any_with_all_warnings_test.go b/setvalidator/any_with_all_warnings_test.go index b8944014..211b1caa 100644 --- a/setvalidator/any_with_all_warnings_test.go +++ b/setvalidator/any_with_all_warnings_test.go @@ -86,7 +86,7 @@ func TestAnyWithAllWarningsValidatorValidateSet(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.SetRequest{ diff --git a/setvalidator/is_required_test.go b/setvalidator/is_required_test.go index c71354c2..fdbf424b 100644 --- a/setvalidator/is_required_test.go +++ b/setvalidator/is_required_test.go @@ -7,11 +7,12 @@ import ( "context" "testing" - "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" ) func TestIsRequiredValidator(t *testing.T) { @@ -53,7 +54,7 @@ func TestIsRequiredValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.SetRequest{ diff --git a/setvalidator/size_at_least_test.go b/setvalidator/size_at_least_test.go index e76da98f..de93cfb4 100644 --- a/setvalidator/size_at_least_test.go +++ b/setvalidator/size_at_least_test.go @@ -68,7 +68,6 @@ func TestSizeAtLeastValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateSet - %s", name), func(t *testing.T) { t.Parallel() diff --git a/setvalidator/size_at_most_test.go b/setvalidator/size_at_most_test.go index 0cebc4d7..8bac993b 100644 --- a/setvalidator/size_at_most_test.go +++ b/setvalidator/size_at_most_test.go @@ -72,7 +72,6 @@ func TestSizeAtMostValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateSet - %s", name), func(t *testing.T) { t.Parallel() diff --git a/setvalidator/size_between_test.go b/setvalidator/size_between_test.go index 0b24e7e4..b8da367b 100644 --- a/setvalidator/size_between_test.go +++ b/setvalidator/size_between_test.go @@ -111,7 +111,6 @@ func TestSizeBetweenValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateSet - %s", name), func(t *testing.T) { t.Parallel() diff --git a/setvalidator/value_float32s_are_test.go b/setvalidator/value_float32s_are_test.go index 51051f06..fa3de8b5 100644 --- a/setvalidator/value_float32s_are_test.go +++ b/setvalidator/value_float32s_are_test.go @@ -125,7 +125,6 @@ func TestValueFloat32sAreValidatorValidateSet(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/setvalidator/value_float64s_are_test.go b/setvalidator/value_float64s_are_test.go index 71a140f4..49c6b892 100644 --- a/setvalidator/value_float64s_are_test.go +++ b/setvalidator/value_float64s_are_test.go @@ -125,7 +125,6 @@ func TestValueFloat64sAreValidatorValidateSet(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/setvalidator/value_int32s_are_test.go b/setvalidator/value_int32s_are_test.go index 487ef96e..a21eaeee 100644 --- a/setvalidator/value_int32s_are_test.go +++ b/setvalidator/value_int32s_are_test.go @@ -125,7 +125,6 @@ func TestValueInt32sAreValidatorValidateSet(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/setvalidator/value_int64s_are_test.go b/setvalidator/value_int64s_are_test.go index b8b6b82d..745a46cd 100644 --- a/setvalidator/value_int64s_are_test.go +++ b/setvalidator/value_int64s_are_test.go @@ -125,7 +125,6 @@ func TestValueInt64sAreValidatorValidateSet(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/setvalidator/value_lists_are_test.go b/setvalidator/value_lists_are_test.go index b4de73d7..3448c103 100644 --- a/setvalidator/value_lists_are_test.go +++ b/setvalidator/value_lists_are_test.go @@ -209,7 +209,6 @@ func TestValueListsAreValidatorValidateSet(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/setvalidator/value_maps_are_test.go b/setvalidator/value_maps_are_test.go index 340ff760..9d9bba93 100644 --- a/setvalidator/value_maps_are_test.go +++ b/setvalidator/value_maps_are_test.go @@ -203,7 +203,6 @@ func TestValueMapsAreValidatorValidateSet(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/setvalidator/value_numbers_are_test.go b/setvalidator/value_numbers_are_test.go index 541b0f37..72d21e5b 100644 --- a/setvalidator/value_numbers_are_test.go +++ b/setvalidator/value_numbers_are_test.go @@ -126,7 +126,6 @@ func TestValueNumbersAreValidatorValidateSet(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/setvalidator/value_sets_are_test.go b/setvalidator/value_sets_are_test.go index b0cb6ae4..ecc8cd66 100644 --- a/setvalidator/value_sets_are_test.go +++ b/setvalidator/value_sets_are_test.go @@ -208,7 +208,6 @@ func TestValueSetsAreValidatorValidateSet(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/setvalidator/value_strings_are_test.go b/setvalidator/value_strings_are_test.go index 666141f2..0bbd5539 100644 --- a/setvalidator/value_strings_are_test.go +++ b/setvalidator/value_strings_are_test.go @@ -125,7 +125,6 @@ func TestValueStringsAreValidatorValidateSet(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/stringvalidator/all_test.go b/stringvalidator/all_test.go index 4d6a30ea..722a189e 100644 --- a/stringvalidator/all_test.go +++ b/stringvalidator/all_test.go @@ -55,7 +55,7 @@ func TestAllValidatorValidateString(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.StringRequest{ diff --git a/stringvalidator/any_test.go b/stringvalidator/any_test.go index acab8f96..0377c5d3 100644 --- a/stringvalidator/any_test.go +++ b/stringvalidator/any_test.go @@ -66,7 +66,7 @@ func TestAnyValidatorValidateString(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.StringRequest{ diff --git a/stringvalidator/any_with_all_warnings_test.go b/stringvalidator/any_with_all_warnings_test.go index 1bc1477c..e65f2796 100644 --- a/stringvalidator/any_with_all_warnings_test.go +++ b/stringvalidator/any_with_all_warnings_test.go @@ -67,7 +67,7 @@ func TestAnyWithAllWarningsValidatorValidateString(t *testing.T) { } for name, test := range tests { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() request := validator.StringRequest{ diff --git a/stringvalidator/length_at_least_test.go b/stringvalidator/length_at_least_test.go index 5ec51cb2..6daada66 100644 --- a/stringvalidator/length_at_least_test.go +++ b/stringvalidator/length_at_least_test.go @@ -55,7 +55,6 @@ func TestLengthAtLeastValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateString - %s", name), func(t *testing.T) { t.Parallel() diff --git a/stringvalidator/length_at_most_test.go b/stringvalidator/length_at_most_test.go index 6eeca554..f3f13598 100644 --- a/stringvalidator/length_at_most_test.go +++ b/stringvalidator/length_at_most_test.go @@ -56,7 +56,6 @@ func TestLengthAtMostValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateString - %s", name), func(t *testing.T) { t.Parallel() diff --git a/stringvalidator/length_between_test.go b/stringvalidator/length_between_test.go index cf3db9b2..6270a772 100644 --- a/stringvalidator/length_between_test.go +++ b/stringvalidator/length_between_test.go @@ -94,7 +94,6 @@ func TestLengthBetweenValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateString - %s", name), func(t *testing.T) { t.Parallel() diff --git a/stringvalidator/none_of_case_insensitive_test.go b/stringvalidator/none_of_case_insensitive_test.go index 6f921f6c..52e67975 100644 --- a/stringvalidator/none_of_case_insensitive_test.go +++ b/stringvalidator/none_of_case_insensitive_test.go @@ -71,7 +71,6 @@ func TestNoneOfCaseInsensitiveValidator(t *testing.T) { } for name, test := range testCases { - name, test := name, test t.Run(fmt.Sprintf("ValidateString - %s", name), func(t *testing.T) { t.Parallel() @@ -125,7 +124,7 @@ func TestNoneOfCaseInsensitiveValidator_Description(t *testing.T) { } for name, test := range testCases { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/stringvalidator/none_of_test.go b/stringvalidator/none_of_test.go index 72ce0285..c2e53435 100644 --- a/stringvalidator/none_of_test.go +++ b/stringvalidator/none_of_test.go @@ -70,7 +70,6 @@ func TestNoneOfValidator(t *testing.T) { } for name, test := range testCases { - name, test := name, test t.Run(fmt.Sprintf("ValidateString - %s", name), func(t *testing.T) { t.Parallel() @@ -124,7 +123,7 @@ func TestNoneOfValidator_Description(t *testing.T) { } for name, test := range testCases { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/stringvalidator/one_of_case_insensitive_test.go b/stringvalidator/one_of_case_insensitive_test.go index aa6be52a..96da0684 100644 --- a/stringvalidator/one_of_case_insensitive_test.go +++ b/stringvalidator/one_of_case_insensitive_test.go @@ -70,7 +70,6 @@ func TestOneOfCaseInsensitiveValidator(t *testing.T) { } for name, test := range testCases { - name, test := name, test t.Run(fmt.Sprintf("ValidateString - %s", name), func(t *testing.T) { t.Parallel() @@ -124,7 +123,7 @@ func TestOneOfCaseInsensitiveValidator_Description(t *testing.T) { } for name, test := range testCases { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/stringvalidator/one_of_test.go b/stringvalidator/one_of_test.go index e2935631..c4c2d6a4 100644 --- a/stringvalidator/one_of_test.go +++ b/stringvalidator/one_of_test.go @@ -71,7 +71,6 @@ func TestOneOfValidator(t *testing.T) { } for name, test := range testCases { - name, test := name, test t.Run(fmt.Sprintf("ValidateString - %s", name), func(t *testing.T) { t.Parallel() @@ -125,7 +124,7 @@ func TestOneOfValidator_Description(t *testing.T) { } for name, test := range testCases { - name, test := name, test + t.Run(name, func(t *testing.T) { t.Parallel() 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, + }, + }, + } +} diff --git a/stringvalidator/regex_matches_test.go b/stringvalidator/regex_matches_test.go index f591b7fe..f84efa9e 100644 --- a/stringvalidator/regex_matches_test.go +++ b/stringvalidator/regex_matches_test.go @@ -46,7 +46,6 @@ func TestRegexMatchesValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateString - %s", name), func(t *testing.T) { t.Parallel() diff --git a/stringvalidator/utf8_length_at_least_test.go b/stringvalidator/utf8_length_at_least_test.go index 27f00724..f18525f8 100644 --- a/stringvalidator/utf8_length_at_least_test.go +++ b/stringvalidator/utf8_length_at_least_test.go @@ -72,7 +72,6 @@ func TestUTF8LengthAtLeastValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateString - %s", name), func(t *testing.T) { t.Parallel() diff --git a/stringvalidator/utf8_length_at_most_test.go b/stringvalidator/utf8_length_at_most_test.go index ce70d88d..0dd450cb 100644 --- a/stringvalidator/utf8_length_at_most_test.go +++ b/stringvalidator/utf8_length_at_most_test.go @@ -72,7 +72,6 @@ func TestUTF8LengthAtMostValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateString - %s", name), func(t *testing.T) { t.Parallel() diff --git a/stringvalidator/utf8_length_between_test.go b/stringvalidator/utf8_length_between_test.go index 9a0b04e5..b002019f 100644 --- a/stringvalidator/utf8_length_between_test.go +++ b/stringvalidator/utf8_length_between_test.go @@ -94,7 +94,6 @@ func TestUTF8LengthBetweenValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(fmt.Sprintf("ValidateString - %s", name), func(t *testing.T) { t.Parallel() diff --git a/tools/go.mod b/tools/go.mod index 738e2a3d..4dbb34ee 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -2,17 +2,16 @@ module tools go 1.22.7 -require github.com/hashicorp/copywrite v0.19.0 +require github.com/hashicorp/copywrite v0.20.0 require ( - github.com/AlecAivazis/survey/v2 v2.3.6 // indirect + github.com/AlecAivazis/survey/v2 v2.3.7 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect github.com/bmatcuk/doublestar/v4 v4.6.0 // indirect github.com/bradleyfalzon/ghinstallation/v2 v2.5.0 // indirect - github.com/cli/go-gh v1.2.1 // indirect + github.com/cli/go-gh/v2 v2.11.2 // indirect github.com/cli/safeexec v1.0.0 // indirect - github.com/cli/shurcooL-graphql v0.0.2 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect @@ -25,41 +24,37 @@ require ( github.com/google/go-querystring v1.1.0 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/henvic/httpretty v0.0.6 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jedib0t/go-pretty v4.3.0+incompatible // indirect github.com/jedib0t/go-pretty/v6 v6.4.6 // indirect github.com/joho/godotenv v1.3.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/knadh/koanf v1.5.0 // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mergestat/timediff v0.0.3 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/muesli/termenv v0.12.0 // indirect github.com/oklog/ulid v1.3.1 // indirect - github.com/rivo/uniseg v0.2.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/samber/lo v1.37.0 // indirect github.com/spf13/cobra v1.6.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/thanhpk/randstr v1.0.4 // indirect - github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e // indirect go.mongodb.org/mongo-driver v1.10.0 // indirect - golang.org/x/crypto v0.21.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect - golang.org/x/net v0.23.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/tools/go.sum b/tools/go.sum index cfc168fa..a889a22a 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -1,8 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8S9ziyw= -github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI= +github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= +github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= @@ -44,12 +44,10 @@ github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cli/go-gh v1.2.1 h1:xFrjejSsgPiwXFP6VYynKWwxLQcNJy3Twbu82ZDlR/o= -github.com/cli/go-gh v1.2.1/go.mod h1:Jxk8X+TCO4Ui/GarwY9tByWm/8zp4jJktzVZNlTW5VM= +github.com/cli/go-gh/v2 v2.11.2 h1:oad1+sESTPNTiTvh3I3t8UmxuovNDxhwLzeMHk45Q9w= +github.com/cli/go-gh/v2 v2.11.2/go.mod h1:vVFhi3TfjseIW26ED9itAR8gQK0aVThTm8sYrsZ5QTI= github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI= github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= -github.com/cli/shurcooL-graphql v0.0.2 h1:rwP5/qQQ2fM0TzkUTwtt6E2LbIYf6R+39cUXTa04NYk= -github.com/cli/shurcooL-graphql v0.0.2/go.mod h1:tlrLmw/n5Q/+4qSvosT+9/W5zc8ZMjnJeYBxSdb4nWA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= @@ -144,12 +142,10 @@ github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= -github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= -github.com/hashicorp/copywrite v0.19.0 h1:f9LVxTDBfFYeQmdBpOsZ+HWknXonI8ZwubbO/RwyuCo= -github.com/hashicorp/copywrite v0.19.0/go.mod h1:6wvQH+ICDoD2bpjO1RJ6fi+h3aY5NeLEM12oTkEtFoc= +github.com/hashicorp/copywrite v0.20.0 h1:i+iNq4lWsGopKIhC0HfZjUvNAnXnU/Pc5e+4L5WF+1Y= +github.com/hashicorp/copywrite v0.20.0/go.mod h1:mu6DAyUI6m6vq8weoJn9a0HDuUUrV+0GQdRp4mD50yU= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= @@ -184,8 +180,6 @@ github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoI github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/henvic/httpretty v0.0.6 h1:JdzGzKZBajBfnvlMALXXMVQWxWMF/ofTy8C3/OSUTxs= -github.com/henvic/httpretty v0.0.6/go.mod h1:X38wLjWXHkXT7r2+uK8LjCMne9rsuNaBLJ+5cU2/Pmo= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/hjson/hjson-go/v4 v4.0.0 h1:wlm6IYYqHjOdXH1gHev4VoXCaW20HdQAGCxdOEEg2cs= @@ -224,8 +218,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -241,10 +233,11 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mergestat/timediff v0.0.3 h1:ucCNh4/ZrTPjFZ081PccNbhx9spymCJkFxSzgVuPU+Y= github.com/mergestat/timediff v0.0.3/go.mod h1:yvMUaRu2oetc+9IbPLYBJviz6sA7xz8OXMDfhBl7YSI= @@ -276,10 +269,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.12.0 h1:KuQRUE3PgxRFWhq4gHvZtPSLCGDqM5q/cYr1pZ39ytc= -github.com/muesli/termenv v0.12.0/go.mod h1:WCCv32tusQ/EEZ5S8oUIIrC/nIuBcxCVqlN4Xfkv+7A= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= @@ -317,8 +306,9 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= @@ -355,8 +345,6 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo= github.com/thanhpk/randstr v1.0.4/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U= -github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e h1:BuzhfgfWQbX0dWzYzT1zsORLnHRv3bcRcsaUk0VmXA8= -github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= @@ -384,8 +372,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= @@ -419,13 +407,12 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -441,8 +428,9 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -476,16 +464,12 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/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-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -493,17 +477,16 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -515,8 +498,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -575,8 +558,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= -gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=