forked from kubernetes/code-generator
-
Notifications
You must be signed in to change notification settings - Fork 0
/
applyconfiguration.go
458 lines (414 loc) · 17.4 KB
/
applyconfiguration.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generators
import (
"io"
"strings"
"k8s.io/gengo/generator"
"k8s.io/gengo/namer"
"k8s.io/gengo/types"
"k8s.io/klog/v2"
"k8s.io/code-generator/cmd/client-gen/generators/util"
clientgentypes "k8s.io/code-generator/cmd/client-gen/types"
)
// applyConfigurationGenerator produces apply configurations for a given GroupVersion and type.
type applyConfigurationGenerator struct {
generator.DefaultGen
outputPackage string
localPackage types.Name
groupVersion clientgentypes.GroupVersion
applyConfig applyConfig
imports namer.ImportTracker
refGraph refGraph
openAPIType *string // if absent, extraction function cannot be generated
}
var _ generator.Generator = &applyConfigurationGenerator{}
func (g *applyConfigurationGenerator) Filter(_ *generator.Context, t *types.Type) bool {
return t == g.applyConfig.Type
}
func (g *applyConfigurationGenerator) Namers(*generator.Context) namer.NameSystems {
return namer.NameSystems{
"raw": namer.NewRawNamer(g.localPackage.Package, g.imports),
"singularKind": namer.NewPublicNamer(0),
}
}
func (g *applyConfigurationGenerator) Imports(*generator.Context) (imports []string) {
return g.imports.ImportLines()
}
// TypeParams provides a struct that an apply configuration
// is generated for as well as the apply configuration details
// and types referenced by the struct.
type TypeParams struct {
Struct *types.Type
ApplyConfig applyConfig
Tags util.Tags
APIVersion string
ExtractInto *types.Type
ParserFunc *types.Type
OpenAPIType *string
}
type memberParams struct {
TypeParams
Member types.Member
MemberType *types.Type
JSONTags JSONTags
ArgType *types.Type // only set for maps and slices
EmbeddedIn *memberParams // parent embedded member, if any
}
func (g *applyConfigurationGenerator) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
sw := generator.NewSnippetWriter(w, c, "$", "$")
klog.V(5).Infof("processing type %v", t)
typeParams := TypeParams{
Struct: t,
ApplyConfig: g.applyConfig,
Tags: genclientTags(t),
APIVersion: g.groupVersion.ToAPIVersion(),
ExtractInto: extractInto,
ParserFunc: types.Ref(g.outputPackage+"/internal", "Parser"),
OpenAPIType: g.openAPIType,
}
g.generateStruct(sw, typeParams)
if typeParams.Tags.GenerateClient {
switch {
case !hasObjectMetaField(t):
sw.Do(clientgenTypeConstructorWithoutObjectMeta, typeParams)
case typeParams.Tags.NonNamespaced:
sw.Do(clientgenTypeConstructorNonNamespaced, typeParams)
default:
sw.Do(clientgenTypeConstructorNamespaced, typeParams)
}
if typeParams.OpenAPIType != nil {
g.generateClientgenExtract(sw, typeParams, !typeParams.Tags.NoStatus)
}
} else {
if hasTypeMetaField(t) {
sw.Do(constructorWithTypeMeta, typeParams)
} else {
sw.Do(constructor, typeParams)
}
}
g.generateWithFuncs(t, typeParams, sw, nil)
return sw.Error()
}
func hasTypeMetaField(t *types.Type) bool {
for _, member := range t.Members {
if typeMeta.Name == member.Type.Name && member.Embedded {
return true
}
}
return false
}
func hasObjectMetaField(t *types.Type) bool {
for _, member := range t.Members {
if objectMeta.Name == member.Type.Name && member.Embedded {
return true
}
}
return false
}
func blocklisted(t *types.Type, member types.Member) bool {
if objectMeta.Name == t.Name && member.Name == "ManagedFields" {
return true
}
if objectMeta.Name == t.Name && member.Name == "SelfLink" {
return true
}
// Hide any fields which are en route to deletion.
if strings.HasPrefix(member.Name, "ZZZ_") {
return true
}
return false
}
func (g *applyConfigurationGenerator) generateWithFuncs(t *types.Type, typeParams TypeParams, sw *generator.SnippetWriter, embed *memberParams) {
for _, member := range t.Members {
if blocklisted(t, member) {
continue
}
memberType := g.refGraph.applyConfigForType(member.Type)
if g.refGraph.isApplyConfig(member.Type) {
memberType = &types.Type{Kind: types.Pointer, Elem: memberType}
}
if jsonTags, ok := lookupJSONTags(member); ok {
memberParams := memberParams{
TypeParams: typeParams,
Member: member,
MemberType: memberType,
JSONTags: jsonTags,
EmbeddedIn: embed,
}
if memberParams.Member.Embedded {
g.generateWithFuncs(member.Type, typeParams, sw, &memberParams)
if !jsonTags.inline {
// non-inlined embeds are nillable and need a "ensure exists" utility function
sw.Do(ensureEmbedExists, memberParams)
}
continue
}
// For slices where the items are generated apply configuration types, accept varargs of
// pointers of the type as "with" function arguments so the "with" function can be used like so:
// WithFoos(Foo().WithName("x"), Foo().WithName("y"))
if t := deref(member.Type); t.Kind == types.Slice && g.refGraph.isApplyConfig(t.Elem) {
memberParams.ArgType = &types.Type{Kind: types.Pointer, Elem: memberType.Elem}
g.generateMemberWithForSlice(sw, member, memberParams)
continue
}
// Note: There are no maps where the values are generated apply configurations (because
// associative lists are used instead). So if a type like this is ever introduced, the
// default "with" function generator will produce a working (but not entirely convenient "with" function)
// that would be used like so:
// WithMap(map[string]FooApplyConfiguration{*Foo().WithName("x")})
switch memberParams.Member.Type.Kind {
case types.Slice:
memberParams.ArgType = memberType.Elem
g.generateMemberWithForSlice(sw, member, memberParams)
case types.Map:
g.generateMemberWithForMap(sw, memberParams)
default:
g.generateMemberWith(sw, memberParams)
}
}
}
}
func (g *applyConfigurationGenerator) generateStruct(sw *generator.SnippetWriter, typeParams TypeParams) {
sw.Do("// $.ApplyConfig.ApplyConfiguration|public$ represents an declarative configuration of the $.ApplyConfig.Type|public$ type for use\n", typeParams)
sw.Do("// with apply.\n", typeParams)
sw.Do("type $.ApplyConfig.ApplyConfiguration|public$ struct {\n", typeParams)
for _, structMember := range typeParams.Struct.Members {
if blocklisted(typeParams.Struct, structMember) {
continue
}
if structMemberTags, ok := lookupJSONTags(structMember); ok {
if !structMemberTags.inline {
structMemberTags.omitempty = true
}
params := memberParams{
TypeParams: typeParams,
Member: structMember,
MemberType: g.refGraph.applyConfigForType(structMember.Type),
JSONTags: structMemberTags,
}
if structMember.Embedded {
if structMemberTags.inline {
sw.Do("$.MemberType|raw$ `json:\"$.JSONTags$\"`\n", params)
} else {
sw.Do("*$.MemberType|raw$ `json:\"$.JSONTags$\"`\n", params)
}
} else if isNillable(structMember.Type) {
sw.Do("$.Member.Name$ $.MemberType|raw$ `json:\"$.JSONTags$\"`\n", params)
} else {
sw.Do("$.Member.Name$ *$.MemberType|raw$ `json:\"$.JSONTags$\"`\n", params)
}
}
}
sw.Do("}\n", typeParams)
}
func deref(t *types.Type) *types.Type {
for t.Kind == types.Pointer {
t = t.Elem
}
return t
}
func isNillable(t *types.Type) bool {
return t.Kind == types.Slice || t.Kind == types.Map
}
func (g *applyConfigurationGenerator) generateMemberWith(sw *generator.SnippetWriter, memberParams memberParams) {
sw.Do("// With$.Member.Name$ sets the $.Member.Name$ field in the declarative configuration to the given value\n", memberParams)
sw.Do("// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n", memberParams)
sw.Do("// If called multiple times, the $.Member.Name$ field is set to the value of the last call.\n", memberParams)
sw.Do("func (b *$.ApplyConfig.ApplyConfiguration|public$) With$.Member.Name$(value $.MemberType|raw$) *$.ApplyConfig.ApplyConfiguration|public$ {\n", memberParams)
g.ensureEnbedExistsIfApplicable(sw, memberParams)
if g.refGraph.isApplyConfig(memberParams.Member.Type) || isNillable(memberParams.Member.Type) {
sw.Do("b.$.Member.Name$ = value\n", memberParams)
} else {
sw.Do("b.$.Member.Name$ = &value\n", memberParams)
}
sw.Do(" return b\n", memberParams)
sw.Do("}\n", memberParams)
}
func (g *applyConfigurationGenerator) generateMemberWithForSlice(sw *generator.SnippetWriter, member types.Member, memberParams memberParams) {
memberIsPointerToSlice := member.Type.Kind == types.Pointer
if memberIsPointerToSlice {
sw.Do(ensureNonEmbedSliceExists, memberParams)
}
sw.Do("// With$.Member.Name$ adds the given value to the $.Member.Name$ field in the declarative configuration\n", memberParams)
sw.Do("// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n", memberParams)
sw.Do("// If called multiple times, values provided by each call will be appended to the $.Member.Name$ field.\n", memberParams)
sw.Do("func (b *$.ApplyConfig.ApplyConfiguration|public$) With$.Member.Name$(values ...$.ArgType|raw$) *$.ApplyConfig.ApplyConfiguration|public$ {\n", memberParams)
g.ensureEnbedExistsIfApplicable(sw, memberParams)
if memberIsPointerToSlice {
sw.Do("b.ensure$.MemberType.Elem|public$Exists()\n", memberParams)
}
sw.Do(" for i := range values {\n", memberParams)
if memberParams.ArgType.Kind == types.Pointer {
sw.Do("if values[i] == nil {\n", memberParams)
sw.Do(" panic(\"nil value passed to With$.Member.Name$\")\n", memberParams)
sw.Do("}\n", memberParams)
if memberIsPointerToSlice {
sw.Do("*b.$.Member.Name$ = append(*b.$.Member.Name$, *values[i])\n", memberParams)
} else {
sw.Do("b.$.Member.Name$ = append(b.$.Member.Name$, *values[i])\n", memberParams)
}
} else {
if memberIsPointerToSlice {
sw.Do("*b.$.Member.Name$ = append(*b.$.Member.Name$, values[i])\n", memberParams)
} else {
sw.Do("b.$.Member.Name$ = append(b.$.Member.Name$, values[i])\n", memberParams)
}
}
sw.Do(" }\n", memberParams)
sw.Do(" return b\n", memberParams)
sw.Do("}\n", memberParams)
}
func (g *applyConfigurationGenerator) generateMemberWithForMap(sw *generator.SnippetWriter, memberParams memberParams) {
sw.Do("// With$.Member.Name$ puts the entries into the $.Member.Name$ field in the declarative configuration\n", memberParams)
sw.Do("// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n", memberParams)
sw.Do("// If called multiple times, the entries provided by each call will be put on the $.Member.Name$ field,\n", memberParams)
sw.Do("// overwriting an existing map entries in $.Member.Name$ field with the same key.\n", memberParams)
sw.Do("func (b *$.ApplyConfig.ApplyConfiguration|public$) With$.Member.Name$(entries $.MemberType|raw$) *$.ApplyConfig.ApplyConfiguration|public$ {\n", memberParams)
g.ensureEnbedExistsIfApplicable(sw, memberParams)
sw.Do(" if b.$.Member.Name$ == nil && len(entries) > 0 {\n", memberParams)
sw.Do(" b.$.Member.Name$ = make($.MemberType|raw$, len(entries))\n", memberParams)
sw.Do(" }\n", memberParams)
sw.Do(" for k, v := range entries {\n", memberParams)
sw.Do(" b.$.Member.Name$[k] = v\n", memberParams)
sw.Do(" }\n", memberParams)
sw.Do(" return b\n", memberParams)
sw.Do("}\n", memberParams)
}
func (g *applyConfigurationGenerator) ensureEnbedExistsIfApplicable(sw *generator.SnippetWriter, memberParams memberParams) {
// Embedded types that are not inlined must be nillable so they are not included in the apply configuration
// when all their fields are omitted.
if memberParams.EmbeddedIn != nil && !memberParams.EmbeddedIn.JSONTags.inline {
sw.Do("b.ensure$.MemberType.Elem|public$Exists()\n", memberParams.EmbeddedIn)
}
}
var ensureEmbedExists = `
func (b *$.ApplyConfig.ApplyConfiguration|public$) ensure$.MemberType.Elem|public$Exists() {
if b.$.MemberType.Elem|public$ == nil {
b.$.MemberType.Elem|public$ = &$.MemberType.Elem|raw${}
}
}
`
var ensureNonEmbedSliceExists = `
func (b *$.ApplyConfig.ApplyConfiguration|public$) ensure$.MemberType.Elem|public$Exists() {
if b.$.Member.Name$ == nil {
b.$.Member.Name$ = &[]$.MemberType.Elem|raw${}
}
}
`
var clientgenTypeConstructorNamespaced = `
// $.ApplyConfig.Type|public$ constructs an declarative configuration of the $.ApplyConfig.Type|public$ type for use with
// apply.
func $.ApplyConfig.Type|public$(name, namespace string) *$.ApplyConfig.ApplyConfiguration|public$ {
b := &$.ApplyConfig.ApplyConfiguration|public${}
b.WithName(name)
b.WithNamespace(namespace)
b.WithKind("$.ApplyConfig.Type|singularKind$")
b.WithAPIVersion("$.APIVersion$")
return b
}
`
var clientgenTypeConstructorNonNamespaced = `
// $.ApplyConfig.Type|public$ constructs an declarative configuration of the $.ApplyConfig.Type|public$ type for use with
// apply.
func $.ApplyConfig.Type|public$(name string) *$.ApplyConfig.ApplyConfiguration|public$ {
b := &$.ApplyConfig.ApplyConfiguration|public${}
b.WithName(name)
b.WithKind("$.ApplyConfig.Type|singularKind$")
b.WithAPIVersion("$.APIVersion$")
return b
}
`
var clientgenTypeConstructorWithoutObjectMeta = `
// $.ApplyConfig.Type|public$ constructs an declarative configuration of the $.ApplyConfig.Type|public$ type for use with
// apply.
func $.ApplyConfig.Type|public$(name string) *$.ApplyConfig.ApplyConfiguration|public$ {
b := &$.ApplyConfig.ApplyConfiguration|public${}
b.WithKind("$.ApplyConfig.Type|singularKind$")
b.WithAPIVersion("$.APIVersion$")
return b
}
`
var constructorWithTypeMeta = `
// $.ApplyConfig.ApplyConfiguration|public$ constructs an declarative configuration of the $.ApplyConfig.Type|public$ type for use with
// apply.
func $.ApplyConfig.Type|public$() *$.ApplyConfig.ApplyConfiguration|public$ {
b := &$.ApplyConfig.ApplyConfiguration|public${}
b.WithKind("$.ApplyConfig.Type|singularKind$")
b.WithAPIVersion("$.APIVersion$")
return b
}
`
var constructor = `
// $.ApplyConfig.ApplyConfiguration|public$ constructs an declarative configuration of the $.ApplyConfig.Type|public$ type for use with
// apply.
func $.ApplyConfig.Type|public$() *$.ApplyConfig.ApplyConfiguration|public$ {
return &$.ApplyConfig.ApplyConfiguration|public${}
}
`
func (g *applyConfigurationGenerator) generateClientgenExtract(sw *generator.SnippetWriter, typeParams TypeParams, includeStatus bool) {
sw.Do(`
// Extract$.ApplyConfig.Type|public$ extracts the applied configuration owned by fieldManager from
// $.Struct|private$. If no managedFields are found in $.Struct|private$ for fieldManager, a
// $.ApplyConfig.ApplyConfiguration|public$ is returned with only the Name, Namespace (if applicable),
// APIVersion and Kind populated. It is possible that no managed fields were found for because other
// field managers have taken ownership of all the fields previously owned by fieldManager, or because
// the fieldManager never owned fields any fields.
// $.Struct|private$ must be a unmodified $.Struct|public$ API object that was retrieved from the Kubernetes API.
// Extract$.ApplyConfig.Type|public$ provides a way to perform a extract/modify-in-place/apply workflow.
// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously
// applied if another fieldManager has updated or force applied any of the previously applied fields.
// Experimental!
func Extract$.ApplyConfig.Type|public$($.Struct|private$ *$.Struct|raw$, fieldManager string) (*$.ApplyConfig.ApplyConfiguration|public$, error) {
return extract$.ApplyConfig.Type|public$($.Struct|private$, fieldManager, "")
}`, typeParams)
if includeStatus {
sw.Do(`
// Extract$.ApplyConfig.Type|public$Status is the same as Extract$.ApplyConfig.Type|public$ except
// that it extracts the status subresource applied configuration.
// Experimental!
func Extract$.ApplyConfig.Type|public$Status($.Struct|private$ *$.Struct|raw$, fieldManager string) (*$.ApplyConfig.ApplyConfiguration|public$, error) {
return extract$.ApplyConfig.Type|public$($.Struct|private$, fieldManager, "status")
}
`, typeParams)
}
if !hasObjectMetaField(typeParams.Struct) {
sw.Do(`
func extract$.ApplyConfig.Type|public$($.Struct|private$ *$.Struct|raw$, fieldManager string, subresource string) (*$.ApplyConfig.ApplyConfiguration|public$, error) {
b := &$.ApplyConfig.ApplyConfiguration|public${}
err := $.ExtractInto|raw$($.Struct|private$, $.ParserFunc|raw$().Type("$.OpenAPIType$"), fieldManager, b, subresource)
if err != nil {
return nil, err
}
`, typeParams)
} else { // it has objectMeta
sw.Do(`
func extract$.ApplyConfig.Type|public$($.Struct|private$ *$.Struct|raw$, fieldManager string, subresource string) (*$.ApplyConfig.ApplyConfiguration|public$, error) {
b := &$.ApplyConfig.ApplyConfiguration|public${}
err := $.ExtractInto|raw$($.Struct|private$, $.ParserFunc|raw$().Type("$.OpenAPIType$"), fieldManager, b, subresource)
if err != nil {
return nil, err
}
b.WithName($.Struct|private$.Name)
`, typeParams)
if !typeParams.Tags.NonNamespaced {
sw.Do("b.WithNamespace($.Struct|private$.Namespace)\n", typeParams)
}
}
sw.Do(`
b.WithKind("$.ApplyConfig.Type|singularKind$")
b.WithAPIVersion("$.APIVersion$")
return b, nil
}
`, typeParams)
}