Update go mod and vendor in client dir to 0.28.0

This commit is contained in:
Raunak Pradip Shah
2023-11-08 10:57:27 +05:30
parent dad8b28c35
commit 1bf2305d28
628 changed files with 61667 additions and 17445 deletions

View File

@@ -1,13 +1,15 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- lavalamp
- deads2k
- wojtek-t
- sttts
reviewers:
- lavalamp
- deads2k
- wojtek-t
- sttts
labels:
- sig/api-machinery
- area/code-generation
emeritus_approvers:
- lavalamp

View File

@@ -0,0 +1,81 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package args
import (
"fmt"
"path"
"github.com/spf13/pflag"
"k8s.io/gengo/args"
"k8s.io/gengo/types"
codegenutil "k8s.io/code-generator/pkg/util"
)
// CustomArgs is a wrapper for arguments to applyconfiguration-gen.
type CustomArgs struct {
// ExternalApplyConfigurations provides the locations of externally generated
// apply configuration types for types referenced by the go structs provided as input.
// Locations are provided as a comma separated list of <package>.<typeName>:<applyconfiguration-package>
// entries.
//
// E.g. if a type references appsv1.Deployment, the location of its apply configuration should
// be provided:
// k8s.io/api/apps/v1.Deployment:k8s.io/client-go/applyconfigurations/apps/v1
//
// meta/v1 types (TypeMeta and ObjectMeta) are always included and do not need to be passed in.
ExternalApplyConfigurations map[types.Name]string
OpenAPISchemaFilePath string
}
// NewDefaults returns default arguments for the generator.
func NewDefaults() (*args.GeneratorArgs, *CustomArgs) {
genericArgs := args.Default().WithoutDefaultFlagParsing()
customArgs := &CustomArgs{
ExternalApplyConfigurations: map[types.Name]string{
// Always include TypeMeta and ObjectMeta. They are sufficient for the vast majority of use cases.
{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "TypeMeta"}: "k8s.io/client-go/applyconfigurations/meta/v1",
{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ObjectMeta"}: "k8s.io/client-go/applyconfigurations/meta/v1",
{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "OwnerReference"}: "k8s.io/client-go/applyconfigurations/meta/v1",
},
}
genericArgs.CustomArgs = customArgs
if pkg := codegenutil.CurrentPackage(); len(pkg) != 0 {
genericArgs.OutputPackagePath = path.Join(pkg, "pkg/client/applyconfigurations")
}
return genericArgs, customArgs
}
func (ca *CustomArgs) AddFlags(fs *pflag.FlagSet, inputBase string) {
pflag.Var(NewExternalApplyConfigurationValue(&ca.ExternalApplyConfigurations, nil), "external-applyconfigurations",
"list of comma separated external apply configurations locations in <type-package>.<type-name>:<applyconfiguration-package> form."+
"For example: k8s.io/api/apps/v1.Deployment:k8s.io/client-go/applyconfigurations/apps/v1")
pflag.StringVar(&ca.OpenAPISchemaFilePath, "openapi-schema", "",
"path to the openapi schema containing all the types that apply configurations will be generated for")
}
// Validate checks the given arguments.
func Validate(genericArgs *args.GeneratorArgs) error {
if len(genericArgs.OutputPackagePath) == 0 {
return fmt.Errorf("output package cannot be empty")
}
return nil
}

View File

@@ -0,0 +1,122 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package args
import (
"bytes"
"encoding/csv"
"flag"
"fmt"
"strings"
"k8s.io/gengo/types"
)
type externalApplyConfigurationValue struct {
externals *map[types.Name]string
}
func NewExternalApplyConfigurationValue(externals *map[types.Name]string, def []string) *externalApplyConfigurationValue {
val := new(externalApplyConfigurationValue)
val.externals = externals
if def != nil {
if err := val.set(def); err != nil {
panic(err)
}
}
return val
}
var _ flag.Value = &externalApplyConfigurationValue{}
func (s *externalApplyConfigurationValue) set(vs []string) error {
for _, input := range vs {
typ, pkg, err := parseExternalMapping(input)
if err != nil {
return err
}
if _, ok := (*s.externals)[typ]; ok {
return fmt.Errorf("duplicate type found in --external-applyconfigurations: %v", typ)
}
(*s.externals)[typ] = pkg
}
return nil
}
func (s *externalApplyConfigurationValue) Set(val string) error {
vs, err := readAsCSV(val)
if err != nil {
return err
}
if err := s.set(vs); err != nil {
return err
}
return nil
}
func (s *externalApplyConfigurationValue) Type() string {
return "string"
}
func (s *externalApplyConfigurationValue) String() string {
var strs []string
for k, v := range *s.externals {
strs = append(strs, fmt.Sprintf("%s.%s:%s", k.Package, k.Name, v))
}
str, _ := writeAsCSV(strs)
return "[" + str + "]"
}
func readAsCSV(val string) ([]string, error) {
if val == "" {
return []string{}, nil
}
stringReader := strings.NewReader(val)
csvReader := csv.NewReader(stringReader)
return csvReader.Read()
}
func writeAsCSV(vals []string) (string, error) {
b := &bytes.Buffer{}
w := csv.NewWriter(b)
err := w.Write(vals)
if err != nil {
return "", err
}
w.Flush()
return strings.TrimSuffix(b.String(), "\n"), nil
}
func parseExternalMapping(mapping string) (typ types.Name, pkg string, err error) {
parts := strings.Split(mapping, ":")
if len(parts) != 2 {
return types.Name{}, "", fmt.Errorf("expected string of the form <package>.<typeName>:<applyconfiguration-package> but got %s", mapping)
}
packageTypeStr := parts[0]
pkg = parts[1]
// need to split on the *last* dot, since k8s.io (and other valid packages) have a dot in it
lastDot := strings.LastIndex(packageTypeStr, ".")
if lastDot == -1 || lastDot == len(packageTypeStr)-1 {
return types.Name{}, "", fmt.Errorf("expected package and type of the form <package>.<typeName> but got %s", packageTypeStr)
}
structPkg := packageTypeStr[:lastDot]
structType := packageTypeStr[lastDot+1:]
return types.Name{Package: structPkg, Name: structType}, pkg, nil
}

View File

@@ -0,0 +1,423 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generators
import (
"io"
"strings"
"k8s.io/gengo/generator"
"k8s.io/gengo/namer"
"k8s.io/gengo/types"
"k8s.io/klog/v2"
"k8s.io/code-generator/cmd/client-gen/generators/util"
clientgentypes "k8s.io/code-generator/cmd/client-gen/types"
)
// applyConfigurationGenerator produces apply configurations for a given GroupVersion and type.
type applyConfigurationGenerator struct {
generator.DefaultGen
outputPackage string
localPackage types.Name
groupVersion clientgentypes.GroupVersion
applyConfig applyConfig
imports namer.ImportTracker
refGraph refGraph
openAPIType *string // if absent, extraction function cannot be generated
}
var _ generator.Generator = &applyConfigurationGenerator{}
func (g *applyConfigurationGenerator) Filter(_ *generator.Context, t *types.Type) bool {
return t == g.applyConfig.Type
}
func (g *applyConfigurationGenerator) Namers(*generator.Context) namer.NameSystems {
return namer.NameSystems{
"raw": namer.NewRawNamer(g.localPackage.Package, g.imports),
"singularKind": namer.NewPublicNamer(0),
}
}
func (g *applyConfigurationGenerator) Imports(*generator.Context) (imports []string) {
return g.imports.ImportLines()
}
// TypeParams provides a struct that an apply configuration
// is generated for as well as the apply configuration details
// and types referenced by the struct.
type TypeParams struct {
Struct *types.Type
ApplyConfig applyConfig
Tags util.Tags
APIVersion string
ExtractInto *types.Type
ParserFunc *types.Type
OpenAPIType *string
}
type memberParams struct {
TypeParams
Member types.Member
MemberType *types.Type
JSONTags JSONTags
ArgType *types.Type // only set for maps and slices
EmbeddedIn *memberParams // parent embedded member, if any
}
func (g *applyConfigurationGenerator) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
sw := generator.NewSnippetWriter(w, c, "$", "$")
klog.V(5).Infof("processing type %v", t)
typeParams := TypeParams{
Struct: t,
ApplyConfig: g.applyConfig,
Tags: genclientTags(t),
APIVersion: g.groupVersion.ToAPIVersion(),
ExtractInto: extractInto,
ParserFunc: types.Ref(g.outputPackage+"/internal", "Parser"),
OpenAPIType: g.openAPIType,
}
g.generateStruct(sw, typeParams)
if typeParams.Tags.GenerateClient {
if typeParams.Tags.NonNamespaced {
sw.Do(clientgenTypeConstructorNonNamespaced, typeParams)
} else {
sw.Do(clientgenTypeConstructorNamespaced, typeParams)
}
if typeParams.OpenAPIType != nil {
g.generateClientgenExtract(sw, typeParams, !typeParams.Tags.NoStatus)
}
} else {
if hasTypeMetaField(t) {
sw.Do(constructorWithTypeMeta, typeParams)
} else {
sw.Do(constructor, typeParams)
}
}
g.generateWithFuncs(t, typeParams, sw, nil)
return sw.Error()
}
func hasTypeMetaField(t *types.Type) bool {
for _, member := range t.Members {
if typeMeta.Name == member.Type.Name && member.Embedded {
return true
}
}
return false
}
func blocklisted(t *types.Type, member types.Member) bool {
if objectMeta.Name == t.Name && member.Name == "ManagedFields" {
return true
}
if objectMeta.Name == t.Name && member.Name == "SelfLink" {
return true
}
// Hide any fields which are en route to deletion.
if strings.HasPrefix(member.Name, "ZZZ_") {
return true
}
return false
}
func (g *applyConfigurationGenerator) generateWithFuncs(t *types.Type, typeParams TypeParams, sw *generator.SnippetWriter, embed *memberParams) {
for _, member := range t.Members {
if blocklisted(t, member) {
continue
}
memberType := g.refGraph.applyConfigForType(member.Type)
if g.refGraph.isApplyConfig(member.Type) {
memberType = &types.Type{Kind: types.Pointer, Elem: memberType}
}
if jsonTags, ok := lookupJSONTags(member); ok {
memberParams := memberParams{
TypeParams: typeParams,
Member: member,
MemberType: memberType,
JSONTags: jsonTags,
EmbeddedIn: embed,
}
if memberParams.Member.Embedded {
g.generateWithFuncs(member.Type, typeParams, sw, &memberParams)
if !jsonTags.inline {
// non-inlined embeds are nillable and need a "ensure exists" utility function
sw.Do(ensureEmbedExists, memberParams)
}
continue
}
// For slices where the items are generated apply configuration types, accept varargs of
// pointers of the type as "with" function arguments so the "with" function can be used like so:
// WithFoos(Foo().WithName("x"), Foo().WithName("y"))
if t := deref(member.Type); t.Kind == types.Slice && g.refGraph.isApplyConfig(t.Elem) {
memberParams.ArgType = &types.Type{Kind: types.Pointer, Elem: memberType.Elem}
g.generateMemberWithForSlice(sw, member, memberParams)
continue
}
// Note: There are no maps where the values are generated apply configurations (because
// associative lists are used instead). So if a type like this is ever introduced, the
// default "with" function generator will produce a working (but not entirely convenient "with" function)
// that would be used like so:
// WithMap(map[string]FooApplyConfiguration{*Foo().WithName("x")})
switch memberParams.Member.Type.Kind {
case types.Slice:
memberParams.ArgType = memberType.Elem
g.generateMemberWithForSlice(sw, member, memberParams)
case types.Map:
g.generateMemberWithForMap(sw, memberParams)
default:
g.generateMemberWith(sw, memberParams)
}
}
}
}
func (g *applyConfigurationGenerator) generateStruct(sw *generator.SnippetWriter, typeParams TypeParams) {
sw.Do("// $.ApplyConfig.ApplyConfiguration|public$ represents an declarative configuration of the $.ApplyConfig.Type|public$ type for use\n", typeParams)
sw.Do("// with apply.\n", typeParams)
sw.Do("type $.ApplyConfig.ApplyConfiguration|public$ struct {\n", typeParams)
for _, structMember := range typeParams.Struct.Members {
if blocklisted(typeParams.Struct, structMember) {
continue
}
if structMemberTags, ok := lookupJSONTags(structMember); ok {
if !structMemberTags.inline {
structMemberTags.omitempty = true
}
params := memberParams{
TypeParams: typeParams,
Member: structMember,
MemberType: g.refGraph.applyConfigForType(structMember.Type),
JSONTags: structMemberTags,
}
if structMember.Embedded {
if structMemberTags.inline {
sw.Do("$.MemberType|raw$ `json:\"$.JSONTags$\"`\n", params)
} else {
sw.Do("*$.MemberType|raw$ `json:\"$.JSONTags$\"`\n", params)
}
} else if isNillable(structMember.Type) {
sw.Do("$.Member.Name$ $.MemberType|raw$ `json:\"$.JSONTags$\"`\n", params)
} else {
sw.Do("$.Member.Name$ *$.MemberType|raw$ `json:\"$.JSONTags$\"`\n", params)
}
}
}
sw.Do("}\n", typeParams)
}
func deref(t *types.Type) *types.Type {
for t.Kind == types.Pointer {
t = t.Elem
}
return t
}
func isNillable(t *types.Type) bool {
return t.Kind == types.Slice || t.Kind == types.Map
}
func (g *applyConfigurationGenerator) generateMemberWith(sw *generator.SnippetWriter, memberParams memberParams) {
sw.Do("// With$.Member.Name$ sets the $.Member.Name$ field in the declarative configuration to the given value\n", memberParams)
sw.Do("// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n", memberParams)
sw.Do("// If called multiple times, the $.Member.Name$ field is set to the value of the last call.\n", memberParams)
sw.Do("func (b *$.ApplyConfig.ApplyConfiguration|public$) With$.Member.Name$(value $.MemberType|raw$) *$.ApplyConfig.ApplyConfiguration|public$ {\n", memberParams)
g.ensureEnbedExistsIfApplicable(sw, memberParams)
if g.refGraph.isApplyConfig(memberParams.Member.Type) || isNillable(memberParams.Member.Type) {
sw.Do("b.$.Member.Name$ = value\n", memberParams)
} else {
sw.Do("b.$.Member.Name$ = &value\n", memberParams)
}
sw.Do(" return b\n", memberParams)
sw.Do("}\n", memberParams)
}
func (g *applyConfigurationGenerator) generateMemberWithForSlice(sw *generator.SnippetWriter, member types.Member, memberParams memberParams) {
memberIsPointerToSlice := member.Type.Kind == types.Pointer
if memberIsPointerToSlice {
sw.Do(ensureNonEmbedSliceExists, memberParams)
}
sw.Do("// With$.Member.Name$ adds the given value to the $.Member.Name$ field in the declarative configuration\n", memberParams)
sw.Do("// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n", memberParams)
sw.Do("// If called multiple times, values provided by each call will be appended to the $.Member.Name$ field.\n", memberParams)
sw.Do("func (b *$.ApplyConfig.ApplyConfiguration|public$) With$.Member.Name$(values ...$.ArgType|raw$) *$.ApplyConfig.ApplyConfiguration|public$ {\n", memberParams)
g.ensureEnbedExistsIfApplicable(sw, memberParams)
if memberIsPointerToSlice {
sw.Do("b.ensure$.MemberType.Elem|public$Exists()\n", memberParams)
}
sw.Do(" for i := range values {\n", memberParams)
if memberParams.ArgType.Kind == types.Pointer {
sw.Do("if values[i] == nil {\n", memberParams)
sw.Do(" panic(\"nil value passed to With$.Member.Name$\")\n", memberParams)
sw.Do("}\n", memberParams)
if memberIsPointerToSlice {
sw.Do("*b.$.Member.Name$ = append(*b.$.Member.Name$, *values[i])\n", memberParams)
} else {
sw.Do("b.$.Member.Name$ = append(b.$.Member.Name$, *values[i])\n", memberParams)
}
} else {
if memberIsPointerToSlice {
sw.Do("*b.$.Member.Name$ = append(*b.$.Member.Name$, values[i])\n", memberParams)
} else {
sw.Do("b.$.Member.Name$ = append(b.$.Member.Name$, values[i])\n", memberParams)
}
}
sw.Do(" }\n", memberParams)
sw.Do(" return b\n", memberParams)
sw.Do("}\n", memberParams)
}
func (g *applyConfigurationGenerator) generateMemberWithForMap(sw *generator.SnippetWriter, memberParams memberParams) {
sw.Do("// With$.Member.Name$ puts the entries into the $.Member.Name$ field in the declarative configuration\n", memberParams)
sw.Do("// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n", memberParams)
sw.Do("// If called multiple times, the entries provided by each call will be put on the $.Member.Name$ field,\n", memberParams)
sw.Do("// overwriting an existing map entries in $.Member.Name$ field with the same key.\n", memberParams)
sw.Do("func (b *$.ApplyConfig.ApplyConfiguration|public$) With$.Member.Name$(entries $.MemberType|raw$) *$.ApplyConfig.ApplyConfiguration|public$ {\n", memberParams)
g.ensureEnbedExistsIfApplicable(sw, memberParams)
sw.Do(" if b.$.Member.Name$ == nil && len(entries) > 0 {\n", memberParams)
sw.Do(" b.$.Member.Name$ = make($.MemberType|raw$, len(entries))\n", memberParams)
sw.Do(" }\n", memberParams)
sw.Do(" for k, v := range entries {\n", memberParams)
sw.Do(" b.$.Member.Name$[k] = v\n", memberParams)
sw.Do(" }\n", memberParams)
sw.Do(" return b\n", memberParams)
sw.Do("}\n", memberParams)
}
func (g *applyConfigurationGenerator) ensureEnbedExistsIfApplicable(sw *generator.SnippetWriter, memberParams memberParams) {
// Embedded types that are not inlined must be nillable so they are not included in the apply configuration
// when all their fields are omitted.
if memberParams.EmbeddedIn != nil && !memberParams.EmbeddedIn.JSONTags.inline {
sw.Do("b.ensure$.MemberType.Elem|public$Exists()\n", memberParams.EmbeddedIn)
}
}
var ensureEmbedExists = `
func (b *$.ApplyConfig.ApplyConfiguration|public$) ensure$.MemberType.Elem|public$Exists() {
if b.$.MemberType.Elem|public$ == nil {
b.$.MemberType.Elem|public$ = &$.MemberType.Elem|raw${}
}
}
`
var ensureNonEmbedSliceExists = `
func (b *$.ApplyConfig.ApplyConfiguration|public$) ensure$.MemberType.Elem|public$Exists() {
if b.$.Member.Name$ == nil {
b.$.Member.Name$ = &[]$.MemberType.Elem|raw${}
}
}
`
var clientgenTypeConstructorNamespaced = `
// $.ApplyConfig.Type|public$ constructs an declarative configuration of the $.ApplyConfig.Type|public$ type for use with
// apply.
func $.ApplyConfig.Type|public$(name, namespace string) *$.ApplyConfig.ApplyConfiguration|public$ {
b := &$.ApplyConfig.ApplyConfiguration|public${}
b.WithName(name)
b.WithNamespace(namespace)
b.WithKind("$.ApplyConfig.Type|singularKind$")
b.WithAPIVersion("$.APIVersion$")
return b
}
`
var clientgenTypeConstructorNonNamespaced = `
// $.ApplyConfig.Type|public$ constructs an declarative configuration of the $.ApplyConfig.Type|public$ type for use with
// apply.
func $.ApplyConfig.Type|public$(name string) *$.ApplyConfig.ApplyConfiguration|public$ {
b := &$.ApplyConfig.ApplyConfiguration|public${}
b.WithName(name)
b.WithKind("$.ApplyConfig.Type|singularKind$")
b.WithAPIVersion("$.APIVersion$")
return b
}
`
var constructorWithTypeMeta = `
// $.ApplyConfig.ApplyConfiguration|public$ constructs an declarative configuration of the $.ApplyConfig.Type|public$ type for use with
// apply.
func $.ApplyConfig.Type|public$() *$.ApplyConfig.ApplyConfiguration|public$ {
b := &$.ApplyConfig.ApplyConfiguration|public${}
b.WithKind("$.ApplyConfig.Type|singularKind$")
b.WithAPIVersion("$.APIVersion$")
return b
}
`
var constructor = `
// $.ApplyConfig.ApplyConfiguration|public$ constructs an declarative configuration of the $.ApplyConfig.Type|public$ type for use with
// apply.
func $.ApplyConfig.Type|public$() *$.ApplyConfig.ApplyConfiguration|public$ {
return &$.ApplyConfig.ApplyConfiguration|public${}
}
`
func (g *applyConfigurationGenerator) generateClientgenExtract(sw *generator.SnippetWriter, typeParams TypeParams, includeStatus bool) {
sw.Do(`
// Extract$.ApplyConfig.Type|public$ extracts the applied configuration owned by fieldManager from
// $.Struct|private$. If no managedFields are found in $.Struct|private$ for fieldManager, a
// $.ApplyConfig.ApplyConfiguration|public$ is returned with only the Name, Namespace (if applicable),
// APIVersion and Kind populated. It is possible that no managed fields were found for because other
// field managers have taken ownership of all the fields previously owned by fieldManager, or because
// the fieldManager never owned fields any fields.
// $.Struct|private$ must be a unmodified $.Struct|public$ API object that was retrieved from the Kubernetes API.
// Extract$.ApplyConfig.Type|public$ provides a way to perform a extract/modify-in-place/apply workflow.
// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously
// applied if another fieldManager has updated or force applied any of the previously applied fields.
// Experimental!
func Extract$.ApplyConfig.Type|public$($.Struct|private$ *$.Struct|raw$, fieldManager string) (*$.ApplyConfig.ApplyConfiguration|public$, error) {
return extract$.ApplyConfig.Type|public$($.Struct|private$, fieldManager, "")
}`, typeParams)
if includeStatus {
sw.Do(`
// Extract$.ApplyConfig.Type|public$Status is the same as Extract$.ApplyConfig.Type|public$ except
// that it extracts the status subresource applied configuration.
// Experimental!
func Extract$.ApplyConfig.Type|public$Status($.Struct|private$ *$.Struct|raw$, fieldManager string) (*$.ApplyConfig.ApplyConfiguration|public$, error) {
return extract$.ApplyConfig.Type|public$($.Struct|private$, fieldManager, "status")
}
`, typeParams)
}
sw.Do(`
func extract$.ApplyConfig.Type|public$($.Struct|private$ *$.Struct|raw$, fieldManager string, subresource string) (*$.ApplyConfig.ApplyConfiguration|public$, error) {
b := &$.ApplyConfig.ApplyConfiguration|public${}
err := $.ExtractInto|raw$($.Struct|private$, $.ParserFunc|raw$().Type("$.OpenAPIType$"), fieldManager, b, subresource)
if err != nil {
return nil, err
}
b.WithName($.Struct|private$.Name)
`, typeParams)
if !typeParams.Tags.NonNamespaced {
sw.Do("b.WithNamespace($.Struct|private$.Namespace)\n", typeParams)
}
sw.Do(`
b.WithKind("$.ApplyConfig.Type|singularKind$")
b.WithAPIVersion("$.APIVersion$")
return b, nil
}
`, typeParams)
}

