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 17// Package generators has the generators for the client-gen utility. 18package generators 19 20import ( 21 "path/filepath" 22 "strings" 23 24 clientgenargs "k8s.io/code-generator/cmd/client-gen/args" 25 "k8s.io/code-generator/cmd/client-gen/generators/fake" 26 "k8s.io/code-generator/cmd/client-gen/generators/scheme" 27 "k8s.io/code-generator/cmd/client-gen/generators/util" 28 "k8s.io/code-generator/cmd/client-gen/path" 29 clientgentypes "k8s.io/code-generator/cmd/client-gen/types" 30 codegennamer "k8s.io/code-generator/pkg/namer" 31 genutil "k8s.io/code-generator/pkg/util" 32 "k8s.io/gengo/args" 33 "k8s.io/gengo/generator" 34 "k8s.io/gengo/namer" 35 "k8s.io/gengo/types" 36 37 "k8s.io/klog/v2" 38) 39 40// NameSystems returns the name system used by the generators in this package. 41func NameSystems(pluralExceptions map[string]string) namer.NameSystems { 42 lowercaseNamer := namer.NewAllLowercasePluralNamer(pluralExceptions) 43 44 publicNamer := &ExceptionNamer{ 45 Exceptions: map[string]string{ 46 // these exceptions are used to deconflict the generated code 47 // you can put your fully qualified package like 48 // to generate a name that doesn't conflict with your group. 49 // "k8s.io/apis/events/v1beta1.Event": "EventResource" 50 }, 51 KeyFunc: func(t *types.Type) string { 52 return t.Name.Package + "." + t.Name.Name 53 }, 54 Delegate: namer.NewPublicNamer(0), 55 } 56 privateNamer := &ExceptionNamer{ 57 Exceptions: map[string]string{ 58 // these exceptions are used to deconflict the generated code 59 // you can put your fully qualified package like 60 // to generate a name that doesn't conflict with your group. 61 // "k8s.io/apis/events/v1beta1.Event": "eventResource" 62 }, 63 KeyFunc: func(t *types.Type) string { 64 return t.Name.Package + "." + t.Name.Name 65 }, 66 Delegate: namer.NewPrivateNamer(0), 67 } 68 publicPluralNamer := &ExceptionNamer{ 69 Exceptions: map[string]string{ 70 // these exceptions are used to deconflict the generated code 71 // you can put your fully qualified package like 72 // to generate a name that doesn't conflict with your group. 73 // "k8s.io/apis/events/v1beta1.Event": "EventResource" 74 }, 75 KeyFunc: func(t *types.Type) string { 76 return t.Name.Package + "." + t.Name.Name 77 }, 78 Delegate: namer.NewPublicPluralNamer(pluralExceptions), 79 } 80 privatePluralNamer := &ExceptionNamer{ 81 Exceptions: map[string]string{ 82 // you can put your fully qualified package like 83 // to generate a name that doesn't conflict with your group. 84 // "k8s.io/apis/events/v1beta1.Event": "eventResource" 85 // these exceptions are used to deconflict the generated code 86 "k8s.io/apis/events/v1beta1.Event": "eventResources", 87 "k8s.io/kubernetes/pkg/apis/events.Event": "eventResources", 88 }, 89 KeyFunc: func(t *types.Type) string { 90 return t.Name.Package + "." + t.Name.Name 91 }, 92 Delegate: namer.NewPrivatePluralNamer(pluralExceptions), 93 } 94 95 return namer.NameSystems{ 96 "singularKind": namer.NewPublicNamer(0), 97 "public": publicNamer, 98 "private": privateNamer, 99 "raw": namer.NewRawNamer("", nil), 100 "publicPlural": publicPluralNamer, 101 "privatePlural": privatePluralNamer, 102 "allLowercasePlural": lowercaseNamer, 103 "resource": codegennamer.NewTagOverrideNamer("resourceName", lowercaseNamer), 104 } 105} 106 107// ExceptionNamer allows you specify exceptional cases with exact names. This allows you to have control 108// for handling various conflicts, like group and resource names for instance. 109type ExceptionNamer struct { 110 Exceptions map[string]string 111 KeyFunc func(*types.Type) string 112 113 Delegate namer.Namer 114} 115 116// Name provides the requested name for a type. 117func (n *ExceptionNamer) Name(t *types.Type) string { 118 key := n.KeyFunc(t) 119 if exception, ok := n.Exceptions[key]; ok { 120 return exception 121 } 122 return n.Delegate.Name(t) 123} 124 125// DefaultNameSystem returns the default name system for ordering the types to be 126// processed by the generators in this package. 127func DefaultNameSystem() string { 128 return "public" 129} 130 131func packageForGroup(gv clientgentypes.GroupVersion, typeList []*types.Type, clientsetPackage string, groupPackageName string, groupGoName string, apiPath string, srcTreePath string, inputPackage string, applyBuilderPackage string, boilerplate []byte) generator.Package { 132 groupVersionClientPackage := filepath.Join(clientsetPackage, "typed", strings.ToLower(groupPackageName), strings.ToLower(gv.Version.NonEmpty())) 133 return &generator.DefaultPackage{ 134 PackageName: strings.ToLower(gv.Version.NonEmpty()), 135 PackagePath: groupVersionClientPackage, 136 HeaderText: boilerplate, 137 PackageDocumentation: []byte( 138 `// This package has the automatically generated typed clients. 139`), 140 // GeneratorFunc returns a list of generators. Each generator makes a 141 // single file. 142 GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) { 143 generators = []generator.Generator{ 144 // Always generate a "doc.go" file. 145 generator.DefaultGen{OptionalName: "doc"}, 146 } 147 // Since we want a file per type that we generate a client for, we 148 // have to provide a function for this. 149 for _, t := range typeList { 150 generators = append(generators, &genClientForType{ 151 DefaultGen: generator.DefaultGen{ 152 OptionalName: strings.ToLower(c.Namers["private"].Name(t)), 153 }, 154 outputPackage: groupVersionClientPackage, 155 inputPackage: inputPackage, 156 clientsetPackage: clientsetPackage, 157 applyConfigurationPackage: applyBuilderPackage, 158 group: gv.Group.NonEmpty(), 159 version: gv.Version.String(), 160 groupGoName: groupGoName, 161 typeToMatch: t, 162 imports: generator.NewImportTracker(), 163 }) 164 } 165 166 generators = append(generators, &genGroup{ 167 DefaultGen: generator.DefaultGen{ 168 OptionalName: groupPackageName + "_client", 169 }, 170 outputPackage: groupVersionClientPackage, 171 inputPackage: inputPackage, 172 clientsetPackage: clientsetPackage, 173 group: gv.Group.NonEmpty(), 174 version: gv.Version.String(), 175 groupGoName: groupGoName, 176 apiPath: apiPath, 177 types: typeList, 178 imports: generator.NewImportTracker(), 179 }) 180 181 expansionFileName := "generated_expansion" 182 generators = append(generators, &genExpansion{ 183 groupPackagePath: filepath.Join(srcTreePath, groupVersionClientPackage), 184 DefaultGen: generator.DefaultGen{ 185 OptionalName: expansionFileName, 186 }, 187 types: typeList, 188 }) 189 190 return generators 191 }, 192 FilterFunc: func(c *generator.Context, t *types.Type) bool { 193 return util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)).GenerateClient 194 }, 195 } 196} 197 198func packageForClientset(customArgs *clientgenargs.CustomArgs, clientsetPackage string, groupGoNames map[clientgentypes.GroupVersion]string, boilerplate []byte) generator.Package { 199 return &generator.DefaultPackage{ 200 PackageName: customArgs.ClientsetName, 201 PackagePath: clientsetPackage, 202 HeaderText: boilerplate, 203 PackageDocumentation: []byte( 204 `// This package has the automatically generated clientset. 205`), 206 // GeneratorFunc returns a list of generators. Each generator generates a 207 // single file. 208 GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) { 209 generators = []generator.Generator{ 210 // Always generate a "doc.go" file. 211 generator.DefaultGen{OptionalName: "doc"}, 212 213 &genClientset{ 214 DefaultGen: generator.DefaultGen{ 215 OptionalName: "clientset", 216 }, 217 groups: customArgs.Groups, 218 groupGoNames: groupGoNames, 219 clientsetPackage: clientsetPackage, 220 outputPackage: customArgs.ClientsetName, 221 imports: generator.NewImportTracker(), 222 }, 223 } 224 return generators 225 }, 226 } 227} 228 229func packageForScheme(customArgs *clientgenargs.CustomArgs, clientsetPackage string, srcTreePath string, groupGoNames map[clientgentypes.GroupVersion]string, boilerplate []byte) generator.Package { 230 schemePackage := filepath.Join(clientsetPackage, "scheme") 231 232 // create runtime.Registry for internal client because it has to know about group versions 233 internalClient := false 234NextGroup: 235 for _, group := range customArgs.Groups { 236 for _, v := range group.Versions { 237 if v.String() == "" { 238 internalClient = true 239 break NextGroup 240 } 241 } 242 } 243 244 return &generator.DefaultPackage{ 245 PackageName: "scheme", 246 PackagePath: schemePackage, 247 HeaderText: boilerplate, 248 PackageDocumentation: []byte( 249 `// This package contains the scheme of the automatically generated clientset. 250`), 251 // GeneratorFunc returns a list of generators. Each generator generates a 252 // single file. 253 GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) { 254 generators = []generator.Generator{ 255 // Always generate a "doc.go" file. 256 generator.DefaultGen{OptionalName: "doc"}, 257 258 &scheme.GenScheme{ 259 DefaultGen: generator.DefaultGen{ 260 OptionalName: "register", 261 }, 262 InputPackages: customArgs.GroupVersionPackages(), 263 OutputPackage: schemePackage, 264 OutputPath: filepath.Join(srcTreePath, schemePackage), 265 Groups: customArgs.Groups, 266 GroupGoNames: groupGoNames, 267 ImportTracker: generator.NewImportTracker(), 268 CreateRegistry: internalClient, 269 }, 270 } 271 return generators 272 }, 273 } 274} 275 276// applyGroupOverrides applies group name overrides to each package, if applicable. If there is a 277// comment of the form "// +groupName=somegroup" or "// +groupName=somegroup.foo.bar.io", use the 278// first field (somegroup) as the name of the group in Go code, e.g. as the func name in a clientset. 279// 280// If the first field of the groupName is not unique within the clientset, use "// +groupName=unique 281func applyGroupOverrides(universe types.Universe, customArgs *clientgenargs.CustomArgs) { 282 // Create a map from "old GV" to "new GV" so we know what changes we need to make. 283 changes := make(map[clientgentypes.GroupVersion]clientgentypes.GroupVersion) 284 for gv, inputDir := range customArgs.GroupVersionPackages() { 285 p := universe.Package(genutil.Vendorless(inputDir)) 286 if override := types.ExtractCommentTags("+", p.Comments)["groupName"]; override != nil { 287 newGV := clientgentypes.GroupVersion{ 288 Group: clientgentypes.Group(override[0]), 289 Version: gv.Version, 290 } 291 changes[gv] = newGV 292 } 293 } 294 295 // Modify customArgs.Groups based on the groupName overrides. 296 newGroups := make([]clientgentypes.GroupVersions, 0, len(customArgs.Groups)) 297 for _, gvs := range customArgs.Groups { 298 gv := clientgentypes.GroupVersion{ 299 Group: gvs.Group, 300 Version: gvs.Versions[0].Version, // we only need a version, and the first will do 301 } 302 if newGV, ok := changes[gv]; ok { 303 // There's an override, so use it. 304 newGVS := clientgentypes.GroupVersions{ 305 PackageName: gvs.PackageName, 306 Group: newGV.Group, 307 Versions: gvs.Versions, 308 } 309 newGroups = append(newGroups, newGVS) 310 } else { 311 // No override. 312 newGroups = append(newGroups, gvs) 313 } 314 } 315 customArgs.Groups = newGroups 316} 317 318// Packages makes the client package definition. 319func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages { 320 boilerplate, err := arguments.LoadGoBoilerplate() 321 if err != nil { 322 klog.Fatalf("Failed loading boilerplate: %v", err) 323 } 324 325 customArgs, ok := arguments.CustomArgs.(*clientgenargs.CustomArgs) 326 if !ok { 327 klog.Fatalf("cannot convert arguments.CustomArgs to clientgenargs.CustomArgs") 328 } 329 includedTypesOverrides := customArgs.IncludedTypesOverrides 330 331 applyGroupOverrides(context.Universe, customArgs) 332 333 gvToTypes := map[clientgentypes.GroupVersion][]*types.Type{} 334 groupGoNames := make(map[clientgentypes.GroupVersion]string) 335 for gv, inputDir := range customArgs.GroupVersionPackages() { 336 p := context.Universe.Package(path.Vendorless(inputDir)) 337 338 // If there's a comment of the form "// +groupGoName=SomeUniqueShortName", use that as 339 // the Go group identifier in CamelCase. It defaults 340 groupGoNames[gv] = namer.IC(strings.Split(gv.Group.NonEmpty(), ".")[0]) 341 if override := types.ExtractCommentTags("+", p.Comments)["groupGoName"]; override != nil { 342 groupGoNames[gv] = namer.IC(override[0]) 343 } 344 345 // Package are indexed with the vendor prefix stripped 346 for n, t := range p.Types { 347 // filter out types which are not included in user specified overrides. 348 typesOverride, ok := includedTypesOverrides[gv] 349 if ok { 350 found := false 351 for _, typeStr := range typesOverride { 352 if typeStr == n { 353 found = true 354 break 355 } 356 } 357 if !found { 358 continue 359 } 360 } else { 361 // User has not specified any override for this group version. 362 // filter out types which don't have genclient. 363 if tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)); !tags.GenerateClient { 364 continue 365 } 366 } 367 if _, found := gvToTypes[gv]; !found { 368 gvToTypes[gv] = []*types.Type{} 369 } 370 gvToTypes[gv] = append(gvToTypes[gv], t) 371 } 372 } 373 374 var packageList []generator.Package 375 clientsetPackage := filepath.Join(arguments.OutputPackagePath, customArgs.ClientsetName) 376 377 packageList = append(packageList, packageForClientset(customArgs, clientsetPackage, groupGoNames, boilerplate)) 378 packageList = append(packageList, packageForScheme(customArgs, clientsetPackage, arguments.OutputBase, groupGoNames, boilerplate)) 379 if customArgs.FakeClient { 380 packageList = append(packageList, fake.PackageForClientset(customArgs, clientsetPackage, groupGoNames, boilerplate)) 381 } 382 383 // If --clientset-only=true, we don't regenerate the individual typed clients. 384 if customArgs.ClientsetOnly { 385 return generator.Packages(packageList) 386 } 387 388 orderer := namer.Orderer{Namer: namer.NewPrivateNamer(0)} 389 gvPackages := customArgs.GroupVersionPackages() 390 for _, group := range customArgs.Groups { 391 for _, version := range group.Versions { 392 gv := clientgentypes.GroupVersion{Group: group.Group, Version: version.Version} 393 types := gvToTypes[gv] 394 inputPath := gvPackages[gv] 395 packageList = append(packageList, packageForGroup(gv, orderer.OrderTypes(types), clientsetPackage, group.PackageName, groupGoNames[gv], customArgs.ClientsetAPIPath, arguments.OutputBase, inputPath, customArgs.ApplyConfigurationPackage, boilerplate)) 396 if customArgs.FakeClient { 397 packageList = append(packageList, fake.PackageForGroup(gv, orderer.OrderTypes(types), clientsetPackage, group.PackageName, groupGoNames[gv], inputPath, customArgs.ApplyConfigurationPackage, boilerplate)) 398 } 399 } 400 } 401 402 return generator.Packages(packageList) 403} 404