Support references to external types for 3rd party use
This commit is contained in:
		| @@ -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. | ||||
|   | ||||
| @@ -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/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) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
| 	} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Joe Betz
					Joe Betz