View File

@@ -0,0 +1,99 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generators
import (
"io"
"gopkg.in/yaml.v2"
"k8s.io/kube-openapi/pkg/schemaconv"
"k8s.io/gengo/generator"
"k8s.io/gengo/namer"
"k8s.io/gengo/types"
)
// utilGenerator generates the ForKind() utility function.
type internalGenerator struct {
generator.DefaultGen
outputPackage string
imports namer.ImportTracker
typeModels *typeModels
filtered bool
}
var _ generator.Generator = &internalGenerator{}
func (g *internalGenerator) Filter(*generator.Context, *types.Type) bool {
// generate file exactly once
if !g.filtered {
g.filtered = true
return true
}
return false
}
func (g *internalGenerator) Namers(*generator.Context) namer.NameSystems {
return namer.NameSystems{
"raw": namer.NewRawNamer(g.outputPackage, g.imports),
"singularKind": namer.NewPublicNamer(0),
}
}
func (g *internalGenerator) Imports(*generator.Context) (imports []string) {
return g.imports.ImportLines()
}
func (g *internalGenerator) GenerateType(c *generator.Context, _ *types.Type, w io.Writer) error {
sw := generator.NewSnippetWriter(w, c, "{{", "}}")
schema, err := schemaconv.ToSchema(g.typeModels.models)
if err != nil {
return err
}
schemaYAML, err := yaml.Marshal(schema)
if err != nil {
return err
}
sw.Do(schemaBlock, map[string]interface{}{
"schemaYAML": string(schemaYAML),
"smdParser": smdParser,
"smdNewParser": smdNewParser,
"yamlObject": yamlObject,
"yamlUnmarshal": yamlUnmarshal,
})
return sw.Error()
}
var schemaBlock = `
func Parser() *{{.smdParser|raw}} {
parserOnce.Do(func() {
var err error
parser, err = {{.smdNewParser|raw}}(schemaYAML)
if err != nil {
panic(fmt.Sprintf("Failed to parse schema: %v", err))
}
})
return parser
}
var parserOnce sync.Once
var parser *{{.smdParser|raw}}
var schemaYAML = {{.yamlObject|raw}}(` + "`{{.schemaYAML}}`" + `)
`

View File

