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	"io"
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
30	"k8s.io/code-generator/cmd/client-gen/generators/util"
31	clientgentypes "k8s.io/code-generator/cmd/client-gen/types"
32
33	"k8s.io/klog"
34)
35
36// NameSystems returns the name system used by the generators in this package.
37func NameSystems() namer.NameSystems {
38	pluralExceptions := map[string]string{
39		"Endpoints": "Endpoints",
40	}
41	return namer.NameSystems{
42		"public":             namer.NewPublicNamer(0),
43		"private":            namer.NewPrivateNamer(0),
44		"raw":                namer.NewRawNamer("", nil),
45		"publicPlural":       namer.NewPublicPluralNamer(pluralExceptions),
46		"allLowercasePlural": namer.NewAllLowercasePluralNamer(pluralExceptions),
47		"lowercaseSingular":  &lowercaseSingularNamer{},
48	}
49}
50
51// lowercaseSingularNamer implements Namer
52type lowercaseSingularNamer struct{}
53
54// Name returns t's name in all lowercase.
55func (n *lowercaseSingularNamer) Name(t *types.Type) string {
56	return strings.ToLower(t.Name.Name)
57}
58
59// DefaultNameSystem returns the default name system for ordering the types to be
60// processed by the generators in this package.
61func DefaultNameSystem() string {
62	return "public"
63}
64
65// Packages makes the client package definition.
66func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages {
67	boilerplate, err := arguments.LoadGoBoilerplate()
68	if err != nil {
69		klog.Fatalf("Failed loading boilerplate: %v", err)
70	}
71
72	var packageList generator.Packages
73	for _, inputDir := range arguments.InputDirs {
74		p := context.Universe.Package(inputDir)
75
76		objectMeta, internal, err := objectMetaForPackage(p)
77		if err != nil {
78			klog.Fatal(err)
79		}
80		if objectMeta == nil {
81			// no types in this package had genclient
82			continue
83		}
84
85		var gv clientgentypes.GroupVersion
86		var internalGVPkg string
87
88		if internal {
89			lastSlash := strings.LastIndex(p.Path, "/")
90			if lastSlash == -1 {
91				klog.Fatalf("error constructing internal group version for package %q", p.Path)
92			}
93			gv.Group = clientgentypes.Group(p.Path[lastSlash+1:])
94			internalGVPkg = p.Path
95		} else {
96			parts := strings.Split(p.Path, "/")
97			gv.Group = clientgentypes.Group(parts[len(parts)-2])
98			gv.Version = clientgentypes.Version(parts[len(parts)-1])
99
100			internalGVPkg = strings.Join(parts[0:len(parts)-1], "/")
101		}
102		groupPackageName := strings.ToLower(gv.Group.NonEmpty())
103
104		// If there's a comment of the form "// +groupName=somegroup" or
105		// "// +groupName=somegroup.foo.bar.io", use the first field (somegroup) as the name of the
106		// group when generating.
107		if override := types.ExtractCommentTags("+", p.Comments)["groupName"]; override != nil {
108			gv.Group = clientgentypes.Group(strings.SplitN(override[0], ".", 2)[0])
109		}
110
111		var typesToGenerate []*types.Type
112		for _, t := range p.Types {
113			tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
114			if !tags.GenerateClient || !tags.HasVerb("list") || !tags.HasVerb("get") {
115				continue
116			}
117			typesToGenerate = append(typesToGenerate, t)
118		}
119		if len(typesToGenerate) == 0 {
120			continue
121		}
122		orderer := namer.Orderer{Namer: namer.NewPrivateNamer(0)}
123		typesToGenerate = orderer.OrderTypes(typesToGenerate)
124
125		packagePath := filepath.Join(arguments.OutputPackagePath, groupPackageName, strings.ToLower(gv.Version.NonEmpty()))
126		packageList = append(packageList, &generator.DefaultPackage{
127			PackageName: strings.ToLower(gv.Version.NonEmpty()),
128			PackagePath: packagePath,
129			HeaderText:  boilerplate,
130			GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
131				generators = append(generators, &expansionGenerator{
132					DefaultGen: generator.DefaultGen{
133						OptionalName: "expansion_generated",
134					},
135					packagePath: filepath.Join(arguments.OutputBase, packagePath),
136					types:       typesToGenerate,
137				})
138
139				for _, t := range typesToGenerate {
140					generators = append(generators, &listerGenerator{
141						DefaultGen: generator.DefaultGen{
142							OptionalName: strings.ToLower(t.Name.Name),
143						},
144						outputPackage:  arguments.OutputPackagePath,
145						groupVersion:   gv,
146						internalGVPkg:  internalGVPkg,
147						typeToGenerate: t,
148						imports:        generator.NewImportTracker(),
149						objectMeta:     objectMeta,
150					})
151				}
152				return generators
153			},
154			FilterFunc: func(c *generator.Context, t *types.Type) bool {
155				tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
156				return tags.GenerateClient && tags.HasVerb("list") && tags.HasVerb("get")
157			},
158		})
159	}
160
161	return packageList
162}
163
164// objectMetaForPackage returns the type of ObjectMeta used by package p.
165func objectMetaForPackage(p *types.Package) (*types.Type, bool, error) {
166	generatingForPackage := false
167	for _, t := range p.Types {
168		// filter out types which dont have genclient.
169		if !util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)).GenerateClient {
170			continue
171		}
172		generatingForPackage = true
173		for _, member := range t.Members {
174			if member.Name == "ObjectMeta" {
175				return member.Type, isInternal(member), nil
176			}
177		}
178	}
179	if generatingForPackage {
180		return nil, false, fmt.Errorf("unable to find ObjectMeta for any types in package %s", p.Path)
181	}
182	return nil, false, nil
183}
184
185// isInternal returns true if the tags for a member do not contain a json tag
186func isInternal(m types.Member) bool {
187	return !strings.Contains(m.Tags, "json")
188}
189
190// listerGenerator produces a file of listers for a given GroupVersion and
191// type.
192type listerGenerator struct {
193	generator.DefaultGen
194	outputPackage  string
195	groupVersion   clientgentypes.GroupVersion
196	internalGVPkg  string
197	typeToGenerate *types.Type
198	imports        namer.ImportTracker
199	objectMeta     *types.Type
200}
201
202var _ generator.Generator = &listerGenerator{}
203
204func (g *listerGenerator) Filter(c *generator.Context, t *types.Type) bool {
205	return t == g.typeToGenerate
206}
207
208func (g *listerGenerator) Namers(c *generator.Context) namer.NameSystems {
209	return namer.NameSystems{
210		"raw": namer.NewRawNamer(g.outputPackage, g.imports),
211	}
212}
213
214func (g *listerGenerator) Imports(c *generator.Context) (imports []string) {
215	imports = append(imports, g.imports.ImportLines()...)
216	imports = append(imports, "k8s.io/apimachinery/pkg/api/errors")
217	imports = append(imports, "k8s.io/apimachinery/pkg/labels")
218	// for Indexer
219	imports = append(imports, "k8s.io/client-go/tools/cache")
220	return
221}
222
223func (g *listerGenerator) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
224	sw := generator.NewSnippetWriter(w, c, "$", "$")
225
226	klog.V(5).Infof("processing type %v", t)
227	m := map[string]interface{}{
228		"Resource":   c.Universe.Function(types.Name{Package: t.Name.Package, Name: "Resource"}),
229		"type":       t,
230		"objectMeta": g.objectMeta,
231	}
232
233	tags, err := util.ParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
234	if err != nil {
235		return err
236	}
237
238	if tags.NonNamespaced {
239		sw.Do(typeListerInterface_NonNamespaced, m)
240	} else {
241		sw.Do(typeListerInterface, m)
242	}
243
244	sw.Do(typeListerStruct, m)
245	sw.Do(typeListerConstructor, m)
246	sw.Do(typeLister_List, m)
247
248	if tags.NonNamespaced {
249		sw.Do(typeLister_NonNamespacedGet, m)
250		return sw.Error()
251	}
252
253	sw.Do(typeLister_NamespaceLister, m)
254	sw.Do(namespaceListerInterface, m)
255	sw.Do(namespaceListerStruct, m)
256	sw.Do(namespaceLister_List, m)
257	sw.Do(namespaceLister_Get, m)
258
259	return sw.Error()
260}
261
262var typeListerInterface = `
263// $.type|public$Lister helps list $.type|publicPlural$.
264type $.type|public$Lister interface {
265	// List lists all $.type|publicPlural$ in the indexer.
266	List(selector labels.Selector) (ret []*$.type|raw$, err error)
267	// $.type|publicPlural$ returns an object that can list and get $.type|publicPlural$.
268	$.type|publicPlural$(namespace string) $.type|public$NamespaceLister
269	$.type|public$ListerExpansion
270}
271`
272
273var typeListerInterface_NonNamespaced = `
274// $.type|public$Lister helps list $.type|publicPlural$.
275type $.type|public$Lister interface {
276	// List lists all $.type|publicPlural$ in the indexer.
277	List(selector labels.Selector) (ret []*$.type|raw$, err error)
278	// Get retrieves the $.type|public$ from the index for a given name.
279	Get(name string) (*$.type|raw$, error)
280	$.type|public$ListerExpansion
281}
282`
283
284var typeListerStruct = `
285// $.type|private$Lister implements the $.type|public$Lister interface.
286type $.type|private$Lister struct {
287	indexer cache.Indexer
288}
289`
290
291var typeListerConstructor = `
292// New$.type|public$Lister returns a new $.type|public$Lister.
293func New$.type|public$Lister(indexer cache.Indexer) $.type|public$Lister {
294	return &$.type|private$Lister{indexer: indexer}
295}
296`
297
298var typeLister_List = `
299// List lists all $.type|publicPlural$ in the indexer.
300func (s *$.type|private$Lister) List(selector labels.Selector) (ret []*$.type|raw$, err error) {
301	err = cache.ListAll(s.indexer, selector, func(m interface{}) {
302		ret = append(ret, m.(*$.type|raw$))
303	})
304	return ret, err
305}
306`
307
308var typeLister_NamespaceLister = `
309// $.type|publicPlural$ returns an object that can list and get $.type|publicPlural$.
310func (s *$.type|private$Lister) $.type|publicPlural$(namespace string) $.type|public$NamespaceLister {
311	return $.type|private$NamespaceLister{indexer: s.indexer, namespace: namespace}
312}
313`
314
315var typeLister_NonNamespacedGet = `
316// Get retrieves the $.type|public$ from the index for a given name.
317func (s *$.type|private$Lister) Get(name string) (*$.type|raw$, error) {
318  obj, exists, err := s.indexer.GetByKey(name)
319  if err != nil {
320    return nil, err
321  }
322  if !exists {
323    return nil, errors.NewNotFound($.Resource|raw$("$.type|lowercaseSingular$"), name)
324  }
325  return obj.(*$.type|raw$), nil
326}
327`
328
329var namespaceListerInterface = `
330// $.type|public$NamespaceLister helps list and get $.type|publicPlural$.
331type $.type|public$NamespaceLister interface {
332	// List lists all $.type|publicPlural$ in the indexer for a given namespace.
333	List(selector labels.Selector) (ret []*$.type|raw$, err error)
334	// Get retrieves the $.type|public$ from the indexer for a given namespace and name.
335	Get(name string) (*$.type|raw$, error)
336	$.type|public$NamespaceListerExpansion
337}
338`
339
340var namespaceListerStruct = `
341// $.type|private$NamespaceLister implements the $.type|public$NamespaceLister
342// interface.
343type $.type|private$NamespaceLister struct {
344	indexer cache.Indexer
345	namespace string
346}
347`
348
349var namespaceLister_List = `
350// List lists all $.type|publicPlural$ in the indexer for a given namespace.
351func (s $.type|private$NamespaceLister) List(selector labels.Selector) (ret []*$.type|raw$, err error) {
352	err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
353		ret = append(ret, m.(*$.type|raw$))
354	})
355	return ret, err
356}
357`
358
359var namespaceLister_Get = `
360// Get retrieves the $.type|public$ from the indexer for a given namespace and name.
361func (s $.type|private$NamespaceLister) Get(name string) (*$.type|raw$, error) {
362	obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
363	if err != nil {
364		return nil, err
365	}
366	if !exists {
367		return nil, errors.NewNotFound($.Resource|raw$("$.type|lowercaseSingular$"), name)
368	}
369	return obj.(*$.type|raw$), nil
370}
371`
372