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
17package fake
18
19import (
20	"io"
21	"path/filepath"
22	"strings"
23
24	"k8s.io/gengo/generator"
25	"k8s.io/gengo/namer"
26	"k8s.io/gengo/types"
27
28	"k8s.io/code-generator/cmd/client-gen/generators/util"
29	"k8s.io/code-generator/cmd/client-gen/path"
30)
31
32// genFakeForType produces a file for each top-level type.
33type genFakeForType struct {
34	generator.DefaultGen
35	outputPackage string
36	group         string
37	version       string
38	groupGoName   string
39	inputPackage  string
40	typeToMatch   *types.Type
41	imports       namer.ImportTracker
42}
43
44var _ generator.Generator = &genFakeForType{}
45
46// Filter ignores all but one type because we're making a single file per type.
47func (g *genFakeForType) Filter(c *generator.Context, t *types.Type) bool { return t == g.typeToMatch }
48
49func (g *genFakeForType) Namers(c *generator.Context) namer.NameSystems {
50	return namer.NameSystems{
51		"raw": namer.NewRawNamer(g.outputPackage, g.imports),
52	}
53}
54
55func (g *genFakeForType) Imports(c *generator.Context) (imports []string) {
56	return g.imports.ImportLines()
57}
58
59// Ideally, we'd like genStatus to return true if there is a subresource path
60// registered for "status" in the API server, but we do not have that
61// information, so genStatus returns true if the type has a status field.
62func genStatus(t *types.Type) bool {
63	// Default to true if we have a Status member
64	hasStatus := false
65	for _, m := range t.Members {
66		if m.Name == "Status" {
67			hasStatus = true
68			break
69		}
70	}
71
72	tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
73	return hasStatus && !tags.NoStatus
74}
75
76// hasObjectMeta returns true if the type has a ObjectMeta field.
77func hasObjectMeta(t *types.Type) bool {
78	for _, m := range t.Members {
79		if m.Embedded == true && m.Name == "ObjectMeta" {
80			return true
81		}
82	}
83	return false
84}
85
86// GenerateType makes the body of a file implementing the individual typed client for type t.
87func (g *genFakeForType) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
88	sw := generator.NewSnippetWriter(w, c, "$", "$")
89	pkg := filepath.Base(t.Name.Package)
90	tags, err := util.ParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
91	if err != nil {
92		return err
93	}
94	canonicalGroup := g.group
95	if canonicalGroup == "core" {
96		canonicalGroup = ""
97	}
98
99	groupName := g.group
100	if g.group == "core" {
101		groupName = ""
102	}
103
104	// allow user to define a group name that's different from the one parsed from the directory.
105	p := c.Universe.Package(path.Vendorless(g.inputPackage))
106	if override := types.ExtractCommentTags("+", p.Comments)["groupName"]; override != nil {
107		groupName = override[0]
108	}
109
110	const pkgClientGoTesting = "k8s.io/client-go/testing"
111	m := map[string]interface{}{
112		"type":                 t,
113		"inputType":            t,
114		"resultType":           t,
115		"subresourcePath":      "",
116		"package":              pkg,
117		"Package":              namer.IC(pkg),
118		"namespaced":           !tags.NonNamespaced,
119		"Group":                namer.IC(g.group),
120		"GroupGoName":          g.groupGoName,
121		"Version":              namer.IC(g.version),
122		"group":                canonicalGroup,
123		"groupName":            groupName,
124		"version":              g.version,
125		"DeleteOptions":        c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "DeleteOptions"}),
126		"ListOptions":          c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ListOptions"}),
127		"GetOptions":           c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "GetOptions"}),
128		"Everything":           c.Universe.Function(types.Name{Package: "k8s.io/apimachinery/pkg/labels", Name: "Everything"}),
129		"GroupVersionResource": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/runtime/schema", Name: "GroupVersionResource"}),
130		"GroupVersionKind":     c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/runtime/schema", Name: "GroupVersionKind"}),
131		"PatchType":            c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/types", Name: "PatchType"}),
132		"watchInterface":       c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/watch", Name: "Interface"}),
133
134		"NewRootListAction":              c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootListAction"}),
135		"NewListAction":                  c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewListAction"}),
136		"NewRootGetAction":               c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootGetAction"}),
137		"NewGetAction":                   c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewGetAction"}),
138		"NewRootDeleteAction":            c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootDeleteAction"}),
139		"NewDeleteAction":                c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewDeleteAction"}),
140		"NewRootDeleteCollectionAction":  c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootDeleteCollectionAction"}),
141		"NewDeleteCollectionAction":      c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewDeleteCollectionAction"}),
142		"NewRootUpdateAction":            c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootUpdateAction"}),
143		"NewUpdateAction":                c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewUpdateAction"}),
144		"NewRootCreateAction":            c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootCreateAction"}),
145		"NewCreateAction":                c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewCreateAction"}),
146		"NewRootWatchAction":             c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootWatchAction"}),
147		"NewWatchAction":                 c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewWatchAction"}),
148		"NewCreateSubresourceAction":     c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewCreateSubresourceAction"}),
149		"NewRootCreateSubresourceAction": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootCreateSubresourceAction"}),
150		"NewUpdateSubresourceAction":     c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewUpdateSubresourceAction"}),
151		"NewGetSubresourceAction":        c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewGetSubresourceAction"}),
152		"NewRootGetSubresourceAction":    c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootGetSubresourceAction"}),
153		"NewRootUpdateSubresourceAction": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootUpdateSubresourceAction"}),
154		"NewRootPatchAction":             c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootPatchAction"}),
155		"NewPatchAction":                 c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewPatchAction"}),
156		"NewRootPatchSubresourceAction":  c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootPatchSubresourceAction"}),
157		"NewPatchSubresourceAction":      c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewPatchSubresourceAction"}),
158		"ExtractFromListOptions":         c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "ExtractFromListOptions"}),
159	}
160
161	if tags.NonNamespaced {
162		sw.Do(structNonNamespaced, m)
163	} else {
164		sw.Do(structNamespaced, m)
165	}
166
167	if tags.NoVerbs {
168		return sw.Error()
169	}
170	sw.Do(resource, m)
171	sw.Do(kind, m)
172
173	if tags.HasVerb("get") {
174		sw.Do(getTemplate, m)
175	}
176	if tags.HasVerb("list") {
177		if hasObjectMeta(t) {
178			sw.Do(listUsingOptionsTemplate, m)
179		} else {
180			sw.Do(listTemplate, m)
181		}
182	}
183	if tags.HasVerb("watch") {
184		sw.Do(watchTemplate, m)
185	}
186
187	if tags.HasVerb("create") {
188		sw.Do(createTemplate, m)
189	}
190	if tags.HasVerb("update") {
191		sw.Do(updateTemplate, m)
192	}
193	if tags.HasVerb("updateStatus") && genStatus(t) {
194		sw.Do(updateStatusTemplate, m)
195	}
196	if tags.HasVerb("delete") {
197		sw.Do(deleteTemplate, m)
198	}
199	if tags.HasVerb("deleteCollection") {
200		sw.Do(deleteCollectionTemplate, m)
201	}
202	if tags.HasVerb("patch") {
203		sw.Do(patchTemplate, m)
204	}
205
206	// generate extended client methods
207	for _, e := range tags.Extensions {
208		inputType := *t
209		resultType := *t
210		if len(e.InputTypeOverride) > 0 {
211			if name, pkg := e.Input(); len(pkg) > 0 {
212				newType := c.Universe.Type(types.Name{Package: pkg, Name: name})
213				inputType = *newType
214			} else {
215				inputType.Name.Name = e.InputTypeOverride
216			}
217		}
218		if len(e.ResultTypeOverride) > 0 {
219			if name, pkg := e.Result(); len(pkg) > 0 {
220				newType := c.Universe.Type(types.Name{Package: pkg, Name: name})
221				resultType = *newType
222			} else {
223				resultType.Name.Name = e.ResultTypeOverride
224			}
225		}
226		m["inputType"] = &inputType
227		m["resultType"] = &resultType
228		m["subresourcePath"] = e.SubResourcePath
229
230		if e.HasVerb("get") {
231			if e.IsSubresource() {
232				sw.Do(adjustTemplate(e.VerbName, e.VerbType, getSubresourceTemplate), m)
233			} else {
234				sw.Do(adjustTemplate(e.VerbName, e.VerbType, getTemplate), m)
235			}
236		}
237
238		if e.HasVerb("list") {
239
240			sw.Do(adjustTemplate(e.VerbName, e.VerbType, listTemplate), m)
241		}
242
243		// TODO: Figure out schemantic for watching a sub-resource.
244		if e.HasVerb("watch") {
245			sw.Do(adjustTemplate(e.VerbName, e.VerbType, watchTemplate), m)
246		}
247
248		if e.HasVerb("create") {
249			if e.IsSubresource() {
250				sw.Do(adjustTemplate(e.VerbName, e.VerbType, createSubresourceTemplate), m)
251			} else {
252				sw.Do(adjustTemplate(e.VerbName, e.VerbType, createTemplate), m)
253			}
254		}
255
256		if e.HasVerb("update") {
257			if e.IsSubresource() {
258				sw.Do(adjustTemplate(e.VerbName, e.VerbType, updateSubresourceTemplate), m)
259			} else {
260				sw.Do(adjustTemplate(e.VerbName, e.VerbType, updateTemplate), m)
261			}
262		}
263
264		// TODO: Figure out schemantic for deleting a sub-resource (what arguments
265		// are passed, does it need two names? etc.
266		if e.HasVerb("delete") {
267			sw.Do(adjustTemplate(e.VerbName, e.VerbType, deleteTemplate), m)
268		}
269
270		if e.HasVerb("patch") {
271			sw.Do(adjustTemplate(e.VerbName, e.VerbType, patchTemplate), m)
272		}
273	}
274
275	return sw.Error()
276}
277
278// adjustTemplate adjust the origin verb template using the expansion name.
279// TODO: Make the verbs in templates parametrized so the strings.Replace() is
280// not needed.
281func adjustTemplate(name, verbType, template string) string {
282	return strings.Replace(template, " "+strings.Title(verbType), " "+name, -1)
283}
284
285// template for the struct that implements the type's interface
286var structNamespaced = `
287// Fake$.type|publicPlural$ implements $.type|public$Interface
288type Fake$.type|publicPlural$ struct {
289	Fake *Fake$.GroupGoName$$.Version$
290	ns     string
291}
292`
293
294// template for the struct that implements the type's interface
295var structNonNamespaced = `
296// Fake$.type|publicPlural$ implements $.type|public$Interface
297type Fake$.type|publicPlural$ struct {
298	Fake *Fake$.GroupGoName$$.Version$
299}
300`
301
302var resource = `
303var $.type|allLowercasePlural$Resource = $.GroupVersionResource|raw${Group: "$.groupName$", Version: "$.version$", Resource: "$.type|resource$"}
304`
305
306var kind = `
307var $.type|allLowercasePlural$Kind = $.GroupVersionKind|raw${Group: "$.groupName$", Version: "$.version$", Kind: "$.type|singularKind$"}
308`
309
310var listTemplate = `
311// List takes label and field selectors, and returns the list of $.type|publicPlural$ that match those selectors.
312func (c *Fake$.type|publicPlural$) List(opts $.ListOptions|raw$) (result *$.type|raw$List, err error) {
313	obj, err := c.Fake.
314		$if .namespaced$Invokes($.NewListAction|raw$($.type|allLowercasePlural$Resource, $.type|allLowercasePlural$Kind, c.ns, opts), &$.type|raw$List{})
315		$else$Invokes($.NewRootListAction|raw$($.type|allLowercasePlural$Resource, $.type|allLowercasePlural$Kind, opts), &$.type|raw$List{})$end$
316	if obj == nil {
317		return nil, err
318	}
319	return obj.(*$.type|raw$List), err
320}
321`
322
323var listUsingOptionsTemplate = `
324// List takes label and field selectors, and returns the list of $.type|publicPlural$ that match those selectors.
325func (c *Fake$.type|publicPlural$) List(opts $.ListOptions|raw$) (result *$.type|raw$List, err error) {
326	obj, err := c.Fake.
327		$if .namespaced$Invokes($.NewListAction|raw$($.type|allLowercasePlural$Resource, $.type|allLowercasePlural$Kind, c.ns, opts), &$.type|raw$List{})
328		$else$Invokes($.NewRootListAction|raw$($.type|allLowercasePlural$Resource, $.type|allLowercasePlural$Kind, opts), &$.type|raw$List{})$end$
329	if obj == nil {
330		return nil, err
331	}
332
333	label, _, _ := $.ExtractFromListOptions|raw$(opts)
334	if label == nil {
335		label = $.Everything|raw$()
336	}
337	list := &$.type|raw$List{ListMeta: obj.(*$.type|raw$List).ListMeta}
338	for _, item := range obj.(*$.type|raw$List).Items {
339		if label.Matches(labels.Set(item.Labels)) {
340			list.Items = append(list.Items, item)
341		}
342	}
343	return list, err
344}
345`
346
347var getTemplate = `
348// Get takes name of the $.type|private$, and returns the corresponding $.resultType|private$ object, and an error if there is any.
349func (c *Fake$.type|publicPlural$) Get(name string, options $.GetOptions|raw$) (result *$.resultType|raw$, err error) {
350	obj, err := c.Fake.
351		$if .namespaced$Invokes($.NewGetAction|raw$($.type|allLowercasePlural$Resource, c.ns, name), &$.resultType|raw${})
352		$else$Invokes($.NewRootGetAction|raw$($.type|allLowercasePlural$Resource, name), &$.resultType|raw${})$end$
353	if obj == nil {
354		return nil, err
355	}
356	return obj.(*$.resultType|raw$), err
357}
358`
359
360var getSubresourceTemplate = `
361// Get takes name of the $.type|private$, and returns the corresponding $.resultType|private$ object, and an error if there is any.
362func (c *Fake$.type|publicPlural$) Get($.type|private$Name string, options $.GetOptions|raw$) (result *$.resultType|raw$, err error) {
363	obj, err := c.Fake.
364		$if .namespaced$Invokes($.NewGetSubresourceAction|raw$($.type|allLowercasePlural$Resource, c.ns, "$.subresourcePath$", $.type|private$Name), &$.resultType|raw${})
365		$else$Invokes($.NewRootGetSubresourceAction|raw$($.type|allLowercasePlural$Resource, "$.subresourcePath$", $.type|private$Name), &$.resultType|raw${})$end$
366	if obj == nil {
367		return nil, err
368	}
369	return obj.(*$.resultType|raw$), err
370}
371`
372
373var deleteTemplate = `
374// Delete takes name of the $.type|private$ and deletes it. Returns an error if one occurs.
375func (c *Fake$.type|publicPlural$) Delete(name string, options *$.DeleteOptions|raw$) error {
376	_, err := c.Fake.
377		$if .namespaced$Invokes($.NewDeleteAction|raw$($.type|allLowercasePlural$Resource, c.ns, name), &$.type|raw${})
378		$else$Invokes($.NewRootDeleteAction|raw$($.type|allLowercasePlural$Resource, name), &$.type|raw${})$end$
379	return err
380}
381`
382
383var deleteCollectionTemplate = `
384// DeleteCollection deletes a collection of objects.
385func (c *Fake$.type|publicPlural$) DeleteCollection(options *$.DeleteOptions|raw$, listOptions $.ListOptions|raw$) error {
386	$if .namespaced$action := $.NewDeleteCollectionAction|raw$($.type|allLowercasePlural$Resource, c.ns, listOptions)
387	$else$action := $.NewRootDeleteCollectionAction|raw$($.type|allLowercasePlural$Resource, listOptions)
388	$end$
389	_, err := c.Fake.Invokes(action, &$.type|raw$List{})
390	return err
391}
392`
393var createTemplate = `
394// Create takes the representation of a $.inputType|private$ and creates it.  Returns the server's representation of the $.resultType|private$, and an error, if there is any.
395func (c *Fake$.type|publicPlural$) Create($.inputType|private$ *$.inputType|raw$) (result *$.resultType|raw$, err error) {
396	obj, err := c.Fake.
397		$if .namespaced$Invokes($.NewCreateAction|raw$($.inputType|allLowercasePlural$Resource, c.ns, $.inputType|private$), &$.resultType|raw${})
398		$else$Invokes($.NewRootCreateAction|raw$($.inputType|allLowercasePlural$Resource, $.inputType|private$), &$.resultType|raw${})$end$
399	if obj == nil {
400		return nil, err
401	}
402	return obj.(*$.resultType|raw$), err
403}
404`
405
406var createSubresourceTemplate = `
407// Create takes the representation of a $.inputType|private$ and creates it.  Returns the server's representation of the $.resultType|private$, and an error, if there is any.
408func (c *Fake$.type|publicPlural$) Create($.type|private$Name string, $.inputType|private$ *$.inputType|raw$) (result *$.resultType|raw$, err error) {
409	obj, err := c.Fake.
410		$if .namespaced$Invokes($.NewCreateSubresourceAction|raw$($.type|allLowercasePlural$Resource, $.type|private$Name, "$.subresourcePath$", c.ns, $.inputType|private$), &$.resultType|raw${})
411		$else$Invokes($.NewRootCreateSubresourceAction|raw$($.type|allLowercasePlural$Resource, "$.subresourcePath$", $.inputType|private$), &$.resultType|raw${})$end$
412	if obj == nil {
413		return nil, err
414	}
415	return obj.(*$.resultType|raw$), err
416}
417`
418
419var updateTemplate = `
420// Update takes the representation of a $.inputType|private$ and updates it. Returns the server's representation of the $.resultType|private$, and an error, if there is any.
421func (c *Fake$.type|publicPlural$) Update($.inputType|private$ *$.inputType|raw$) (result *$.resultType|raw$, err error) {
422	obj, err := c.Fake.
423		$if .namespaced$Invokes($.NewUpdateAction|raw$($.inputType|allLowercasePlural$Resource, c.ns, $.inputType|private$), &$.resultType|raw${})
424		$else$Invokes($.NewRootUpdateAction|raw$($.inputType|allLowercasePlural$Resource, $.inputType|private$), &$.resultType|raw${})$end$
425	if obj == nil {
426		return nil, err
427	}
428	return obj.(*$.resultType|raw$), err
429}
430`
431
432var updateSubresourceTemplate = `
433// Update takes the representation of a $.inputType|private$ and updates it. Returns the server's representation of the $.resultType|private$, and an error, if there is any.
434func (c *Fake$.type|publicPlural$) Update($.type|private$Name string, $.inputType|private$ *$.inputType|raw$) (result *$.resultType|raw$, err error) {
435	obj, err := c.Fake.
436		$if .namespaced$Invokes($.NewUpdateSubresourceAction|raw$($.type|allLowercasePlural$Resource, "$.subresourcePath$", c.ns, $.inputType|private$), &$.inputType|raw${})
437		$else$Invokes($.NewRootUpdateSubresourceAction|raw$($.type|allLowercasePlural$Resource, "$.subresourcePath$", $.inputType|private$), &$.resultType|raw${})$end$
438	if obj == nil {
439		return nil, err
440	}
441	return obj.(*$.resultType|raw$), err
442}
443`
444
445var updateStatusTemplate = `
446// UpdateStatus was generated because the type contains a Status member.
447// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
448func (c *Fake$.type|publicPlural$) UpdateStatus($.type|private$ *$.type|raw$) (*$.type|raw$, error) {
449	obj, err := c.Fake.
450		$if .namespaced$Invokes($.NewUpdateSubresourceAction|raw$($.type|allLowercasePlural$Resource, "status", c.ns, $.type|private$), &$.type|raw${})
451		$else$Invokes($.NewRootUpdateSubresourceAction|raw$($.type|allLowercasePlural$Resource, "status", $.type|private$), &$.type|raw${})$end$
452	if obj == nil {
453		return nil, err
454	}
455	return obj.(*$.type|raw$), err
456}
457`
458
459var watchTemplate = `
460// Watch returns a $.watchInterface|raw$ that watches the requested $.type|privatePlural$.
461func (c *Fake$.type|publicPlural$) Watch(opts $.ListOptions|raw$) ($.watchInterface|raw$, error) {
462	return c.Fake.
463		$if .namespaced$InvokesWatch($.NewWatchAction|raw$($.type|allLowercasePlural$Resource, c.ns, opts))
464		$else$InvokesWatch($.NewRootWatchAction|raw$($.type|allLowercasePlural$Resource, opts))$end$
465}
466`
467
468var patchTemplate = `
469// Patch applies the patch and returns the patched $.resultType|private$.
470func (c *Fake$.type|publicPlural$) Patch(name string, pt $.PatchType|raw$, data []byte, subresources ...string) (result *$.resultType|raw$, err error) {
471	obj, err := c.Fake.
472		$if .namespaced$Invokes($.NewPatchSubresourceAction|raw$($.type|allLowercasePlural$Resource, c.ns, name, pt, data, subresources... ), &$.resultType|raw${})
473		$else$Invokes($.NewRootPatchSubresourceAction|raw$($.type|allLowercasePlural$Resource, name, pt, data, subresources...), &$.resultType|raw${})$end$
474	if obj == nil {
475		return nil, err
476	}
477	return obj.(*$.resultType|raw$), err
478}
479`
480