1/*
2Copyright 2015 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17// Package generators has the generators for the client-gen utility.
18package generators
19
20import (
21	"path/filepath"
22	"strings"
23
24	clientgenargs "k8s.io/code-generator/cmd/client-gen/args"
25	"k8s.io/code-generator/cmd/client-gen/generators/fake"
26	"k8s.io/code-generator/cmd/client-gen/generators/scheme"
27	"k8s.io/code-generator/cmd/client-gen/generators/util"
28	"k8s.io/code-generator/cmd/client-gen/path"
29	clientgentypes "k8s.io/code-generator/cmd/client-gen/types"
30	codegennamer "k8s.io/code-generator/pkg/namer"
31	genutil "k8s.io/code-generator/pkg/util"
32	"k8s.io/gengo/args"
33	"k8s.io/gengo/generator"
34	"k8s.io/gengo/namer"
35	"k8s.io/gengo/types"
36
37	"k8s.io/klog/v2"
38)
39
40// NameSystems returns the name system used by the generators in this package.
41func NameSystems(pluralExceptions map[string]string) namer.NameSystems {
42	lowercaseNamer := namer.NewAllLowercasePluralNamer(pluralExceptions)
43
44	publicNamer := &ExceptionNamer{
45		Exceptions: map[string]string{
46			// these exceptions are used to deconflict the generated code
47			// you can put your fully qualified package like
48			// to generate a name that doesn't conflict with your group.
49			// "k8s.io/apis/events/v1beta1.Event": "EventResource"
50		},
51		KeyFunc: func(t *types.Type) string {
52			return t.Name.Package + "." + t.Name.Name
53		},
54		Delegate: namer.NewPublicNamer(0),
55	}
56	privateNamer := &ExceptionNamer{
57		Exceptions: map[string]string{
58			// these exceptions are used to deconflict the generated code
59			// you can put your fully qualified package like
60			// to generate a name that doesn't conflict with your group.
61			// "k8s.io/apis/events/v1beta1.Event": "eventResource"
62		},
63		KeyFunc: func(t *types.Type) string {
64			return t.Name.Package + "." + t.Name.Name
65		},
66		Delegate: namer.NewPrivateNamer(0),
67	}
68	publicPluralNamer := &ExceptionNamer{
69		Exceptions: map[string]string{
70			// these exceptions are used to deconflict the generated code
71			// you can put your fully qualified package like
72			// to generate a name that doesn't conflict with your group.
73			// "k8s.io/apis/events/v1beta1.Event": "EventResource"
74		},
75		KeyFunc: func(t *types.Type) string {
76			return t.Name.Package + "." + t.Name.Name
77		},
78		Delegate: namer.NewPublicPluralNamer(pluralExceptions),
79	}
80	privatePluralNamer := &ExceptionNamer{
81		Exceptions: map[string]string{
82			// you can put your fully qualified package like
83			// to generate a name that doesn't conflict with your group.
84			// "k8s.io/apis/events/v1beta1.Event": "eventResource"
85			// these exceptions are used to deconflict the generated code
86			"k8s.io/apis/events/v1beta1.Event":        "eventResources",
87			"k8s.io/kubernetes/pkg/apis/events.Event": "eventResources",
88		},
89		KeyFunc: func(t *types.Type) string {
90			return t.Name.Package + "." + t.Name.Name
91		},
92		Delegate: namer.NewPrivatePluralNamer(pluralExceptions),
93	}
94
95	return namer.NameSystems{
96		"singularKind":       namer.NewPublicNamer(0),
97		"public":             publicNamer,
98		"private":            privateNamer,
99		"raw":                namer.NewRawNamer("", nil),
100		"publicPlural":       publicPluralNamer,
101		"privatePlural":      privatePluralNamer,
102		"allLowercasePlural": lowercaseNamer,
103		"resource":           codegennamer.NewTagOverrideNamer("resourceName", lowercaseNamer),
104	}
105}
106
107// ExceptionNamer allows you specify exceptional cases with exact names.  This allows you to have control
108// for handling various conflicts, like group and resource names for instance.
109type ExceptionNamer struct {
110	Exceptions map[string]string
111	KeyFunc    func(*types.Type) string
112
113	Delegate namer.Namer
114}
115
116// Name provides the requested name for a type.
117func (n *ExceptionNamer) Name(t *types.Type) string {
118	key := n.KeyFunc(t)
119	if exception, ok := n.Exceptions[key]; ok {
120		return exception
121	}
122	return n.Delegate.Name(t)
123}
124
125// DefaultNameSystem returns the default name system for ordering the types to be
126// processed by the generators in this package.
127func DefaultNameSystem() string {
128	return "public"
129}
130
131func packageForGroup(gv clientgentypes.GroupVersion, typeList []*types.Type, clientsetPackage string, groupPackageName string, groupGoName string, apiPath string, srcTreePath string, inputPackage string, applyBuilderPackage string, boilerplate []byte) generator.Package {
132	groupVersionClientPackage := filepath.Join(clientsetPackage, "typed", strings.ToLower(groupPackageName), strings.ToLower(gv.Version.NonEmpty()))
133	return &generator.DefaultPackage{
134		PackageName: strings.ToLower(gv.Version.NonEmpty()),
135		PackagePath: groupVersionClientPackage,
136		HeaderText:  boilerplate,
137		PackageDocumentation: []byte(
138			`// This package has the automatically generated typed clients.
139`),
140		// GeneratorFunc returns a list of generators. Each generator makes a
141		// single file.
142		GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
143			generators = []generator.Generator{
144				// Always generate a "doc.go" file.
145				generator.DefaultGen{OptionalName: "doc"},
146			}
147			// Since we want a file per type that we generate a client for, we
148			// have to provide a function for this.
149			for _, t := range typeList {
150				generators = append(generators, &genClientForType{
151					DefaultGen: generator.DefaultGen{
152						OptionalName: strings.ToLower(c.Namers["private"].Name(t)),
153					},
154					outputPackage:             groupVersionClientPackage,
155					inputPackage:              inputPackage,
156					clientsetPackage:          clientsetPackage,
157					applyConfigurationPackage: applyBuilderPackage,
158					group:                     gv.Group.NonEmpty(),
159					version:                   gv.Version.String(),
160					groupGoName:               groupGoName,
161					typeToMatch:               t,
162					imports:                   generator.NewImportTracker(),
163				})
164			}
165
166			generators = append(generators, &genGroup{
167				DefaultGen: generator.DefaultGen{
168					OptionalName: groupPackageName + "_client",
169				},
170				outputPackage:    groupVersionClientPackage,
171				inputPackage:     inputPackage,
172				clientsetPackage: clientsetPackage,
173				group:            gv.Group.NonEmpty(),
174				version:          gv.Version.String(),
175				groupGoName:      groupGoName,
176				apiPath:          apiPath,
177				types:            typeList,
178				imports:          generator.NewImportTracker(),
179			})
180
181			expansionFileName := "generated_expansion"
182			generators = append(generators, &genExpansion{
183				groupPackagePath: filepath.Join(srcTreePath, groupVersionClientPackage),
184				DefaultGen: generator.DefaultGen{
185					OptionalName: expansionFileName,
186				},
187				types: typeList,
188			})
189
190			return generators
191		},
192		FilterFunc: func(c *generator.Context, t *types.Type) bool {
193			return util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)).GenerateClient
194		},
195	}
196}
197
198func packageForClientset(customArgs *clientgenargs.CustomArgs, clientsetPackage string, groupGoNames map[clientgentypes.GroupVersion]string, boilerplate []byte) generator.Package {
199	return &generator.DefaultPackage{
200		PackageName: customArgs.ClientsetName,
201		PackagePath: clientsetPackage,
202		HeaderText:  boilerplate,
203		PackageDocumentation: []byte(
204			`// This package has the automatically generated clientset.
205`),
206		// GeneratorFunc returns a list of generators. Each generator generates a
207		// single file.
208		GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
209			generators = []generator.Generator{
210				// Always generate a "doc.go" file.
211				generator.DefaultGen{OptionalName: "doc"},
212
213				&genClientset{
214					DefaultGen: generator.DefaultGen{
215						OptionalName: "clientset",
216					},
217					groups:           customArgs.Groups,
218					groupGoNames:     groupGoNames,
219					clientsetPackage: clientsetPackage,
220					outputPackage:    customArgs.ClientsetName,
221					imports:          generator.NewImportTracker(),
222				},
223			}
224			return generators
225		},
226	}
227}
228
229func packageForScheme(customArgs *clientgenargs.CustomArgs, clientsetPackage string, srcTreePath string, groupGoNames map[clientgentypes.GroupVersion]string, boilerplate []byte) generator.Package {
230	schemePackage := filepath.Join(clientsetPackage, "scheme")
231
232	// create runtime.Registry for internal client because it has to know about group versions
233	internalClient := false
234NextGroup:
235	for _, group := range customArgs.Groups {
236		for _, v := range group.Versions {
237			if v.String() == "" {
238				internalClient = true
239				break NextGroup
240			}
241		}
242	}
243
244	return &generator.DefaultPackage{
245		PackageName: "scheme",
246		PackagePath: schemePackage,
247		HeaderText:  boilerplate,
248		PackageDocumentation: []byte(
249			`// This package contains the scheme of the automatically generated clientset.
250`),
251		// GeneratorFunc returns a list of generators. Each generator generates a
252		// single file.
253		GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
254			generators = []generator.Generator{
255				// Always generate a "doc.go" file.
256				generator.DefaultGen{OptionalName: "doc"},
257
258				&scheme.GenScheme{
259					DefaultGen: generator.DefaultGen{
260						OptionalName: "register",
261					},
262					InputPackages:  customArgs.GroupVersionPackages(),
263					OutputPackage:  schemePackage,
264					OutputPath:     filepath.Join(srcTreePath, schemePackage),
265					Groups:         customArgs.Groups,
266					GroupGoNames:   groupGoNames,
267					ImportTracker:  generator.NewImportTracker(),
268					CreateRegistry: internalClient,
269				},
270			}
271			return generators
272		},
273	}
274}
275
276// applyGroupOverrides applies group name overrides to each package, if applicable. If there is a
277// comment of the form "// +groupName=somegroup" or "// +groupName=somegroup.foo.bar.io", use the
278// first field (somegroup) as the name of the group in Go code, e.g. as the func name in a clientset.
279//
280// If the first field of the groupName is not unique within the clientset, use "// +groupName=unique
281func applyGroupOverrides(universe types.Universe, customArgs *clientgenargs.CustomArgs) {
282	// Create a map from "old GV" to "new GV" so we know what changes we need to make.
283	changes := make(map[clientgentypes.GroupVersion]clientgentypes.GroupVersion)
284	for gv, inputDir := range customArgs.GroupVersionPackages() {
285		p := universe.Package(genutil.Vendorless(inputDir))
286		if override := types.ExtractCommentTags("+", p.Comments)["groupName"]; override != nil {
287			newGV := clientgentypes.GroupVersion{
288				Group:   clientgentypes.Group(override[0]),
289				Version: gv.Version,
290			}
291			changes[gv] = newGV
292		}
293	}
294
295	// Modify customArgs.Groups based on the groupName overrides.
296	newGroups := make([]clientgentypes.GroupVersions, 0, len(customArgs.Groups))
297	for _, gvs := range customArgs.Groups {
298		gv := clientgentypes.GroupVersion{
299			Group:   gvs.Group,
300			Version: gvs.Versions[0].Version, // we only need a version, and the first will do
301		}
302		if newGV, ok := changes[gv]; ok {
303			// There's an override, so use it.
304			newGVS := clientgentypes.GroupVersions{
305				PackageName: gvs.PackageName,
306				Group:       newGV.Group,
307				Versions:    gvs.Versions,
308			}
309			newGroups = append(newGroups, newGVS)
310		} else {
311			// No override.
312			newGroups = append(newGroups, gvs)
313		}
314	}
315	customArgs.Groups = newGroups
316}
317
318// Packages makes the client package definition.
319func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages {
320	boilerplate, err := arguments.LoadGoBoilerplate()
321	if err != nil {
322		klog.Fatalf("Failed loading boilerplate: %v", err)
323	}
324
325	customArgs, ok := arguments.CustomArgs.(*clientgenargs.CustomArgs)
326	if !ok {
327		klog.Fatalf("cannot convert arguments.CustomArgs to clientgenargs.CustomArgs")
328	}
329	includedTypesOverrides := customArgs.IncludedTypesOverrides
330
331	applyGroupOverrides(context.Universe, customArgs)
332
333	gvToTypes := map[clientgentypes.GroupVersion][]*types.Type{}
334	groupGoNames := make(map[clientgentypes.GroupVersion]string)
335	for gv, inputDir := range customArgs.GroupVersionPackages() {
336		p := context.Universe.Package(path.Vendorless(inputDir))
337
338		// If there's a comment of the form "// +groupGoName=SomeUniqueShortName", use that as
339		// the Go group identifier in CamelCase. It defaults
340		groupGoNames[gv] = namer.IC(strings.Split(gv.Group.NonEmpty(), ".")[0])
341		if override := types.ExtractCommentTags("+", p.Comments)["groupGoName"]; override != nil {
342			groupGoNames[gv] = namer.IC(override[0])
343		}
344
345		// Package are indexed with the vendor prefix stripped
346		for n, t := range p.Types {
347			// filter out types which are not included in user specified overrides.
348			typesOverride, ok := includedTypesOverrides[gv]
349			if ok {
350				found := false
351				for _, typeStr := range typesOverride {
352					if typeStr == n {
353						found = true
354						break
355					}
356				}
357				if !found {
358					continue
359				}
360			} else {
361				// User has not specified any override for this group version.
362				// filter out types which don't have genclient.
363				if tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)); !tags.GenerateClient {
364					continue
365				}
366			}
367			if _, found := gvToTypes[gv]; !found {
368				gvToTypes[gv] = []*types.Type{}
369			}
370			gvToTypes[gv] = append(gvToTypes[gv], t)
371		}
372	}
373
374	var packageList []generator.Package
375	clientsetPackage := filepath.Join(arguments.OutputPackagePath, customArgs.ClientsetName)
376
377	packageList = append(packageList, packageForClientset(customArgs, clientsetPackage, groupGoNames, boilerplate))
378	packageList = append(packageList, packageForScheme(customArgs, clientsetPackage, arguments.OutputBase, groupGoNames, boilerplate))
379	if customArgs.FakeClient {
380		packageList = append(packageList, fake.PackageForClientset(customArgs, clientsetPackage, groupGoNames, boilerplate))
381	}
382
383	// If --clientset-only=true, we don't regenerate the individual typed clients.
384	if customArgs.ClientsetOnly {
385		return generator.Packages(packageList)
386	}
387
388	orderer := namer.Orderer{Namer: namer.NewPrivateNamer(0)}
389	gvPackages := customArgs.GroupVersionPackages()
390	for _, group := range customArgs.Groups {
391		for _, version := range group.Versions {
392			gv := clientgentypes.GroupVersion{Group: group.Group, Version: version.Version}
393			types := gvToTypes[gv]
394			inputPath := gvPackages[gv]
395			packageList = append(packageList, packageForGroup(gv, orderer.OrderTypes(types), clientsetPackage, group.PackageName, groupGoNames[gv], customArgs.ClientsetAPIPath, arguments.OutputBase, inputPath, customArgs.ApplyConfigurationPackage, boilerplate))
396			if customArgs.FakeClient {
397				packageList = append(packageList, fake.PackageForGroup(gv, orderer.OrderTypes(types), clientsetPackage, group.PackageName, groupGoNames[gv], inputPath, customArgs.ApplyConfigurationPackage, boilerplate))
398			}
399		}
400	}
401
402	return generator.Packages(packageList)
403}
404