Support references to external types for 3rd party use

This commit is contained in:
Joe Betz 2021-02-25 13:04:44 -08:00
parent c541c69331
commit 86184be5f1
5 changed files with 165 additions and 6 deletions

View File

@ -20,20 +20,51 @@ import (
"fmt"
"path"
"github.com/spf13/pflag"
"k8s.io/gengo/args"
"k8s.io/gengo/types"
codegenutil "k8s.io/code-generator/pkg/util"
)
// ClientGenArgs 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
}
// NewDefaults returns default arguments for the generator.
func NewDefaults() *args.GeneratorArgs {
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",
},
}
genericArgs.CustomArgs = customArgs
if pkg := codegenutil.CurrentPackage(); len(pkg) != 0 {
genericArgs.OutputPackagePath = path.Join(pkg, "pkg/client/applyconfigurations")
}
return genericArgs
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")
}
// Validate checks the given arguments.

View File

@ -0,0 +1,125 @@
/*
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
changed bool
}
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 {
if !s.changed {
*s.externals = map[types.Name]string{}
}
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]
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)
}
structPkg := ptParts[0]
structType := ptParts[1]
return types.Name{Package: structPkg, Name: structType}, pkg, nil
}

View File

@ -28,6 +28,7 @@ import (
"k8s.io/gengo/types"
"k8s.io/klog/v2"
applygenargs "k8s.io/code-generator/cmd/applyconfiguration-gen/args"
clientgentypes "k8s.io/code-generator/cmd/client-gen/types"
)
@ -59,7 +60,8 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat
}
pkgTypes := packageTypesForInputDirs(context, arguments.InputDirs, arguments.OutputPackagePath)
refs := refGraphForReachableTypes(pkgTypes)
initialTypes := arguments.CustomArgs.(*applygenargs.CustomArgs).ExternalApplyConfigurations
refs := refGraphForReachableTypes(pkgTypes, initialTypes)
groupVersions := make(map[string]clientgentypes.GroupVersions)
groupGoNames := make(map[string]string)

View File

@ -28,8 +28,8 @@ 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(pkgTypes map[string]*types.Package) refGraph {
refs := refGraph{}
func refGraphForReachableTypes(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

View File

@ -32,9 +32,10 @@ import (
func main() {
klog.InitFlags(nil)
genericArgs := generatorargs.NewDefaults()
genericArgs, customArgs := generatorargs.NewDefaults()
genericArgs.GoHeaderFilePath = filepath.Join(args.DefaultSourceTree(), util.BoilerplatePath())
genericArgs.AddFlags(pflag.CommandLine)
customArgs.AddFlags(pflag.CommandLine, "k8s.io/kubernetes/pkg/apis") // TODO: move this input path out of client-gen
if err := flag.Set("logtostderr", "true"); err != nil {
klog.Fatalf("Error: %v", err)
}