Skip to content

Commit

Permalink
Add support for write only attributes (#1044)
Browse files Browse the repository at this point in the history
* initial ephemeral resource interfaces

* add ephemeral resource configure data

* attribute implementations

* uncomment custom type tests

* added block implementations

* add nested attribute implementations

* add schema test

* remove todo

* doc updates, renames, removals

* initial protov5 + fwserver implementation (protov6 stubbed)

* add fromproto5 tests

* add toproto5 tests

* add proto5server tests

* implement protov6

* schema + metadata tests

* add close proto5/6 tests

* add fwserver tests for schema/metadata

* prevent random false positives

* validate fwserver tests

* open/renew/close fwserver tests

* update error message

* update plugin go

* Update `terraform-plugin-go` dependency

* remove `config` from renew

* Implement write only attributes in the `resource/schema` package

* Implement write only attributes in the `datasource/schema` package

* Implement write only attributes in the `provider/schema` and `provider/metaschema` packages

* Implement write only attributes in the `internal/testing/testschema` package

* Update `terraform-plugin-go` dependency

* Implement write only attributes in the `ephemeral/schema` package

* Populate writeOnly fields in `internal/toproto5` and `internal/toproto6`

* Implement `ValidateResourceConfigClientCapabilities` in the `ValidateResourceConfig` RPC

* Add attribute validation for write only attributes

* Initial `RequiredWriteOnlyNilsAttributePaths()` implementation

* Complete `RequiredWriteOnlyNilsAttributePaths()` implementation

* Implement `validator.ValidateSchemaClientCapabilities`

* Implement automatic write-only value nullification during `ApplyResourceState` RPC

* Explicitly set `ValidateSchemaClientCapabilities` during `ValidateDataSourceConfig`, `ValidateEphemeralResourceConfig`, and `ValidateProviderResourceConfig` RPCs

* Nullify write-only attributes during Plan and Apply regardless of client capability

* remove apply client capability

* add validation for older terraform client versions

* add client capabilities to nested attribute validation

* Update wording of `IsWriteOnly` comment for `ephemeral/schema`, `provider/schema`, and `provider/metaschema`

* Update various comments for wording

* Update test cases

* Update wording for `write-only` attribute validation errors

* Refactor write_only_nested_attribute_validation.go and write_only_nested_attribute_validation_test.go

* Move `Required` + `WriteOnly` validations from `PlanResourceChange` RPC to `ValidateResourceConfig` RPC

* Add write-only value nullification to `ReadResource`, `ImportResourceState`, `UpgradeResourceState`, and `MoveResourceState` RPCs

* Add missing `IsWriteOnly()` unit tests for `ephemeral/schema` package

* Add godoc comment to `NullifyWriteOnlyAttributes()`

* Add testing for nested types for `NullifyWriteOnlyAttributes()`

* Add website documentation

* Add recommendation to use private state to store hashes

* Add changelog entries

---------

Co-authored-by: Austin Valle <[email protected]>
  • Loading branch information
SBGoods and austinvalle authored Feb 10, 2025
1 parent 3c0bf49 commit e1e6866
Show file tree
Hide file tree
Showing 277 changed files with 9,882 additions and 204 deletions.
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20250206-114700.yaml
Original file line number Diff line number Diff line change
@@ -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"
5 changes: 5 additions & 0 deletions .changes/unreleased/NOTES-20250206-114436.yaml
Original file line number Diff line number Diff line change
@@ -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"
8 changes: 7 additions & 1 deletion datasource/schema/bool_attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
31 changes: 30 additions & 1 deletion datasource/schema/bool_attribute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ 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"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
"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) {
Expand Down Expand Up @@ -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)
}
})
}
}
8 changes: 7 additions & 1 deletion datasource/schema/dynamic_attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
31 changes: 30 additions & 1 deletion datasource/schema/dynamic_attribute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ 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"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
"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) {
Expand Down Expand Up @@ -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()

Expand Down
5 changes: 5 additions & 0 deletions datasource/schema/float32_attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
28 changes: 28 additions & 0 deletions datasource/schema/float32_attribute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
}
}
8 changes: 7 additions & 1 deletion datasource/schema/float64_attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
31 changes: 30 additions & 1 deletion datasource/schema/float64_attribute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ 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"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
"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) {
Expand Down Expand Up @@ -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)
}
})
}
}
5 changes: 5 additions & 0 deletions datasource/schema/int32_attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
28 changes: 28 additions & 0 deletions datasource/schema/int32_attribute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
}
}
8 changes: 7 additions & 1 deletion datasource/schema/int64_attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
31 changes: 30 additions & 1 deletion datasource/schema/int64_attribute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ 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"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
"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) {
Expand Down Expand Up @@ -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)
}
})
}
}
Loading

0 comments on commit e1e6866

Please sign in to comment.