@@ -0,0 +1,99 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generators
import (
"reflect"
"strings"
"k8s.io/gengo/types"
)
// TODO: This implements the same functionality as https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/runtime/converter.go#L236
// but is based on the highly efficient approach from https://golang.org/src/encoding/json/encode.go
// JSONTags represents a go json field tag.
type JSONTags struct {
name string
omit bool
inline bool
omitempty bool
}
func (t JSONTags) String() string {
var tag string
if !t.inline {
tag += t.name
}
if t.omitempty {
tag += ",omitempty"
}
if t.inline {
tag += ",inline"
}
return tag
}
func lookupJSONTags(m types.Member) (JSONTags, bool) {
tag := reflect.StructTag(m.Tags).Get("json")
if tag == "" || tag == "-" {
return JSONTags{}, false
}
name, opts := parseTag(tag)
if name == "" {
name = m.Name
}
return JSONTags{
name: name,
omit: false,
inline: opts.Contains("inline"),
omitempty: opts.Contains("omitempty"),
}, true
}
type tagOptions string
// parseTag splits a struct field's json tag into its name and
// comma-separated options.
func parseTag(tag string) (string, tagOptions) {
if idx := strings.Index(tag, ","); idx != -1 {
return tag[:idx], tagOptions(tag[idx+1:])
}
return tag, ""
}
// Contains reports whether a comma-separated listAlias of options
// contains a particular substr flag. substr must be surrounded by a
// string boundary or commas.
func (o tagOptions) Contains(optionName string) bool {
if len(o) == 0 {
return false
}
s := string(o)
for s != "" {
var next string
i := strings.Index(s, ",")
if i >= 0 {
s, next = s[:i], s[i+1:]
}
if s == optionName {
return true
}
s = next
}
return false
}

View File

@@ -0,0 +1,198 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generators
import (
"encoding/json"
"fmt"
"os"
"strings"
openapiv2 "github.com/google/gnostic-models/openapiv2"
"k8s.io/gengo/types"
utilproto "k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kube-openapi/pkg/validation/spec"
)
type typeModels struct {
models utilproto.Models
gvkToOpenAPIType map[gvk]string
}
type gvk struct {
group, version, kind string
}
func newTypeModels(openAPISchemaFilePath string, pkgTypes map[string]*types.Package) (*typeModels, error) {
if len(openAPISchemaFilePath) == 0 {
return emptyModels, nil // No Extract<type>() functions will be generated.
}
rawOpenAPISchema, err := os.ReadFile(openAPISchemaFilePath)
if err != nil {
return nil, fmt.Errorf("failed to read openapi-schema file: %w", err)
}
// Read in the provided openAPI schema.
openAPISchema := &spec.Swagger{}
err = json.Unmarshal(rawOpenAPISchema, openAPISchema)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal typeModels JSON: %w", err)
}
// Build a mapping from openAPI type name to GVK.
// Find the root types needed by by client-go for apply.
gvkToOpenAPIType := map[gvk]string{}
rootDefs := map[string]spec.Schema{}
for _, p := range pkgTypes {
gv := groupVersion(p)
for _, t := range p.Types {
tags := genclientTags(t)
hasApply := tags.HasVerb("apply") || tags.HasVerb("applyStatus")
if tags.GenerateClient && hasApply {
openAPIType := friendlyName(typeName(t))
gvk := gvk{
group: gv.Group.String(),
version: gv.Version.String(),
kind: t.Name.Name,
}
rootDefs[openAPIType] = openAPISchema.Definitions[openAPIType]
gvkToOpenAPIType[gvk] = openAPIType
}
}
}
// Trim the schema down to just the types needed by client-go for apply.
requiredDefs := make(map[string]spec.Schema)
for name, def := range rootDefs {
requiredDefs[name] = def
findReferenced(&def, openAPISchema.Definitions, requiredDefs)
}
openAPISchema.Definitions = requiredDefs
// Convert the openAPI schema to the models format and validate it.
models, err := toValidatedModels(openAPISchema)
if err != nil {
return nil, err
}
return &typeModels{models: models, gvkToOpenAPIType: gvkToOpenAPIType}, nil
}
var emptyModels = &typeModels{
models: &utilproto.Definitions{},
gvkToOpenAPIType: map[gvk]string{},
}
func toValidatedModels(openAPISchema *spec.Swagger) (utilproto.Models, error) {
// openapi_v2.ParseDocument only accepts a []byte of the JSON or YAML file to be parsed.
// so we do an inefficient marshal back to json and then read it back in as yaml
// but get the benefit of running the models through utilproto.NewOpenAPIData to
// validate all the references between types
rawMinimalOpenAPISchema, err := json.Marshal(openAPISchema)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal openAPI as JSON: %w", err)
}
document, err := openapiv2.ParseDocument(rawMinimalOpenAPISchema)
if err != nil {
return nil, fmt.Errorf("failed to parse OpenAPI document for file: %w", err)
}
// Construct the models and validate all references are valid.
models, err := utilproto.NewOpenAPIData(document)
if err != nil {
return nil, fmt.Errorf("failed to create OpenAPI models for file: %w", err)
}
return models, nil
}
// findReferenced recursively finds all schemas referenced from the given def.
// toValidatedModels makes sure no references get missed.
func findReferenced(def *spec.Schema, allSchemas, referencedOut map[string]spec.Schema) {
// follow $ref, if any
refPtr := def.Ref.GetPointer()
if refPtr != nil && !refPtr.IsEmpty() {
name := refPtr.String()
if !strings.HasPrefix(name, "/definitions/") {
return
}
name = strings.TrimPrefix(name, "/definitions/")
schema, ok := allSchemas[name]
if !ok {
panic(fmt.Sprintf("allSchemas schema is missing referenced type: %s", name))
}
if _, ok := referencedOut[name]; !ok {
referencedOut[name] = schema
findReferenced(&schema, allSchemas, referencedOut)
}
}
// follow any nested schemas
if def.Items != nil {
if def.Items.Schema != nil {
findReferenced(def.Items.Schema, allSchemas, referencedOut)
}
for _, item := range def.Items.Schemas {
findReferenced(&item, allSchemas, referencedOut)
}
}
if def.AllOf != nil {
for _, s := range def.AllOf {
findReferenced(&s, allSchemas, referencedOut)
}
}
if def.AnyOf != nil {
for _, s := range def.AnyOf {
findReferenced(&s, allSchemas, referencedOut)
}
}
if def.OneOf != nil {
for _, s := range def.OneOf {
findReferenced(&s, allSchemas, referencedOut)
}
}
if def.Not != nil {
findReferenced(def.Not, allSchemas, referencedOut)
}
if def.Properties != nil {
for _, prop := range def.Properties {
findReferenced(&prop, allSchemas, referencedOut)
}
}
if def.AdditionalProperties != nil && def.AdditionalProperties.Schema != nil {
findReferenced(def.AdditionalProperties.Schema, allSchemas, referencedOut)
}
if def.PatternProperties != nil {
for _, s := range def.PatternProperties {
findReferenced(&s, allSchemas, referencedOut)
}
}
if def.Dependencies != nil {
for _, d := range def.Dependencies {
if d.Schema != nil {
findReferenced(d.Schema, allSchemas, referencedOut)
}
}
}
if def.AdditionalItems != nil && def.AdditionalItems.Schema != nil {
findReferenced(def.AdditionalItems.Schema, allSchemas, referencedOut)
}
if def.Definitions != nil {
for _, s := range def.Definitions {
findReferenced(&s, allSchemas, referencedOut)
}
}
}

View File

@@ -0,0 +1,297 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generators
import (
"fmt"
"path"
"path/filepath"
"sort"
"strings"
"k8s.io/gengo/args"
"k8s.io/gengo/generator"
"k8s.io/gengo/namer"
"k8s.io/gengo/types"
"k8s.io/klog/v2"
applygenargs "k8s.io/code-generator/cmd/applyconfiguration-gen/args"
"k8s.io/code-generator/cmd/client-gen/generators/util"
clientgentypes "k8s.io/code-generator/cmd/client-gen/types"
)
const (
// ApplyConfigurationTypeSuffix is the suffix of generated apply configuration types.
ApplyConfigurationTypeSuffix = "ApplyConfiguration"
)
// NameSystems returns the name system used by the generators in this package.
func NameSystems() namer.NameSystems {
return namer.NameSystems{
"public": namer.NewPublicNamer(0),
"private": namer.NewPrivateNamer(0),
"raw": namer.NewRawNamer("", nil),
}
}
// DefaultNameSystem returns the default name system for ordering the types to be
// processed by the generators in this package.
func DefaultNameSystem() string {
return "public"
}
// Packages makes the client package definition.
func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages {
boilerplate, err := arguments.LoadGoBoilerplate()
if err != nil {
klog.Fatalf("Failed loading boilerplate: %v", err)
}
pkgTypes := packageTypesForInputDirs(context, arguments.InputDirs, arguments.OutputPackagePath)
customArgs := arguments.CustomArgs.(*applygenargs.CustomArgs)
initialTypes := customArgs.ExternalApplyConfigurations
refs := refGraphForReachableTypes(context.Universe, pkgTypes, initialTypes)
typeModels, err := newTypeModels(customArgs.OpenAPISchemaFilePath, pkgTypes)
if err != nil {
klog.Fatalf("Failed build type models from typeModels %s: %v", customArgs.OpenAPISchemaFilePath, err)
}
groupVersions := make(map[string]clientgentypes.GroupVersions)
groupGoNames := make(map[string]string)
applyConfigsForGroupVersion := make(map[clientgentypes.GroupVersion][]applyConfig)
var packageList generator.Packages
for pkg, p := range pkgTypes {
gv := groupVersion(p)
pkgType := types.Name{Name: gv.Group.PackageName(), Package: pkg}
var toGenerate []applyConfig
for _, t := range p.Types {
// If we don't have an ObjectMeta field, we lack the information required to make the Apply or ApplyStatus call
// to the kube-apiserver, so we don't need to generate the type at all
clientTags := genclientTags(t)
if clientTags.GenerateClient && !hasObjectMetaField(t) {
klog.V(5).Infof("skipping type %v because does not have ObjectMeta", t)
continue
}
if typePkg, ok := refs[t.Name]; ok {
toGenerate = append(toGenerate, applyConfig{
Type: t,
ApplyConfiguration: types.Ref(typePkg, t.Name.Name+ApplyConfigurationTypeSuffix),
})
}
}
if len(toGenerate) == 0 {
continue // Don't generate empty packages
}
sort.Sort(applyConfigSort(toGenerate))
// generate the apply configurations
packageList = append(packageList, generatorForApplyConfigurationsPackage(arguments.OutputPackagePath, boilerplate, pkgType, gv, toGenerate, refs, typeModels))
// group all the generated apply configurations by gv so ForKind() can be generated
groupPackageName := gv.Group.NonEmpty()
groupVersionsEntry, ok := groupVersions[groupPackageName]
if !ok {
groupVersionsEntry = clientgentypes.GroupVersions{
PackageName: groupPackageName,
Group: gv.Group,
}
}
groupVersionsEntry.Versions = append(groupVersionsEntry.Versions, clientgentypes.PackageVersion{
Version: gv.Version,
Package: path.Clean(p.Path),
})
groupGoNames[groupPackageName] = goName(gv, p)
applyConfigsForGroupVersion[gv] = toGenerate
groupVersions[groupPackageName] = groupVersionsEntry
}
// generate ForKind() utility function
packageList = append(packageList, generatorForUtils(arguments.OutputPackagePath, boilerplate, groupVersions, applyConfigsForGroupVersion, groupGoNames))
// generate internal embedded schema, required for generated Extract functions
packageList = append(packageList, generatorForInternal(filepath.Join(arguments.OutputPackagePath, "internal"), boilerplate, typeModels))
return packageList
}
func friendlyName(name string) string {
nameParts := strings.Split(name, "/")
// Reverse first part. e.g., io.k8s... instead of k8s.io...
if len(nameParts) > 0 && strings.Contains(nameParts[0], ".") {
parts := strings.Split(nameParts[0], ".")
for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 {
parts[i], parts[j] = parts[j], parts[i]
}
nameParts[0] = strings.Join(parts, ".")
}
return strings.Join(nameParts, ".")
}
func typeName(t *types.Type) string {
typePackage := t.Name.Package
if strings.Contains(typePackage, "/vendor/") {
typePackage = typePackage[strings.Index(typePackage, "/vendor/")+len("/vendor/"):]
}
return fmt.Sprintf("%s.%s", typePackage, t.Name.Name)
}
func generatorForApplyConfigurationsPackage(outputPackagePath string, boilerplate []byte, packageName types.Name, gv clientgentypes.GroupVersion, typesToGenerate []applyConfig, refs refGraph, models *typeModels) *generator.DefaultPackage {
return &generator.DefaultPackage{
PackageName: gv.Version.PackageName(),
PackagePath: packageName.Package,
HeaderText: boilerplate,
GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
for _, toGenerate := range typesToGenerate {
var openAPIType *string
gvk := gvk{
group: gv.Group.String(),
version: gv.Version.String(),
kind: toGenerate.Type.Name.Name,
}
if v, ok := models.gvkToOpenAPIType[gvk]; ok {
openAPIType = &v
}
generators = append(generators, &applyConfigurationGenerator{
DefaultGen: generator.DefaultGen{
OptionalName: strings.ToLower(toGenerate.Type.Name.Name),
},
outputPackage: outputPackagePath,
localPackage: packageName,
groupVersion: gv,
applyConfig: toGenerate,
imports: generator.NewImportTracker(),
refGraph: refs,
openAPIType: openAPIType,
})
}
return generators
},
}
}
func generatorForUtils(outPackagePath string, boilerplate []byte, groupVersions map[string]clientgentypes.GroupVersions, applyConfigsForGroupVersion map[clientgentypes.GroupVersion][]applyConfig, groupGoNames map[string]string) *generator.DefaultPackage {
return &generator.DefaultPackage{
PackageName: filepath.Base(outPackagePath),
PackagePath: outPackagePath,
HeaderText: boilerplate,
GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
generators = append(generators, &utilGenerator{
DefaultGen: generator.DefaultGen{
OptionalName: "utils",
},
outputPackage: outPackagePath,
imports: generator.NewImportTracker(),
groupVersions: groupVersions,
typesForGroupVersion: applyConfigsForGroupVersion,
groupGoNames: groupGoNames,
})
return generators
},
}
}
func generatorForInternal(outPackagePath string, boilerplate []byte, models *typeModels) *generator.DefaultPackage {
return &generator.DefaultPackage{
PackageName: filepath.Base(outPackagePath),
PackagePath: outPackagePath,
HeaderText: boilerplate,
GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
generators = append(generators, &internalGenerator{
DefaultGen: generator.DefaultGen{
OptionalName: "internal",
},
outputPackage: outPackagePath,
imports: generator.NewImportTracker(),
typeModels: models,
})
return generators
},
}
}
func goName(gv clientgentypes.GroupVersion, p *types.Package) string {
goName := namer.IC(strings.Split(gv.Group.NonEmpty(), ".")[0])
if override := types.ExtractCommentTags("+", p.Comments)["groupGoName"]; override != nil {
goName = namer.IC(override[0])
}
return goName
}
func packageTypesForInputDirs(context *generator.Context, inputDirs []string, outputPath string) map[string]*types.Package {
pkgTypes := map[string]*types.Package{}
for _, inputDir := range inputDirs {
p := context.Universe.Package(inputDir)
internal := isInternalPackage(p)
if internal {
klog.Warningf("Skipping internal package: %s", p.Path)
continue
}
// This is how the client generator finds the package we are creating. It uses the API package name, not the group name.
// This matches the approach of the client-gen, so the two generator can work together.
// For example, if openshift/api/cloudnetwork/v1 contains an apigroup cloud.network.openshift.io, the client-gen
// builds a package called cloudnetwork/v1 to contain it. This change makes the applyconfiguration-gen use the same.
_, gvPackageString := util.ParsePathGroupVersion(p.Path)
pkg := filepath.Join(outputPath, strings.ToLower(gvPackageString))
pkgTypes[pkg] = p
}
return pkgTypes
}
func groupVersion(p *types.Package) (gv clientgentypes.GroupVersion) {
parts := strings.Split(p.Path, "/")
gv.Group = clientgentypes.Group(parts[len(parts)-2])
gv.Version = clientgentypes.Version(parts[len(parts)-1])
// If there's a comment of the form "// +groupName=somegroup" or
// "// +groupName=somegroup.foo.bar.io", use the first field (somegroup) as the name of the
// group when generating.
if override := types.ExtractCommentTags("+", p.Comments)["groupName"]; override != nil {
gv.Group = clientgentypes.Group(override[0])
}
return gv
}
// isInternalPackage returns true if the package is an internal package
func isInternalPackage(p *types.Package) bool {
for _, t := range p.Types {
for _, member := range t.Members {
if member.Name == "ObjectMeta" {
return isInternal(member)
}
}
}
return false
}
// isInternal returns true if the tags for a member do not contain a json tag
func isInternal(m types.Member) bool {
_, ok := lookupJSONTags(m)
return !ok
}
func hasObjectMetaField(t *types.Type) bool {
for _, member := range t.Members {
if objectMeta.Name == member.Type.Name && member.Embedded {
return true
}
}
return false
}

