Skip to content

Commit

Permalink
all: Add support for write-only attributes (#1375)
Browse files Browse the repository at this point in the history
* Update `terraform-plugin-go` dependency

* Add `WriteOnly` attribute to schema and internal schema validation.

* Add `WriteOnly` validation for data source, provider, and provider meta schemas.

* Add WriteOnly capabilities validation to `ValidateResourceTypeConfig` RPC

* Skip value validation for `Required` + `WriteOnly` attributes.

* Fix intermittent test failures for `hasWriteOnly()`

* Validate non-null values for `Required` and `WriteOnly` attributes in `PlanResourceChange()`

* Add initial implementation for `PreferWriteOnlyAttribute()` validator

* Finish `PreferWriteOnlyAttribute()` validator implementation.

* Move `schema.ValidateResourceConfigFuncs` to `schema.Resource` and implement validation in `ValidateResourceTypeConfig()` RPC

* Add automatic state handling for writeOnly attributes

* Apply suggestions from code review

Co-authored-by: Austin Valle <[email protected]>

* Wrap `setWriteOnlyNullValues` call in client capabilities check

* Refactor tests to match diag summary changes

* Move write-only helper functions and tests to their own files.

* Refactor test attribute names for clarity

* Refactor `validateWriteOnlyNullValues()` to build an attribute path.`

* Refactor `validateWriteOnlyRequiredValues()` to build an attribute path.`

* Refactor field and function names based on PR feedback.

* Add clarifying comments.

* Add internal validation preventing data sources from defining `ValidateRawResourceConfigFuncs`

* Change `writeOnlyAttributeName` parameter to use `cty.Path`

* Simplify validation condition logic

* run `go mod tidy`

* update `terraform-plugin-go` dependency

* Add write-only support to `ProtoToConfigSchema()`

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

* Introduce `(*ResourceData).GetRawWriteOnly()` and `(*ResourceData).GetWriteOnly()` for retrieving write-only values during apply

* Revert "Introduce `(*ResourceData).GetRawWriteOnly()` and `(*ResourceData).GetWriteOnly()` for retrieving write-only values during apply"

This reverts commit 1479d62.

* Introduce `(*ResourceData).GetRawConfigAt()` helper method for retrieving write-only attributes during apply.

* null out write-only values

* Return `diag.Diagnostics` instead of error for `(*ResourceData).GetRawConfigAt()`

* Update `terraform-plugin-go` dependency

* Add additional tests for automatic write-only value nullification

* Resolve linting errors and add copyright headers

* Remove "incorrect" test case

* Use `cty.DynamicVal` as default value for `GetRawConfigAt()`

* Throw validation error for computed blocks with write-only attributes

* add `GetRawConfigAt` to `ResourceDiff` for usage in `CustomizeDiff` functions

* unit tests for `ResourceDiff`

* Add validation error for `WriteOnly` and `ForceNew`

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

* Move `Required` + `WriteOnly` attribute validation to `ValidateResourceTypeConfig` RPC

* Add website documentation

* Add changelog entries

* Update `terraform-plugin-go` dependency to `v0.24.0`

* Replace fully qualified links with relative links in website documentation

* Add more test cases for `GetRawConfigAt()`

* Add link to ephemeral resource documentation

* Apply suggestions from code review

Co-authored-by: Austin Valle <[email protected]>

* Apply suggestions from code review

Co-authored-by: Austin Valle <[email protected]>

* Fix write-only attribute error assertions

* Prevent `WriteOnly` from being used with `Default` and `DefaultFunc`.

* Update error messaging

* Add null value test case

* Rename "write-only attributes" to "write-only arguments" in website documentation

* Add configuration examples to `cty.Path` documentation

* Add changelog entry for `ValidateRawResourceConfigFuncs`

---------

Co-authored-by: Austin Valle <[email protected]>
  • Loading branch information
SBGoods and austinvalle authored Feb 3, 2025
1 parent 160f3e6 commit 19e5b30
Show file tree
Hide file tree
Showing 31 changed files with 5,970 additions and 524 deletions.
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20250121-165644.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'helper/schema: Added `WriteOnly` schema behavior 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-01-21T16:56:44.038893-05:00
custom:
Issue: "1375"
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20250121-170105.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'helper/validation: Added `PreferWriteOnlyAttribute()` validator that warns practitioners when a write-only version of
a configured attribute is available.'
time: 2025-01-21T17:01:05.40229-05:00
custom:
Issue: "1375"
5 changes: 5 additions & 0 deletions .changes/unreleased/FEATURES-20250203-151933.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: FEATURES
body: 'schema/resource: Added `ValidateRawResourceConfigFuncs` field which allows resources to define validation logic during the `ValidateResourceTypeConfig` RPC.'
time: 2025-02-03T15:19:33.669857-05:00
custom:
Issue: "1375"
5 changes: 5 additions & 0 deletions .changes/unreleased/NOTES-20250121-170545.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-01-21T17:05:45.398836-05:00
custom:
Issue: "1375"
1 change: 1 addition & 0 deletions helper/schema/core_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ func (s *Schema) coreConfigSchemaAttribute() *configschema.Attribute {
Description: desc,
DescriptionKind: descKind,
Deprecated: s.Deprecated != "",
WriteOnly: s.WriteOnly,
}
}

