1/*
2Copyright 2017 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 scheme
18
19import (
20	"fmt"
21	"io"
22	"os"
23	"path/filepath"
24	"strings"
25
26	"k8s.io/code-generator/cmd/client-gen/path"
27	clientgentypes "k8s.io/code-generator/cmd/client-gen/types"
28	"k8s.io/gengo/generator"
29	"k8s.io/gengo/namer"
30	"k8s.io/gengo/types"
31)
32
33// GenScheme produces a package for a clientset with the scheme, codecs and parameter codecs.
34type GenScheme struct {
35	generator.DefaultGen
36	OutputPackage   string
37	Groups          []clientgentypes.GroupVersions
38	GroupGoNames    map[clientgentypes.GroupVersion]string
39	InputPackages   map[clientgentypes.GroupVersion]string
40	OutputPath      string
41	ImportTracker   namer.ImportTracker
42	PrivateScheme   bool
43	CreateRegistry  bool
44	schemeGenerated bool
45}
46
47func (g *GenScheme) Namers(c *generator.Context) namer.NameSystems {
48	return namer.NameSystems{
49		"raw": namer.NewRawNamer(g.OutputPackage, g.ImportTracker),
50	}
51}
52
53// We only want to call GenerateType() once.
54func (g *GenScheme) Filter(c *generator.Context, t *types.Type) bool {
55	ret := !g.schemeGenerated
56	g.schemeGenerated = true
57	return ret
58}
59
60func (g *GenScheme) Imports(c *generator.Context) (imports []string) {
61	imports = append(imports, g.ImportTracker.ImportLines()...)
62	for _, group := range g.Groups {
63		for _, version := range group.Versions {
64			packagePath := g.InputPackages[clientgentypes.GroupVersion{Group: group.Group, Version: version.Version}]
65			groupAlias := strings.ToLower(g.GroupGoNames[clientgentypes.GroupVersion{Group: group.Group, Version: version.Version}])
66			if g.CreateRegistry {
67				// import the install package for internal clientsets instead of the type package with register.go
68				if version.Version != "" {
69					packagePath = filepath.Dir(packagePath)
70				}
71				packagePath = filepath.Join(packagePath, "install")
72
73				imports = append(imports, fmt.Sprintf("%s \"%s\"", groupAlias, path.Vendorless(packagePath)))
74				break
75			} else {
76				imports = append(imports, fmt.Sprintf("%s%s \"%s\"", groupAlias, strings.ToLower(version.Version.NonEmpty()), path.Vendorless(packagePath)))
77			}
78		}
79	}
80	return
81}
82
83func (g *GenScheme) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
84	sw := generator.NewSnippetWriter(w, c, "$", "$")
85
86	allGroupVersions := clientgentypes.ToGroupVersionInfo(g.Groups, g.GroupGoNames)
87	allInstallGroups := clientgentypes.ToGroupInstallPackages(g.Groups, g.GroupGoNames)
88
89	m := map[string]interface{}{
90		"allGroupVersions":          allGroupVersions,
91		"allInstallGroups":          allInstallGroups,
92		"customRegister":            false,
93		"runtimeNewParameterCodec":  c.Universe.Function(types.Name{Package: "k8s.io/apimachinery/pkg/runtime", Name: "NewParameterCodec"}),
94		"runtimeNewScheme":          c.Universe.Function(types.Name{Package: "k8s.io/apimachinery/pkg/runtime", Name: "NewScheme"}),
95		"serializerNewCodecFactory": c.Universe.Function(types.Name{Package: "k8s.io/apimachinery/pkg/runtime/serializer", Name: "NewCodecFactory"}),
96		"runtimeScheme":             c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/runtime", Name: "Scheme"}),
97		"runtimeSchemeBuilder":      c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/runtime", Name: "SchemeBuilder"}),
98		"runtimeUtilMust":           c.Universe.Function(types.Name{Package: "k8s.io/apimachinery/pkg/util/runtime", Name: "Must"}),
99		"schemaGroupVersion":        c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/runtime/schema", Name: "GroupVersion"}),
100		"metav1AddToGroupVersion":   c.Universe.Function(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "AddToGroupVersion"}),
101	}
102	globals := map[string]string{
103		"Scheme":         "Scheme",
104		"Codecs":         "Codecs",
105		"ParameterCodec": "ParameterCodec",
106		"Registry":       "Registry",
107	}
108	for k, v := range globals {
109		if g.PrivateScheme {
110			m[k] = strings.ToLower(v[0:1]) + v[1:]
111		} else {
112			m[k] = v
113		}
114	}
115
116	sw.Do(globalsTemplate, m)
117
118	if g.OutputPath != "" {
119		if _, err := os.Stat(filepath.Join(g.OutputPath, strings.ToLower("register_custom.go"))); err == nil {
120			m["customRegister"] = true
121		}
122	}
123
124	if g.CreateRegistry {
125		sw.Do(registryRegistration, m)
126	} else {
127		sw.Do(simpleRegistration, m)
128	}
129
130	return sw.Error()
131}
132
133var globalsTemplate = `
134var $.Scheme$ = $.runtimeNewScheme|raw$()
135var $.Codecs$ = $.serializerNewCodecFactory|raw$($.Scheme$)
136var $.ParameterCodec$ = $.runtimeNewParameterCodec|raw$($.Scheme$)`
137
138var registryRegistration = `
139
140func init() {
141	$.metav1AddToGroupVersion|raw$($.Scheme$, $.schemaGroupVersion|raw${Version: "v1"})
142	Install($.Scheme$)
143}
144
145// Install registers the API group and adds types to a scheme
146func Install(scheme *$.runtimeScheme|raw$) {
147	$- range .allInstallGroups$
148	$.InstallPackageAlias$.Install(scheme)
149	$- end$
150	$if .customRegister$
151	ExtraInstall(scheme)
152	$end -$
153}
154`
155
156var simpleRegistration = `
157var localSchemeBuilder = $.runtimeSchemeBuilder|raw${
158	$- range .allGroupVersions$
159	$.PackageAlias$.AddToScheme,
160	$- end$
161	$if .customRegister$
162	ExtraAddToScheme,
163	$end -$
164}
165
166// AddToScheme adds all types of this clientset into the given scheme. This allows composition
167// of clientsets, like in:
168//
169//   import (
170//     "k8s.io/client-go/kubernetes"
171//     clientsetscheme "k8s.io/client-go/kubernetes/scheme"
172//     aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
173//   )
174//
175//   kclientset, _ := kubernetes.NewForConfig(c)
176//   _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
177//
178// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
179// correctly.
180var AddToScheme = localSchemeBuilder.AddToScheme
181
182func init() {
183	$.metav1AddToGroupVersion|raw$($.Scheme$, $.schemaGroupVersion|raw${Version: "v1"})
184	$.runtimeUtilMust|raw$(AddToScheme($.Scheme$))
185}
186`
187