2 Copyright 2018 The Kubernetes Authors.
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
25 "k8s.io/apimachinery/pkg/util/sets"
27 "sigs.k8s.io/controller-tools/pkg/internal/codegen"
30 type genUnversionedType struct {
32 Resource *codegen.APIResource
35 func (b *APIs) parseAPIs() {
36 apis := &codegen.APIs{
39 Groups: map[string]*codegen.APIGroup{},
41 Informers: b.Informers,
44 for group, versionMap := range b.ByGroupVersionKind {
45 apiGroup := &codegen.APIGroup{
47 GroupTitle: strings.Title(group),
49 Versions: map[string]*codegen.APIVersion{},
50 UnversionedResources: map[string]*codegen.APIResource{},
53 for version, kindMap := range versionMap {
54 apiVersion := &codegen.APIVersion{
58 Resources: map[string]*codegen.APIResource{},
60 for kind, resource := range kindMap {
61 apiResource := &codegen.APIResource{
62 Domain: resource.Domain,
63 Version: resource.Version,
64 Group: resource.Group,
65 Resource: resource.Resource,
69 Subresources: resource.Subresources,
70 StatusStrategy: resource.StatusStrategy,
71 Strategy: resource.Strategy,
72 NonNamespaced: resource.NonNamespaced,
73 ShortName: resource.ShortName,
75 parseDoc(resource, apiResource)
76 apiVersion.Resources[kind] = apiResource
77 // Set the package for the api version
78 apiVersion.Pkg = b.context.Universe[resource.Type.Name.Package]
79 // Set the package for the api group
80 apiGroup.Pkg = b.context.Universe[filepath.Dir(resource.Type.Name.Package)]
81 if apiGroup.Pkg != nil {
82 apiGroup.PkgPath = apiGroup.Pkg.Path
85 apiGroup.UnversionedResources[kind] = apiResource
88 apiGroup.Versions[version] = apiVersion
90 b.parseStructs(apiGroup)
91 apis.Groups[group] = apiGroup
93 apis.Pkg = b.context.Universe[b.APIsPkg]
97 func (b *APIs) parseStructs(apigroup *codegen.APIGroup) {
98 remaining := []genUnversionedType{}
99 for _, version := range apigroup.Versions {
100 for _, resource := range version.Resources {
101 remaining = append(remaining, genUnversionedType{resource.Type, resource})
104 for _, version := range b.SubByGroupVersionKind[apigroup.Group] {
105 for _, kind := range version {
106 remaining = append(remaining, genUnversionedType{kind, nil})
110 done := sets.String{}
111 for len(remaining) > 0 {
112 // Pop the next element from the list
114 remaining[0] = remaining[len(remaining)-1]
115 remaining = remaining[:len(remaining)-1]
117 // Already processed this type. Skip it
118 if done.Has(next.Type.Name.Name) {
121 done.Insert(next.Type.Name.Name)
123 // Generate the struct and append to the list
124 result, additionalTypes := parseType(next.Type)
126 // This is a resource, so generate the client
127 if b.genClient(next.Type) {
128 result.GenClient = true
129 result.GenDeepCopy = true
132 if next.Resource != nil {
133 result.NonNamespaced = IsNonNamespaced(next.Type)
136 if b.genDeepCopy(next.Type) {
137 result.GenDeepCopy = true
139 apigroup.Structs = append(apigroup.Structs, result)
141 // Add the newly discovered subtypes
142 for _, at := range additionalTypes {
143 remaining = append(remaining, genUnversionedType{at, nil})
148 // parseType parses the type into a Struct, and returns a list of types that
150 func parseType(t *types.Type) (*codegen.Struct, []*types.Type) {
151 remaining := []*types.Type{}
153 s := &codegen.Struct{
156 GenUnversioned: true, // Generate unversioned structs by default
159 for _, c := range t.CommentLines {
160 if strings.Contains(c, "+genregister:unversioned=false") {
161 // Don't generate the unversioned struct
162 s.GenUnversioned = false
166 for _, member := range t.Members {
167 uType := member.Type.Name.Name
168 memberName := member.Name
171 // Use the element type for Pointers, Maps and Slices
172 mSubType := member.Type
174 for mSubType.Elem != nil {
175 mSubType = mSubType.Elem
179 // Strip the package from the field type
180 uType = strings.Replace(member.Type.String(), mSubType.Name.Package+".", "", 1)
183 base := filepath.Base(member.Type.String())
184 samepkg := t.Name.Package == mSubType.Name.Package
186 // If not in the same package, calculate the import pkg
188 parts := strings.Split(base, ".")
190 // Don't generate unversioned types for core types, just use the versioned types
191 if strings.HasPrefix(mSubType.Name.Package, "k8s.io/api/") {
192 // Import the package under an alias so it doesn't conflict with other groups
193 // having the same version
194 importAlias := path.Base(path.Dir(mSubType.Name.Package)) + path.Base(mSubType.Name.Package)
195 uImport = fmt.Sprintf("%s \"%s\"", importAlias, mSubType.Name.Package)
197 // Replace the full package with the alias when referring to the type
198 uType = strings.Replace(member.Type.String(), mSubType.Name.Package, importAlias, 1)
200 // Replace the full package with the alias when referring to the type
201 uType = fmt.Sprintf("%s.%s", importAlias, parts[1])
204 switch member.Type.Name.Package {
205 case "k8s.io/apimachinery/pkg/apis/meta/v1":
206 // Use versioned types for meta/v1
207 uImport = fmt.Sprintf("%s \"%s\"", "metav1", "k8s.io/apimachinery/pkg/apis/meta/v1")
208 uType = "metav1." + parts[1]
210 // Use unversioned types for everything else
214 // handle Pointers, Maps, Slices
216 // We need to parse the package from the Type String
218 str := member.Type.String()
219 startPkg := strings.LastIndexAny(str, "*]")
220 endPkg := strings.LastIndexAny(str, ".")
221 pkg := str[startPkg+1 : endPkg]
222 name := str[endPkg+1:]
223 prefix := str[:startPkg+1]
225 uImportBase := path.Base(pkg)
226 uImportName := path.Base(path.Dir(pkg)) + uImportBase
227 uImport = fmt.Sprintf("%s \"%s\"", uImportName, pkg)
229 uType = prefix + uImportName + "." + name
231 // handle non- Pointer, Maps, Slices
232 pkg := t.Name.Package
235 // Come up with the alias the package is imported under
236 // Concatenate with directory package to reduce naming collisions
237 uImportBase := path.Base(pkg)
238 uImportName := path.Base(path.Dir(pkg)) + uImportBase
240 // Create the import statement
241 uImport = fmt.Sprintf("%s \"%s\"", uImportName, pkg)
243 // Create the field type name - should be <pkgalias>.<TypeName>
244 uType = uImportName + "." + name
255 s.Fields = append(s.Fields, &codegen.Field{
257 VersionedPackage: member.Type.Name.Package,
258 UnversionedImport: uImport,
259 UnversionedType: uType,
262 // Add this member Type for processing if it isn't a primitive and
263 // is part of the same API group
264 if !mSubType.IsPrimitive() && GetGroup(mSubType) == GetGroup(t) {
265 remaining = append(remaining, mSubType)
271 func (b *APIs) genClient(c *types.Type) bool {
272 comments := Comments(c.CommentLines)
273 resource := comments.getTag("resource", ":") + comments.getTag("kubebuilder:resource", ":")
274 return len(resource) > 0
277 func (b *APIs) genDeepCopy(c *types.Type) bool {
278 comments := Comments(c.CommentLines)
279 return comments.hasTag("subresource-request")
282 func parseDoc(resource, apiResource *codegen.APIResource) {
283 if HasDocAnnotation(resource.Type) {
284 resource.DocAnnotation = getDocAnnotation(resource.Type, "warning", "note")
285 apiResource.DocAnnotation = resource.DocAnnotation