Skip to content

Commit 37ed41e

Browse files
authored
feat(internal/gcloud): add structs for gcloud config YAML (#2566)
Introduce Go structs for parsing a sample gcloud config YAML file. This is a first step to evaluate if we can use sidekick for automating gcloud command generation.
1 parent 9dfd11e commit 37ed41e

File tree

4 files changed

+407
-1
lines changed

4 files changed

+407
-1
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ require (
1717
google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e
1818
google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e
1919
google.golang.org/protobuf v1.36.6
20+
gopkg.in/yaml.v2 v2.4.0
2021
)
2122

2223
require (
@@ -33,6 +34,5 @@ require (
3334
golang.org/x/text v0.24.0 // indirect
3435
google.golang.org/grpc v1.71.1 // indirect
3536
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
36-
gopkg.in/yaml.v2 v2.4.0 // indirect
3737
gopkg.in/yaml.v3 v3.0.1 // indirect
3838
)

internal/gcloud/gcloud.go

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package gcloud
16+
17+
// Config represents the top-level schema of a gcloud config YAML file.
18+
type Config struct {
19+
// ServiceName is the name of a service. Each gcloud.yaml file should
20+
// correlate to a single service config with one or more APIs defined.
21+
ServiceName string `yaml:"service_name"`
22+
23+
// APIs describes the APIs for which to generate a gcloud surface.
24+
APIs []API `yaml:"apis,omitempty"`
25+
26+
// ResourcePatterns describes resource patterns not included in
27+
// descriptors, providing additional patterns that might be used for
28+
// resource identification or command generation.
29+
ResourcePatterns []ResourcePattern `yaml:"resource_patterns,omitempty"`
30+
}
31+
32+
// API describes an API to generate a surface for. This structure holds
33+
// configurations specific to a single API within the gcloud surface.
34+
type API struct {
35+
// Name is the name of the API. This should be the API name as it appears
36+
// in the normalized service config (e.g., "compute.googleapis.com").
37+
Name string `yaml:"name"`
38+
39+
// APIVersion is the API version of the API (e.g., "v1", "v2beta1").
40+
APIVersion string `yaml:"api_version,omitempty"`
41+
42+
// SupportsStarUpdateMasks indicates that this API supports '*' updateMasks
43+
// in accordance with https://google.aip.dev/134#request-message. The
44+
// default is assumed to be true for AIP compliant APIs.
45+
SupportsStarUpdateMasks bool `yaml:"supports_star_update_masks,omitempty"`
46+
47+
// RootIsHidden applies the gcloud 'hidden' flag to the root command group
48+
// of the generated surface. When true, the top-level command group for
49+
// this API will not appear in `--help` output by default. See
50+
// go/gcloud-advanced-topics#hiding-commands-and-command-groups for more
51+
// details.
52+
RootIsHidden bool `yaml:"root_is_hidden"`
53+
54+
// ReleaseTracks are the gcloud release tracks this surface should appear
55+
// in. This determines the visibility and stability level of the generated
56+
// commands and resources.
57+
ReleaseTracks []ReleaseTrack `yaml:"release_tracks,omitempty"`
58+
59+
// HelpText contains all help text configurations for the surfaces
60+
// including groups, commands, resources, and flags/arguments related to
61+
// this API.
62+
HelpText *HelpText `yaml:"help_text,omitempty"`
63+
64+
// OutputFormatting contains all output formatting rules for commands
65+
// within this API. These rules dictate how the results of commands are
66+
// displayed to the user.
67+
OutputFormatting []*OutputFormatting `yaml:"output_formatting,omitempty"`
68+
69+
// CommandOperationsConfig contains long running operations config for
70+
// methods within this API. This allows customization of how asynchronous
71+
// operations are handled and displayed.
72+
CommandOperationsConfig []*CommandOperationsConfig `yaml:"command_operations_config,omitempty"`
73+
}
74+
75+
// HelpText contains rules for various types of help text within an API
76+
// surface. It groups help text definitions by the type of CLI element they
77+
// apply to.
78+
type HelpText struct {
79+
// ServiceRules defines help text rules specifically for services.
80+
ServiceRules []*HelpTextRule `yaml:"service_rules,omitempty"`
81+
82+
// MessageRules defines help text rules specifically for messages (resource
83+
// command groups).
84+
MessageRules []*HelpTextRule `yaml:"message_rules,omitempty"`
85+
86+
// MethodRules defines help text rules specifically for API methods
87+
// (commands).
88+
MethodRules []*HelpTextRule `yaml:"method_rules,omitempty"`
89+
90+
// FieldRules defines help text rules specifically for individual fields
91+
// (flags/arguments).
92+
FieldRules []*HelpTextRule `yaml:"field_rules,omitempty"`
93+
}
94+
95+
// HelpTextRule maps an API selector to its corresponding HelpTextElement.
96+
// This allows for targeted help text customization based on specific API
97+
// elements.
98+
type HelpTextRule struct {
99+
// Selector is a comma-separated list of patterns for any element such as a
100+
// method, a field, an enum value. Each pattern is a qualified name of the
101+
// element which may end in "*", indicating a wildcard. Wildcards are only
102+
// allowed at the end and for a whole component of the qualified name, i.e.
103+
// "foo.*" is ok, but not "foo.b*" or "foo.*.bar".
104+
//
105+
// Wildcard may not be applicable for some elements, in those cases an
106+
// 'InvalidSelectorWildcardError' error will be thrown. Additionally, some
107+
// gcloud data elements expect a singular selector, if a comma separated
108+
// selector string is passed, a 'InvalidSelectorList' error will be thrown.
109+
//
110+
// See http://google3/google/api/documentation.proto;l=253;rcl=525006895
111+
// for API selector details.
112+
Selector string `yaml:"selector,omitempty"`
113+
114+
// HelpText contains the detailed help text content for the selected
115+
// element.
116+
HelpText *HelpTextElement `yaml:"help_text,omitempty"`
117+
}
118+
119+
// HelpTextElement describes the actual content of the help text for a CLI
120+
// Element. This structure holds the brief, description, and examples for a
121+
// given element. This can be linked to an individual API RPC/Command, a
122+
// Resource Message, Enum, Service, Enum.value or Message.field.
123+
type HelpTextElement struct {
124+
// Brief is a concise, single-line summary of the help text for the CLI
125+
// element.
126+
Brief string `yaml:"brief,omitempty"`
127+
128+
// Description provides a detailed, multi-line description for the CLI
129+
// element.
130+
Description string `yaml:"description,omitempty"`
131+
132+
// Examples provides a list of string examples illustrating how to use the
133+
// CLI element.
134+
Examples []string `yaml:"examples,omitempty"`
135+
}
136+
137+
// OutputFormatting contains a collection of command output formatting rules.
138+
// These rules are used to specify how the output of gcloud commands should be
139+
// presented.
140+
type OutputFormatting struct {
141+
// Selector is a comma-separated list of patterns for any element such as a
142+
// method, a field, an enum value. Each pattern is a qualified name of the
143+
// element which may end in "*", indicating a wildcard. Wildcards are only
144+
// allowed at the end and for a whole component of the qualified name, i.e.
145+
// "foo.*" is ok, but not "foo.b*" or "foo.*.bar".
146+
//
147+
// Wildcard may not be applicable for some elements, in those cases an
148+
// 'InvalidSelectorWildcardError' error will be thrown. Additionally, some
149+
// gcloud data elements expect a singular selector, if a comma separated
150+
// selector string is passed, a 'InvalidSelectorList' error will be thrown.
151+
//
152+
// See http://google3/google/api/documentation.proto;l=253;rcl=525006895
153+
// for API selector details. Must point to a single RPC/command. Wildcards
154+
// ('*') not allowed for output formatting.
155+
Selector string `yaml:"selector"`
156+
157+
// Format is the output formatting string to apply. This string typically
158+
// follows the `gcloud topic formats` specification (e.g., "table(name,
159+
// createTime)", "json").
160+
Format string `yaml:"format"`
161+
}
162+
163+
// CommandOperationsConfig contains a collection of command operations
164+
// configuration rules. These rules govern the behavior of long-running
165+
// operations triggered by gcloud commands.
166+
type CommandOperationsConfig struct {
167+
// Selector is a comma-separated list of patterns for any element such as a
168+
// method, a field, an enum value. Each pattern is a qualified name of the
169+
// element which may end in "*", indicating a wildcard. Wildcards are only
170+
// allowed at the end and for a whole component of the qualified name, i.e.
171+
// "foo.*" is ok, but not "foo.b*" or "foo.*.bar".
172+
//
173+
// Wildcard may not be applicable for some elements, in those cases an
174+
// 'InvalidSelectorWildcardError' error will be thrown. Additionally, some
175+
// gcloud data elements expect a singular selector, if a comma separated
176+
// selector string is passed, a 'InvalidSelectorList' error will be thrown.
177+
//
178+
// See http://google3/google/api/documentation.proto;l=253;rcl=525006895
179+
// for API selector details.
180+
Selector string `yaml:"selector"`
181+
182+
// DisplayOperationResult determines whether to display the resource result
183+
// in the output of the command by default. Set to `true` to display the
184+
// operation result instead of the final resource. See
185+
// http://go/gcloud-creating-commands#async for more details.
186+
DisplayOperationResult bool `yaml:"display_operation_result"`
187+
}
188+
189+
// ReleaseTrack is an enumeration of the gcloud release tracks. These indicate
190+
// the stability level and visibility of commands and features.
191+
type ReleaseTrack string
192+
193+
const (
194+
// ReleaseTrackAlpha represents the ALPHA release track. Features in this
195+
// track are experimental and subject to change.
196+
ReleaseTrackAlpha ReleaseTrack = "ALPHA"
197+
198+
// ReleaseTrackBeta represents the BETA release track. Features in this
199+
// track are more stable than ALPHA but may still undergo minor changes.
200+
ReleaseTrackBeta ReleaseTrack = "BETA"
201+
202+
// ReleaseTrackGA represents the GA (Generally Available) release track.
203+
// Features in this track are stable and suitable for production use.
204+
ReleaseTrackGA ReleaseTrack = "GA"
205+
)
206+
207+
// ResourcePattern describes resource patterns not explicitly included in API
208+
// descriptors. These patterns can be used to define additional resource
209+
// identifiers or custom resource structures.
210+
type ResourcePattern struct {
211+
// Type is the resource type (e.g., "example.googleapis.com/Service").
212+
Type string `yaml:"type"`
213+
214+
// Patterns is a list of resource patterns (e.g.,
215+
// "projects/{project}/locations/{location}/services/{service}"). These
216+
// define the structure of resource names.
217+
Patterns []string `yaml:"patterns,omitempty"`
218+
219+
// APIVersion is the API version associated with this resource pattern
220+
// (e.g., "v1").
221+
APIVersion string `yaml:"api_version,omitempty"`
222+
}

internal/gcloud/gcloud_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package gcloud
16+
17+
import (
18+
"os"
19+
"strings"
20+
"testing"
21+
22+
"github.com/google/go-cmp/cmp"
23+
"gopkg.in/yaml.v2"
24+
)
25+
26+
func TestGcloudConfig(t *testing.T) {
27+
data, err := os.ReadFile("testdata/parallelstore/gcloud.yaml")
28+
if err != nil {
29+
t.Fatalf("failed to read temporary YAML file: %v", err)
30+
}
31+
32+
var config Config
33+
if err := yaml.Unmarshal(data, &config); err != nil {
34+
t.Fatalf("failed to unmarshal YAML: %v", err)
35+
}
36+
37+
got, err := yaml.Marshal(&config)
38+
if err != nil {
39+
t.Fatalf("failed to marshal struct to YAML: %v", err)
40+
}
41+
42+
var index int
43+
lines := strings.Split(string(data), "\n")
44+
for i, line := range lines {
45+
if strings.HasPrefix(line, "#") {
46+
// Skip the header, and the new lines after the header
47+
index = i + 2
48+
continue
49+
}
50+
}
51+
want := strings.Join(lines[index:], "\n")
52+
if diff := cmp.Diff(want, string(got)); diff != "" {
53+
t.Errorf("mismatch(-want, +got)\n%s", diff)
54+
}
55+
}

0 commit comments

Comments
 (0)