View File

@@ -0,0 +1,179 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generators
import (
"k8s.io/gengo/types"
"k8s.io/code-generator/cmd/client-gen/generators/util"
)
// refGraph maps existing types to the package the corresponding applyConfig types will be generated in
// so that references between apply configurations can be correctly generated.
type refGraph map[types.Name]string
// refGraphForReachableTypes returns a refGraph that contains all reachable types from
// the root clientgen types of the provided packages.
func refGraphForReachableTypes(universe types.Universe, pkgTypes map[string]*types.Package, initialTypes map[types.Name]string) refGraph {
var refs refGraph = initialTypes
// Include only types that are reachable from the root clientgen types.
// We don't want to generate apply configurations for types that are not reachable from a root
// clientgen type.
reachableTypes := map[types.Name]*types.Type{}
for _, p := range pkgTypes {
for _, t := range p.Types {
tags := genclientTags(t)
hasApply := tags.HasVerb("apply") || tags.HasVerb("applyStatus")
if tags.GenerateClient && hasApply {
findReachableTypes(t, reachableTypes)
}
// If any apply extensions have custom inputs, add them.
for _, extension := range tags.Extensions {
if extension.HasVerb("apply") {
if len(extension.InputTypeOverride) > 0 {
inputType := *t
if name, pkg := extension.Input(); len(pkg) > 0 {
inputType = *(universe.Type(types.Name{Package: pkg, Name: name}))
} else {
inputType.Name.Name = extension.InputTypeOverride
}
findReachableTypes(&inputType, reachableTypes)
}
}
}
}
}
for pkg, p := range pkgTypes {
for _, t := range p.Types {
if _, ok := reachableTypes[t.Name]; !ok {
continue
}
if requiresApplyConfiguration(t) {
refs[t.Name] = pkg
}
}
}
return refs
}
// applyConfigForType find the type used in the generate apply configurations for a field.
// This may either be an existing type or one of the other generated applyConfig types.
func (t refGraph) applyConfigForType(field *types.Type) *types.Type {
switch field.Kind {
case types.Struct:
if pkg, ok := t[field.Name]; ok { // TODO(jpbetz): Refs to types defined in a separate system (e.g. TypeMeta if generating a 3rd party controller) end up referencing the go struct, not the apply configuration type
return types.Ref(pkg, field.Name.Name+ApplyConfigurationTypeSuffix)
}
return field
case types.Map:
if _, ok := t[field.Elem.Name]; ok {
return &types.Type{
Kind: types.Map,
Elem: t.applyConfigForType(field.Elem),
Key: t.applyConfigForType(field.Key),
}
}
return field
case types.Slice:
if _, ok := t[field.Elem.Name]; ok {
return &types.Type{
Kind: types.Slice,
Elem: t.applyConfigForType(field.Elem),
}
}
return field
case types.Pointer:
return t.applyConfigForType(field.Elem)
default:
return field
}
}
func (t refGraph) isApplyConfig(field *types.Type) bool {
switch field.Kind {
case types.Struct:
_, ok := t[field.Name]
return ok
case types.Pointer:
return t.isApplyConfig(field.Elem)
}
return false
}
// genclientTags returns the genclient Tags for the given type.
func genclientTags(t *types.Type) util.Tags {
return util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
}
// findReachableTypes finds all types transitively reachable from a given root type, including
// the root type itself.
func findReachableTypes(t *types.Type, referencedTypes map[types.Name]*types.Type) {
if _, ok := referencedTypes[t.Name]; ok {
return
}
referencedTypes[t.Name] = t
if t.Elem != nil {
findReachableTypes(t.Elem, referencedTypes)
}
if t.Underlying != nil {
findReachableTypes(t.Underlying, referencedTypes)
}
if t.Key != nil {
findReachableTypes(t.Key, referencedTypes)
}
for _, m := range t.Members {
findReachableTypes(m.Type, referencedTypes)
}
}
// excludeTypes contains well known types that we do not generate apply configurations for.
// Hard coding because we only have two, very specific types that serve a special purpose
// in the type system here.
var excludeTypes = map[types.Name]struct{}{
rawExtension.Name: {},
unknown.Name: {},
// DO NOT ADD TO THIS LIST. If we need to exclude other types, we should consider allowing the
// go type declarations to be annotated as excluded from this generator.
}
// requiresApplyConfiguration returns true if a type applyConfig should be generated for the given type.
// types applyConfig are only generated for struct types that contain fields with json tags.
func requiresApplyConfiguration(t *types.Type) bool {
for t.Kind == types.Alias {
t = t.Underlying
}
if t.Kind != types.Struct {
return false
}
if _, ok := excludeTypes[t.Name]; ok {
return false
}
var hasJSONTaggedMembers bool
for _, member := range t.Members {
if _, ok := lookupJSONTags(member); ok {
hasJSONTaggedMembers = true
}
}
if !hasJSONTaggedMembers {
return false
}
return true
}

View File

@@ -0,0 +1,33 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generators
import "k8s.io/gengo/types"
var (
applyConfiguration = types.Ref("k8s.io/apimachinery/pkg/runtime", "ApplyConfiguration")
groupVersionKind = types.Ref("k8s.io/apimachinery/pkg/runtime/schema", "GroupVersionKind")
typeMeta = types.Ref("k8s.io/apimachinery/pkg/apis/meta/v1", "TypeMeta")
objectMeta = types.Ref("k8s.io/apimachinery/pkg/apis/meta/v1", "ObjectMeta")
rawExtension = types.Ref("k8s.io/apimachinery/pkg/runtime", "RawExtension")
unknown = types.Ref("k8s.io/apimachinery/pkg/runtime", "Unknown")
extractInto = types.Ref("k8s.io/apimachinery/pkg/util/managedfields", "ExtractInto")
smdNewParser = types.Ref("sigs.k8s.io/structured-merge-diff/v4/typed", "NewParser")
smdParser = types.Ref("sigs.k8s.io/structured-merge-diff/v4/typed", "Parser")
yamlObject = types.Ref("sigs.k8s.io/structured-merge-diff/v4/typed", "YAMLObject")
yamlUnmarshal = types.Ref("gopkg.in/yaml.v2", "Unmarshal")
)

View File

@@ -0,0 +1,163 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generators
import (
"io"
"sort"
"strings"
clientgentypes "k8s.io/code-generator/cmd/client-gen/types"
"k8s.io/gengo/generator"
"k8s.io/gengo/namer"
"k8s.io/gengo/types"
)
// utilGenerator generates the ForKind() utility function.
type utilGenerator struct {
generator.DefaultGen
outputPackage string
imports namer.ImportTracker
groupVersions map[string]clientgentypes.GroupVersions
groupGoNames map[string]string
typesForGroupVersion map[clientgentypes.GroupVersion][]applyConfig
filtered bool
}
var _ generator.Generator = &utilGenerator{}
func (g *utilGenerator) Filter(*generator.Context, *types.Type) bool {
// generate file exactly once
if !g.filtered {
g.filtered = true
return true
}
return false
}
func (g *utilGenerator) Namers(*generator.Context) namer.NameSystems {
return namer.NameSystems{
"raw": namer.NewRawNamer(g.outputPackage, g.imports),
"singularKind": namer.NewPublicNamer(0),
}
}
func (g *utilGenerator) Imports(*generator.Context) (imports []string) {
return g.imports.ImportLines()
}
type group struct {
GroupGoName string
Name string
Versions []*version
}
type groupSort []group
func (g groupSort) Len() int { return len(g) }
func (g groupSort) Less(i, j int) bool {
return strings.ToLower(g[i].Name) < strings.ToLower(g[j].Name)
}
func (g groupSort) Swap(i, j int) { g[i], g[j] = g[j], g[i] }
type version struct {
Name string
GoName string
Resources []applyConfig
}
type versionSort []*version
func (v versionSort) Len() int { return len(v) }
func (v versionSort) Less(i, j int) bool {
return strings.ToLower(v[i].Name) < strings.ToLower(v[j].Name)
}
func (v versionSort) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
type applyConfig struct {
Type *types.Type
ApplyConfiguration *types.Type
}
type applyConfigSort []applyConfig
func (v applyConfigSort) Len() int { return len(v) }
func (v applyConfigSort) Less(i, j int) bool {
return strings.ToLower(v[i].Type.Name.Name) < strings.ToLower(v[j].Type.Name.Name)
}
func (v applyConfigSort) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
func (g *utilGenerator) GenerateType(c *generator.Context, _ *types.Type, w io.Writer) error {
sw := generator.NewSnippetWriter(w, c, "{{", "}}")
var groups []group
schemeGVs := make(map[*version]*types.Type)
for groupPackageName, groupVersions := range g.groupVersions {
group := group{
GroupGoName: g.groupGoNames[groupPackageName],
Name: groupVersions.Group.NonEmpty(),
Versions: []*version{},
}
for _, v := range groupVersions.Versions {
gv := clientgentypes.GroupVersion{Group: groupVersions.Group, Version: v.Version}
version := &version{
Name: v.Version.NonEmpty(),
GoName: namer.IC(v.Version.NonEmpty()),
Resources: g.typesForGroupVersion[gv],
}
schemeGVs[version] = c.Universe.Variable(types.Name{
Package: g.typesForGroupVersion[gv][0].Type.Name.Package,
Name: "SchemeGroupVersion",
})
group.Versions = append(group.Versions, version)
}
sort.Sort(versionSort(group.Versions))
groups = append(groups, group)
}
sort.Sort(groupSort(groups))
m := map[string]interface{}{
"groups": groups,
"schemeGVs": schemeGVs,
"schemaGroupVersionKind": groupVersionKind,
"applyConfiguration": applyConfiguration,
}
sw.Do(forKindFunc, m)
return sw.Error()
}
var forKindFunc = `
// ForKind returns an apply configuration type for the given GroupVersionKind, or nil if no
// apply configuration type exists for the given GroupVersionKind.
func ForKind(kind {{.schemaGroupVersionKind|raw}}) interface{} {
switch kind {
{{range $group := .groups -}}{{$GroupGoName := .GroupGoName -}}
{{range $version := .Versions -}}
// Group={{$group.Name}}, Version={{.Name}}
{{range .Resources -}}
case {{index $.schemeGVs $version|raw}}.WithKind("{{.Type|singularKind}}"):
return &{{.ApplyConfiguration|raw}}{}
{{end}}
{{end}}
{{end -}}
}
return nil
}
`

