diff --git a/internal/fwschema/write_only_nested_attribute_validation.go b/internal/fwschema/write_only_nested_attribute_validation.go index 4cba87413..bb4812cb2 100644 --- a/internal/fwschema/write_only_nested_attribute_validation.go +++ b/internal/fwschema/write_only_nested_attribute_validation.go @@ -52,6 +52,34 @@ func ContainsAnyWriteOnlyChildAttributes(nestedAttr NestedAttribute) bool { return false } +// BlockContainsAnyWriteOnlyChildAttributes will return true if any child attribute for the +// given nested block has WriteOnly set to true. +func BlockContainsAnyWriteOnlyChildAttributes(block Block) bool { + nestedObjAttrs := block.GetNestedObject().GetAttributes() + nestedObjBlocks := block.GetNestedObject().GetBlocks() + + for _, childAttr := range nestedObjAttrs { + if childAttr.IsWriteOnly() { + return true + } + + nestedAttribute, ok := childAttr.(NestedAttribute) + if ok { + if ContainsAnyWriteOnlyChildAttributes(nestedAttribute) { + return true + } + } + } + + for _, childBlock := range nestedObjBlocks { + if BlockContainsAnyWriteOnlyChildAttributes(childBlock) { + return true + } + } + + return false +} + func InvalidWriteOnlyNestedAttributeDiag(attributePath path.Path) diag.Diagnostic { return diag.NewErrorDiagnostic( "Invalid Schema Implementation", @@ -62,6 +90,26 @@ func InvalidWriteOnlyNestedAttributeDiag(attributePath path.Path) diag.Diagnosti ) } +func InvalidSetNestedAttributeWithWriteOnlyDiag(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 set nested attribute that contains a WriteOnly child attribute.\n\n", attributePath)+ + "Every child attribute of a set nested attribute must have WriteOnly set to false.", + ) +} + +func SetBlockCollectionWithWriteOnlyDiag(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 set nested block that contains a WriteOnly child attribute.\n\n", attributePath)+ + "Every child attribute within a set nested block must have WriteOnly set to false.", + ) +} + func InvalidComputedNestedAttributeWithWriteOnlyDiag(attributePath path.Path) diag.Diagnostic { return diag.NewErrorDiagnostic( "Invalid Schema Implementation", diff --git a/internal/fwserver/write_only_nullification_test.go b/internal/fwserver/write_only_nullification_test.go index c73851772..47ca9780f 100644 --- a/internal/fwserver/write_only_nullification_test.go +++ b/internal/fwserver/write_only_nullification_test.go @@ -539,93 +539,6 @@ func TestNullifyWriteOnlyAttributes_NestedTypes(t *testing.T) { 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{ @@ -788,92 +701,6 @@ func TestNullifyWriteOnlyAttributes_NestedTypes(t *testing.T) { }, }, }, - "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{ @@ -1053,74 +880,6 @@ func TestNullifyWriteOnlyAttributes_NestedTypes(t *testing.T) { }), }), }), - "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, @@ -1257,94 +1016,6 @@ func TestNullifyWriteOnlyAttributes_NestedTypes(t *testing.T) { }), }), }), - "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{ @@ -1450,45 +1121,6 @@ func TestNullifyWriteOnlyAttributes_NestedTypes(t *testing.T) { }, }, }, 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, @@ -1609,84 +1241,6 @@ func TestNullifyWriteOnlyAttributes_NestedTypes(t *testing.T) { }), }), }), - "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 { diff --git a/resource/schema/set_attribute.go b/resource/schema/set_attribute.go index 843f58fb3..b65656dac 100644 --- a/resource/schema/set_attribute.go +++ b/resource/schema/set_attribute.go @@ -166,16 +166,6 @@ 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 @@ -240,9 +230,9 @@ func (a SetAttribute) IsSensitive() bool { return a.Sensitive } -// IsWriteOnly returns the WriteOnly field value. +// IsWriteOnly returns false as write-only attributes are not supported for sets and set-based data. func (a SetAttribute) IsWriteOnly() bool { - return a.WriteOnly + return false } // SetDefaultValue returns the Default field value. diff --git a/resource/schema/set_attribute_test.go b/resource/schema/set_attribute_test.go index ebc7f291b..2795fbde8 100644 --- a/resource/schema/set_attribute_test.go +++ b/resource/schema/set_attribute_test.go @@ -394,12 +394,6 @@ func TestSetAttributeIsWriteOnly(t *testing.T) { attribute: schema.SetAttribute{}, expected: false, }, - "writeOnly": { - attribute: schema.SetAttribute{ - WriteOnly: true, - }, - expected: true, - }, } for name, testCase := range testCases { diff --git a/resource/schema/set_nested_attribute.go b/resource/schema/set_nested_attribute.go index 56c449307..5d408cf1a 100644 --- a/resource/schema/set_nested_attribute.go +++ b/resource/schema/set_nested_attribute.go @@ -173,19 +173,6 @@ 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 @@ -268,9 +255,9 @@ func (a SetNestedAttribute) IsSensitive() bool { return a.Sensitive } -// IsWriteOnly returns the WriteOnly field value. +// IsWriteOnly returns false as write-only attributes are not supported for sets and set-based data. func (a SetNestedAttribute) IsWriteOnly() bool { - return a.WriteOnly + return false } // SetDefaultValue returns the Default field value. @@ -297,12 +284,8 @@ 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 fwschema.ContainsAnyWriteOnlyChildAttributes(a) { + resp.Diagnostics.Append(fwschema.InvalidSetNestedAttributeWithWriteOnlyDiag(req.Path)) } if a.SetDefaultValue() != nil { diff --git a/resource/schema/set_nested_attribute_test.go b/resource/schema/set_nested_attribute_test.go index 0e24d3f72..a0210f410 100644 --- a/resource/schema/set_nested_attribute_test.go +++ b/resource/schema/set_nested_attribute_test.go @@ -563,12 +563,6 @@ func TestSetNestedAttributeIsWriteOnly(t *testing.T) { attribute: schema.SetNestedAttribute{}, expected: false, }, - "writeOnly": { - attribute: schema.SetNestedAttribute{ - WriteOnly: true, - }, - expected: true, - }, } for name, testCase := range testCases { @@ -913,9 +907,9 @@ func TestSetNestedAttributeValidateImplementation(t *testing.T) { }, }, }, - "writeOnly-with-child-writeOnly-no-error-diagnostic": { + "child-writeOnly-attribute-error-diagnostic": { attribute: schema.SetNestedAttribute{ - WriteOnly: true, + Optional: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "test_attr": schema.StringAttribute{ @@ -928,59 +922,32 @@ func TestSetNestedAttributeValidateImplementation(t *testing.T) { 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.", + "\"test\" is a set nested attribute that contains a WriteOnly child attribute.\n\n"+ + "Every child attribute of a set nested attribute must have WriteOnly set to false.", ), }, }, }, - "computed-without-child-writeOnly-no-error-diagnostic": { + "nested-attribute-with-child-writeOnly-attribute-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, + Optional: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ - "test_attr": schema.StringAttribute{ - WriteOnly: true, + "test_nested_set": schema.SetNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "test_attr": schema.StringAttribute{ + WriteOnly: true, + }, + }, + }, }, }, }, @@ -995,8 +962,8 @@ func TestSetNestedAttributeValidateImplementation(t *testing.T) { "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.", + "\"test\" is a set nested attribute that contains a WriteOnly child attribute.\n\n"+ + "Every child attribute of a set nested attribute must have WriteOnly set to false.", ), }, }, diff --git a/resource/schema/set_nested_block.go b/resource/schema/set_nested_block.go index 78de8afb1..6b4b728bc 100644 --- a/resource/schema/set_nested_block.go +++ b/resource/schema/set_nested_block.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" @@ -15,7 +17,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,10 @@ func (b SetNestedBlock) Type() attr.Type { // errors or panics. This logic runs during the GetProviderSchema RPC and // should never include false positives. func (b SetNestedBlock) ValidateImplementation(ctx context.Context, req fwschema.ValidateImplementationRequest, resp *fwschema.ValidateImplementationResponse) { + if fwschema.BlockContainsAnyWriteOnlyChildAttributes(b) { + resp.Diagnostics.Append(fwschema.SetBlockCollectionWithWriteOnlyDiag(req.Path)) + } + if b.CustomType == nil && fwtype.ContainsCollectionWithDynamic(b.Type()) { resp.Diagnostics.Append(fwtype.BlockCollectionWithDynamicTypeDiag(req.Path)) } diff --git a/resource/schema/set_nested_block_test.go b/resource/schema/set_nested_block_test.go index 4aee18302..409e5d0e5 100644 --- a/resource/schema/set_nested_block_test.go +++ b/resource/schema/set_nested_block_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" @@ -20,7 +22,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) func TestSetNestedBlockApplyTerraform5AttributePathStep(t *testing.T) { @@ -548,6 +549,32 @@ func TestSetNestedBlockValidateImplementation(t *testing.T) { }, }, }, + "nestedobject-write-only": { + block: schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + 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 set nested block that contains a WriteOnly child attribute.\n\n"+ + "Every child attribute within a set nested block must have WriteOnly set to false.", + ), + }, + }, + }, } for name, testCase := range testCases { diff --git a/resource/schema/write_only_nested_attribute_validation_test.go b/resource/schema/write_only_nested_attribute_validation_test.go index acc2ca1de..b14b97931 100644 --- a/resource/schema/write_only_nested_attribute_validation_test.go +++ b/resource/schema/write_only_nested_attribute_validation_test.go @@ -7,14 +7,13 @@ 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 + nestedAttr schema.NestedAttribute expected bool }{ "empty nested attribute returns true": { @@ -138,7 +137,6 @@ func TestContainsAllWriteOnlyChildAttributes(t *testing.T) { }, }, "set_nested_attribute": schema.SetNestedAttribute{ - WriteOnly: false, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "string_attribute": schema.StringAttribute{ @@ -203,7 +201,6 @@ func TestContainsAllWriteOnlyChildAttributes(t *testing.T) { }, "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{ @@ -219,7 +216,6 @@ func TestContainsAllWriteOnlyChildAttributes(t *testing.T) { }, "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{ @@ -233,107 +229,6 @@ func TestContainsAllWriteOnlyChildAttributes(t *testing.T) { }, 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{ @@ -634,7 +529,7 @@ func TestContainsAllWriteOnlyChildAttributes(t *testing.T) { func TestContainsAnyWriteOnlyChildAttributes(t *testing.T) { t.Parallel() tests := map[string]struct { - nestedAttr metaschema.NestedAttribute + nestedAttr schema.NestedAttribute expected bool }{ "empty nested attribute returns false": { @@ -763,7 +658,6 @@ func TestContainsAnyWriteOnlyChildAttributes(t *testing.T) { }, }, "set_nested_attribute": schema.SetNestedAttribute{ - WriteOnly: false, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "string_attribute": schema.StringAttribute{ @@ -863,16 +757,15 @@ func TestContainsAnyWriteOnlyChildAttributes(t *testing.T) { 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, + WriteOnly: true, + Optional: true, }, "float32_attribute": schema.Float32Attribute{ - WriteOnly: false, - Computed: true, + WriteOnly: true, + Optional: true, }, }, }, @@ -882,7 +775,7 @@ func TestContainsAnyWriteOnlyChildAttributes(t *testing.T) { }, expected: true, }, - "set nested attribute with non-writeOnly child nested attribute returns false": { + "set nested attribute with no writeOnly child nested attributes returns false": { nestedAttr: schema.SetNestedAttribute{ NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ @@ -905,51 +798,11 @@ func TestContainsAnyWriteOnlyChildAttributes(t *testing.T) { }, 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": { + "set nested attribute with one 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{ @@ -1279,3 +1132,670 @@ func TestContainsAnyWriteOnlyChildAttributes(t *testing.T) { }) } } + +func TestBlockContainsAnyWriteOnlyChildAttributes(t *testing.T) { + t.Parallel() + tests := map[string]struct { + block schema.Block + expected bool + }{ + "empty nested block returns false": { + block: schema.ListNestedBlock{}, + expected: false, + }, + "list nested block with writeOnly child attribute returns true": { + block: schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: true, + }, + }, + }, + }, + expected: true, + }, + "list nested block with non-writeOnly child attribute returns false": { + block: schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: false, + }, + }, + }, + }, + expected: false, + }, + "list nested block with multiple writeOnly child attributes returns true": { + block: schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: true, + }, + "float32_attribute": schema.Float32Attribute{ + WriteOnly: true, + }, + }, + }, + }, + expected: true, + }, + "list nested block with one non-writeOnly child attribute returns true": { + block: schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: true, + }, + "float32_attribute": schema.Float32Attribute{ + WriteOnly: false, + }, + }, + }, + }, + expected: true, + }, + "list nested block with writeOnly child nested attribute returns true": { + block: schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + 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 block with non-writeOnly child nested attribute returns false": { + block: schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + 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 block with one non-writeOnly child nested attribute returns true": { + block: schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + 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, + }, + }, + }, + }, + "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 block with one non-writeOnly nested child attribute returns true": { + block: schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + 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, + }, + "list double-nested block with top-level writeOnly nested child attribute returns true": { + block: schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + 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: false, + }, + }, + }, + }, + }, + Blocks: map[string]schema.Block{ + "double_list_nested_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: true, + }, + }, + }, + }, + }, + }, + }, + expected: true, + }, + "list double-nested block with one non-writeOnly nested child attribute returns true": { + block: schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + 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: false, + }, + }, + }, + }, + }, + Blocks: map[string]schema.Block{ + "double_list_nested_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + 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 block with non-writeOnly child attribute returns false": { + block: schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: false, + }, + }, + }, + }, + expected: false, + }, + "set nested block with multiple writeOnly child attributes returns true": { + block: schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: true, + }, + "float32_attribute": schema.Float32Attribute{ + WriteOnly: true, + }, + }, + }, + }, + expected: true, + }, + "set nested block with one non-writeOnly child attribute returns true": { + block: schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: true, + }, + "float32_attribute": schema.Float32Attribute{ + WriteOnly: false, + }, + }, + }, + }, + expected: true, + }, + "set nested block with writeOnly child nested attribute returns true": { + block: schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + 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, + }, + "set nested block with non-writeOnly child nested attribute returns false": { + block: schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + 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 block with one non-writeOnly child nested attribute returns true": { + block: schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + 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, + }, + }, + }, + }, + "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, + }, + "set nested block with one non-writeOnly nested child attribute returns true": { + block: schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "set_nested_attribute": schema.SetNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: false, + }, + "float32_attribute": schema.Float32Attribute{ + WriteOnly: true, + }, + }, + }, + }, + }, + }, + }, + expected: true, + }, + "set double-nested block with top-level writeOnly nested child attribute returns true": { + block: schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "set_nested_attribute": schema.SetNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: false, + }, + "float32_attribute": schema.Float32Attribute{ + WriteOnly: false, + }, + }, + }, + }, + }, + Blocks: map[string]schema.Block{ + "double_set_nested_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: true, + }, + }, + }, + }, + }, + }, + }, + expected: true, + }, + "set double-nested block with one non-writeOnly nested child attribute returns true": { + block: schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "set_nested_attribute": schema.SetNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: false, + }, + "float32_attribute": schema.Float32Attribute{ + WriteOnly: false, + }, + }, + }, + }, + }, + Blocks: map[string]schema.Block{ + "double_set_nested_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "set_nested_attribute": schema.SetNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: false, + }, + "float32_attribute": schema.Float32Attribute{ + WriteOnly: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expected: true, + }, + "single nested block with non-writeOnly child attribute returns false": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: false, + }, + }, + }, + expected: false, + }, + "single nested block with multiple writeOnly child attributes returns true": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: true, + }, + "float32_attribute": schema.Float32Attribute{ + WriteOnly: true, + }, + }, + }, + expected: true, + }, + "single nested block with one non-writeOnly child attribute returns true": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: true, + }, + "float32_attribute": schema.Float32Attribute{ + WriteOnly: false, + }, + }, + }, + expected: true, + }, + "single nested block with writeOnly child nested attribute returns true": { + block: schema.SingleNestedBlock{ + 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, + }, + "single nested block with non-writeOnly child nested attribute returns false": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "single_nested_attribute": schema.SingleNestedAttribute{ + 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 block with one non-writeOnly child nested attribute returns true": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "single_nested_attribute": schema.SingleNestedAttribute{ + 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: 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, + }, + "single nested block with one non-writeOnly nested child attribute returns true": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "single_nested_attribute": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: false, + }, + "float32_attribute": schema.Float32Attribute{ + WriteOnly: true, + }, + }, + }, + }, + }, + expected: true, + }, + "single double-nested block with top-level writeOnly nested child attribute returns true": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "single_nested_attribute": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: false, + }, + "float32_attribute": schema.Float32Attribute{ + WriteOnly: false, + }, + }, + }, + }, + Blocks: map[string]schema.Block{ + "double_single_nested_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: true, + }, + }, + }, + }, + }, + expected: true, + }, + "single double-nested block with one non-writeOnly nested child attribute returns true": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "single_nested_attribute": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{ + WriteOnly: false, + }, + "float32_attribute": schema.Float32Attribute{ + WriteOnly: false, + }, + }, + }, + }, + Blocks: map[string]schema.Block{ + "double_single_nested_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "single_nested_attribute": schema.SingleNestedAttribute{ + 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.BlockContainsAnyWriteOnlyChildAttributes(tt.block); got != tt.expected { + t.Errorf("BlockContainsAllWriteOnlyChildAttributes() = %v, want %v", got, tt.expected) + } + }) + } +} 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 9e4f8afe9..01ce7e51f 100644 --- a/website/docs/plugin/framework/handling-data/attributes/set-nested.mdx +++ b/website/docs/plugin/framework/handling-data/attributes/set-nested.mdx @@ -159,20 +159,6 @@ 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 9f3d7dcd3..512489aad 100644 --- a/website/docs/plugin/framework/handling-data/attributes/set.mdx +++ b/website/docs/plugin/framework/handling-data/attributes/set.mdx @@ -128,18 +128,6 @@ 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/resources/write-only-arguments.mdx b/website/docs/plugin/framework/resources/write-only-arguments.mdx index 79549b9cb..ee61e2b99 100644 --- a/website/docs/plugin/framework/resources/write-only-arguments.mdx +++ b/website/docs/plugin/framework/resources/write-only-arguments.mdx @@ -15,7 +15,9 @@ which should either use the value by making the appropriate change to the API or 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 arguments can accept ephemeral and non-ephemeral values. + +- Write-only arguments cannot be used with set attributes, set nested attributes, and set nested blocks. - 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`. @@ -25,15 +27,15 @@ write-only arguments should always be `null`. - 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 + - 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. +one of `Required` or `Optional` set to `true`. If a list nested, map nested, or single nested attribute has `WriteOnly` set to `true`, all child attributes must also have `WriteOnly` set to `true`. +A set nested block cannot have any child attributes with `WriteOnly` set to `true`. `Computed` cannot be set to true for write-only arguments. **Schema example:**