Expand Down
19 changes: 19 additions & 0 deletions helper/schema/core_schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,25 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
BlockTypes: map[string]*configschema.NestedBlock{},
}),
},
"write-only": {
map[string]*Schema{
"string": {
Type: TypeString,
Optional: true,
WriteOnly: true,
},
},
testResource(&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"string": {
Type: cty.String,
Optional: true,
WriteOnly: true,
},
},
BlockTypes: map[string]*configschema.NestedBlock{},
}),
},
}

for name, test := range tests {
Expand Down
38 changes: 38 additions & 0 deletions helper/schema/grpc_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,32 @@ func (s *GRPCProviderServer) ValidateResourceTypeConfig(ctx context.Context, req
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, err)
return resp, nil
}
if req.ClientCapabilities == nil || !req.ClientCapabilities.WriteOnlyAttributesAllowed {
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, validateWriteOnlyNullValues(configVal, schemaBlock, cty.Path{}))
}

r := s.provider.ResourcesMap[req.TypeName]

// Calling all ValidateRawResourceConfigFunc here since they validate on the raw go-cty config value
// and were introduced after the public provider.ValidateResource method.
if r.ValidateRawResourceConfigFuncs != nil {
writeOnlyAllowed := false

if req.ClientCapabilities != nil {
writeOnlyAllowed = req.ClientCapabilities.WriteOnlyAttributesAllowed
}

validateReq := ValidateResourceConfigFuncRequest{
WriteOnlyAttributesAllowed: writeOnlyAllowed,
RawConfig: configVal,
}

for _, validateFunc := range r.ValidateRawResourceConfigFuncs {
validateResp := &ValidateResourceConfigFuncResponse{}
validateFunc(ctx, validateReq, validateResp)
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, validateResp.Diagnostics)
}
}

config := terraform.NewResourceConfigShimmed(configVal, schemaBlock)

Expand Down Expand Up @@ -394,6 +420,9 @@ func (s *GRPCProviderServer) UpgradeResourceState(ctx context.Context, req *tfpr
// Normalize the value and fill in any missing blocks.
val = objchange.NormalizeObjectFromLegacySDK(val, schemaBlock)

// Set any write-only attribute values to null
val = setWriteOnlyNullValues(val, schemaBlock)

// encode the final state to the expected msgpack format
newStateMP, err := msgpack.Marshal(val, schemaBlock.ImpliedType())
if err != nil {
Expand Down Expand Up @@ -738,6 +767,7 @@ func (s *GRPCProviderServer) ReadResource(ctx context.Context, req *tfprotov5.Re

newStateVal = normalizeNullValues(newStateVal, stateVal, false)
newStateVal = copyTimeoutValues(newStateVal, stateVal)
newStateVal = setWriteOnlyNullValues(newStateVal, schemaBlock)

newStateMP, err := msgpack.Marshal(newStateVal, schemaBlock.ImpliedType())
if err != nil {
Expand Down Expand Up @@ -937,6 +967,9 @@ func (s *GRPCProviderServer) PlanResourceChange(ctx context.Context, req *tfprot
plannedStateVal = SetUnknowns(plannedStateVal, schemaBlock)
}

// Set any write-only attribute values to null
plannedStateVal = setWriteOnlyNullValues(plannedStateVal, schemaBlock)

plannedMP, err := msgpack.Marshal(plannedStateVal, schemaBlock.ImpliedType())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, err)
Expand Down Expand Up @@ -1184,6 +1217,8 @@ func (s *GRPCProviderServer) ApplyResourceChange(ctx context.Context, req *tfpro

newStateVal = copyTimeoutValues(newStateVal, plannedStateVal)

newStateVal = setWriteOnlyNullValues(newStateVal, schemaBlock)

newStateMP, err := msgpack.Marshal(newStateVal, schemaBlock.ImpliedType())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, err)
Expand Down Expand Up @@ -1305,6 +1340,9 @@ func (s *GRPCProviderServer) ImportResourceState(ctx context.Context, req *tfpro
newStateVal = cty.ObjectVal(newStateValueMap)
}

// Set any write-only attribute values to null
newStateVal = setWriteOnlyNullValues(newStateVal, schemaBlock)

newStateMP, err := msgpack.Marshal(newStateVal, schemaBlock.ImpliedType())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, err)
Expand Down
Loading

0 comments on commit 19e5b30

Please sign in to comment.