View File

@@ -0,0 +1,54 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// typebuilder-gen is a tool for auto-generating apply builder functions.
package main
import (
"flag"
"github.com/spf13/pflag"
"k8s.io/klog/v2"
generatorargs "k8s.io/code-generator/cmd/applyconfiguration-gen/args"
"k8s.io/code-generator/cmd/applyconfiguration-gen/generators"
)
func main() {
klog.InitFlags(nil)
genericArgs, customArgs := generatorargs.NewDefaults()
genericArgs.AddFlags(pflag.CommandLine)
customArgs.AddFlags(pflag.CommandLine, "k8s.io/kubernetes/pkg/apis") // TODO: move this input path out of applyconfiguration-gen
if err := flag.Set("logtostderr", "true"); err != nil {
klog.Fatalf("Error: %v", err)
}
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
pflag.Parse()
if err := generatorargs.Validate(genericArgs); err != nil {
klog.Fatalf("Error: %v", err)
}
// Run it.
if err := genericArgs.Execute(
generators.NameSystems(),
generators.DefaultNameSystem(),
generators.Packages,
); err != nil {
klog.Fatalf("Error: %v", err)
}
klog.V(2).Info("Completed successfully.")
}

View File

@@ -1,10 +1,11 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- lavalamp
- wojtek-t
- caesarxuchao
reviewers:
- lavalamp
- wojtek-t
- caesarxuchao
- jpbetz
emeritus_approvers:
- lavalamp

View File

