diff --git a/.changes/unreleased/FEATURES-20250206-114700.yaml b/.changes/unreleased/FEATURES-20250206-114700.yaml
new file mode 100644
index 000000000..ce306a4c9
--- /dev/null
+++ b/.changes/unreleased/FEATURES-20250206-114700.yaml
@@ -0,0 +1,6 @@
+kind: FEATURES
+body: 'resource/schema: Added `WriteOnly` schema field for managed resource schemas to indicate a write-only attribute.
+Write-only attribute values are not saved to the Terraform plan or state artifacts.'
+time: 2025-02-06T11:47:00.176842-05:00
+custom:
+ Issue: "1044"
diff --git a/.changes/unreleased/NOTES-20250206-114436.yaml b/.changes/unreleased/NOTES-20250206-114436.yaml
new file mode 100644
index 000000000..0ce8a7603
--- /dev/null
+++ b/.changes/unreleased/NOTES-20250206-114436.yaml
@@ -0,0 +1,5 @@
+kind: NOTES
+body: Write-only attribute support is in technical preview and offered without compatibility promises until Terraform 1.11 is generally available.
+time: 2025-02-06T11:44:36.156747-05:00
+custom:
+ Issue: "1044"
diff --git a/datasource/schema/bool_attribute.go b/datasource/schema/bool_attribute.go
index b9f6e3820..1d984a8db 100644
--- a/datasource/schema/bool_attribute.go
+++ b/datasource/schema/bool_attribute.go
@@ -4,13 +4,14 @@
package schema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -181,6 +182,11 @@ func (a BoolAttribute) IsRequired() bool {
return a.Required
}
+// IsWriteOnly returns false as write-only attributes are not supported in data source schemas.
+func (a BoolAttribute) IsWriteOnly() bool {
+ return false
+}
+
// IsSensitive returns the Sensitive field value.
func (a BoolAttribute) IsSensitive() bool {
return a.Sensitive
diff --git a/datasource/schema/bool_attribute_test.go b/datasource/schema/bool_attribute_test.go
index 40e36a08d..b493d892d 100644
--- a/datasource/schema/bool_attribute_test.go
+++ b/datasource/schema/bool_attribute_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -16,7 +18,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestBoolAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -423,3 +424,31 @@ func TestBoolAttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestBoolAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.BoolAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.BoolAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/datasource/schema/dynamic_attribute.go b/datasource/schema/dynamic_attribute.go
index 6b1b6c83e..4659cadf6 100644
--- a/datasource/schema/dynamic_attribute.go
+++ b/datasource/schema/dynamic_attribute.go
@@ -4,13 +4,14 @@
package schema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -182,6 +183,11 @@ func (a DynamicAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not supported in data source schemas.
+func (a DynamicAttribute) IsWriteOnly() bool {
+ return false
+}
+
// DynamicValidators returns the Validators field value.
func (a DynamicAttribute) DynamicValidators() []validator.Dynamic {
return a.Validators
diff --git a/datasource/schema/dynamic_attribute_test.go b/datasource/schema/dynamic_attribute_test.go
index 8981dbecd..95fe01fb4 100644
--- a/datasource/schema/dynamic_attribute_test.go
+++ b/datasource/schema/dynamic_attribute_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -16,7 +18,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestDynamicAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -390,6 +391,34 @@ func TestDynamicAttributeIsSensitive(t *testing.T) {
}
}
+func TestDynamicAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.DynamicAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.DynamicAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestDynamicAttributeDynamicValidators(t *testing.T) {
t.Parallel()
diff --git a/datasource/schema/float32_attribute.go b/datasource/schema/float32_attribute.go
index 8f3dbdc21..d2510f5c3 100644
--- a/datasource/schema/float32_attribute.go
+++ b/datasource/schema/float32_attribute.go
@@ -189,3 +189,8 @@ func (a Float32Attribute) IsRequired() bool {
func (a Float32Attribute) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly returns false as write-only attributes are not supported in data source schemas.
+func (a Float32Attribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/datasource/schema/float32_attribute_test.go b/datasource/schema/float32_attribute_test.go
index 0da3cc94e..1040f6245 100644
--- a/datasource/schema/float32_attribute_test.go
+++ b/datasource/schema/float32_attribute_test.go
@@ -424,3 +424,31 @@ func TestFloat32AttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestFloat32AttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.Float32Attribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.Float32Attribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/datasource/schema/float64_attribute.go b/datasource/schema/float64_attribute.go
index 1313353ec..1d893dd33 100644
--- a/datasource/schema/float64_attribute.go
+++ b/datasource/schema/float64_attribute.go
@@ -4,13 +4,14 @@
package schema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -188,3 +189,8 @@ func (a Float64Attribute) IsRequired() bool {
func (a Float64Attribute) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly returns false as write-only attributes are not supported in data source schemas.
+func (a Float64Attribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/datasource/schema/float64_attribute_test.go b/datasource/schema/float64_attribute_test.go
index f2e05ebf7..b83e16abd 100644
--- a/datasource/schema/float64_attribute_test.go
+++ b/datasource/schema/float64_attribute_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -16,7 +18,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestFloat64AttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -423,3 +424,31 @@ func TestFloat64AttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestFloat64AttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.Float64Attribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.Float64Attribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/datasource/schema/int32_attribute.go b/datasource/schema/int32_attribute.go
index 89f852e8c..d06a9b8ce 100644
--- a/datasource/schema/int32_attribute.go
+++ b/datasource/schema/int32_attribute.go
@@ -189,3 +189,8 @@ func (a Int32Attribute) IsRequired() bool {
func (a Int32Attribute) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly returns false as write-only attributes are not supported in data source schemas.
+func (a Int32Attribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/datasource/schema/int32_attribute_test.go b/datasource/schema/int32_attribute_test.go
index 3df85e9c7..23ce97fd5 100644
--- a/datasource/schema/int32_attribute_test.go
+++ b/datasource/schema/int32_attribute_test.go
@@ -424,3 +424,31 @@ func TestInt32AttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestInt32AttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.Int32Attribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.Int32Attribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/datasource/schema/int64_attribute.go b/datasource/schema/int64_attribute.go
index ab9d5ca1b..85bd4a445 100644
--- a/datasource/schema/int64_attribute.go
+++ b/datasource/schema/int64_attribute.go
@@ -4,13 +4,14 @@
package schema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -188,3 +189,8 @@ func (a Int64Attribute) IsRequired() bool {
func (a Int64Attribute) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly returns false as write-only attributes are not supported in data source schemas.
+func (a Int64Attribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/datasource/schema/int64_attribute_test.go b/datasource/schema/int64_attribute_test.go
index c1ac3c7a4..b15eeb85f 100644
--- a/datasource/schema/int64_attribute_test.go
+++ b/datasource/schema/int64_attribute_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -16,7 +18,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestInt64AttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -423,3 +424,31 @@ func TestInt64AttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestInt64AttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.Int64Attribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.Int64Attribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/datasource/schema/list_attribute.go b/datasource/schema/list_attribute.go
index 9d502067f..2e5140602 100644
--- a/datasource/schema/list_attribute.go
+++ b/datasource/schema/list_attribute.go
@@ -6,6 +6,8 @@ package schema
import (
"context"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
@@ -13,7 +15,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -202,6 +203,11 @@ func (a ListAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not supported in data source schemas.
+func (a ListAttribute) IsWriteOnly() bool {
+ return false
+}
+
// ListValidators returns the Validators field value.
func (a ListAttribute) ListValidators() []validator.List {
return a.Validators
diff --git a/datasource/schema/list_attribute_test.go b/datasource/schema/list_attribute_test.go
index 27294a130..1e7335ce3 100644
--- a/datasource/schema/list_attribute_test.go
+++ b/datasource/schema/list_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/diag"
@@ -19,7 +21,6 @@ import (
"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-go/tftypes"
)
func TestListAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -398,6 +399,34 @@ func TestListAttributeIsSensitive(t *testing.T) {
}
}
+func TestListAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.ListAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.ListAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestListAttributeListValidators(t *testing.T) {
t.Parallel()
diff --git a/datasource/schema/list_nested_attribute.go b/datasource/schema/list_nested_attribute.go
index b9b70d6fb..922320627 100644
--- a/datasource/schema/list_nested_attribute.go
+++ b/datasource/schema/list_nested_attribute.go
@@ -7,6 +7,8 @@ import (
"context"
"fmt"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
@@ -14,7 +16,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -230,6 +231,11 @@ func (a ListNestedAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not supported in data source schemas.
+func (a ListNestedAttribute) IsWriteOnly() bool {
+ return false
+}
+
// ListValidators returns the Validators field value.
func (a ListNestedAttribute) ListValidators() []validator.List {
return a.Validators
diff --git a/datasource/schema/list_nested_attribute_test.go b/datasource/schema/list_nested_attribute_test.go
index 5d1e7db88..fd1cf6941 100644
--- a/datasource/schema/list_nested_attribute_test.go
+++ b/datasource/schema/list_nested_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/diag"
@@ -19,7 +21,6 @@ import (
"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-go/tftypes"
)
func TestListNestedAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -569,6 +570,34 @@ func TestListNestedAttributeIsSensitive(t *testing.T) {
}
}
+func TestListNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.ListNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.ListNestedAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestListNestedAttributeListValidators(t *testing.T) {
t.Parallel()
diff --git a/datasource/schema/map_attribute.go b/datasource/schema/map_attribute.go
index 516576a4e..3d6c57680 100644
--- a/datasource/schema/map_attribute.go
+++ b/datasource/schema/map_attribute.go
@@ -6,6 +6,8 @@ package schema
import (
"context"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
@@ -13,7 +15,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -205,6 +206,11 @@ func (a MapAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not supported in data source schemas.
+func (a MapAttribute) IsWriteOnly() bool {
+ return false
+}
+
// MapValidators returns the Validators field value.
func (a MapAttribute) MapValidators() []validator.Map {
return a.Validators
diff --git a/datasource/schema/map_attribute_test.go b/datasource/schema/map_attribute_test.go
index d79425462..ab3d31167 100644
--- a/datasource/schema/map_attribute_test.go
+++ b/datasource/schema/map_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/diag"
@@ -19,7 +21,6 @@ import (
"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-go/tftypes"
)
func TestMapAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -398,6 +399,34 @@ func TestMapAttributeIsSensitive(t *testing.T) {
}
}
+func TestMapAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.MapAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.MapAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestMapAttributeMapValidators(t *testing.T) {
t.Parallel()
diff --git a/datasource/schema/map_nested_attribute.go b/datasource/schema/map_nested_attribute.go
index 9729efdc3..9bf1bb957 100644
--- a/datasource/schema/map_nested_attribute.go
+++ b/datasource/schema/map_nested_attribute.go
@@ -231,6 +231,11 @@ func (a MapNestedAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not supported in data source schemas.
+func (a MapNestedAttribute) IsWriteOnly() bool {
+ return false
+}
+
// MapValidators returns the Validators field value.
func (a MapNestedAttribute) MapValidators() []validator.Map {
return a.Validators
diff --git a/datasource/schema/map_nested_attribute_test.go b/datasource/schema/map_nested_attribute_test.go
index ea6dc480d..671f55593 100644
--- a/datasource/schema/map_nested_attribute_test.go
+++ b/datasource/schema/map_nested_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/diag"
@@ -19,7 +21,6 @@ import (
"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-go/tftypes"
)
func TestMapNestedAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -569,6 +570,34 @@ func TestMapNestedAttributeIsSensitive(t *testing.T) {
}
}
+func TestMapNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.MapNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.MapNestedAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestMapNestedAttributeMapNestedValidators(t *testing.T) {
t.Parallel()
diff --git a/datasource/schema/number_attribute.go b/datasource/schema/number_attribute.go
index ffe4e0839..c21f74a15 100644
--- a/datasource/schema/number_attribute.go
+++ b/datasource/schema/number_attribute.go
@@ -4,13 +4,14 @@
package schema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -185,6 +186,11 @@ func (a NumberAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not supported in data source schemas.
+func (a NumberAttribute) IsWriteOnly() bool {
+ return false
+}
+
// NumberValidators returns the Validators field value.
func (a NumberAttribute) NumberValidators() []validator.Number {
return a.Validators
diff --git a/datasource/schema/number_attribute_test.go b/datasource/schema/number_attribute_test.go
index f8cec323d..449dda7ce 100644
--- a/datasource/schema/number_attribute_test.go
+++ b/datasource/schema/number_attribute_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -16,7 +18,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestNumberAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -390,6 +391,34 @@ func TestNumberAttributeIsSensitive(t *testing.T) {
}
}
+func TestNumberAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.NumberAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.NumberAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestNumberAttributeNumberValidators(t *testing.T) {
t.Parallel()
diff --git a/datasource/schema/object_attribute.go b/datasource/schema/object_attribute.go
index eafa40c6e..a004329ac 100644
--- a/datasource/schema/object_attribute.go
+++ b/datasource/schema/object_attribute.go
@@ -6,6 +6,8 @@ package schema
import (
"context"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
@@ -13,7 +15,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -204,6 +205,11 @@ func (a ObjectAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not supported in data source schemas.
+func (a ObjectAttribute) IsWriteOnly() bool {
+ return false
+}
+
// ObjectValidators returns the Validators field value.
func (a ObjectAttribute) ObjectValidators() []validator.Object {
return a.Validators
diff --git a/datasource/schema/object_attribute_test.go b/datasource/schema/object_attribute_test.go
index c517465ed..ab3c428ce 100644
--- a/datasource/schema/object_attribute_test.go
+++ b/datasource/schema/object_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/diag"
@@ -19,7 +21,6 @@ import (
"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-go/tftypes"
)
func TestObjectAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -404,6 +405,34 @@ func TestObjectAttributeIsSensitive(t *testing.T) {
}
}
+func TestObjectAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.ObjectAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.ObjectAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestObjectAttributeObjectValidators(t *testing.T) {
t.Parallel()
diff --git a/datasource/schema/set_attribute.go b/datasource/schema/set_attribute.go
index 261b02424..859b0558d 100644
--- a/datasource/schema/set_attribute.go
+++ b/datasource/schema/set_attribute.go
@@ -6,6 +6,8 @@ package schema
import (
"context"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
@@ -13,7 +15,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -200,6 +201,11 @@ func (a SetAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not supported in data source schemas.
+func (a SetAttribute) IsWriteOnly() bool {
+ return false
+}
+
// SetValidators returns the Validators field value.
func (a SetAttribute) SetValidators() []validator.Set {
return a.Validators
diff --git a/datasource/schema/set_attribute_test.go b/datasource/schema/set_attribute_test.go
index 793095705..9a0760b9b 100644
--- a/datasource/schema/set_attribute_test.go
+++ b/datasource/schema/set_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/diag"
@@ -19,7 +21,6 @@ import (
"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-go/tftypes"
)
func TestSetAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -398,6 +399,34 @@ func TestSetAttributeIsSensitive(t *testing.T) {
}
}
+func TestSetAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.SetAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.SetAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestSetAttributeSetValidators(t *testing.T) {
t.Parallel()
diff --git a/datasource/schema/set_nested_attribute.go b/datasource/schema/set_nested_attribute.go
index 1b17b8743..26b14e449 100644
--- a/datasource/schema/set_nested_attribute.go
+++ b/datasource/schema/set_nested_attribute.go
@@ -226,6 +226,11 @@ func (a SetNestedAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not supported in data source schemas.
+func (a SetNestedAttribute) IsWriteOnly() bool {
+ return false
+}
+
// SetValidators returns the Validators field value.
func (a SetNestedAttribute) SetValidators() []validator.Set {
return a.Validators
diff --git a/datasource/schema/set_nested_attribute_test.go b/datasource/schema/set_nested_attribute_test.go
index 7f7815f70..4b064ee7c 100644
--- a/datasource/schema/set_nested_attribute_test.go
+++ b/datasource/schema/set_nested_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/diag"
@@ -19,7 +21,6 @@ import (
"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-go/tftypes"
)
func TestSetNestedAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -569,6 +570,34 @@ func TestSetNestedAttributeIsSensitive(t *testing.T) {
}
}
+func TestSetNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.SetNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.SetNestedAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestSetNestedAttributeSetValidators(t *testing.T) {
t.Parallel()
diff --git a/datasource/schema/single_nested_attribute.go b/datasource/schema/single_nested_attribute.go
index 811e76de4..b6f4e7f32 100644
--- a/datasource/schema/single_nested_attribute.go
+++ b/datasource/schema/single_nested_attribute.go
@@ -240,6 +240,11 @@ func (a SingleNestedAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not supported in data source schemas.
+func (a SingleNestedAttribute) IsWriteOnly() bool {
+ return false
+}
+
// ObjectValidators returns the Validators field value.
func (a SingleNestedAttribute) ObjectValidators() []validator.Object {
return a.Validators
diff --git a/datasource/schema/single_nested_attribute_test.go b/datasource/schema/single_nested_attribute_test.go
index 25a5e565e..2a62a8e42 100644
--- a/datasource/schema/single_nested_attribute_test.go
+++ b/datasource/schema/single_nested_attribute_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -16,7 +18,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestSingleNestedAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -530,6 +531,34 @@ func TestSingleNestedAttributeIsSensitive(t *testing.T) {
}
}
+func TestSingleNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.SingleNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.SingleNestedAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestSingleNestedAttributeObjectValidators(t *testing.T) {
t.Parallel()
diff --git a/datasource/schema/string_attribute.go b/datasource/schema/string_attribute.go
index 0c2dd9aba..95534fece 100644
--- a/datasource/schema/string_attribute.go
+++ b/datasource/schema/string_attribute.go
@@ -4,13 +4,14 @@
package schema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -181,6 +182,11 @@ func (a StringAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not supported in data source schemas.
+func (a StringAttribute) IsWriteOnly() bool {
+ return false
+}
+
// StringValidators returns the Validators field value.
func (a StringAttribute) StringValidators() []validator.String {
return a.Validators
diff --git a/datasource/schema/string_attribute_test.go b/datasource/schema/string_attribute_test.go
index d436b9bd1..f86110feb 100644
--- a/datasource/schema/string_attribute_test.go
+++ b/datasource/schema/string_attribute_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -16,7 +18,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestStringAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -390,6 +391,34 @@ func TestStringAttributeIsSensitive(t *testing.T) {
}
}
+func TestStringAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.StringAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.StringAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestStringAttributeStringValidators(t *testing.T) {
t.Parallel()
diff --git a/ephemeral/schema/bool_attribute.go b/ephemeral/schema/bool_attribute.go
index 47e248aae..56790dee2 100644
--- a/ephemeral/schema/bool_attribute.go
+++ b/ephemeral/schema/bool_attribute.go
@@ -4,13 +4,14 @@
package schema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -183,3 +184,9 @@ func (a BoolAttribute) IsRequired() bool {
func (a BoolAttribute) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to ephemeral resource schemas,
+// as these schemas describe data that is explicitly not saved to any artifact.
+func (a BoolAttribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/ephemeral/schema/bool_attribute_test.go b/ephemeral/schema/bool_attribute_test.go
index 077ee1f1f..7eebac422 100644
--- a/ephemeral/schema/bool_attribute_test.go
+++ b/ephemeral/schema/bool_attribute_test.go
@@ -9,6 +9,7 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -423,3 +424,31 @@ func TestBoolAttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestBoolAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.BoolAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.BoolAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/ephemeral/schema/dynamic_attribute.go b/ephemeral/schema/dynamic_attribute.go
index 5088fef63..9cd22e70a 100644
--- a/ephemeral/schema/dynamic_attribute.go
+++ b/ephemeral/schema/dynamic_attribute.go
@@ -4,13 +4,14 @@
package schema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -180,6 +181,12 @@ func (a DynamicAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to ephemeral resource schemas,
+// as these schemas describe data that is explicitly not saved to any artifact.
+func (a DynamicAttribute) IsWriteOnly() bool {
+ return false
+}
+
// DynamicValidators returns the Validators field value.
func (a DynamicAttribute) DynamicValidators() []validator.Dynamic {
return a.Validators
diff --git a/ephemeral/schema/dynamic_attribute_test.go b/ephemeral/schema/dynamic_attribute_test.go
index a718167a6..99fbf6f4e 100644
--- a/ephemeral/schema/dynamic_attribute_test.go
+++ b/ephemeral/schema/dynamic_attribute_test.go
@@ -390,6 +390,34 @@ func TestDynamicAttributeIsSensitive(t *testing.T) {
}
}
+func TestDynamicAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.DynamicAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.DynamicAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestDynamicAttributeDynamicValidators(t *testing.T) {
t.Parallel()
diff --git a/ephemeral/schema/float32_attribute.go b/ephemeral/schema/float32_attribute.go
index 93ce2ac7d..46af5bae2 100644
--- a/ephemeral/schema/float32_attribute.go
+++ b/ephemeral/schema/float32_attribute.go
@@ -187,3 +187,9 @@ func (a Float32Attribute) IsRequired() bool {
func (a Float32Attribute) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to ephemeral resource schemas,
+// as these schemas describe data that is explicitly not saved to any artifact.
+func (a Float32Attribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/ephemeral/schema/float32_attribute_test.go b/ephemeral/schema/float32_attribute_test.go
index e9e45d785..f542972aa 100644
--- a/ephemeral/schema/float32_attribute_test.go
+++ b/ephemeral/schema/float32_attribute_test.go
@@ -424,3 +424,31 @@ func TestFloat32AttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestFloat32AttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.Float32Attribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.Float32Attribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/ephemeral/schema/float64_attribute.go b/ephemeral/schema/float64_attribute.go
index ff1912d1e..f4699e210 100644
--- a/ephemeral/schema/float64_attribute.go
+++ b/ephemeral/schema/float64_attribute.go
@@ -4,13 +4,14 @@
package schema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -186,3 +187,9 @@ func (a Float64Attribute) IsRequired() bool {
func (a Float64Attribute) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to ephemeral resource schemas,
+// as these schemas describe data that is explicitly not saved to any artifact.
+func (a Float64Attribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/ephemeral/schema/float64_attribute_test.go b/ephemeral/schema/float64_attribute_test.go
index 4c3f2703a..db9e8db2b 100644
--- a/ephemeral/schema/float64_attribute_test.go
+++ b/ephemeral/schema/float64_attribute_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -16,7 +18,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestFloat64AttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -423,3 +424,31 @@ func TestFloat64AttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestFloat54AttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.Float64Attribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.Float64Attribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/ephemeral/schema/int32_attribute.go b/ephemeral/schema/int32_attribute.go
index f98cbe3b0..76f6e0194 100644
--- a/ephemeral/schema/int32_attribute.go
+++ b/ephemeral/schema/int32_attribute.go
@@ -187,3 +187,9 @@ func (a Int32Attribute) IsRequired() bool {
func (a Int32Attribute) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to ephemeral resource schemas,
+// as these schemas describe data that is explicitly not saved to any artifact.
+func (a Int32Attribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/ephemeral/schema/int32_attribute_test.go b/ephemeral/schema/int32_attribute_test.go
index bd6d4d36c..e118cb0db 100644
--- a/ephemeral/schema/int32_attribute_test.go
+++ b/ephemeral/schema/int32_attribute_test.go
@@ -424,3 +424,31 @@ func TestInt32AttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestInt2AttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.Int32Attribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.Int32Attribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/ephemeral/schema/int64_attribute.go b/ephemeral/schema/int64_attribute.go
index 52b1b7ffd..27cb2dd23 100644
--- a/ephemeral/schema/int64_attribute.go
+++ b/ephemeral/schema/int64_attribute.go
@@ -4,13 +4,14 @@
package schema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -186,3 +187,9 @@ func (a Int64Attribute) IsRequired() bool {
func (a Int64Attribute) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to ephemeral resource schemas,
+// as these schemas describe data that is explicitly not saved to any artifact.
+func (a Int64Attribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/ephemeral/schema/int64_attribute_test.go b/ephemeral/schema/int64_attribute_test.go
index e61165c5f..5f07ff2ff 100644
--- a/ephemeral/schema/int64_attribute_test.go
+++ b/ephemeral/schema/int64_attribute_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -16,7 +18,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestInt64AttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -423,3 +424,31 @@ func TestInt64AttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestInt64AttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.Int64Attribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.Int64Attribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/ephemeral/schema/list_attribute.go b/ephemeral/schema/list_attribute.go
index 3846316b6..258757f99 100644
--- a/ephemeral/schema/list_attribute.go
+++ b/ephemeral/schema/list_attribute.go
@@ -6,6 +6,8 @@ package schema
import (
"context"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
@@ -13,7 +15,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -74,7 +75,6 @@ type ListAttribute struct {
// Sensitive indicates whether the value of this attribute should be
// considered sensitive data. Setting it to true will obscure the value
- // in CLI output.
Sensitive bool
// Description is used in various tooling, like the language server, to
@@ -200,6 +200,12 @@ func (a ListAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to ephemeral resource schemas,
+// as these schemas describe data that is explicitly not saved to any artifact.
+func (a ListAttribute) IsWriteOnly() bool {
+ return false
+}
+
// ListValidators returns the Validators field value.
func (a ListAttribute) ListValidators() []validator.List {
return a.Validators
diff --git a/ephemeral/schema/list_attribute_test.go b/ephemeral/schema/list_attribute_test.go
index 1b22bbbdc..4ab1985d8 100644
--- a/ephemeral/schema/list_attribute_test.go
+++ b/ephemeral/schema/list_attribute_test.go
@@ -521,3 +521,31 @@ func TestListAttributeValidateImplementation(t *testing.T) {
})
}
}
+
+func TestListAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.ListAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.ListAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/ephemeral/schema/list_nested_attribute.go b/ephemeral/schema/list_nested_attribute.go
index 4a91f2742..a4478fb48 100644
--- a/ephemeral/schema/list_nested_attribute.go
+++ b/ephemeral/schema/list_nested_attribute.go
@@ -7,6 +7,8 @@ import (
"context"
"fmt"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
@@ -14,7 +16,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -228,6 +229,12 @@ func (a ListNestedAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to ephemeral resource schemas,
+// as these schemas describe data that is explicitly not saved to any artifact.
+func (a ListNestedAttribute) IsWriteOnly() bool {
+ return false
+}
+
// ListValidators returns the Validators field value.
func (a ListNestedAttribute) ListValidators() []validator.List {
return a.Validators
diff --git a/ephemeral/schema/list_nested_attribute_test.go b/ephemeral/schema/list_nested_attribute_test.go
index 3d1ae651d..7e1153e79 100644
--- a/ephemeral/schema/list_nested_attribute_test.go
+++ b/ephemeral/schema/list_nested_attribute_test.go
@@ -569,6 +569,34 @@ func TestListNestedAttributeIsSensitive(t *testing.T) {
}
}
+func TestListNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.ListNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.ListNestedAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestListNestedAttributeListValidators(t *testing.T) {
t.Parallel()
diff --git a/ephemeral/schema/map_attribute.go b/ephemeral/schema/map_attribute.go
index 366619ed5..0741747a4 100644
--- a/ephemeral/schema/map_attribute.go
+++ b/ephemeral/schema/map_attribute.go
@@ -6,6 +6,8 @@ package schema
import (
"context"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
@@ -13,7 +15,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -203,6 +204,12 @@ func (a MapAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to ephemeral resource schemas,
+// as these schemas describe data that is explicitly not saved to any artifact.
+func (a MapAttribute) IsWriteOnly() bool {
+ return false
+}
+
// MapValidators returns the Validators field value.
func (a MapAttribute) MapValidators() []validator.Map {
return a.Validators
diff --git a/ephemeral/schema/map_attribute_test.go b/ephemeral/schema/map_attribute_test.go
index 94219da83..7da8aa46f 100644
--- a/ephemeral/schema/map_attribute_test.go
+++ b/ephemeral/schema/map_attribute_test.go
@@ -398,6 +398,34 @@ func TestMapAttributeIsSensitive(t *testing.T) {
}
}
+func TestMapAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.MapAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.MapAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestMapAttributeMapValidators(t *testing.T) {
t.Parallel()
diff --git a/ephemeral/schema/map_nested_attribute.go b/ephemeral/schema/map_nested_attribute.go
index 11d701cc9..de057e106 100644
--- a/ephemeral/schema/map_nested_attribute.go
+++ b/ephemeral/schema/map_nested_attribute.go
@@ -229,6 +229,12 @@ func (a MapNestedAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to ephemeral resource schemas,
+// as these schemas describe data that is explicitly not saved to any artifact.
+func (a MapNestedAttribute) IsWriteOnly() bool {
+ return false
+}
+
// MapValidators returns the Validators field value.
func (a MapNestedAttribute) MapValidators() []validator.Map {
return a.Validators
diff --git a/ephemeral/schema/map_nested_attribute_test.go b/ephemeral/schema/map_nested_attribute_test.go
index 0e7986f89..ef21cf89b 100644
--- a/ephemeral/schema/map_nested_attribute_test.go
+++ b/ephemeral/schema/map_nested_attribute_test.go
@@ -569,6 +569,34 @@ func TestMapNestedAttributeIsSensitive(t *testing.T) {
}
}
+func TestMapNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.MapNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.MapNestedAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestMapNestedAttributeMapNestedValidators(t *testing.T) {
t.Parallel()
diff --git a/ephemeral/schema/number_attribute.go b/ephemeral/schema/number_attribute.go
index 547396409..17d557398 100644
--- a/ephemeral/schema/number_attribute.go
+++ b/ephemeral/schema/number_attribute.go
@@ -4,13 +4,14 @@
package schema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -183,6 +184,12 @@ func (a NumberAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to ephemeral resource schemas,
+// as these schemas describe data that is explicitly not saved to any artifact.
+func (a NumberAttribute) IsWriteOnly() bool {
+ return false
+}
+
// NumberValidators returns the Validators field value.
func (a NumberAttribute) NumberValidators() []validator.Number {
return a.Validators
diff --git a/ephemeral/schema/number_attribute_test.go b/ephemeral/schema/number_attribute_test.go
index 7e326b90e..d3636d006 100644
--- a/ephemeral/schema/number_attribute_test.go
+++ b/ephemeral/schema/number_attribute_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -16,7 +18,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestNumberAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -390,6 +391,34 @@ func TestNumberAttributeIsSensitive(t *testing.T) {
}
}
+func TestNumberAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.NumberAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.NumberAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestNumberAttributeNumberValidators(t *testing.T) {
t.Parallel()
diff --git a/ephemeral/schema/object_attribute.go b/ephemeral/schema/object_attribute.go
index 1ff506f9d..fa808e4ba 100644
--- a/ephemeral/schema/object_attribute.go
+++ b/ephemeral/schema/object_attribute.go
@@ -6,6 +6,8 @@ package schema
import (
"context"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
@@ -13,7 +15,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -76,7 +77,6 @@ type ObjectAttribute struct {
// Sensitive indicates whether the value of this attribute should be
// considered sensitive data. Setting it to true will obscure the value
- // in CLI output.
Sensitive bool
// Description is used in various tooling, like the language server, to
@@ -202,6 +202,12 @@ func (a ObjectAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to ephemeral resource schemas,
+// as these schemas describe data that is explicitly not saved to any artifact.
+func (a ObjectAttribute) IsWriteOnly() bool {
+ return false
+}
+
// ObjectValidators returns the Validators field value.
func (a ObjectAttribute) ObjectValidators() []validator.Object {
return a.Validators
diff --git a/ephemeral/schema/object_attribute_test.go b/ephemeral/schema/object_attribute_test.go
index 429aba1e7..76f4aab38 100644
--- a/ephemeral/schema/object_attribute_test.go
+++ b/ephemeral/schema/object_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema"
@@ -19,7 +21,6 @@ import (
"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-go/tftypes"
)
func TestObjectAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -438,6 +439,34 @@ func TestObjectAttributeObjectValidators(t *testing.T) {
}
}
+func TestObjectAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.ObjectAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.ObjectAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestObjectAttributeValidateImplementation(t *testing.T) {
t.Parallel()
diff --git a/ephemeral/schema/set_attribute.go b/ephemeral/schema/set_attribute.go
index 7f6a408ed..7ecd08ffd 100644
--- a/ephemeral/schema/set_attribute.go
+++ b/ephemeral/schema/set_attribute.go
@@ -6,6 +6,8 @@ package schema
import (
"context"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
@@ -13,7 +15,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -198,6 +199,12 @@ func (a SetAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to ephemeral resource schemas,
+// as these schemas describe data that is explicitly not saved to any artifact.
+func (a SetAttribute) IsWriteOnly() bool {
+ return false
+}
+
// SetValidators returns the Validators field value.
func (a SetAttribute) SetValidators() []validator.Set {
return a.Validators
diff --git a/ephemeral/schema/set_attribute_test.go b/ephemeral/schema/set_attribute_test.go
index 0b6903829..a6211d666 100644
--- a/ephemeral/schema/set_attribute_test.go
+++ b/ephemeral/schema/set_attribute_test.go
@@ -398,6 +398,34 @@ func TestSetAttributeIsSensitive(t *testing.T) {
}
}
+func TestSetAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.SetAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.SetAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestSetAttributeSetValidators(t *testing.T) {
t.Parallel()
diff --git a/ephemeral/schema/set_nested_attribute.go b/ephemeral/schema/set_nested_attribute.go
index 7ed6d2fdf..658dc0df7 100644
--- a/ephemeral/schema/set_nested_attribute.go
+++ b/ephemeral/schema/set_nested_attribute.go
@@ -224,6 +224,12 @@ func (a SetNestedAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to ephemeral resource schemas,
+// as these schemas describe data that is explicitly not saved to any artifact.
+func (a SetNestedAttribute) IsWriteOnly() bool {
+ return false
+}
+
// SetValidators returns the Validators field value.
func (a SetNestedAttribute) SetValidators() []validator.Set {
return a.Validators
diff --git a/ephemeral/schema/set_nested_attribute_test.go b/ephemeral/schema/set_nested_attribute_test.go
index 1bd3daa65..bcfa627a4 100644
--- a/ephemeral/schema/set_nested_attribute_test.go
+++ b/ephemeral/schema/set_nested_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema"
@@ -19,7 +21,6 @@ import (
"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-go/tftypes"
)
func TestSetNestedAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -569,6 +570,34 @@ func TestSetNestedAttributeIsSensitive(t *testing.T) {
}
}
+func TestSetNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.SetNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.SetNestedAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestSetNestedAttributeSetValidators(t *testing.T) {
t.Parallel()
diff --git a/ephemeral/schema/single_nested_attribute.go b/ephemeral/schema/single_nested_attribute.go
index a57db9c65..167b8c136 100644
--- a/ephemeral/schema/single_nested_attribute.go
+++ b/ephemeral/schema/single_nested_attribute.go
@@ -238,6 +238,12 @@ func (a SingleNestedAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to ephemeral resource schemas,
+// as these schemas describe data that is explicitly not saved to any artifact.
+func (a SingleNestedAttribute) IsWriteOnly() bool {
+ return false
+}
+
// ObjectValidators returns the Validators field value.
func (a SingleNestedAttribute) ObjectValidators() []validator.Object {
return a.Validators
diff --git a/ephemeral/schema/single_nested_attribute_test.go b/ephemeral/schema/single_nested_attribute_test.go
index 5b7209edb..d3690828f 100644
--- a/ephemeral/schema/single_nested_attribute_test.go
+++ b/ephemeral/schema/single_nested_attribute_test.go
@@ -530,6 +530,34 @@ func TestSingleNestedAttributeIsSensitive(t *testing.T) {
}
}
+func TestSingleNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.SingleNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.SingleNestedAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestSingleNestedAttributeObjectValidators(t *testing.T) {
t.Parallel()
diff --git a/ephemeral/schema/string_attribute.go b/ephemeral/schema/string_attribute.go
index 1b5c7db22..3cfefe97c 100644
--- a/ephemeral/schema/string_attribute.go
+++ b/ephemeral/schema/string_attribute.go
@@ -4,13 +4,14 @@
package schema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -179,6 +180,12 @@ func (a StringAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to ephemeral resource schemas,
+// as these schemas describe data that is explicitly not saved to any artifact.
+func (a StringAttribute) IsWriteOnly() bool {
+ return false
+}
+
// StringValidators returns the Validators field value.
func (a StringAttribute) StringValidators() []validator.String {
return a.Validators
diff --git a/ephemeral/schema/string_attribute_test.go b/ephemeral/schema/string_attribute_test.go
index 1c95add7d..e0577b3cb 100644
--- a/ephemeral/schema/string_attribute_test.go
+++ b/ephemeral/schema/string_attribute_test.go
@@ -390,6 +390,34 @@ func TestStringAttributeIsSensitive(t *testing.T) {
}
}
+func TestStringAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.StringAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.StringAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestStringAttributeStringValidators(t *testing.T) {
t.Parallel()
diff --git a/internal/fromproto5/client_capabilities.go b/internal/fromproto5/client_capabilities.go
index 0eaf40c50..737354888 100644
--- a/internal/fromproto5/client_capabilities.go
+++ b/internal/fromproto5/client_capabilities.go
@@ -89,3 +89,16 @@ func OpenEphemeralResourceClientCapabilities(in *tfprotov5.OpenEphemeralResource
DeferralAllowed: in.DeferralAllowed,
}
}
+
+func ValidateResourceTypeConfigClientCapabilities(in *tfprotov5.ValidateResourceTypeConfigClientCapabilities) resource.ValidateConfigClientCapabilities {
+ if in == nil {
+ // Client did not indicate any supported capabilities
+ return resource.ValidateConfigClientCapabilities{
+ WriteOnlyAttributesAllowed: false,
+ }
+ }
+
+ return resource.ValidateConfigClientCapabilities{
+ WriteOnlyAttributesAllowed: in.WriteOnlyAttributesAllowed,
+ }
+}
diff --git a/internal/fromproto5/validateresourcetypeconfig.go b/internal/fromproto5/validateresourcetypeconfig.go
index ae6e41298..1919f9e9c 100644
--- a/internal/fromproto5/validateresourcetypeconfig.go
+++ b/internal/fromproto5/validateresourcetypeconfig.go
@@ -6,11 +6,12 @@ package fromproto5
import (
"context"
+ "github.com/hashicorp/terraform-plugin-go/tfprotov5"
+
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/resource"
- "github.com/hashicorp/terraform-plugin-go/tfprotov5"
)
// ValidateResourceTypeConfigRequest returns the *fwserver.ValidateResourceConfigRequest
@@ -26,6 +27,7 @@ func ValidateResourceTypeConfigRequest(ctx context.Context, proto5 *tfprotov5.Va
fw.Config = config
fw.Resource = resource
+ fw.ClientCapabilities = ValidateResourceTypeConfigClientCapabilities(proto5.ClientCapabilities)
return fw, diags
}
diff --git a/internal/fromproto5/validateresourcetypeconfig_test.go b/internal/fromproto5/validateresourcetypeconfig_test.go
index 8aec17098..5c07807ed 100644
--- a/internal/fromproto5/validateresourcetypeconfig_test.go
+++ b/internal/fromproto5/validateresourcetypeconfig_test.go
@@ -8,6 +8,9 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tfprotov5"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fromproto5"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -15,8 +18,6 @@ import (
"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/tfprotov5"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestValidateResourceTypeConfigRequest(t *testing.T) {
@@ -88,6 +89,39 @@ func TestValidateResourceTypeConfigRequest(t *testing.T) {
},
},
},
+ "client-capabilities": {
+ input: &tfprotov5.ValidateResourceTypeConfigRequest{
+ Config: &testProto5DynamicValue,
+ ClientCapabilities: &tfprotov5.ValidateResourceTypeConfigClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ },
+ resourceSchema: testFwSchema,
+ expected: &fwserver.ValidateResourceConfigRequest{
+ ClientCapabilities: resource.ValidateConfigClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ Config: &tfsdk.Config{
+ Raw: testProto5Value,
+ Schema: testFwSchema,
+ },
+ },
+ },
+ "client-capabilities-not-set": {
+ input: &tfprotov5.ValidateResourceTypeConfigRequest{
+ Config: &testProto5DynamicValue,
+ },
+ resourceSchema: testFwSchema,
+ expected: &fwserver.ValidateResourceConfigRequest{
+ ClientCapabilities: resource.ValidateConfigClientCapabilities{
+ WriteOnlyAttributesAllowed: false,
+ },
+ Config: &tfsdk.Config{
+ Raw: testProto5Value,
+ Schema: testFwSchema,
+ },
+ },
+ },
}
for name, testCase := range testCases {
diff --git a/internal/fromproto6/client_capabilities.go b/internal/fromproto6/client_capabilities.go
index cd9c92b9c..d22d81623 100644
--- a/internal/fromproto6/client_capabilities.go
+++ b/internal/fromproto6/client_capabilities.go
@@ -89,3 +89,16 @@ func OpenEphemeralResourceClientCapabilities(in *tfprotov6.OpenEphemeralResource
DeferralAllowed: in.DeferralAllowed,
}
}
+
+func ValidateResourceConfigClientCapabilities(in *tfprotov6.ValidateResourceConfigClientCapabilities) resource.ValidateConfigClientCapabilities {
+ if in == nil {
+ // Client did not indicate any supported capabilities
+ return resource.ValidateConfigClientCapabilities{
+ WriteOnlyAttributesAllowed: false,
+ }
+ }
+
+ return resource.ValidateConfigClientCapabilities{
+ WriteOnlyAttributesAllowed: in.WriteOnlyAttributesAllowed,
+ }
+}
diff --git a/internal/fromproto6/validateresourceconfig.go b/internal/fromproto6/validateresourceconfig.go
index 1eb65aa87..f3ea643c9 100644
--- a/internal/fromproto6/validateresourceconfig.go
+++ b/internal/fromproto6/validateresourceconfig.go
@@ -6,11 +6,12 @@ package fromproto6
import (
"context"
+ "github.com/hashicorp/terraform-plugin-go/tfprotov6"
+
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/resource"
- "github.com/hashicorp/terraform-plugin-go/tfprotov6"
)
// ValidateResourceConfigRequest returns the *fwserver.ValidateResourceConfigRequest
@@ -26,6 +27,7 @@ func ValidateResourceConfigRequest(ctx context.Context, proto6 *tfprotov6.Valida
fw.Config = config
fw.Resource = resource
+ fw.ClientCapabilities = ValidateResourceConfigClientCapabilities(proto6.ClientCapabilities)
return fw, diags
}
diff --git a/internal/fromproto6/validateresourceconfig_test.go b/internal/fromproto6/validateresourceconfig_test.go
index 4e21d432c..f649a0d77 100644
--- a/internal/fromproto6/validateresourceconfig_test.go
+++ b/internal/fromproto6/validateresourceconfig_test.go
@@ -8,6 +8,9 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tfprotov6"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fromproto6"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -15,8 +18,6 @@ import (
"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/tfprotov6"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestValidateResourceConfigRequest(t *testing.T) {
@@ -88,6 +89,39 @@ func TestValidateResourceConfigRequest(t *testing.T) {
},
},
},
+ "client-capabilities": {
+ input: &tfprotov6.ValidateResourceConfigRequest{
+ Config: &testProto6DynamicValue,
+ ClientCapabilities: &tfprotov6.ValidateResourceConfigClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ },
+ resourceSchema: testFwSchema,
+ expected: &fwserver.ValidateResourceConfigRequest{
+ ClientCapabilities: resource.ValidateConfigClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ Config: &tfsdk.Config{
+ Raw: testProto6Value,
+ Schema: testFwSchema,
+ },
+ },
+ },
+ "client-capabilities-not-set": {
+ input: &tfprotov6.ValidateResourceConfigRequest{
+ Config: &testProto6DynamicValue,
+ },
+ resourceSchema: testFwSchema,
+ expected: &fwserver.ValidateResourceConfigRequest{
+ ClientCapabilities: resource.ValidateConfigClientCapabilities{
+ WriteOnlyAttributesAllowed: false,
+ },
+ Config: &tfsdk.Config{
+ Raw: testProto6Value,
+ Schema: testFwSchema,
+ },
+ },
+ },
}
for name, testCase := range testCases {
diff --git a/internal/fwschema/attribute.go b/internal/fwschema/attribute.go
index 4face5a95..6c440b319 100644
--- a/internal/fwschema/attribute.go
+++ b/internal/fwschema/attribute.go
@@ -4,8 +4,9 @@
package fwschema
import (
- "github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-go/tftypes"
+
+ "github.com/hashicorp/terraform-plugin-framework/attr"
)
// Attribute is the core interface required for implementing Terraform
@@ -63,6 +64,13 @@ type Attribute interface {
// sensitive. This is named differently than Sensitive to prevent a
// conflict with the tfsdk.Attribute field name.
IsSensitive() bool
+
+ // IsWriteOnly should return true if the attribute configuration value is
+ // write-only. This is named differently than WriteOnly to prevent a
+ // conflict with the tfsdk.Attribute field name.
+ //
+ // Write-only attributes are a managed-resource schema concept only.
+ IsWriteOnly() bool
}
// AttributesEqual is a helper function to perform equality testing on two
@@ -101,5 +109,9 @@ func AttributesEqual(a, b Attribute) bool {
return false
}
+ if a.IsWriteOnly() != b.IsWriteOnly() {
+ return false
+ }
+
return true
}
diff --git a/internal/fwschema/write_only_nested_attribute_validation.go b/internal/fwschema/write_only_nested_attribute_validation.go
new file mode 100644
index 000000000..4cba87413
--- /dev/null
+++ b/internal/fwschema/write_only_nested_attribute_validation.go
@@ -0,0 +1,73 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package fwschema
+
+import (
+ "fmt"
+
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+)
+
+// ContainsAllWriteOnlyChildAttributes will return true if all child attributes for the
+// given nested attribute have WriteOnly set to true.
+func ContainsAllWriteOnlyChildAttributes(nestedAttr NestedAttribute) bool {
+ nestedObjAttrs := nestedAttr.GetNestedObject().GetAttributes()
+
+ for _, childAttr := range nestedObjAttrs {
+ if !childAttr.IsWriteOnly() {
+ return false
+ }
+
+ nestedAttribute, ok := childAttr.(NestedAttribute)
+ if ok {
+ if !ContainsAllWriteOnlyChildAttributes(nestedAttribute) {
+ return false
+ }
+ }
+ }
+
+ return true
+}
+
+// ContainsAnyWriteOnlyChildAttributes will return true if any child attribute for the
+// given nested attribute has WriteOnly set to true.
+func ContainsAnyWriteOnlyChildAttributes(nestedAttr NestedAttribute) bool {
+ nestedObjAttrs := nestedAttr.GetNestedObject().GetAttributes()
+
+ for _, childAttr := range nestedObjAttrs {
+ if childAttr.IsWriteOnly() {
+ return true
+ }
+
+ nestedAttribute, ok := childAttr.(NestedAttribute)
+ if ok {
+ if ContainsAnyWriteOnlyChildAttributes(nestedAttribute) {
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
+func InvalidWriteOnlyNestedAttributeDiag(attributePath path.Path) diag.Diagnostic {
+ return diag.NewErrorDiagnostic(
+ "Invalid Schema Implementation",
+ "When validating the schema, an implementation issue was found. "+
+ "This is always an issue with the provider and should be reported to the provider developers.\n\n"+
+ fmt.Sprintf("%q is a WriteOnly nested attribute that contains a non-WriteOnly child attribute.\n\n", attributePath)+
+ "Every child attribute of a WriteOnly nested attribute must also have WriteOnly set to true.",
+ )
+}
+
+func InvalidComputedNestedAttributeWithWriteOnlyDiag(attributePath path.Path) diag.Diagnostic {
+ return diag.NewErrorDiagnostic(
+ "Invalid Schema Implementation",
+ "When validating the schema, an implementation issue was found. "+
+ "This is always an issue with the provider and should be reported to the provider developers.\n\n"+
+ fmt.Sprintf("%q is a Computed nested attribute that contains a WriteOnly child attribute.\n\n", attributePath)+
+ "Every child attribute of a Computed nested attribute must have WriteOnly set to false.",
+ )
+}
diff --git a/internal/fwschemadata/data_nullify_collection_blocks.go b/internal/fwschemadata/data_nullify_collection_blocks.go
index f907d6d16..a0a19ecd4 100644
--- a/internal/fwschemadata/data_nullify_collection_blocks.go
+++ b/internal/fwschemadata/data_nullify_collection_blocks.go
@@ -7,11 +7,12 @@ import (
"context"
"errors"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fromtftypes"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/logging"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// NullifyCollectionBlocks converts list and set block empty values to null
diff --git a/internal/fwserver/attribute_validation.go b/internal/fwserver/attribute_validation.go
index 98b05f0fb..3c2492b14 100644
--- a/internal/fwserver/attribute_validation.go
+++ b/internal/fwserver/attribute_validation.go
@@ -34,6 +34,11 @@ type ValidateAttributeRequest struct {
// Config contains the entire configuration of the data source, provider, or resource.
Config tfsdk.Config
+
+ // ClientCapabilities defines optionally supported protocol features for
+ // schema validation RPCs, such as forward-compatible Terraform
+ // behavior changes.
+ ClientCapabilities validator.ValidateSchemaClientCapabilities
}
// ValidateAttributeResponse represents a response to a
@@ -65,6 +70,24 @@ func AttributeValidate(ctx context.Context, a fwschema.Attribute, req ValidateAt
return
}
+ if a.IsWriteOnly() && a.IsRequired() && a.IsOptional() {
+ resp.Diagnostics.AddAttributeError(
+ req.AttributePath,
+ "Invalid Attribute Definition",
+ "WriteOnly Attributes must be set with only one of Required or Optional. This is always a problem with the provider and should be reported to the provider developer.",
+ )
+ return
+ }
+
+ if a.IsWriteOnly() && a.IsComputed() {
+ resp.Diagnostics.AddAttributeError(
+ req.AttributePath,
+ "Invalid Attribute Definition",
+ "WriteOnly Attributes cannot be set with Computed. This is always a problem with the provider and should be reported to the provider developer.",
+ )
+ return
+ }
+
configData := &fwschemadata.Data{
Description: fwschemadata.DataDescriptionConfiguration,
Schema: req.Config.Schema,
@@ -106,6 +129,19 @@ func AttributeValidate(ctx context.Context, a fwschema.Attribute, req ValidateAt
)
}
+ // If the client doesn't support write-only attributes (first supported in Terraform v1.11.0), then we raise an early validation error
+ // to avoid a confusing data consistency error when the provider attempts to return "null" for a write-only attribute in the planned/final state.
+ //
+ // Write-only attributes can only be successfully used with a supporting client, so the only option for a practitoner to utilize a write-only attribute
+ // is to upgrade their Terraform CLI version to v1.11.0 or later.
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed && a.IsWriteOnly() && !attributeConfig.IsNull() {
+ resp.Diagnostics.AddAttributeError(
+ req.AttributePath,
+ "WriteOnly Attribute Not Allowed",
+ fmt.Sprintf("The resource contains a non-null value for WriteOnly attribute %s. Write-only attributes are only supported in Terraform 1.11 and later.", req.AttributePath.String()),
+ )
+ }
+
req.AttributeConfig = attributeConfig
switch attributeWithValidators := a.(type) {
@@ -200,10 +236,11 @@ func AttributeValidateBool(ctx context.Context, attribute fwxschema.AttributeWit
}
validateReq := validator.BoolRequest{
- Config: req.Config,
- ConfigValue: configValue,
- Path: req.AttributePath,
- PathExpression: req.AttributePathExpression,
+ ClientCapabilities: req.ClientCapabilities,
+ Config: req.Config,
+ ConfigValue: configValue,
+ Path: req.AttributePath,
+ PathExpression: req.AttributePathExpression,
}
for _, attributeValidator := range attribute.BoolValidators() {
@@ -265,10 +302,11 @@ func AttributeValidateFloat32(ctx context.Context, attribute fwxschema.Attribute
}
validateReq := validator.Float32Request{
- Config: req.Config,
- ConfigValue: configValue,
- Path: req.AttributePath,
- PathExpression: req.AttributePathExpression,
+ ClientCapabilities: req.ClientCapabilities,
+ Config: req.Config,
+ ConfigValue: configValue,
+ Path: req.AttributePath,
+ PathExpression: req.AttributePathExpression,
}
for _, attributeValidator := range attribute.Float32Validators() {
@@ -330,10 +368,11 @@ func AttributeValidateFloat64(ctx context.Context, attribute fwxschema.Attribute
}
validateReq := validator.Float64Request{
- Config: req.Config,
- ConfigValue: configValue,
- Path: req.AttributePath,
- PathExpression: req.AttributePathExpression,
+ ClientCapabilities: req.ClientCapabilities,
+ Config: req.Config,
+ ConfigValue: configValue,
+ Path: req.AttributePath,
+ PathExpression: req.AttributePathExpression,
}
for _, attributeValidator := range attribute.Float64Validators() {
@@ -395,10 +434,11 @@ func AttributeValidateInt32(ctx context.Context, attribute fwxschema.AttributeWi
}
validateReq := validator.Int32Request{
- Config: req.Config,
- ConfigValue: configValue,
- Path: req.AttributePath,
- PathExpression: req.AttributePathExpression,
+ ClientCapabilities: req.ClientCapabilities,
+ Config: req.Config,
+ ConfigValue: configValue,
+ Path: req.AttributePath,
+ PathExpression: req.AttributePathExpression,
}
for _, attributeValidator := range attribute.Int32Validators() {
@@ -460,10 +500,11 @@ func AttributeValidateInt64(ctx context.Context, attribute fwxschema.AttributeWi
}
validateReq := validator.Int64Request{
- Config: req.Config,
- ConfigValue: configValue,
- Path: req.AttributePath,
- PathExpression: req.AttributePathExpression,
+ ClientCapabilities: req.ClientCapabilities,
+ Config: req.Config,
+ ConfigValue: configValue,
+ Path: req.AttributePath,
+ PathExpression: req.AttributePathExpression,
}
for _, attributeValidator := range attribute.Int64Validators() {
@@ -525,10 +566,11 @@ func AttributeValidateList(ctx context.Context, attribute fwxschema.AttributeWit
}
validateReq := validator.ListRequest{
- Config: req.Config,
- ConfigValue: configValue,
- Path: req.AttributePath,
- PathExpression: req.AttributePathExpression,
+ ClientCapabilities: req.ClientCapabilities,
+ Config: req.Config,
+ ConfigValue: configValue,
+ Path: req.AttributePath,
+ PathExpression: req.AttributePathExpression,
}
for _, attributeValidator := range attribute.ListValidators() {
@@ -590,10 +632,11 @@ func AttributeValidateMap(ctx context.Context, attribute fwxschema.AttributeWith
}
validateReq := validator.MapRequest{
- Config: req.Config,
- ConfigValue: configValue,
- Path: req.AttributePath,
- PathExpression: req.AttributePathExpression,
+ ClientCapabilities: req.ClientCapabilities,
+ Config: req.Config,
+ ConfigValue: configValue,
+ Path: req.AttributePath,
+ PathExpression: req.AttributePathExpression,
}
for _, attributeValidator := range attribute.MapValidators() {
@@ -655,10 +698,11 @@ func AttributeValidateNumber(ctx context.Context, attribute fwxschema.AttributeW
}
validateReq := validator.NumberRequest{
- Config: req.Config,
- ConfigValue: configValue,
- Path: req.AttributePath,
- PathExpression: req.AttributePathExpression,
+ ClientCapabilities: req.ClientCapabilities,
+ Config: req.Config,
+ ConfigValue: configValue,
+ Path: req.AttributePath,
+ PathExpression: req.AttributePathExpression,
}
for _, attributeValidator := range attribute.NumberValidators() {
@@ -720,10 +764,11 @@ func AttributeValidateObject(ctx context.Context, attribute fwxschema.AttributeW
}
validateReq := validator.ObjectRequest{
- Config: req.Config,
- ConfigValue: configValue,
- Path: req.AttributePath,
- PathExpression: req.AttributePathExpression,
+ ClientCapabilities: req.ClientCapabilities,
+ Config: req.Config,
+ ConfigValue: configValue,
+ Path: req.AttributePath,
+ PathExpression: req.AttributePathExpression,
}
for _, attributeValidator := range attribute.ObjectValidators() {
@@ -785,10 +830,11 @@ func AttributeValidateSet(ctx context.Context, attribute fwxschema.AttributeWith
}
validateReq := validator.SetRequest{
- Config: req.Config,
- ConfigValue: configValue,
- Path: req.AttributePath,
- PathExpression: req.AttributePathExpression,
+ ClientCapabilities: req.ClientCapabilities,
+ Config: req.Config,
+ ConfigValue: configValue,
+ Path: req.AttributePath,
+ PathExpression: req.AttributePathExpression,
}
for _, attributeValidator := range attribute.SetValidators() {
@@ -850,10 +896,11 @@ func AttributeValidateString(ctx context.Context, attribute fwxschema.AttributeW
}
validateReq := validator.StringRequest{
- Config: req.Config,
- ConfigValue: configValue,
- Path: req.AttributePath,
- PathExpression: req.AttributePathExpression,
+ ClientCapabilities: req.ClientCapabilities,
+ Config: req.Config,
+ ConfigValue: configValue,
+ Path: req.AttributePath,
+ PathExpression: req.AttributePathExpression,
}
for _, attributeValidator := range attribute.StringValidators() {
@@ -915,10 +962,11 @@ func AttributeValidateDynamic(ctx context.Context, attribute fwxschema.Attribute
}
validateReq := validator.DynamicRequest{
- Config: req.Config,
- ConfigValue: configValue,
- Path: req.AttributePath,
- PathExpression: req.AttributePathExpression,
+ ClientCapabilities: req.ClientCapabilities,
+ Config: req.Config,
+ ConfigValue: configValue,
+ Path: req.AttributePath,
+ PathExpression: req.AttributePathExpression,
}
for _, attributeValidator := range attribute.DynamicValidators() {
@@ -992,6 +1040,7 @@ func AttributeValidateNestedAttributes(ctx context.Context, a fwschema.Attribute
AttributePath: req.AttributePath.AtListIndex(idx),
AttributePathExpression: req.AttributePathExpression.AtListIndex(idx),
Config: req.Config,
+ ClientCapabilities: req.ClientCapabilities,
}
nestedAttributeObjectResp := &ValidateAttributeResponse{}
@@ -1026,6 +1075,7 @@ func AttributeValidateNestedAttributes(ctx context.Context, a fwschema.Attribute
AttributePath: req.AttributePath.AtSetValue(value),
AttributePathExpression: req.AttributePathExpression.AtSetValue(value),
Config: req.Config,
+ ClientCapabilities: req.ClientCapabilities,
}
nestedAttributeObjectResp := &ValidateAttributeResponse{}
@@ -1060,6 +1110,7 @@ func AttributeValidateNestedAttributes(ctx context.Context, a fwschema.Attribute
AttributePath: req.AttributePath.AtMapKey(key),
AttributePathExpression: req.AttributePathExpression.AtMapKey(key),
Config: req.Config,
+ ClientCapabilities: req.ClientCapabilities,
}
nestedAttributeObjectResp := &ValidateAttributeResponse{}
@@ -1097,6 +1148,7 @@ func AttributeValidateNestedAttributes(ctx context.Context, a fwschema.Attribute
AttributePath: req.AttributePath,
AttributePathExpression: req.AttributePathExpression,
Config: req.Config,
+ ClientCapabilities: req.ClientCapabilities,
}
nestedAttributeObjectResp := &ValidateAttributeResponse{}
@@ -1144,10 +1196,11 @@ func NestedAttributeObjectValidate(ctx context.Context, o fwschema.NestedAttribu
}
validateReq := validator.ObjectRequest{
- Config: req.Config,
- ConfigValue: object,
- Path: req.AttributePath,
- PathExpression: req.AttributePathExpression,
+ ClientCapabilities: req.ClientCapabilities,
+ Config: req.Config,
+ ConfigValue: object,
+ Path: req.AttributePath,
+ PathExpression: req.AttributePathExpression,
}
for _, objectValidator := range objectWithValidators.ObjectValidators() {
@@ -1182,6 +1235,7 @@ func NestedAttributeObjectValidate(ctx context.Context, o fwschema.NestedAttribu
AttributePath: req.AttributePath.AtName(nestedName),
AttributePathExpression: req.AttributePathExpression.AtName(nestedName),
Config: req.Config,
+ ClientCapabilities: req.ClientCapabilities,
}
nestedAttrResp := &ValidateAttributeResponse{}
diff --git a/internal/fwserver/attribute_validation_test.go b/internal/fwserver/attribute_validation_test.go
index 457264e0f..6f0fbc5ab 100644
--- a/internal/fwserver/attribute_validation_test.go
+++ b/internal/fwserver/attribute_validation_test.go
@@ -1700,6 +1700,275 @@ func TestAttributeValidate(t *testing.T) {
},
},
},
+ "write-only-attr-with-required": {
+ req: ValidateAttributeRequest{
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ AttributePath: path.Root("test"),
+ Config: tfsdk.Config{
+ Raw: tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "test": tftypes.String,
+ },
+ }, map[string]tftypes.Value{
+ "test": tftypes.NewValue(tftypes.String, "testvalue"),
+ }),
+ Schema: testschema.Schema{
+ Attributes: map[string]fwschema.Attribute{
+ "test": testschema.Attribute{
+ Type: types.StringType,
+ WriteOnly: true,
+ Required: true,
+ },
+ },
+ },
+ },
+ },
+ resp: ValidateAttributeResponse{},
+ },
+ "write-only-attr-with-required-null-value": {
+ req: ValidateAttributeRequest{
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ AttributePath: path.Root("test"),
+ Config: tfsdk.Config{
+ Raw: tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "test": tftypes.String,
+ },
+ }, map[string]tftypes.Value{
+ "test": tftypes.NewValue(tftypes.String, nil),
+ }),
+ Schema: testschema.Schema{
+ Attributes: map[string]fwschema.Attribute{
+ "test": testschema.Attribute{
+ Type: types.StringType,
+ WriteOnly: true,
+ Required: true,
+ },
+ },
+ },
+ },
+ },
+ resp: ValidateAttributeResponse{
+ Diagnostics: diag.Diagnostics{
+ diag.NewAttributeErrorDiagnostic(
+ path.Root("test"),
+ "Missing Configuration for Required Attribute",
+ "Must set a configuration value for the test attribute as the provider has marked it as required.\n\n"+
+ "Refer to the provider documentation or contact the provider developers for additional information about configurable attributes that are required.",
+ ),
+ },
+ },
+ },
+ "write-only-attr-with-optional": {
+ req: ValidateAttributeRequest{
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ AttributePath: path.Root("test"),
+ Config: tfsdk.Config{
+ Raw: tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "test": tftypes.String,
+ },
+ }, map[string]tftypes.Value{
+ "test": tftypes.NewValue(tftypes.String, "testvalue"),
+ }),
+ Schema: testschema.Schema{
+ Attributes: map[string]fwschema.Attribute{
+ "test": testschema.Attribute{
+ Type: types.StringType,
+ WriteOnly: true,
+ Optional: true,
+ },
+ },
+ },
+ },
+ },
+ resp: ValidateAttributeResponse{},
+ },
+ "write-only-attr-with-computed": {
+ req: ValidateAttributeRequest{
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ AttributePath: path.Root("test"),
+ Config: tfsdk.Config{
+ Raw: tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "test": tftypes.String,
+ },
+ }, map[string]tftypes.Value{
+ "test": tftypes.NewValue(tftypes.String, nil),
+ }),
+ Schema: testschema.Schema{
+ Attributes: map[string]fwschema.Attribute{
+ "test": testschema.Attribute{
+ Type: types.StringType,
+ WriteOnly: true,
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ resp: ValidateAttributeResponse{
+ Diagnostics: diag.Diagnostics{
+ diag.NewAttributeErrorDiagnostic(
+ path.Root("test"),
+ "Invalid Attribute Definition",
+ "WriteOnly Attributes cannot be set with Computed. This is always a problem with the provider and should be reported to the provider developer.",
+ ),
+ },
+ },
+ },
+ "write-only-attr-missing-required-and-optional": {
+ req: ValidateAttributeRequest{
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ AttributePath: path.Root("test"),
+ Config: tfsdk.Config{
+ Raw: tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "test": tftypes.String,
+ },
+ }, map[string]tftypes.Value{
+ "test": tftypes.NewValue(tftypes.String, "testvalue"),
+ }),
+ Schema: testschema.Schema{
+ Attributes: map[string]fwschema.Attribute{
+ "test": testschema.Attribute{
+ Type: types.StringType,
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ resp: ValidateAttributeResponse{
+ Diagnostics: diag.Diagnostics{
+ diag.NewAttributeErrorDiagnostic(
+ path.Root("test"),
+ "Invalid Attribute Definition",
+ "Attribute missing Required, Optional, or Computed definition. This is always a problem with the provider and should be reported to the provider developer.",
+ ),
+ },
+ },
+ },
+ "write-only-attr-with-required-and-optional": {
+ req: ValidateAttributeRequest{
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ AttributePath: path.Root("test"),
+ Config: tfsdk.Config{
+ Raw: tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "test": tftypes.String,
+ },
+ }, map[string]tftypes.Value{
+ "test": tftypes.NewValue(tftypes.String, "testvalue"),
+ }),
+ Schema: testschema.Schema{
+ Attributes: map[string]fwschema.Attribute{
+ "test": testschema.Attribute{
+ Type: types.StringType,
+ WriteOnly: true,
+ Required: true,
+ Optional: true,
+ },
+ },
+ },
+ },
+ },
+ resp: ValidateAttributeResponse{
+ Diagnostics: diag.Diagnostics{
+ diag.NewAttributeErrorDiagnostic(
+ path.Root("test"),
+ "Invalid Attribute Definition",
+ "WriteOnly Attributes must be set with only one of Required or Optional. This is always a problem with the provider and should be reported to the provider developer.",
+ ),
+ },
+ },
+ },
+ "write-only-attr-with-computed-required-and-optional": {
+ req: ValidateAttributeRequest{
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ AttributePath: path.Root("test"),
+ Config: tfsdk.Config{
+ Raw: tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "test": tftypes.String,
+ },
+ }, map[string]tftypes.Value{
+ "test": tftypes.NewValue(tftypes.String, "testvalue"),
+ }),
+ Schema: testschema.Schema{
+ Attributes: map[string]fwschema.Attribute{
+ "test": testschema.Attribute{
+ Type: types.StringType,
+ WriteOnly: true,
+ Required: true,
+ Optional: true,
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ resp: ValidateAttributeResponse{
+ Diagnostics: diag.Diagnostics{
+ diag.NewAttributeErrorDiagnostic(
+ path.Root("test"),
+ "Invalid Attribute Definition",
+ "WriteOnly Attributes must be set with only one of Required or Optional. This is always a problem with the provider and should be reported to the provider developer.",
+ ),
+ },
+ },
+ },
+ "write-only-attr-set-no-client-capability": {
+ req: ValidateAttributeRequest{
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ // Client indicating it doesn't support write-only attributes
+ WriteOnlyAttributesAllowed: false,
+ },
+ AttributePath: path.Root("test"),
+ Config: tfsdk.Config{
+ Raw: tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "test": tftypes.String,
+ },
+ }, map[string]tftypes.Value{
+ "test": tftypes.NewValue(tftypes.String, "hello world!"),
+ }),
+ Schema: testschema.Schema{
+ Attributes: map[string]fwschema.Attribute{
+ "test": testschema.Attribute{
+ Required: true,
+ WriteOnly: true,
+ Type: types.StringType,
+ },
+ },
+ },
+ },
+ },
+ resp: ValidateAttributeResponse{
+ Diagnostics: diag.Diagnostics{
+ diag.NewAttributeErrorDiagnostic(
+ path.Root("test"),
+ "WriteOnly Attribute Not Allowed",
+ "The resource contains a non-null value for WriteOnly attribute test. "+
+ "Write-only attributes are only supported in Terraform 1.11 and later.",
+ ),
+ },
+ },
+ },
}
for name, tc := range testCases {
@@ -1786,6 +2055,32 @@ func TestAttributeValidateBool(t *testing.T) {
response: &ValidateAttributeResponse{},
expected: &ValidateAttributeResponse{},
},
+ "request-client-capabilities": {
+ attribute: testschema.AttributeWithBoolValidators{
+ Validators: []validator.Bool{
+ testvalidator.Bool{
+ ValidateBoolMethod: func(ctx context.Context, req validator.BoolRequest, resp *validator.BoolResponse) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected BoolRequest.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ request: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ AttributeConfig: types.BoolValue(true),
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ },
+ response: &ValidateAttributeResponse{},
+ expected: &ValidateAttributeResponse{},
+ },
+
"request-config": {
attribute: testschema.AttributeWithBoolValidators{
Validators: []validator.Bool{
@@ -1990,6 +2285,32 @@ func TestAttributeValidateFloat32(t *testing.T) {
response: &ValidateAttributeResponse{},
expected: &ValidateAttributeResponse{},
},
+ "request-client-capabilities": {
+ attribute: testschema.AttributeWithFloat32Validators{
+ Validators: []validator.Float32{
+ testvalidator.Float32{
+ ValidateFloat32Method: func(ctx context.Context, req validator.Float32Request, resp *validator.Float32Response) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected Float32Request.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ request: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ AttributeConfig: types.Float32Value(0.1),
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ },
+ response: &ValidateAttributeResponse{},
+ expected: &ValidateAttributeResponse{},
+ },
+
"request-config": {
attribute: testschema.AttributeWithFloat32Validators{
Validators: []validator.Float32{
@@ -2194,6 +2515,32 @@ func TestAttributeValidateFloat64(t *testing.T) {
response: &ValidateAttributeResponse{},
expected: &ValidateAttributeResponse{},
},
+ "request-client-capabilities": {
+ attribute: testschema.AttributeWithFloat64Validators{
+ Validators: []validator.Float64{
+ testvalidator.Float64{
+ ValidateFloat64Method: func(ctx context.Context, req validator.Float64Request, resp *validator.Float64Response) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected Float64Request.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ request: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ AttributeConfig: types.Float64Value(0.2),
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ },
+ response: &ValidateAttributeResponse{},
+ expected: &ValidateAttributeResponse{},
+ },
+
"request-config": {
attribute: testschema.AttributeWithFloat64Validators{
Validators: []validator.Float64{
@@ -2398,6 +2745,32 @@ func TestAttributeValidateInt32(t *testing.T) {
response: &ValidateAttributeResponse{},
expected: &ValidateAttributeResponse{},
},
+ "request-client-capabilities": {
+ attribute: testschema.AttributeWithInt32Validators{
+ Validators: []validator.Int32{
+ testvalidator.Int32{
+ ValidateInt32Method: func(ctx context.Context, req validator.Int32Request, resp *validator.Int32Response) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected Int32Request.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ request: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ AttributeConfig: types.Int32Value(1),
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ },
+ response: &ValidateAttributeResponse{},
+ expected: &ValidateAttributeResponse{},
+ },
+
"request-config": {
attribute: testschema.AttributeWithInt32Validators{
Validators: []validator.Int32{
@@ -2602,6 +2975,32 @@ func TestAttributeValidateInt64(t *testing.T) {
response: &ValidateAttributeResponse{},
expected: &ValidateAttributeResponse{},
},
+ "request-client-capabilities": {
+ attribute: testschema.AttributeWithInt64Validators{
+ Validators: []validator.Int64{
+ testvalidator.Int64{
+ ValidateInt64Method: func(ctx context.Context, req validator.Int64Request, resp *validator.Int64Response) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected Int64Request.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ request: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ AttributeConfig: types.Int64Value(2),
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ },
+ response: &ValidateAttributeResponse{},
+ expected: &ValidateAttributeResponse{},
+ },
+
"request-config": {
attribute: testschema.AttributeWithInt64Validators{
Validators: []validator.Int64{
@@ -2808,6 +3207,32 @@ func TestAttributeValidateList(t *testing.T) {
response: &ValidateAttributeResponse{},
expected: &ValidateAttributeResponse{},
},
+ "request-client-capabilities": {
+ attribute: testschema.AttributeWithListValidators{
+ Validators: []validator.List{
+ testvalidator.List{
+ ValidateListMethod: func(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected ListRequest.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ request: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ AttributeConfig: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("test")}),
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ },
+ response: &ValidateAttributeResponse{},
+ expected: &ValidateAttributeResponse{},
+ },
+
"request-config": {
attribute: testschema.AttributeWithListValidators{
ElementType: types.StringType,
@@ -3033,6 +3458,35 @@ func TestAttributeValidateMap(t *testing.T) {
response: &ValidateAttributeResponse{},
expected: &ValidateAttributeResponse{},
},
+ "request-client-capabilities": {
+ attribute: testschema.AttributeWithMapValidators{
+ Validators: []validator.Map{
+ testvalidator.Map{
+ ValidateMapMethod: func(ctx context.Context, req validator.MapRequest, resp *validator.MapResponse) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected MapRequest.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ request: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ AttributeConfig: types.MapValueMust(
+ types.StringType,
+ map[string]attr.Value{"testkey": types.StringValue("testvalue")},
+ ),
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ },
+ response: &ValidateAttributeResponse{},
+ expected: &ValidateAttributeResponse{},
+ },
+
"request-config": {
attribute: testschema.AttributeWithMapValidators{
ElementType: types.StringType,
@@ -3262,6 +3716,32 @@ func TestAttributeValidateNumber(t *testing.T) {
response: &ValidateAttributeResponse{},
expected: &ValidateAttributeResponse{},
},
+ "request-client-capabilities": {
+ attribute: testschema.AttributeWithNumberValidators{
+ Validators: []validator.Number{
+ testvalidator.Number{
+ ValidateNumberMethod: func(ctx context.Context, req validator.NumberRequest, resp *validator.NumberResponse) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected NumberRequest.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ request: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ AttributeConfig: types.NumberValue(big.NewFloat(1.2)),
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ },
+ response: &ValidateAttributeResponse{},
+ expected: &ValidateAttributeResponse{},
+ },
+
"request-config": {
attribute: testschema.AttributeWithNumberValidators{
Validators: []validator.Number{
@@ -3478,6 +3958,35 @@ func TestAttributeValidateObject(t *testing.T) {
response: &ValidateAttributeResponse{},
expected: &ValidateAttributeResponse{},
},
+ "request-client-capabilities": {
+ attribute: testschema.AttributeWithObjectValidators{
+ Validators: []validator.Object{
+ testvalidator.Object{
+ ValidateObjectMethod: func(ctx context.Context, req validator.ObjectRequest, resp *validator.ObjectResponse) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected ObjectRequest.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ request: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ AttributeConfig: types.ObjectValueMust(
+ map[string]attr.Type{"testattr": types.StringType},
+ map[string]attr.Value{"testattr": types.StringValue("testvalue")},
+ ),
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ },
+ response: &ValidateAttributeResponse{},
+ expected: &ValidateAttributeResponse{},
+ },
+
"request-config": {
attribute: testschema.AttributeWithObjectValidators{
AttributeTypes: map[string]attr.Type{
@@ -3715,6 +4224,32 @@ func TestAttributeValidateSet(t *testing.T) {
response: &ValidateAttributeResponse{},
expected: &ValidateAttributeResponse{},
},
+ "request-client-capabilities": {
+ attribute: testschema.AttributeWithSetValidators{
+ Validators: []validator.Set{
+ testvalidator.Set{
+ ValidateSetMethod: func(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected SetRequest.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ request: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ AttributeConfig: types.SetValueMust(types.StringType, []attr.Value{types.StringValue("test")}),
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ },
+ response: &ValidateAttributeResponse{},
+ expected: &ValidateAttributeResponse{},
+ },
+
"request-config": {
attribute: testschema.AttributeWithSetValidators{
ElementType: types.StringType,
@@ -3932,6 +4467,32 @@ func TestAttributeValidateString(t *testing.T) {
response: &ValidateAttributeResponse{},
expected: &ValidateAttributeResponse{},
},
+ "request-client-capabilities": {
+ attribute: testschema.AttributeWithStringValidators{
+ Validators: []validator.String{
+ testvalidator.String{
+ ValidateStringMethod: func(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected StringRequest.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ request: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ AttributeConfig: types.StringValue("testVal"),
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ },
+ response: &ValidateAttributeResponse{},
+ expected: &ValidateAttributeResponse{},
+ },
+
"request-config": {
attribute: testschema.AttributeWithStringValidators{
Validators: []validator.String{
@@ -4136,6 +4697,32 @@ func TestAttributeValidateDynamic(t *testing.T) {
response: &ValidateAttributeResponse{},
expected: &ValidateAttributeResponse{},
},
+ "request-client-capabilities": {
+ attribute: testschema.AttributeWithDynamicValidators{
+ Validators: []validator.Dynamic{
+ testvalidator.Dynamic{
+ ValidateDynamicMethod: func(ctx context.Context, req validator.DynamicRequest, resp *validator.DynamicResponse) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected DynamicRequest.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ request: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ AttributeConfig: types.DynamicValue(types.StringValue("test")),
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ },
+ response: &ValidateAttributeResponse{},
+ expected: &ValidateAttributeResponse{},
+ },
+
"request-config": {
attribute: testschema.AttributeWithDynamicValidators{
Validators: []validator.Dynamic{
@@ -4373,6 +4960,32 @@ func TestNestedAttributeObjectValidateObject(t *testing.T) {
response: &ValidateAttributeResponse{},
expected: &ValidateAttributeResponse{},
},
+ "request-client-capabilities": {
+ object: testschema.NestedAttributeObjectWithValidators{
+ Validators: []validator.Object{
+ testvalidator.Object{
+ ValidateObjectMethod: func(ctx context.Context, req validator.ObjectRequest, resp *validator.ObjectResponse) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected ObjectRequest.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ request: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ AttributeConfig: testAttributeConfig,
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ },
+ response: &ValidateAttributeResponse{},
+ expected: &ValidateAttributeResponse{},
+ },
+
"request-config": {
object: testschema.NestedAttributeObjectWithValidators{
Validators: []validator.Object{
diff --git a/internal/fwserver/block_validation.go b/internal/fwserver/block_validation.go
index d65b1b5c7..fcf3898f8 100644
--- a/internal/fwserver/block_validation.go
+++ b/internal/fwserver/block_validation.go
@@ -76,6 +76,7 @@ func BlockValidate(ctx context.Context, b fwschema.Block, req ValidateAttributeR
AttributeConfig: value,
AttributePath: req.AttributePath.AtListIndex(idx),
AttributePathExpression: req.AttributePathExpression.AtListIndex(idx),
+ ClientCapabilities: req.ClientCapabilities,
Config: req.Config,
}
nestedBlockObjectResp := &ValidateAttributeResponse{}
@@ -110,6 +111,7 @@ func BlockValidate(ctx context.Context, b fwschema.Block, req ValidateAttributeR
AttributeConfig: value,
AttributePath: req.AttributePath.AtSetValue(value),
AttributePathExpression: req.AttributePathExpression.AtSetValue(value),
+ ClientCapabilities: req.ClientCapabilities,
Config: req.Config,
}
nestedBlockObjectResp := &ValidateAttributeResponse{}
@@ -143,6 +145,7 @@ func BlockValidate(ctx context.Context, b fwschema.Block, req ValidateAttributeR
AttributeConfig: o,
AttributePath: req.AttributePath,
AttributePathExpression: req.AttributePathExpression,
+ ClientCapabilities: req.ClientCapabilities,
Config: req.Config,
}
nestedBlockObjectResp := &ValidateAttributeResponse{}
@@ -203,10 +206,11 @@ func BlockValidateList(ctx context.Context, block fwxschema.BlockWithListValidat
}
validateReq := validator.ListRequest{
- Config: req.Config,
- ConfigValue: configValue,
- Path: req.AttributePath,
- PathExpression: req.AttributePathExpression,
+ ClientCapabilities: req.ClientCapabilities,
+ Config: req.Config,
+ ConfigValue: configValue,
+ Path: req.AttributePath,
+ PathExpression: req.AttributePathExpression,
}
for _, blockValidator := range block.ListValidators() {
@@ -268,10 +272,11 @@ func BlockValidateObject(ctx context.Context, block fwxschema.BlockWithObjectVal
}
validateReq := validator.ObjectRequest{
- Config: req.Config,
- ConfigValue: configValue,
- Path: req.AttributePath,
- PathExpression: req.AttributePathExpression,
+ ClientCapabilities: req.ClientCapabilities,
+ Config: req.Config,
+ ConfigValue: configValue,
+ Path: req.AttributePath,
+ PathExpression: req.AttributePathExpression,
}
for _, blockValidator := range block.ObjectValidators() {
@@ -333,10 +338,11 @@ func BlockValidateSet(ctx context.Context, block fwxschema.BlockWithSetValidator
}
validateReq := validator.SetRequest{
- Config: req.Config,
- ConfigValue: configValue,
- Path: req.AttributePath,
- PathExpression: req.AttributePathExpression,
+ ClientCapabilities: req.ClientCapabilities,
+ Config: req.Config,
+ ConfigValue: configValue,
+ Path: req.AttributePath,
+ PathExpression: req.AttributePathExpression,
}
for _, blockValidator := range block.SetValidators() {
@@ -395,10 +401,11 @@ func NestedBlockObjectValidate(ctx context.Context, o fwschema.NestedBlockObject
}
validateReq := validator.ObjectRequest{
- Config: req.Config,
- ConfigValue: object,
- Path: req.AttributePath,
- PathExpression: req.AttributePathExpression,
+ ClientCapabilities: req.ClientCapabilities,
+ Config: req.Config,
+ ConfigValue: object,
+ Path: req.AttributePath,
+ PathExpression: req.AttributePathExpression,
}
for _, objectValidator := range objectWithValidators.ObjectValidators() {
@@ -432,6 +439,7 @@ func NestedBlockObjectValidate(ctx context.Context, o fwschema.NestedBlockObject
nestedAttrReq := ValidateAttributeRequest{
AttributePath: req.AttributePath.AtName(nestedName),
AttributePathExpression: req.AttributePathExpression.AtName(nestedName),
+ ClientCapabilities: req.ClientCapabilities,
Config: req.Config,
}
nestedAttrResp := &ValidateAttributeResponse{}
@@ -445,6 +453,7 @@ func NestedBlockObjectValidate(ctx context.Context, o fwschema.NestedBlockObject
nestedBlockReq := ValidateAttributeRequest{
AttributePath: req.AttributePath.AtName(nestedName),
AttributePathExpression: req.AttributePathExpression.AtName(nestedName),
+ ClientCapabilities: req.ClientCapabilities,
Config: req.Config,
}
nestedBlockResp := &ValidateAttributeResponse{}
diff --git a/internal/fwserver/block_validation_test.go b/internal/fwserver/block_validation_test.go
index 367a0df1c..40726f22f 100644
--- a/internal/fwserver/block_validation_test.go
+++ b/internal/fwserver/block_validation_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -20,7 +22,6 @@ import (
"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"
)
func TestBlockValidate(t *testing.T) {
@@ -746,6 +747,77 @@ func TestBlockValidate(t *testing.T) {
},
},
},
+ "list-validation-client-capabilities": {
+ req: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{WriteOnlyAttributesAllowed: true},
+ Config: tfsdk.Config{
+ Raw: tftypes.NewValue(
+ tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "test": tftypes.List{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested_attr": tftypes.String,
+ },
+ },
+ },
+ },
+ },
+ map[string]tftypes.Value{
+ "test": tftypes.NewValue(
+ tftypes.List{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested_attr": tftypes.String,
+ },
+ },
+ },
+ []tftypes.Value{
+ tftypes.NewValue(
+ tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested_attr": tftypes.String,
+ },
+ },
+ map[string]tftypes.Value{
+ "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"),
+ },
+ ),
+ },
+ ),
+ },
+ ),
+ Schema: testschema.Schema{
+ Blocks: map[string]fwschema.Block{
+ "test": testschema.Block{
+ NestedObject: testschema.NestedBlockObject{
+ Attributes: map[string]fwschema.Attribute{
+ "nested_attr": testschema.AttributeWithStringValidators{
+ Required: true,
+ Validators: []validator.String{
+ testvalidator.String{
+ ValidateStringMethod: func(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected StringRequest.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ },
+ },
+ NestingMode: fwschema.BlockNestingModeList,
+ },
+ },
+ },
+ },
+ },
+ resp: ValidateAttributeResponse{},
+ },
"set-no-validation": {
req: ValidateAttributeRequest{
AttributePath: path.Root("test"),
@@ -874,6 +946,77 @@ func TestBlockValidate(t *testing.T) {
},
},
},
+ "set-validation-client-capabilities": {
+ req: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{WriteOnlyAttributesAllowed: true},
+ Config: tfsdk.Config{
+ Raw: tftypes.NewValue(
+ tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "test": tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested_attr": tftypes.String,
+ },
+ },
+ },
+ },
+ },
+ map[string]tftypes.Value{
+ "test": tftypes.NewValue(
+ tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested_attr": tftypes.String,
+ },
+ },
+ },
+ []tftypes.Value{
+ tftypes.NewValue(
+ tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested_attr": tftypes.String,
+ },
+ },
+ map[string]tftypes.Value{
+ "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"),
+ },
+ ),
+ },
+ ),
+ },
+ ),
+ Schema: testschema.Schema{
+ Blocks: map[string]fwschema.Block{
+ "test": testschema.Block{
+ NestedObject: testschema.NestedBlockObject{
+ Attributes: map[string]fwschema.Attribute{
+ "nested_attr": testschema.AttributeWithStringValidators{
+ Required: true,
+ Validators: []validator.String{
+ testvalidator.String{
+ ValidateStringMethod: func(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected StringRequest.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ },
+ },
+ NestingMode: fwschema.BlockNestingModeSet,
+ },
+ },
+ },
+ },
+ },
+ resp: ValidateAttributeResponse{},
+ },
"single-no-validation": {
req: ValidateAttributeRequest{
AttributePath: path.Root("test"),
@@ -976,6 +1119,64 @@ func TestBlockValidate(t *testing.T) {
},
},
},
+ "single-validation-client-capabilities": {
+ req: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{WriteOnlyAttributesAllowed: true},
+ Config: tfsdk.Config{
+ Raw: tftypes.NewValue(
+ tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "test": tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested_attr": tftypes.String,
+ },
+ },
+ },
+ },
+ map[string]tftypes.Value{
+ "test": tftypes.NewValue(
+ tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested_attr": tftypes.String,
+ },
+ },
+ map[string]tftypes.Value{
+ "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"),
+ },
+ ),
+ },
+ ),
+ Schema: testschema.Schema{
+ Blocks: map[string]fwschema.Block{
+ "test": testschema.Block{
+ NestedObject: testschema.NestedBlockObject{
+ Attributes: map[string]fwschema.Attribute{
+ "nested_attr": testschema.AttributeWithStringValidators{
+ Required: true,
+ Validators: []validator.String{
+ testvalidator.String{
+ ValidateStringMethod: func(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected StringRequest.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ },
+ },
+ NestingMode: fwschema.BlockNestingModeSingle,
+ },
+ },
+ },
+ },
+ },
+ resp: ValidateAttributeResponse{},
+ },
}
for name, tc := range testCases {
@@ -1085,6 +1286,44 @@ func TestBlockValidateList(t *testing.T) {
response: &ValidateAttributeResponse{},
expected: &ValidateAttributeResponse{},
},
+ "request-client-capabilities": {
+ block: testschema.BlockWithListValidators{
+ Attributes: map[string]fwschema.Attribute{
+ "testattr": testschema.AttributeWithStringValidators{},
+ },
+ Validators: []validator.List{
+ testvalidator.List{
+ ValidateListMethod: func(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected ListRequest.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ request: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ AttributeConfig: types.ListValueMust(
+ types.ObjectType{
+ AttrTypes: map[string]attr.Type{"testattr": types.StringType},
+ },
+ []attr.Value{
+ types.ObjectValueMust(
+ map[string]attr.Type{"testattr": types.StringType},
+ map[string]attr.Value{"testattr": types.StringValue("test")},
+ ),
+ },
+ ),
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ },
+ response: &ValidateAttributeResponse{},
+ expected: &ValidateAttributeResponse{},
+ },
"request-config": {
block: testschema.BlockWithListValidators{
Attributes: map[string]fwschema.Attribute{
@@ -1402,6 +1641,37 @@ func TestBlockValidateObject(t *testing.T) {
response: &ValidateAttributeResponse{},
expected: &ValidateAttributeResponse{},
},
+ "request-client-capabilities": {
+ block: testschema.BlockWithObjectValidators{
+ Attributes: map[string]fwschema.Attribute{
+ "testattr": testschema.AttributeWithStringValidators{},
+ },
+ Validators: []validator.Object{
+ testvalidator.Object{
+ ValidateObjectMethod: func(ctx context.Context, req validator.ObjectRequest, resp *validator.ObjectResponse) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected ObjectRequest.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ request: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ AttributeConfig: types.ObjectValueMust(
+ map[string]attr.Type{"testattr": types.StringType},
+ map[string]attr.Value{"testattr": types.StringValue("test")},
+ ),
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ },
+ response: &ValidateAttributeResponse{},
+ expected: &ValidateAttributeResponse{},
+ },
"request-config": {
block: testschema.BlockWithObjectValidators{
Attributes: map[string]fwschema.Attribute{
@@ -1679,6 +1949,44 @@ func TestBlockValidateSet(t *testing.T) {
response: &ValidateAttributeResponse{},
expected: &ValidateAttributeResponse{},
},
+ "request-client-capabilities": {
+ block: testschema.BlockWithSetValidators{
+ Attributes: map[string]fwschema.Attribute{
+ "testattr": testschema.AttributeWithStringValidators{},
+ },
+ Validators: []validator.Set{
+ testvalidator.Set{
+ ValidateSetMethod: func(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected SetRequest.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ request: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ AttributeConfig: types.SetValueMust(
+ types.ObjectType{
+ AttrTypes: map[string]attr.Type{"testattr": types.StringType},
+ },
+ []attr.Value{
+ types.ObjectValueMust(
+ map[string]attr.Type{"testattr": types.StringType},
+ map[string]attr.Value{"testattr": types.StringValue("test")},
+ ),
+ },
+ ),
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ },
+ response: &ValidateAttributeResponse{},
+ expected: &ValidateAttributeResponse{},
+ },
"request-config": {
block: testschema.BlockWithSetValidators{
Attributes: map[string]fwschema.Attribute{
@@ -2069,6 +2377,31 @@ func TestNestedBlockObjectValidateObject(t *testing.T) {
response: &ValidateAttributeResponse{},
expected: &ValidateAttributeResponse{},
},
+ "request-client-capabilities": {
+ object: testschema.NestedBlockObjectWithValidators{
+ Validators: []validator.Object{
+ testvalidator.Object{
+ ValidateObjectMethod: func(ctx context.Context, req validator.ObjectRequest, resp *validator.ObjectResponse) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError(
+ "Unexpected ObjectRequest.ClientCapabilities",
+ "Missing WriteOnlyAttributesAllowed client capability",
+ )
+ }
+ },
+ },
+ },
+ },
+ request: ValidateAttributeRequest{
+ AttributePath: path.Root("test"),
+ AttributeConfig: testAttributeConfig,
+ ClientCapabilities: validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ },
+ },
+ response: &ValidateAttributeResponse{},
+ expected: &ValidateAttributeResponse{},
+ },
"request-config": {
object: testschema.NestedBlockObjectWithValidators{
Validators: []validator.Object{
diff --git a/internal/fwserver/schema_validation.go b/internal/fwserver/schema_validation.go
index 32c50f9d4..dedce9d43 100644
--- a/internal/fwserver/schema_validation.go
+++ b/internal/fwserver/schema_validation.go
@@ -9,6 +9,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
)
@@ -20,6 +21,11 @@ type ValidateSchemaRequest struct {
// interpolation or other functionality that would prevent Terraform
// from knowing the value at request time.
Config tfsdk.Config
+
+ // ClientCapabilities defines optionally supported protocol features for
+ // schema validation RPCs, such as forward-compatible Terraform
+ // behavior changes.
+ ClientCapabilities validator.ValidateSchemaClientCapabilities
}
// ValidateSchemaResponse represents a response to a
@@ -43,6 +49,7 @@ func SchemaValidate(ctx context.Context, s fwschema.Schema, req ValidateSchemaRe
AttributePath: path.Root(name),
AttributePathExpression: path.MatchRoot(name),
Config: req.Config,
+ ClientCapabilities: req.ClientCapabilities,
}
// Instantiate a new response for each request to prevent validators
// from modifying or removing diagnostics.
@@ -58,6 +65,7 @@ func SchemaValidate(ctx context.Context, s fwschema.Schema, req ValidateSchemaRe
AttributePath: path.Root(name),
AttributePathExpression: path.MatchRoot(name),
Config: req.Config,
+ ClientCapabilities: req.ClientCapabilities,
}
// Instantiate a new response for each request to prevent validators
// from modifying or removing diagnostics.
diff --git a/internal/fwserver/server_applyresourcechange_test.go b/internal/fwserver/server_applyresourcechange_test.go
index 59e10d76c..41ab43c77 100644
--- a/internal/fwserver/server_applyresourcechange_test.go
+++ b/internal/fwserver/server_applyresourcechange_test.go
@@ -59,6 +59,31 @@ func TestServerApplyResourceChange(t *testing.T) {
TestRequired types.String `tfsdk:"test_required"`
}
+ testSchemaTypeWriteOnly := tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "test_optional_write_only": tftypes.String,
+ "test_required_write_only": tftypes.String,
+ },
+ }
+
+ testSchemaWriteOnly := schema.Schema{
+ Attributes: map[string]schema.Attribute{
+ "test_optional_write_only": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "test_required_write_only": schema.StringAttribute{
+ Required: true,
+ WriteOnly: true,
+ },
+ },
+ }
+
+ type testSchemaDataWriteOnly struct {
+ TestOptionalWriteOnly types.String `tfsdk:"test_optional_write_only"`
+ TestRequiredWriteOnly types.String `tfsdk:"test_required_write_only"`
+ }
+
testProviderMetaType := tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"test_provider_meta_attribute": tftypes.String,
@@ -398,6 +423,53 @@ func TestServerApplyResourceChange(t *testing.T) {
Private: testEmptyPrivate,
},
},
+ "create-response-newstate-write-only": {
+ server: &fwserver.Server{
+ Provider: &testprovider.Provider{},
+ },
+ request: &fwserver.ApplyResourceChangeRequest{
+ Config: &tfsdk.Config{
+ Raw: tftypes.NewValue(testSchemaTypeWriteOnly, map[string]tftypes.Value{
+ "test_optional_write_only": tftypes.NewValue(tftypes.String, "test-config-value"),
+ "test_required_write_only": tftypes.NewValue(tftypes.String, "test-config-value"),
+ }),
+ Schema: testSchemaWriteOnly,
+ },
+ PlannedState: &tfsdk.Plan{
+ Raw: tftypes.NewValue(testSchemaTypeWriteOnly, map[string]tftypes.Value{
+ "test_optional_write_only": tftypes.NewValue(tftypes.String, "test-config-value"),
+ "test_required_write_only": tftypes.NewValue(tftypes.String, "test-config-value"),
+ }),
+ Schema: testSchemaWriteOnly,
+ },
+ PriorState: testEmptyState,
+ ResourceSchema: testSchemaWriteOnly,
+ Resource: &testprovider.Resource{
+ CreateMethod: func(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var data testSchemaDataWriteOnly
+
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+ },
+ DeleteMethod: func(_ context.Context, _ resource.DeleteRequest, resp *resource.DeleteResponse) {
+ resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Delete")
+ },
+ UpdateMethod: func(_ context.Context, _ resource.UpdateRequest, resp *resource.UpdateResponse) {
+ resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Update")
+ },
+ },
+ },
+ expectedResponse: &fwserver.ApplyResourceChangeResponse{
+ NewState: &tfsdk.State{
+ Raw: tftypes.NewValue(testSchemaTypeWriteOnly, map[string]tftypes.Value{
+ "test_optional_write_only": tftypes.NewValue(tftypes.String, nil),
+ "test_required_write_only": tftypes.NewValue(tftypes.String, nil),
+ }),
+ Schema: testSchemaWriteOnly,
+ },
+ Private: testEmptyPrivate,
+ },
+ },
"create-response-private": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
@@ -1259,6 +1331,59 @@ func TestServerApplyResourceChange(t *testing.T) {
Private: testEmptyPrivate,
},
},
+ "update-response-newstate-write-only-nullification": {
+ server: &fwserver.Server{
+ Provider: &testprovider.Provider{},
+ },
+ request: &fwserver.ApplyResourceChangeRequest{
+ Config: &tfsdk.Config{
+ Raw: tftypes.NewValue(testSchemaTypeWriteOnly, map[string]tftypes.Value{
+ "test_optional_write_only": tftypes.NewValue(tftypes.String, "test-config-value"),
+ "test_required_write_only": tftypes.NewValue(tftypes.String, "test-config-value"),
+ }),
+ Schema: testSchemaWriteOnly,
+ },
+ PlannedState: &tfsdk.Plan{
+ Raw: tftypes.NewValue(testSchemaTypeWriteOnly, map[string]tftypes.Value{
+ "test_optional_write_only": tftypes.NewValue(tftypes.String, "test-config-value"),
+ "test_required_write_only": tftypes.NewValue(tftypes.String, "test-config-value"),
+ }),
+ Schema: testSchemaWriteOnly,
+ },
+ PriorState: &tfsdk.State{
+ Raw: tftypes.NewValue(testSchemaTypeWriteOnly, map[string]tftypes.Value{
+ "test_optional_write_only": tftypes.NewValue(tftypes.String, "old-optional-value"),
+ "test_required_write_only": tftypes.NewValue(tftypes.String, "test-config-value"),
+ }),
+ Schema: testSchemaWriteOnly,
+ },
+ ResourceSchema: testSchemaWriteOnly,
+ Resource: &testprovider.Resource{
+ CreateMethod: func(_ context.Context, _ resource.CreateRequest, resp *resource.CreateResponse) {
+ resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Create")
+ },
+ DeleteMethod: func(_ context.Context, _ resource.DeleteRequest, resp *resource.DeleteResponse) {
+ resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Delete")
+ },
+ UpdateMethod: func(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var data testSchemaDataWriteOnly
+
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+ },
+ },
+ },
+ expectedResponse: &fwserver.ApplyResourceChangeResponse{
+ NewState: &tfsdk.State{
+ Raw: tftypes.NewValue(testSchemaTypeWriteOnly, map[string]tftypes.Value{
+ "test_optional_write_only": tftypes.NewValue(tftypes.String, nil),
+ "test_required_write_only": tftypes.NewValue(tftypes.String, nil),
+ }),
+ Schema: testSchemaWriteOnly,
+ },
+ Private: testEmptyPrivate,
+ },
+ },
"update-response-private": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
diff --git a/internal/fwserver/server_createresource.go b/internal/fwserver/server_createresource.go
index 30c491690..d5a0aef2e 100644
--- a/internal/fwserver/server_createresource.go
+++ b/internal/fwserver/server_createresource.go
@@ -156,11 +156,21 @@ func (s *Server) CreateResource(ctx context.Context, req *CreateResourceRequest,
return
}
- if semanticEqualityResp.NewData.TerraformValue.Equal(resp.NewState.Raw) {
- return
+ if !semanticEqualityResp.NewData.TerraformValue.Equal(resp.NewState.Raw) {
+ logging.FrameworkDebug(ctx, "State updated due to semantic equality")
+
+ resp.NewState.Raw = semanticEqualityResp.NewData.TerraformValue
}
- logging.FrameworkDebug(ctx, "State updated due to semantic equality")
+ // Set any write-only attributes in the state to null
+ modifiedState, err := tftypes.Transform(resp.NewState.Raw, NullifyWriteOnlyAttributes(ctx, resp.NewState.Schema))
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error Modifying State",
+ "There was an unexpected error modifying the NewState. This is always a problem with the provider. Please report the following to the provider developer:\n\n"+err.Error(),
+ )
+ return
+ }
- resp.NewState.Raw = semanticEqualityResp.NewData.TerraformValue
+ resp.NewState.Raw = modifiedState
}
diff --git a/internal/fwserver/server_createresource_test.go b/internal/fwserver/server_createresource_test.go
index d3e83c31b..988a268c9 100644
--- a/internal/fwserver/server_createresource_test.go
+++ b/internal/fwserver/server_createresource_test.go
@@ -33,6 +33,13 @@ func TestServerCreateResource(t *testing.T) {
},
}
+ testSchemaTypeWriteOnly := tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "test_required": tftypes.String,
+ "test_write_only": tftypes.String,
+ },
+ }
+
testSchema := schema.Schema{
Attributes: map[string]schema.Attribute{
"test_computed": schema.StringAttribute{
@@ -76,6 +83,18 @@ func TestServerCreateResource(t *testing.T) {
},
}
+ testSchemaWithWriteOnly := schema.Schema{
+ Attributes: map[string]schema.Attribute{
+ "test_required": schema.StringAttribute{
+ Required: true,
+ },
+ "test_write_only": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ }
+
testEmptyState := &tfsdk.State{
Raw: tftypes.NewValue(testSchemaType, nil),
Schema: testSchema,
@@ -91,6 +110,11 @@ func TestServerCreateResource(t *testing.T) {
TestRequired testtypes.StringValueWithSemanticEquals `tfsdk:"test_required"`
}
+ type testSchemaDataWriteOnly struct {
+ TestRequired types.String `tfsdk:"test_required"`
+ TestWriteOnly types.String `tfsdk:"test_write_only"`
+ }
+
testProviderMetaType := tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"test_provider_meta_attribute": tftypes.String,
@@ -506,6 +530,39 @@ func TestServerCreateResource(t *testing.T) {
Private: testEmptyPrivate,
},
},
+ "response-newstate-write-only-nullification": {
+ server: &fwserver.Server{
+ Provider: &testprovider.Provider{},
+ },
+ request: &fwserver.CreateResourceRequest{
+ PlannedState: &tfsdk.Plan{
+ Raw: tftypes.NewValue(testSchemaTypeWriteOnly, map[string]tftypes.Value{
+ "test_required": tftypes.NewValue(tftypes.String, "test-plannedstate-value"),
+ "test_write_only": tftypes.NewValue(tftypes.String, "test-write-only-value"),
+ }),
+ Schema: testSchemaWithWriteOnly,
+ },
+ ResourceSchema: testSchemaWithWriteOnly,
+ Resource: &testprovider.Resource{
+ CreateMethod: func(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var data testSchemaDataWriteOnly
+
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+ },
+ },
+ },
+ expectedResponse: &fwserver.CreateResourceResponse{
+ NewState: &tfsdk.State{
+ Raw: tftypes.NewValue(testSchemaTypeWriteOnly, map[string]tftypes.Value{
+ "test_required": tftypes.NewValue(tftypes.String, "test-plannedstate-value"),
+ "test_write_only": tftypes.NewValue(tftypes.String, nil),
+ }),
+ Schema: testSchemaWithWriteOnly,
+ },
+ Private: testEmptyPrivate,
+ },
+ },
"response-private": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
diff --git a/internal/fwserver/server_importresourcestate.go b/internal/fwserver/server_importresourcestate.go
index 7288cc3e7..4abe204cd 100644
--- a/internal/fwserver/server_importresourcestate.go
+++ b/internal/fwserver/server_importresourcestate.go
@@ -142,6 +142,18 @@ func (s *Server) ImportResourceState(ctx context.Context, req *ImportResourceSta
return
}
+ // Set any write-only attributes in the import state to null
+ modifiedState, err := tftypes.Transform(importResp.State.Raw, NullifyWriteOnlyAttributes(ctx, importResp.State.Schema))
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error Modifying Import State",
+ "There was an unexpected error modifying the Import State. This is always a problem with the provider. Please report the following to the provider developer:\n\n"+err.Error(),
+ )
+ return
+ }
+
+ importResp.State.Raw = modifiedState
+
if importResp.State.Raw.Equal(req.EmptyState.Raw) {
resp.Diagnostics.AddError(
"Missing Resource Import State",
diff --git a/internal/fwserver/server_importresourcestate_test.go b/internal/fwserver/server_importresourcestate_test.go
index bd0275a12..d605ef3d9 100644
--- a/internal/fwserver/server_importresourcestate_test.go
+++ b/internal/fwserver/server_importresourcestate_test.go
@@ -33,12 +33,26 @@ func TestServerImportResourceState(t *testing.T) {
},
}
+ testTypeWriteOnly := tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "id": tftypes.String,
+ "write-only": tftypes.String,
+ "required": tftypes.String,
+ },
+ }
+
testEmptyStateValue := tftypes.NewValue(testType, map[string]tftypes.Value{
"id": tftypes.NewValue(tftypes.String, nil),
"optional": tftypes.NewValue(tftypes.String, nil),
"required": tftypes.NewValue(tftypes.String, nil),
})
+ testEmptyStateValueWriteOnly := tftypes.NewValue(testTypeWriteOnly, map[string]tftypes.Value{
+ "id": tftypes.NewValue(tftypes.String, nil),
+ "write-only": tftypes.NewValue(tftypes.String, nil),
+ "required": tftypes.NewValue(tftypes.String, nil),
+ })
+
testUnknownStateValue := tftypes.NewValue(testType, tftypes.UnknownValue)
testStateValue := tftypes.NewValue(testType, map[string]tftypes.Value{
@@ -61,11 +75,31 @@ func TestServerImportResourceState(t *testing.T) {
},
}
+ testSchemaWriteOnly := schema.Schema{
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Computed: true,
+ },
+ "write-only": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "required": schema.StringAttribute{
+ Required: true,
+ },
+ },
+ }
+
testEmptyState := &tfsdk.State{
Raw: testEmptyStateValue,
Schema: testSchema,
}
+ testEmptyStateWriteOnly := &tfsdk.State{
+ Raw: testEmptyStateValueWriteOnly,
+ Schema: testSchemaWriteOnly,
+ }
+
testUnknownState := &tfsdk.State{
Raw: testUnknownStateValue,
Schema: testSchema,
@@ -416,6 +450,39 @@ func TestServerImportResourceState(t *testing.T) {
},
},
},
+ "response-importedresources-write-only-nullification": {
+ server: &fwserver.Server{
+ Provider: &testprovider.Provider{},
+ },
+ request: &fwserver.ImportResourceStateRequest{
+ EmptyState: *testEmptyStateWriteOnly,
+ ID: "test-id",
+ Resource: &testprovider.ResourceWithImportState{
+ Resource: &testprovider.Resource{},
+ ImportStateMethod: func(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("write-only"), "write-only-val")...)
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+ },
+ },
+ TypeName: "test_resource",
+ },
+ expectedResponse: &fwserver.ImportResourceStateResponse{
+ ImportedResources: []fwserver.ImportedResource{
+ {
+ State: tfsdk.State{
+ Raw: tftypes.NewValue(testTypeWriteOnly, map[string]tftypes.Value{
+ "id": tftypes.NewValue(tftypes.String, "test-id"),
+ "write-only": tftypes.NewValue(tftypes.String, nil),
+ "required": tftypes.NewValue(tftypes.String, nil),
+ }),
+ Schema: testSchemaWriteOnly,
+ },
+ TypeName: "test_resource",
+ Private: testEmptyPrivate,
+ },
+ },
+ },
+ },
}
for name, testCase := range testCases {
diff --git a/internal/fwserver/server_moveresourcestate.go b/internal/fwserver/server_moveresourcestate.go
index 480e4a956..1a832f3fe 100644
--- a/internal/fwserver/server_moveresourcestate.go
+++ b/internal/fwserver/server_moveresourcestate.go
@@ -205,6 +205,18 @@ func (s *Server) MoveResourceState(ctx context.Context, req *MoveResourceStateRe
return
}
+ // Set any write-only attributes in the move resource state to null
+ modifiedState, err := tftypes.Transform(moveStateResp.TargetState.Raw, NullifyWriteOnlyAttributes(ctx, moveStateResp.TargetState.Schema))
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error Modifying Move Resource State",
+ "There was an unexpected error modifying the Move Resource State. This is always a problem with the provider. Please report the following to the provider developer:\n\n"+err.Error(),
+ )
+ return
+ }
+
+ moveStateResp.TargetState.Raw = modifiedState
+
// If the implement has set the state in any way, return the response.
if !moveStateResp.TargetState.Raw.Equal(tftypes.NewValue(req.TargetResourceSchema.Type().TerraformType(ctx), nil)) {
resp.Diagnostics = moveStateResp.Diagnostics
diff --git a/internal/fwserver/server_moveresourcestate_test.go b/internal/fwserver/server_moveresourcestate_test.go
index 1a8e0fb77..931b84a76 100644
--- a/internal/fwserver/server_moveresourcestate_test.go
+++ b/internal/fwserver/server_moveresourcestate_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/internal/privatestate"
@@ -18,7 +20,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestServerMoveResourceState(t *testing.T) {
@@ -40,6 +41,22 @@ func TestServerMoveResourceState(t *testing.T) {
}
schemaType := testSchema.Type().TerraformType(ctx)
+ testSchemaWriteOnly := schema.Schema{
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Computed: true,
+ },
+ "write_only_attribute": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "required_attribute": schema.StringAttribute{
+ Required: true,
+ },
+ },
+ }
+ schemaTypeWriteOnly := testSchemaWriteOnly.Type().TerraformType(ctx)
+
testCases := map[string]struct {
server *fwserver.Server
request *fwserver.MoveResourceStateRequest
@@ -757,6 +774,44 @@ func TestServerMoveResourceState(t *testing.T) {
},
},
},
+ "response-TargetState-write-only-nullification": {
+ server: &fwserver.Server{
+ Provider: &testprovider.Provider{},
+ },
+ request: &fwserver.MoveResourceStateRequest{
+ SourceRawState: testNewRawState(t, map[string]interface{}{
+ "id": "test-id-value",
+ "write_only_attribute": nil,
+ "required_attribute": true,
+ }),
+ TargetResource: &testprovider.ResourceWithMoveState{
+ MoveStateMethod: func(ctx context.Context) []resource.StateMover {
+ return []resource.StateMover{
+ {
+ StateMover: func(_ context.Context, req resource.MoveStateRequest, resp *resource.MoveStateResponse) {
+ resp.Diagnostics.Append(resp.TargetState.SetAttribute(ctx, path.Root("id"), "test-id-value")...)
+ resp.Diagnostics.Append(resp.TargetState.SetAttribute(ctx, path.Root("write_only_attribute"), "movestate-val")...)
+ resp.Diagnostics.Append(resp.TargetState.SetAttribute(ctx, path.Root("required_attribute"), "true")...)
+ },
+ },
+ }
+ },
+ },
+ TargetResourceSchema: testSchemaWriteOnly,
+ TargetTypeName: "test_resource",
+ },
+ expectedResponse: &fwserver.MoveResourceStateResponse{
+ TargetPrivate: privatestate.EmptyData(ctx),
+ TargetState: &tfsdk.State{
+ Raw: tftypes.NewValue(schemaTypeWriteOnly, map[string]tftypes.Value{
+ "id": tftypes.NewValue(tftypes.String, "test-id-value"),
+ "write_only_attribute": tftypes.NewValue(tftypes.String, nil),
+ "required_attribute": tftypes.NewValue(tftypes.String, "true"),
+ }),
+ Schema: testSchemaWriteOnly,
+ },
+ },
+ },
}
for name, testCase := range testCases {
diff --git a/internal/fwserver/server_planresourcechange.go b/internal/fwserver/server_planresourcechange.go
index d66fc4897..aacd2c998 100644
--- a/internal/fwserver/server_planresourcechange.go
+++ b/internal/fwserver/server_planresourcechange.go
@@ -13,6 +13,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/internal/fromtftypes"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata"
"github.com/hashicorp/terraform-plugin-framework/internal/logging"
@@ -338,6 +339,18 @@ func (s *Server) PlanResourceChange(ctx context.Context, req *PlanResourceChange
"Ensure all resource plan modifiers do not attempt to change resource plan data from being a null value if the request plan is a null value.",
)
}
+
+ // Set any write-only attributes in the plan to null
+ modifiedPlan, err := tftypes.Transform(resp.PlannedState.Raw, NullifyWriteOnlyAttributes(ctx, resp.PlannedState.Schema))
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error Modifying Planned State",
+ "There was an unexpected error modifying the PlannedState. This is always a problem with the provider. Please report the following to the provider developer:\n\n"+err.Error(),
+ )
+ return
+ }
+
+ resp.PlannedState.Raw = modifiedPlan
}
func MarkComputedNilsAsUnknown(ctx context.Context, config tftypes.Value, resourceSchema fwschema.Schema) func(*tftypes.AttributePath, tftypes.Value) (tftypes.Value, error) {
@@ -500,6 +513,67 @@ func NormaliseRequiresReplace(ctx context.Context, rs path.Paths) path.Paths {
return ret[:j]
}
+// RequiredWriteOnlyNilsAttributePaths returns a tftypes.Walk() function
+// that populates reqWriteOnlyNilsPaths with the paths of Required and WriteOnly
+// attributes that have a null value.
+func RequiredWriteOnlyNilsAttributePaths(ctx context.Context, schema fwschema.Schema, reqWriteOnlyNilsPaths *path.Paths) func(path *tftypes.AttributePath, value tftypes.Value) (bool, error) {
+ return func(attrPath *tftypes.AttributePath, value tftypes.Value) (bool, error) {
+ // we are only modifying attributes, not the entire resource
+ if len(attrPath.Steps()) < 1 {
+ return true, nil
+ }
+
+ ctx = logging.FrameworkWithAttributePath(ctx, attrPath.String())
+
+ attribute, err := schema.AttributeAtTerraformPath(ctx, attrPath)
+
+ if err != nil {
+ if errors.Is(err, fwschema.ErrPathInsideAtomicAttribute) {
+ // atomic attributes can be nested block objects that contain child writeOnly attributes
+ return true, nil
+ }
+
+ if errors.Is(err, fwschema.ErrPathIsBlock) {
+ // blocks do not have the writeOnly field but can contain child writeOnly attributes
+ return true, nil
+ }
+
+ if errors.Is(err, fwschema.ErrPathInsideDynamicAttribute) {
+ return false, nil
+ }
+
+ logging.FrameworkError(ctx, "couldn't find attribute in resource schema")
+ return false, fmt.Errorf("couldn't find attribute in resource schema: %w", err)
+ }
+
+ if attribute.IsWriteOnly() {
+ if attribute.IsRequired() && value.IsNull() {
+ fwPath, diags := fromtftypes.AttributePath(ctx, attrPath, schema)
+ if diags.HasError() {
+ for _, diagErr := range diags.Errors() {
+ logging.FrameworkError(ctx,
+ "Error converting tftypes.AttributePath to path.Path",
+ map[string]any{
+ logging.KeyError: diagErr.Detail(),
+ },
+ )
+ }
+
+ return false, fmt.Errorf("couldn't convert tftypes.AttributePath to path.Path")
+ }
+ reqWriteOnlyNilsPaths.Append(fwPath)
+
+ // if the value is nil, there is no need to continue walking
+ return false, nil
+ }
+ // check for any writeOnly child attributes
+ return true, nil
+ }
+
+ return false, nil
+ }
+}
+
// planToState returns a *tfsdk.State with a copied value from a tfsdk.Plan.
func planToState(plan tfsdk.Plan) *tfsdk.State {
return &tfsdk.State{
diff --git a/internal/fwserver/server_planresourcechange_test.go b/internal/fwserver/server_planresourcechange_test.go
index 66e693935..4e22b36c6 100644
--- a/internal/fwserver/server_planresourcechange_test.go
+++ b/internal/fwserver/server_planresourcechange_test.go
@@ -7,6 +7,7 @@ import (
"context"
"fmt"
"math/big"
+ "sort"
"testing"
"github.com/google/go-cmp/cmp"
@@ -363,6 +364,305 @@ func TestMarkComputedNilsAsUnknown(t *testing.T) {
}
}
+func TestRequiredWriteOnlyNilsAttributePath(t *testing.T) {
+ t.Parallel()
+
+ s := schema.Schema{
+ Attributes: map[string]schema.Attribute{
+ "string-value": schema.StringAttribute{
+ Required: true,
+ },
+ "string-nil-optional-writeonly": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "string-value-optional-writeonly": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "string-nil-required-writeonly": schema.StringAttribute{
+ Required: true,
+ WriteOnly: true,
+ },
+ "string-value-required-writeonly": schema.StringAttribute{
+ Required: true,
+ WriteOnly: true,
+ },
+ "list-value": schema.ListAttribute{
+ ElementType: types.StringType,
+ Required: true,
+ },
+ "list-nil-optional-writeonly": schema.ListAttribute{
+ ElementType: types.StringType,
+ Optional: true,
+ WriteOnly: true,
+ },
+ "list-value-optional-writeonly": schema.ListAttribute{
+ ElementType: types.StringType,
+ Optional: true,
+ WriteOnly: true,
+ },
+ "list-nil-required-writeonly": schema.ListAttribute{
+ ElementType: types.StringType,
+ Required: true,
+ WriteOnly: true,
+ },
+ "list-value-required-writeonly": schema.ListAttribute{
+ ElementType: types.StringType,
+ Required: true,
+ WriteOnly: true,
+ },
+ "dynamic-value": schema.DynamicAttribute{
+ Required: true,
+ },
+ "dynamic-nil-optional-writeonly": schema.DynamicAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "dynamic-value-optional-writeonly": schema.DynamicAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "dynamic-nil-required-writeonly": schema.DynamicAttribute{
+ Required: true,
+ WriteOnly: true,
+ },
+ "dynamic-value-required-writeonly": schema.DynamicAttribute{
+ Required: true,
+ WriteOnly: true,
+ },
+ // underlying values of dynamic attributes should be left alone
+ "dynamic-value-with-underlying-list-required-writeonly": schema.DynamicAttribute{
+ Required: true,
+ WriteOnly: true,
+ },
+ "object-nil-required-writeonly": schema.ObjectAttribute{
+ AttributeTypes: map[string]attr.Type{
+ "string-nil": types.StringType,
+ "string-set": types.StringType,
+ },
+ Required: true,
+ WriteOnly: true,
+ },
+ "object-value-required-writeonly": schema.ObjectAttribute{
+ AttributeTypes: map[string]attr.Type{
+ "string-nil": types.StringType,
+ "string-set": types.StringType,
+ },
+ Required: true,
+ WriteOnly: true,
+ },
+ "nested-nil-required-writeonly": schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "string-nil": schema.StringAttribute{
+ Required: true,
+ WriteOnly: true,
+ },
+ "string-set": schema.StringAttribute{
+ Required: true,
+ WriteOnly: true,
+ },
+ },
+ Required: true,
+ WriteOnly: true,
+ },
+ "nested-value-required-writeonly": schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "string-nil": schema.StringAttribute{
+ Required: true,
+ WriteOnly: true,
+ },
+ "string-set": schema.StringAttribute{
+ Required: true,
+ WriteOnly: true,
+ },
+ },
+ Required: true,
+ WriteOnly: true,
+ },
+ "optional-nested-value-required-writeonly-attributes": schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "string-nil": schema.StringAttribute{
+ Required: true,
+ WriteOnly: true,
+ },
+ "string-set": schema.StringAttribute{
+ Required: true,
+ WriteOnly: true,
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ Blocks: map[string]schema.Block{
+ "block-nil-required-writeonly-attributes": schema.SetNestedBlock{
+ NestedObject: schema.NestedBlockObject{
+ Attributes: map[string]schema.Attribute{
+ "string-nil": schema.StringAttribute{
+ Required: true,
+ WriteOnly: true,
+ },
+ "string-set": schema.StringAttribute{
+ Required: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ "block-value-required-writeonly-attributes": schema.SetNestedBlock{
+ NestedObject: schema.NestedBlockObject{
+ Attributes: map[string]schema.Attribute{
+ "string-nil": schema.StringAttribute{
+ Required: true,
+ WriteOnly: true,
+ },
+ "string-set": schema.StringAttribute{
+ Required: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ }
+ input := tftypes.NewValue(s.Type().TerraformType(context.Background()), map[string]tftypes.Value{
+ "string-value": tftypes.NewValue(tftypes.String, "hello, world"),
+ "string-nil-optional-writeonly": tftypes.NewValue(tftypes.String, nil),
+ "string-value-optional-writeonly": tftypes.NewValue(tftypes.String, "hello, world"),
+ "string-nil-required-writeonly": tftypes.NewValue(tftypes.String, nil),
+ "string-value-required-writeonly": tftypes.NewValue(tftypes.String, "hello, world"),
+ "list-value": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{tftypes.NewValue(tftypes.String, "hello, world")}),
+ "list-nil-optional-writeonly": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, nil),
+ "list-value-optional-writeonly": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{tftypes.NewValue(tftypes.String, "hello, world")}),
+ "list-nil-required-writeonly": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, nil),
+ "list-value-required-writeonly": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{
+ tftypes.NewValue(tftypes.String, "hello, world"),
+ tftypes.NewValue(tftypes.String, nil),
+ }),
+ "dynamic-value": tftypes.NewValue(tftypes.String, "hello, world"),
+ "dynamic-nil-optional-writeonly": tftypes.NewValue(tftypes.DynamicPseudoType, nil),
+ "dynamic-value-optional-writeonly": tftypes.NewValue(tftypes.String, "hello, world"),
+ "dynamic-nil-required-writeonly": tftypes.NewValue(tftypes.DynamicPseudoType, nil),
+ "dynamic-value-required-writeonly": tftypes.NewValue(tftypes.String, "hello, world"),
+ "dynamic-value-with-underlying-list-required-writeonly": tftypes.NewValue(
+ tftypes.List{
+ ElementType: tftypes.Bool,
+ },
+ []tftypes.Value{
+ tftypes.NewValue(tftypes.Bool, true),
+ tftypes.NewValue(tftypes.Bool, nil),
+ },
+ ),
+ "object-nil-required-writeonly": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ }, nil),
+ "object-value-required-writeonly": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ }, map[string]tftypes.Value{
+ "string-nil": tftypes.NewValue(tftypes.String, nil),
+ "string-set": tftypes.NewValue(tftypes.String, "foo"),
+ }),
+ "nested-nil-required-writeonly": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ }, nil),
+ "nested-value-required-writeonly": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ }, map[string]tftypes.Value{
+ "string-nil": tftypes.NewValue(tftypes.String, nil),
+ "string-set": tftypes.NewValue(tftypes.String, "bar"),
+ }),
+ "optional-nested-value-required-writeonly-attributes": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ }, map[string]tftypes.Value{
+ "string-nil": tftypes.NewValue(tftypes.String, nil),
+ "string-set": tftypes.NewValue(tftypes.String, "bar"),
+ }),
+ "block-nil-required-writeonly-attributes": tftypes.NewValue(tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ },
+ }, nil),
+ "block-value-required-writeonly-attributes": tftypes.NewValue(tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ },
+ }, []tftypes.Value{
+ tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ }, map[string]tftypes.Value{
+ "string-nil": tftypes.NewValue(tftypes.String, nil),
+ "string-set": tftypes.NewValue(tftypes.String, "bar"),
+ }),
+ }),
+ })
+ expected := path.Paths{
+ path.Root("block-value-required-writeonly-attributes").
+ AtSetValue(types.ObjectValueMust(
+ map[string]attr.Type{
+ "string-nil": types.StringType,
+ "string-set": types.StringType,
+ },
+ map[string]attr.Value{
+ "string-nil": types.StringNull(),
+ "string-set": types.StringValue("bar"),
+ },
+ )).
+ AtName("string-nil"),
+ path.Root("dynamic-nil-required-writeonly"),
+ path.Root("list-nil-required-writeonly"),
+ path.Root("nested-value-required-writeonly").AtName("string-nil"),
+ path.Root("object-nil-required-writeonly"),
+ path.Root("optional-nested-value-required-writeonly-attributes").AtName("string-nil"),
+ path.Root("string-nil-required-writeonly"),
+ path.Root("nested-nil-required-writeonly"),
+ }
+
+ var got path.Paths
+ err := tftypes.Walk(input, fwserver.RequiredWriteOnlyNilsAttributePaths(context.Background(), s, &got))
+ if err != nil {
+ t.Errorf("Unexpected error: %s", err)
+ return
+ }
+
+ sort.Slice(got, func(i, j int) bool {
+ return got[i].String() < got[j].String()
+ })
+
+ sort.Slice(expected, func(i, j int) bool {
+ return expected[i].String() < expected[j].String()
+ })
+
+ if diff := cmp.Diff(got, expected, cmpopts.EquateEmpty()); diff != "" {
+ t.Errorf("Unexpected diff (+wanted, -got): %s", diff)
+ return
+ }
+}
+
func TestNormaliseRequiresReplace(t *testing.T) {
t.Parallel()
diff --git a/internal/fwserver/server_readresource.go b/internal/fwserver/server_readresource.go
index 628a2e445..d260e2912 100644
--- a/internal/fwserver/server_readresource.go
+++ b/internal/fwserver/server_readresource.go
@@ -6,6 +6,8 @@ package fwserver
import (
"context"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata"
"github.com/hashicorp/terraform-plugin-framework/internal/logging"
@@ -157,11 +159,21 @@ func (s *Server) ReadResource(ctx context.Context, req *ReadResourceRequest, res
return
}
- if semanticEqualityResp.NewData.TerraformValue.Equal(resp.NewState.Raw) {
- return
+ if !semanticEqualityResp.NewData.TerraformValue.Equal(resp.NewState.Raw) {
+ logging.FrameworkDebug(ctx, "State updated due to semantic equality")
+
+ resp.NewState.Raw = semanticEqualityResp.NewData.TerraformValue
}
- logging.FrameworkDebug(ctx, "State updated due to semantic equality")
+ // Set any write-only attributes in the state to null
+ modifiedState, err := tftypes.Transform(resp.NewState.Raw, NullifyWriteOnlyAttributes(ctx, resp.NewState.Schema))
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error Modifying State",
+ "There was an unexpected error modifying the NewState. This is always a problem with the provider. Please report the following to the provider developer:\n\n"+err.Error(),
+ )
+ return
+ }
- resp.NewState.Raw = semanticEqualityResp.NewData.TerraformValue
+ resp.NewState.Raw = modifiedState
}
diff --git a/internal/fwserver/server_readresource_test.go b/internal/fwserver/server_readresource_test.go
index a9520edd4..f6711551a 100644
--- a/internal/fwserver/server_readresource_test.go
+++ b/internal/fwserver/server_readresource_test.go
@@ -34,11 +34,23 @@ func TestServerReadResource(t *testing.T) {
},
}
+ testTypeWriteOnly := tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "test_write_only": tftypes.String,
+ "test_required": tftypes.String,
+ },
+ }
+
testCurrentStateValue := tftypes.NewValue(testType, map[string]tftypes.Value{
"test_computed": tftypes.NewValue(tftypes.String, nil),
"test_required": tftypes.NewValue(tftypes.String, "test-currentstate-value"),
})
+ testCurrentStateValueWriteOnly := tftypes.NewValue(testTypeWriteOnly, map[string]tftypes.Value{
+ "test_write_only": tftypes.NewValue(tftypes.String, nil),
+ "test_required": tftypes.NewValue(tftypes.String, "test-currentstate-value"),
+ })
+
testNewStateValue := tftypes.NewValue(testType, map[string]tftypes.Value{
"test_computed": tftypes.NewValue(tftypes.String, "test-newstate-value"),
"test_required": tftypes.NewValue(tftypes.String, "test-currentstate-value"),
@@ -55,6 +67,18 @@ func TestServerReadResource(t *testing.T) {
},
}
+ testSchemaWriteOnly := schema.Schema{
+ Attributes: map[string]schema.Attribute{
+ "test_write_only": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "test_required": schema.StringAttribute{
+ Required: true,
+ },
+ },
+ }
+
testSchemaWithSemanticEquals := schema.Schema{
Attributes: map[string]schema.Attribute{
"test_computed": schema.StringAttribute{
@@ -97,6 +121,11 @@ func TestServerReadResource(t *testing.T) {
Schema: testSchema,
}
+ testCurrentStateWriteOnly := &tfsdk.State{
+ Raw: testCurrentStateValueWriteOnly,
+ Schema: testSchemaWriteOnly,
+ }
+
testNewState := &tfsdk.State{
Raw: testNewStateValue,
Schema: testSchema,
@@ -562,6 +591,38 @@ func TestServerReadResource(t *testing.T) {
Private: testEmptyPrivate,
},
},
+ "response-state-write-only-nullification": {
+ server: &fwserver.Server{
+ Provider: &testprovider.Provider{},
+ },
+ request: &fwserver.ReadResourceRequest{
+ CurrentState: testCurrentStateWriteOnly,
+ Resource: &testprovider.Resource{
+ ReadMethod: func(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var data struct {
+ TestWriteOnly types.String `tfsdk:"test_write_only"`
+ TestRequired types.String `tfsdk:"test_required"`
+ }
+
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+
+ data.TestWriteOnly = types.StringValue("test-write-only-value")
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+ },
+ },
+ },
+ expectedResponse: &fwserver.ReadResourceResponse{
+ NewState: &tfsdk.State{
+ Raw: tftypes.NewValue(testTypeWriteOnly, map[string]tftypes.Value{
+ "test_write_only": tftypes.NewValue(tftypes.String, nil),
+ "test_required": tftypes.NewValue(tftypes.String, "test-currentstate-value"),
+ }),
+ Schema: testSchemaWriteOnly,
+ },
+ Private: testEmptyPrivate,
+ },
+ },
"response-private": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
diff --git a/internal/fwserver/server_updateresource.go b/internal/fwserver/server_updateresource.go
index 9112c35c2..ad1d9f998 100644
--- a/internal/fwserver/server_updateresource.go
+++ b/internal/fwserver/server_updateresource.go
@@ -169,11 +169,21 @@ func (s *Server) UpdateResource(ctx context.Context, req *UpdateResourceRequest,
return
}
- if semanticEqualityResp.NewData.TerraformValue.Equal(resp.NewState.Raw) {
- return
+ if !semanticEqualityResp.NewData.TerraformValue.Equal(resp.NewState.Raw) {
+ logging.FrameworkDebug(ctx, "State updated due to semantic equality")
+
+ resp.NewState.Raw = semanticEqualityResp.NewData.TerraformValue
}
- logging.FrameworkDebug(ctx, "State updated due to semantic equality")
+ // Set any write-only attributes in the state to null
+ modifiedState, err := tftypes.Transform(resp.NewState.Raw, NullifyWriteOnlyAttributes(ctx, resp.NewState.Schema))
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error Modifying State",
+ "There was an unexpected error modifying the NewState. This is always a problem with the provider. Please report the following to the provider developer:\n\n"+err.Error(),
+ )
+ return
+ }
- resp.NewState.Raw = semanticEqualityResp.NewData.TerraformValue
+ resp.NewState.Raw = modifiedState
}
diff --git a/internal/fwserver/server_updateresource_test.go b/internal/fwserver/server_updateresource_test.go
index 6ad7a38d6..5c0bb5c7d 100644
--- a/internal/fwserver/server_updateresource_test.go
+++ b/internal/fwserver/server_updateresource_test.go
@@ -34,6 +34,13 @@ func TestServerUpdateResource(t *testing.T) {
},
}
+ testSchemaTypeWriteOnly := tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "test_required": tftypes.String,
+ "test_write_only": tftypes.String,
+ },
+ }
+
testSchema := schema.Schema{
Attributes: map[string]schema.Attribute{
"test_computed": schema.StringAttribute{
@@ -77,6 +84,18 @@ func TestServerUpdateResource(t *testing.T) {
},
}
+ testSchemaWithWriteOnly := schema.Schema{
+ Attributes: map[string]schema.Attribute{
+ "test_required": schema.StringAttribute{
+ Required: true,
+ },
+ "test_write_only": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ }
+
type testSchemaData struct {
TestComputed types.String `tfsdk:"test_computed"`
TestRequired types.String `tfsdk:"test_required"`
@@ -87,6 +106,11 @@ func TestServerUpdateResource(t *testing.T) {
TestRequired testtypes.StringValueWithSemanticEquals `tfsdk:"test_required"`
}
+ type testSchemaDataWriteOnly struct {
+ TestRequired types.String `tfsdk:"test_required"`
+ TestWriteOnly types.String `tfsdk:"test_write_only"`
+ }
+
testProviderMetaType := tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"test_provider_meta_attribute": tftypes.String,
@@ -777,6 +801,39 @@ func TestServerUpdateResource(t *testing.T) {
Private: testEmptyPrivate,
},
},
+ "response-newstate-write-only-nullification": {
+ server: &fwserver.Server{
+ Provider: &testprovider.Provider{},
+ },
+ request: &fwserver.UpdateResourceRequest{
+ PlannedState: &tfsdk.Plan{
+ Raw: tftypes.NewValue(testSchemaTypeWriteOnly, map[string]tftypes.Value{
+ "test_required": tftypes.NewValue(tftypes.String, "test-plannedstate-value"),
+ "test_write_only": tftypes.NewValue(tftypes.String, "test-write-only-value"),
+ }),
+ Schema: testSchemaWithWriteOnly,
+ },
+ ResourceSchema: testSchemaWithWriteOnly,
+ Resource: &testprovider.Resource{
+ UpdateMethod: func(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var data testSchemaDataWriteOnly
+
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+ },
+ },
+ },
+ expectedResponse: &fwserver.UpdateResourceResponse{
+ NewState: &tfsdk.State{
+ Raw: tftypes.NewValue(testSchemaTypeWriteOnly, map[string]tftypes.Value{
+ "test_required": tftypes.NewValue(tftypes.String, "test-plannedstate-value"),
+ "test_write_only": tftypes.NewValue(tftypes.String, nil),
+ }),
+ Schema: testSchemaWithWriteOnly,
+ },
+ Private: testEmptyPrivate,
+ },
+ },
"response-private": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
diff --git a/internal/fwserver/server_upgraderesourcestate.go b/internal/fwserver/server_upgraderesourcestate.go
index b2cc340e5..536c3dfd0 100644
--- a/internal/fwserver/server_upgraderesourcestate.go
+++ b/internal/fwserver/server_upgraderesourcestate.go
@@ -225,9 +225,19 @@ func (s *Server) UpgradeResourceState(ctx context.Context, req *UpgradeResourceS
return
}
+ // Set any write-only attributes in the state to null
+ modifiedState, err := tftypes.Transform(upgradedStateValue, NullifyWriteOnlyAttributes(ctx, req.ResourceSchema))
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error Modifying Upgraded Resource State",
+ "There was an unexpected error modifying the Upgraded Resource State. This is always a problem with the provider. Please report the following to the provider developer:\n\n"+err.Error(),
+ )
+ return
+ }
+
resp.UpgradedState = &tfsdk.State{
Schema: req.ResourceSchema,
- Raw: upgradedStateValue,
+ Raw: modifiedState,
}
return
@@ -243,5 +253,27 @@ func (s *Server) UpgradeResourceState(ctx context.Context, req *UpgradeResourceS
return
}
+ // Set any write-only attributes in the state to null
+ modifiedState, err := tftypes.Transform(upgradeResourceStateResponse.State.Raw, NullifyWriteOnlyAttributes(ctx, req.ResourceSchema))
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error Modifying Upgraded Resource State",
+ "There was an unexpected error modifying the Upgraded Resource State. This is always a problem with the provider. Please report the following to the provider developer:\n\n"+err.Error(),
+ )
+ return
+ }
+ upgradeResourceStateResponse.State.Raw = modifiedState
+
+ // If the write-only nullification results in a null state, then this is a provider error
+ if upgradeResourceStateResponse.State.Raw.Type() == nil || upgradeResourceStateResponse.State.Raw.IsNull() {
+ resp.Diagnostics.AddError(
+ "Missing Upgraded Resource State",
+ fmt.Sprintf("After attempting a resource state upgrade to version %d, the provider did not return any state data. ", req.Version)+
+ "Preventing the unexpected loss of resource state data. "+
+ "This is always an issue with the Terraform Provider and should be reported to the provider developer.",
+ )
+ return
+ }
+
resp.UpgradedState = &upgradeResourceStateResponse.State
}
diff --git a/internal/fwserver/server_upgraderesourcestate_test.go b/internal/fwserver/server_upgraderesourcestate_test.go
index b720bed7e..1e733874f 100644
--- a/internal/fwserver/server_upgraderesourcestate_test.go
+++ b/internal/fwserver/server_upgraderesourcestate_test.go
@@ -42,6 +42,23 @@ func TestServerUpgradeResourceState(t *testing.T) {
}
schemaType := testSchema.Type().TerraformType(ctx)
+ testSchemaWriteOnly := schema.Schema{
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Computed: true,
+ },
+ "write_only_attribute": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "required_attribute": schema.StringAttribute{
+ Required: true,
+ },
+ },
+ Version: 1, // Must be above 0
+ }
+ schemaTypeWriteOnly := testSchemaWriteOnly.Type().TerraformType(ctx)
+
testCases := map[string]struct {
server *fwserver.Server
request *fwserver.UpgradeResourceStateRequest
@@ -342,6 +359,71 @@ func TestServerUpgradeResourceState(t *testing.T) {
},
},
},
+ "RawState-DynamicValue-write-only-nullification": {
+ server: &fwserver.Server{
+ Provider: &testprovider.Provider{},
+ },
+ request: &fwserver.UpgradeResourceStateRequest{
+ RawState: testNewRawState(t, map[string]interface{}{
+ "id": "test-id-value",
+ "required_attribute": true,
+ }),
+ ResourceSchema: testSchemaWriteOnly,
+ Resource: &testprovider.ResourceWithUpgradeState{
+ Resource: &testprovider.Resource{},
+ UpgradeStateMethod: func(ctx context.Context) map[int64]resource.StateUpgrader {
+ return map[int64]resource.StateUpgrader{
+ 0: {
+ StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) {
+ var rawState struct {
+ Id string `json:"id"`
+ RequiredAttribute bool `json:"required_attribute"`
+ }
+
+ if err := json.Unmarshal(req.RawState.JSON, &rawState); err != nil {
+ resp.Diagnostics.AddError(
+ "Unable to Unmarshal Prior State",
+ err.Error(),
+ )
+ return
+ }
+
+ dynamicValue, err := tfprotov6.NewDynamicValue(
+ schemaTypeWriteOnly,
+ tftypes.NewValue(schemaTypeWriteOnly, map[string]tftypes.Value{
+ "id": tftypes.NewValue(tftypes.String, rawState.Id),
+ "write_only_attribute": tftypes.NewValue(tftypes.String, "write-only-dynamic-value"),
+ "required_attribute": tftypes.NewValue(tftypes.String, fmt.Sprintf("%t", rawState.RequiredAttribute)),
+ }),
+ )
+
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Unable to Create Upgraded State",
+ err.Error(),
+ )
+ return
+ }
+
+ resp.DynamicValue = &dynamicValue
+ },
+ },
+ }
+ },
+ },
+ Version: 0,
+ },
+ expectedResponse: &fwserver.UpgradeResourceStateResponse{
+ UpgradedState: &tfsdk.State{
+ Raw: tftypes.NewValue(schemaTypeWriteOnly, map[string]tftypes.Value{
+ "id": tftypes.NewValue(tftypes.String, "test-id-value"),
+ "write_only_attribute": tftypes.NewValue(tftypes.String, nil),
+ "required_attribute": tftypes.NewValue(tftypes.String, "true"),
+ }),
+ Schema: testSchemaWriteOnly,
+ },
+ },
+ },
"ResourceType-UpgradeState-not-implemented": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
@@ -517,6 +599,72 @@ func TestServerUpgradeResourceState(t *testing.T) {
},
},
},
+ "PriorSchema-and-State-write-only-nullification": {
+ server: &fwserver.Server{
+ Provider: &testprovider.Provider{},
+ },
+ request: &fwserver.UpgradeResourceStateRequest{
+ RawState: testNewRawState(t, map[string]interface{}{
+ "id": "test-id-value",
+ "required_attribute": true,
+ }),
+ ResourceSchema: testSchemaWriteOnly,
+ Resource: &testprovider.ResourceWithUpgradeState{
+ Resource: &testprovider.Resource{},
+ UpgradeStateMethod: func(ctx context.Context) map[int64]resource.StateUpgrader {
+ return map[int64]resource.StateUpgrader{
+ 0: {
+ PriorSchema: &schema.Schema{
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Computed: true,
+ },
+ "required_attribute": schema.BoolAttribute{
+ Required: true,
+ },
+ },
+ },
+ StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) {
+ var priorStateData struct {
+ Id string `tfsdk:"id"`
+ RequiredAttribute bool `tfsdk:"required_attribute"`
+ }
+
+ resp.Diagnostics.Append(req.State.Get(ctx, &priorStateData)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ upgradedStateData := struct {
+ Id string `tfsdk:"id"`
+ WriteOnlyAttribute string `tfsdk:"write_only_attribute"`
+ RequiredAttribute string `tfsdk:"required_attribute"`
+ }{
+ Id: priorStateData.Id,
+ WriteOnlyAttribute: "write-only-upgraded-state",
+ RequiredAttribute: fmt.Sprintf("%t", priorStateData.RequiredAttribute),
+ }
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, upgradedStateData)...)
+ },
+ },
+ }
+ },
+ },
+ Version: 0,
+ },
+ expectedResponse: &fwserver.UpgradeResourceStateResponse{
+ UpgradedState: &tfsdk.State{
+ Raw: tftypes.NewValue(schemaTypeWriteOnly, map[string]tftypes.Value{
+ "id": tftypes.NewValue(tftypes.String, "test-id-value"),
+ "write_only_attribute": tftypes.NewValue(tftypes.String, nil),
+ "required_attribute": tftypes.NewValue(tftypes.String, "true"),
+ }),
+ Schema: testSchemaWriteOnly,
+ },
+ },
+ },
"PriorSchema-and-State-json-mismatch": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
diff --git a/internal/fwserver/server_validatedatasourceconfig.go b/internal/fwserver/server_validatedatasourceconfig.go
index 3379b15ad..33653982d 100644
--- a/internal/fwserver/server_validatedatasourceconfig.go
+++ b/internal/fwserver/server_validatedatasourceconfig.go
@@ -9,6 +9,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/logging"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
)
@@ -96,8 +97,17 @@ func (s *Server) ValidateDataSourceConfig(ctx context.Context, req *ValidateData
resp.Diagnostics.Append(vdscResp.Diagnostics...)
}
+ schemaCapabilities := validator.ValidateSchemaClientCapabilities{
+ // The SchemaValidate function is shared between provider, resource,
+ // data source and ephemeral resource schemas; however, WriteOnlyAttributesAllowed
+ // capability is only valid for resource schemas, so this is explicitly set to false
+ // for all other schema types.
+ WriteOnlyAttributesAllowed: false,
+ }
+
validateSchemaReq := ValidateSchemaRequest{
- Config: *req.Config,
+ ClientCapabilities: schemaCapabilities,
+ Config: *req.Config,
}
// Instantiate a new response for each request to prevent validators
// from modifying or removing diagnostics.
diff --git a/internal/fwserver/server_validateephemeralresourceconfig.go b/internal/fwserver/server_validateephemeralresourceconfig.go
index a99a0dbfb..6956af068 100644
--- a/internal/fwserver/server_validateephemeralresourceconfig.go
+++ b/internal/fwserver/server_validateephemeralresourceconfig.go
@@ -9,6 +9,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
"github.com/hashicorp/terraform-plugin-framework/internal/logging"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
)
@@ -96,8 +97,17 @@ func (s *Server) ValidateEphemeralResourceConfig(ctx context.Context, req *Valid
resp.Diagnostics.Append(vdscResp.Diagnostics...)
}
+ schemaCapabilities := validator.ValidateSchemaClientCapabilities{
+ // The SchemaValidate function is shared between provider, resource,
+ // data source and ephemeral resource schemas; however, WriteOnlyAttributesAllowed
+ // capability is only valid for resource schemas, so this is explicitly set to false
+ // for all other schema types.
+ WriteOnlyAttributesAllowed: false,
+ }
+
validateSchemaReq := ValidateSchemaRequest{
- Config: *req.Config,
+ ClientCapabilities: schemaCapabilities,
+ Config: *req.Config,
}
// Instantiate a new response for each request to prevent validators
// from modifying or removing diagnostics.
diff --git a/internal/fwserver/server_validateproviderconfig.go b/internal/fwserver/server_validateproviderconfig.go
index 588f021c2..0109e6e07 100644
--- a/internal/fwserver/server_validateproviderconfig.go
+++ b/internal/fwserver/server_validateproviderconfig.go
@@ -9,6 +9,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/logging"
"github.com/hashicorp/terraform-plugin-framework/provider"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
)
@@ -77,8 +78,17 @@ func (s *Server) ValidateProviderConfig(ctx context.Context, req *ValidateProvid
resp.Diagnostics.Append(vpcRes.Diagnostics...)
}
+ schemaCapabilities := validator.ValidateSchemaClientCapabilities{
+ // The SchemaValidate function is shared between provider, resource,
+ // data source and ephemeral resource schemas; however, WriteOnlyAttributesAllowed
+ // capability is only valid for resource schemas, so this is explicitly set to false
+ // for all other schema types.
+ WriteOnlyAttributesAllowed: false,
+ }
+
validateSchemaReq := ValidateSchemaRequest{
- Config: *req.Config,
+ ClientCapabilities: schemaCapabilities,
+ Config: *req.Config,
}
// Instantiate a new response for each request to prevent validators
// from modifying or removing diagnostics.
diff --git a/internal/fwserver/server_validateresourceconfig.go b/internal/fwserver/server_validateresourceconfig.go
index 79e8ae9b7..591ce5a2f 100644
--- a/internal/fwserver/server_validateresourceconfig.go
+++ b/internal/fwserver/server_validateresourceconfig.go
@@ -9,14 +9,16 @@ import (
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/logging"
"github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
)
// ValidateResourceConfigRequest is the framework server request for the
// ValidateResourceConfig RPC.
type ValidateResourceConfigRequest struct {
- Config *tfsdk.Config
- Resource resource.Resource
+ ClientCapabilities resource.ValidateConfigClientCapabilities
+ Config *tfsdk.Config
+ Resource resource.Resource
}
// ValidateResourceConfigResponse is the framework server response for the
@@ -51,7 +53,8 @@ func (s *Server) ValidateResourceConfig(ctx context.Context, req *ValidateResour
}
vdscReq := resource.ValidateConfigRequest{
- Config: *req.Config,
+ ClientCapabilities: req.ClientCapabilities,
+ Config: *req.Config,
}
if resourceWithConfigValidators, ok := req.Resource.(resource.ResourceWithConfigValidators); ok {
@@ -96,8 +99,13 @@ func (s *Server) ValidateResourceConfig(ctx context.Context, req *ValidateResour
resp.Diagnostics.Append(vdscResp.Diagnostics...)
}
+ schemaCapabilities := validator.ValidateSchemaClientCapabilities{
+ WriteOnlyAttributesAllowed: req.ClientCapabilities.WriteOnlyAttributesAllowed,
+ }
+
validateSchemaReq := ValidateSchemaRequest{
- Config: *req.Config,
+ ClientCapabilities: schemaCapabilities,
+ Config: *req.Config,
}
// Instantiate a new response for each request to prevent validators
// from modifying or removing diagnostics.
diff --git a/internal/fwserver/server_validateresourceconfig_test.go b/internal/fwserver/server_validateresourceconfig_test.go
index 859612031..489d6a234 100644
--- a/internal/fwserver/server_validateresourceconfig_test.go
+++ b/internal/fwserver/server_validateresourceconfig_test.go
@@ -8,6 +8,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testprovider"
@@ -18,7 +20,6 @@ import (
"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"
)
func TestServerValidateResourceConfig(t *testing.T) {
@@ -47,6 +48,10 @@ func TestServerValidateResourceConfig(t *testing.T) {
Schema: testSchema,
}
+ testClientCapabilities := resource.ValidateConfigClientCapabilities{
+ WriteOnlyAttributesAllowed: true,
+ }
+
testSchemaAttributeValidator := schema.Schema{
Attributes: map[string]schema.Attribute{
"test": schema.StringAttribute{
@@ -69,6 +74,28 @@ func TestServerValidateResourceConfig(t *testing.T) {
Schema: testSchemaAttributeValidator,
}
+ testSchemaAttributeValidatorClientCapabilities := schema.Schema{
+ Attributes: map[string]schema.Attribute{
+ "test": schema.StringAttribute{
+ Required: true,
+ Validators: []validator.String{
+ testvalidator.String{
+ ValidateStringMethod: func(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError("Incorrect req.ClientCapabilities", "expected WriteOnlyAttributesAllowed client capability")
+ }
+ },
+ },
+ },
+ },
+ },
+ }
+
+ testConfigAttributeValidatorClientCapabilities := tfsdk.Config{
+ Raw: testValue,
+ Schema: testSchemaAttributeValidatorClientCapabilities,
+ }
+
testSchemaAttributeValidatorError := schema.Schema{
Attributes: map[string]schema.Attribute{
"test": schema.StringAttribute{
@@ -128,6 +155,21 @@ func TestServerValidateResourceConfig(t *testing.T) {
},
expectedResponse: &fwserver.ValidateResourceConfigResponse{},
},
+ "request-config-AttributeValidator-client-capabilities": {
+ server: &fwserver.Server{
+ Provider: &testprovider.Provider{},
+ },
+ request: &fwserver.ValidateResourceConfigRequest{
+ ClientCapabilities: testClientCapabilities,
+ Config: &testConfigAttributeValidatorClientCapabilities,
+ Resource: &testprovider.Resource{
+ SchemaMethod: func(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = testSchemaAttributeValidatorClientCapabilities
+ },
+ },
+ },
+ expectedResponse: &fwserver.ValidateResourceConfigResponse{},
+ },
"request-config-AttributeValidator-diagnostic": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
@@ -185,6 +227,34 @@ func TestServerValidateResourceConfig(t *testing.T) {
},
expectedResponse: &fwserver.ValidateResourceConfigResponse{},
},
+ "request-config-ResourceWithConfigValidators-client-capabilities": {
+ server: &fwserver.Server{
+ Provider: &testprovider.Provider{},
+ },
+ request: &fwserver.ValidateResourceConfigRequest{
+ ClientCapabilities: testClientCapabilities,
+ Config: &testConfig,
+ Resource: &testprovider.ResourceWithConfigValidators{
+ Resource: &testprovider.Resource{
+ SchemaMethod: func(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = testSchema
+ },
+ },
+ ConfigValidatorsMethod: func(ctx context.Context) []resource.ConfigValidator {
+ return []resource.ConfigValidator{
+ &testprovider.ResourceConfigValidator{
+ ValidateResourceMethod: func(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError("Incorrect req.ClientCapabilities", "expected WriteOnlyAttributesAllowed client capability")
+ }
+ },
+ },
+ }
+ },
+ },
+ },
+ expectedResponse: &fwserver.ValidateResourceConfigResponse{},
+ },
"request-config-ResourceWithConfigValidators-diagnostics": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
@@ -259,6 +329,28 @@ func TestServerValidateResourceConfig(t *testing.T) {
},
expectedResponse: &fwserver.ValidateResourceConfigResponse{},
},
+ "request-config-ResourceWithValidateConfig-client-capabilities": {
+ server: &fwserver.Server{
+ Provider: &testprovider.Provider{},
+ },
+ request: &fwserver.ValidateResourceConfigRequest{
+ ClientCapabilities: testClientCapabilities,
+ Config: &testConfig,
+ Resource: &testprovider.ResourceWithValidateConfig{
+ Resource: &testprovider.Resource{
+ SchemaMethod: func(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = testSchema
+ },
+ },
+ ValidateConfigMethod: func(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
+ if !req.ClientCapabilities.WriteOnlyAttributesAllowed {
+ resp.Diagnostics.AddError("Incorrect req.ClientCapabilities", "expected WriteOnlyAttributesAllowed client capability")
+ }
+ },
+ },
+ },
+ expectedResponse: &fwserver.ValidateResourceConfigResponse{},
+ },
"request-config-ResourceWithValidateConfig-diagnostic": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
diff --git a/internal/fwserver/write_only_nullification.go b/internal/fwserver/write_only_nullification.go
new file mode 100644
index 000000000..5bfc77609
--- /dev/null
+++ b/internal/fwserver/write_only_nullification.go
@@ -0,0 +1,77 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package fwserver
+
+import (
+ "context"
+ "errors"
+ "fmt"
+
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
+ "github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
+ "github.com/hashicorp/terraform-plugin-framework/internal/logging"
+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
+)
+
+// NullifyWriteOnlyAttributes transforms a tftypes.Value, setting all write-only attribute values
+// to null according to the given managed resource schema. This function is called in all managed
+// resource RPCs before a response is sent to Terraform Core. Terraform Core expects all write-only
+// attribute values to be null to prevent data consistency errors. This can technically be done
+// manually by the provider developers, but the Framework is handling it instead for convenience.
+func NullifyWriteOnlyAttributes(ctx context.Context, resourceSchema fwschema.Schema) func(*tftypes.AttributePath, tftypes.Value) (tftypes.Value, error) {
+ return func(path *tftypes.AttributePath, val tftypes.Value) (tftypes.Value, error) {
+ ctx = logging.FrameworkWithAttributePath(ctx, path.String())
+
+ // we are only modifying attributes, not the entire resource
+ if len(path.Steps()) < 1 {
+ return val, nil
+ }
+
+ attribute, err := resourceSchema.AttributeAtTerraformPath(ctx, path)
+
+ if err != nil {
+ if errors.Is(err, fwschema.ErrPathInsideAtomicAttribute) {
+ // ignore attributes/elements inside schema.Attributes, they have no schema of their own
+ logging.FrameworkTrace(ctx, "attribute is a non-schema attribute, not nullifying")
+ return val, nil
+ }
+
+ if errors.Is(err, fwschema.ErrPathIsBlock) {
+ // ignore blocks, they do not have a writeOnly field
+ logging.FrameworkTrace(ctx, "attribute is a block, not nullifying")
+ return val, nil
+ }
+
+ if errors.Is(err, fwschema.ErrPathInsideDynamicAttribute) {
+ // ignore attributes/elements inside schema.DynamicAttribute, they have no schema of their own
+ logging.FrameworkTrace(ctx, "attribute is inside of a dynamic attribute, not nullifying")
+ return val, nil
+ }
+
+ logging.FrameworkError(ctx, "couldn't find attribute in resource schema")
+
+ return tftypes.Value{}, fmt.Errorf("couldn't find attribute in resource schema: %w", err)
+ }
+
+ // Value type from new state to create null with
+ newValueType := attribute.GetType().TerraformType(ctx)
+
+ // If the attribute is dynamic set the new value type to DynamicPseudoType
+ // instead of the underlying concrete type
+ // TODO: verify if this is the correct behavior once Terraform Core implementation is complete
+ _, isDynamic := attribute.GetType().(basetypes.DynamicTypable)
+ if isDynamic {
+ newValueType = tftypes.DynamicPseudoType
+ }
+
+ if attribute.IsWriteOnly() && !val.IsNull() {
+ logging.FrameworkDebug(ctx, "Nullifying write-only attribute in the newState")
+
+ return tftypes.NewValue(newValueType, nil), nil
+ }
+
+ return val, nil
+ }
+}
diff --git a/internal/fwserver/write_only_nullification_test.go b/internal/fwserver/write_only_nullification_test.go
new file mode 100644
index 000000000..7aba06585
--- /dev/null
+++ b/internal/fwserver/write_only_nullification_test.go
@@ -0,0 +1,1710 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package fwserver
+
+import (
+ "context"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+func TestNullifyWriteOnlyAttributes(t *testing.T) {
+ t.Parallel()
+
+ s := schema.Schema{
+ Attributes: map[string]schema.Attribute{
+ "string-value": schema.StringAttribute{
+ Required: true,
+ },
+ "string-nil": schema.StringAttribute{
+ Optional: true,
+ },
+ "string-nil-write-only": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "string-value-write-only": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "dynamic-value": schema.DynamicAttribute{
+ Required: true,
+ },
+ "dynamic-nil": schema.DynamicAttribute{
+ Optional: true,
+ },
+ "dynamic-underlying-string-nil-computed": schema.DynamicAttribute{
+ WriteOnly: true,
+ },
+ "dynamic-nil-write-only": schema.DynamicAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "dynamic-value-write-only": schema.DynamicAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "dynamic-value-with-underlying-list-write-only": schema.DynamicAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "object-nil-write-only": schema.ObjectAttribute{
+ AttributeTypes: map[string]attr.Type{
+ "string-nil": types.StringType,
+ "string-set": types.StringType,
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ "object-value-write-only": schema.ObjectAttribute{
+ AttributeTypes: map[string]attr.Type{
+ "string-nil": types.StringType,
+ "string-set": types.StringType,
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ "nested-nil-write-only": schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "string-nil": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "string-set": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ "nested-value-write-only": schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "string-nil": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "string-set": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ Blocks: map[string]schema.Block{
+ "block-nil-write-only": schema.SetNestedBlock{
+ NestedObject: schema.NestedBlockObject{
+ Attributes: map[string]schema.Attribute{
+ "string-nil": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "string-set": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ "block-value-write-only": schema.SetNestedBlock{
+ NestedObject: schema.NestedBlockObject{
+ Attributes: map[string]schema.Attribute{
+ "string-nil": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "string-set": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ }
+ input := tftypes.NewValue(s.Type().TerraformType(context.Background()), map[string]tftypes.Value{
+ "string-value": tftypes.NewValue(tftypes.String, "hello, world"),
+ "string-nil": tftypes.NewValue(tftypes.String, nil),
+ "string-nil-write-only": tftypes.NewValue(tftypes.String, nil),
+ "string-value-write-only": tftypes.NewValue(tftypes.String, "hello, world"),
+ "dynamic-value": tftypes.NewValue(tftypes.String, "hello, world"),
+ "dynamic-nil": tftypes.NewValue(tftypes.DynamicPseudoType, nil),
+ "dynamic-underlying-string-nil-computed": tftypes.NewValue(tftypes.String, nil),
+ "dynamic-nil-write-only": tftypes.NewValue(tftypes.DynamicPseudoType, nil),
+ "dynamic-value-write-only": tftypes.NewValue(tftypes.String, "hello, world"),
+ "dynamic-value-with-underlying-list-write-only": tftypes.NewValue(
+ tftypes.List{
+ ElementType: tftypes.Bool,
+ },
+ []tftypes.Value{
+ tftypes.NewValue(tftypes.Bool, true),
+ tftypes.NewValue(tftypes.Bool, false),
+ },
+ ),
+ "object-nil-write-only": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ }, nil),
+ "object-value-write-only": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ }, map[string]tftypes.Value{
+ "string-nil": tftypes.NewValue(tftypes.String, nil),
+ "string-set": tftypes.NewValue(tftypes.String, "foo"),
+ }),
+ "nested-nil-write-only": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ }, nil),
+ "nested-value-write-only": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ }, map[string]tftypes.Value{
+ "string-nil": tftypes.NewValue(tftypes.String, nil),
+ "string-set": tftypes.NewValue(tftypes.String, "bar"),
+ }),
+ "block-nil-write-only": tftypes.NewValue(tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ },
+ }, nil),
+ "block-value-write-only": tftypes.NewValue(tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ },
+ }, []tftypes.Value{
+ tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ }, map[string]tftypes.Value{
+ "string-nil": tftypes.NewValue(tftypes.String, nil),
+ "string-set": tftypes.NewValue(tftypes.String, "bar"),
+ }),
+ }),
+ })
+ expected := tftypes.NewValue(s.Type().TerraformType(context.Background()), map[string]tftypes.Value{
+ "string-value": tftypes.NewValue(tftypes.String, "hello, world"),
+ "string-nil": tftypes.NewValue(tftypes.String, nil),
+ "string-nil-write-only": tftypes.NewValue(tftypes.String, nil),
+ "string-value-write-only": tftypes.NewValue(tftypes.String, nil),
+ "dynamic-value": tftypes.NewValue(tftypes.String, "hello, world"),
+ "dynamic-nil": tftypes.NewValue(tftypes.DynamicPseudoType, nil),
+ "dynamic-underlying-string-nil-computed": tftypes.NewValue(tftypes.DynamicPseudoType, nil),
+ "dynamic-nil-write-only": tftypes.NewValue(tftypes.DynamicPseudoType, nil),
+ "dynamic-value-write-only": tftypes.NewValue(tftypes.DynamicPseudoType, nil),
+ "dynamic-value-with-underlying-list-write-only": tftypes.NewValue(tftypes.DynamicPseudoType, nil),
+ "object-nil-write-only": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ }, nil),
+ "object-value-write-only": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ }, nil),
+ "nested-nil-write-only": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ }, nil),
+ "nested-value-write-only": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ }, nil),
+ "block-nil-write-only": tftypes.NewValue(tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ },
+ }, nil),
+ "block-value-write-only": tftypes.NewValue(tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ },
+ }, []tftypes.Value{
+ tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "string-nil": tftypes.String,
+ "string-set": tftypes.String,
+ },
+ }, map[string]tftypes.Value{
+ "string-nil": tftypes.NewValue(tftypes.String, nil),
+ "string-set": tftypes.NewValue(tftypes.String, nil),
+ }),
+ }),
+ })
+
+ got, err := tftypes.Transform(input, NullifyWriteOnlyAttributes(context.Background(), s))
+ if err != nil {
+ t.Errorf("Unexpected error: %s", err)
+ return
+ }
+
+ diff, err := expected.Diff(got)
+ if err != nil {
+ t.Errorf("Error diffing values: %s", err)
+ return
+ }
+ for _, valDiff := range diff {
+ t.Errorf("Unexpected diff at path %v: expected: %v, got: %v", valDiff.Path, valDiff.Value1, valDiff.Value2)
+ }
+}
+
+func TestNullifyWriteOnlyAttributes_NestedTypes(t *testing.T) {
+ t.Parallel()
+ nestedObjectType := tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ },
+ }
+
+ s := schema.Schema{
+ Attributes: map[string]schema.Attribute{
+ "single-nested-attribute": schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "nested-single-nested-attribute": schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ Optional: true,
+ },
+ "nested-single-nested-attribute-wo": schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ Optional: true,
+ },
+ "single-nested-attribute-wo": schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "nested-single-nested-attribute": schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ Optional: true,
+ },
+ "nested-single-nested-attribute-wo": schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ "map-nested-attribute": schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "nested-map-nested-attribute": schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ "nested-map-nested-attribute-wo": schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ },
+ "map-nested-attribute-wo": schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "nested-map-nested-attribute": schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ },
+ "nested-map-nested-attribute-wo": schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ "list-nested-attribute": schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "nested-list-nested-attribute": schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ },
+ "nested-list-nested-attribute-wo": schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ },
+ "list-nested-attribute-wo": schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "nested-list-nested-attribute": schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ },
+ "nested-list-nested-attribute-wo": schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ "set-nested-attribute": schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "nested-set-nested-attribute": schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ },
+ "nested-set-nested-attribute-wo": schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ },
+ "set-nested-attribute-wo": schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "nested-set-nested-attribute": schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ },
+ "nested-set-nested-attribute-wo": schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ Blocks: map[string]schema.Block{
+ "single-nested-block": schema.SingleNestedBlock{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "nested-single-nested-attribute": schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ Optional: true,
+ },
+ "nested-single-nested-attribute-wo": schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ Blocks: map[string]schema.Block{
+ "nested-single-nested-block": schema.SingleNestedBlock{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "nested-single-nested-attribute": schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ Optional: true,
+ },
+ "nested-single-nested-attribute-wo": schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ "list-nested-block": schema.ListNestedBlock{
+ NestedObject: schema.NestedBlockObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "nested-list-nested-attribute": schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ },
+ "nested-list-nested-attribute-wo": schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ Blocks: map[string]schema.Block{
+ "nested-list-nested-block": schema.ListNestedBlock{
+ NestedObject: schema.NestedBlockObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "nested-list-nested-attribute": schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ },
+ "nested-list-nested-attribute-wo": schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "set-nested-block": schema.SetNestedBlock{
+ NestedObject: schema.NestedBlockObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "nested-set-nested-attribute": schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ },
+ "nested-set-nested-attribute-wo": schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ Blocks: map[string]schema.Block{
+ "nested-set-nested-block": schema.SetNestedBlock{
+ NestedObject: schema.NestedBlockObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ "nested-set-nested-attribute": schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ },
+ "nested-set-nested-attribute-wo": schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "nested-string": schema.StringAttribute{
+ Optional: true,
+ },
+ "nested-string-wo": schema.StringAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+ input := tftypes.NewValue(s.Type().TerraformType(context.Background()), map[string]tftypes.Value{
+ "single-nested-attribute": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-single-nested-attribute": nestedObjectType,
+ "nested-single-nested-attribute-wo": nestedObjectType,
+ },
+ },
+ map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ "nested-single-nested-attribute": tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ "nested-single-nested-attribute-wo": tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ "single-nested-attribute-wo": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-single-nested-attribute": nestedObjectType,
+ "nested-single-nested-attribute-wo": nestedObjectType,
+ },
+ },
+ map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ "nested-single-nested-attribute": tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ "nested-single-nested-attribute-wo": tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ "map-nested-attribute": tftypes.NewValue(tftypes.Map{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-map-nested-attribute": tftypes.Map{ElementType: nestedObjectType},
+ "nested-map-nested-attribute-wo": tftypes.Map{ElementType: nestedObjectType},
+ },
+ },
+ }, map[string]tftypes.Value{
+ "keyA": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-map-nested-attribute": tftypes.Map{ElementType: nestedObjectType},
+ "nested-map-nested-attribute-wo": tftypes.Map{ElementType: nestedObjectType},
+ },
+ }, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ "nested-map-nested-attribute": tftypes.NewValue(tftypes.Map{ElementType: nestedObjectType}, map[string]tftypes.Value{
+ "keyA": tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ "nested-map-nested-attribute-wo": tftypes.NewValue(tftypes.Map{ElementType: nestedObjectType}, map[string]tftypes.Value{
+ "keyA": tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ }),
+ }),
+ "map-nested-attribute-wo": tftypes.NewValue(tftypes.Map{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-map-nested-attribute": tftypes.Map{ElementType: nestedObjectType},
+ "nested-map-nested-attribute-wo": tftypes.Map{ElementType: nestedObjectType},
+ },
+ },
+ }, map[string]tftypes.Value{
+ "keyA": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-map-nested-attribute": tftypes.Map{ElementType: nestedObjectType},
+ "nested-map-nested-attribute-wo": tftypes.Map{ElementType: nestedObjectType},
+ },
+ }, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ "nested-map-nested-attribute": tftypes.NewValue(tftypes.Map{ElementType: nestedObjectType}, map[string]tftypes.Value{
+ "keyA": tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ "nested-map-nested-attribute-wo": tftypes.NewValue(tftypes.Map{ElementType: nestedObjectType}, map[string]tftypes.Value{
+ "keyA": tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ }),
+ }),
+ "list-nested-attribute": tftypes.NewValue(tftypes.List{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-list-nested-attribute": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-attribute-wo": tftypes.List{ElementType: nestedObjectType},
+ },
+ },
+ }, []tftypes.Value{
+ tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-list-nested-attribute": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-attribute-wo": tftypes.List{ElementType: nestedObjectType},
+ },
+ }, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ "nested-list-nested-attribute": tftypes.NewValue(tftypes.List{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ "nested-list-nested-attribute-wo": tftypes.NewValue(tftypes.List{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ }),
+ }),
+ "list-nested-attribute-wo": tftypes.NewValue(tftypes.List{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-list-nested-attribute": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-attribute-wo": tftypes.List{ElementType: nestedObjectType},
+ },
+ },
+ }, []tftypes.Value{
+ tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-list-nested-attribute": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-attribute-wo": tftypes.List{ElementType: nestedObjectType},
+ },
+ }, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ "nested-list-nested-attribute": tftypes.NewValue(tftypes.List{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ "nested-list-nested-attribute-wo": tftypes.NewValue(tftypes.List{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ }),
+ }),
+ "set-nested-attribute": tftypes.NewValue(tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-set-nested-attribute": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-attribute-wo": tftypes.Set{ElementType: nestedObjectType},
+ },
+ },
+ }, []tftypes.Value{
+ tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-set-nested-attribute": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-attribute-wo": tftypes.Set{ElementType: nestedObjectType},
+ },
+ }, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ "nested-set-nested-attribute": tftypes.NewValue(tftypes.Set{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ "nested-set-nested-attribute-wo": tftypes.NewValue(tftypes.Set{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ }),
+ }),
+ "set-nested-attribute-wo": tftypes.NewValue(tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-set-nested-attribute": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-attribute-wo": tftypes.Set{ElementType: nestedObjectType},
+ },
+ },
+ }, []tftypes.Value{
+ tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-set-nested-attribute": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-attribute-wo": tftypes.Set{ElementType: nestedObjectType},
+ },
+ }, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ "nested-set-nested-attribute": tftypes.NewValue(tftypes.Set{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ "nested-set-nested-attribute-wo": tftypes.NewValue(tftypes.Set{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ }),
+ }),
+ "single-nested-block": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-single-nested-attribute": nestedObjectType,
+ "nested-single-nested-attribute-wo": nestedObjectType,
+ "nested-single-nested-block": tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-single-nested-attribute": nestedObjectType,
+ "nested-single-nested-attribute-wo": nestedObjectType,
+ },
+ },
+ },
+ },
+ map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ "nested-single-nested-attribute": tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ "nested-single-nested-attribute-wo": tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ "nested-single-nested-block": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-single-nested-attribute": nestedObjectType,
+ "nested-single-nested-attribute-wo": nestedObjectType,
+ },
+ },
+ map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ "nested-single-nested-attribute": tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ "nested-single-nested-attribute-wo": tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ }),
+ "list-nested-block": tftypes.NewValue(tftypes.List{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-list-nested-attribute": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-attribute-wo": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-block": tftypes.List{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-list-nested-attribute": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-attribute-wo": tftypes.List{ElementType: nestedObjectType},
+ },
+ },
+ },
+ },
+ },
+ }, []tftypes.Value{
+ tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-list-nested-attribute": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-attribute-wo": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-block": tftypes.List{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-list-nested-attribute": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-attribute-wo": tftypes.List{ElementType: nestedObjectType},
+ },
+ },
+ },
+ },
+ }, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ "nested-list-nested-attribute": tftypes.NewValue(tftypes.List{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ "nested-list-nested-attribute-wo": tftypes.NewValue(tftypes.List{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ "nested-list-nested-block": tftypes.NewValue(tftypes.List{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-list-nested-attribute": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-attribute-wo": tftypes.List{ElementType: nestedObjectType},
+ },
+ },
+ }, []tftypes.Value{
+ tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-list-nested-attribute": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-attribute-wo": tftypes.List{ElementType: nestedObjectType},
+ },
+ }, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ "nested-list-nested-attribute": tftypes.NewValue(tftypes.List{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ "nested-list-nested-attribute-wo": tftypes.NewValue(tftypes.List{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ }),
+ }),
+ }),
+ }),
+ "set-nested-block": tftypes.NewValue(tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-set-nested-attribute": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-attribute-wo": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-block": tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-set-nested-attribute": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-attribute-wo": tftypes.Set{ElementType: nestedObjectType},
+ },
+ },
+ },
+ },
+ },
+ }, []tftypes.Value{
+ tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-set-nested-attribute": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-attribute-wo": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-block": tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-set-nested-attribute": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-attribute-wo": tftypes.Set{ElementType: nestedObjectType},
+ },
+ },
+ },
+ },
+ }, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ "nested-set-nested-attribute": tftypes.NewValue(tftypes.Set{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ "nested-set-nested-attribute-wo": tftypes.NewValue(tftypes.Set{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ "nested-set-nested-block": tftypes.NewValue(tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-set-nested-attribute": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-attribute-wo": tftypes.Set{ElementType: nestedObjectType},
+ },
+ },
+ }, []tftypes.Value{
+ tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-set-nested-attribute": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-attribute-wo": tftypes.Set{ElementType: nestedObjectType},
+ },
+ }, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ "nested-set-nested-attribute": tftypes.NewValue(tftypes.Set{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ "nested-set-nested-attribute-wo": tftypes.NewValue(tftypes.Set{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, "foo-wo"),
+ }),
+ }),
+ }),
+ }),
+ }),
+ }),
+ })
+ expected := tftypes.NewValue(s.Type().TerraformType(context.Background()), map[string]tftypes.Value{
+ "single-nested-attribute": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-single-nested-attribute": nestedObjectType,
+ "nested-single-nested-attribute-wo": nestedObjectType,
+ },
+ },
+ map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ "nested-single-nested-attribute": tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ }),
+ "nested-single-nested-attribute-wo": tftypes.NewValue(nestedObjectType, nil),
+ }),
+ "single-nested-attribute-wo": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-single-nested-attribute": nestedObjectType,
+ "nested-single-nested-attribute-wo": nestedObjectType,
+ },
+ }, nil),
+ "map-nested-attribute": tftypes.NewValue(tftypes.Map{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-map-nested-attribute": tftypes.Map{ElementType: nestedObjectType},
+ "nested-map-nested-attribute-wo": tftypes.Map{ElementType: nestedObjectType},
+ },
+ },
+ }, map[string]tftypes.Value{
+ "keyA": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-map-nested-attribute": tftypes.Map{ElementType: nestedObjectType},
+ "nested-map-nested-attribute-wo": tftypes.Map{ElementType: nestedObjectType},
+ },
+ }, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ "nested-map-nested-attribute": tftypes.NewValue(tftypes.Map{ElementType: nestedObjectType}, map[string]tftypes.Value{
+ "keyA": tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ }),
+ }),
+ "nested-map-nested-attribute-wo": tftypes.NewValue(tftypes.Map{ElementType: nestedObjectType}, nil),
+ }),
+ }),
+ "map-nested-attribute-wo": tftypes.NewValue(tftypes.Map{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-map-nested-attribute": tftypes.Map{ElementType: nestedObjectType},
+ "nested-map-nested-attribute-wo": tftypes.Map{ElementType: nestedObjectType},
+ },
+ },
+ }, nil),
+ "list-nested-attribute": tftypes.NewValue(tftypes.List{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-list-nested-attribute": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-attribute-wo": tftypes.List{ElementType: nestedObjectType},
+ },
+ },
+ }, []tftypes.Value{
+ tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-list-nested-attribute": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-attribute-wo": tftypes.List{ElementType: nestedObjectType},
+ },
+ }, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ "nested-list-nested-attribute": tftypes.NewValue(tftypes.List{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ }),
+ }),
+ "nested-list-nested-attribute-wo": tftypes.NewValue(tftypes.List{ElementType: nestedObjectType}, nil),
+ }),
+ }),
+ "list-nested-attribute-wo": tftypes.NewValue(tftypes.List{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-list-nested-attribute": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-attribute-wo": tftypes.List{ElementType: nestedObjectType},
+ },
+ },
+ }, nil),
+ "set-nested-attribute": tftypes.NewValue(tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-set-nested-attribute": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-attribute-wo": tftypes.Set{ElementType: nestedObjectType},
+ },
+ },
+ }, []tftypes.Value{
+ tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-set-nested-attribute": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-attribute-wo": tftypes.Set{ElementType: nestedObjectType},
+ },
+ }, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ "nested-set-nested-attribute": tftypes.NewValue(tftypes.Set{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ }),
+ }),
+ "nested-set-nested-attribute-wo": tftypes.NewValue(tftypes.Set{ElementType: nestedObjectType}, nil),
+ }),
+ }),
+ "set-nested-attribute-wo": tftypes.NewValue(tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-set-nested-attribute": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-attribute-wo": tftypes.Set{ElementType: nestedObjectType},
+ },
+ },
+ }, nil),
+ "single-nested-block": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-single-nested-attribute": nestedObjectType,
+ "nested-single-nested-attribute-wo": nestedObjectType,
+ "nested-single-nested-block": tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-single-nested-attribute": nestedObjectType,
+ "nested-single-nested-attribute-wo": nestedObjectType,
+ },
+ },
+ },
+ },
+ map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ "nested-single-nested-attribute": tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ }),
+ "nested-single-nested-attribute-wo": tftypes.NewValue(nestedObjectType, nil),
+ "nested-single-nested-block": tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-single-nested-attribute": nestedObjectType,
+ "nested-single-nested-attribute-wo": nestedObjectType,
+ },
+ },
+ map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ "nested-single-nested-attribute": tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ }),
+ "nested-single-nested-attribute-wo": tftypes.NewValue(nestedObjectType, nil),
+ }),
+ }),
+ "list-nested-block": tftypes.NewValue(tftypes.List{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-list-nested-attribute": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-attribute-wo": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-block": tftypes.List{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-list-nested-attribute": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-attribute-wo": tftypes.List{ElementType: nestedObjectType},
+ },
+ },
+ },
+ },
+ },
+ }, []tftypes.Value{
+ tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-list-nested-attribute": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-attribute-wo": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-block": tftypes.List{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-list-nested-attribute": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-attribute-wo": tftypes.List{ElementType: nestedObjectType},
+ },
+ },
+ },
+ },
+ }, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ "nested-list-nested-attribute": tftypes.NewValue(tftypes.List{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ }),
+ }),
+ "nested-list-nested-attribute-wo": tftypes.NewValue(tftypes.List{ElementType: nestedObjectType}, nil),
+ "nested-list-nested-block": tftypes.NewValue(tftypes.List{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-list-nested-attribute": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-attribute-wo": tftypes.List{ElementType: nestedObjectType},
+ },
+ },
+ }, []tftypes.Value{
+ tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-list-nested-attribute": tftypes.List{ElementType: nestedObjectType},
+ "nested-list-nested-attribute-wo": tftypes.List{ElementType: nestedObjectType},
+ },
+ }, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ "nested-list-nested-attribute": tftypes.NewValue(tftypes.List{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ }),
+ }),
+ "nested-list-nested-attribute-wo": tftypes.NewValue(tftypes.List{ElementType: nestedObjectType}, nil),
+ }),
+ }),
+ }),
+ }),
+ "set-nested-block": tftypes.NewValue(tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-set-nested-attribute": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-attribute-wo": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-block": tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-set-nested-attribute": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-attribute-wo": tftypes.Set{ElementType: nestedObjectType},
+ },
+ },
+ },
+ },
+ },
+ }, []tftypes.Value{
+ tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-set-nested-attribute": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-attribute-wo": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-block": tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-set-nested-attribute": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-attribute-wo": tftypes.Set{ElementType: nestedObjectType},
+ },
+ },
+ },
+ },
+ }, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ "nested-set-nested-attribute": tftypes.NewValue(tftypes.Set{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ }),
+ }),
+ "nested-set-nested-attribute-wo": tftypes.NewValue(tftypes.Set{ElementType: nestedObjectType}, nil),
+ "nested-set-nested-block": tftypes.NewValue(tftypes.Set{
+ ElementType: tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-set-nested-attribute": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-attribute-wo": tftypes.Set{ElementType: nestedObjectType},
+ },
+ },
+ }, []tftypes.Value{
+ tftypes.NewValue(tftypes.Object{
+ AttributeTypes: map[string]tftypes.Type{
+ "nested-string": tftypes.String,
+ "nested-string-wo": tftypes.String,
+ "nested-set-nested-attribute": tftypes.Set{ElementType: nestedObjectType},
+ "nested-set-nested-attribute-wo": tftypes.Set{ElementType: nestedObjectType},
+ },
+ }, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ "nested-set-nested-attribute": tftypes.NewValue(tftypes.Set{ElementType: nestedObjectType}, []tftypes.Value{
+ tftypes.NewValue(nestedObjectType, map[string]tftypes.Value{
+ "nested-string": tftypes.NewValue(tftypes.String, "foo"),
+ "nested-string-wo": tftypes.NewValue(tftypes.String, nil),
+ }),
+ }),
+ "nested-set-nested-attribute-wo": tftypes.NewValue(tftypes.Set{ElementType: nestedObjectType}, nil),
+ }),
+ }),
+ }),
+ }),
+ })
+ got, err := tftypes.Transform(input, NullifyWriteOnlyAttributes(context.Background(), s))
+ if err != nil {
+ t.Errorf("Unexpected error: %s", err)
+ return
+ }
+
+ diff, err := expected.Diff(got)
+ if err != nil {
+ t.Errorf("Error diffing values: %s", err)
+ return
+ }
+ for _, valDiff := range diff {
+ t.Errorf("Unexpected diff at path %v: expected: %v, got: %v", valDiff.Path, valDiff.Value1, valDiff.Value2)
+ }
+}
diff --git a/internal/proto6server/server_validateresourceconfig_test.go b/internal/proto6server/server_validateresourceconfig_test.go
index 11e911745..36ea3f91c 100644
--- a/internal/proto6server/server_validateresourceconfig_test.go
+++ b/internal/proto6server/server_validateresourceconfig_test.go
@@ -8,12 +8,13 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tfprotov6"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testprovider"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
- "github.com/hashicorp/terraform-plugin-go/tfprotov6"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestServerValidateResourceConfig(t *testing.T) {
diff --git a/internal/testing/testschema/attribute.go b/internal/testing/testschema/attribute.go
index fccc26b42..979db68eb 100644
--- a/internal/testing/testschema/attribute.go
+++ b/internal/testing/testschema/attribute.go
@@ -4,9 +4,10 @@
package testschema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
var _ fwschema.Attribute = Attribute{}
@@ -19,6 +20,7 @@ type Attribute struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Type attr.Type
}
@@ -77,3 +79,8 @@ func (a Attribute) IsRequired() bool {
func (a Attribute) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a Attribute) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithbooldefault.go b/internal/testing/testschema/attributewithbooldefault.go
index 5d7b25671..66edc07e5 100644
--- a/internal/testing/testschema/attributewithbooldefault.go
+++ b/internal/testing/testschema/attributewithbooldefault.go
@@ -22,6 +22,7 @@ type AttributeWithBoolDefaultValue struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Default defaults.Bool
}
@@ -85,3 +86,8 @@ func (a AttributeWithBoolDefaultValue) IsRequired() bool {
func (a AttributeWithBoolDefaultValue) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithBoolDefaultValue) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithboolplanmodifiers.go b/internal/testing/testschema/attributewithboolplanmodifiers.go
index d27661894..397f18186 100644
--- a/internal/testing/testschema/attributewithboolplanmodifiers.go
+++ b/internal/testing/testschema/attributewithboolplanmodifiers.go
@@ -4,12 +4,13 @@
package testschema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
var _ fwxschema.AttributeWithBoolPlanModifiers = AttributeWithBoolPlanModifiers{}
@@ -22,6 +23,7 @@ type AttributeWithBoolPlanModifiers struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
PlanModifiers []planmodifier.Bool
}
@@ -85,3 +87,8 @@ func (a AttributeWithBoolPlanModifiers) IsRequired() bool {
func (a AttributeWithBoolPlanModifiers) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithBoolPlanModifiers) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithboolvalidators.go b/internal/testing/testschema/attributewithboolvalidators.go
index 5cc0943ca..044c25cf4 100644
--- a/internal/testing/testschema/attributewithboolvalidators.go
+++ b/internal/testing/testschema/attributewithboolvalidators.go
@@ -4,12 +4,13 @@
package testschema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
var _ fwxschema.AttributeWithBoolValidators = AttributeWithBoolValidators{}
@@ -22,6 +23,7 @@ type AttributeWithBoolValidators struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Validators []validator.Bool
}
@@ -85,3 +87,8 @@ func (a AttributeWithBoolValidators) IsRequired() bool {
func (a AttributeWithBoolValidators) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithBoolValidators) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithdynamicdefault.go b/internal/testing/testschema/attributewithdynamicdefault.go
index d366beb9a..b562132a8 100644
--- a/internal/testing/testschema/attributewithdynamicdefault.go
+++ b/internal/testing/testschema/attributewithdynamicdefault.go
@@ -22,6 +22,7 @@ type AttributeWithDynamicDefaultValue struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Default defaults.Dynamic
}
@@ -85,3 +86,8 @@ func (a AttributeWithDynamicDefaultValue) IsRequired() bool {
func (a AttributeWithDynamicDefaultValue) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithDynamicDefaultValue) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithdynamicplanmodifiers.go b/internal/testing/testschema/attributewithdynamicplanmodifiers.go
index abb7ca6bf..74a80587a 100644
--- a/internal/testing/testschema/attributewithdynamicplanmodifiers.go
+++ b/internal/testing/testschema/attributewithdynamicplanmodifiers.go
@@ -22,6 +22,7 @@ type AttributeWithDynamicPlanModifiers struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
PlanModifiers []planmodifier.Dynamic
}
@@ -85,3 +86,8 @@ func (a AttributeWithDynamicPlanModifiers) IsSensitive() bool {
func (a AttributeWithDynamicPlanModifiers) DynamicPlanModifiers() []planmodifier.Dynamic {
return a.PlanModifiers
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithDynamicPlanModifiers) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithdynamicvalidators.go b/internal/testing/testschema/attributewithdynamicvalidators.go
index 1fe086775..e4ef1024e 100644
--- a/internal/testing/testschema/attributewithdynamicvalidators.go
+++ b/internal/testing/testschema/attributewithdynamicvalidators.go
@@ -4,12 +4,13 @@
package testschema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
var _ fwxschema.AttributeWithDynamicValidators = AttributeWithDynamicValidators{}
@@ -22,6 +23,7 @@ type AttributeWithDynamicValidators struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Validators []validator.Dynamic
}
@@ -85,3 +87,8 @@ func (a AttributeWithDynamicValidators) IsSensitive() bool {
func (a AttributeWithDynamicValidators) DynamicValidators() []validator.Dynamic {
return a.Validators
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithDynamicValidators) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithfloat32default.go b/internal/testing/testschema/attributewithfloat32default.go
index e1dabdd87..c3aeb627d 100644
--- a/internal/testing/testschema/attributewithfloat32default.go
+++ b/internal/testing/testschema/attributewithfloat32default.go
@@ -22,6 +22,7 @@ type AttributeWithFloat32DefaultValue struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Default defaults.Float32
}
@@ -85,3 +86,8 @@ func (a AttributeWithFloat32DefaultValue) IsRequired() bool {
func (a AttributeWithFloat32DefaultValue) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithFloat32DefaultValue) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithfloat32planmodifiers.go b/internal/testing/testschema/attributewithfloat32planmodifiers.go
index b3ef10ebc..f93d87229 100644
--- a/internal/testing/testschema/attributewithfloat32planmodifiers.go
+++ b/internal/testing/testschema/attributewithfloat32planmodifiers.go
@@ -23,6 +23,7 @@ type AttributeWithFloat32PlanModifiers struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
PlanModifiers []planmodifier.Float32
}
@@ -86,3 +87,8 @@ func (a AttributeWithFloat32PlanModifiers) IsRequired() bool {
func (a AttributeWithFloat32PlanModifiers) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithFloat32PlanModifiers) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithfloat32validators.go b/internal/testing/testschema/attributewithfloat32validators.go
index d9d38f9ea..7fb02a5ad 100644
--- a/internal/testing/testschema/attributewithfloat32validators.go
+++ b/internal/testing/testschema/attributewithfloat32validators.go
@@ -23,6 +23,7 @@ type AttributeWithFloat32Validators struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Validators []validator.Float32
}
@@ -86,3 +87,8 @@ func (a AttributeWithFloat32Validators) IsRequired() bool {
func (a AttributeWithFloat32Validators) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithFloat32Validators) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithfloat64default.go b/internal/testing/testschema/attributewithfloat64default.go
index 33b717f33..484ec37d9 100644
--- a/internal/testing/testschema/attributewithfloat64default.go
+++ b/internal/testing/testschema/attributewithfloat64default.go
@@ -22,6 +22,7 @@ type AttributeWithFloat64DefaultValue struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Default defaults.Float64
}
@@ -85,3 +86,8 @@ func (a AttributeWithFloat64DefaultValue) IsRequired() bool {
func (a AttributeWithFloat64DefaultValue) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithFloat64DefaultValue) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithfloat64planmodifiers.go b/internal/testing/testschema/attributewithfloat64planmodifiers.go
index 6273cbd5d..ba04291da 100644
--- a/internal/testing/testschema/attributewithfloat64planmodifiers.go
+++ b/internal/testing/testschema/attributewithfloat64planmodifiers.go
@@ -22,6 +22,7 @@ type AttributeWithFloat64PlanModifiers struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
PlanModifiers []planmodifier.Float64
}
@@ -85,3 +86,8 @@ func (a AttributeWithFloat64PlanModifiers) IsRequired() bool {
func (a AttributeWithFloat64PlanModifiers) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithFloat64PlanModifiers) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithfloat64validators.go b/internal/testing/testschema/attributewithfloat64validators.go
index b2b2b5f70..02ef17704 100644
--- a/internal/testing/testschema/attributewithfloat64validators.go
+++ b/internal/testing/testschema/attributewithfloat64validators.go
@@ -22,6 +22,7 @@ type AttributeWithFloat64Validators struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Validators []validator.Float64
}
@@ -85,3 +86,8 @@ func (a AttributeWithFloat64Validators) IsRequired() bool {
func (a AttributeWithFloat64Validators) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithFloat64Validators) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithint32default.go b/internal/testing/testschema/attributewithint32default.go
index bbfe22af8..f332bae41 100644
--- a/internal/testing/testschema/attributewithint32default.go
+++ b/internal/testing/testschema/attributewithint32default.go
@@ -22,6 +22,7 @@ type AttributeWithInt32DefaultValue struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Default defaults.Int32
}
@@ -85,3 +86,8 @@ func (a AttributeWithInt32DefaultValue) IsRequired() bool {
func (a AttributeWithInt32DefaultValue) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithInt32DefaultValue) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithint32planmodifiers.go b/internal/testing/testschema/attributewithint32planmodifiers.go
index aff453d9c..7f131df58 100644
--- a/internal/testing/testschema/attributewithint32planmodifiers.go
+++ b/internal/testing/testschema/attributewithint32planmodifiers.go
@@ -23,6 +23,7 @@ type AttributeWithInt32PlanModifiers struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
PlanModifiers []planmodifier.Int32
}
@@ -86,3 +87,8 @@ func (a AttributeWithInt32PlanModifiers) IsRequired() bool {
func (a AttributeWithInt32PlanModifiers) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithInt32PlanModifiers) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithint32validators.go b/internal/testing/testschema/attributewithint32validators.go
index 7c6913bcc..8a4546e9e 100644
--- a/internal/testing/testschema/attributewithint32validators.go
+++ b/internal/testing/testschema/attributewithint32validators.go
@@ -23,6 +23,7 @@ type AttributeWithInt32Validators struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Validators []validator.Int32
}
@@ -86,3 +87,8 @@ func (a AttributeWithInt32Validators) IsRequired() bool {
func (a AttributeWithInt32Validators) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithInt32Validators) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithint64default.go b/internal/testing/testschema/attributewithint64default.go
index ca9e12b96..574a88a58 100644
--- a/internal/testing/testschema/attributewithint64default.go
+++ b/internal/testing/testschema/attributewithint64default.go
@@ -22,6 +22,7 @@ type AttributeWithInt64DefaultValue struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Default defaults.Int64
}
@@ -85,3 +86,8 @@ func (a AttributeWithInt64DefaultValue) IsRequired() bool {
func (a AttributeWithInt64DefaultValue) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithInt64DefaultValue) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithint64planmodifiers.go b/internal/testing/testschema/attributewithint64planmodifiers.go
index 368a865c0..e21a43251 100644
--- a/internal/testing/testschema/attributewithint64planmodifiers.go
+++ b/internal/testing/testschema/attributewithint64planmodifiers.go
@@ -22,6 +22,7 @@ type AttributeWithInt64PlanModifiers struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
PlanModifiers []planmodifier.Int64
}
@@ -85,3 +86,8 @@ func (a AttributeWithInt64PlanModifiers) IsRequired() bool {
func (a AttributeWithInt64PlanModifiers) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithInt64PlanModifiers) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithint64validators.go b/internal/testing/testschema/attributewithint64validators.go
index 07cf28bd7..c4e23e166 100644
--- a/internal/testing/testschema/attributewithint64validators.go
+++ b/internal/testing/testschema/attributewithint64validators.go
@@ -22,6 +22,7 @@ type AttributeWithInt64Validators struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Validators []validator.Int64
}
@@ -85,3 +86,8 @@ func (a AttributeWithInt64Validators) IsRequired() bool {
func (a AttributeWithInt64Validators) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithInt64Validators) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithlistdefault.go b/internal/testing/testschema/attributewithlistdefault.go
index 1f6c65d5a..ff23c5215 100644
--- a/internal/testing/testschema/attributewithlistdefault.go
+++ b/internal/testing/testschema/attributewithlistdefault.go
@@ -23,6 +23,7 @@ type AttributeWithListDefaultValue struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Default defaults.List
}
@@ -88,3 +89,8 @@ func (a AttributeWithListDefaultValue) IsRequired() bool {
func (a AttributeWithListDefaultValue) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithListDefaultValue) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithlistplanmodifiers.go b/internal/testing/testschema/attributewithlistplanmodifiers.go
index 08ed953fa..fbb50c332 100644
--- a/internal/testing/testschema/attributewithlistplanmodifiers.go
+++ b/internal/testing/testschema/attributewithlistplanmodifiers.go
@@ -23,6 +23,7 @@ type AttributeWithListPlanModifiers struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
PlanModifiers []planmodifier.List
}
@@ -84,6 +85,11 @@ func (a AttributeWithListPlanModifiers) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithListPlanModifiers) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// ListPlanModifiers satisfies the fwxschema.AttributeWithListPlanModifiers interface.
func (a AttributeWithListPlanModifiers) ListPlanModifiers() []planmodifier.List {
return a.PlanModifiers
diff --git a/internal/testing/testschema/attributewithlistvalidators.go b/internal/testing/testschema/attributewithlistvalidators.go
index bb47ca9d6..fefa2eb02 100644
--- a/internal/testing/testschema/attributewithlistvalidators.go
+++ b/internal/testing/testschema/attributewithlistvalidators.go
@@ -23,6 +23,7 @@ type AttributeWithListValidators struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Validators []validator.List
}
@@ -84,6 +85,11 @@ func (a AttributeWithListValidators) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithListValidators) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// ListValidators satisfies the fwxschema.AttributeWithListValidators interface.
func (a AttributeWithListValidators) ListValidators() []validator.List {
return a.Validators
diff --git a/internal/testing/testschema/attributewithmapdefault.go b/internal/testing/testschema/attributewithmapdefault.go
index a8bf1910d..2b223cd1d 100644
--- a/internal/testing/testschema/attributewithmapdefault.go
+++ b/internal/testing/testschema/attributewithmapdefault.go
@@ -23,6 +23,7 @@ type AttributeWithMapDefaultValue struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Default defaults.Map
}
@@ -88,3 +89,8 @@ func (a AttributeWithMapDefaultValue) IsRequired() bool {
func (a AttributeWithMapDefaultValue) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithMapDefaultValue) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithmapplanmodifiers.go b/internal/testing/testschema/attributewithmapplanmodifiers.go
index 1d067e1d4..06c7b2fe4 100644
--- a/internal/testing/testschema/attributewithmapplanmodifiers.go
+++ b/internal/testing/testschema/attributewithmapplanmodifiers.go
@@ -23,6 +23,7 @@ type AttributeWithMapPlanModifiers struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
PlanModifiers []planmodifier.Map
}
@@ -84,6 +85,11 @@ func (a AttributeWithMapPlanModifiers) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithMapPlanModifiers) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// MapPlanModifiers satisfies the fwxschema.AttributeWithMapPlanModifiers interface.
func (a AttributeWithMapPlanModifiers) MapPlanModifiers() []planmodifier.Map {
return a.PlanModifiers
diff --git a/internal/testing/testschema/attributewithmapvalidators.go b/internal/testing/testschema/attributewithmapvalidators.go
index 1469ceee2..5e3a9dac8 100644
--- a/internal/testing/testschema/attributewithmapvalidators.go
+++ b/internal/testing/testschema/attributewithmapvalidators.go
@@ -23,6 +23,7 @@ type AttributeWithMapValidators struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Validators []validator.Map
}
@@ -84,6 +85,11 @@ func (a AttributeWithMapValidators) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithMapValidators) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// MapValidators satisfies the fwxschema.AttributeWithMapValidators interface.
func (a AttributeWithMapValidators) MapValidators() []validator.Map {
return a.Validators
diff --git a/internal/testing/testschema/attributewithnumberdefault.go b/internal/testing/testschema/attributewithnumberdefault.go
index 7bca51169..effa79507 100644
--- a/internal/testing/testschema/attributewithnumberdefault.go
+++ b/internal/testing/testschema/attributewithnumberdefault.go
@@ -22,6 +22,7 @@ type AttributeWithNumberDefaultValue struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Default defaults.Number
}
@@ -85,3 +86,8 @@ func (a AttributeWithNumberDefaultValue) IsRequired() bool {
func (a AttributeWithNumberDefaultValue) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithNumberDefaultValue) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithnumberplanmodifiers.go b/internal/testing/testschema/attributewithnumberplanmodifiers.go
index 7073a1e72..cf9299778 100644
--- a/internal/testing/testschema/attributewithnumberplanmodifiers.go
+++ b/internal/testing/testschema/attributewithnumberplanmodifiers.go
@@ -22,6 +22,7 @@ type AttributeWithNumberPlanModifiers struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
PlanModifiers []planmodifier.Number
}
@@ -81,6 +82,11 @@ func (a AttributeWithNumberPlanModifiers) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithNumberPlanModifiers) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// NumberPlanModifiers satisfies the fwxschema.AttributeWithNumberPlanModifiers interface.
func (a AttributeWithNumberPlanModifiers) NumberPlanModifiers() []planmodifier.Number {
return a.PlanModifiers
diff --git a/internal/testing/testschema/attributewithnumbervalidators.go b/internal/testing/testschema/attributewithnumbervalidators.go
index 0e63b7db3..af1869d38 100644
--- a/internal/testing/testschema/attributewithnumbervalidators.go
+++ b/internal/testing/testschema/attributewithnumbervalidators.go
@@ -22,6 +22,7 @@ type AttributeWithNumberValidators struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Validators []validator.Number
}
@@ -81,6 +82,11 @@ func (a AttributeWithNumberValidators) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithNumberValidators) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// NumberValidators satisfies the fwxschema.AttributeWithNumberValidators interface.
func (a AttributeWithNumberValidators) NumberValidators() []validator.Number {
return a.Validators
diff --git a/internal/testing/testschema/attributewithobjectdefault.go b/internal/testing/testschema/attributewithobjectdefault.go
index 54e0e594e..ed25e0a90 100644
--- a/internal/testing/testschema/attributewithobjectdefault.go
+++ b/internal/testing/testschema/attributewithobjectdefault.go
@@ -23,6 +23,7 @@ type AttributeWithObjectDefaultValue struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Default defaults.Object
}
@@ -88,3 +89,8 @@ func (a AttributeWithObjectDefaultValue) IsRequired() bool {
func (a AttributeWithObjectDefaultValue) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithObjectDefaultValue) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithobjectplanmodifiers.go b/internal/testing/testschema/attributewithobjectplanmodifiers.go
index 87f5933e2..e4ec023bd 100644
--- a/internal/testing/testschema/attributewithobjectplanmodifiers.go
+++ b/internal/testing/testschema/attributewithobjectplanmodifiers.go
@@ -23,6 +23,7 @@ type AttributeWithObjectPlanModifiers struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
PlanModifiers []planmodifier.Object
}
@@ -84,6 +85,11 @@ func (a AttributeWithObjectPlanModifiers) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithObjectPlanModifiers) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// ObjectPlanModifiers satisfies the fwxschema.AttributeWithObjectPlanModifiers interface.
func (a AttributeWithObjectPlanModifiers) ObjectPlanModifiers() []planmodifier.Object {
return a.PlanModifiers
diff --git a/internal/testing/testschema/attributewithobjectvalidators.go b/internal/testing/testschema/attributewithobjectvalidators.go
index 854be0dbd..534c47cf6 100644
--- a/internal/testing/testschema/attributewithobjectvalidators.go
+++ b/internal/testing/testschema/attributewithobjectvalidators.go
@@ -23,6 +23,7 @@ type AttributeWithObjectValidators struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Validators []validator.Object
}
@@ -84,6 +85,11 @@ func (a AttributeWithObjectValidators) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithObjectValidators) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// ObjectValidators satisfies the fwxschema.AttributeWithObjectValidators interface.
func (a AttributeWithObjectValidators) ObjectValidators() []validator.Object {
return a.Validators
diff --git a/internal/testing/testschema/attributewithsetdefault.go b/internal/testing/testschema/attributewithsetdefault.go
index 351ce17b8..f770f956c 100644
--- a/internal/testing/testschema/attributewithsetdefault.go
+++ b/internal/testing/testschema/attributewithsetdefault.go
@@ -23,6 +23,7 @@ type AttributeWithSetDefaultValue struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Default defaults.Set
}
@@ -88,3 +89,8 @@ func (a AttributeWithSetDefaultValue) IsRequired() bool {
func (a AttributeWithSetDefaultValue) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithSetDefaultValue) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithsetplanmodifiers.go b/internal/testing/testschema/attributewithsetplanmodifiers.go
index 4efb38ac4..a36f5adc4 100644
--- a/internal/testing/testschema/attributewithsetplanmodifiers.go
+++ b/internal/testing/testschema/attributewithsetplanmodifiers.go
@@ -4,12 +4,13 @@
package testschema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
var _ fwxschema.AttributeWithSetPlanModifiers = AttributeWithSetPlanModifiers{}
@@ -23,6 +24,7 @@ type AttributeWithSetPlanModifiers struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
PlanModifiers []planmodifier.Set
}
@@ -84,6 +86,11 @@ func (a AttributeWithSetPlanModifiers) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithSetPlanModifiers) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// SetPlanModifiers satisfies the fwxschema.AttributeWithSetPlanModifiers interface.
func (a AttributeWithSetPlanModifiers) SetPlanModifiers() []planmodifier.Set {
return a.PlanModifiers
diff --git a/internal/testing/testschema/attributewithsetvalidators.go b/internal/testing/testschema/attributewithsetvalidators.go
index 217b11b21..32bc5256f 100644
--- a/internal/testing/testschema/attributewithsetvalidators.go
+++ b/internal/testing/testschema/attributewithsetvalidators.go
@@ -23,6 +23,7 @@ type AttributeWithSetValidators struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Validators []validator.Set
}
@@ -84,6 +85,11 @@ func (a AttributeWithSetValidators) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithSetValidators) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// SetValidators satisfies the fwxschema.AttributeWithSetValidators interface.
func (a AttributeWithSetValidators) SetValidators() []validator.Set {
return a.Validators
diff --git a/internal/testing/testschema/attributewithstringdefault.go b/internal/testing/testschema/attributewithstringdefault.go
index 88e93f004..01e1e6f4e 100644
--- a/internal/testing/testschema/attributewithstringdefault.go
+++ b/internal/testing/testschema/attributewithstringdefault.go
@@ -22,6 +22,7 @@ type AttributeWithStringDefaultValue struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Default defaults.String
}
@@ -85,3 +86,8 @@ func (a AttributeWithStringDefaultValue) IsRequired() bool {
func (a AttributeWithStringDefaultValue) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithStringDefaultValue) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/attributewithstringplanmodifiers.go b/internal/testing/testschema/attributewithstringplanmodifiers.go
index cbf324d66..d1b5af8d5 100644
--- a/internal/testing/testschema/attributewithstringplanmodifiers.go
+++ b/internal/testing/testschema/attributewithstringplanmodifiers.go
@@ -22,6 +22,7 @@ type AttributeWithStringPlanModifiers struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
PlanModifiers []planmodifier.String
}
@@ -81,6 +82,11 @@ func (a AttributeWithStringPlanModifiers) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithStringPlanModifiers) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// StringPlanModifiers satisfies the fwxschema.AttributeWithStringPlanModifiers interface.
func (a AttributeWithStringPlanModifiers) StringPlanModifiers() []planmodifier.String {
return a.PlanModifiers
diff --git a/internal/testing/testschema/attributewithstringvalidators.go b/internal/testing/testschema/attributewithstringvalidators.go
index a864dd314..e1ddf7e59 100644
--- a/internal/testing/testschema/attributewithstringvalidators.go
+++ b/internal/testing/testschema/attributewithstringvalidators.go
@@ -22,6 +22,7 @@ type AttributeWithStringValidators struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Validators []validator.String
}
@@ -81,6 +82,11 @@ func (a AttributeWithStringValidators) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a AttributeWithStringValidators) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// StringValidators satisfies the fwxschema.AttributeWithStringValidators interface.
func (a AttributeWithStringValidators) StringValidators() []validator.String {
return a.Validators
diff --git a/internal/testing/testschema/nested_attribute.go b/internal/testing/testschema/nested_attribute.go
index f078f7cb9..f37301359 100644
--- a/internal/testing/testschema/nested_attribute.go
+++ b/internal/testing/testschema/nested_attribute.go
@@ -27,6 +27,7 @@ type NestedAttribute struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Type attr.Type
}
@@ -157,3 +158,8 @@ func (a NestedAttribute) IsRequired() bool {
func (a NestedAttribute) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a NestedAttribute) IsWriteOnly() bool {
+ return a.WriteOnly
+}
diff --git a/internal/testing/testschema/nested_attribute_with_list_default.go b/internal/testing/testschema/nested_attribute_with_list_default.go
index 0980629cd..a1b70cd87 100644
--- a/internal/testing/testschema/nested_attribute_with_list_default.go
+++ b/internal/testing/testschema/nested_attribute_with_list_default.go
@@ -27,6 +27,7 @@ type NestedAttributeWithListDefaultValue struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Type attr.Type
}
@@ -102,6 +103,11 @@ func (a NestedAttributeWithListDefaultValue) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a NestedAttributeWithListDefaultValue) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// ListDefaultValue satisfies the fwschema.AttributeWithListDefaultValue interface.
func (a NestedAttributeWithListDefaultValue) ListDefaultValue() defaults.List {
return a.Default
diff --git a/internal/testing/testschema/nested_attribute_with_list_plan_modifiers.go b/internal/testing/testschema/nested_attribute_with_list_plan_modifiers.go
index e20d89df8..2904c21ee 100644
--- a/internal/testing/testschema/nested_attribute_with_list_plan_modifiers.go
+++ b/internal/testing/testschema/nested_attribute_with_list_plan_modifiers.go
@@ -27,6 +27,7 @@ type NestedAttributeWithListPlanModifiers struct {
PlanModifiers []planmodifier.List
Required bool
Sensitive bool
+ WriteOnly bool
Type attr.Type
}
@@ -102,6 +103,11 @@ func (a NestedAttributeWithListPlanModifiers) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a NestedAttributeWithListPlanModifiers) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// ListPlanModifiers satisfies the fwxschema.AttributeWithListPlanModifiers interface.
func (a NestedAttributeWithListPlanModifiers) ListPlanModifiers() []planmodifier.List {
return a.PlanModifiers
diff --git a/internal/testing/testschema/nested_attribute_with_map_default.go b/internal/testing/testschema/nested_attribute_with_map_default.go
index 128ae137c..6eac9d3ff 100644
--- a/internal/testing/testschema/nested_attribute_with_map_default.go
+++ b/internal/testing/testschema/nested_attribute_with_map_default.go
@@ -27,6 +27,7 @@ type NestedAttributeWithMapDefaultValue struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Type attr.Type
}
@@ -102,6 +103,11 @@ func (a NestedAttributeWithMapDefaultValue) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a NestedAttributeWithMapDefaultValue) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// MapDefaultValue satisfies the fwschema.AttributeWithMapDefaultValue interface.
func (a NestedAttributeWithMapDefaultValue) MapDefaultValue() defaults.Map {
return a.Default
diff --git a/internal/testing/testschema/nested_attribute_with_map_plan_modifiers.go b/internal/testing/testschema/nested_attribute_with_map_plan_modifiers.go
index 27ba35b45..35f2a0732 100644
--- a/internal/testing/testschema/nested_attribute_with_map_plan_modifiers.go
+++ b/internal/testing/testschema/nested_attribute_with_map_plan_modifiers.go
@@ -27,6 +27,7 @@ type NestedAttributeWithMapPlanModifiers struct {
PlanModifiers []planmodifier.Map
Required bool
Sensitive bool
+ WriteOnly bool
Type attr.Type
}
@@ -102,6 +103,11 @@ func (a NestedAttributeWithMapPlanModifiers) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a NestedAttributeWithMapPlanModifiers) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// MapPlanModifiers satisfies the fwxschema.AttributeWithMapPlanModifiers interface.
func (a NestedAttributeWithMapPlanModifiers) MapPlanModifiers() []planmodifier.Map {
return a.PlanModifiers
diff --git a/internal/testing/testschema/nested_attribute_with_object_default.go b/internal/testing/testschema/nested_attribute_with_object_default.go
index 0f6a96489..e8579c2ac 100644
--- a/internal/testing/testschema/nested_attribute_with_object_default.go
+++ b/internal/testing/testschema/nested_attribute_with_object_default.go
@@ -28,6 +28,7 @@ type NestedAttributeWithObjectDefaultValue struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Type attr.Type
}
@@ -105,6 +106,11 @@ func (a NestedAttributeWithObjectDefaultValue) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a NestedAttributeWithObjectDefaultValue) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// ObjectDefaultValue satisfies the fwschema.AttributeWithListDefaultValue interface.
func (a NestedAttributeWithObjectDefaultValue) ObjectDefaultValue() defaults.Object {
return a.Default
diff --git a/internal/testing/testschema/nested_attribute_with_object_plan_modifiers.go b/internal/testing/testschema/nested_attribute_with_object_plan_modifiers.go
index 00f303165..2d86af989 100644
--- a/internal/testing/testschema/nested_attribute_with_object_plan_modifiers.go
+++ b/internal/testing/testschema/nested_attribute_with_object_plan_modifiers.go
@@ -26,6 +26,7 @@ type NestedAttributeWithObjectPlanModifiers struct {
PlanModifiers []planmodifier.Object
Required bool
Sensitive bool
+ WriteOnly bool
Type attr.Type
}
@@ -99,6 +100,11 @@ func (a NestedAttributeWithObjectPlanModifiers) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a NestedAttributeWithObjectPlanModifiers) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// ObjectPlanModifiers satisfies the fwxschema.AttributeWithObjectPlanModifiers interface.
func (a NestedAttributeWithObjectPlanModifiers) ObjectPlanModifiers() []planmodifier.Object {
return a.PlanModifiers
diff --git a/internal/testing/testschema/nested_attribute_with_set_default.go b/internal/testing/testschema/nested_attribute_with_set_default.go
index a8f39e9a6..3a80c28a7 100644
--- a/internal/testing/testschema/nested_attribute_with_set_default.go
+++ b/internal/testing/testschema/nested_attribute_with_set_default.go
@@ -27,6 +27,7 @@ type NestedAttributeWithSetDefaultValue struct {
Optional bool
Required bool
Sensitive bool
+ WriteOnly bool
Type attr.Type
}
@@ -102,6 +103,11 @@ func (a NestedAttributeWithSetDefaultValue) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a NestedAttributeWithSetDefaultValue) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// MapDefaultValue satisfies the fwschema.AttributeWithMapDefaultValue interface.
func (a NestedAttributeWithSetDefaultValue) SetDefaultValue() defaults.Set {
return a.Default
diff --git a/internal/testing/testschema/nested_attribute_with_set_plan_modifiers.go b/internal/testing/testschema/nested_attribute_with_set_plan_modifiers.go
index 6c988d86a..1d886e70e 100644
--- a/internal/testing/testschema/nested_attribute_with_set_plan_modifiers.go
+++ b/internal/testing/testschema/nested_attribute_with_set_plan_modifiers.go
@@ -27,6 +27,7 @@ type NestedAttributeWithSetPlanModifiers struct {
PlanModifiers []planmodifier.Set
Required bool
Sensitive bool
+ WriteOnly bool
Type attr.Type
}
@@ -102,6 +103,11 @@ func (a NestedAttributeWithSetPlanModifiers) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly satisfies the fwschema.Attribute interface.
+func (a NestedAttributeWithSetPlanModifiers) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// SetPlanModifiers satisfies the fwxschema.AttributeWithSetPlanModifiers interface.
func (a NestedAttributeWithSetPlanModifiers) SetPlanModifiers() []planmodifier.Set {
return a.PlanModifiers
diff --git a/internal/toproto5/getproviderschema_test.go b/internal/toproto5/getproviderschema_test.go
index 7a6cd4761..104814f5d 100644
--- a/internal/toproto5/getproviderschema_test.go
+++ b/internal/toproto5/getproviderschema_test.go
@@ -278,6 +278,38 @@ func TestGetProviderSchemaResponse(t *testing.T) {
ResourceSchemas: map[string]*tfprotov5.Schema{},
},
},
+ "data-source-attribute-write-only": {
+ input: &fwserver.GetProviderSchemaResponse{
+ DataSourceSchemas: map[string]fwschema.Schema{
+ "test_data_source": datasourceschema.Schema{
+ Attributes: map[string]datasourceschema.Attribute{
+ "test_attribute": datasourceschema.BoolAttribute{
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ expected: &tfprotov5.GetProviderSchemaResponse{
+ DataSourceSchemas: map[string]*tfprotov5.Schema{
+ "test_data_source": {
+ Block: &tfprotov5.SchemaBlock{
+ Attributes: []*tfprotov5.SchemaAttribute{
+ {
+ Computed: true,
+ Name: "test_attribute",
+ WriteOnly: false,
+ Type: tftypes.Bool,
+ },
+ },
+ },
+ },
+ },
+ EphemeralResourceSchemas: map[string]*tfprotov5.Schema{},
+ Functions: map[string]*tfprotov5.Function{},
+ ResourceSchemas: map[string]*tfprotov5.Schema{},
+ },
+ },
"data-source-attribute-type-bool": {
input: &fwserver.GetProviderSchemaResponse{
DataSourceSchemas: map[string]fwschema.Schema{
@@ -1338,6 +1370,38 @@ func TestGetProviderSchemaResponse(t *testing.T) {
ResourceSchemas: map[string]*tfprotov5.Schema{},
},
},
+ "ephemeral-resource-attribute-write-only": {
+ input: &fwserver.GetProviderSchemaResponse{
+ EphemeralResourceSchemas: map[string]fwschema.Schema{
+ "test_ephemeral_resource": ephemeralschema.Schema{
+ Attributes: map[string]ephemeralschema.Attribute{
+ "test_attribute": ephemeralschema.BoolAttribute{
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ expected: &tfprotov5.GetProviderSchemaResponse{
+ DataSourceSchemas: map[string]*tfprotov5.Schema{},
+ EphemeralResourceSchemas: map[string]*tfprotov5.Schema{
+ "test_ephemeral_resource": {
+ Block: &tfprotov5.SchemaBlock{
+ Attributes: []*tfprotov5.SchemaAttribute{
+ {
+ Computed: true,
+ Name: "test_attribute",
+ WriteOnly: false,
+ Type: tftypes.Bool,
+ },
+ },
+ },
+ },
+ },
+ Functions: map[string]*tfprotov5.Function{},
+ ResourceSchemas: map[string]*tfprotov5.Schema{},
+ },
+ },
"ephemeral-resource-attribute-type-bool": {
input: &fwserver.GetProviderSchemaResponse{
EphemeralResourceSchemas: map[string]fwschema.Schema{
@@ -2461,6 +2525,35 @@ func TestGetProviderSchemaResponse(t *testing.T) {
ResourceSchemas: map[string]*tfprotov5.Schema{},
},
},
+ "provider-attribute-write-only": {
+ input: &fwserver.GetProviderSchemaResponse{
+ Provider: providerschema.Schema{
+ Attributes: map[string]providerschema.Attribute{
+ "test_attribute": providerschema.BoolAttribute{
+ Optional: true,
+ },
+ },
+ },
+ },
+ expected: &tfprotov5.GetProviderSchemaResponse{
+ DataSourceSchemas: map[string]*tfprotov5.Schema{},
+ EphemeralResourceSchemas: map[string]*tfprotov5.Schema{},
+ Functions: map[string]*tfprotov5.Function{},
+ Provider: &tfprotov5.Schema{
+ Block: &tfprotov5.SchemaBlock{
+ Attributes: []*tfprotov5.SchemaAttribute{
+ {
+ Name: "test_attribute",
+ Optional: true,
+ WriteOnly: false,
+ Type: tftypes.Bool,
+ },
+ },
+ },
+ },
+ ResourceSchemas: map[string]*tfprotov5.Schema{},
+ },
+ },
"provider-attribute-type-bool": {
input: &fwserver.GetProviderSchemaResponse{
Provider: providerschema.Schema{
@@ -4042,6 +4135,39 @@ func TestGetProviderSchemaResponse(t *testing.T) {
},
},
},
+ "resource-attribute-write-only": {
+ input: &fwserver.GetProviderSchemaResponse{
+ ResourceSchemas: map[string]fwschema.Schema{
+ "test_resource": resourceschema.Schema{
+ Attributes: map[string]resourceschema.Attribute{
+ "test_attribute": resourceschema.BoolAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ expected: &tfprotov5.GetProviderSchemaResponse{
+ DataSourceSchemas: map[string]*tfprotov5.Schema{},
+ EphemeralResourceSchemas: map[string]*tfprotov5.Schema{},
+ Functions: map[string]*tfprotov5.Function{},
+ ResourceSchemas: map[string]*tfprotov5.Schema{
+ "test_resource": {
+ Block: &tfprotov5.SchemaBlock{
+ Attributes: []*tfprotov5.SchemaAttribute{
+ {
+ Optional: true,
+ Name: "test_attribute",
+ WriteOnly: true,
+ Type: tftypes.Bool,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
"resource-attribute-type-bool": {
input: &fwserver.GetProviderSchemaResponse{
ResourceSchemas: map[string]fwschema.Schema{
diff --git a/internal/toproto5/schema_attribute.go b/internal/toproto5/schema_attribute.go
index 74d8fa551..c9bc37e3a 100644
--- a/internal/toproto5/schema_attribute.go
+++ b/internal/toproto5/schema_attribute.go
@@ -6,9 +6,10 @@ package toproto5
import (
"context"
- "github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tftypes"
+
+ "github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
)
// SchemaAttribute returns the *tfprotov5.SchemaAttribute equivalent of an
@@ -34,6 +35,7 @@ func SchemaAttribute(ctx context.Context, name string, path *tftypes.AttributePa
Computed: a.IsComputed(),
Sensitive: a.IsSensitive(),
Type: a.GetType().TerraformType(ctx),
+ WriteOnly: a.IsWriteOnly(),
}
if a.GetDeprecationMessage() != "" {
diff --git a/internal/toproto6/getproviderschema_test.go b/internal/toproto6/getproviderschema_test.go
index 2df173187..161ae67a3 100644
--- a/internal/toproto6/getproviderschema_test.go
+++ b/internal/toproto6/getproviderschema_test.go
@@ -278,6 +278,38 @@ func TestGetProviderSchemaResponse(t *testing.T) {
ResourceSchemas: map[string]*tfprotov6.Schema{},
},
},
+ "data-source-attribute-write-only": {
+ input: &fwserver.GetProviderSchemaResponse{
+ DataSourceSchemas: map[string]fwschema.Schema{
+ "test_data_source": datasourceschema.Schema{
+ Attributes: map[string]datasourceschema.Attribute{
+ "test_attribute": datasourceschema.BoolAttribute{
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ expected: &tfprotov6.GetProviderSchemaResponse{
+ DataSourceSchemas: map[string]*tfprotov6.Schema{
+ "test_data_source": {
+ Block: &tfprotov6.SchemaBlock{
+ Attributes: []*tfprotov6.SchemaAttribute{
+ {
+ Computed: true,
+ Name: "test_attribute",
+ WriteOnly: false,
+ Type: tftypes.Bool,
+ },
+ },
+ },
+ },
+ },
+ Functions: map[string]*tfprotov6.Function{},
+ EphemeralResourceSchemas: map[string]*tfprotov6.Schema{},
+ ResourceSchemas: map[string]*tfprotov6.Schema{},
+ },
+ },
"data-source-attribute-type-bool": {
input: &fwserver.GetProviderSchemaResponse{
DataSourceSchemas: map[string]fwschema.Schema{
@@ -2526,6 +2558,35 @@ func TestGetProviderSchemaResponse(t *testing.T) {
ResourceSchemas: map[string]*tfprotov6.Schema{},
},
},
+ "provider-attribute-write-only": {
+ input: &fwserver.GetProviderSchemaResponse{
+ Provider: providerschema.Schema{
+ Attributes: map[string]providerschema.Attribute{
+ "test_attribute": providerschema.BoolAttribute{
+ Optional: true,
+ },
+ },
+ },
+ },
+ expected: &tfprotov6.GetProviderSchemaResponse{
+ DataSourceSchemas: map[string]*tfprotov6.Schema{},
+ EphemeralResourceSchemas: map[string]*tfprotov6.Schema{},
+ Functions: map[string]*tfprotov6.Function{},
+ Provider: &tfprotov6.Schema{
+ Block: &tfprotov6.SchemaBlock{
+ Attributes: []*tfprotov6.SchemaAttribute{
+ {
+ Name: "test_attribute",
+ Optional: true,
+ WriteOnly: false,
+ Type: tftypes.Bool,
+ },
+ },
+ },
+ },
+ ResourceSchemas: map[string]*tfprotov6.Schema{},
+ },
+ },
"provider-attribute-type-bool": {
input: &fwserver.GetProviderSchemaResponse{
Provider: providerschema.Schema{
@@ -4183,6 +4244,39 @@ func TestGetProviderSchemaResponse(t *testing.T) {
},
},
},
+ "resource-attribute-write-only": {
+ input: &fwserver.GetProviderSchemaResponse{
+ ResourceSchemas: map[string]fwschema.Schema{
+ "test_resource": resourceschema.Schema{
+ Attributes: map[string]resourceschema.Attribute{
+ "test_attribute": resourceschema.BoolAttribute{
+ Optional: true,
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ expected: &tfprotov6.GetProviderSchemaResponse{
+ DataSourceSchemas: map[string]*tfprotov6.Schema{},
+ EphemeralResourceSchemas: map[string]*tfprotov6.Schema{},
+ Functions: map[string]*tfprotov6.Function{},
+ ResourceSchemas: map[string]*tfprotov6.Schema{
+ "test_resource": {
+ Block: &tfprotov6.SchemaBlock{
+ Attributes: []*tfprotov6.SchemaAttribute{
+ {
+ Optional: true,
+ Name: "test_attribute",
+ WriteOnly: true,
+ Type: tftypes.Bool,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
"resource-attribute-type-bool": {
input: &fwserver.GetProviderSchemaResponse{
ResourceSchemas: map[string]fwschema.Schema{
diff --git a/internal/toproto6/schema_attribute.go b/internal/toproto6/schema_attribute.go
index 492a5a2ab..d020a95d2 100644
--- a/internal/toproto6/schema_attribute.go
+++ b/internal/toproto6/schema_attribute.go
@@ -7,9 +7,10 @@ import (
"context"
"sort"
- "github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-go/tftypes"
+
+ "github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
)
// SchemaAttribute returns the *tfprotov6.SchemaAttribute equivalent of an
@@ -27,6 +28,7 @@ func SchemaAttribute(ctx context.Context, name string, path *tftypes.AttributePa
Computed: a.IsComputed(),
Sensitive: a.IsSensitive(),
Type: a.GetType().TerraformType(ctx),
+ WriteOnly: a.IsWriteOnly(),
}
if a.GetDeprecationMessage() != "" {
diff --git a/provider/metaschema/bool_attribute.go b/provider/metaschema/bool_attribute.go
index 6d96a516a..374296341 100644
--- a/provider/metaschema/bool_attribute.go
+++ b/provider/metaschema/bool_attribute.go
@@ -4,11 +4,12 @@
package metaschema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -117,3 +118,9 @@ func (a BoolAttribute) IsRequired() bool {
func (a BoolAttribute) IsSensitive() bool {
return false
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to provider meta schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a BoolAttribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/provider/metaschema/bool_attribute_test.go b/provider/metaschema/bool_attribute_test.go
index 617225b54..50b3e06bb 100644
--- a/provider/metaschema/bool_attribute_test.go
+++ b/provider/metaschema/bool_attribute_test.go
@@ -9,13 +9,14 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestBoolAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -370,3 +371,31 @@ func TestBoolAttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestBoolAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute metaschema.BoolAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: metaschema.BoolAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/provider/metaschema/float64_attribute.go b/provider/metaschema/float64_attribute.go
index 8a4478655..ac2b79b0a 100644
--- a/provider/metaschema/float64_attribute.go
+++ b/provider/metaschema/float64_attribute.go
@@ -4,11 +4,12 @@
package metaschema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -120,3 +121,9 @@ func (a Float64Attribute) IsRequired() bool {
func (a Float64Attribute) IsSensitive() bool {
return false
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to provider meta schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a Float64Attribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/provider/metaschema/float64_attribute_test.go b/provider/metaschema/float64_attribute_test.go
index 71cb2f688..f6f5ebd7f 100644
--- a/provider/metaschema/float64_attribute_test.go
+++ b/provider/metaschema/float64_attribute_test.go
@@ -9,13 +9,14 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestFloat64AttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -370,3 +371,31 @@ func TestFloat64AttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestFloat64AttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute metaschema.Float64Attribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: metaschema.Float64Attribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/provider/metaschema/int64_attribute.go b/provider/metaschema/int64_attribute.go
index 8751d574e..aeccd7030 100644
--- a/provider/metaschema/int64_attribute.go
+++ b/provider/metaschema/int64_attribute.go
@@ -4,11 +4,12 @@
package metaschema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -120,3 +121,9 @@ func (a Int64Attribute) IsRequired() bool {
func (a Int64Attribute) IsSensitive() bool {
return false
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to provider meta schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a Int64Attribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/provider/metaschema/int64_attribute_test.go b/provider/metaschema/int64_attribute_test.go
index 28efcebd7..2aa4d585e 100644
--- a/provider/metaschema/int64_attribute_test.go
+++ b/provider/metaschema/int64_attribute_test.go
@@ -9,13 +9,14 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestInt64AttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -370,3 +371,31 @@ func TestInt64AttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestInt64AttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute metaschema.Int64Attribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: metaschema.Int64Attribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/provider/metaschema/list_attribute.go b/provider/metaschema/list_attribute.go
index a3ff30e65..187d9c47c 100644
--- a/provider/metaschema/list_attribute.go
+++ b/provider/metaschema/list_attribute.go
@@ -6,11 +6,12 @@ package metaschema
import (
"context"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -134,6 +135,12 @@ func (a ListAttribute) IsSensitive() bool {
return false
}
+// IsWriteOnly returns false as write-only attributes are not relevant to provider meta schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a ListAttribute) IsWriteOnly() bool {
+ return false
+}
+
// ValidateImplementation contains logic for validating the
// provider-defined implementation of the attribute to prevent unexpected
// errors or panics. This logic runs during the GetProviderSchema RPC
diff --git a/provider/metaschema/list_attribute_test.go b/provider/metaschema/list_attribute_test.go
index 6c864e8d6..424c1d602 100644
--- a/provider/metaschema/list_attribute_test.go
+++ b/provider/metaschema/list_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -18,7 +20,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestListAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -379,6 +380,34 @@ func TestListAttributeIsSensitive(t *testing.T) {
}
}
+func TestListAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute metaschema.ListAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: metaschema.ListAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestListAttributeValidateImplementation(t *testing.T) {
t.Parallel()
diff --git a/provider/metaschema/list_nested_attribute.go b/provider/metaschema/list_nested_attribute.go
index a6b4f875a..0fa1b8221 100644
--- a/provider/metaschema/list_nested_attribute.go
+++ b/provider/metaschema/list_nested_attribute.go
@@ -6,11 +6,12 @@ package metaschema
import (
"fmt"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -159,3 +160,9 @@ func (a ListNestedAttribute) IsRequired() bool {
func (a ListNestedAttribute) IsSensitive() bool {
return false
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to provider meta schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a ListNestedAttribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/provider/metaschema/list_nested_attribute_test.go b/provider/metaschema/list_nested_attribute_test.go
index dc6188822..c2aeae811 100644
--- a/provider/metaschema/list_nested_attribute_test.go
+++ b/provider/metaschema/list_nested_attribute_test.go
@@ -9,13 +9,14 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestListNestedAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -546,3 +547,31 @@ func TestListNestedAttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestListNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute metaschema.ListNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: metaschema.ListNestedAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/provider/metaschema/map_attribute.go b/provider/metaschema/map_attribute.go
index 51ee02edb..9103231ff 100644
--- a/provider/metaschema/map_attribute.go
+++ b/provider/metaschema/map_attribute.go
@@ -6,11 +6,12 @@ package metaschema
import (
"context"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -137,6 +138,12 @@ func (a MapAttribute) IsSensitive() bool {
return false
}
+// IsWriteOnly returns false as write-only attributes are not relevant to provider meta schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a MapAttribute) IsWriteOnly() bool {
+ return false
+}
+
// ValidateImplementation contains logic for validating the
// provider-defined implementation of the attribute to prevent unexpected
// errors or panics. This logic runs during the GetProviderSchema RPC
diff --git a/provider/metaschema/map_attribute_test.go b/provider/metaschema/map_attribute_test.go
index 0956d8cc4..52ee19318 100644
--- a/provider/metaschema/map_attribute_test.go
+++ b/provider/metaschema/map_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -18,7 +20,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestMapAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -379,6 +380,34 @@ func TestMapAttributeIsSensitive(t *testing.T) {
}
}
+func TestMapAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute metaschema.MapAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: metaschema.MapAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestMapAttributeValidateImplementation(t *testing.T) {
t.Parallel()
diff --git a/provider/metaschema/map_nested_attribute.go b/provider/metaschema/map_nested_attribute.go
index 47a3b4348..587c56c0a 100644
--- a/provider/metaschema/map_nested_attribute.go
+++ b/provider/metaschema/map_nested_attribute.go
@@ -6,11 +6,12 @@ package metaschema
import (
"fmt"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -159,3 +160,9 @@ func (a MapNestedAttribute) IsRequired() bool {
func (a MapNestedAttribute) IsSensitive() bool {
return false
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to provider meta schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a MapNestedAttribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/provider/metaschema/map_nested_attribute_test.go b/provider/metaschema/map_nested_attribute_test.go
index 7c320b1bf..88eb68b26 100644
--- a/provider/metaschema/map_nested_attribute_test.go
+++ b/provider/metaschema/map_nested_attribute_test.go
@@ -9,13 +9,14 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestMapNestedAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -546,3 +547,31 @@ func TestMapNestedAttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestMapNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute metaschema.MapNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: metaschema.MapNestedAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/provider/metaschema/number_attribute.go b/provider/metaschema/number_attribute.go
index 86e45b8ab..511e7000a 100644
--- a/provider/metaschema/number_attribute.go
+++ b/provider/metaschema/number_attribute.go
@@ -4,11 +4,12 @@
package metaschema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -121,3 +122,9 @@ func (a NumberAttribute) IsRequired() bool {
func (a NumberAttribute) IsSensitive() bool {
return false
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to provider meta schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a NumberAttribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/provider/metaschema/number_attribute_test.go b/provider/metaschema/number_attribute_test.go
index 819f2d2a1..588dd4e0b 100644
--- a/provider/metaschema/number_attribute_test.go
+++ b/provider/metaschema/number_attribute_test.go
@@ -9,13 +9,14 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestNumberAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -370,3 +371,31 @@ func TestNumberAttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestNumberAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute metaschema.NumberAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: metaschema.NumberAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/provider/metaschema/object_attribute.go b/provider/metaschema/object_attribute.go
index aa4c67be9..aabe40d4c 100644
--- a/provider/metaschema/object_attribute.go
+++ b/provider/metaschema/object_attribute.go
@@ -6,11 +6,12 @@ package metaschema
import (
"context"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -136,6 +137,12 @@ func (a ObjectAttribute) IsSensitive() bool {
return false
}
+// IsWriteOnly returns false as write-only attributes are not relevant to provider meta schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a ObjectAttribute) IsWriteOnly() bool {
+ return false
+}
+
// ValidateImplementation contains logic for validating the
// provider-defined implementation of the attribute to prevent unexpected
// errors or panics. This logic runs during the GetProviderSchema RPC
diff --git a/provider/metaschema/object_attribute_test.go b/provider/metaschema/object_attribute_test.go
index e24db5dd1..f439faf0a 100644
--- a/provider/metaschema/object_attribute_test.go
+++ b/provider/metaschema/object_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -18,7 +20,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestObjectAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -385,6 +386,34 @@ func TestObjectAttributeIsSensitive(t *testing.T) {
}
}
+func TestObjectAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute metaschema.ObjectAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: metaschema.ObjectAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestObjectAttributeValidateImplementation(t *testing.T) {
t.Parallel()
diff --git a/provider/metaschema/set_attribute.go b/provider/metaschema/set_attribute.go
index 919713075..f7d3e4112 100644
--- a/provider/metaschema/set_attribute.go
+++ b/provider/metaschema/set_attribute.go
@@ -6,11 +6,12 @@ package metaschema
import (
"context"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -132,6 +133,12 @@ func (a SetAttribute) IsSensitive() bool {
return false
}
+// IsWriteOnly returns false as write-only attributes are not relevant to provider meta schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a SetAttribute) IsWriteOnly() bool {
+ return false
+}
+
// ValidateImplementation contains logic for validating the
// provider-defined implementation of the attribute to prevent unexpected
// errors or panics. This logic runs during the GetProviderSchema RPC
diff --git a/provider/metaschema/set_attribute_test.go b/provider/metaschema/set_attribute_test.go
index 8cb5a95d5..620209b6f 100644
--- a/provider/metaschema/set_attribute_test.go
+++ b/provider/metaschema/set_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -18,7 +20,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestSetAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -379,6 +380,34 @@ func TestSetAttributeIsSensitive(t *testing.T) {
}
}
+func TestSetAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute metaschema.SetAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: metaschema.SetAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestSetAttributeValidateImplementation(t *testing.T) {
t.Parallel()
diff --git a/provider/metaschema/set_nested_attribute.go b/provider/metaschema/set_nested_attribute.go
index 233866a00..a3c6fbf9e 100644
--- a/provider/metaschema/set_nested_attribute.go
+++ b/provider/metaschema/set_nested_attribute.go
@@ -6,11 +6,12 @@ package metaschema
import (
"fmt"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -154,3 +155,9 @@ func (a SetNestedAttribute) IsRequired() bool {
func (a SetNestedAttribute) IsSensitive() bool {
return false
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to provider meta schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a SetNestedAttribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/provider/metaschema/set_nested_attribute_test.go b/provider/metaschema/set_nested_attribute_test.go
index 623fa3668..d0d86508c 100644
--- a/provider/metaschema/set_nested_attribute_test.go
+++ b/provider/metaschema/set_nested_attribute_test.go
@@ -9,13 +9,14 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestSetNestedAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -546,3 +547,31 @@ func TestSetNestedAttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestSetNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute metaschema.SetNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: metaschema.SetNestedAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/provider/metaschema/single_nested_attribute.go b/provider/metaschema/single_nested_attribute.go
index 0ed1a22fd..160fb1c80 100644
--- a/provider/metaschema/single_nested_attribute.go
+++ b/provider/metaschema/single_nested_attribute.go
@@ -6,11 +6,12 @@ package metaschema
import (
"fmt"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -174,3 +175,9 @@ func (a SingleNestedAttribute) IsRequired() bool {
func (a SingleNestedAttribute) IsSensitive() bool {
return false
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to provider meta schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a SingleNestedAttribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/provider/metaschema/single_nested_attribute_test.go b/provider/metaschema/single_nested_attribute_test.go
index f7c7cbda5..f04d55634 100644
--- a/provider/metaschema/single_nested_attribute_test.go
+++ b/provider/metaschema/single_nested_attribute_test.go
@@ -9,13 +9,14 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestSingleNestedAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -510,3 +511,31 @@ func TestSingleNestedAttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestSingleNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute metaschema.SingleNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: metaschema.SingleNestedAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/provider/metaschema/string_attribute.go b/provider/metaschema/string_attribute.go
index 3a14d0721..fe25c5014 100644
--- a/provider/metaschema/string_attribute.go
+++ b/provider/metaschema/string_attribute.go
@@ -4,11 +4,12 @@
package metaschema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -117,3 +118,9 @@ func (a StringAttribute) IsRequired() bool {
func (a StringAttribute) IsSensitive() bool {
return false
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to provider meta schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a StringAttribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/provider/metaschema/string_attribute_test.go b/provider/metaschema/string_attribute_test.go
index 544fa268a..00f34d5b2 100644
--- a/provider/metaschema/string_attribute_test.go
+++ b/provider/metaschema/string_attribute_test.go
@@ -9,13 +9,14 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestStringAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -370,3 +371,31 @@ func TestStringAttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestStringAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute metaschema.StringAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: metaschema.StringAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/provider/schema/bool_attribute.go b/provider/schema/bool_attribute.go
index c411062e0..0502821ca 100644
--- a/provider/schema/bool_attribute.go
+++ b/provider/schema/bool_attribute.go
@@ -4,13 +4,14 @@
package schema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -178,3 +179,9 @@ func (a BoolAttribute) IsRequired() bool {
func (a BoolAttribute) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to provider schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a BoolAttribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/provider/schema/bool_attribute_test.go b/provider/schema/bool_attribute_test.go
index 043f2a896..50e4af6ed 100644
--- a/provider/schema/bool_attribute_test.go
+++ b/provider/schema/bool_attribute_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
@@ -16,7 +18,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestBoolAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -417,3 +418,31 @@ func TestBoolAttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestBoolAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.BoolAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.BoolAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/provider/schema/dynamic_attribute.go b/provider/schema/dynamic_attribute.go
index c738d348d..4b31279f8 100644
--- a/provider/schema/dynamic_attribute.go
+++ b/provider/schema/dynamic_attribute.go
@@ -4,13 +4,14 @@
package schema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -175,3 +176,9 @@ func (a DynamicAttribute) IsSensitive() bool {
func (a DynamicAttribute) DynamicValidators() []validator.Dynamic {
return a.Validators
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to provider schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a DynamicAttribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/provider/schema/dynamic_attribute_test.go b/provider/schema/dynamic_attribute_test.go
index c06eaf811..0779fe460 100644
--- a/provider/schema/dynamic_attribute_test.go
+++ b/provider/schema/dynamic_attribute_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
@@ -16,7 +18,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestDynamicAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -384,6 +385,34 @@ func TestDynamicAttributeIsSensitive(t *testing.T) {
}
}
+func TestDynamicAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.DynamicAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.DynamicAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestDynamicAttributeDynamicValidators(t *testing.T) {
t.Parallel()
diff --git a/provider/schema/float32_attribute.go b/provider/schema/float32_attribute.go
index a36c5c435..8e62dc96d 100644
--- a/provider/schema/float32_attribute.go
+++ b/provider/schema/float32_attribute.go
@@ -4,13 +4,14 @@
package schema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -181,3 +182,9 @@ func (a Float32Attribute) IsRequired() bool {
func (a Float32Attribute) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to provider schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a Float32Attribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/provider/schema/float32_attribute_test.go b/provider/schema/float32_attribute_test.go
index e779b4a0a..107216df6 100644
--- a/provider/schema/float32_attribute_test.go
+++ b/provider/schema/float32_attribute_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
@@ -16,7 +18,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestFloat32AttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -417,3 +418,31 @@ func TestFloat32AttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestFloat32AttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.Float32Attribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.Float32Attribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/provider/schema/float64_attribute.go b/provider/schema/float64_attribute.go
index 786965e3a..f0c0cc008 100644
--- a/provider/schema/float64_attribute.go
+++ b/provider/schema/float64_attribute.go
@@ -4,13 +4,14 @@
package schema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -181,3 +182,9 @@ func (a Float64Attribute) IsRequired() bool {
func (a Float64Attribute) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to provider schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a Float64Attribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/provider/schema/float64_attribute_test.go b/provider/schema/float64_attribute_test.go
index 96604d0e7..b08486708 100644
--- a/provider/schema/float64_attribute_test.go
+++ b/provider/schema/float64_attribute_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
@@ -16,7 +18,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestFloat64AttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -417,3 +418,31 @@ func TestFloat64AttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestFloat64AttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.Float64Attribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.Float64Attribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/provider/schema/int32_attribute.go b/provider/schema/int32_attribute.go
index 16ff58f0f..1f8c60c39 100644
--- a/provider/schema/int32_attribute.go
+++ b/provider/schema/int32_attribute.go
@@ -182,3 +182,9 @@ func (a Int32Attribute) IsRequired() bool {
func (a Int32Attribute) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to provider schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a Int32Attribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/provider/schema/int32_attribute_test.go b/provider/schema/int32_attribute_test.go
index b3ac0687c..a612fb42f 100644
--- a/provider/schema/int32_attribute_test.go
+++ b/provider/schema/int32_attribute_test.go
@@ -418,3 +418,31 @@ func TestInt32AttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestInt32AttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.Int32Attribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.Int32Attribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/provider/schema/int64_attribute.go b/provider/schema/int64_attribute.go
index 3fd9713f1..25243bbf1 100644
--- a/provider/schema/int64_attribute.go
+++ b/provider/schema/int64_attribute.go
@@ -4,13 +4,14 @@
package schema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -181,3 +182,9 @@ func (a Int64Attribute) IsRequired() bool {
func (a Int64Attribute) IsSensitive() bool {
return a.Sensitive
}
+
+// IsWriteOnly returns false as write-only attributes are not relevant to provider schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a Int64Attribute) IsWriteOnly() bool {
+ return false
+}
diff --git a/provider/schema/int64_attribute_test.go b/provider/schema/int64_attribute_test.go
index f5e457663..42efcaed4 100644
--- a/provider/schema/int64_attribute_test.go
+++ b/provider/schema/int64_attribute_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
@@ -16,7 +18,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestInt64AttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -417,3 +418,31 @@ func TestInt64AttributeIsSensitive(t *testing.T) {
})
}
}
+
+func TestInt64AttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.Int64Attribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.Int64Attribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
diff --git a/provider/schema/list_attribute.go b/provider/schema/list_attribute.go
index e733b297f..b85848b58 100644
--- a/provider/schema/list_attribute.go
+++ b/provider/schema/list_attribute.go
@@ -6,6 +6,8 @@ package schema
import (
"context"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
@@ -13,7 +15,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -195,6 +196,12 @@ func (a ListAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to provider schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a ListAttribute) IsWriteOnly() bool {
+ return false
+}
+
// ListValidators returns the Validators field value.
func (a ListAttribute) ListValidators() []validator.List {
return a.Validators
diff --git a/provider/schema/list_attribute_test.go b/provider/schema/list_attribute_test.go
index 3baa9382b..2a529ae13 100644
--- a/provider/schema/list_attribute_test.go
+++ b/provider/schema/list_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -19,7 +21,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestListAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -392,6 +393,34 @@ func TestListAttributeIsSensitive(t *testing.T) {
}
}
+func TestListAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.ListAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.ListAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestListAttributeListValidators(t *testing.T) {
t.Parallel()
diff --git a/provider/schema/list_nested_attribute.go b/provider/schema/list_nested_attribute.go
index 0c82da4ad..700299c05 100644
--- a/provider/schema/list_nested_attribute.go
+++ b/provider/schema/list_nested_attribute.go
@@ -7,6 +7,8 @@ import (
"context"
"fmt"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
@@ -14,7 +16,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -223,6 +224,12 @@ func (a ListNestedAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to provider schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a ListNestedAttribute) IsWriteOnly() bool {
+ return false
+}
+
// ListValidators returns the Validators field value.
func (a ListNestedAttribute) ListValidators() []validator.List {
return a.Validators
diff --git a/provider/schema/list_nested_attribute_test.go b/provider/schema/list_nested_attribute_test.go
index 43d76a612..33d6f1723 100644
--- a/provider/schema/list_nested_attribute_test.go
+++ b/provider/schema/list_nested_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -19,7 +21,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestListNestedAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -563,6 +564,34 @@ func TestListNestedAttributeIsSensitive(t *testing.T) {
}
}
+func TestListNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.ListNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.ListNestedAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestListNestedAttributeListValidators(t *testing.T) {
t.Parallel()
diff --git a/provider/schema/map_attribute.go b/provider/schema/map_attribute.go
index 77dc2b61d..82b5a05d7 100644
--- a/provider/schema/map_attribute.go
+++ b/provider/schema/map_attribute.go
@@ -6,6 +6,8 @@ package schema
import (
"context"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
@@ -13,7 +15,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -198,6 +199,12 @@ func (a MapAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to provider schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a MapAttribute) IsWriteOnly() bool {
+ return false
+}
+
// MapValidators returns the Validators field value.
func (a MapAttribute) MapValidators() []validator.Map {
return a.Validators
diff --git a/provider/schema/map_attribute_test.go b/provider/schema/map_attribute_test.go
index 0bbef5f25..8c5548e6d 100644
--- a/provider/schema/map_attribute_test.go
+++ b/provider/schema/map_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -19,7 +21,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestMapAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -392,6 +393,34 @@ func TestMapAttributeIsSensitive(t *testing.T) {
}
}
+func TestMapAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.MapAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.MapAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestMapAttributeMapValidators(t *testing.T) {
t.Parallel()
diff --git a/provider/schema/map_nested_attribute.go b/provider/schema/map_nested_attribute.go
index 2eed2fa08..14fb4092b 100644
--- a/provider/schema/map_nested_attribute.go
+++ b/provider/schema/map_nested_attribute.go
@@ -223,6 +223,12 @@ func (a MapNestedAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to provider schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a MapNestedAttribute) IsWriteOnly() bool {
+ return false
+}
+
// MapValidators returns the Validators field value.
func (a MapNestedAttribute) MapValidators() []validator.Map {
return a.Validators
diff --git a/provider/schema/map_nested_attribute_test.go b/provider/schema/map_nested_attribute_test.go
index 193960869..129321f22 100644
--- a/provider/schema/map_nested_attribute_test.go
+++ b/provider/schema/map_nested_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -19,7 +21,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestMapNestedAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -563,6 +564,34 @@ func TestMapNestedAttributeIsSensitive(t *testing.T) {
}
}
+func TestMapNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.MapNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.MapNestedAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestMapNestedAttributeMapNestedValidators(t *testing.T) {
t.Parallel()
diff --git a/provider/schema/number_attribute.go b/provider/schema/number_attribute.go
index f3e90e2b7..bb6ffc6d6 100644
--- a/provider/schema/number_attribute.go
+++ b/provider/schema/number_attribute.go
@@ -4,13 +4,14 @@
package schema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -178,6 +179,12 @@ func (a NumberAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to provider schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a NumberAttribute) IsWriteOnly() bool {
+ return false
+}
+
// NumberValidators returns the Validators field value.
func (a NumberAttribute) NumberValidators() []validator.Number {
return a.Validators
diff --git a/provider/schema/number_attribute_test.go b/provider/schema/number_attribute_test.go
index b957cc73f..306e975e5 100644
--- a/provider/schema/number_attribute_test.go
+++ b/provider/schema/number_attribute_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
@@ -16,7 +18,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestNumberAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -384,6 +385,34 @@ func TestNumberAttributeIsSensitive(t *testing.T) {
}
}
+func TestNumberAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.NumberAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.NumberAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestNumberAttributeNumberValidators(t *testing.T) {
t.Parallel()
diff --git a/provider/schema/object_attribute.go b/provider/schema/object_attribute.go
index 3041f5a79..c5c81a1ba 100644
--- a/provider/schema/object_attribute.go
+++ b/provider/schema/object_attribute.go
@@ -6,6 +6,8 @@ package schema
import (
"context"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
@@ -13,7 +15,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -197,6 +198,12 @@ func (a ObjectAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to provider schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a ObjectAttribute) IsWriteOnly() bool {
+ return false
+}
+
// ObjectValidators returns the Validators field value.
func (a ObjectAttribute) ObjectValidators() []validator.Object {
return a.Validators
diff --git a/provider/schema/object_attribute_test.go b/provider/schema/object_attribute_test.go
index 01f65b864..089d03f71 100644
--- a/provider/schema/object_attribute_test.go
+++ b/provider/schema/object_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -19,7 +21,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestObjectAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -398,6 +399,34 @@ func TestObjectAttributeIsSensitive(t *testing.T) {
}
}
+func TestObjectAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.ObjectAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.ObjectAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestObjectAttributeObjectValidators(t *testing.T) {
t.Parallel()
diff --git a/provider/schema/set_attribute.go b/provider/schema/set_attribute.go
index 3297452b7..eaf73344e 100644
--- a/provider/schema/set_attribute.go
+++ b/provider/schema/set_attribute.go
@@ -6,6 +6,8 @@ package schema
import (
"context"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
@@ -13,7 +15,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -193,6 +194,12 @@ func (a SetAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to provider schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a SetAttribute) IsWriteOnly() bool {
+ return false
+}
+
// SetValidators returns the Validators field value.
func (a SetAttribute) SetValidators() []validator.Set {
return a.Validators
diff --git a/provider/schema/set_attribute_test.go b/provider/schema/set_attribute_test.go
index 862b2d8dd..42aec99d1 100644
--- a/provider/schema/set_attribute_test.go
+++ b/provider/schema/set_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -19,7 +21,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestSetAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -392,6 +393,34 @@ func TestSetAttributeIsSensitive(t *testing.T) {
}
}
+func TestSetAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.SetAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.SetAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestSetAttributeSetValidators(t *testing.T) {
t.Parallel()
diff --git a/provider/schema/set_nested_attribute.go b/provider/schema/set_nested_attribute.go
index 7a2fb6060..a9dad64a7 100644
--- a/provider/schema/set_nested_attribute.go
+++ b/provider/schema/set_nested_attribute.go
@@ -219,6 +219,12 @@ func (a SetNestedAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to provider schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a SetNestedAttribute) IsWriteOnly() bool {
+ return false
+}
+
// SetValidators returns the Validators field value.
func (a SetNestedAttribute) SetValidators() []validator.Set {
return a.Validators
diff --git a/provider/schema/set_nested_attribute_test.go b/provider/schema/set_nested_attribute_test.go
index 163604a9c..942cd2a25 100644
--- a/provider/schema/set_nested_attribute_test.go
+++ b/provider/schema/set_nested_attribute_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
@@ -19,7 +21,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestSetNestedAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -563,6 +564,34 @@ func TestSetNestedAttributeIsSensitive(t *testing.T) {
}
}
+func TestSetNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.SetNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.SetNestedAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestSetNestedAttributeSetValidators(t *testing.T) {
t.Parallel()
diff --git a/provider/schema/single_nested_attribute.go b/provider/schema/single_nested_attribute.go
index 4aa669bf1..aded0988e 100644
--- a/provider/schema/single_nested_attribute.go
+++ b/provider/schema/single_nested_attribute.go
@@ -233,6 +233,12 @@ func (a SingleNestedAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to provider schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a SingleNestedAttribute) IsWriteOnly() bool {
+ return false
+}
+
// ObjectValidators returns the Validators field value.
func (a SingleNestedAttribute) ObjectValidators() []validator.Object {
return a.Validators
diff --git a/provider/schema/single_nested_attribute_test.go b/provider/schema/single_nested_attribute_test.go
index a471cbc20..6d40629a2 100644
--- a/provider/schema/single_nested_attribute_test.go
+++ b/provider/schema/single_nested_attribute_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
@@ -16,7 +18,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestSingleNestedAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -524,6 +525,34 @@ func TestSingleNestedAttributeIsSensitive(t *testing.T) {
}
}
+func TestSingleNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.SingleNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.SingleNestedAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestSingleNestedAttributeObjectValidators(t *testing.T) {
t.Parallel()
diff --git a/provider/schema/string_attribute.go b/provider/schema/string_attribute.go
index 7ab7a0c42..eda7a02c4 100644
--- a/provider/schema/string_attribute.go
+++ b/provider/schema/string_attribute.go
@@ -4,13 +4,14 @@
package schema
import (
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
// Ensure the implementation satisifies the desired interfaces.
@@ -174,6 +175,12 @@ func (a StringAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns false as write-only attributes are not relevant to provider schemas,
+// as these schemas describe data explicitly not saved to any artifact.
+func (a StringAttribute) IsWriteOnly() bool {
+ return false
+}
+
// StringValidators returns the Validators field value.
func (a StringAttribute) StringValidators() []validator.String {
return a.Validators
diff --git a/provider/schema/string_attribute_test.go b/provider/schema/string_attribute_test.go
index 4d24ef73d..58b0cafeb 100644
--- a/provider/schema/string_attribute_test.go
+++ b/provider/schema/string_attribute_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
@@ -16,7 +18,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestStringAttributeApplyTerraform5AttributePathStep(t *testing.T) {
@@ -384,6 +385,34 @@ func TestStringAttributeIsSensitive(t *testing.T) {
}
}
+func TestStringAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.StringAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.StringAttribute{},
+ expected: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestStringAttributeStringValidators(t *testing.T) {
t.Parallel()
diff --git a/resource/schema/bool_attribute.go b/resource/schema/bool_attribute.go
index abb0b8708..fa80f565c 100644
--- a/resource/schema/bool_attribute.go
+++ b/resource/schema/bool_attribute.go
@@ -152,6 +152,16 @@ type BoolAttribute struct {
// computed and the value could be altered by other changes then a default
// should be avoided and a plan modifier should be used instead.
Default defaults.Bool
+
+ // WriteOnly indicates that Terraform will not store this attribute value
+ // in the plan or state artifacts.
+ // If WriteOnly is true, either Optional or Required must also be true.
+ // WriteOnly cannot be set with Computed.
+ //
+ // This functionality is only supported in Terraform 1.11 and later.
+ // Practitioners that choose a value for this attribute with older
+ // versions of Terraform will receive an error.
+ WriteOnly bool
}
// ApplyTerraform5AttributePathStep always returns an error as it is not
@@ -229,6 +239,11 @@ func (a BoolAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns the WriteOnly field value.
+func (a BoolAttribute) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// ValidateImplementation contains logic for validating the
// provider-defined implementation of the attribute to prevent unexpected
// errors or panics. This logic runs during the GetProviderSchema RPC and
diff --git a/resource/schema/bool_attribute_test.go b/resource/schema/bool_attribute_test.go
index d4b85ef2d..e2d77dd42 100644
--- a/resource/schema/bool_attribute_test.go
+++ b/resource/schema/bool_attribute_test.go
@@ -512,6 +512,40 @@ func TestBoolAttributeIsSensitive(t *testing.T) {
}
}
+func TestBoolAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.BoolAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.BoolAttribute{},
+ expected: false,
+ },
+ "writeOnly": {
+ attribute: schema.BoolAttribute{
+ WriteOnly: true,
+ },
+ expected: true,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestBoolAttributeValidateImplementation(t *testing.T) {
t.Parallel()
diff --git a/resource/schema/dynamic_attribute.go b/resource/schema/dynamic_attribute.go
index 7b97625d9..e06600ab4 100644
--- a/resource/schema/dynamic_attribute.go
+++ b/resource/schema/dynamic_attribute.go
@@ -153,6 +153,16 @@ type DynamicAttribute struct {
// computed and the value could be altered by other changes then a default
// should be avoided and a plan modifier should be used instead.
Default defaults.Dynamic
+
+ // WriteOnly indicates that Terraform will not store this attribute value
+ // in the plan or state artifacts.
+ // If WriteOnly is true, either Optional or Required must also be true.
+ // WriteOnly cannot be set with Computed.
+ //
+ // This functionality is only supported in Terraform 1.11 and later.
+ // Practitioners that choose a value for this attribute with older
+ // versions of Terraform will receive an error.
+ WriteOnly bool
}
// ApplyTerraform5AttributePathStep always returns an error as it is not
@@ -215,6 +225,11 @@ func (a DynamicAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns the WriteOnly field value.
+func (a DynamicAttribute) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// DynamicDefaultValue returns the Default field value.
func (a DynamicAttribute) DynamicDefaultValue() defaults.Dynamic {
return a.Default
diff --git a/resource/schema/dynamic_attribute_test.go b/resource/schema/dynamic_attribute_test.go
index f99dc598c..93c5010f3 100644
--- a/resource/schema/dynamic_attribute_test.go
+++ b/resource/schema/dynamic_attribute_test.go
@@ -397,6 +397,40 @@ func TestDynamicAttributeIsSensitive(t *testing.T) {
}
}
+func TestDynamicAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.DynamicAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.DynamicAttribute{},
+ expected: false,
+ },
+ "writeOnly": {
+ attribute: schema.DynamicAttribute{
+ WriteOnly: true,
+ },
+ expected: true,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestDynamicAttributeDynamicDefaultValue(t *testing.T) {
t.Parallel()
diff --git a/resource/schema/float32_attribute.go b/resource/schema/float32_attribute.go
index 9e8e7a22a..3064b4ed9 100644
--- a/resource/schema/float32_attribute.go
+++ b/resource/schema/float32_attribute.go
@@ -155,6 +155,16 @@ type Float32Attribute struct {
// computed and the value could be altered by other changes then a default
// should be avoided and a plan modifier should be used instead.
Default defaults.Float32
+
+ // WriteOnly indicates that Terraform will not store this attribute value
+ // in the plan or state artifacts.
+ // If WriteOnly is true, either Optional or Required must also be true.
+ // WriteOnly cannot be set with Computed.
+ //
+ // This functionality is only supported in Terraform 1.11 and later.
+ // Practitioners that choose a value for this attribute with older
+ // versions of Terraform will receive an error.
+ WriteOnly bool
}
// ApplyTerraform5AttributePathStep always returns an error as it is not
@@ -232,6 +242,11 @@ func (a Float32Attribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns the WriteOnly field value.
+func (a Float32Attribute) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// ValidateImplementation contains logic for validating the
// provider-defined implementation of the attribute to prevent unexpected
// errors or panics. This logic runs during the GetProviderSchema RPC and
diff --git a/resource/schema/float32_attribute_test.go b/resource/schema/float32_attribute_test.go
index 75f1a9637..3c90d0081 100644
--- a/resource/schema/float32_attribute_test.go
+++ b/resource/schema/float32_attribute_test.go
@@ -512,6 +512,40 @@ func TestFloat32AttributeIsSensitive(t *testing.T) {
}
}
+func TestFloat32AttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.Float32Attribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.Float32Attribute{},
+ expected: false,
+ },
+ "writeOnly": {
+ attribute: schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ expected: true,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestFloat32AttributeValidateImplementation(t *testing.T) {
t.Parallel()
diff --git a/resource/schema/float64_attribute.go b/resource/schema/float64_attribute.go
index 7d762a4a2..205af3f98 100644
--- a/resource/schema/float64_attribute.go
+++ b/resource/schema/float64_attribute.go
@@ -155,6 +155,16 @@ type Float64Attribute struct {
// computed and the value could be altered by other changes then a default
// should be avoided and a plan modifier should be used instead.
Default defaults.Float64
+
+ // WriteOnly indicates that Terraform will not store this attribute value
+ // in the plan or state artifacts.
+ // If WriteOnly is true, either Optional or Required must also be true.
+ // WriteOnly cannot be set with Computed.
+ //
+ // This functionality is only supported in Terraform 1.11 and later.
+ // Practitioners that choose a value for this attribute with older
+ // versions of Terraform will receive an error.
+ WriteOnly bool
}
// ApplyTerraform5AttributePathStep always returns an error as it is not
@@ -232,6 +242,11 @@ func (a Float64Attribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns the WriteOnly field value.
+func (a Float64Attribute) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// ValidateImplementation contains logic for validating the
// provider-defined implementation of the attribute to prevent unexpected
// errors or panics. This logic runs during the GetProviderSchema RPC and
diff --git a/resource/schema/float64_attribute_test.go b/resource/schema/float64_attribute_test.go
index a7d155c9f..939080080 100644
--- a/resource/schema/float64_attribute_test.go
+++ b/resource/schema/float64_attribute_test.go
@@ -512,6 +512,40 @@ func TestFloat64AttributeIsSensitive(t *testing.T) {
}
}
+func TestFloat64AttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.Float64Attribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.Float64Attribute{},
+ expected: false,
+ },
+ "writeOnly": {
+ attribute: schema.Float64Attribute{
+ WriteOnly: true,
+ },
+ expected: true,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestFloat64AttributeValidateImplementation(t *testing.T) {
t.Parallel()
diff --git a/resource/schema/int32_attribute.go b/resource/schema/int32_attribute.go
index 41b74bcf3..d3f97d60b 100644
--- a/resource/schema/int32_attribute.go
+++ b/resource/schema/int32_attribute.go
@@ -155,6 +155,16 @@ type Int32Attribute struct {
// computed and the value could be altered by other changes then a default
// should be avoided and a plan modifier should be used instead.
Default defaults.Int32
+
+ // WriteOnly indicates that Terraform will not store this attribute value
+ // in the plan or state artifacts.
+ // If WriteOnly is true, either Optional or Required must also be true.
+ // WriteOnly cannot be set with Computed.
+ //
+ // This functionality is only supported in Terraform 1.11 and later.
+ // Practitioners that choose a value for this attribute with older
+ // versions of Terraform will receive an error.
+ WriteOnly bool
}
// ApplyTerraform5AttributePathStep always returns an error as it is not
@@ -232,6 +242,11 @@ func (a Int32Attribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns the WriteOnly field value.
+func (a Int32Attribute) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// ValidateImplementation contains logic for validating the
// provider-defined implementation of the attribute to prevent unexpected
// errors or panics. This logic runs during the GetProviderSchema RPC and
diff --git a/resource/schema/int32_attribute_test.go b/resource/schema/int32_attribute_test.go
index 152d957f7..48eb3d2f2 100644
--- a/resource/schema/int32_attribute_test.go
+++ b/resource/schema/int32_attribute_test.go
@@ -512,6 +512,40 @@ func TestInt32AttributeIsSensitive(t *testing.T) {
}
}
+func TestInt32AttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.Int32Attribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.Int32Attribute{},
+ expected: false,
+ },
+ "writeOnly": {
+ attribute: schema.Int32Attribute{
+ WriteOnly: true,
+ },
+ expected: true,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestInt32AttributeValidateImplementation(t *testing.T) {
t.Parallel()
diff --git a/resource/schema/int64_attribute.go b/resource/schema/int64_attribute.go
index 65ec795e9..c65eb41fa 100644
--- a/resource/schema/int64_attribute.go
+++ b/resource/schema/int64_attribute.go
@@ -155,6 +155,16 @@ type Int64Attribute struct {
// computed and the value could be altered by other changes then a default
// should be avoided and a plan modifier should be used instead.
Default defaults.Int64
+
+ // WriteOnly indicates that Terraform will not store this attribute value
+ // in the plan or state artifacts.
+ // If WriteOnly is true, either Optional or Required must also be true.
+ // WriteOnly cannot be set with Computed.
+ //
+ // This functionality is only supported in Terraform 1.11 and later.
+ // Practitioners that choose a value for this attribute with older
+ // versions of Terraform will receive an error.
+ WriteOnly bool
}
// ApplyTerraform5AttributePathStep always returns an error as it is not
@@ -232,6 +242,11 @@ func (a Int64Attribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns the WriteOnly field value.
+func (a Int64Attribute) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// ValidateImplementation contains logic for validating the
// provider-defined implementation of the attribute to prevent unexpected
// errors or panics. This logic runs during the GetProviderSchema RPC and
diff --git a/resource/schema/int64_attribute_test.go b/resource/schema/int64_attribute_test.go
index a961c1fb8..032f523c9 100644
--- a/resource/schema/int64_attribute_test.go
+++ b/resource/schema/int64_attribute_test.go
@@ -512,6 +512,40 @@ func TestInt64AttributeIsSensitive(t *testing.T) {
}
}
+func TestInt64AttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.Int64Attribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.Int64Attribute{},
+ expected: false,
+ },
+ "writeOnly": {
+ attribute: schema.Int64Attribute{
+ WriteOnly: true,
+ },
+ expected: true,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestInt64AttributeValidateImplementation(t *testing.T) {
t.Parallel()
diff --git a/resource/schema/list_attribute.go b/resource/schema/list_attribute.go
index 1dc0e0e8c..9c1536dbe 100644
--- a/resource/schema/list_attribute.go
+++ b/resource/schema/list_attribute.go
@@ -168,6 +168,16 @@ type ListAttribute struct {
// computed and the value could be altered by other changes then a default
// should be avoided and a plan modifier should be used instead.
Default defaults.List
+
+ // WriteOnly indicates that Terraform will not store this attribute value
+ // in the plan or state artifacts.
+ // If WriteOnly is true, either Optional or Required must also be true.
+ // WriteOnly cannot be set with Computed.
+ //
+ // This functionality is only supported in Terraform 1.11 and later.
+ // Practitioners that choose a value for this attribute with older
+ // versions of Terraform will receive an error.
+ WriteOnly bool
}
// ApplyTerraform5AttributePathStep returns the result of stepping into a list
@@ -232,6 +242,11 @@ func (a ListAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns the WriteOnly field value.
+func (a ListAttribute) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// ListDefaultValue returns the Default field value.
func (a ListAttribute) ListDefaultValue() defaults.List {
return a.Default
diff --git a/resource/schema/list_attribute_test.go b/resource/schema/list_attribute_test.go
index 784ec70d6..07db0c333 100644
--- a/resource/schema/list_attribute_test.go
+++ b/resource/schema/list_attribute_test.go
@@ -403,6 +403,40 @@ func TestListAttributeIsSensitive(t *testing.T) {
}
}
+func TestListAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.ListAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.ListAttribute{},
+ expected: false,
+ },
+ "writeOnly": {
+ attribute: schema.ListAttribute{
+ WriteOnly: true,
+ },
+ expected: true,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestListAttributeListDefaultValue(t *testing.T) {
t.Parallel()
diff --git a/resource/schema/list_nested_attribute.go b/resource/schema/list_nested_attribute.go
index 95fd2ba01..ee1845bb5 100644
--- a/resource/schema/list_nested_attribute.go
+++ b/resource/schema/list_nested_attribute.go
@@ -178,6 +178,19 @@ type ListNestedAttribute struct {
// computed and the value could be altered by other changes then a default
// should be avoided and a plan modifier should be used instead.
Default defaults.List
+
+ // WriteOnly indicates that Terraform will not store this attribute value
+ // in the plan or state artifacts.
+ // If WriteOnly is true, either Optional or Required must also be true.
+ // WriteOnly cannot be set with Computed.
+ //
+ // If WriteOnly is true for a nested attribute, all of its child attributes
+ // must also set WriteOnly to true and no child attribute can be Computed.
+ //
+ // This functionality is only supported in Terraform 1.11 and later.
+ // Practitioners that choose a value for this attribute with older
+ // versions of Terraform will receive an error.
+ WriteOnly bool
}
// ApplyTerraform5AttributePathStep returns the Attributes field value if step
@@ -260,6 +273,11 @@ func (a ListNestedAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns the WriteOnly field value.
+func (a ListNestedAttribute) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// ListDefaultValue returns the Default field value.
func (a ListNestedAttribute) ListDefaultValue() defaults.List {
return a.Default
@@ -284,6 +302,14 @@ func (a ListNestedAttribute) ValidateImplementation(ctx context.Context, req fws
resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path))
}
+ if a.IsWriteOnly() && !fwschema.ContainsAllWriteOnlyChildAttributes(a) {
+ resp.Diagnostics.Append(fwschema.InvalidWriteOnlyNestedAttributeDiag(req.Path))
+ }
+
+ if a.IsComputed() && fwschema.ContainsAnyWriteOnlyChildAttributes(a) {
+ resp.Diagnostics.Append(fwschema.InvalidComputedNestedAttributeWithWriteOnlyDiag(req.Path))
+ }
+
if a.ListDefaultValue() != nil {
if !a.IsComputed() {
resp.Diagnostics.Append(nonComputedAttributeWithDefaultDiag(req.Path))
diff --git a/resource/schema/list_nested_attribute_test.go b/resource/schema/list_nested_attribute_test.go
index a73c7a843..290fd0dc7 100644
--- a/resource/schema/list_nested_attribute_test.go
+++ b/resource/schema/list_nested_attribute_test.go
@@ -574,6 +574,40 @@ func TestListNestedAttributeIsSensitive(t *testing.T) {
}
}
+func TestListNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.ListNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.ListNestedAttribute{},
+ expected: false,
+ },
+ "writeOnly": {
+ attribute: schema.ListNestedAttribute{
+ WriteOnly: true,
+ },
+ expected: true,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestListNestedAttributeListDefaultValue(t *testing.T) {
t.Parallel()
@@ -909,6 +943,94 @@ func TestListNestedAttributeValidateImplementation(t *testing.T) {
},
},
},
+ "writeOnly-with-child-writeOnly-no-error-diagnostic": {
+ attribute: schema.ListNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "test_attr": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ request: fwschema.ValidateImplementationRequest{
+ Name: "test",
+ Path: path.Root("test"),
+ },
+ expected: &fwschema.ValidateImplementationResponse{},
+ },
+ "writeOnly-without-child-writeOnly-error-diagnostic": {
+ attribute: schema.ListNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "test_attr": schema.StringAttribute{
+ Computed: true,
+ },
+ },
+ },
+ },
+ request: fwschema.ValidateImplementationRequest{
+ Name: "test",
+ Path: path.Root("test"),
+ },
+ expected: &fwschema.ValidateImplementationResponse{
+ Diagnostics: diag.Diagnostics{
+ diag.NewErrorDiagnostic(
+ "Invalid Schema Implementation",
+ "When validating the schema, an implementation issue was found. "+
+ "This is always an issue with the provider and should be reported to the provider developers.\n\n"+
+ "\"test\" is a WriteOnly nested attribute that contains a non-WriteOnly child attribute.\n\n"+
+ "Every child attribute of a WriteOnly nested attribute must also have WriteOnly set to true.",
+ ),
+ },
+ },
+ },
+ "computed-without-child-writeOnly-no-error-diagnostic": {
+ attribute: schema.ListNestedAttribute{
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "test_attr": schema.StringAttribute{
+ Computed: true,
+ },
+ },
+ },
+ },
+ request: fwschema.ValidateImplementationRequest{
+ Name: "test",
+ Path: path.Root("test"),
+ },
+ expected: &fwschema.ValidateImplementationResponse{},
+ },
+ "computed-with-child-writeOnly-error-diagnostic": {
+ attribute: schema.ListNestedAttribute{
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "test_attr": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ request: fwschema.ValidateImplementationRequest{
+ Name: "test",
+ Path: path.Root("test"),
+ },
+ expected: &fwschema.ValidateImplementationResponse{
+ Diagnostics: diag.Diagnostics{
+ diag.NewErrorDiagnostic(
+ "Invalid Schema Implementation",
+ "When validating the schema, an implementation issue was found. "+
+ "This is always an issue with the provider and should be reported to the provider developers.\n\n"+
+ "\"test\" is a Computed nested attribute that contains a WriteOnly child attribute.\n\n"+
+ "Every child attribute of a Computed nested attribute must have WriteOnly set to false.",
+ ),
+ },
+ },
+ },
}
for name, testCase := range testCases {
diff --git a/resource/schema/map_attribute.go b/resource/schema/map_attribute.go
index ac50f63f8..f76a295c3 100644
--- a/resource/schema/map_attribute.go
+++ b/resource/schema/map_attribute.go
@@ -171,6 +171,16 @@ type MapAttribute struct {
// computed and the value could be altered by other changes then a default
// should be avoided and a plan modifier should be used instead.
Default defaults.Map
+
+ // WriteOnly indicates that Terraform will not store this attribute value
+ // in the plan or state artifacts.
+ // If WriteOnly is true, either Optional or Required must also be true.
+ // WriteOnly cannot be set with Computed.
+ //
+ // This functionality is only supported in Terraform 1.11 and later.
+ // Practitioners that choose a value for this attribute with older
+ // versions of Terraform will receive an error.
+ WriteOnly bool
}
// ApplyTerraform5AttributePathStep returns the result of stepping into a map
@@ -235,6 +245,11 @@ func (a MapAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns the WriteOnly field value.
+func (a MapAttribute) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// MapDefaultValue returns the Default field value.
func (a MapAttribute) MapDefaultValue() defaults.Map {
return a.Default
diff --git a/resource/schema/map_attribute_test.go b/resource/schema/map_attribute_test.go
index 4f56036d5..9e48ea787 100644
--- a/resource/schema/map_attribute_test.go
+++ b/resource/schema/map_attribute_test.go
@@ -403,6 +403,40 @@ func TestMapAttributeIsSensitive(t *testing.T) {
}
}
+func TestMapAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.MapAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.MapAttribute{},
+ expected: false,
+ },
+ "writeOnly": {
+ attribute: schema.MapAttribute{
+ WriteOnly: true,
+ },
+ expected: true,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestMapAttributeMapDefaultValue(t *testing.T) {
t.Parallel()
diff --git a/resource/schema/map_nested_attribute.go b/resource/schema/map_nested_attribute.go
index ab2230b3b..db868f726 100644
--- a/resource/schema/map_nested_attribute.go
+++ b/resource/schema/map_nested_attribute.go
@@ -178,6 +178,19 @@ type MapNestedAttribute struct {
// computed and the value could be altered by other changes then a default
// should be avoided and a plan modifier should be used instead.
Default defaults.Map
+
+ // WriteOnly indicates that Terraform will not store this attribute value
+ // in the plan or state artifacts.
+ // If WriteOnly is true, either Optional or Required must also be true.
+ // WriteOnly cannot be set with Computed.
+ //
+ // If WriteOnly is true for a nested attribute, all of its child attributes
+ // must also set WriteOnly to true and no child attribute can be Computed.
+ //
+ // This functionality is only supported in Terraform 1.11 and later.
+ // Practitioners that choose a value for this attribute with older
+ // versions of Terraform will receive an error.
+ WriteOnly bool
}
// ApplyTerraform5AttributePathStep returns the Attributes field value if step
@@ -260,6 +273,11 @@ func (a MapNestedAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns the WriteOnly field value.
+func (a MapNestedAttribute) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// MapDefaultValue returns the Default field value.
func (a MapNestedAttribute) MapDefaultValue() defaults.Map {
return a.Default
@@ -284,6 +302,14 @@ func (a MapNestedAttribute) ValidateImplementation(ctx context.Context, req fwsc
resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path))
}
+ if a.IsWriteOnly() && !fwschema.ContainsAllWriteOnlyChildAttributes(a) {
+ resp.Diagnostics.Append(fwschema.InvalidWriteOnlyNestedAttributeDiag(req.Path))
+ }
+
+ if a.IsComputed() && fwschema.ContainsAnyWriteOnlyChildAttributes(a) {
+ resp.Diagnostics.Append(fwschema.InvalidComputedNestedAttributeWithWriteOnlyDiag(req.Path))
+ }
+
if a.MapDefaultValue() != nil {
if !a.IsComputed() {
resp.Diagnostics.Append(nonComputedAttributeWithDefaultDiag(req.Path))
diff --git a/resource/schema/map_nested_attribute_test.go b/resource/schema/map_nested_attribute_test.go
index 1fd1a7624..4c7a72489 100644
--- a/resource/schema/map_nested_attribute_test.go
+++ b/resource/schema/map_nested_attribute_test.go
@@ -574,6 +574,40 @@ func TestMapNestedAttributeIsSensitive(t *testing.T) {
}
}
+func TestMapNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.MapNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.MapNestedAttribute{},
+ expected: false,
+ },
+ "writeOnly": {
+ attribute: schema.MapNestedAttribute{
+ WriteOnly: true,
+ },
+ expected: true,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestMapNestedAttributeMapNestedDefaultValue(t *testing.T) {
t.Parallel()
@@ -909,6 +943,94 @@ func TestMapNestedAttributeValidateImplementation(t *testing.T) {
},
},
},
+ "writeOnly-with-child-writeOnly-no-error-diagnostic": {
+ attribute: schema.MapNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "test_attr": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ request: fwschema.ValidateImplementationRequest{
+ Name: "test",
+ Path: path.Root("test"),
+ },
+ expected: &fwschema.ValidateImplementationResponse{},
+ },
+ "writeOnly-without-child-writeOnly-error-diagnostic": {
+ attribute: schema.MapNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "test_attr": schema.StringAttribute{
+ Computed: true,
+ },
+ },
+ },
+ },
+ request: fwschema.ValidateImplementationRequest{
+ Name: "test",
+ Path: path.Root("test"),
+ },
+ expected: &fwschema.ValidateImplementationResponse{
+ Diagnostics: diag.Diagnostics{
+ diag.NewErrorDiagnostic(
+ "Invalid Schema Implementation",
+ "When validating the schema, an implementation issue was found. "+
+ "This is always an issue with the provider and should be reported to the provider developers.\n\n"+
+ "\"test\" is a WriteOnly nested attribute that contains a non-WriteOnly child attribute.\n\n"+
+ "Every child attribute of a WriteOnly nested attribute must also have WriteOnly set to true.",
+ ),
+ },
+ },
+ },
+ "computed-without-child-writeOnly-no-error-diagnostic": {
+ attribute: schema.MapNestedAttribute{
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "test_attr": schema.StringAttribute{
+ Computed: true,
+ },
+ },
+ },
+ },
+ request: fwschema.ValidateImplementationRequest{
+ Name: "test",
+ Path: path.Root("test"),
+ },
+ expected: &fwschema.ValidateImplementationResponse{},
+ },
+ "computed-with-child-writeOnly-error-diagnostic": {
+ attribute: schema.MapNestedAttribute{
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "test_attr": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ request: fwschema.ValidateImplementationRequest{
+ Name: "test",
+ Path: path.Root("test"),
+ },
+ expected: &fwschema.ValidateImplementationResponse{
+ Diagnostics: diag.Diagnostics{
+ diag.NewErrorDiagnostic(
+ "Invalid Schema Implementation",
+ "When validating the schema, an implementation issue was found. "+
+ "This is always an issue with the provider and should be reported to the provider developers.\n\n"+
+ "\"test\" is a Computed nested attribute that contains a WriteOnly child attribute.\n\n"+
+ "Every child attribute of a Computed nested attribute must have WriteOnly set to false.",
+ ),
+ },
+ },
+ },
}
for name, testCase := range testCases {
diff --git a/resource/schema/number_attribute.go b/resource/schema/number_attribute.go
index d2b9c59af..8f367592e 100644
--- a/resource/schema/number_attribute.go
+++ b/resource/schema/number_attribute.go
@@ -156,6 +156,16 @@ type NumberAttribute struct {
// computed and the value could be altered by other changes then a default
// should be avoided and a plan modifier should be used instead.
Default defaults.Number
+
+ // WriteOnly indicates that Terraform will not store this attribute value
+ // in the plan or state artifacts.
+ // If WriteOnly is true, either Optional or Required must also be true.
+ // WriteOnly cannot be set with Computed.
+ //
+ // This functionality is only supported in Terraform 1.11 and later.
+ // Practitioners that choose a value for this attribute with older
+ // versions of Terraform will receive an error.
+ WriteOnly bool
}
// ApplyTerraform5AttributePathStep always returns an error as it is not
@@ -218,6 +228,11 @@ func (a NumberAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns the WriteOnly field value.
+func (a NumberAttribute) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// NumberDefaultValue returns the Default field value.
func (a NumberAttribute) NumberDefaultValue() defaults.Number {
return a.Default
diff --git a/resource/schema/number_attribute_test.go b/resource/schema/number_attribute_test.go
index 2bc8731ca..e9ff78cc8 100644
--- a/resource/schema/number_attribute_test.go
+++ b/resource/schema/number_attribute_test.go
@@ -398,6 +398,40 @@ func TestNumberAttributeIsSensitive(t *testing.T) {
}
}
+func TestNumberAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.NumberAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.NumberAttribute{},
+ expected: false,
+ },
+ "writeOnly": {
+ attribute: schema.NumberAttribute{
+ WriteOnly: true,
+ },
+ expected: true,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestNumberAttributeNumberDefaultValue(t *testing.T) {
t.Parallel()
diff --git a/resource/schema/object_attribute.go b/resource/schema/object_attribute.go
index 7b9fe6a56..03f35aa00 100644
--- a/resource/schema/object_attribute.go
+++ b/resource/schema/object_attribute.go
@@ -170,6 +170,16 @@ type ObjectAttribute struct {
// computed and the value could be altered by other changes then a default
// should be avoided and a plan modifier should be used instead.
Default defaults.Object
+
+ // WriteOnly indicates that Terraform will not store this attribute value
+ // in the plan or state artifacts.
+ // If WriteOnly is true, either Optional or Required must also be true.
+ // WriteOnly cannot be set with Computed.
+ //
+ // This functionality is only supported in Terraform 1.11 and later.
+ // Practitioners that choose a value for this attribute with older
+ // versions of Terraform will receive an error.
+ WriteOnly bool
}
// ApplyTerraform5AttributePathStep returns the result of stepping into an
@@ -234,6 +244,11 @@ func (a ObjectAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns the WriteOnly field value.
+func (a ObjectAttribute) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// ObjectDefaultValue returns the Default field value.
func (a ObjectAttribute) ObjectDefaultValue() defaults.Object {
return a.Default
diff --git a/resource/schema/object_attribute_test.go b/resource/schema/object_attribute_test.go
index 071e2293c..8f1b6cc3c 100644
--- a/resource/schema/object_attribute_test.go
+++ b/resource/schema/object_attribute_test.go
@@ -409,6 +409,40 @@ func TestObjectAttributeIsSensitive(t *testing.T) {
}
}
+func TestObjectAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.ObjectAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.ObjectAttribute{},
+ expected: false,
+ },
+ "writeOnly": {
+ attribute: schema.ObjectAttribute{
+ WriteOnly: true,
+ },
+ expected: true,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestObjectAttributeObjectDefaultValue(t *testing.T) {
t.Parallel()
diff --git a/resource/schema/set_attribute.go b/resource/schema/set_attribute.go
index 7a54221bb..843f58fb3 100644
--- a/resource/schema/set_attribute.go
+++ b/resource/schema/set_attribute.go
@@ -166,6 +166,16 @@ type SetAttribute struct {
// computed and the value could be altered by other changes then a default
// should be avoided and a plan modifier should be used instead.
Default defaults.Set
+
+ // WriteOnly indicates that Terraform will not store this attribute value
+ // in the plan or state artifacts.
+ // If WriteOnly is true, either Optional or Required must also be true.
+ // WriteOnly cannot be set with Computed.
+ //
+ // This functionality is only supported in Terraform 1.11 and later.
+ // Practitioners that choose a value for this attribute with older
+ // versions of Terraform will receive an error.
+ WriteOnly bool
}
// ApplyTerraform5AttributePathStep returns the result of stepping into a set
@@ -230,6 +240,11 @@ func (a SetAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns the WriteOnly field value.
+func (a SetAttribute) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// SetDefaultValue returns the Default field value.
func (a SetAttribute) SetDefaultValue() defaults.Set {
return a.Default
diff --git a/resource/schema/set_attribute_test.go b/resource/schema/set_attribute_test.go
index 98483f724..9f7c6d0ac 100644
--- a/resource/schema/set_attribute_test.go
+++ b/resource/schema/set_attribute_test.go
@@ -403,6 +403,40 @@ func TestSetAttributeIsSensitive(t *testing.T) {
}
}
+func TestSetAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.SetAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.SetAttribute{},
+ expected: false,
+ },
+ "writeOnly": {
+ attribute: schema.SetAttribute{
+ WriteOnly: true,
+ },
+ expected: true,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestSetAttributeSetDefaultValue(t *testing.T) {
t.Parallel()
diff --git a/resource/schema/set_nested_attribute.go b/resource/schema/set_nested_attribute.go
index dee37b591..56c449307 100644
--- a/resource/schema/set_nested_attribute.go
+++ b/resource/schema/set_nested_attribute.go
@@ -173,6 +173,19 @@ type SetNestedAttribute struct {
// computed and the value could be altered by other changes then a default
// should be avoided and a plan modifier should be used instead.
Default defaults.Set
+
+ // WriteOnly indicates that Terraform will not store this attribute value
+ // in the plan or state artifacts.
+ // If WriteOnly is true, either Optional or Required must also be true.
+ // WriteOnly cannot be set with Computed.
+ //
+ // If WriteOnly is true for a nested attribute, all of its child attributes
+ // must also set WriteOnly to true and no child attribute can be Computed.
+ //
+ // This functionality is only supported in Terraform 1.11 and later.
+ // Practitioners that choose a value for this attribute with older
+ // versions of Terraform will receive an error.
+ WriteOnly bool
}
// ApplyTerraform5AttributePathStep returns the Attributes field value if step
@@ -255,6 +268,11 @@ func (a SetNestedAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns the WriteOnly field value.
+func (a SetNestedAttribute) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// SetDefaultValue returns the Default field value.
func (a SetNestedAttribute) SetDefaultValue() defaults.Set {
return a.Default
@@ -279,6 +297,14 @@ func (a SetNestedAttribute) ValidateImplementation(ctx context.Context, req fwsc
resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path))
}
+ if a.IsWriteOnly() && !fwschema.ContainsAllWriteOnlyChildAttributes(a) {
+ resp.Diagnostics.Append(fwschema.InvalidWriteOnlyNestedAttributeDiag(req.Path))
+ }
+
+ if a.IsComputed() && fwschema.ContainsAnyWriteOnlyChildAttributes(a) {
+ resp.Diagnostics.Append(fwschema.InvalidComputedNestedAttributeWithWriteOnlyDiag(req.Path))
+ }
+
if a.SetDefaultValue() != nil {
if !a.IsComputed() {
resp.Diagnostics.Append(nonComputedAttributeWithDefaultDiag(req.Path))
diff --git a/resource/schema/set_nested_attribute_test.go b/resource/schema/set_nested_attribute_test.go
index d148db457..8ea3cb58c 100644
--- a/resource/schema/set_nested_attribute_test.go
+++ b/resource/schema/set_nested_attribute_test.go
@@ -574,6 +574,40 @@ func TestSetNestedAttributeIsSensitive(t *testing.T) {
}
}
+func TestSetNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.SetNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.SetNestedAttribute{},
+ expected: false,
+ },
+ "writeOnly": {
+ attribute: schema.SetNestedAttribute{
+ WriteOnly: true,
+ },
+ expected: true,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestSetNestedAttributeSetDefaultValue(t *testing.T) {
t.Parallel()
@@ -909,6 +943,94 @@ func TestSetNestedAttributeValidateImplementation(t *testing.T) {
},
},
},
+ "writeOnly-with-child-writeOnly-no-error-diagnostic": {
+ attribute: schema.SetNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "test_attr": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ request: fwschema.ValidateImplementationRequest{
+ Name: "test",
+ Path: path.Root("test"),
+ },
+ expected: &fwschema.ValidateImplementationResponse{},
+ },
+ "writeOnly-without-child-writeOnly-error-diagnostic": {
+ attribute: schema.SetNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "test_attr": schema.StringAttribute{
+ Computed: true,
+ },
+ },
+ },
+ },
+ request: fwschema.ValidateImplementationRequest{
+ Name: "test",
+ Path: path.Root("test"),
+ },
+ expected: &fwschema.ValidateImplementationResponse{
+ Diagnostics: diag.Diagnostics{
+ diag.NewErrorDiagnostic(
+ "Invalid Schema Implementation",
+ "When validating the schema, an implementation issue was found. "+
+ "This is always an issue with the provider and should be reported to the provider developers.\n\n"+
+ "\"test\" is a WriteOnly nested attribute that contains a non-WriteOnly child attribute.\n\n"+
+ "Every child attribute of a WriteOnly nested attribute must also have WriteOnly set to true.",
+ ),
+ },
+ },
+ },
+ "computed-without-child-writeOnly-no-error-diagnostic": {
+ attribute: schema.SetNestedAttribute{
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "test_attr": schema.StringAttribute{
+ Computed: true,
+ },
+ },
+ },
+ },
+ request: fwschema.ValidateImplementationRequest{
+ Name: "test",
+ Path: path.Root("test"),
+ },
+ expected: &fwschema.ValidateImplementationResponse{},
+ },
+ "computed-with-child-writeOnly-error-diagnostic": {
+ attribute: schema.SetNestedAttribute{
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "test_attr": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ request: fwschema.ValidateImplementationRequest{
+ Name: "test",
+ Path: path.Root("test"),
+ },
+ expected: &fwschema.ValidateImplementationResponse{
+ Diagnostics: diag.Diagnostics{
+ diag.NewErrorDiagnostic(
+ "Invalid Schema Implementation",
+ "When validating the schema, an implementation issue was found. "+
+ "This is always an issue with the provider and should be reported to the provider developers.\n\n"+
+ "\"test\" is a Computed nested attribute that contains a WriteOnly child attribute.\n\n"+
+ "Every child attribute of a Computed nested attribute must have WriteOnly set to false.",
+ ),
+ },
+ },
+ },
}
for name, testCase := range testCases {
diff --git a/resource/schema/single_nested_attribute.go b/resource/schema/single_nested_attribute.go
index 3dbda942a..12cab9800 100644
--- a/resource/schema/single_nested_attribute.go
+++ b/resource/schema/single_nested_attribute.go
@@ -167,6 +167,16 @@ type SingleNestedAttribute struct {
// computed and the value could be altered by other changes then a default
// should be avoided and a plan modifier should be used instead.
Default defaults.Object
+
+ // WriteOnly indicates that Terraform will not store this attribute value
+ // in the plan or state artifacts.
+ // If WriteOnly is true, either Optional or Required must also be true.
+ // WriteOnly cannot be set with Computed.
+ //
+ // This functionality is only supported in Terraform 1.11 and later.
+ // Practitioners that choose a value for this attribute with older
+ // versions of Terraform will receive an error.
+ WriteOnly bool
}
// ApplyTerraform5AttributePathStep returns the Attributes field value if step
@@ -271,6 +281,11 @@ func (a SingleNestedAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns the WriteOnly field value.
+func (a SingleNestedAttribute) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// ObjectDefaultValue returns the Default field value.
func (a SingleNestedAttribute) ObjectDefaultValue() defaults.Object {
return a.Default
@@ -295,6 +310,14 @@ func (a SingleNestedAttribute) ValidateImplementation(ctx context.Context, req f
resp.Diagnostics.Append(nonComputedAttributeWithDefaultDiag(req.Path))
}
+ if a.IsWriteOnly() && !fwschema.ContainsAllWriteOnlyChildAttributes(a) {
+ resp.Diagnostics.Append(fwschema.InvalidWriteOnlyNestedAttributeDiag(req.Path))
+ }
+
+ if a.IsComputed() && fwschema.ContainsAnyWriteOnlyChildAttributes(a) {
+ resp.Diagnostics.Append(fwschema.InvalidComputedNestedAttributeWithWriteOnlyDiag(req.Path))
+ }
+
if a.ObjectDefaultValue() != nil {
if !a.IsComputed() {
resp.Diagnostics.Append(nonComputedAttributeWithDefaultDiag(req.Path))
diff --git a/resource/schema/single_nested_attribute_test.go b/resource/schema/single_nested_attribute_test.go
index 1f20b65b3..f1b666566 100644
--- a/resource/schema/single_nested_attribute_test.go
+++ b/resource/schema/single_nested_attribute_test.go
@@ -538,6 +538,40 @@ func TestSingleNestedAttributeIsSensitive(t *testing.T) {
}
}
+func TestSingleNestedAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.SingleNestedAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.SingleNestedAttribute{},
+ expected: false,
+ },
+ "writeOnly": {
+ attribute: schema.SingleNestedAttribute{
+ WriteOnly: true,
+ },
+ expected: true,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestSingleNestedAttributeObjectDefaultValue(t *testing.T) {
t.Parallel()
@@ -819,6 +853,86 @@ func TestSingleNestedAttributeValidateImplementation(t *testing.T) {
},
},
},
+ "writeOnly-with-child-writeOnly-no-error-diagnostic": {
+ attribute: schema.SingleNestedAttribute{
+ WriteOnly: true,
+ Attributes: map[string]schema.Attribute{
+ "test_attr": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ request: fwschema.ValidateImplementationRequest{
+ Name: "test",
+ Path: path.Root("test"),
+ },
+ expected: &fwschema.ValidateImplementationResponse{},
+ },
+ "writeOnly-without-child-writeOnly-error-diagnostic": {
+ attribute: schema.SingleNestedAttribute{
+ WriteOnly: true,
+ Attributes: map[string]schema.Attribute{
+ "test_attr": schema.StringAttribute{
+ Computed: true,
+ },
+ },
+ },
+ request: fwschema.ValidateImplementationRequest{
+ Name: "test",
+ Path: path.Root("test"),
+ },
+ expected: &fwschema.ValidateImplementationResponse{
+ Diagnostics: diag.Diagnostics{
+ diag.NewErrorDiagnostic(
+ "Invalid Schema Implementation",
+ "When validating the schema, an implementation issue was found. "+
+ "This is always an issue with the provider and should be reported to the provider developers.\n\n"+
+ "\"test\" is a WriteOnly nested attribute that contains a non-WriteOnly child attribute.\n\n"+
+ "Every child attribute of a WriteOnly nested attribute must also have WriteOnly set to true.",
+ ),
+ },
+ },
+ },
+ "computed-without-child-writeOnly-no-error-diagnostic": {
+ attribute: schema.SingleNestedAttribute{
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "test_attr": schema.StringAttribute{
+ Computed: true,
+ },
+ },
+ },
+ request: fwschema.ValidateImplementationRequest{
+ Name: "test",
+ Path: path.Root("test"),
+ },
+ expected: &fwschema.ValidateImplementationResponse{},
+ },
+ "computed-with-child-writeOnly-error-diagnostic": {
+ attribute: schema.SingleNestedAttribute{
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "test_attr": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ request: fwschema.ValidateImplementationRequest{
+ Name: "test",
+ Path: path.Root("test"),
+ },
+ expected: &fwschema.ValidateImplementationResponse{
+ Diagnostics: diag.Diagnostics{
+ diag.NewErrorDiagnostic(
+ "Invalid Schema Implementation",
+ "When validating the schema, an implementation issue was found. "+
+ "This is always an issue with the provider and should be reported to the provider developers.\n\n"+
+ "\"test\" is a Computed nested attribute that contains a WriteOnly child attribute.\n\n"+
+ "Every child attribute of a Computed nested attribute must have WriteOnly set to false.",
+ ),
+ },
+ },
+ },
}
for name, testCase := range testCases {
diff --git a/resource/schema/string_attribute.go b/resource/schema/string_attribute.go
index 7e3b8a1c2..693327016 100644
--- a/resource/schema/string_attribute.go
+++ b/resource/schema/string_attribute.go
@@ -152,6 +152,16 @@ type StringAttribute struct {
// computed and the value could be altered by other changes then a default
// should be avoided and a plan modifier should be used instead.
Default defaults.String
+
+ // WriteOnly indicates that Terraform will not store this attribute value
+ // in the plan or state artifacts.
+ // If WriteOnly is true, either Optional or Required must also be true.
+ // WriteOnly cannot be set with Computed.
+ //
+ // This functionality is only supported in Terraform 1.11 and later.
+ // Practitioners that choose a value for this attribute with older
+ // versions of Terraform will receive an error.
+ WriteOnly bool
}
// ApplyTerraform5AttributePathStep always returns an error as it is not
@@ -214,6 +224,11 @@ func (a StringAttribute) IsSensitive() bool {
return a.Sensitive
}
+// IsWriteOnly returns the WriteOnly field value.
+func (a StringAttribute) IsWriteOnly() bool {
+ return a.WriteOnly
+}
+
// StringDefaultValue returns the Default field value.
func (a StringAttribute) StringDefaultValue() defaults.String {
return a.Default
diff --git a/resource/schema/string_attribute_test.go b/resource/schema/string_attribute_test.go
index 65bc15b10..20f5ccf36 100644
--- a/resource/schema/string_attribute_test.go
+++ b/resource/schema/string_attribute_test.go
@@ -397,6 +397,40 @@ func TestStringAttributeIsSensitive(t *testing.T) {
}
}
+func TestStringAttributeIsWriteOnly(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ attribute schema.StringAttribute
+ expected bool
+ }{
+ "not-writeOnly": {
+ attribute: schema.StringAttribute{},
+ expected: false,
+ },
+ "writeOnly": {
+ attribute: schema.StringAttribute{
+ WriteOnly: true,
+ },
+ expected: true,
+ },
+ }
+
+ for name, testCase := range testCases {
+ name, testCase := name, testCase
+
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ got := testCase.attribute.IsWriteOnly()
+
+ if diff := cmp.Diff(got, testCase.expected); diff != "" {
+ t.Errorf("unexpected difference: %s", diff)
+ }
+ })
+ }
+}
+
func TestStringAttributeStringDefaultValue(t *testing.T) {
t.Parallel()
diff --git a/resource/schema/write_only_nested_attribute_validation_test.go b/resource/schema/write_only_nested_attribute_validation_test.go
new file mode 100644
index 000000000..acc2ca1de
--- /dev/null
+++ b/resource/schema/write_only_nested_attribute_validation_test.go
@@ -0,0 +1,1281 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package schema_test
+
+import (
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
+ "github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+)
+
+func TestContainsAllWriteOnlyChildAttributes(t *testing.T) {
+ t.Parallel()
+ tests := map[string]struct {
+ nestedAttr metaschema.NestedAttribute
+ expected bool
+ }{
+ "empty nested attribute returns true": {
+ nestedAttr: schema.ListNestedAttribute{},
+ expected: true,
+ },
+ "list nested attribute with writeOnly child attribute returns true": {
+ nestedAttr: schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "list nested attribute with non-writeOnly child attribute returns false": {
+ nestedAttr: schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "list nested attribute with multiple writeOnly child attributes returns true": {
+ nestedAttr: schema.ListNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "list nested attribute with one non-writeOnly child attribute returns false": {
+ nestedAttr: schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "list nested attribute with writeOnly child nested attributes returns true": {
+ nestedAttr: schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "list_nested_attribute": schema.ListNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "list nested attribute with non-writeOnly child nested attribute returns false": {
+ nestedAttr: schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "list_nested_attribute": schema.ListNestedAttribute{
+ WriteOnly: false,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "list nested attribute with one non-writeOnly child nested attribute returns false": {
+ nestedAttr: schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "list_nested_attribute": schema.ListNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ "set_nested_attribute": schema.SetNestedAttribute{
+ WriteOnly: false,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "list nested attribute with one non-writeOnly nested child attribute returns false": {
+ nestedAttr: schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "list_nested_attribute": schema.ListNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "set nested attribute with writeOnly child attribute returns true": {
+ nestedAttr: schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "set nested attribute with non-writeOnly child attribute returns false": {
+ nestedAttr: schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "set nested attribute with multiple writeOnly child attributes returns true": {
+ nestedAttr: schema.SetNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "set nested attribute with one non-writeOnly child attribute returns false": {
+ nestedAttr: schema.SetNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "set nested attribute with writeOnly child nested attributes returns true": {
+ nestedAttr: schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "set_nested_attribute": schema.SetNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "set nested attribute with non-writeOnly child nested attribute returns false": {
+ nestedAttr: schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "set_nested_attribute": schema.SetNestedAttribute{
+ WriteOnly: false,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "set nested attribute with one non-writeOnly child nested attribute returns false": {
+ nestedAttr: schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "set_nested_attribute": schema.SetNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ "list_nested_attribute": schema.ListNestedAttribute{
+ WriteOnly: false,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "set nested attribute with one non-writeOnly nested child attribute returns false": {
+ nestedAttr: schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "set_nested_attribute": schema.SetNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "map nested attribute with writeOnly child attribute returns true": {
+ nestedAttr: schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "map nested attribute with non-writeOnly child attribute returns false": {
+ nestedAttr: schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "map nested attribute with multiple writeOnly child attributes returns true": {
+ nestedAttr: schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "map nested attribute with one non-writeOnly child attribute returns false": {
+ nestedAttr: schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "map nested attribute with writeOnly child nested attributes returns true": {
+ nestedAttr: schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "map_nested_attribute": schema.MapNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "map nested attribute with non-writeOnly child nested attribute returns false": {
+ nestedAttr: schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "map_nested_attribute": schema.MapNestedAttribute{
+ WriteOnly: false,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "map nested attribute with one non-writeOnly child nested attribute returns false": {
+ nestedAttr: schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "map_nested_attribute": schema.MapNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ "list_nested_attribute": schema.ListNestedAttribute{
+ WriteOnly: false,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "map nested attribute with one non-writeOnly nested child attribute returns false": {
+ nestedAttr: schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "map_nested_attribute": schema.MapNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "single nested attribute with writeOnly child attribute returns true": {
+ nestedAttr: schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ expected: true,
+ },
+ "single nested attribute with non-writeOnly child attribute returns false": {
+ nestedAttr: schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ expected: false,
+ },
+ "single nested attribute with multiple writeOnly child attributes returns true": {
+ nestedAttr: schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ expected: true,
+ },
+ "single nested attribute with one non-writeOnly child attribute returns false": {
+ nestedAttr: schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ expected: false,
+ },
+ "single nested attribute with writeOnly child nested attributes returns true": {
+ nestedAttr: schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "single_nested_attribute": schema.SingleNestedAttribute{
+ WriteOnly: true,
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "single nested attribute with non-writeOnly child nested attribute returns false": {
+ nestedAttr: schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "single_nested_attribute": schema.SingleNestedAttribute{
+ WriteOnly: false,
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "single nested attribute with one non-writeOnly child nested attribute returns false": {
+ nestedAttr: schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "single_nested_attribute": schema.SingleNestedAttribute{
+ WriteOnly: true,
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ "list_nested_attribute": schema.ListNestedAttribute{
+ WriteOnly: false,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "single nested attribute with one non-writeOnly nested child attribute returns false": {
+ nestedAttr: schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "single_nested_attribute": schema.SingleNestedAttribute{
+ WriteOnly: true,
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ }
+ for name, tt := range tests {
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+ if got := fwschema.ContainsAllWriteOnlyChildAttributes(tt.nestedAttr); got != tt.expected {
+ t.Errorf("ContainsAllWriteOnlyChildAttributes() = %v, want %v", got, tt.expected)
+ }
+ })
+ }
+}
+
+func TestContainsAnyWriteOnlyChildAttributes(t *testing.T) {
+ t.Parallel()
+ tests := map[string]struct {
+ nestedAttr metaschema.NestedAttribute
+ expected bool
+ }{
+ "empty nested attribute returns false": {
+ nestedAttr: schema.ListNestedAttribute{},
+ expected: false,
+ },
+ "list nested attribute with writeOnly child attribute returns true": {
+ nestedAttr: schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "list nested attribute with non-writeOnly child attribute returns false": {
+ nestedAttr: schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "list nested attribute with multiple writeOnly child attributes returns true": {
+ nestedAttr: schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "list nested attribute with one non-writeOnly child attribute returns true": {
+ nestedAttr: schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "list nested attribute with writeOnly child nested attributes returns true": {
+ nestedAttr: schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "list_nested_attribute": schema.ListNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "list nested attribute with non-writeOnly child nested attribute returns false": {
+ nestedAttr: schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "list_nested_attribute": schema.ListNestedAttribute{
+ WriteOnly: false,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "list nested attribute with one non-writeOnly child nested attribute returns true": {
+ nestedAttr: schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "list_nested_attribute": schema.ListNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ },
+ },
+ },
+ "set_nested_attribute": schema.SetNestedAttribute{
+ WriteOnly: false,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "list nested attribute with one non-writeOnly nested child attribute returns true": {
+ nestedAttr: schema.ListNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "list_nested_attribute": schema.ListNestedAttribute{
+ WriteOnly: false,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "set nested attribute with writeOnly child attribute returns true": {
+ nestedAttr: schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "set nested attribute with non-writeOnly child attribute returns false": {
+ nestedAttr: schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "set nested attribute with multiple writeOnly child attributes returns true": {
+ nestedAttr: schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "set nested attribute with one non-writeOnly child attribute returns true": {
+ nestedAttr: schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "set nested attribute with writeOnly child nested attributes returns true": {
+ nestedAttr: schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "set_nested_attribute": schema.SetNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "set nested attribute with non-writeOnly child nested attribute returns false": {
+ nestedAttr: schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "set_nested_attribute": schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "set nested attribute with one non-writeOnly child nested attribute returns true": {
+ nestedAttr: schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "set_nested_attribute": schema.SetNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ },
+ },
+ },
+ "list_nested_attribute": schema.ListNestedAttribute{
+ WriteOnly: false,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "set nested attribute with one non-writeOnly nested child attribute returns true": {
+ nestedAttr: schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "set_nested_attribute": schema.SetNestedAttribute{
+ WriteOnly: false,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "map nested attribute with writeOnly child attribute returns true": {
+ nestedAttr: schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "map nested attribute with non-writeOnly child attribute returns false": {
+ nestedAttr: schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "map nested attribute with multiple writeOnly child attributes returns true": {
+ nestedAttr: schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "map nested attribute with one non-writeOnly child attribute returns true": {
+ nestedAttr: schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "map nested attribute with writeOnly child nested attributes returns true": {
+ nestedAttr: schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "map_nested_attribute": schema.MapNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "map nested attribute with non-writeOnly child nested attribute returns false": {
+ nestedAttr: schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "map_nested_attribute": schema.MapNestedAttribute{
+ WriteOnly: false,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "map nested attribute with one non-writeOnly child nested attribute returns true": {
+ nestedAttr: schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "map_nested_attribute": schema.MapNestedAttribute{
+ WriteOnly: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ },
+ },
+ },
+ "list_nested_attribute": schema.ListNestedAttribute{
+ WriteOnly: false,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "map nested attribute with one non-writeOnly nested child attribute returns true": {
+ nestedAttr: schema.MapNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "map_nested_attribute": schema.MapNestedAttribute{
+ WriteOnly: false,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+
+ "single nested attribute with writeOnly child attribute returns true": {
+ nestedAttr: schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ expected: true,
+ },
+ "single nested attribute with non-writeOnly child attribute returns false": {
+ nestedAttr: schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ expected: false,
+ },
+ "single nested attribute with multiple writeOnly child attributes returns true": {
+ nestedAttr: schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ expected: true,
+ },
+ "single nested attribute with one non-writeOnly child attribute returns true": {
+ nestedAttr: schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ },
+ },
+ },
+ expected: true,
+ },
+ "single nested attribute with writeOnly child nested attributes returns true": {
+ nestedAttr: schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "single_nested_attribute": schema.SingleNestedAttribute{
+ WriteOnly: true,
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "single nested attribute with non-writeOnly child nested attribute returns false": {
+ nestedAttr: schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "single_nested_attribute": schema.SingleNestedAttribute{
+ WriteOnly: false,
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ expected: false,
+ },
+ "single nested attribute with one non-writeOnly child nested attribute returns true": {
+ nestedAttr: schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "single_nested_attribute": schema.SingleNestedAttribute{
+ WriteOnly: true,
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ },
+ },
+ "list_nested_attribute": schema.ListNestedAttribute{
+ WriteOnly: false,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: false,
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ "single nested attribute with one non-writeOnly nested child attribute returns true": {
+ nestedAttr: schema.SingleNestedAttribute{
+ Attributes: map[string]schema.Attribute{
+ "single_nested_attribute": schema.SingleNestedAttribute{
+ WriteOnly: false,
+ Attributes: map[string]schema.Attribute{
+ "string_attribute": schema.StringAttribute{
+ WriteOnly: false,
+ },
+ "float32_attribute": schema.Float32Attribute{
+ WriteOnly: true,
+ },
+ },
+ },
+ },
+ },
+ expected: true,
+ },
+ }
+ for name, tt := range tests {
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+ if got := fwschema.ContainsAnyWriteOnlyChildAttributes(tt.nestedAttr); got != tt.expected {
+ t.Errorf("ContainsAllWriteOnlyChildAttributes() = %v, want %v", got, tt.expected)
+ }
+ })
+ }
+}
diff --git a/resource/validate_config.go b/resource/validate_config.go
index 40e1213bf..f35c4aa3f 100644
--- a/resource/validate_config.go
+++ b/resource/validate_config.go
@@ -8,6 +8,17 @@ import (
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
)
+// ValidateConfigClientCapabilities allows Terraform to publish information
+// regarding optionally supported protocol features for the
+// ValidateResourceConfig RPC, such as forward-compatible Terraform behavior
+// changes.
+type ValidateConfigClientCapabilities struct {
+ // WriteOnlyAttributesAllowed indicates that the Terraform client
+ // initiating the request supports write-only attributes for managed
+ // resources.
+ WriteOnlyAttributesAllowed bool
+}
+
// ValidateConfigRequest represents a request to validate the
// configuration of a resource. An instance of this request struct is
// supplied as an argument to the Resource ValidateConfig receiver method
@@ -19,6 +30,11 @@ type ValidateConfigRequest struct {
// interpolation or other functionality that would prevent Terraform
// from knowing the value at request time.
Config tfsdk.Config
+
+ // ClientCapabilities defines optionally supported protocol features for
+ // the ValidateResourceConfig RPC, such as forward-compatible Terraform
+ // behavior changes.
+ ClientCapabilities ValidateConfigClientCapabilities
}
// ValidateConfigResponse represents a response to a
diff --git a/schema/validator/bool.go b/schema/validator/bool.go
index 64115e713..26f6df1a4 100644
--- a/schema/validator/bool.go
+++ b/schema/validator/bool.go
@@ -35,6 +35,11 @@ type BoolRequest struct {
// ConfigValue contains the value of the attribute for validation from the configuration.
ConfigValue types.Bool
+
+ // ClientCapabilities defines optionally supported protocol features for
+ // schema validation RPCs, such as forward-compatible Terraform
+ // behavior changes.
+ ClientCapabilities ValidateSchemaClientCapabilities
}
// BoolResponse is a response to a BoolRequest.
diff --git a/schema/validator/client_capabilities.go b/schema/validator/client_capabilities.go
new file mode 100644
index 000000000..8f0bbe9a0
--- /dev/null
+++ b/schema/validator/client_capabilities.go
@@ -0,0 +1,17 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package validator
+
+// ValidateSchemaClientCapabilities allows Terraform to publish information
+// regarding optionally supported protocol features for the schema validation
+// RPCs, such as forward-compatible Terraform behavior changes.
+type ValidateSchemaClientCapabilities struct {
+ // WriteOnlyAttributesAllowed indicates that the Terraform client
+ // initiating the request supports write-only attributes for managed
+ // resources.
+ //
+ // This client capability is only populated during managed resource schema
+ // validation.
+ WriteOnlyAttributesAllowed bool
+}
diff --git a/schema/validator/dynamic.go b/schema/validator/dynamic.go
index b035175a1..e5d2cb58c 100644
--- a/schema/validator/dynamic.go
+++ b/schema/validator/dynamic.go
@@ -35,6 +35,11 @@ type DynamicRequest struct {
// ConfigValue contains the value of the attribute for validation from the configuration.
ConfigValue types.Dynamic
+
+ // ClientCapabilities defines optionally supported protocol features for
+ // schema validation RPCs, such as forward-compatible Terraform
+ // behavior changes.
+ ClientCapabilities ValidateSchemaClientCapabilities
}
// DynamicResponse is a response to a DynamicRequest.
diff --git a/schema/validator/float32.go b/schema/validator/float32.go
index c1cd8421d..9f38507b2 100644
--- a/schema/validator/float32.go
+++ b/schema/validator/float32.go
@@ -35,6 +35,11 @@ type Float32Request struct {
// ConfigValue contains the value of the attribute for validation from the configuration.
ConfigValue types.Float32
+
+ // ClientCapabilities defines optionally supported protocol features for
+ // schema validation RPCs, such as forward-compatible Terraform
+ // behavior changes.
+ ClientCapabilities ValidateSchemaClientCapabilities
}
// Float32Response is a response to a Float32Request.
diff --git a/schema/validator/float64.go b/schema/validator/float64.go
index f09111ac7..7c788d8f3 100644
--- a/schema/validator/float64.go
+++ b/schema/validator/float64.go
@@ -35,6 +35,11 @@ type Float64Request struct {
// ConfigValue contains the value of the attribute for validation from the configuration.
ConfigValue types.Float64
+
+ // ClientCapabilities defines optionally supported protocol features for
+ // schema validation RPCs, such as forward-compatible Terraform
+ // behavior changes.
+ ClientCapabilities ValidateSchemaClientCapabilities
}
// Float64Response is a response to a Float64Request.
diff --git a/schema/validator/int32.go b/schema/validator/int32.go
index 2cbbc3cc2..d13185226 100644
--- a/schema/validator/int32.go
+++ b/schema/validator/int32.go
@@ -35,6 +35,11 @@ type Int32Request struct {
// ConfigValue contains the value of the attribute for validation from the configuration.
ConfigValue types.Int32
+
+ // ClientCapabilities defines optionally supported protocol features for
+ // schema validation RPCs, such as forward-compatible Terraform
+ // behavior changes.
+ ClientCapabilities ValidateSchemaClientCapabilities
}
// Int32Response is a response to a Int32Request.
diff --git a/schema/validator/int64.go b/schema/validator/int64.go
index 8e8accdcb..061ab1cf8 100644
--- a/schema/validator/int64.go
+++ b/schema/validator/int64.go
@@ -35,6 +35,11 @@ type Int64Request struct {
// ConfigValue contains the value of the attribute for validation from the configuration.
ConfigValue types.Int64
+
+ // ClientCapabilities defines optionally supported protocol features for
+ // schema validation RPCs, such as forward-compatible Terraform
+ // behavior changes.
+ ClientCapabilities ValidateSchemaClientCapabilities
}
// Int64Response is a response to a Int64Request.
diff --git a/schema/validator/list.go b/schema/validator/list.go
index e5b6083d8..e2dc5ecd1 100644
--- a/schema/validator/list.go
+++ b/schema/validator/list.go
@@ -35,6 +35,11 @@ type ListRequest struct {
// ConfigValue contains the value of the attribute for validation from the configuration.
ConfigValue types.List
+
+ // ClientCapabilities defines optionally supported protocol features for
+ // schema validation RPCs, such as forward-compatible Terraform
+ // behavior changes.
+ ClientCapabilities ValidateSchemaClientCapabilities
}
// ListResponse is a response to a ListRequest.
diff --git a/schema/validator/map.go b/schema/validator/map.go
index 2a41cc7ac..eda09a239 100644
--- a/schema/validator/map.go
+++ b/schema/validator/map.go
@@ -35,6 +35,11 @@ type MapRequest struct {
// ConfigValue contains the value of the attribute for validation from the configuration.
ConfigValue types.Map
+
+ // ClientCapabilities defines optionally supported protocol features for
+ // schema validation RPCs, such as forward-compatible Terraform
+ // behavior changes.
+ ClientCapabilities ValidateSchemaClientCapabilities
}
// MapResponse is a response to a MapRequest.
diff --git a/schema/validator/number.go b/schema/validator/number.go
index ef7692c20..2bccf9ac6 100644
--- a/schema/validator/number.go
+++ b/schema/validator/number.go
@@ -35,6 +35,11 @@ type NumberRequest struct {
// ConfigValue contains the value of the attribute for validation from the configuration.
ConfigValue types.Number
+
+ // ClientCapabilities defines optionally supported protocol features for
+ // schema validation RPCs, such as forward-compatible Terraform
+ // behavior changes.
+ ClientCapabilities ValidateSchemaClientCapabilities
}
// NumberResponse is a response to a NumberRequest.
diff --git a/schema/validator/object.go b/schema/validator/object.go
index 88029e0ad..a2d96a24c 100644
--- a/schema/validator/object.go
+++ b/schema/validator/object.go
@@ -35,6 +35,11 @@ type ObjectRequest struct {
// ConfigValue contains the value of the attribute for validation from the configuration.
ConfigValue types.Object
+
+ // ClientCapabilities defines optionally supported protocol features for
+ // schema validation RPCs, such as forward-compatible Terraform
+ // behavior changes.
+ ClientCapabilities ValidateSchemaClientCapabilities
}
// ObjectResponse is a response to a ObjectRequest.
diff --git a/schema/validator/set.go b/schema/validator/set.go
index ce7cdea34..f3aaf0b0f 100644
--- a/schema/validator/set.go
+++ b/schema/validator/set.go
@@ -35,6 +35,11 @@ type SetRequest struct {
// ConfigValue contains the value of the attribute for validation from the configuration.
ConfigValue types.Set
+
+ // ClientCapabilities defines optionally supported protocol features for
+ // schema validation RPCs, such as forward-compatible Terraform
+ // behavior changes.
+ ClientCapabilities ValidateSchemaClientCapabilities
}
// SetResponse is a response to a SetRequest.
diff --git a/schema/validator/string.go b/schema/validator/string.go
index b453a7bfc..4427e2691 100644
--- a/schema/validator/string.go
+++ b/schema/validator/string.go
@@ -35,6 +35,11 @@ type StringRequest struct {
// ConfigValue contains the value of the attribute for validation from the configuration.
ConfigValue types.String
+
+ // ClientCapabilities defines optionally supported protocol features for
+ // schema validation RPCs, such as forward-compatible Terraform
+ // behavior changes.
+ ClientCapabilities ValidateSchemaClientCapabilities
}
// StringResponse is a response to a StringRequest.
diff --git a/website/data/plugin-framework-nav-data.json b/website/data/plugin-framework-nav-data.json
index 2297fad3c..b41e7901f 100644
--- a/website/data/plugin-framework-nav-data.json
+++ b/website/data/plugin-framework-nav-data.json
@@ -98,6 +98,10 @@
{
"title": "Timeouts",
"path": "resources/timeouts"
+ },
+ {
+ "title": "Write-only Arguments",
+ "path": "resources/write-only-arguments"
}
]
},
diff --git a/website/docs/plugin/framework/handling-data/attributes/bool.mdx b/website/docs/plugin/framework/handling-data/attributes/bool.mdx
index 576baff50..b1b7eb2e1 100644
--- a/website/docs/plugin/framework/handling-data/attributes/bool.mdx
+++ b/website/docs/plugin/framework/handling-data/attributes/bool.mdx
@@ -105,6 +105,18 @@ The [`boolplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugi
Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state.
+### WriteOnly
+
+
+
+ Only managed resources implement this concept.
+
+
+
+Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments).
+Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral)
+and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later.
+
### Validation
Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types).
diff --git a/website/docs/plugin/framework/handling-data/attributes/dynamic.mdx b/website/docs/plugin/framework/handling-data/attributes/dynamic.mdx
index 44701cc24..66da060bf 100644
--- a/website/docs/plugin/framework/handling-data/attributes/dynamic.mdx
+++ b/website/docs/plugin/framework/handling-data/attributes/dynamic.mdx
@@ -134,6 +134,18 @@ The [`dynamicplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-pl
Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state.
+### WriteOnly
+
+
+
+ Only managed resources implement this concept.
+
+
+
+Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments).
+Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral)
+and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later.
+
### Validation
Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types).
diff --git a/website/docs/plugin/framework/handling-data/attributes/float32.mdx b/website/docs/plugin/framework/handling-data/attributes/float32.mdx
index 5f710dafe..f0a654718 100644
--- a/website/docs/plugin/framework/handling-data/attributes/float32.mdx
+++ b/website/docs/plugin/framework/handling-data/attributes/float32.mdx
@@ -111,6 +111,18 @@ The [`float32planmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-pl
Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state.
+### WriteOnly
+
+
+
+ Only managed resources implement this concept.
+
+
+
+Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments).
+Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral)
+and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later.
+
### Validation
Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types).
diff --git a/website/docs/plugin/framework/handling-data/attributes/float64.mdx b/website/docs/plugin/framework/handling-data/attributes/float64.mdx
index da31351b6..a43e8b7ab 100644
--- a/website/docs/plugin/framework/handling-data/attributes/float64.mdx
+++ b/website/docs/plugin/framework/handling-data/attributes/float64.mdx
@@ -111,6 +111,18 @@ The [`float64planmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-pl
Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state.
+### WriteOnly
+
+
+
+ Only managed resources implement this concept.
+
+
+
+Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments).
+Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral)
+and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later.
+
### Validation
Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types).
diff --git a/website/docs/plugin/framework/handling-data/attributes/int32.mdx b/website/docs/plugin/framework/handling-data/attributes/int32.mdx
index f0c4ad56c..cd9c443e9 100644
--- a/website/docs/plugin/framework/handling-data/attributes/int32.mdx
+++ b/website/docs/plugin/framework/handling-data/attributes/int32.mdx
@@ -111,6 +111,18 @@ The [`int32planmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plug
Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state.
+### WriteOnly
+
+
+
+ Only managed resources implement this concept.
+
+
+
+Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments).
+Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral)
+and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later.
+
### Validation
Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types).
diff --git a/website/docs/plugin/framework/handling-data/attributes/int64.mdx b/website/docs/plugin/framework/handling-data/attributes/int64.mdx
index 416976e45..6bba8efe4 100644
--- a/website/docs/plugin/framework/handling-data/attributes/int64.mdx
+++ b/website/docs/plugin/framework/handling-data/attributes/int64.mdx
@@ -111,6 +111,18 @@ The [`int64planmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plug
Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state.
+### WriteOnly
+
+
+
+ Only managed resources implement this concept.
+
+
+
+Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments).
+Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral)
+and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later.
+
### Validation
Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types).
diff --git a/website/docs/plugin/framework/handling-data/attributes/list-nested.mdx b/website/docs/plugin/framework/handling-data/attributes/list-nested.mdx
index ea0272133..1a3bd86e6 100644
--- a/website/docs/plugin/framework/handling-data/attributes/list-nested.mdx
+++ b/website/docs/plugin/framework/handling-data/attributes/list-nested.mdx
@@ -159,6 +159,20 @@ The [`listplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugi
Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state.
+### WriteOnly
+
+
+
+ Only managed resources implement this concept.
+
+
+
+Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments).
+Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral)
+and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later.
+
+If a nested attribute has the `WriteOnly` field set, all child attributes must also have `WriteOnly` set.
+
### Validation
Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types).
diff --git a/website/docs/plugin/framework/handling-data/attributes/list.mdx b/website/docs/plugin/framework/handling-data/attributes/list.mdx
index 2104e2475..50ffcf6b5 100644
--- a/website/docs/plugin/framework/handling-data/attributes/list.mdx
+++ b/website/docs/plugin/framework/handling-data/attributes/list.mdx
@@ -128,6 +128,18 @@ The [`listplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugi
Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state.
+### WriteOnly
+
+
+
+ Only managed resources implement this concept.
+
+
+
+Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments).
+Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral)
+and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later.
+
### Validation
Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types).
diff --git a/website/docs/plugin/framework/handling-data/attributes/map-nested.mdx b/website/docs/plugin/framework/handling-data/attributes/map-nested.mdx
index 210f25a9d..53424755a 100644
--- a/website/docs/plugin/framework/handling-data/attributes/map-nested.mdx
+++ b/website/docs/plugin/framework/handling-data/attributes/map-nested.mdx
@@ -159,6 +159,20 @@ The [`mapplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin
Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state.
+### WriteOnly
+
+
+
+ Only managed resources implement this concept.
+
+
+
+Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments).
+Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral)
+and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later.
+
+If a nested attribute has the `WriteOnly` field set, all child attributes must also have `WriteOnly` set.
+
### Validation
Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types).
diff --git a/website/docs/plugin/framework/handling-data/attributes/map.mdx b/website/docs/plugin/framework/handling-data/attributes/map.mdx
index 7eb65aa89..a5b3faea9 100644
--- a/website/docs/plugin/framework/handling-data/attributes/map.mdx
+++ b/website/docs/plugin/framework/handling-data/attributes/map.mdx
@@ -131,6 +131,18 @@ The [`mapplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin
Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state.
+### WriteOnly
+
+
+
+ Only managed resources implement this concept.
+
+
+
+Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments).
+Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral)
+and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later.
+
### Validation
Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types).
diff --git a/website/docs/plugin/framework/handling-data/attributes/number.mdx b/website/docs/plugin/framework/handling-data/attributes/number.mdx
index 4fb48a1ba..270a35070 100644
--- a/website/docs/plugin/framework/handling-data/attributes/number.mdx
+++ b/website/docs/plugin/framework/handling-data/attributes/number.mdx
@@ -111,6 +111,18 @@ The [`numberplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plu
Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state.
+### WriteOnly
+
+
+
+ Only managed resources implement this concept.
+
+
+
+Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments).
+Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral)
+and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later.
+
### Validation
Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types).
diff --git a/website/docs/plugin/framework/handling-data/attributes/object.mdx b/website/docs/plugin/framework/handling-data/attributes/object.mdx
index d7f2c837d..de7b56083 100644
--- a/website/docs/plugin/framework/handling-data/attributes/object.mdx
+++ b/website/docs/plugin/framework/handling-data/attributes/object.mdx
@@ -165,6 +165,18 @@ Only the object attribute itself, not individual sub-attributes, can define its
Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state.
+### WriteOnly
+
+
+
+ Only managed resources implement this concept.
+
+
+
+Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments).
+Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral)
+and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later.
+
### Validation
Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types).
diff --git a/website/docs/plugin/framework/handling-data/attributes/set-nested.mdx b/website/docs/plugin/framework/handling-data/attributes/set-nested.mdx
index 01ce7e51f..9e4f8afe9 100644
--- a/website/docs/plugin/framework/handling-data/attributes/set-nested.mdx
+++ b/website/docs/plugin/framework/handling-data/attributes/set-nested.mdx
@@ -159,6 +159,20 @@ The [`setplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin
Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state.
+### WriteOnly
+
+
+
+ Only managed resources implement this concept.
+
+
+
+Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments).
+Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral)
+and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later.
+
+If a nested attribute has the `WriteOnly` field set, all child attributes must also have `WriteOnly` set.
+
### Validation
Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types).
diff --git a/website/docs/plugin/framework/handling-data/attributes/set.mdx b/website/docs/plugin/framework/handling-data/attributes/set.mdx
index 512489aad..9f3d7dcd3 100644
--- a/website/docs/plugin/framework/handling-data/attributes/set.mdx
+++ b/website/docs/plugin/framework/handling-data/attributes/set.mdx
@@ -128,6 +128,18 @@ The [`setplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin
Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state.
+### WriteOnly
+
+
+
+ Only managed resources implement this concept.
+
+
+
+Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments).
+Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral)
+and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later.
+
### Validation
Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types).
diff --git a/website/docs/plugin/framework/handling-data/attributes/single-nested.mdx b/website/docs/plugin/framework/handling-data/attributes/single-nested.mdx
index 56675912f..24e52a883 100644
--- a/website/docs/plugin/framework/handling-data/attributes/single-nested.mdx
+++ b/website/docs/plugin/framework/handling-data/attributes/single-nested.mdx
@@ -155,6 +155,20 @@ The [`objectplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plu
Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state.
+### WriteOnly
+
+
+
+ Only managed resources implement this concept.
+
+
+
+Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments).
+Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral)
+and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later.
+
+If a nested attribute has the `WriteOnly` field set, all child attributes must also have `WriteOnly` set.
+
### Validation
Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types).
diff --git a/website/docs/plugin/framework/handling-data/attributes/string.mdx b/website/docs/plugin/framework/handling-data/attributes/string.mdx
index d423109dd..6d414dbff 100644
--- a/website/docs/plugin/framework/handling-data/attributes/string.mdx
+++ b/website/docs/plugin/framework/handling-data/attributes/string.mdx
@@ -112,6 +112,18 @@ The [`stringplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plu
Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state.
+### WriteOnly
+
+
+
+ Only managed resources implement this concept.
+
+
+
+Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments).
+Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral)
+and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later.
+
### Validation
Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types).
diff --git a/website/docs/plugin/framework/resources/index.mdx b/website/docs/plugin/framework/resources/index.mdx
index 61d8b5798..6aa9000c8 100644
--- a/website/docs/plugin/framework/resources/index.mdx
+++ b/website/docs/plugin/framework/resources/index.mdx
@@ -30,6 +30,7 @@ Further documentation is available for deeper resource concepts:
- [Upgrade state](/terraform/plugin/framework/resources/state-upgrade) to transparently update state data outside plans.
- [Validate](/terraform/plugin/framework/resources/validate-configuration) practitioner configuration against acceptable values.
- [Timeouts](/terraform/plugin/framework/resources/timeouts) in practitioner configuration for use in resource create, read, update and delete functions.
+- [Write-only Arguments](/terraform/plugin/framework/resources/write-only-arguments) are special types of attributes that can accept [ephemeral values](/terraform/language/resources/ephemeral) and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later.
## Define Resource Type
diff --git a/website/docs/plugin/framework/resources/write-only-arguments.mdx b/website/docs/plugin/framework/resources/write-only-arguments.mdx
new file mode 100644
index 000000000..b418e34f0
--- /dev/null
+++ b/website/docs/plugin/framework/resources/write-only-arguments.mdx
@@ -0,0 +1,112 @@
+---
+page_title: 'Plugin Development - Framework: Write-only Arguments'
+description: >-
+ How to implement write-only arguments with the provider development framework.
+---
+
+# Write-only Arguments
+
+Write-only arguments are managed resource attributes that are configured by practitioners but are not persisted to the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later.
+Write-only arguments should be used to handle secret values that do not need to be persisted in Terraform state, such as passwords, API keys, etc.
+The provider is expected to be the terminal point for an ephemeral value,
+which should either use the value by making the appropriate change to the API or ignore the value. Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) and are not required to be consistent between plan and apply operations.
+
+## General Concepts
+
+The following are high level differences between `Required`/`Optional` arguments and write-only arguments:
+
+- Write-only arguments can accept ephemeral and non-ephemeral values
+
+- Write-only argument values are only available in the configuration. The prior state, planned state, and final state values for
+write-only arguments should always be `null`.
+ - Provider developers do not need to explicitly set write-only argument values to `null` after using them as the plugin framework will handle the nullification of write-only arguments for all RPCs.
+
+- Any value that is set for a write-only argument in the state or plan (during [Plan Modification](/terraform/plugin/framework/resources/plan-modification)) by the provider will be reverted to `null` by plugin framework before the RPC response is sent to Terraform.
+
+- Write-only argument values cannot produce a Terraform plan difference.
+ - This is because the prior state value for a write-only argument will always be `null` and the planned/final state value will also be `null`, therefore, it cannot produce a diff on its own.
+ - The one exception to this case is if the write-only argument is added to `requires_replace` during Plan Modification (i.e., using the [`RequiresReplace()`](/terraform/plugin/framework/resources/plan-modification#requiresreplace) plan modifier), in that case, the write-only argument will always cause a diff/trigger a resource recreation
+
+- Since write-only arguments can accept ephemeral values, write-only argument configuration values are not expected to be consistent between plan and apply.
+
+## Schema
+
+An attribute can be made write-only by setting the `WriteOnly` field to `true` in the schema. Attributes with `WriteOnly` set to `true` must also have
+one of `Required` or `Optional` set to `true`. If a nested attribute has `WriteOnly` set to `true`, all child attributes must also have `WriteOnly` set to `true`.
+`Computed` cannot be set to true for write-only arguments.
+
+**Schema example:**
+
+```go
+"password_wo": schema.StringAttribute{
+ Required: true,
+ WriteOnly: true,
+},
+```
+
+## Retrieving Write-only Values
+
+Write-only argument values should be retrieved from the configuration instead of the plan. Refer to [accessing values](/terraform/plugin/framework/handling-data/accessing-values) for more details on
+retrieving values from configuration.
+
+## PreferWriteOnlyAttribute Validators
+
+The `PreferWriteOnlyAttribute()` validators available in the [`terraform-plugin-framework-validators` Go module](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators)
+can be used when you have a write-only version of an existing attribute, and you want to encourage practitioners to use the write-only version whenever possible.
+
+The validator returns a warning if the Terraform client is 1.11 or above and the value of the existing attribute is non-null.
+
+`PreferWriteOnlyAttribute()` is available as a resource-level validator in the [`resourcevalidator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator) or
+as an attribute-level validator in the `[type]validator` packages (i.e., [`stringvalidator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator))
+
+Usage:
+
+```go
+// Resource-level validator
+// Used inside a resource.Resource type ConfigValidators method
+ _ = []resource.ConfigValidator{
+ // Throws a warning diagnostic encouraging practitioners to use
+ // password_wo if password has a known value
+ resourcevalidator.PreferWriteOnlyAttribute(
+ path.MatchRoot("password"),
+ path.MatchRoot("password_wo"),
+ ),
+ }
+
+// Attribute-level validator
+// Used within a Schema method of a Resource
+ _ = schema.Schema{
+ Attributes: map[string]schema.Attribute{
+ "password": schema.StringAttribute{
+ Optional: true,
+ Validators: []validator.String{
+ // Throws a warning diagnostic encouraging practitioners to use
+ // password_wo if password has a known value.
+ stringvalidator.PreferWriteOnlyAttribute(
+ path.MatchRoot("password_wo"),
+ ),
+ },
+ },
+ "password_wo": schema.StringAttribute{
+ WriteOnly: true,
+ Optional: true,
+ },
+ },
+ }
+```
+
+```hcl
+resource "example_db_instance" "ex" {
+ username = "foo"
+ password = "bar" # returns a warning encouraging practitioners to use `password_wo` instead.
+}
+```
+
+## Best Practices
+
+Since write-only arguments have no prior values, user intent or value changes cannot be determined with a write-only argument alone. To determine when to use/not use a write-only argument value in your provider, we recommend one of the following:
+
+- Pair write-only arguments with a configuration attribute (required or optional) to “trigger” the use of the write-only argument
+ - For example, a `password_wo` write-only argument can be paired with a configured `password_wo_version` attribute. When the `password_wo_version` is modified, the provider will send the `password_wo` value to the API.
+- Use a keepers attribute (which is used in the [Random Provider](https://registry.terraform.io/providers/hashicorp/random/latest/docs#resource-keepers)) that will take in arbitrary key-pair values. Whenever there is a change to the `keepers` attribute, the provider will use the write-only argument value.
+- Use the resource's [private state] to store secure hashes of write-only argument values, the provider will then use the hash to determine if a write-only argument value has changed in later Terraform runs.
\ No newline at end of file