1/*
2Copyright 2016 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
17package generators
18
19import (
20	"fmt"
21	"path"
22	"path/filepath"
23	"strings"
24
25	"k8s.io/gengo/args"
26	"k8s.io/gengo/generator"
27	"k8s.io/gengo/namer"
28	"k8s.io/gengo/types"
29	"k8s.io/klog/v2"
30
31	"k8s.io/code-generator/cmd/client-gen/generators/util"
32	clientgentypes "k8s.io/code-generator/cmd/client-gen/types"
33	informergenargs "k8s.io/code-generator/cmd/informer-gen/args"
34	genutil "k8s.io/code-generator/pkg/util"
35)
36
37// NameSystems returns the name system used by the generators in this package.
38func NameSystems(pluralExceptions map[string]string) namer.NameSystems {
39	return namer.NameSystems{
40		"public":             namer.NewPublicNamer(0),
41		"private":            namer.NewPrivateNamer(0),
42		"raw":                namer.NewRawNamer("", nil),
43		"publicPlural":       namer.NewPublicPluralNamer(pluralExceptions),
44		"allLowercasePlural": namer.NewAllLowercasePluralNamer(pluralExceptions),
45		"lowercaseSingular":  &lowercaseSingularNamer{},
46	}
47}
48
49// lowercaseSingularNamer implements Namer
50type lowercaseSingularNamer struct{}
51
52// Name returns t's name in all lowercase.
53func (n *lowercaseSingularNamer) Name(t *types.Type) string {
54	return strings.ToLower(t.Name.Name)
55}
56
57// DefaultNameSystem returns the default name system for ordering the types to be
58// processed by the generators in this package.
59func DefaultNameSystem() string {
60	return "public"
61}
62
63// objectMetaForPackage returns the type of ObjectMeta used by package p.
64func objectMetaForPackage(p *types.Package) (*types.Type, bool, error) {
65	generatingForPackage := false
66	for _, t := range p.Types {
67		if !util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)).GenerateClient {
68			continue
69		}
70		generatingForPackage = true
71		for _, member := range t.Members {
72			if member.Name == "ObjectMeta" {
73				return member.Type, isInternal(member), nil
74			}
75		}
76	}
77	if generatingForPackage {
78		return nil, false, fmt.Errorf("unable to find ObjectMeta for any types in package %s", p.Path)
79	}
80	return nil, false, nil
81}
82
83// isInternal returns true if the tags for a member do not contain a json tag
84func isInternal(m types.Member) bool {
85	return !strings.Contains(m.Tags, "json")
86}
87
88func packageForInternalInterfaces(base string) string {
89	return filepath.Join(base, "internalinterfaces")
90}
91
92// Packages makes the client package definition.
93func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages {
94	boilerplate, err := arguments.LoadGoBoilerplate()
95	if err != nil {
96		klog.Fatalf("Failed loading boilerplate: %v", err)
97	}
98
99	customArgs, ok := arguments.CustomArgs.(*informergenargs.CustomArgs)
100	if !ok {
101		klog.Fatalf("Wrong CustomArgs type: %T", arguments.CustomArgs)
102	}
103
104	internalVersionPackagePath := filepath.Join(arguments.OutputPackagePath)
105	externalVersionPackagePath := filepath.Join(arguments.OutputPackagePath)
106	if !customArgs.SingleDirectory {
107		internalVersionPackagePath = filepath.Join(arguments.OutputPackagePath, "internalversion")
108		externalVersionPackagePath = filepath.Join(arguments.OutputPackagePath, "externalversions")
109	}
110
111	var packageList generator.Packages
112	typesForGroupVersion := make(map[clientgentypes.GroupVersion][]*types.Type)
113
114	externalGroupVersions := make(map[string]clientgentypes.GroupVersions)
115	internalGroupVersions := make(map[string]clientgentypes.GroupVersions)
116	groupGoNames := make(map[string]string)
117	for _, inputDir := range arguments.InputDirs {
118		p := context.Universe.Package(genutil.Vendorless(inputDir))
119
120		objectMeta, internal, err := objectMetaForPackage(p)
121		if err != nil {
122			klog.Fatal(err)
123		}
124		if objectMeta == nil {
125			// no types in this package had genclient
126			continue
127		}
128
129		var gv clientgentypes.GroupVersion
130		var targetGroupVersions map[string]clientgentypes.GroupVersions
131
132		if internal {
133			lastSlash := strings.LastIndex(p.Path, "/")
134			if lastSlash == -1 {
135				klog.Fatalf("error constructing internal group version for package %q", p.Path)
136			}
137			gv.Group = clientgentypes.Group(p.Path[lastSlash+1:])
138			targetGroupVersions = internalGroupVersions
139		} else {
140			parts := strings.Split(p.Path, "/")
141			gv.Group = clientgentypes.Group(parts[len(parts)-2])
142			gv.Version = clientgentypes.Version(parts[len(parts)-1])
143			targetGroupVersions = externalGroupVersions
144		}
145		groupPackageName := gv.Group.NonEmpty()
146		gvPackage := path.Clean(p.Path)
147
148		// If there's a comment of the form "// +groupName=somegroup" or
149		// "// +groupName=somegroup.foo.bar.io", use the first field (somegroup) as the name of the
150		// group when generating.
151		if override := types.ExtractCommentTags("+", p.Comments)["groupName"]; override != nil {
152			gv.Group = clientgentypes.Group(override[0])
153		}
154
155		// If there's a comment of the form "// +groupGoName=SomeUniqueShortName", use that as
156		// the Go group identifier in CamelCase. It defaults
157		groupGoNames[groupPackageName] = namer.IC(strings.Split(gv.Group.NonEmpty(), ".")[0])
158		if override := types.ExtractCommentTags("+", p.Comments)["groupGoName"]; override != nil {
159			groupGoNames[groupPackageName] = namer.IC(override[0])
160		}
161
162		var typesToGenerate []*types.Type
163		for _, t := range p.Types {
164			tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
165			if !tags.GenerateClient || tags.NoVerbs || !tags.HasVerb("list") || !tags.HasVerb("watch") {
166				continue
167			}
168
169			typesToGenerate = append(typesToGenerate, t)
170
171			if _, ok := typesForGroupVersion[gv]; !ok {
172				typesForGroupVersion[gv] = []*types.Type{}
173			}
174			typesForGroupVersion[gv] = append(typesForGroupVersion[gv], t)
175		}
176		if len(typesToGenerate) == 0 {
177			continue
178		}
179
180		groupVersionsEntry, ok := targetGroupVersions[groupPackageName]
181		if !ok {
182			groupVersionsEntry = clientgentypes.GroupVersions{
183				PackageName: groupPackageName,
184				Group:       gv.Group,
185			}
186		}
187		groupVersionsEntry.Versions = append(groupVersionsEntry.Versions, clientgentypes.PackageVersion{Version: gv.Version, Package: gvPackage})
188		targetGroupVersions[groupPackageName] = groupVersionsEntry
189
190		orderer := namer.Orderer{Namer: namer.NewPrivateNamer(0)}
191		typesToGenerate = orderer.OrderTypes(typesToGenerate)
192
193		if internal {
194			packageList = append(packageList, versionPackage(internalVersionPackagePath, groupPackageName, gv, groupGoNames[groupPackageName], boilerplate, typesToGenerate, customArgs.InternalClientSetPackage, customArgs.ListersPackage))
195		} else {
196			packageList = append(packageList, versionPackage(externalVersionPackagePath, groupPackageName, gv, groupGoNames[groupPackageName], boilerplate, typesToGenerate, customArgs.VersionedClientSetPackage, customArgs.ListersPackage))
197		}
198	}
199
200	if len(externalGroupVersions) != 0 {
201		packageList = append(packageList, factoryInterfacePackage(externalVersionPackagePath, boilerplate, customArgs.VersionedClientSetPackage))
202		packageList = append(packageList, factoryPackage(externalVersionPackagePath, boilerplate, groupGoNames, genutil.PluralExceptionListToMapOrDie(customArgs.PluralExceptions), externalGroupVersions,
203			customArgs.VersionedClientSetPackage,
204			typesForGroupVersion))
205		for _, gvs := range externalGroupVersions {
206			packageList = append(packageList, groupPackage(externalVersionPackagePath, gvs, boilerplate))
207		}
208	}
209
210	if len(internalGroupVersions) != 0 {
211		packageList = append(packageList, factoryInterfacePackage(internalVersionPackagePath, boilerplate, customArgs.InternalClientSetPackage))
212		packageList = append(packageList, factoryPackage(internalVersionPackagePath, boilerplate, groupGoNames, genutil.PluralExceptionListToMapOrDie(customArgs.PluralExceptions), internalGroupVersions, customArgs.InternalClientSetPackage, typesForGroupVersion))
213		for _, gvs := range internalGroupVersions {
214			packageList = append(packageList, groupPackage(internalVersionPackagePath, gvs, boilerplate))
215		}
216	}
217
218	return packageList
219}
220
221func factoryPackage(basePackage string, boilerplate []byte, groupGoNames, pluralExceptions map[string]string, groupVersions map[string]clientgentypes.GroupVersions, clientSetPackage string,
222	typesForGroupVersion map[clientgentypes.GroupVersion][]*types.Type) generator.Package {
223	return &generator.DefaultPackage{
224		PackageName: filepath.Base(basePackage),
225		PackagePath: basePackage,
226		HeaderText:  boilerplate,
227		GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
228			generators = append(generators, &factoryGenerator{
229				DefaultGen: generator.DefaultGen{
230					OptionalName: "factory",
231				},
232				outputPackage:             basePackage,
233				imports:                   generator.NewImportTracker(),
234				groupVersions:             groupVersions,
235				clientSetPackage:          clientSetPackage,
236				internalInterfacesPackage: packageForInternalInterfaces(basePackage),
237				gvGoNames:                 groupGoNames,
238			})
239
240			generators = append(generators, &genericGenerator{
241				DefaultGen: generator.DefaultGen{
242					OptionalName: "generic",
243				},
244				outputPackage:        basePackage,
245				imports:              generator.NewImportTracker(),
246				groupVersions:        groupVersions,
247				pluralExceptions:     pluralExceptions,
248				typesForGroupVersion: typesForGroupVersion,
249				groupGoNames:         groupGoNames,
250			})
251
252			return generators
253		},
254	}
255}
256
257func factoryInterfacePackage(basePackage string, boilerplate []byte, clientSetPackage string) generator.Package {
258	packagePath := packageForInternalInterfaces(basePackage)
259
260	return &generator.DefaultPackage{
261		PackageName: filepath.Base(packagePath),
262		PackagePath: packagePath,
263		HeaderText:  boilerplate,
264		GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
265			generators = append(generators, &factoryInterfaceGenerator{
266				DefaultGen: generator.DefaultGen{
267					OptionalName: "factory_interfaces",
268				},
269				outputPackage:    packagePath,
270				imports:          generator.NewImportTracker(),
271				clientSetPackage: clientSetPackage,
272			})
273
274			return generators
275		},
276	}
277}
278
279func groupPackage(basePackage string, groupVersions clientgentypes.GroupVersions, boilerplate []byte) generator.Package {
280	packagePath := filepath.Join(basePackage, groupVersions.PackageName)
281	groupPkgName := strings.Split(string(groupVersions.PackageName), ".")[0]
282
283	return &generator.DefaultPackage{
284		PackageName: groupPkgName,
285		PackagePath: packagePath,
286		HeaderText:  boilerplate,
287		GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
288			generators = append(generators, &groupInterfaceGenerator{
289				DefaultGen: generator.DefaultGen{
290					OptionalName: "interface",
291				},
292				outputPackage:             packagePath,
293				groupVersions:             groupVersions,
294				imports:                   generator.NewImportTracker(),
295				internalInterfacesPackage: packageForInternalInterfaces(basePackage),
296			})
297			return generators
298		},
299		FilterFunc: func(c *generator.Context, t *types.Type) bool {
300			tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
301			return tags.GenerateClient && tags.HasVerb("list") && tags.HasVerb("watch")
302		},
303	}
304}
305
306func versionPackage(basePackage string, groupPkgName string, gv clientgentypes.GroupVersion, groupGoName string, boilerplate []byte, typesToGenerate []*types.Type, clientSetPackage, listersPackage string) generator.Package {
307	packagePath := filepath.Join(basePackage, groupPkgName, strings.ToLower(gv.Version.NonEmpty()))
308
309	return &generator.DefaultPackage{
310		PackageName: strings.ToLower(gv.Version.NonEmpty()),
311		PackagePath: packagePath,
312		HeaderText:  boilerplate,
313		GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
314			generators = append(generators, &versionInterfaceGenerator{
315				DefaultGen: generator.DefaultGen{
316					OptionalName: "interface",
317				},
318				outputPackage:             packagePath,
319				imports:                   generator.NewImportTracker(),
320				types:                     typesToGenerate,
321				internalInterfacesPackage: packageForInternalInterfaces(basePackage),
322			})
323
324			for _, t := range typesToGenerate {
325				generators = append(generators, &informerGenerator{
326					DefaultGen: generator.DefaultGen{
327						OptionalName: strings.ToLower(t.Name.Name),
328					},
329					outputPackage:             packagePath,
330					groupPkgName:              groupPkgName,
331					groupVersion:              gv,
332					groupGoName:               groupGoName,
333					typeToGenerate:            t,
334					imports:                   generator.NewImportTracker(),
335					clientSetPackage:          clientSetPackage,
336					listersPackage:            listersPackage,
337					internalInterfacesPackage: packageForInternalInterfaces(basePackage),
338				})
339			}
340			return generators
341		},
342		FilterFunc: func(c *generator.Context, t *types.Type) bool {
343			tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
344			return tags.GenerateClient && tags.HasVerb("list") && tags.HasVerb("watch")
345		},
346	}
347}
348