diff --git a/internal/cligen/model.go b/internal/cligen/model.go index ae5ce44dd63..15ab313eca5 100644 --- a/internal/cligen/model.go +++ b/internal/cligen/model.go @@ -143,6 +143,7 @@ type MethodJSON struct { IsJsonOnly bool `json:"is_json_only,omitempty"` IsCrudCreate bool `json:"is_crud_create,omitempty"` IsCrudRead bool `json:"is_crud_read,omitempty"` + IsCrudUpdate bool `json:"is_crud_update,omitempty"` IsHiddenCLI bool `json:"is_hidden_cli,omitempty"` IsResponseByteStream bool `json:"is_response_byte_stream,omitempty"` HasRequiredPositionalArguments bool `json:"has_required_positional_arguments,omitempty"` @@ -192,6 +193,7 @@ type FieldJSON struct { IsQuery bool `json:"is_query,omitempty"` IsComputed bool `json:"is_computed,omitempty"` IsOutputOnly bool `json:"is_output_only,omitempty"` + IsImmutable bool `json:"is_immutable,omitempty"` IsRequestBodyField bool `json:"is_request_body_field,omitempty"` IsOptionalObject bool `json:"is_optional_object,omitempty"` diff --git a/internal/cligen/templates/service.go.tmpl b/internal/cligen/templates/service.go.tmpl index a13724dca2b..e2a1846fa17 100644 --- a/internal/cligen/templates/service.go.tmpl +++ b/internal/cligen/templates/service.go.tmpl @@ -221,6 +221,13 @@ func new{{.PascalName}}() *cobra.Command { {{else if .Entity.MapValue }}// TODO: map via StringToStringVar: {{.Name}} {{else if .IsComputed -}} {{else if .IsOutputOnly -}} + {{- /* Immutable (create-only) fields are settable at create but cannot + change on update. They keep their flag on create-type commands + (and any non-update command) and are omitted from update-type + commands. Computed/output-only fields are handled above and stay + flagless everywhere, so a field that is both immutable and + computed (e.g. dashboards parent_path) is unaffected by this. */ -}} + {{else if and .IsImmutable $method.IsCrudUpdate -}} {{else if .Entity.Enum }}cmd.Flags().Var(&{{- template "request-body-obj" (dict "Method" $method "Field" .)}}, "{{.KebabName}}", `{{.Summary | without "`" | trimSuffix "."}}{{if .Summary}}. {{end}}Supported values: {{template "printArray" .Entity.Enum}}{{if $hasFieldMask}}. Wire name: '{{.Name}}'.{{end}}`) {{else if or .Entity.IsDuration .Entity.IsTimestamp .Entity.IsFieldMask}} var {{.CamelName}}Param string cmd.Flags().StringVar(&{{.CamelName}}Param, "{{.KebabName}}", {{.CamelName}}Param, `{{.Summary | without "`" | trimSuffix "."}}{{if .Summary}}.{{end}}{{if $hasFieldMask}} Wire name: '{{.Name}}'.{{end}}`) @@ -403,7 +410,7 @@ func new{{.PascalName}}() *cobra.Command { {{- template "args-scan" (dict "Arg" $arg "Field" $field "Method" $method "HasIdPrompt" $hasIdPrompt "ExcludeFromJson" $excludeFromJson)}} {{- end -}} {{- range .AllFields -}} - {{- if and (not .Required) (not .IsComputed) (not .IsOutputOnly)}} + {{- if and (not .Required) (not .IsComputed) (not .IsOutputOnly) (not (and .IsImmutable $method.IsCrudUpdate))}} {{if or .Entity.IsDuration .Entity.IsTimestamp .Entity.IsFieldMask}} if {{.CamelName}}Param != "" { {{template "parse-well-known-type" (dict "Method" $method "Field" . "SourceValue" (printf "%sParam" .CamelName))}} diff --git a/internal/cligen/transform.go b/internal/cligen/transform.go index ea7864877e0..82a62122668 100644 --- a/internal/cligen/transform.go +++ b/internal/cligen/transform.go @@ -130,6 +130,7 @@ func fromMethod(m *clijson.MethodJSON) *MethodJSON { IsJsonOnly: m.IsJsonOnly, IsCrudCreate: m.IsCrudCreate, IsCrudRead: m.IsCrudRead, + IsCrudUpdate: m.IsCrudUpdate, IsHiddenCLI: m.IsHiddenCLI, IsResponseByteStream: m.IsResponseByteStream, HasRequiredPositionalArguments: m.HasRequiredPositionalArguments, @@ -161,6 +162,7 @@ func fromField(f *clijson.FieldJSON) *FieldJSON { IsQuery: f.IsQuery, IsComputed: f.IsComputed, IsOutputOnly: f.IsOutputOnly, + IsImmutable: f.IsImmutable, IsRequestBodyField: f.IsRequestBodyField, IsOptionalObject: f.IsOptionalObject, Entity: fromEntity(f.Entity), diff --git a/internal/clijson/types.go b/internal/clijson/types.go index 7b0f02277e1..6ac602e05e8 100644 --- a/internal/clijson/types.go +++ b/internal/clijson/types.go @@ -117,6 +117,7 @@ type MethodJSON struct { IsJsonOnly bool `json:"is_json_only,omitempty"` IsCrudCreate bool `json:"is_crud_create,omitempty"` IsCrudRead bool `json:"is_crud_read,omitempty"` + IsCrudUpdate bool `json:"is_crud_update,omitempty"` IsHiddenCLI bool `json:"is_hidden_cli,omitempty"` IsResponseByteStream bool `json:"is_response_byte_stream,omitempty"` HasRequiredPositionalArguments bool `json:"has_required_positional_arguments,omitempty"` @@ -145,11 +146,15 @@ type FieldJSON struct { Description string `json:"description,omitempty"` - Required bool `json:"required,omitempty"` - IsPath bool `json:"is_path,omitempty"` - IsQuery bool `json:"is_query,omitempty"` - IsComputed bool `json:"is_computed,omitempty"` - IsOutputOnly bool `json:"is_output_only,omitempty"` + Required bool `json:"required,omitempty"` + IsPath bool `json:"is_path,omitempty"` + IsQuery bool `json:"is_query,omitempty"` + IsComputed bool `json:"is_computed,omitempty"` + IsOutputOnly bool `json:"is_output_only,omitempty"` + // IsImmutable is the AIP-203 IMMUTABLE behavior: settable at create but not + // changeable on update. The CLI keeps the flag on create-type commands and + // omits it from update-type commands. + IsImmutable bool `json:"is_immutable,omitempty"` IsRequestBodyField bool `json:"is_request_body_field,omitempty"` IsOptionalObject bool `json:"is_optional_object,omitempty"`