Support references to external types for 3rd party use
This commit is contained in:
		| @@ -20,20 +20,51 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"path" | 	"path" | ||||||
|  |  | ||||||
|  | 	"github.com/spf13/pflag" | ||||||
| 	"k8s.io/gengo/args" | 	"k8s.io/gengo/args" | ||||||
|  | 	"k8s.io/gengo/types" | ||||||
|  |  | ||||||
| 	codegenutil "k8s.io/code-generator/pkg/util" | 	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. | // NewDefaults returns default arguments for the generator. | ||||||
| func NewDefaults() *args.GeneratorArgs { | func NewDefaults() (*args.GeneratorArgs, *CustomArgs) { | ||||||
| 	genericArgs := args.Default().WithoutDefaultFlagParsing() | 	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 { | 	if pkg := codegenutil.CurrentPackage(); len(pkg) != 0 { | ||||||
| 		genericArgs.OutputPackagePath = path.Join(pkg, "pkg/client/applyconfigurations") | 		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. | // Validate checks the given arguments. | ||||||
|   | |||||||
| @@ -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 | ||||||
|  | } | ||||||
| @@ -28,6 +28,7 @@ import ( | |||||||
| 	"k8s.io/gengo/types" | 	"k8s.io/gengo/types" | ||||||
| 	"k8s.io/klog/v2" | 	"k8s.io/klog/v2" | ||||||
|  |  | ||||||
|  | 	applygenargs "k8s.io/code-generator/cmd/applyconfiguration-gen/args" | ||||||
| 	clientgentypes "k8s.io/code-generator/cmd/client-gen/types" | 	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) | 	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) | 	groupVersions := make(map[string]clientgentypes.GroupVersions) | ||||||
| 	groupGoNames := make(map[string]string) | 	groupGoNames := make(map[string]string) | ||||||
|   | |||||||
| @@ -28,8 +28,8 @@ type refGraph map[types.Name]string | |||||||
|  |  | ||||||
| // refGraphForReachableTypes returns a refGraph that contains all reachable types from | // refGraphForReachableTypes returns a refGraph that contains all reachable types from | ||||||
| // the root clientgen types of the provided packages. | // the root clientgen types of the provided packages. | ||||||
| func refGraphForReachableTypes(pkgTypes map[string]*types.Package) refGraph { | func refGraphForReachableTypes(pkgTypes map[string]*types.Package, initialTypes map[types.Name]string) refGraph { | ||||||
| 	refs := refGraph{} | 	var refs refGraph = initialTypes | ||||||
|  |  | ||||||
| 	// Include only types that are reachable from the root clientgen types. | 	// 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 | 	// We don't want to generate apply configurations for types that are not reachable from a root | ||||||
|   | |||||||
| @@ -32,9 +32,10 @@ import ( | |||||||
|  |  | ||||||
| func main() { | func main() { | ||||||
| 	klog.InitFlags(nil) | 	klog.InitFlags(nil) | ||||||
| 	genericArgs := generatorargs.NewDefaults() | 	genericArgs, customArgs := generatorargs.NewDefaults() | ||||||
| 	genericArgs.GoHeaderFilePath = filepath.Join(args.DefaultSourceTree(), util.BoilerplatePath()) | 	genericArgs.GoHeaderFilePath = filepath.Join(args.DefaultSourceTree(), util.BoilerplatePath()) | ||||||
| 	genericArgs.AddFlags(pflag.CommandLine) | 	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 { | 	if err := flag.Set("logtostderr", "true"); err != nil { | ||||||
| 		klog.Fatalf("Error: %v", err) | 		klog.Fatalf("Error: %v", err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Joe Betz
					Joe Betz