Merge pull request #111934 from deads2k/apply-gen

make applyconfiguration-gen work in non-kube repositiories
This commit is contained in:
Kubernetes Prow Robot 2022-08-26 19:26:51 -07:00 committed by GitHub
commit 5f57708c88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 67 additions and 21 deletions

View File

@ -50,8 +50,9 @@ func NewDefaults() (*args.GeneratorArgs, *CustomArgs) {
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: "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

View File

@ -28,7 +28,6 @@ import (
type externalApplyConfigurationValue struct {
externals *map[types.Name]string
changed bool
}
func NewExternalApplyConfigurationValue(externals *map[types.Name]string, def []string) *externalApplyConfigurationValue {
@ -45,10 +44,6 @@ func NewExternalApplyConfigurationValue(externals *map[types.Name]string, def []
var _ flag.Value = &externalApplyConfigurationValue{}
func (s *externalApplyConfigurationValue) set(vs []string) error {
if !s.changed {
*s.externals = map[types.Name]string{}
}
for _, input := range vs {
typ, pkg, err := parseExternalMapping(input)
if err != nil {
@ -71,6 +66,7 @@ func (s *externalApplyConfigurationValue) Set(val string) error {
if err := s.set(vs); err != nil {
return err
}
return nil
}
@ -114,12 +110,13 @@ func parseExternalMapping(mapping string) (typ types.Name, pkg string, err error
}
packageTypeStr := parts[0]
pkg = parts[1]
ptParts := strings.Split(packageTypeStr, ".")
if len(ptParts) != 2 {
return types.Name{}, "", fmt.Errorf("expected package and type of the form <package>#<typeName> but got %s", packageTypeStr)
// 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 := ptParts[0]
structType := ptParts[1]
structPkg := packageTypeStr[:lastDot]
structType := packageTypeStr[lastDot+1:]
return types.Name{Package: structPkg, Name: structType}, pkg, nil
}

View File

@ -118,7 +118,7 @@ func (g *applyConfigurationGenerator) GenerateType(c *generator.Context, t *type
func hasTypeMetaField(t *types.Type) bool {
for _, member := range t.Members {
if typeMeta.Name == member.Type.Name {
if typeMeta.Name == member.Type.Name && member.Embedded {
return true
}
}
@ -157,7 +157,6 @@ func (g *applyConfigurationGenerator) generateWithFuncs(t *types.Type, typeParam
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
@ -165,12 +164,13 @@ func (g *applyConfigurationGenerator) generateWithFuncs(t *types.Type, typeParam
}
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, memberParams)
g.generateMemberWithForSlice(sw, member, memberParams)
continue
}
// Note: There are no maps where the values are generated apply configurations (because
@ -182,7 +182,7 @@ func (g *applyConfigurationGenerator) generateWithFuncs(t *types.Type, typeParam
switch memberParams.Member.Type.Kind {
case types.Slice:
memberParams.ArgType = memberType.Elem
g.generateMemberWithForSlice(sw, memberParams)
g.generateMemberWithForSlice(sw, member, memberParams)
case types.Map:
g.generateMemberWithForMap(sw, memberParams)
default:
@ -252,20 +252,39 @@ func (g *applyConfigurationGenerator) generateMemberWith(sw *generator.SnippetWr
sw.Do("}\n", memberParams)
}
func (g *applyConfigurationGenerator) generateMemberWithForSlice(sw *generator.SnippetWriter, memberParams 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)
sw.Do("b.$.Member.Name$ = append(b.$.Member.Name$, *values[i])\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 {
sw.Do("b.$.Member.Name$ = append(b.$.Member.Name$, values[i])\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)
}
}
sw.Do(" }\n", memberParams)
sw.Do(" return b\n", memberParams)
@ -305,6 +324,14 @@ func (b *$.ApplyConfig.ApplyConfiguration|public$) ensure$.MemberType.Elem|publi
}
`
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.

View File

@ -30,6 +30,7 @@ import (
"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"
)
@ -81,6 +82,13 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat
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,
@ -236,8 +244,12 @@ func packageTypesForInputDirs(context *generator.Context, inputDirs []string, ou
klog.Warningf("Skipping internal package: %s", p.Path)
continue
}
gv := groupVersion(p)
pkg := filepath.Join(outputPath, gv.Group.PackageName(), strings.ToLower(gv.Version.NonEmpty()))
// 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
@ -274,3 +286,12 @@ 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
}