Skip to content

Commit 1ba53f0

Browse files
provider: eliminated the need to manually add *_wo and *_wo_version for write-only properties (#14230)
Co-authored-by: Stephen Lewis (Burrows) <stephen.r.burrows@gmail.com>
1 parent 2a9cae0 commit 1ba53f0

10 files changed

Lines changed: 474 additions & 67 deletions

File tree

docs/content/reference/field.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,11 @@ sensitive: true
108108
```
109109

110110
### `write_only`
111-
If true, the field is considered "write-only", which means that its value will
112-
be obscured in Terraform output as well as not be stored in state. This field is meant to replace `sensitive` as it doesn't store the value in state.
113-
See [Ephemerality in Resources - Use Write-only arguments](https://developer.hashicorp.com/terraform/language/resources/ephemeral/write-only)
114-
for more information.
111+
Set to true to enable write-only functionality for this field.
112+
If true, the write-only fields will be automatically generated by the code generator (`<field_name>_wo` and `<field_name>_wo_version`).
113+
When the write-only variant of a field is used, it means that its value will be obscured in Terraform output as well as not be stored in state.
114+
This field is meant to replace `sensitive` as it doesn't store the value in state.
115+
See [Ephemerality in Resources - Use Write-only arguments](https://developer.hashicorp.com/terraform/language/resources/ephemeral/write-only) for more information.
115116

116117
Write-only fields are only supported in Terraform v1.11+. Because the provider supports earlier Terraform versions, write only fields must be paired with (mutually exclusive) `sensitive` fields covering the same functionality for compatibility with those older versions.
117118
This field cannot be used in conjuction with `immutable` or `sensitive`.

mmv1/api/resource.go

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -737,14 +737,94 @@ func (r Resource) GetIdentity() []*Type {
737737
})
738738
}
739739

740-
func (r *Resource) AddLabelsRelatedFields(props []*Type, parent *Type) []*Type {
740+
func buildWriteOnlyField(name string, versionFieldName string, originalField *Type, originalFieldLineage string) *Type {
741+
description := fmt.Sprintf("%s Note: This property is write-only and will not be read from the API. For more info see [updating write-only attributes](/docs/providers/google/guides/using_write_only_attributes.html#updating-write-only-attributes)", originalField.Description)
742+
fieldPathOriginalField := originalFieldLineage
743+
fieldPathCurrentField := strings.ReplaceAll(originalFieldLineage, google.Underscore(originalField.Name), google.Underscore(name))
744+
requiredWith := strings.ReplaceAll(originalFieldLineage, google.Underscore(originalField.Name), google.Underscore(versionFieldName))
745+
746+
apiName := originalField.ApiName
747+
if apiName == "" {
748+
apiName = originalField.Name
749+
}
750+
751+
options := []func(*Type){
752+
propertyWithType("String"),
753+
propertyWithRequired(false),
754+
propertyWithDescription(description),
755+
propertyWithWriteOnly(true),
756+
propertyWithApiName(apiName),
757+
propertyWithIgnoreRead(true),
758+
propertyWithRequiredWith([]string{requiredWith}),
759+
}
760+
761+
if originalField.Required {
762+
exactlyOneOf := append(originalField.ExactlyOneOf, fieldPathOriginalField, fieldPathCurrentField)
763+
options = append(options, propertyWithExactlyOneOf(exactlyOneOf))
764+
} else {
765+
conflicts := append(originalField.Conflicts, fieldPathOriginalField)
766+
options = append(options, propertyWithConflicts(conflicts))
767+
}
768+
769+
if len(originalField.AtLeastOneOf) > 0 {
770+
atLeastOneOf := append(originalField.AtLeastOneOf, fieldPathCurrentField)
771+
options = append(options, propertyWithAtLeastOneOf(atLeastOneOf))
772+
}
773+
774+
return NewProperty(name, originalField.ApiName, options)
775+
}
776+
777+
func buildWriteOnlyVersionField(name string, originalField *Type, writeOnlyField *Type, originalFieldLineage string) *Type {
778+
description := fmt.Sprintf("Triggers update of %s write-only. For more info see [updating write-only attributes](/docs/providers/google/guides/using_write_only_attributes.html#updating-write-only-attributes)", google.Underscore(writeOnlyField.Name))
779+
requiredWith := strings.ReplaceAll(originalFieldLineage, google.Underscore(originalField.Name), google.Underscore(writeOnlyField.Name))
780+
781+
options := []func(*Type){
782+
propertyWithType("String"),
783+
propertyWithImmutable(originalField.Immutable),
784+
propertyWithDescription(description),
785+
propertyWithRequiredWith([]string{requiredWith}),
786+
propertyWithClientSide(true),
787+
}
788+
789+
return NewProperty(name, name, options)
790+
}
791+
792+
func (r *Resource) addWriteOnlyFields(props []*Type, propWithWoConfigured *Type, propWithWoConfiguredLineagePath string) []*Type {
793+
if len(propWithWoConfigured.RequiredWith) > 0 {
794+
log.Fatalf("WriteOnly property '%s' in resource '%s' cannot have RequiredWith set. This combination is not supported.", propWithWoConfigured.Name, r.Name)
795+
}
796+
woFieldName := fmt.Sprintf("%sWo", propWithWoConfigured.Name)
797+
woVersionFieldName := fmt.Sprintf("%sVersion", woFieldName)
798+
writeOnlyField := buildWriteOnlyField(woFieldName, woVersionFieldName, propWithWoConfigured, propWithWoConfiguredLineagePath)
799+
writeOnlyVersionField := buildWriteOnlyVersionField(woVersionFieldName, propWithWoConfigured, writeOnlyField, propWithWoConfiguredLineagePath)
800+
props = append(props, writeOnlyField, writeOnlyVersionField)
801+
return props
802+
}
803+
804+
func (r *Resource) buildCurrentPropLineage(p *Type, lineage string) string {
805+
underscoreName := google.Underscore(p.Name)
806+
if lineage == "" {
807+
return underscoreName
808+
}
809+
return fmt.Sprintf("%s.0.%s", lineage, underscoreName)
810+
}
811+
812+
// AddExtraFields processes properties and adds supplementary fields based on property types.
813+
// It handles write-only properties, labels, and annotations.
814+
func (r *Resource) AddExtraFields(props []*Type, parent *Type, lineage string) []*Type {
741815
for _, p := range props {
816+
currentPropLineage := r.buildCurrentPropLineage(p, lineage)
817+
if p.WriteOnly && !strings.HasSuffix(p.Name, "Wo") {
818+
props = r.addWriteOnlyFields(props, p, currentPropLineage)
819+
p.WriteOnly = false
820+
p.Required = false
821+
}
742822
if p.IsA("KeyValueLabels") {
743823
props = r.addLabelsFields(props, parent, p)
744824
} else if p.IsA("KeyValueAnnotations") {
745825
props = r.addAnnotationsFields(props, parent, p)
746826
} else if p.IsA("NestedObject") && len(p.AllProperties()) > 0 {
747-
p.Properties = r.AddLabelsRelatedFields(p.AllProperties(), p)
827+
p.Properties = r.AddExtraFields(p.AllProperties(), p, currentPropLineage)
748828
}
749829
}
750830
return props
@@ -763,6 +843,7 @@ func (r *Resource) addLabelsFields(props []*Type, parent *Type, labels *Type) []
763843

764844
terraformLabelsField := buildTerraformLabelsField("labels", parent, labels)
765845
effectiveLabelsField := buildEffectiveLabelsField("labels", labels)
846+
766847
props = append(props, terraformLabelsField, effectiveLabelsField)
767848

768849
// The effective_labels field is used to write to API, instead of the labels field.
@@ -799,6 +880,7 @@ func (r *Resource) addAnnotationsFields(props []*Type, parent *Type, annotations
799880
}
800881

801882
effectiveAnnotationsField := buildEffectiveLabelsField("annotations", annotations)
883+
802884
props = append(props, effectiveAnnotationsField)
803885
return props
804886
}

0 commit comments

Comments
 (0)