@@ -131,12 +131,10 @@ func DefaultNameSystem() string {
func 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 {
groupVersionClientPackage := filepath.Join(clientsetPackage, "typed", strings.ToLower(groupPackageName), strings.ToLower(gv.Version.NonEmpty()))
return &generator.DefaultPackage{
PackageName: strings.ToLower(gv.Version.NonEmpty()),
PackagePath: groupVersionClientPackage,
HeaderText: boilerplate,
PackageDocumentation: []byte(
`// This package has the automatically generated typed clients.
`),
PackageName: strings.ToLower(gv.Version.NonEmpty()),
PackagePath: groupVersionClientPackage,
HeaderText: boilerplate,
PackageDocumentation: []byte("// This package has the automatically generated typed clients.\n"),
// GeneratorFunc returns a list of generators. Each generator makes a
// single file.
GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
@@ -200,16 +198,10 @@ func packageForClientset(customArgs *clientgenargs.CustomArgs, clientsetPackage
PackageName: customArgs.ClientsetName,
PackagePath: clientsetPackage,
HeaderText: boilerplate,
PackageDocumentation: []byte(
`// This package has the automatically generated clientset.
`),
// GeneratorFunc returns a list of generators. Each generator generates a
// single file.
GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
generators = []generator.Generator{
// Always generate a "doc.go" file.
generator.DefaultGen{OptionalName: "doc"},
&genClientset{
DefaultGen: generator.DefaultGen{
OptionalName: "clientset",
@@ -242,12 +234,10 @@ NextGroup:
}
return &generator.DefaultPackage{
PackageName: "scheme",
PackagePath: schemePackage,
HeaderText: boilerplate,
PackageDocumentation: []byte(
`// This package contains the scheme of the automatically generated clientset.
`),
PackageName: "scheme",
PackagePath: schemePackage,
HeaderText: boilerplate,
PackageDocumentation: []byte("// This package contains the scheme of the automatically generated clientset.\n"),
// GeneratorFunc returns a list of generators. Each generator generates a
// single file.
GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {

View File

@@ -27,7 +27,6 @@ import (
"k8s.io/gengo/types"
"k8s.io/code-generator/cmd/client-gen/generators/util"
"k8s.io/code-generator/cmd/client-gen/path"
)
// genFakeForType produces a file for each top-level type.
@@ -93,51 +92,33 @@ func (g *genFakeForType) GenerateType(c *generator.Context, t *types.Type, w io.
if err != nil {
return err
}
canonicalGroup := g.group
if canonicalGroup == "core" {
canonicalGroup = ""
}
groupName := g.group
if g.group == "core" {
groupName = ""
}
// allow user to define a group name that's different from the one parsed from the directory.
p := c.Universe.Package(path.Vendorless(g.inputPackage))
if override := types.ExtractCommentTags("+", p.Comments)["groupName"]; override != nil {
groupName = override[0]
}
const pkgClientGoTesting = "k8s.io/client-go/testing"
m := map[string]interface{}{
"type": t,
"inputType": t,
"resultType": t,
"subresourcePath": "",
"package": pkg,
"Package": namer.IC(pkg),
"namespaced": !tags.NonNamespaced,
"Group": namer.IC(g.group),
"GroupGoName": g.groupGoName,
"Version": namer.IC(g.version),
"group": canonicalGroup,
"groupName": groupName,
"version": g.version,
"CreateOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "CreateOptions"}),
"DeleteOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "DeleteOptions"}),
"GetOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "GetOptions"}),
"ListOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ListOptions"}),
"PatchOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "PatchOptions"}),
"ApplyOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ApplyOptions"}),
"UpdateOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "UpdateOptions"}),
"Everything": c.Universe.Function(types.Name{Package: "k8s.io/apimachinery/pkg/labels", Name: "Everything"}),
"GroupVersionResource": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/runtime/schema", Name: "GroupVersionResource"}),
"GroupVersionKind": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/runtime/schema", Name: "GroupVersionKind"}),
"PatchType": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/types", Name: "PatchType"}),
"ApplyPatchType": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/types", Name: "ApplyPatchType"}),
"watchInterface": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/watch", Name: "Interface"}),
"jsonMarshal": c.Universe.Type(types.Name{Package: "encoding/json", Name: "Marshal"}),
"type": t,
"inputType": t,
"resultType": t,
"subresourcePath": "",
"package": pkg,
"Package": namer.IC(pkg),
"namespaced": !tags.NonNamespaced,
"Group": namer.IC(g.group),
"GroupGoName": g.groupGoName,
"Version": namer.IC(g.version),
"version": g.version,
"SchemeGroupVersion": c.Universe.Type(types.Name{Package: t.Name.Package, Name: "SchemeGroupVersion"}),
"CreateOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "CreateOptions"}),
"DeleteOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "DeleteOptions"}),
"GetOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "GetOptions"}),
"ListOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ListOptions"}),
"PatchOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "PatchOptions"}),
"ApplyOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ApplyOptions"}),
"UpdateOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "UpdateOptions"}),
"Everything": c.Universe.Function(types.Name{Package: "k8s.io/apimachinery/pkg/labels", Name: "Everything"}),
"PatchType": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/types", Name: "PatchType"}),
"ApplyPatchType": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/types", Name: "ApplyPatchType"}),
"watchInterface": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/watch", Name: "Interface"}),
"jsonMarshal": c.Universe.Type(types.Name{Package: "encoding/json", Name: "Marshal"}),
"NewRootListAction": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootListAction"}),
"NewListAction": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewListAction"}),
@@ -340,11 +321,11 @@ type Fake$.type|publicPlural$ struct {
`
var resource = `
var $.type|allLowercasePlural$Resource = $.GroupVersionResource|raw${Group: "$.groupName$", Version: "$.version$", Resource: "$.type|resource$"}
var $.type|allLowercasePlural$Resource = $.SchemeGroupVersion|raw$.WithResource("$.type|resource$")
`
var kind = `
var $.type|allLowercasePlural$Kind = $.GroupVersionKind|raw${Group: "$.groupName$", Version: "$.version$", Kind: "$.type|singularKind$"}
var $.type|allLowercasePlural$Kind = $.SchemeGroupVersion|raw$.WithKind("$.type|singularKind$")
`
var listTemplate = `

View File

@@ -50,6 +50,7 @@ type Generator struct {
KeepGogoproto bool
SkipGeneratedRewrite bool
DropEmbeddedFields string
TrimPathPrefix string
}
func New() *Generator {
@@ -95,6 +96,7 @@ func (g *Generator) BindFlags(flag *flag.FlagSet) {
flag.BoolVar(&g.KeepGogoproto, "keep-gogoproto", g.KeepGogoproto, "If true, the generated IDL will contain gogoprotobuf extensions which are normally removed")
flag.BoolVar(&g.SkipGeneratedRewrite, "skip-generated-rewrite", g.SkipGeneratedRewrite, "If true, skip fixing up the generated.pb.go file (debugging only).")
flag.StringVar(&g.DropEmbeddedFields, "drop-embedded-fields", g.DropEmbeddedFields, "Comma-delimited list of embedded Go types to omit from generated protobufs")
flag.StringVar(&g.TrimPathPrefix, "trim-path-prefix", g.TrimPathPrefix, "If set, trim the specified prefix from --output-package when generating files.")
}
func Run(g *Generator) {
@@ -200,6 +202,7 @@ func Run(g *Generator) {
c.Verify = g.Common.VerifyOnly
c.FileTypes["protoidl"] = NewProtoFile()
c.TrimPathPrefix = g.TrimPathPrefix
// order package by imports, importees first
deps := deps(c, protobufNames.packages)
@@ -270,14 +273,28 @@ func Run(g *Generator) {
outputPath = filepath.Join(g.VendorOutputBase, p.OutputPath())
}
// When working outside of GOPATH, we typically won't want to generate the
// full path for a package. For example, if our current project's root/base
// package is github.com/foo/bar, outDir=., p.Path()=github.com/foo/bar/generated,
// then we really want to be writing files to ./generated, not ./github.com/foo/bar/generated.
// The following will trim a path prefix (github.com/foo/bar) from p.Path() to arrive at
// a relative path that works with projects not in GOPATH.
if g.TrimPathPrefix != "" {
separator := string(filepath.Separator)
if !strings.HasSuffix(g.TrimPathPrefix, separator) {
g.TrimPathPrefix += separator
}
path = strings.TrimPrefix(path, g.TrimPathPrefix)
outputPath = strings.TrimPrefix(outputPath, g.TrimPathPrefix)
}
// generate the gogoprotobuf protoc
cmd := exec.Command("protoc", append(args, path)...)
out, err := cmd.CombinedOutput()
if len(out) > 0 {
log.Print(string(out))
}
if err != nil {
log.Println(strings.Join(cmd.Args, " "))
log.Println(string(out))
log.Fatalf("Unable to generate protoc on %s: %v", p.PackageName, err)
}
@@ -397,9 +414,9 @@ func importOrder(deps map[string][]string) ([]string, error) {
if len(remainingNodes) > 0 {
return nil, fmt.Errorf("cycle: remaining nodes: %#v, remaining edges: %#v", remainingNodes, graph)
}
for _, n := range sorted {
fmt.Println("topological order", n)
}
//for _, n := range sorted {
// fmt.Println("topological order", n)
//}
return sorted, nil
}

View File

@@ -109,8 +109,7 @@ func RewriteGeneratedGogoProtobufFile(name string, extractFn ExtractFunc, option
// as being "optional" (they may be nil on the wire). This allows protobuf to serialize a map or slice and
// properly discriminate between empty and nil (which is not possible in protobuf).
// TODO: move into upstream gogo-protobuf once https://github.com/gogo/protobuf/issues/181
//
// has agreement
// has agreement
func rewriteOptionalMethods(decl ast.Decl, isOptional OptionalFunc) {
switch t := decl.(type) {
case *ast.FuncDecl:

View File

@@ -234,7 +234,7 @@ func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[ref
return res
}
// InternalInformerFor returns the SharedIndexInformer for obj using an internal
// InformerFor returns the SharedIndexInformer for obj using an internal
// client.
func (f *sharedInformerFactory) InformerFor(obj {{.runtimeObject|raw}}, newFunc {{.interfacesNewInformerFunc|raw}}) {{.cacheSharedIndexInformer|raw}} {
f.lock.Lock()
@@ -310,7 +310,7 @@ type SharedInformerFactory interface {
// ForResource gives generic access to a shared informer of the matching type.
ForResource(resource {{.schemaGroupVersionResource|raw}}) (GenericInformer, error)
// InternalInformerFor returns the SharedIndexInformer for obj using an internal
// InformerFor returns the SharedIndexInformer for obj using an internal
// client.
InformerFor(obj {{.runtimeObject|raw}}, newFunc {{.interfacesNewInformerFunc|raw}}) {{.cacheSharedIndexInformer|raw}}

View File

@@ -25,7 +25,7 @@ if [ "$#" -lt 4 ] || [ "${1}" == "--help" ]; then
cat <<EOF
Usage: $(basename "$0") <generators> <output-package> <apis-package> <groups-versions> ...
<generators> the generators comma separated to run (deepcopy,defaulter,client,lister,informer) or "all".
<generators> the generators comma separated to run (deepcopy,defaulter,applyconfiguration,client,lister,informer).
<output-package> the output package name (e.g. github.com/example/project/pkg/generated).
<apis-package> the external types dir (e.g. github.com/example/api or github.com/example/project/pkg/apis).
<groups-versions> the groups and their versions in the format "groupA:v1,v2 groupB:v1 groupC:v2", relative
@@ -33,9 +33,12 @@ Usage: $(basename "$0") <generators> <output-package> <apis-package> <groups-ver
... arbitrary flags passed to all generator binaries.
Examples:
$(basename "$0") all github.com/example/project/pkg/client github.com/example/project/pkg/apis "foo:v1 bar:v1alpha1,v1beta1"
$(basename "$0") deepcopy,client github.com/example/project/pkg/client github.com/example/project/pkg/apis "foo:v1 bar:v1alpha1,v1beta1"
Example:
$(basename "$0") \
deepcopy,client \
github.com/example/project/pkg/client \
github.com/example/project/pkg/apis \
"foo:v1 bar:v1alpha1,v1beta1"
EOF
exit 0
fi
@@ -46,61 +49,18 @@ APIS_PKG="$3"
GROUPS_WITH_VERSIONS="$4"
shift 4
(
# To support running this script from anywhere, first cd into this directory,
# and then install with forced module mode on and fully qualified name.
cd "$(dirname "${0}")"
GO111MODULE=on go install k8s.io/code-generator/cmd/{defaulter-gen,client-gen,lister-gen,informer-gen,deepcopy-gen}
)
# Go installs the above commands to get installed in $GOBIN if defined, and $GOPATH/bin otherwise:
GOBIN="$(go env GOBIN)"
gobin="${GOBIN:-$(go env GOPATH)/bin}"
echo "WARNING: $(basename "$0") is deprecated."
echo "WARNING: Please use k8s.io/code-generator/kube_codegen.sh instead."
echo
function codegen::join() { local IFS="$1"; shift; echo "$*"; }
# enumerate group versions
FQ_APIS=() # e.g. k8s.io/api/apps/v1
for GVs in ${GROUPS_WITH_VERSIONS}; do
IFS=: read -r G Vs <<<"${GVs}"
# enumerate versions
for V in ${Vs//,/ }; do
FQ_APIS+=("${APIS_PKG}/${G}/${V}")
done
done
if [ "${GENS}" = "all" ] || grep -qw "deepcopy" <<<"${GENS}"; then
echo "Generating deepcopy funcs"
"${gobin}/deepcopy-gen" \
--input-dirs "$(codegen::join , "${FQ_APIS[@]}")" \
-O zz_generated.deepcopy \
"$@"
if [ "${GENS}" = "all" ] || grep -qw "all" <<<"${GENS}"; then
ALL="applyconfiguration,client,deepcopy,informer,lister"
echo "WARNING: Specifying \"all\" as a generator is deprecated."
echo "WARNING: Please list the specific generators needed."
echo "WARNING: \"all\" is now an alias for \"${ALL}\"; new code generators WILL NOT be added to this set"
echo
GENS="${ALL}"
fi
if [ "${GENS}" = "all" ] || grep -qw "client" <<<"${GENS}"; then
echo "Generating clientset for ${GROUPS_WITH_VERSIONS} at ${OUTPUT_PKG}/${CLIENTSET_PKG_NAME:-clientset}"
"${gobin}/client-gen" \
--clientset-name "${CLIENTSET_NAME_VERSIONED:-versioned}" \
--input-base "" \
--input "$(codegen::join , "${FQ_APIS[@]}")" \
--output-package "${OUTPUT_PKG}/${CLIENTSET_PKG_NAME:-clientset}" \
"$@"
fi
if [ "${GENS}" = "all" ] || grep -qw "lister" <<<"${GENS}"; then
echo "Generating listers for ${GROUPS_WITH_VERSIONS} at ${OUTPUT_PKG}/listers"
"${gobin}/lister-gen" \
--input-dirs "$(codegen::join , "${FQ_APIS[@]}")" \
--output-package "${OUTPUT_PKG}/listers" \
"$@"
fi
if [ "${GENS}" = "all" ] || grep -qw "informer" <<<"${GENS}"; then
echo "Generating informers for ${GROUPS_WITH_VERSIONS} at ${OUTPUT_PKG}/informers"
"${gobin}/informer-gen" \
--input-dirs "$(codegen::join , "${FQ_APIS[@]}")" \
--versioned-clientset-package "${OUTPUT_PKG}/${CLIENTSET_PKG_NAME:-clientset}/${CLIENTSET_NAME_VERSIONED:-versioned}" \
--listers-package "${OUTPUT_PKG}/listers" \
--output-package "${OUTPUT_PKG}/informers" \
"$@"
fi
INT_APIS_PKG=""
exec "$(dirname "${BASH_SOURCE[0]}")/generate-internal-groups.sh" "${GENS}" "${OUTPUT_PKG}" "${INT_APIS_PKG}" "${APIS_PKG}" "${GROUPS_WITH_VERSIONS}" "$@"

View File

@@ -25,17 +25,21 @@ if [ "$#" -lt 5 ] || [ "${1}" == "--help" ]; then
cat <<EOF
Usage: $(basename "$0") <generators> <output-package> <internal-apis-package> <extensiona-apis-package> <groups-versions> ...
<generators> the generators comma separated to run (deepcopy,defaulter,conversion,client,lister,informer,openapi) or "all".
<generators> the generators comma separated to run (applyconfiguration,client,conversion,deepcopy,defaulter,informer,lister,openapi).
<output-package> the output package name (e.g. github.com/example/project/pkg/generated).
<int-apis-package> the internal types dir (e.g. github.com/example/project/pkg/apis).
<int-apis-package> the internal types dir (e.g. github.com/example/project/pkg/apis) or "" if none.
<ext-apis-package> the external types dir (e.g. github.com/example/project/pkg/apis or githubcom/example/apis).
<groups-versions> the groups and their versions in the format "groupA:v1,v2 groupB:v1 groupC:v2", relative
to <api-package>.
... arbitrary flags passed to all generator binaries.
Examples:
$(basename "$0") all github.com/example/project/pkg/client github.com/example/project/pkg/apis github.com/example/project/pkg/apis "foo:v1 bar:v1alpha1,v1beta1"
$(basename "$0") deepcopy,defaulter,conversion github.com/example/project/pkg/client github.com/example/project/pkg/apis github.com/example/project/apis "foo:v1 bar:v1alpha1,v1beta1"
Example:
$(basename "$0") \
deepcopy,defaulter,conversion \
github.com/example/project/pkg/client \
github.com/example/project/pkg/apis \
github.com/example/project/apis \
"foo:v1 bar:v1alpha1,v1beta1"
EOF
exit 0
fi
@@ -47,97 +51,219 @@ EXT_APIS_PKG="$4"
GROUPS_WITH_VERSIONS="$5"
shift 5
echo "WARNING: $(basename "$0") is deprecated."
echo "WARNING: Please use k8s.io/code-generator/kube_codegen.sh instead."
echo
if [ "${GENS}" = "all" ] || grep -qw "all" <<<"${GENS}"; then
ALL="client,conversion,deepcopy,defaulter,informer,lister,openapi"
echo "WARNING: Specifying \"all\" as a generator is deprecated."
echo "WARNING: Please list the specific generators needed."
echo "WARNING: \"all\" is now an alias for \"${ALL}\"; new code generators WILL NOT be added to this set"
echo
GENS="${ALL}"
fi
(
# To support running this script from anywhere, first cd into this directory,
# and then install with forced module mode on and fully qualified name.
cd "$(dirname "${0}")"
GO111MODULE=on go install k8s.io/code-generator/cmd/{defaulter-gen,conversion-gen,client-gen,lister-gen,informer-gen,deepcopy-gen,openapi-gen}
BINS=(
applyconfiguration-gen
client-gen
conversion-gen
deepcopy-gen
defaulter-gen
informer-gen
lister-gen
openapi-gen
)
# Compile all the tools at once - it's slightly faster but also just simpler.
# shellcheck disable=2046 # printf word-splitting is intentional
GO111MODULE=on go install $(printf "k8s.io/code-generator/cmd/%s " "${BINS[@]}")
)
# Go installs the above commands to get installed in $GOBIN if defined, and $GOPATH/bin otherwise:
GOBIN="$(go env GOBIN)"
gobin="${GOBIN:-$(go env GOPATH)/bin}"
function git_find() {
# Similar to find but faster and easier to understand. We want to include
# modified and untracked files because this might be running against code
# which is not tracked by git yet.
git ls-files -cmo --exclude-standard "$@"
}
function git_grep() {
# We want to include modified and untracked files because this might be
# running against code which is not tracked by git yet.
git grep --untracked "$@"
}
function codegen::join() { local IFS="$1"; shift; echo "$*"; }
# enumerate group versions
ALL_FQ_APIS=() # e.g. k8s.io/kubernetes/pkg/apis/apps k8s.io/api/apps/v1
INT_FQ_APIS=() # e.g. k8s.io/kubernetes/pkg/apis/apps
EXT_FQ_APIS=() # e.g. k8s.io/api/apps/v1
GROUP_VERSIONS=() # e.g. apps/v1
for GVs in ${GROUPS_WITH_VERSIONS}; do
IFS=: read -r G Vs <<<"${GVs}"
if [ -n "${INT_APIS_PKG}" ]; then
ALL_FQ_APIS+=("${INT_APIS_PKG}/${G}")
INT_FQ_APIS+=("${INT_APIS_PKG}/${G}")
fi
# enumerate versions
for V in ${Vs//,/ }; do
ALL_FQ_APIS+=("${EXT_APIS_PKG}/${G}/${V}")
EXT_FQ_APIS+=("${EXT_APIS_PKG}/${G}/${V}")
GROUP_VERSIONS+=("${G}/${V}")
done
done
if [ "${GENS}" = "all" ] || grep -qw "deepcopy" <<<"${GENS}"; then
if grep -qw "deepcopy" <<<"${GENS}"; then
# Nuke existing files
for dir in $(GO111MODULE=on go list -f '{{.Dir}}' "${ALL_FQ_APIS[@]}"); do
pushd "${dir}" >/dev/null
git_find -z ':(glob)**'/zz_generated.deepcopy.go | xargs -0 rm -f
popd >/dev/null
done
echo "Generating deepcopy funcs"
"${GOPATH}/bin/deepcopy-gen" \
--input-dirs "$(codegen::join , "${ALL_FQ_APIS[@]}")" -O zz_generated.deepcopy \
"${gobin}/deepcopy-gen" \
--input-dirs "$(codegen::join , "${ALL_FQ_APIS[@]}")" \
-O zz_generated.deepcopy \
"$@"
fi
if [ "${GENS}" = "all" ] || grep -qw "defaulter" <<<"${GENS}"; then
if grep -qw "defaulter" <<<"${GENS}"; then
# Nuke existing files
for dir in $(GO111MODULE=on go list -f '{{.Dir}}' "${ALL_FQ_APIS[@]}"); do
pushd "${dir}" >/dev/null
git_find -z ':(glob)**'/zz_generated.defaults.go | xargs -0 rm -f
popd >/dev/null
done
echo "Generating defaulters"
"${GOPATH}/bin/defaulter-gen" \
--input-dirs "$(codegen::join , "${EXT_FQ_APIS[@]}")" -O zz_generated.defaults \
"${gobin}/defaulter-gen" \
--input-dirs "$(codegen::join , "${EXT_FQ_APIS[@]}")" \
-O zz_generated.defaults \
"$@"
fi
if [ "${GENS}" = "all" ] || grep -qw "conversion" <<<"${GENS}"; then
if grep -qw "conversion" <<<"${GENS}"; then
# Nuke existing files
for dir in $(GO111MODULE=on go list -f '{{.Dir}}' "${ALL_FQ_APIS[@]}"); do
pushd "${dir}" >/dev/null
git_find -z ':(glob)**'/zz_generated.conversion.go | xargs -0 rm -f
popd >/dev/null
done
echo "Generating conversions"
"${GOPATH}/bin/conversion-gen" \
--input-dirs "$(codegen::join , "${ALL_FQ_APIS[@]}")" -O zz_generated.conversion \
"${gobin}/conversion-gen" \
--input-dirs "$(codegen::join , "${ALL_FQ_APIS[@]}")" \
-O zz_generated.conversion \
"$@"
fi
if [ "${GENS}" = "all" ] || grep -qw "client" <<<"${GENS}"; then
echo "Generating clientset for ${GROUPS_WITH_VERSIONS} at ${OUTPUT_PKG}/${CLIENTSET_PKG_NAME:-clientset}"
if [ -n "${INT_APIS_PKG}" ]; then
IFS=" " read -r -a APIS <<< "$(printf '%s/ ' "${INT_FQ_APIS[@]}")"
"${GOPATH}/bin/client-gen" \
--clientset-name "${CLIENTSET_NAME_INTERNAL:-internalversion}" \
--input-base "" \
--input "$(codegen::join , "${APIS[@]}")" \
--output-package "${OUTPUT_PKG}/${CLIENTSET_PKG_NAME:-clientset}" \
"$@"
if grep -qw "applyconfiguration" <<<"${GENS}"; then
APPLY_CONFIGURATION_PACKAGE="${OUTPUT_PKG}/${APPLYCONFIGURATION_PKG_NAME:-applyconfiguration}"
# Nuke existing files
root="$(GO111MODULE=on go list -f '{{.Dir}}' "${APPLY_CONFIGURATION_PACKAGE}" 2>/dev/null || true)"
if [ -n "${root}" ]; then
pushd "${root}" >/dev/null
git_grep -l --null \
-e '^// Code generated by applyconfiguration-gen. DO NOT EDIT.$' \
':(glob)**/*.go' \
| xargs -0 rm -f
popd >/dev/null
fi
"${GOPATH}/bin/client-gen" \
--clientset-name "${CLIENTSET_NAME_VERSIONED:-versioned}" \
echo "Generating apply configuration for ${GROUPS_WITH_VERSIONS} at ${APPLY_CONFIGURATION_PACKAGE}"
"${gobin}/applyconfiguration-gen" \
--input-dirs "$(codegen::join , "${EXT_FQ_APIS[@]}")" \
--output-package "${APPLY_CONFIGURATION_PACKAGE}" \
"$@"
fi
if grep -qw "client" <<<"${GENS}"; then
CLIENTSET_PKG="${CLIENTSET_PKG_NAME:-clientset}"
CLIENTSET_NAME="${CLIENTSET_NAME_VERSIONED:-versioned}"
# Nuke existing files
root="$(GO111MODULE=on go list -f '{{.Dir}}' "${OUTPUT_PKG}/${CLIENTSET_PKG}/${CLIENTSET_NAME}" 2>/dev/null || true)"
if [ -n "${root}" ]; then
pushd "${root}" >/dev/null
git_grep -l --null \
-e '^// Code generated by client-gen. DO NOT EDIT.$' \
':(glob)**/*.go' \
| xargs -0 rm -f
popd >/dev/null
fi
echo "Generating clientset for ${GROUPS_WITH_VERSIONS} at ${OUTPUT_PKG}/${CLIENTSET_PKG}"
"${gobin}/client-gen" \
--clientset-name "${CLIENTSET_NAME}" \
--input-base "" \
--input "$(codegen::join , "${EXT_FQ_APIS[@]}")" \
--output-package "${OUTPUT_PKG}/${CLIENTSET_PKG_NAME:-clientset}" \
--output-package "${OUTPUT_PKG}/${CLIENTSET_PKG}" \
--apply-configuration-package "${APPLY_CONFIGURATION_PACKAGE:-}" \
"$@"
fi
if [ "${GENS}" = "all" ] || grep -qw "lister" <<<"${GENS}"; then
if grep -qw "lister" <<<"${GENS}"; then
# Nuke existing files
for gv in "${GROUP_VERSIONS[@]}"; do
root="$(GO111MODULE=on go list -f '{{.Dir}}' "${OUTPUT_PKG}/listers/${gv}" 2>/dev/null || true)"
if [ -n "${root}" ]; then
pushd "${root}" >/dev/null
git_grep -l --null \
-e '^// Code generated by lister-gen. DO NOT EDIT.$' \
':(glob)**/*.go' \
| xargs -0 rm -f
popd >/dev/null
fi
done
echo "Generating listers for ${GROUPS_WITH_VERSIONS} at ${OUTPUT_PKG}/listers"
"${GOPATH}/bin/lister-gen" \
--input-dirs "$(codegen::join , "${ALL_FQ_APIS[@]}")" \
"${gobin}/lister-gen" \
--input-dirs "$(codegen::join , "${EXT_FQ_APIS[@]}")" \
--output-package "${OUTPUT_PKG}/listers" \
"$@"
fi
if [ "${GENS}" = "all" ] || grep -qw "informer" <<<"${GENS}"; then
if grep -qw "informer" <<<"${GENS}"; then
# Nuke existing files
root="$(GO111MODULE=on go list -f '{{.Dir}}' "${OUTPUT_PKG}/informers/externalversions" 2>/dev/null || true)"
if [ -n "${root}" ]; then
pushd "${root}" >/dev/null
git_grep -l --null \
-e '^// Code generated by informer-gen. DO NOT EDIT.$' \
':(glob)**/*.go' \
| xargs -0 rm -f
popd >/dev/null
fi
echo "Generating informers for ${GROUPS_WITH_VERSIONS} at ${OUTPUT_PKG}/informers"
"${GOPATH}/bin/informer-gen" \
--input-dirs "$(codegen::join , "${ALL_FQ_APIS[@]}")" \
--versioned-clientset-package "${OUTPUT_PKG}/${CLIENTSET_PKG_NAME:-clientset}/${CLIENTSET_NAME_VERSIONED:-versioned}" \
--internal-clientset-package "${OUTPUT_PKG}/${CLIENTSET_PKG_NAME:-clientset}/${CLIENTSET_NAME_INTERNAL:-internalversion}" \
"${gobin}/informer-gen" \
--input-dirs "$(codegen::join , "${EXT_FQ_APIS[@]}")" \
--versioned-clientset-package "${OUTPUT_PKG}/${CLIENTSET_PKG}/${CLIENTSET_NAME}" \
--listers-package "${OUTPUT_PKG}/listers" \
--output-package "${OUTPUT_PKG}/informers" \
"$@"
fi
if [ "${GENS}" = "all" ] || grep -qw "openapi" <<<"${GENS}"; then
if grep -qw "openapi" <<<"${GENS}"; then
# Nuke existing files
for dir in $(GO111MODULE=on go list -f '{{.Dir}}' "${FQ_APIS[@]}"); do
pushd "${dir}" >/dev/null
git_find -z ':(glob)**'/zz_generated.openapi.go | xargs -0 rm -f
popd >/dev/null
done
echo "Generating OpenAPI definitions for ${GROUPS_WITH_VERSIONS} at ${OUTPUT_PKG}/openapi"
declare -a OPENAPI_EXTRA_PACKAGES
"${GOPATH}/bin/openapi-gen" \
"${gobin}/openapi-gen" \
--input-dirs "$(codegen::join , "${EXT_FQ_APIS[@]}" "${OPENAPI_EXTRA_PACKAGES[@]+"${OPENAPI_EXTRA_PACKAGES[@]}"}")" \
--input-dirs "k8s.io/apimachinery/pkg/apis/meta/v1,k8s.io/apimachinery/pkg/runtime,k8s.io/apimachinery/pkg/version" \
--output-package "${OUTPUT_PKG}/openapi" \

637
client/vendor/k8s.io/code-generator/kube_codegen.sh generated vendored Normal file
View File

@@ -0,0 +1,637 @@
#!/usr/bin/env bash
# Copyright 2023 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This presents several functions for packages which want to use kubernetes
# code-generation tools.
set -o errexit
set -o nounset
set -o pipefail
KUBE_CODEGEN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
function kube::codegen::internal::git_find() {
# Similar to find but faster and easier to understand. We want to include
# modified and untracked files because this might be running against code
# which is not tracked by git yet.
git ls-files -cmo --exclude-standard "$@"
}
function kube::codegen::internal::git_grep() {
# We want to include modified and untracked files because this might be
# running against code which is not tracked by git yet.
git grep --untracked "$@"
}
# Generate tagged helper code: conversions, deepcopy, and defaults
#
# Args:
# --input-pkg-root <string>
# The root package under which to search for files which request code to be
# generated. This must be Go package syntax, e.g. "k8s.io/foo/bar".
#
# --output-base <string>
# The root directory under which to emit code. The concatenation of
# <output-base> + <input-pkg-root> must be valid.
#
# --boilerplate <string = path_to_kube_codegen_boilerplate>
# An optional override for the header file to insert into generated files.
#
function kube::codegen::gen_helpers() {
local in_pkg_root=""
local out_base="" # gengo needs the output dir must be $out_base/$out_pkg_root
local boilerplate="${KUBE_CODEGEN_ROOT}/hack/boilerplate.go.txt"
local v="${KUBE_VERBOSE:-0}"
while [ "$#" -gt 0 ]; do
case "$1" in
"--input-pkg-root")
in_pkg_root="$2"
shift 2
;;
"--output-base")
out_base="$2"
shift 2
;;
"--boilerplate")
boilerplate="$2"
shift 2
;;
*)
echo "unknown argument: $1" >&2
return 1
;;
esac
done
if [ -z "${in_pkg_root}" ]; then
echo "--input-pkg-root is required" >&2
return 1
fi
if [ -z "${out_base}" ]; then
echo "--output-base is required" >&2
return 1
fi
(
# To support running this from anywhere, first cd into this directory,
# and then install with forced module mode on and fully qualified name.
cd "${KUBE_CODEGEN_ROOT}"
BINS=(
conversion-gen
deepcopy-gen
defaulter-gen
)
# shellcheck disable=2046 # printf word-splitting is intentional
GO111MODULE=on go install $(printf "k8s.io/code-generator/cmd/%s " "${BINS[@]}")
)
# Go installs in $GOBIN if defined, and $GOPATH/bin otherwise
gobin="${GOBIN:-$(go env GOPATH)/bin}"
# These tools all assume out-dir == in-dir.
root="${out_base}/${in_pkg_root}"
mkdir -p "${root}"
root="$(cd "${root}" && pwd -P)"
# Deepcopy
#
local input_pkgs=()
while read -r file; do
dir="$(dirname "${file}")"
pkg="$(cd "${dir}" && GO111MODULE=on go list -find .)"
input_pkgs+=("${pkg}")
done < <(
( kube::codegen::internal::git_grep -l \
-e '+k8s:deepcopy-gen=' \
":(glob)${root}"/'**/*.go' \
|| true \
) | LC_ALL=C sort -u
)
if [ "${#input_pkgs[@]}" != 0 ]; then
echo "Generating deepcopy code for ${#input_pkgs[@]} targets"
kube::codegen::internal::git_find -z \
":(glob)${root}"/'**/zz_generated.deepcopy.go' \
| xargs -0 rm -f
local inputs=()
for arg in "${input_pkgs[@]}"; do
inputs+=("--input-dirs" "$arg")
done
"${gobin}/deepcopy-gen" \
-v "${v}" \
-O zz_generated.deepcopy \
--go-header-file "${boilerplate}" \
--output-base "${out_base}" \
"${inputs[@]}"
fi
# Defaults
#
local input_pkgs=()
while read -r file; do
dir="$(dirname "${file}")"
pkg="$(cd "${dir}" && GO111MODULE=on go list -find .)"
input_pkgs+=("${pkg}")
done < <(
( kube::codegen::internal::git_grep -l \
-e '+k8s:defaulter-gen=' \
":(glob)${root}"/'**/*.go' \
|| true \
) | LC_ALL=C sort -u
)
if [ "${#input_pkgs[@]}" != 0 ]; then
echo "Generating defaulter code for ${#input_pkgs[@]} targets"
kube::codegen::internal::git_find -z \
":(glob)${root}"/'**/zz_generated.defaults.go' \
| xargs -0 rm -f
local inputs=()
for arg in "${input_pkgs[@]}"; do
inputs+=("--input-dirs" "$arg")
done
"${gobin}/defaulter-gen" \
-v "${v}" \
-O zz_generated.defaults \
--go-header-file "${boilerplate}" \
--output-base "${out_base}" \
"${inputs[@]}"
fi
# Conversions
#
local input_pkgs=()
while read -r file; do
dir="$(dirname "${file}")"
pkg="$(cd "${dir}" && GO111MODULE=on go list -find .)"
input_pkgs+=("${pkg}")
done < <(
( kube::codegen::internal::git_grep -l \
-e '+k8s:conversion-gen=' \
":(glob)${root}"/'**/*.go' \
|| true \
) | LC_ALL=C sort -u
)
if [ "${#input_pkgs[@]}" != 0 ]; then
echo "Generating conversion code for ${#input_pkgs[@]} targets"
kube::codegen::internal::git_find -z \
":(glob)${root}"/'**/zz_generated.conversion.go' \
| xargs -0 rm -f
local inputs=()
for arg in "${input_pkgs[@]}"; do
inputs+=("--input-dirs" "$arg")
done
"${gobin}/conversion-gen" \
-v "${v}" \
-O zz_generated.conversion \
--go-header-file "${boilerplate}" \
--output-base "${out_base}" \
"${inputs[@]}"
fi
}
# Generate openapi code
#
# Args:
# --input-pkg-root <string>
# The root package under which to search for files which request openapi to
# be generated. This must be Go package syntax, e.g. "k8s.io/foo/bar".
#
# --output-pkg-root <string>
# The root package under which generated directories and files
# will be placed. This must be go package syntax, e.g. "k8s.io/foo/bar".
#
# --output-base <string>
# The root directory under which to emit code. The concatenation of
# <output-base> + <input-pkg-root> must be valid.
#
# --openapi-name <string = "openapi">
# An optional override for the leaf name of the generated directory.
#
# --extra-pkgs <string>
# An optional list of additional packages to be imported during openapi
# generation. The argument must be Go package syntax, e.g.
# "k8s.io/foo/bar". It may be a single value or a comma-delimited list.
# This flag may be repeated.
#
# --report-filename <string = "/dev/null">
# An optional path at which to write an API violations report. "-" means
# stdout.
#
# --update-report
# If specified, update the report file in place, rather than diffing it.
#
# --boilerplate <string = path_to_kube_codegen_boilerplate>
# An optional override for the header file to insert into generated files.
#
function kube::codegen::gen_openapi() {
local in_pkg_root=""
local out_pkg_root=""
local out_base="" # gengo needs the output dir must be $out_base/$out_pkg_root
local openapi_subdir="openapi"
local extra_pkgs=()
local report="/dev/null"
local update_report=""
local boilerplate="${KUBE_CODEGEN_ROOT}/hack/boilerplate.go.txt"
local v="${KUBE_VERBOSE:-0}"
while [ "$#" -gt 0 ]; do
case "$1" in
"--input-pkg-root")
in_pkg_root="$2"
shift 2
;;
"--output-pkg-root")
out_pkg_root="$2"
shift 2
;;
"--output-base")
out_base="$2"
shift 2
;;
"--openapi-name")
openapi_subdir="$2"
shift 2
;;
"--extra-pkgs")
extra_pkgs+=("$2")
shift 2
;;
"--report-filename")
report="$2"
shift 2
;;
"--update-report")
update_report="true"
shift
;;
"--boilerplate")
boilerplate="$2"
shift 2
;;
*)
echo "unknown argument: $1" >&2
return 1
;;
esac
done
if [ -z "${in_pkg_root}" ]; then
echo "--input-pkg-root is required" >&2
return 1
fi
if [ -z "${out_pkg_root}" ]; then
echo "--output-pkg-root is required" >&2
return 1
fi
if [ -z "${out_base}" ]; then
echo "--output-base is required" >&2
return 1
fi
local new_report
new_report="$(mktemp -t "$(basename "$0").api_violations.XXXXXX")"
if [ -n "${update_report}" ]; then
new_report="${report}"
fi
(
# To support running this from anywhere, first cd into this directory,
# and then install with forced module mode on and fully qualified name.
cd "${KUBE_CODEGEN_ROOT}"
BINS=(
openapi-gen
)
# shellcheck disable=2046 # printf word-splitting is intentional
GO111MODULE=on go install $(printf "k8s.io/code-generator/cmd/%s " "${BINS[@]}")
)
# Go installs in $GOBIN if defined, and $GOPATH/bin otherwise
gobin="${GOBIN:-$(go env GOPATH)/bin}"
# These tools all assume out-dir == in-dir.
root="${out_base}/${in_pkg_root}"
mkdir -p "${root}"
root="$(cd "${root}" && pwd -P)"
local input_pkgs=( "${extra_pkgs[@]:+"${extra_pkgs[@]}"}")
while read -r file; do
dir="$(dirname "${file}")"
pkg="$(cd "${dir}" && GO111MODULE=on go list -find .)"
input_pkgs+=("${pkg}")
done < <(
( kube::codegen::internal::git_grep -l \
-e '+k8s:openapi-gen=' \
":(glob)${root}"/'**/*.go' \
|| true \
) | LC_ALL=C sort -u
)
if [ "${#input_pkgs[@]}" != 0 ]; then
echo "Generating openapi code for ${#input_pkgs[@]} targets"
kube::codegen::internal::git_find -z \
":(glob)${root}"/'**/zz_generated.openapi.go' \
| xargs -0 rm -f
local inputs=()
for arg in "${input_pkgs[@]}"; do
inputs+=("--input-dirs" "$arg")
done
"${gobin}/openapi-gen" \
-v "${v}" \
-O zz_generated.openapi \
--go-header-file "${boilerplate}" \
--output-base "${out_base}" \
--output-package "${out_pkg_root}/${openapi_subdir}" \
--report-filename "${new_report}" \
--input-dirs "k8s.io/apimachinery/pkg/apis/meta/v1" \
--input-dirs "k8s.io/apimachinery/pkg/runtime" \
--input-dirs "k8s.io/apimachinery/pkg/version" \
"${inputs[@]}"
fi
touch "${report}" # in case it doesn't exist yet
if ! diff -u "${report}" "${new_report}"; then
echo -e "ERROR:"
echo -e "\tAPI rule check failed for ${report}: new reported violations"
echo -e "\tPlease read api/api-rules/README.md"
return 1
fi
}
# Generate client code
#
# Args:
# --input-pkg-root <string>
# The root package under which to search for types.go files which request
# clients to be generated. This must be Go package syntax, e.g.
# "k8s.io/foo/bar".
#
# --output-pkg-root <string>
# The root package into which generated directories and files will be
# placed. This must be Go package syntax, e.g. "k8s.io/foo/bar".
#
# --output-base <string>
# The root directory under which to emit code. The concatenation of
# <output-base> + <output-pkg-root> must be valid.
#
# --boilerplate <string = path_to_kube_codegen_boilerplate>
# An optional override for the header file to insert into generated files.
#
# --clientset-name <string = "clientset">
# An optional override for the leaf name of the generated "clientset" directory.
#
# --versioned-name <string = "versioned">
# An optional override for the leaf name of the generated
# "<clientset>/versioned" directory.
#
# --with-applyconfig
# Enables generation of applyconfiguration files.
#
# --applyconfig-name <string = "applyconfiguration">
# An optional override for the leaf name of the generated "applyconfiguration" directory.
#
# --with-watch
# Enables generation of listers and informers for APIs which support WATCH.
#
# --listers-name <string = "listers">
# An optional override for the leaf name of the generated "listers" directory.
#
# --informers-name <string = "informers">
# An optional override for the leaf name of the generated "informers" directory.
#
function kube::codegen::gen_client() {
local in_pkg_root=""
local out_pkg_root=""
local out_base="" # gengo needs the output dir must be $out_base/$out_pkg_root
local clientset_subdir="clientset"
local clientset_versioned_name="versioned"
local applyconfig="false"
local applyconfig_subdir="applyconfiguration"
local watchable="false"
local listers_subdir="listers"
local informers_subdir="informers"
local boilerplate="${KUBE_CODEGEN_ROOT}/hack/boilerplate.go.txt"
local v="${KUBE_VERBOSE:-0}"
while [ "$#" -gt 0 ]; do
case "$1" in
"--input-pkg-root")
in_pkg_root="$2"
shift 2
;;
"--output-pkg-root")
out_pkg_root="$2"
shift 2
;;
"--output-base")
out_base="$2"
shift 2
;;
"--boilerplate")
boilerplate="$2"
shift 2
;;
"--clientset-name")
clientset_subdir="$2"
shift 2
;;
"--versioned-name")
clientset_versioned_name="$2"
shift 2
;;
"--with-applyconfig")
applyconfig="true"
shift
;;
"--applyconfig-name")
applyconfig_subdir="$2"
shift 2
;;
"--with-watch")
watchable="true"
shift
;;
"--listers-name")
listers_subdir="$2"
shift 2
;;
"--informers-name")
informers_subdir="$2"
shift 2
;;
*)
echo "unknown argument: $1" >&2
return 1
;;
esac
done
if [ -z "${in_pkg_root}" ]; then
echo "--input-pkg-root is required" >&2
return 1
fi
if [ -z "${out_pkg_root}" ]; then
echo "--output-pkg-root is required" >&2
return 1
fi
if [ -z "${out_base}" ]; then
echo "--output-base is required" >&2
return 1
fi
(
# To support running this from anywhere, first cd into this directory,
# and then install with forced module mode on and fully qualified name.
cd "${KUBE_CODEGEN_ROOT}"
BINS=(
applyconfiguration-gen
client-gen
informer-gen
lister-gen
)
# shellcheck disable=2046 # printf word-splitting is intentional
GO111MODULE=on go install $(printf "k8s.io/code-generator/cmd/%s " "${BINS[@]}")
)
# Go installs in $GOBIN if defined, and $GOPATH/bin otherwise
gobin="${GOBIN:-$(go env GOPATH)/bin}"
in_root="${out_base}/${in_pkg_root}"
mkdir -p "${in_root}"
in_root="$(cd "${in_root}" && pwd -P)"
out_root="${out_base}/${out_pkg_root}"
mkdir -p "${out_root}"
out_root="$(cd "${out_root}" && pwd -P)"
local group_versions=()
local input_pkgs=()
while read -r file; do
dir="$(dirname "${file}")"
pkg="$(cd "${dir}" && GO111MODULE=on go list -find .)"
leaf="$(basename "${dir}")"
if grep -E -q '^v[0-9]+((alpha|beta)[0-9]+)?$' <<< "${leaf}"; then
input_pkgs+=("${pkg}")
dir2="$(dirname "${dir}")"
leaf2="$(basename "${dir2}")"
group_versions+=("${leaf2}/${leaf}")
fi
done < <(
( kube::codegen::internal::git_grep -l \
-e '+genclient' \
":(glob)${in_root}"/'**/types.go' \
|| true \
) | LC_ALL=C sort -u
)
if [ "${#group_versions[@]}" == 0 ]; then
return 0
fi
applyconfig_pkg="" # set this for later use, iff enabled
if [ "${applyconfig}" == "true" ]; then
applyconfig_pkg="${out_pkg_root}/${applyconfig_subdir}"
echo "Generating applyconfig code for ${#input_pkgs[@]} targets"
( kube::codegen::internal::git_grep -l --null \
-e '^// Code generated by applyconfiguration-gen. DO NOT EDIT.$' \
":(glob)${out_root}/${applyconfig_subdir}"/'**/*.go' \
|| true \
) | xargs -0 rm -f
local inputs=()
for arg in "${input_pkgs[@]}"; do
inputs+=("--input-dirs" "$arg")
done
"${gobin}/applyconfiguration-gen" \
-v "${v}" \
--go-header-file "${boilerplate}" \
--output-base "${out_base}" \
--output-package "${out_pkg_root}/${applyconfig_subdir}" \
"${inputs[@]}"
fi
echo "Generating client code for ${#group_versions[@]} targets"
( kube::codegen::internal::git_grep -l --null \
-e '^// Code generated by client-gen. DO NOT EDIT.$' \
":(glob)${out_root}/${clientset_subdir}"/'**/*.go' \
|| true \
) | xargs -0 rm -f
local inputs=()
for arg in "${group_versions[@]}"; do
inputs+=("--input" "$arg")
done
"${gobin}/client-gen" \
-v "${v}" \
--go-header-file "${boilerplate}" \
--clientset-name "${clientset_versioned_name}" \
--input-base "${in_pkg_root}" \
--output-base "${out_base}" \
--output-package "${out_pkg_root}/${clientset_subdir}" \
--apply-configuration-package "${applyconfig_pkg}" \
"${inputs[@]}"
if [ "${watchable}" == "true" ]; then
echo "Generating lister code for ${#input_pkgs[@]} targets"
( kube::codegen::internal::git_grep -l --null \
-e '^// Code generated by lister-gen. DO NOT EDIT.$' \
":(glob)${out_root}/${listers_subdir}"/'**/*.go' \
|| true \
) | xargs -0 rm -f
local inputs=()
for arg in "${input_pkgs[@]}"; do
inputs+=("--input-dirs" "$arg")
done
"${gobin}/lister-gen" \
-v "${v}" \
--go-header-file "${boilerplate}" \
--output-base "${out_base}" \
--output-package "${out_pkg_root}/${listers_subdir}" \
"${inputs[@]}"
echo "Generating informer code for ${#input_pkgs[@]} targets"
( kube::codegen::internal::git_grep -l --null \
-e '^// Code generated by informer-gen. DO NOT EDIT.$' \
":(glob)${out_root}/${informers_subdir}"/'**/*.go' \
|| true \
) | xargs -0 rm -f
local inputs=()
for arg in "${input_pkgs[@]}"; do
inputs+=("--input-dirs" "$arg")
done
"${gobin}/informer-gen" \
-v "${v}" \
--go-header-file "${boilerplate}" \
--output-base "${out_base}" \
--output-package "${out_pkg_root}/${informers_subdir}" \
--versioned-clientset-package "${out_pkg_root}/${clientset_subdir}/${clientset_versioned_name}" \
--listers-package "${out_pkg_root}/${listers_subdir}" \
"${inputs[@]}"
fi
}

View File

@@ -22,6 +22,7 @@ limitations under the License.
package codegenerator
import (
_ "k8s.io/code-generator/cmd/applyconfiguration-gen"
_ "k8s.io/code-generator/cmd/client-gen"
_ "k8s.io/code-generator/cmd/conversion-gen"
_ "k8s.io/code-generator/cmd/deepcopy-gen"