Skip to content

Commit c9cdfa4

Browse files
authored
fix(internal/sidekick/surfer): always specify primary (#5898)
Always specify is_primary_resource Fix #5875
1 parent 84a51e6 commit c9cdfa4

19 files changed

Lines changed: 161 additions & 74 deletions

File tree

doc/gcloud/gcloud-declarative-schema.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,19 @@ This document describes the schema for the gcloud Declarative YAML.
1717
| `help_text` | string | Is the help text shown for the argument in gcloud help and error messages. |
1818
| `action` | string | Overrides the argparse action used for the argument, for example store_true or store_true_false. Typically used with boolean fields. |
1919
| `is_positional` | bool | Makes the argument positional rather than a flag. |
20-
| `is_primary_resource` | bool | Marks this argument as the primary resource the command operates on. At most one argument per command may set this. |
21-
| `request_id_field` | string | Is the request-message field that receives the user-supplied resource ID on a create command, for example instanceId. |
22-
| `resource_spec` | [ResourceSpec](#resourcespec-configuration) (optional) | Declares this argument as a gcloud concept resource and describes its collection and attributes. |
2320
| `required` | bool | Makes the argument mandatory. gcloud rejects invocations that leave it unset. |
2421
| `repeated` | bool | Accepts more than one value for the argument and maps to a repeated API field. |
2522
| `clearable` | bool | Causes gcloud to also generate companion flags such as<br>--clear-..., --add-..., and --remove-... on update commands. |
2623
| `type` | string | Selects the gcloud argument parser, for example long, double, or a fully-qualified reference to an ArgDict or ArgList parser. |
2724
| `default` | [Default](#default-configuration) | Is the value used when the user does not supply the argument. |
2825
| `choices` | list of [Choice](#choice-configuration) | Is the fixed set of values the user can supply. Used for enum-typed fields; each choice maps an arg_value to an enum_value. |
2926
| `spec` | list of [ArgSpec](#argspec-configuration) | Lists the sub-fields of a structured argument such as a map. For a map field, Spec typically contains entries for key and value. |
30-
| `resource_method_params` | map[string]string | Maps API method parameter names to attribute names on a resource argument. Use it when the API method uses a non-standard parameter name for the resource. |
3127

3228
## Arguments Configuration
3329

3430
| Field | Type | Description |
3531
| :--- | :--- | :--- |
36-
| `params` | list of [Argument](#argument-configuration) | Is the ordered list of arguments the command accepts. Each entry becomes either a positional argument or a flag. |
32+
| `params` | list of any | Is the ordered list of arguments the command accepts. Each entry becomes either a positional argument or a flag. |
3733

3834
## Async Configuration
3935

@@ -103,6 +99,22 @@ This document describes the schema for the gcloud Declarative YAML.
10399
| `method` | string | Is the API method name. When empty, gcloud infers the method from the command type (for example, list for a list command). |
104100
| `static_fields` | map[string]string | Sets request fields to fixed values regardless of user input. Keys are dot-separated field paths in the request message. |
105101

102+
## ResourceArg Configuration
103+
104+
| Field | Type | Description |
105+
| :--- | :--- | :--- |
106+
| `arg_name` | string | Is the name of the argument as it appears to the user, such as instance. For flags gcloud prepends --. |
107+
| `help_text` | string | Is the help text shown for the argument in gcloud help and error messages. |
108+
| `is_positional` | bool | Makes the argument positional rather than a flag. |
109+
| `is_primary_resource` | bool | Marks this argument as the primary resource the command operates on. At most one argument per command may set this. |
110+
| `request_id_field` | string | Is the request-message field that receives the user-supplied resource ID on a create command, for example instanceId. |
111+
| `resource_spec` | [ResourceSpec](#resourcespec-configuration) (optional) | Declares this argument as a gcloud concept resource and describes its collection and attributes. |
112+
| `required` | bool | Makes the argument mandatory. gcloud rejects invocations that leave it unset. |
113+
| `repeated` | bool | Accepts more than one value for the argument and maps to a repeated API field. |
114+
| `clearable` | bool | Causes gcloud to also generate companion flags such as<br>--clear-..., --add-..., and --remove-... on update commands. |
115+
| `spec` | list of [ArgSpec](#argspec-configuration) | Lists the sub-fields of a structured argument such as a map. For a map field, Spec typically contains entries for key and value. |
116+
| `resource_method_params` | map[string]string | Maps API method parameter names to attribute names on a resource argument. Use it when the API method uses a non-standard parameter name for the resource. |
117+
106118
## ResourceSpec Configuration
107119

108120
| Field | Type | Description |

internal/sidekick/surfer/command_writer.go

Lines changed: 66 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -162,57 +162,77 @@ func mapCommandToYAML(c *Command, track ReleaseTrack) *declarative.Command {
162162
return y
163163
}
164164

165-
func mapArgumentsToYAML(args []Argument) []declarative.Argument {
166-
var ya []declarative.Argument
165+
func mapArgumentsToYAML(args []Argument) []any {
166+
var ya []any
167167
for _, a := range args {
168-
t := a.Type
169-
if t == "str" {
170-
t = ""
168+
if a.ResourceSpec != nil {
169+
ya = append(ya, mapResourceArgToYAML(a))
170+
} else {
171+
ya = append(ya, mapArgumentToYAML(a))
171172
}
172-
var def declarative.Default
173-
if a.Action == "store_true" {
174-
var nilVal any = nil
175-
def.Value = &nilVal
176-
}
177-
var apiField string
178-
var rmp map[string]string
179-
180-
if len(a.APIField) > 0 {
181-
parts := make([]string, len(a.APIField))
182-
for i, p := range a.APIField {
183-
parts[i] = strcase.ToLowerCamel(p)
184-
}
185-
joinedPath := strings.Join(parts, ".")
186-
187-
if a.ResourceSpec != nil {
188-
rmp = map[string]string{
189-
joinedPath: "{__relative_name__}",
190-
}
191-
} else {
192-
apiField = joinedPath
193-
}
173+
}
174+
return ya
175+
}
176+
177+
func mapResourceArgToYAML(a Argument) declarative.ResourceArg {
178+
var rmp map[string]string
179+
if path := apiFieldPath(a.APIField); path != "" {
180+
rmp = map[string]string{
181+
path: "{__relative_name__}",
194182
}
183+
}
195184

196-
ya = append(ya, declarative.Argument{
197-
ArgName: strcase.ToKebab(a.ArgName),
198-
APIField: apiField,
199-
HelpText: a.HelpText,
200-
Action: a.Action,
201-
Default: def,
202-
IsPositional: a.IsPositional,
203-
IsPrimaryResource: a.IsPrimaryResource,
204-
RequestIDField: a.RequestIDField,
205-
ResourceSpec: mapResourceSpecToYAML(a.ResourceSpec),
206-
Required: a.Required,
207-
Repeated: a.Repeated,
208-
Clearable: a.Clearable,
209-
Type: t,
210-
Choices: mapChoicesToYAML(a.Choices),
211-
Spec: mapArgSpecsToYAML(a.Spec),
212-
ResourceMethodParams: rmp,
213-
})
185+
return declarative.ResourceArg{
186+
ArgName: strcase.ToKebab(a.ArgName),
187+
HelpText: a.HelpText,
188+
IsPositional: a.IsPositional,
189+
IsPrimaryResource: a.IsPrimaryResource,
190+
RequestIDField: a.RequestIDField,
191+
ResourceSpec: mapResourceSpecToYAML(a.ResourceSpec),
192+
Required: a.Required,
193+
Repeated: a.Repeated,
194+
Clearable: a.Clearable,
195+
Spec: mapArgSpecsToYAML(a.Spec),
196+
ResourceMethodParams: rmp,
214197
}
215-
return ya
198+
}
199+
200+
func mapArgumentToYAML(a Argument) declarative.Argument {
201+
t := a.Type
202+
if t == "str" {
203+
t = ""
204+
}
205+
var def declarative.Default
206+
if a.Action == "store_true" {
207+
var nilVal any = nil
208+
def.Value = &nilVal
209+
}
210+
211+
return declarative.Argument{
212+
ArgName: strcase.ToKebab(a.ArgName),
213+
APIField: apiFieldPath(a.APIField),
214+
HelpText: a.HelpText,
215+
Action: a.Action,
216+
Default: def,
217+
IsPositional: a.IsPositional,
218+
Required: a.Required,
219+
Repeated: a.Repeated,
220+
Clearable: a.Clearable,
221+
Type: t,
222+
Choices: mapChoicesToYAML(a.Choices),
223+
Spec: mapArgSpecsToYAML(a.Spec),
224+
}
225+
}
226+
227+
func apiFieldPath(apiField []string) string {
228+
if len(apiField) == 0 {
229+
return ""
230+
}
231+
parts := make([]string, len(apiField))
232+
for i, p := range apiField {
233+
parts[i] = strcase.ToLowerCamel(p)
234+
}
235+
return strings.Join(parts, ".")
216236
}
217237

218238
func mapResourceSpecToYAML(spec *ResourceSpec) *declarative.ResourceSpec {

internal/sidekick/surfer/command_writer_test.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -240,15 +240,13 @@ func TestMapCommandToYAML(t *testing.T) {
240240
Format: "table",
241241
},
242242
Arguments: declarative.Arguments{
243-
Params: []declarative.Argument{
244-
{
243+
Params: []any{
244+
declarative.ResourceArg{
245245
ArgName: "arg",
246246
Spec: []declarative.ArgSpec{
247247
{APIField: "s"},
248248
},
249-
Choices: []declarative.Choice{
250-
{ArgValue: "av", EnumValue: "ev", HelpText: "ch"},
251-
},
249+
IsPrimaryResource: false,
252250
ResourceSpec: &declarative.ResourceSpec{
253251
Name: "resourceName",
254252
PluralName: "resourcePlural",
@@ -259,18 +257,19 @@ func TestMapCommandToYAML(t *testing.T) {
259257
DisableAutoCompleters: true,
260258
},
261259
},
262-
{
260+
declarative.Argument{
263261
ArgName: "bool-arg",
264262
Type: "bool",
265263
Action: "store_true",
266264
Default: declarative.Default{Value: &nilVal},
267265
},
268-
{
266+
declarative.Argument{
269267
ArgName: "arg-with-apifield",
270268
APIField: "part1.part2",
271269
},
272-
{
273-
ArgName: "arg-with-apifield-and-resource",
270+
declarative.ResourceArg{
271+
ArgName: "arg-with-apifield-and-resource",
272+
IsPrimaryResource: false,
274273
ResourceSpec: &declarative.ResourceSpec{
275274
Name: "nestedResource",
276275
},

internal/sidekick/surfer/declarative/declarative.go

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,10 @@ func (d Default) MarshalYAML() (any, error) {
188188
type Arguments struct {
189189
// Params is the ordered list of arguments the command accepts. Each entry
190190
// becomes either a positional argument or a flag.
191-
Params []Argument `yaml:"params,omitempty"`
191+
Params []any `yaml:"params,omitempty"`
192192
}
193193

194-
// Argument describes a single command argument.
194+
// Argument describes a standard command argument (scalar flag or positional).
195195
type Argument struct {
196196
// ArgName is the name of the argument as it appears to the user, such as
197197
// instance-id. For flags gcloud prepends --.
@@ -212,18 +212,6 @@ type Argument struct {
212212
// IsPositional makes the argument positional rather than a flag.
213213
IsPositional bool `yaml:"is_positional"`
214214

215-
// IsPrimaryResource marks this argument as the primary resource the
216-
// command operates on. At most one argument per command may set this.
217-
IsPrimaryResource bool `yaml:"is_primary_resource,omitempty"`
218-
219-
// RequestIDField is the request-message field that receives the
220-
// user-supplied resource ID on a create command, for example instanceId.
221-
RequestIDField string `yaml:"request_id_field,omitempty"`
222-
223-
// ResourceSpec declares this argument as a gcloud concept resource and
224-
// describes its collection and attributes.
225-
ResourceSpec *ResourceSpec `yaml:"resource_spec,omitempty"`
226-
227215
// Required makes the argument mandatory. gcloud rejects invocations that
228216
// leave it unset.
229217
Required bool `yaml:"required"`
@@ -250,6 +238,48 @@ type Argument struct {
250238
// Spec lists the sub-fields of a structured argument such as a map. For a
251239
// map field, Spec typically contains entries for key and value.
252240
Spec []ArgSpec `yaml:"spec,omitempty"`
241+
}
242+
243+
// ResourceArg describes a concept-resource command argument.
244+
type ResourceArg struct {
245+
// ArgName is the name of the argument as it appears to the user, such as
246+
// instance. For flags gcloud prepends --.
247+
ArgName string `yaml:"arg_name,omitempty"`
248+
249+
// HelpText is the help text shown for the argument in gcloud help and
250+
// error messages.
251+
HelpText string `yaml:"help_text"`
252+
253+
// IsPositional makes the argument positional rather than a flag.
254+
IsPositional bool `yaml:"is_positional"`
255+
256+
// IsPrimaryResource marks this argument as the primary resource the
257+
// command operates on. At most one argument per command may set this.
258+
IsPrimaryResource bool `yaml:"is_primary_resource"`
259+
260+
// RequestIDField is the request-message field that receives the
261+
// user-supplied resource ID on a create command, for example instanceId.
262+
RequestIDField string `yaml:"request_id_field,omitempty"`
263+
264+
// ResourceSpec declares this argument as a gcloud concept resource and
265+
// describes its collection and attributes.
266+
ResourceSpec *ResourceSpec `yaml:"resource_spec,omitempty"`
267+
268+
// Required makes the argument mandatory. gcloud rejects invocations that
269+
// leave it unset.
270+
Required bool `yaml:"required"`
271+
272+
// Repeated accepts more than one value for the argument and maps to a
273+
// repeated API field.
274+
Repeated bool `yaml:"repeated,omitempty"`
275+
276+
// Clearable causes gcloud to also generate companion flags such as
277+
// --clear-..., --add-..., and --remove-... on update commands.
278+
Clearable bool `yaml:"clearable,omitempty"`
279+
280+
// Spec lists the sub-fields of a structured argument such as a map. For a
281+
// map field, Spec typically contains entries for key and value.
282+
Spec []ArgSpec `yaml:"spec,omitempty"`
253283

254284
// ResourceMethodParams maps API method parameter names to attribute names
255285
// on a resource argument. Use it when the API method uses a non-standard

internal/surfer/testdata/apis/developerconnect/expected/current/surface/developerconnect/account_connectors/users/_partials/_fetch_access_token_ga.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
The resource name of the AccountConnector in the format
3232
`projects/*/locations/*/accountConnectors/*`.
3333
is_positional: false
34+
is_primary_resource: false
3435
resource_spec:
3536
attributes:
3637
- attribute_name: project

internal/surfer/testdata/apis/developerconnect/expected/current/surface/developerconnect/account_connectors/users/_partials/_fetch_self_ga.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
- arg_name: name
3030
help_text: Name of the AccountConnector resource
3131
is_positional: false
32+
is_primary_resource: false
3233
resource_spec:
3334
attributes:
3435
- attribute_name: project

internal/surfer/testdata/apis/developerconnect/expected/current/surface/developerconnect/account_connectors/users/_partials/_finish_o_auth_flow_ga.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
The resource name of the AccountConnector in the format
4444
`projects/*/locations/*/accountConnectors/*`.
4545
is_positional: false
46+
is_primary_resource: false
4647
resource_spec:
4748
attributes:
4849
- attribute_name: project

internal/surfer/testdata/apis/developerconnect/expected/current/surface/developerconnect/account_connectors/users/_partials/_start_o_auth_flow_ga.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
The resource name of the AccountConnector in the format
3232
`projects/*/locations/*/accountConnectors/*`.
3333
is_positional: false
34+
is_primary_resource: false
3435
resource_spec:
3536
attributes:
3637
- attribute_name: project

internal/surfer/testdata/apis/developerconnect/expected/current/surface/developerconnect/connections/_partials/_fetch_git_hub_installations_ga.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
The resource name of the connection in the format
3232
`projects/*/locations/*/connections/*`.
3333
is_positional: false
34+
is_primary_resource: false
3435
resource_spec:
3536
attributes:
3637
- attribute_name: project

internal/surfer/testdata/apis/developerconnect/expected/current/surface/developerconnect/connections/_partials/_fetch_linkable_git_repositories_ga.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
The name of the Connection.
3232
Format: `projects/*/locations/*/connections/*`.
3333
is_positional: false
34+
is_primary_resource: false
3435
resource_spec:
3536
attributes:
3637
- attribute_name: project

0 commit comments

Comments
 (0)