15
15
package updatetypes
16
16
17
17
import (
18
- "bytes "
18
+ "context "
19
19
"fmt"
20
20
"os"
21
- "strings"
22
21
23
- "github.com/GoogleCloudPlatform/k8s-config-connector/dev/tools/controllerbuilder/pkg/codegen"
24
- "github.com/GoogleCloudPlatform/k8s-config-connector/dev/tools/controllerbuilder/pkg/gocode"
25
22
"github.com/GoogleCloudPlatform/k8s-config-connector/dev/tools/controllerbuilder/pkg/options"
23
+ "github.com/GoogleCloudPlatform/k8s-config-connector/dev/tools/controllerbuilder/pkg/typeupdater"
26
24
27
25
"github.com/spf13/cobra"
28
- "google.golang.org/protobuf/proto"
29
- "google.golang.org/protobuf/reflect/protodesc"
30
- "google.golang.org/protobuf/reflect/protoreflect"
31
- "google.golang.org/protobuf/types/descriptorpb"
32
- "k8s.io/apimachinery/pkg/util/sets"
33
- "k8s.io/klog/v2"
34
26
)
35
27
36
- const kccProtoPrefix = "+kcc:proto="
37
-
38
28
type UpdateTypeOptions struct {
39
29
* options.GenerateOptions
40
30
41
- parentMessageFullName string
42
- newField string
43
- ignoredFields string // TODO: could be part of GenerateOptions
44
- apiDirectory string
45
- goPackagePath string
31
+ parentNessage string // The fully qualified name of the parent prroto message of the field to be inserted
32
+ fieldToInsert string
33
+ ignoredFields string // TODO: could be part of GenerateOptions
34
+ apiDirectory string
35
+ goPackagePath string
46
36
}
47
37
48
38
func (o * UpdateTypeOptions ) InitDefaults () error {
@@ -56,8 +46,9 @@ func (o *UpdateTypeOptions) InitDefaults() error {
56
46
}
57
47
58
48
func (o * UpdateTypeOptions ) BindFlags (cmd * cobra.Command ) {
59
- cmd .Flags ().StringVar (& o .parentMessageFullName , "parent-message-full-name" , o .parentMessageFullName , "Fully qualified name of the proto message holding the new field" )
60
- cmd .Flags ().StringVar (& o .newField , "new-field" , o .newField , "Name of the new field" )
49
+ cmd .Flags ().StringVar (& o .parentNessage , "parent-message" , o .parentNessage , "Fully qualified name of the proto message holding the new field. e.g. `google.cloud.bigquery.datatransfer.v1.TransferConfig`" )
50
+ cmd .Flags ().StringVar (& o .fieldToInsert , "field-to-insert" , o .fieldToInsert , "Name of the new field to be inserted, e.g. `schedule_options_v2`" )
51
+ // TODO: Update this flag to accept a file path pointing to the ignored fields YAML file.
61
52
cmd .Flags ().StringVar (& o .ignoredFields , "ignored-fields" , o .ignoredFields , "Comma-separated list of fields to ignore" )
62
53
cmd .Flags ().StringVar (& o .apiDirectory , "api-dir" , o .apiDirectory , "Base directory for APIs" )
63
54
cmd .Flags ().StringVar (& o .goPackagePath , "api-go-package-path" , o .goPackagePath , "Package path" )
@@ -77,8 +68,8 @@ func BuildCommand(baseOptions *options.GenerateOptions) *cobra.Command {
77
68
Use : "update-types" ,
78
69
Short : "update KRM types for a proto service" ,
79
70
RunE : func (cmd * cobra.Command , args []string ) error {
80
- updater := NewTypeUpdater ( opt )
81
- if err := updater . Run ( ); err != nil {
71
+ ctx := cmd . Context ( )
72
+ if err := runTypeUpdater ( ctx , opt ); err != nil {
82
73
return err
83
74
}
84
75
return nil
@@ -90,159 +81,22 @@ func BuildCommand(baseOptions *options.GenerateOptions) *cobra.Command {
90
81
return cmd
91
82
}
92
83
93
- type TypeUpdater struct {
94
- opts * UpdateTypeOptions
95
- newField newProtoField
96
- dependentMessages map [string ]protoreflect.MessageDescriptor // key: fully qualified name of proto message
97
- generatedGoField generatedGoField // TODO: support multiple new fields
98
- generatedGoStructs []generatedGoStruct
99
- }
100
-
101
- type newProtoField struct {
102
- field protoreflect.FieldDescriptor
103
- parentMessage protoreflect.MessageDescriptor
104
- }
105
-
106
- type generatedGoField struct {
107
- parentMessage string // fully qualified name of the parent proto message of this field
108
- content []byte // the content of the generated Go field
109
- }
110
-
111
- type generatedGoStruct struct {
112
- name string // fully qualified name of the proto message
113
- content []byte // the content of the generated Go struct
114
- }
115
-
116
- func NewTypeUpdater (opts * UpdateTypeOptions ) * TypeUpdater {
117
- return & TypeUpdater {
118
- opts : opts ,
119
- }
120
- }
121
-
122
- func (u * TypeUpdater ) Run () error {
123
- // 1. find new field and its dependent proto messages that needs to be generated
124
- if err := u .analyze (); err != nil {
125
- return nil
84
+ func runTypeUpdater (ctx context.Context , opt * UpdateTypeOptions ) error {
85
+ if opt .apiDirectory == "" {
86
+ return fmt .Errorf ("--api-dir is required" )
126
87
}
127
88
128
- // 2. generate Go types for the new field and its dependent proto messages
129
- if err := u .generate (); err != nil {
130
- return err
131
- }
132
-
133
- // 3. insert the generated Go code back to files
134
- if err := u .insertGoField (); err != nil {
135
- return err
89
+ typeUpdaterOpts := & typeupdater.UpdaterOptions {
90
+ ProtoSourcePath : opt .GenerateOptions .ProtoSourcePath ,
91
+ ParentMessageFullName : opt .parentNessage ,
92
+ FieldToInsert : opt .fieldToInsert ,
93
+ IgnoredFields : opt .ignoredFields ,
94
+ APIDirectory : opt .apiDirectory ,
95
+ GoPackagePath : opt .goPackagePath ,
136
96
}
137
- if err := u .insertGoMessages (); err != nil {
138
- return err
139
- }
140
-
141
- return nil
142
- }
143
-
144
- // anaylze finds the new field, its parent message, and all dependent messages that need to be generated.
145
- func (u * TypeUpdater ) analyze () error {
146
- parentMessage , newField , err := findNewField (u .opts .ProtoSourcePath , u .opts .parentMessageFullName , u .opts .newField )
147
- if err != nil {
148
- return err
149
- }
150
- u .newField = newProtoField {
151
- field : newField ,
152
- parentMessage : parentMessage ,
153
- }
154
-
155
- msgs , err := findDependentMsgs (newField , sets .NewString (strings .Split (u .opts .ignoredFields , "," )... ))
156
- if err != nil {
157
- return err
158
- }
159
-
160
- codegen .RemoveNotMappedToGoStruct (msgs )
161
-
162
- if err := removeAlreadyGenerated (u .opts .goPackagePath , u .opts .apiDirectory , msgs ); err != nil {
97
+ updater := typeupdater .NewTypeUpdater (typeUpdaterOpts )
98
+ if err := updater .Run (); err != nil {
163
99
return err
164
100
}
165
- u .dependentMessages = msgs
166
- return nil
167
- }
168
-
169
- // findNewField locates the parent message and the new field in the proto file
170
- func findNewField (pbSourcePath , parentMsgFullName , newFieldName string ) (protoreflect.MessageDescriptor , protoreflect.FieldDescriptor , error ) {
171
- fileData , err := os .ReadFile (pbSourcePath )
172
- if err != nil {
173
- return nil , nil , fmt .Errorf ("reading %q: %w" , pbSourcePath , err )
174
- }
175
-
176
- fds := & descriptorpb.FileDescriptorSet {}
177
- if err := proto .Unmarshal (fileData , fds ); err != nil {
178
- return nil , nil , fmt .Errorf ("unmarshalling %q: %w" , pbSourcePath , err )
179
- }
180
-
181
- files , err := protodesc .NewFiles (fds )
182
- if err != nil {
183
- return nil , nil , fmt .Errorf ("building file description: %w" , err )
184
- }
185
-
186
- // Find the parent message
187
- messageDesc , err := files .FindDescriptorByName (protoreflect .FullName (parentMsgFullName ))
188
- if err != nil {
189
- return nil , nil , err
190
- }
191
- msgDesc , ok := messageDesc .(protoreflect.MessageDescriptor )
192
- if ! ok {
193
- return nil , nil , fmt .Errorf ("unexpected descriptor type: %T" , msgDesc )
194
- }
195
-
196
- // Find the new field in parent message
197
- fieldDesc := msgDesc .Fields ().ByName (protoreflect .Name (newFieldName ))
198
- if fieldDesc == nil {
199
- return nil , nil , fmt .Errorf ("field not found in message" )
200
- }
201
-
202
- return msgDesc , fieldDesc , nil
203
- }
204
-
205
- // findDependentMsgs finds all dependent proto messages for the given field, ignoring specified fields
206
- func findDependentMsgs (field protoreflect.FieldDescriptor , ignoredProtoFields sets.String ) (map [string ]protoreflect.MessageDescriptor , error ) {
207
- deps := make (map [string ]protoreflect.MessageDescriptor )
208
- codegen .FindDependenciesForField (field , deps , ignoredProtoFields )
209
- return deps , nil
210
- }
211
-
212
- // removeAlreadyGenerated removes proto messages that have already been generated (including manually edited)
213
- func removeAlreadyGenerated (goPackagePath , outputAPIDirectory string , targets map [string ]protoreflect.MessageDescriptor ) error {
214
- packages , err := gocode .LoadPackageTree (goPackagePath , outputAPIDirectory )
215
- if err != nil {
216
- return err
217
- }
218
- for _ , p := range packages {
219
- for _ , s := range p .Structs {
220
- if annotation := s .GetAnnotation ("+kcc:proto" ); annotation != "" {
221
- delete (targets , annotation )
222
- }
223
- }
224
- }
225
- return nil
226
- }
227
-
228
- func (u * TypeUpdater ) generate () error {
229
- var buf bytes.Buffer
230
- klog .Infof ("generate Go code for field %s" , u .newField .field .Name ())
231
- codegen .WriteField (& buf , u .newField .field , u .newField .parentMessage , 0 )
232
- u .generatedGoField = generatedGoField {
233
- parentMessage : string (u .newField .parentMessage .FullName ()),
234
- content : buf .Bytes (),
235
- }
236
-
237
- for _ , msg := range u .dependentMessages {
238
- var buf bytes.Buffer
239
- klog .Infof ("generate Go code for messge %s" , msg .FullName ())
240
- codegen .WriteMessage (& buf , msg )
241
- u .generatedGoStructs = append (u .generatedGoStructs ,
242
- generatedGoStruct {
243
- name : string (msg .FullName ()),
244
- content : buf .Bytes (),
245
- })
246
- }
247
101
return nil
248
102
}
0 commit comments