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=
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: