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 "path" 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 "k8s.io/klog/v2" 30 31 "k8s.io/code-generator/cmd/client-gen/generators/util" 32 clientgentypes "k8s.io/code-generator/cmd/client-gen/types" 33 informergenargs "k8s.io/code-generator/cmd/informer-gen/args" 34 genutil "k8s.io/code-generator/pkg/util" 35) 36 37// NameSystems returns the name system used by the generators in this package. 38func NameSystems(pluralExceptions map[string]string) namer.NameSystems { 39 return namer.NameSystems{ 40 "public": namer.NewPublicNamer(0), 41 "private": namer.NewPrivateNamer(0), 42 "raw": namer.NewRawNamer("", nil), 43 "publicPlural": namer.NewPublicPluralNamer(pluralExceptions), 44 "allLowercasePlural": namer.NewAllLowercasePluralNamer(pluralExceptions), 45 "lowercaseSingular": &lowercaseSingularNamer{}, 46 } 47} 48 49// lowercaseSingularNamer implements Namer 50type lowercaseSingularNamer struct{} 51 52// Name returns t's name in all lowercase. 53func (n *lowercaseSingularNamer) Name(t *types.Type) string { 54 return strings.ToLower(t.Name.Name) 55} 56 57// DefaultNameSystem returns the default name system for ordering the types to be 58// processed by the generators in this package. 59func DefaultNameSystem() string { 60 return "public" 61} 62 63// objectMetaForPackage returns the type of ObjectMeta used by package p. 64func objectMetaForPackage(p *types.Package) (*types.Type, bool, error) { 65 generatingForPackage := false 66 for _, t := range p.Types { 67 if !util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)).GenerateClient { 68 continue 69 } 70 generatingForPackage = true 71 for _, member := range t.Members { 72 if member.Name == "ObjectMeta" { 73 return member.Type, isInternal(member), nil 74 } 75 } 76 } 77 if generatingForPackage { 78 return nil, false, fmt.Errorf("unable to find ObjectMeta for any types in package %s", p.Path) 79 } 80 return nil, false, nil 81} 82 83// isInternal returns true if the tags for a member do not contain a json tag 84func isInternal(m types.Member) bool { 85 return !strings.Contains(m.Tags, "json") 86} 87 88func packageForInternalInterfaces(base string) string { 89 return filepath.Join(base, "internalinterfaces") 90} 91 92// Packages makes the client package definition. 93func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages { 94 boilerplate, err := arguments.LoadGoBoilerplate() 95 if err != nil { 96 klog.Fatalf("Failed loading boilerplate: %v", err) 97 } 98 99 customArgs, ok := arguments.CustomArgs.(*informergenargs.CustomArgs) 100 if !ok { 101 klog.Fatalf("Wrong CustomArgs type: %T", arguments.CustomArgs) 102 } 103 104 internalVersionPackagePath := filepath.Join(arguments.OutputPackagePath) 105 externalVersionPackagePath := filepath.Join(arguments.OutputPackagePath) 106 if !customArgs.SingleDirectory { 107 internalVersionPackagePath = filepath.Join(arguments.OutputPackagePath, "internalversion") 108 externalVersionPackagePath = filepath.Join(arguments.OutputPackagePath, "externalversions") 109 } 110 111 var packageList generator.Packages 112 typesForGroupVersion := make(map[clientgentypes.GroupVersion][]*types.Type) 113 114 externalGroupVersions := make(map[string]clientgentypes.GroupVersions) 115 internalGroupVersions := make(map[string]clientgentypes.GroupVersions) 116 groupGoNames := make(map[string]string) 117 for _, inputDir := range arguments.InputDirs { 118 p := context.Universe.Package(genutil.Vendorless(inputDir)) 119 120 objectMeta, internal, err := objectMetaForPackage(p) 121 if err != nil { 122 klog.Fatal(err) 123 } 124 if objectMeta == nil { 125 // no types in this package had genclient 126 continue 127 } 128 129 var gv clientgentypes.GroupVersion 130 var targetGroupVersions map[string]clientgentypes.GroupVersions 131 132 if internal { 133 lastSlash := strings.LastIndex(p.Path, "/") 134 if lastSlash == -1 { 135 klog.Fatalf("error constructing internal group version for package %q", p.Path) 136 } 137 gv.Group = clientgentypes.Group(p.Path[lastSlash+1:]) 138 targetGroupVersions = internalGroupVersions 139 } else { 140 parts := strings.Split(p.Path, "/") 141 gv.Group = clientgentypes.Group(parts[len(parts)-2]) 142 gv.Version = clientgentypes.Version(parts[len(parts)-1]) 143 targetGroupVersions = externalGroupVersions 144 } 145 groupPackageName := gv.Group.NonEmpty() 146 gvPackage := path.Clean(p.Path) 147 148 // If there's a comment of the form "// +groupName=somegroup" or 149 // "// +groupName=somegroup.foo.bar.io", use the first field (somegroup) as the name of the 150 // group when generating. 151 if override := types.ExtractCommentTags("+", p.Comments)["groupName"]; override != nil { 152 gv.Group = clientgentypes.Group(override[0]) 153 } 154 155 // If there's a comment of the form "// +groupGoName=SomeUniqueShortName", use that as 156 // the Go group identifier in CamelCase. It defaults 157 groupGoNames[groupPackageName] = namer.IC(strings.Split(gv.Group.NonEmpty(), ".")[0]) 158 if override := types.ExtractCommentTags("+", p.Comments)["groupGoName"]; override != nil { 159 groupGoNames[groupPackageName] = namer.IC(override[0]) 160 } 161 162 var typesToGenerate []*types.Type 163 for _, t := range p.Types { 164 tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)) 165 if !tags.GenerateClient || tags.NoVerbs || !tags.HasVerb("list") || !tags.HasVerb("watch") { 166 continue 167 } 168 169 typesToGenerate = append(typesToGenerate, t) 170 171 if _, ok := typesForGroupVersion[gv]; !ok { 172 typesForGroupVersion[gv] = []*types.Type{} 173 } 174 typesForGroupVersion[gv] = append(typesForGroupVersion[gv], t) 175 } 176 if len(typesToGenerate) == 0 { 177 continue 178 } 179 180 groupVersionsEntry, ok := targetGroupVersions[groupPackageName] 181 if !ok { 182 groupVersionsEntry = clientgentypes.GroupVersions{ 183 PackageName: groupPackageName, 184 Group: gv.Group, 185 } 186 } 187 groupVersionsEntry.Versions = append(groupVersionsEntry.Versions, clientgentypes.PackageVersion{Version: gv.Version, Package: gvPackage}) 188 targetGroupVersions[groupPackageName] = groupVersionsEntry 189 190 orderer := namer.Orderer{Namer: namer.NewPrivateNamer(0)} 191 typesToGenerate = orderer.OrderTypes(typesToGenerate) 192 193 if internal { 194 packageList = append(packageList, versionPackage(internalVersionPackagePath, groupPackageName, gv, groupGoNames[groupPackageName], boilerplate, typesToGenerate, customArgs.InternalClientSetPackage, customArgs.ListersPackage)) 195 } else { 196 packageList = append(packageList, versionPackage(externalVersionPackagePath, groupPackageName, gv, groupGoNames[groupPackageName], boilerplate, typesToGenerate, customArgs.VersionedClientSetPackage, customArgs.ListersPackage)) 197 } 198 } 199 200 if len(externalGroupVersions) != 0 { 201 packageList = append(packageList, factoryInterfacePackage(externalVersionPackagePath, boilerplate, customArgs.VersionedClientSetPackage)) 202 packageList = append(packageList, factoryPackage(externalVersionPackagePath, boilerplate, groupGoNames, genutil.PluralExceptionListToMapOrDie(customArgs.PluralExceptions), externalGroupVersions, 203 customArgs.VersionedClientSetPackage, 204 typesForGroupVersion)) 205 for _, gvs := range externalGroupVersions { 206 packageList = append(packageList, groupPackage(externalVersionPackagePath, gvs, boilerplate)) 207 } 208 } 209 210 if len(internalGroupVersions) != 0 { 211 packageList = append(packageList, factoryInterfacePackage(internalVersionPackagePath, boilerplate, customArgs.InternalClientSetPackage)) 212 packageList = append(packageList, factoryPackage(internalVersionPackagePath, boilerplate, groupGoNames, genutil.PluralExceptionListToMapOrDie(customArgs.PluralExceptions), internalGroupVersions, customArgs.InternalClientSetPackage, typesForGroupVersion)) 213 for _, gvs := range internalGroupVersions { 214 packageList = append(packageList, groupPackage(internalVersionPackagePath, gvs, boilerplate)) 215 } 216 } 217 218 return packageList 219} 220 221func factoryPackage(basePackage string, boilerplate []byte, groupGoNames, pluralExceptions map[string]string, groupVersions map[string]clientgentypes.GroupVersions, clientSetPackage string, 222 typesForGroupVersion map[clientgentypes.GroupVersion][]*types.Type) generator.Package { 223 return &generator.DefaultPackage{ 224 PackageName: filepath.Base(basePackage), 225 PackagePath: basePackage, 226 HeaderText: boilerplate, 227 GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) { 228 generators = append(generators, &factoryGenerator{ 229 DefaultGen: generator.DefaultGen{ 230 OptionalName: "factory", 231 }, 232 outputPackage: basePackage, 233 imports: generator.NewImportTracker(), 234 groupVersions: groupVersions, 235 clientSetPackage: clientSetPackage, 236 internalInterfacesPackage: packageForInternalInterfaces(basePackage), 237 gvGoNames: groupGoNames, 238 }) 239 240 generators = append(generators, &genericGenerator{ 241 DefaultGen: generator.DefaultGen{ 242 OptionalName: "generic", 243 }, 244 outputPackage: basePackage, 245 imports: generator.NewImportTracker(), 246 groupVersions: groupVersions, 247 pluralExceptions: pluralExceptions, 248 typesForGroupVersion: typesForGroupVersion, 249 groupGoNames: groupGoNames, 250 }) 251 252 return generators 253 }, 254 } 255} 256 257func factoryInterfacePackage(basePackage string, boilerplate []byte, clientSetPackage string) generator.Package { 258 packagePath := packageForInternalInterfaces(basePackage) 259 260 return &generator.DefaultPackage{ 261 PackageName: filepath.Base(packagePath), 262 PackagePath: packagePath, 263 HeaderText: boilerplate, 264 GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) { 265 generators = append(generators, &factoryInterfaceGenerator{ 266 DefaultGen: generator.DefaultGen{ 267 OptionalName: "factory_interfaces", 268 }, 269 outputPackage: packagePath, 270 imports: generator.NewImportTracker(), 271 clientSetPackage: clientSetPackage, 272 }) 273 274 return generators 275 }, 276 } 277} 278 279func groupPackage(basePackage string, groupVersions clientgentypes.GroupVersions, boilerplate []byte) generator.Package { 280 packagePath := filepath.Join(basePackage, groupVersions.PackageName) 281 groupPkgName := strings.Split(string(groupVersions.PackageName), ".")[0] 282 283 return &generator.DefaultPackage{ 284 PackageName: groupPkgName, 285 PackagePath: packagePath, 286 HeaderText: boilerplate, 287 GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) { 288 generators = append(generators, &groupInterfaceGenerator{ 289 DefaultGen: generator.DefaultGen{ 290 OptionalName: "interface", 291 }, 292 outputPackage: packagePath, 293 groupVersions: groupVersions, 294 imports: generator.NewImportTracker(), 295 internalInterfacesPackage: packageForInternalInterfaces(basePackage), 296 }) 297 return generators 298 }, 299 FilterFunc: func(c *generator.Context, t *types.Type) bool { 300 tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)) 301 return tags.GenerateClient && tags.HasVerb("list") && tags.HasVerb("watch") 302 }, 303 } 304} 305 306func versionPackage(basePackage string, groupPkgName string, gv clientgentypes.GroupVersion, groupGoName string, boilerplate []byte, typesToGenerate []*types.Type, clientSetPackage, listersPackage string) generator.Package { 307 packagePath := filepath.Join(basePackage, groupPkgName, strings.ToLower(gv.Version.NonEmpty())) 308 309 return &generator.DefaultPackage{ 310 PackageName: strings.ToLower(gv.Version.NonEmpty()), 311 PackagePath: packagePath, 312 HeaderText: boilerplate, 313 GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) { 314 generators = append(generators, &versionInterfaceGenerator{ 315 DefaultGen: generator.DefaultGen{ 316 OptionalName: "interface", 317 }, 318 outputPackage: packagePath, 319 imports: generator.NewImportTracker(), 320 types: typesToGenerate, 321 internalInterfacesPackage: packageForInternalInterfaces(basePackage), 322 }) 323 324 for _, t := range typesToGenerate { 325 generators = append(generators, &informerGenerator{ 326 DefaultGen: generator.DefaultGen{ 327 OptionalName: strings.ToLower(t.Name.Name), 328 }, 329 outputPackage: packagePath, 330 groupPkgName: groupPkgName, 331 groupVersion: gv, 332 groupGoName: groupGoName, 333 typeToGenerate: t, 334 imports: generator.NewImportTracker(), 335 clientSetPackage: clientSetPackage, 336 listersPackage: listersPackage, 337 internalInterfacesPackage: packageForInternalInterfaces(basePackage), 338 }) 339 } 340 return generators 341 }, 342 FilterFunc: func(c *generator.Context, t *types.Type) bool { 343 tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)) 344 return tags.GenerateClient && tags.HasVerb("list") && tags.HasVerb("watch") 345 }, 346 } 347} 348