Skip to content

Commit 19e5b30

Browse files
SBGoodsaustinvalle
andauthored
all: Add support for write-only attributes (#1375)
* 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]>
1 parent 160f3e6 commit 19e5b30

31 files changed

+5970
-524
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
kind: FEATURES
2+
body: 'helper/schema: Added `WriteOnly` schema behavior for managed resource schemas to indicate a write-only attribute.
3+
Write-only attribute values are not saved to the Terraform plan or state artifacts.'
4+
time: 2025-01-21T16:56:44.038893-05:00
5+
custom:
6+
Issue: "1375"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
kind: FEATURES
2+
body: 'helper/validation: Added `PreferWriteOnlyAttribute()` validator that warns practitioners when a write-only version of
3+
a configured attribute is available.'
4+
time: 2025-01-21T17:01:05.40229-05:00
5+
custom:
6+
Issue: "1375"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: FEATURES
2+
body: 'schema/resource: Added `ValidateRawResourceConfigFuncs` field which allows resources to define validation logic during the `ValidateResourceTypeConfig` RPC.'
3+
time: 2025-02-03T15:19:33.669857-05:00
4+
custom:
5+
Issue: "1375"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: NOTES
2+
body: Write-only attribute support is in technical preview and offered without compatibility promises until Terraform 1.11 is generally available.
3+
time: 2025-01-21T17:05:45.398836-05:00
4+
custom:
5+
Issue: "1375"

helper/schema/core_schema.go

+1
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ func (s *Schema) coreConfigSchemaAttribute() *configschema.Attribute {
167167
Description: desc,
168168
DescriptionKind: descKind,
169169
Deprecated: s.Deprecated != "",
170+
WriteOnly: s.WriteOnly,
170171
}
171172
}
172173

helper/schema/core_schema_test.go

+19
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,25 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
458458
BlockTypes: map[string]*configschema.NestedBlock{},
459459
}),
460460
},
461+
"write-only": {
462+
map[string]*Schema{
463+
"string": {
464+
Type: TypeString,
465+
Optional: true,
466+
WriteOnly: true,
467+
},
468+
},
469+
testResource(&configschema.Block{
470+
Attributes: map[string]*configschema.Attribute{
471+
"string": {
472+
Type: cty.String,
473+
Optional: true,
474+
WriteOnly: true,
475+
},
476+
},
477+
BlockTypes: map[string]*configschema.NestedBlock{},
478+
}),
479+
},
461480
}
462481

463482
for name, test := range tests {

helper/schema/grpc_provider.go

+38
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,32 @@ func (s *GRPCProviderServer) ValidateResourceTypeConfig(ctx context.Context, req
283283
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, err)
284284
return resp, nil
285285
}
286+
if req.ClientCapabilities == nil || !req.ClientCapabilities.WriteOnlyAttributesAllowed {
287+
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, validateWriteOnlyNullValues(configVal, schemaBlock, cty.Path{}))
288+
}
289+
290+
r := s.provider.ResourcesMap[req.TypeName]
291+
292+
// Calling all ValidateRawResourceConfigFunc here since they validate on the raw go-cty config value
293+
// and were introduced after the public provider.ValidateResource method.
294+
if r.ValidateRawResourceConfigFuncs != nil {
295+
writeOnlyAllowed := false
296+
297+
if req.ClientCapabilities != nil {
298+
writeOnlyAllowed = req.ClientCapabilities.WriteOnlyAttributesAllowed
299+
}
300+
301+
validateReq := ValidateResourceConfigFuncRequest{
302+
WriteOnlyAttributesAllowed: writeOnlyAllowed,
303+
RawConfig: configVal,
304+
}
305+
306+
for _, validateFunc := range r.ValidateRawResourceConfigFuncs {
307+
validateResp := &ValidateResourceConfigFuncResponse{}
308+
validateFunc(ctx, validateReq, validateResp)
309+
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, validateResp.Diagnostics)
310+
}
311+
}
286312

287313
config := terraform.NewResourceConfigShimmed(configVal, schemaBlock)
288314

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

423+
// Set any write-only attribute values to null
424+
val = setWriteOnlyNullValues(val, schemaBlock)
425+
397426
// encode the final state to the expected msgpack format
398427
newStateMP, err := msgpack.Marshal(val, schemaBlock.ImpliedType())
399428
if err != nil {
@@ -738,6 +767,7 @@ func (s *GRPCProviderServer) ReadResource(ctx context.Context, req *tfprotov5.Re
738767

739768
newStateVal = normalizeNullValues(newStateVal, stateVal, false)
740769
newStateVal = copyTimeoutValues(newStateVal, stateVal)
770+
newStateVal = setWriteOnlyNullValues(newStateVal, schemaBlock)
741771

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

970+
// Set any write-only attribute values to null
971+
plannedStateVal = setWriteOnlyNullValues(plannedStateVal, schemaBlock)
972+
940973
plannedMP, err := msgpack.Marshal(plannedStateVal, schemaBlock.ImpliedType())
941974
if err != nil {
942975
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, err)
@@ -1184,6 +1217,8 @@ func (s *GRPCProviderServer) ApplyResourceChange(ctx context.Context, req *tfpro
11841217

11851218
newStateVal = copyTimeoutValues(newStateVal, plannedStateVal)
11861219

1220+
newStateVal = setWriteOnlyNullValues(newStateVal, schemaBlock)
1221+
11871222
newStateMP, err := msgpack.Marshal(newStateVal, schemaBlock.ImpliedType())
11881223
if err != nil {
11891224
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, err)
@@ -1305,6 +1340,9 @@ func (s *GRPCProviderServer) ImportResourceState(ctx context.Context, req *tfpro
13051340
newStateVal = cty.ObjectVal(newStateValueMap)
13061341
}
13071342

1343+
// Set any write-only attribute values to null
1344+
newStateVal = setWriteOnlyNullValues(newStateVal, schemaBlock)
1345+
13081346
newStateMP, err := msgpack.Marshal(newStateVal, schemaBlock.ImpliedType())
13091347
if err != nil {
13101348
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, err)

0 commit comments

Comments
 (0)