Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
342bf27
feat: initial implementation of the constraint group registry
ramonvermeulen Oct 29, 2025
7177185
fix: also enable `write_only` for the `service_key`
ramonvermeulen Oct 29, 2025
61aa6ea
refactor: slightly simplify implementation
ramonvermeulen Oct 29, 2025
3d9bf59
fix: this branching seems redundant
ramonvermeulen Oct 29, 2025
6cd97e8
fix: format
ramonvermeulen Oct 29, 2025
c93f109
test: added tests for write-only labels (encountered some issues due …
ramonvermeulen Nov 10, 2025
8a48fca
fix: apply write-only templating logic for nested fields
ramonvermeulen Nov 12, 2025
ac2487d
fix: use write-only generation instead of the custom implementation
ramonvermeulen Nov 12, 2025
0b3e95a
Merge branch 'main' into f/24327-write-only-pointer-constraint-groups
ramonvermeulen Dec 11, 2025
25a9534
feat: implementation of nested write only documentation generation
ramonvermeulen Nov 26, 2025
a6f3f56
fix: remove sorting (changes too much generated markdowns, harder to …
ramonvermeulen Nov 26, 2025
e8d2222
Merge branch 'main' into f/24327-write-only-pointer-constraint-groups
ramonvermeulen Dec 22, 2025
0700799
fix: apply changes after `Lineage` refactor
ramonvermeulen Dec 22, 2025
7784bfd
Merge branch 'main' into f/24327-write-only-pointer-constraint-groups
ramonvermeulen Jan 6, 2026
bc7d6ff
fix: solved the markdown rendering
ramonvermeulen Jan 6, 2026
a779460
fix: apply quick suggestions from code review @melinath
ramonvermeulen Jan 14, 2026
dde31a9
feat: additional changes to clean-up the decoder
ramonvermeulen Jan 20, 2026
5d25a49
fix: make sensitiveLabels `ignore_read` and make encoder more specific
ramonvermeulen Jan 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions mmv1/api/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,29 @@ func (t Type) WriteOnlyProperties() []*Type {
return props
}

// AllUniqueNestedProperties Returns all unique nested properties (regular and write-only), preserving order and sorted by name.
func (t Type) AllUniqueNestedProperties() []*Type {
seen := make(map[string]bool)
var result []*Type

for _, p := range t.NestedProperties() {
key := strings.Join(p.Lineage(), "|")
if !seen[key] {
result = append(result, p)
seen[key] = true
}
}
for _, p := range t.WriteOnlyProperties() {
key := strings.Join(p.Lineage(), "|")
if !seen[key] {
result = append(result, p)
seen[key] = true
}
}

return result
}

func (t Type) Removed() bool {
return t.RemovedMessage != ""
}
Expand Down
7 changes: 6 additions & 1 deletion mmv1/products/monitoring/NotificationChannel.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ properties:
the sensitive_labels block, but cannot be configured in both places.
- name: 'sensitiveLabels'
type: NestedObject
ignore_read: true
description: |
Different notification type behaviors are configured primarily using the the `labels` field on this
resource. This block contains the labels which contain secrets or passwords so that they can be marked
Expand All @@ -102,13 +103,14 @@ properties:

Credentials may not be specified in both locations and will cause an error. Changing from one location
to a different credential configuration in the config will require an apply to update state.
url_param_only: true
properties:
- name: 'authToken'
type: String
api_name: 'auth_token'
description: |
An authorization token for a notification channel. Channel types that support this field include: slack
sensitive: true
write_only: true
exactly_one_of:
- 'sensitive_labels.0.auth_token'
- 'sensitive_labels.0.password'
Expand All @@ -118,15 +120,18 @@ properties:
description: |
An password for a notification channel. Channel types that support this field include: webhook_basicauth
sensitive: true
write_only: true
exactly_one_of:
- 'sensitive_labels.0.auth_token'
- 'sensitive_labels.0.password'
- 'sensitive_labels.0.service_key'
- name: 'serviceKey'
type: String
api_name: 'service_key'
description: |
An servicekey token for a notification channel. Channel types that support this field include: pagerduty
sensitive: true
write_only: true
exactly_one_of:
- 'sensitive_labels.0.auth_token'
- 'sensitive_labels.0.password'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@

var sensitiveLabels = []string{"auth_token", "service_key", "password"}
var writeOnlySensitiveLabels = []string{"auth_token_wo_version", "service_key_wo_version", "password_wo_version"}

func sensitiveLabelCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v interface{}) error {
for _, sl := range sensitiveLabels {
mapLabel := diff.Get("labels." + sl).(string)
authLabel := diff.Get("sensitive_labels.0." + sl).(string)
if mapLabel != "" && authLabel != "" {
return fmt.Errorf("Sensitive label [%s] cannot be set in both `labels` and the `sensitive_labels` block.", sl)
}
}
return nil
}
for _, sl := range sensitiveLabels {
l := strings.TrimSuffix(sl, "_wo")
l = strings.TrimSuffix(l, "_wo_version")
val := diff.Get("labels." + l).(string)
sensitiveVal := diff.Get("sensitive_labels.0." + sl).(string)
if val != "" && sensitiveVal != "" {
return fmt.Errorf("Sensitive label %q cannot be set at the same time as label %q.", sl, l)
}
}
return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,40 @@
See the License for the specific language governing permissions and
limitations under the License.
*/ -}}
if labelmap, ok := res["labels"]; ok {
labels := labelmap.(map[string]interface{})
for _, sl := range sensitiveLabels {
if _, apiOk := labels[sl]; apiOk {
if _, exists := d.GetOkExists("sensitive_labels.0." + sl); exists {
delete(labels, sl)
} else {
labels[sl] = d.Get("labels." + sl)
labels, ok := res["labels"].(map[string]interface{})
if !ok {
return res, nil
}

sensitiveLabelsRaw := d.Get("sensitive_labels")
if sensitiveLabelsRaw != nil {
if sensitiveLabelsArr, ok := sensitiveLabelsRaw.([]interface{}); ok && len(sensitiveLabelsArr) > 0 {
if configuredSensitiveLabels, ok := sensitiveLabelsArr[0].(map[string]interface{}); ok && len(configuredSensitiveLabels) > 0 {
sensitiveLabels := make(map[string]interface{})

for configKey, configVal := range configuredSensitiveLabels {
apiField := strings.TrimSuffix(configKey, "_wo")
delete(labels, apiField)
sensitiveLabels[configKey] = configVal
}

if len(sensitiveLabels) > 0 {
res["sensitiveLabels"] = sensitiveLabels
}
return res, nil
}
}
}

return res, nil
// Replace obfuscated API values with configured values to prevent a diff
// Scenario where end-user uses "sensitive" labels directly in "labels" block
if configuredLabels, ok := d.Get("labels").(map[string]interface{}); ok {
for _, field := range []string{"auth_token", "password", "service_key"} {
if configVal, exists := configuredLabels[field]; exists && configVal != nil && configVal != "" {
labels[field] = configVal
}
}
}

return res, nil

Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,22 @@ labels = labelmap.(map[string]string)

for _, sl := range sensitiveLabels {
if auth, _ := d.GetOkExists("sensitive_labels.0." + sl); auth != "" {

labels[sl] = auth.(string)
}
}

for _, slwov := range writeOnlySensitiveLabels {
if v, _ := d.GetOkExists("sensitive_labels.0." + slwov); v != "" {
target := strings.TrimSuffix(slwov, "_version")
woProp := tpgresource.GetRawConfigAttributeAsString(d, "sensitive_labels.0." + target)
if woProp != "" {
labels[strings.TrimSuffix(slwov, "_wo_version")] = woProp
}
}
}

obj["labels"] = labels
delete(obj, "sensitiveLabels")

return obj, nil
return obj, nil
12 changes: 6 additions & 6 deletions mmv1/templates/terraform/expand_property_method.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ func expand{{$.GetPrefix}}{{$.TitlelizeProperty}}(v interface{}, d tpgresource.T
original := raw.(map[string]interface{})
transformed := make(map[string]interface{})
{{- range $prop := $.NestedProperties }}
{{- if not (eq $prop.Name $prop.KeyName) }}
{{- if not (or (eq $prop.Name $prop.KeyName) $prop.ClientSide) }}

transformed{{$prop.TitlelizeProperty}}, err := expand{{$.GetPrefix}}{{$.TitlelizeProperty}}{{$prop.TitlelizeProperty}}(original["{{ underscore $prop.Name }}"], d, config)
transformed{{$prop.TitlelizeProperty}}, err := expand{{$.GetPrefix}}{{$.TitlelizeProperty}}{{$prop.TitlelizeProperty}}({{if $prop.WriteOnly }}tpgresource.GetRawConfigAttributeAsString(d.(*schema.ResourceData), "{{ join $prop.Lineage ".0." }}"){{ else }}original["{{ underscore $prop.Name }}"]{{ end }}, d, config)
if err != nil {
return nil, err
{{- if or ($prop.SendEmptyValue) (and $prop.TGCSendEmptyValue $.ResourceMetadata.IsTgcCompiler) }}
Expand Down Expand Up @@ -65,8 +65,8 @@ func expand{{$.GetPrefix}}{{$.TitlelizeProperty}}(v interface{}, d tpgresource.T
func expand{{$.GetPrefix}}{{$.TitlelizeProperty}}(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
transformed := make(map[string]interface{})
{{- range $prop := $.NestedProperties }}
{{- if not (and (hasPrefix $prop.Type "KeyValue") $prop.IgnoreWrite) }}
transformed{{$prop.TitlelizeProperty}}, err := expand{{$.GetPrefix}}{{$.TitlelizeProperty}}{{$prop.TitlelizeProperty}}({{ if $prop.FlattenObject }}nil{{ else }}d.Get("{{ underscore $prop.Name }}"), d, config)
{{- if not (or (and (hasPrefix $prop.Type "KeyValue") $prop.IgnoreWrite) $prop.ClientSide) }}
transformed{{$prop.TitlelizeProperty}}, err := expand{{$.GetPrefix}}{{$.TitlelizeProperty}}{{$prop.TitlelizeProperty}}({{ if $prop.FlattenObject }}nil{{ else if $prop.WriteOnly }}tpgresource.GetRawConfigAttributeAsString(d.(*schema.ResourceData), "{{ join $prop.Lineage ".0." }}"){{ else }}d.Get("{{ underscore $prop.Name }}"), d, config)
if err != nil {
return nil, err
{{- if or ($prop.SendEmptyValue) (and $prop.TGCSendEmptyValue $.ResourceMetadata.IsTgcCompiler) }}
Expand Down Expand Up @@ -121,8 +121,8 @@ func expand{{$.GetPrefix}}{{$.TitlelizeProperty}}(v interface{}, d tpgresource.T
{{- end }}{{/* if $.IsA "Array */}}
transformed := make(map[string]interface{})
{{ range $prop := $.NestedProperties }}
{{- if not (and (hasPrefix $prop.Type "KeyValue") $prop.IgnoreWrite) }}
transformed{{$prop.TitlelizeProperty}}, err := expand{{$.GetPrefix}}{{$.TitlelizeProperty}}{{$prop.TitlelizeProperty}}(original["{{ underscore $prop.Name }}"], d, config)
{{- if not (or (and (hasPrefix $prop.Type "KeyValue") $prop.IgnoreWrite) $prop.ClientSide) }}
transformed{{$prop.TitlelizeProperty}}, err := expand{{$.GetPrefix}}{{$.TitlelizeProperty}}{{$prop.TitlelizeProperty}}({{if $prop.WriteOnly }}tpgresource.GetRawConfigAttributeAsString(d.(*schema.ResourceData), "{{ join $prop.Lineage ".0." }}"){{ else }}original["{{ underscore $prop.Name }}"]{{ end }}, d, config)
if err != nil {
return nil, err
{{- if or ($prop.SendEmptyValue) (and $prop.TGCSendEmptyValue $.ResourceMetadata.IsTgcCompiler) }}
Expand Down
2 changes: 1 addition & 1 deletion mmv1/templates/terraform/flatten_property_method.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License. */ -}}
{{- define "flattenPropertyMethod" }}
{{- if or $.WriteOnlyLegacy $.WriteOnly }}
{{- if and (or $.WriteOnlyLegacy $.WriteOnly) }}
{{- else if and $.CustomFlatten (not $.ShouldIgnoreCustomFlatten) }}
{{- customTemplate $ $.CustomFlatten false -}}
{{- else -}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,32 @@
{{ "" }}
{{- if and $.FlattenObject (not $.WriteOnlyProperties) }}
{{- range $np := $.NestedProperties }}
{{- $nested := or $.NestedProperties $.WriteOnlyProperties }}
{{- if and $.FlattenObject $nested }}
{{- range $np := $.AllUniqueNestedProperties }}
{{- trimTemplate "nested_property_documentation.html.markdown.tmpl" $np -}}
{{- end -}}
{{- else if and $.NestedProperties (not $.WriteOnlyProperties) }}
{{- else if $nested }}
<a name="nested_{{ join $.Lineage "_" }}"></a>The `{{ underscore $.Name }}` block {{ if $.Output }}contains{{ else }}supports{{ end }}:
{{ "" }}
{{- if $.IsA "Map" }}
* `{{ underscore $.KeyName }}` - (Required) The identifier for this object. Format specified above.
{{ "" }}
{{- end -}}
{{- if $.NestedProperties }}
{{- range $np := $.NestedProperties }}
{{- range $np := $.AllUniqueNestedProperties -}}
{{- trimTemplate "property_documentation.html.markdown.tmpl" $np -}}
{{- end -}}
{{- end -}}
{{ "" }}
{{- $innerNested := false }}
{{- range $np := $.NestedProperties }}
{{- range $np := $.AllUniqueNestedProperties }}
{{- if $np.NestedProperties }}
{{- $innerNested = true }}
{{- end }}
{{- end }}
{{- if $innerNested}}
{{ "" }}
{{- end }}
{{- range $np := $.NestedProperties }}
{{- range $np := $.AllUniqueNestedProperties }}
{{- if $np.NestedProperties }}
{{- trimTemplate "nested_property_documentation.html.markdown.tmpl" $np -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{ "" }}

This file was deleted.

4 changes: 4 additions & 0 deletions mmv1/third_party/terraform/acctest/resource_inventory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,10 @@ func TestValidateResourceMetadata(t *testing.T) {
"google_os_config_v2_policy_orchestrator_for_folder.orchestration_state.previous_iteration_state.error.details.type_url": true,
"google_os_config_v2_policy_orchestrator_for_organization.orchestration_state.current_iteration_state.error.details.type_url": true,
"google_os_config_v2_policy_orchestrator_for_organization.orchestration_state.previous_iteration_state.error.details.type_url": true,
"google_monitoring_notification_channel.sensitive_labels.auth_token": true,
"google_monitoring_notification_channel.sensitive_labels.auth_token_wo": true,
"google_monitoring_notification_channel.sensitive_labels.service_key": true,
"google_monitoring_notification_channel.sensitive_labels.service_key_wo": true,
}

// Validate yaml files
Expand Down
Loading
Loading