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	"io"
21	"sort"
22	"strings"
23
24	clientgentypes "k8s.io/code-generator/cmd/client-gen/types"
25	codegennamer "k8s.io/code-generator/pkg/namer"
26	"k8s.io/gengo/generator"
27	"k8s.io/gengo/namer"
28	"k8s.io/gengo/types"
29)
30
31// genericGenerator generates the generic informer.
32type genericGenerator struct {
33	generator.DefaultGen
34	outputPackage        string
35	imports              namer.ImportTracker
36	groupVersions        map[string]clientgentypes.GroupVersions
37	groupGoNames         map[string]string
38	pluralExceptions     map[string]string
39	typesForGroupVersion map[clientgentypes.GroupVersion][]*types.Type
40	filtered             bool
41}
42
43var _ generator.Generator = &genericGenerator{}
44
45func (g *genericGenerator) Filter(c *generator.Context, t *types.Type) bool {
46	if !g.filtered {
47		g.filtered = true
48		return true
49	}
50	return false
51}
52
53func (g *genericGenerator) Namers(c *generator.Context) namer.NameSystems {
54	return namer.NameSystems{
55		"raw":                namer.NewRawNamer(g.outputPackage, g.imports),
56		"allLowercasePlural": namer.NewAllLowercasePluralNamer(g.pluralExceptions),
57		"publicPlural":       namer.NewPublicPluralNamer(g.pluralExceptions),
58		"resource":           codegennamer.NewTagOverrideNamer("resourceName", namer.NewAllLowercasePluralNamer(g.pluralExceptions)),
59	}
60}
61
62func (g *genericGenerator) Imports(c *generator.Context) (imports []string) {
63	imports = append(imports, g.imports.ImportLines()...)
64	imports = append(imports, "fmt")
65	return
66}
67
68type group struct {
69	GroupGoName string
70	Name        string
71	Versions    []*version
72}
73
74type groupSort []group
75
76func (g groupSort) Len() int { return len(g) }
77func (g groupSort) Less(i, j int) bool {
78	return strings.ToLower(g[i].Name) < strings.ToLower(g[j].Name)
79}
80func (g groupSort) Swap(i, j int) { g[i], g[j] = g[j], g[i] }
81
82type version struct {
83	Name      string
84	GoName    string
85	Resources []*types.Type
86}
87
88type versionSort []*version
89
90func (v versionSort) Len() int { return len(v) }
91func (v versionSort) Less(i, j int) bool {
92	return strings.ToLower(v[i].Name) < strings.ToLower(v[j].Name)
93}
94func (v versionSort) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
95
96func (g *genericGenerator) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
97	sw := generator.NewSnippetWriter(w, c, "{{", "}}")
98
99	groups := []group{}
100	schemeGVs := make(map[*version]*types.Type)
101
102	orderer := namer.Orderer{Namer: namer.NewPrivateNamer(0)}
103	for groupPackageName, groupVersions := range g.groupVersions {
104		group := group{
105			GroupGoName: g.groupGoNames[groupPackageName],
106			Name:        groupVersions.Group.NonEmpty(),
107			Versions:    []*version{},
108		}
109		for _, v := range groupVersions.Versions {
110			gv := clientgentypes.GroupVersion{Group: groupVersions.Group, Version: v.Version}
111			version := &version{
112				Name:      v.Version.NonEmpty(),
113				GoName:    namer.IC(v.Version.NonEmpty()),
114				Resources: orderer.OrderTypes(g.typesForGroupVersion[gv]),
115			}
116			func() {
117				schemeGVs[version] = c.Universe.Variable(types.Name{Package: g.typesForGroupVersion[gv][0].Name.Package, Name: "SchemeGroupVersion"})
118			}()
119			group.Versions = append(group.Versions, version)
120		}
121		sort.Sort(versionSort(group.Versions))
122		groups = append(groups, group)
123	}
124	sort.Sort(groupSort(groups))
125
126	m := map[string]interface{}{
127		"cacheGenericLister":         c.Universe.Type(cacheGenericLister),
128		"cacheNewGenericLister":      c.Universe.Function(cacheNewGenericLister),
129		"cacheSharedIndexInformer":   c.Universe.Type(cacheSharedIndexInformer),
130		"groups":                     groups,
131		"schemeGVs":                  schemeGVs,
132		"schemaGroupResource":        c.Universe.Type(schemaGroupResource),
133		"schemaGroupVersionResource": c.Universe.Type(schemaGroupVersionResource),
134	}
135
136	sw.Do(genericInformer, m)
137	sw.Do(forResource, m)
138
139	return sw.Error()
140}
141
142var genericInformer = `
143// GenericInformer is type of SharedIndexInformer which will locate and delegate to other
144// sharedInformers based on type
145type GenericInformer interface {
146	Informer() {{.cacheSharedIndexInformer|raw}}
147	Lister() {{.cacheGenericLister|raw}}
148}
149
150type genericInformer struct {
151	informer {{.cacheSharedIndexInformer|raw}}
152	resource {{.schemaGroupResource|raw}}
153}
154
155// Informer returns the SharedIndexInformer.
156func (f *genericInformer) Informer() {{.cacheSharedIndexInformer|raw}} {
157	return f.informer
158}
159
160// Lister returns the GenericLister.
161func (f *genericInformer) Lister() {{.cacheGenericLister|raw}} {
162	return {{.cacheNewGenericLister|raw}}(f.Informer().GetIndexer(), f.resource)
163}
164`
165
166var forResource = `
167// ForResource gives generic access to a shared informer of the matching type
168// TODO extend this to unknown resources with a client pool
169func (f *sharedInformerFactory) ForResource(resource {{.schemaGroupVersionResource|raw}}) (GenericInformer, error) {
170	switch resource {
171		{{range $group := .groups -}}{{$GroupGoName := .GroupGoName -}}
172			{{range $version := .Versions -}}
173	// Group={{$group.Name}}, Version={{.Name}}
174				{{range .Resources -}}
175	case {{index $.schemeGVs $version|raw}}.WithResource("{{.|resource}}"):
176		return &genericInformer{resource: resource.GroupResource(), informer: f.{{$GroupGoName}}().{{$version.GoName}}().{{.|publicPlural}}().Informer()}, nil
177				{{end}}
178			{{end}}
179		{{end -}}
180	}
181
182	return nil, fmt.Errorf("no informer found for %v", resource)
183}
184`
185