mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 12:43:23 +00:00
Add apply configuration generator
This commit is contained in:
parent
60a714058b
commit
09cc895c84
@ -29,10 +29,12 @@ kube::golang::setup_env
|
|||||||
go install k8s.io/kubernetes/vendor/k8s.io/code-generator/cmd/client-gen
|
go install k8s.io/kubernetes/vendor/k8s.io/code-generator/cmd/client-gen
|
||||||
go install k8s.io/kubernetes/vendor/k8s.io/code-generator/cmd/lister-gen
|
go install k8s.io/kubernetes/vendor/k8s.io/code-generator/cmd/lister-gen
|
||||||
go install k8s.io/kubernetes/vendor/k8s.io/code-generator/cmd/informer-gen
|
go install k8s.io/kubernetes/vendor/k8s.io/code-generator/cmd/informer-gen
|
||||||
|
go install k8s.io/kubernetes/vendor/k8s.io/code-generator/cmd/applyconfiguration-gen
|
||||||
|
|
||||||
clientgen=$(kube::util::find-binary "client-gen")
|
clientgen=$(kube::util::find-binary "client-gen")
|
||||||
listergen=$(kube::util::find-binary "lister-gen")
|
listergen=$(kube::util::find-binary "lister-gen")
|
||||||
informergen=$(kube::util::find-binary "informer-gen")
|
informergen=$(kube::util::find-binary "informer-gen")
|
||||||
|
applyconfigurationgen=$(kube::util::find-binary "applyconfiguration-gen")
|
||||||
|
|
||||||
IFS=" " read -r -a GROUP_VERSIONS <<< "${KUBE_AVAILABLE_GROUP_VERSIONS}"
|
IFS=" " read -r -a GROUP_VERSIONS <<< "${KUBE_AVAILABLE_GROUP_VERSIONS}"
|
||||||
GV_DIRS=()
|
GV_DIRS=()
|
||||||
@ -54,6 +56,22 @@ done
|
|||||||
# delimit by commas for the command
|
# delimit by commas for the command
|
||||||
GV_DIRS_CSV=$(IFS=',';echo "${GV_DIRS[*]// /,}";IFS=$)
|
GV_DIRS_CSV=$(IFS=',';echo "${GV_DIRS[*]// /,}";IFS=$)
|
||||||
|
|
||||||
|
applyconfigurationgen_external_apis=()
|
||||||
|
# because client-gen doesn't do policy/v1alpha1, we have to skip it too
|
||||||
|
kube::util::read-array applyconfigurationgen_external_apis < <(
|
||||||
|
cd "${KUBE_ROOT}/staging/src"
|
||||||
|
find k8s.io/api -name types.go -print0 | xargs -0 -n1 dirname | sort | grep -v pkg.apis.policy.v1alpha1
|
||||||
|
)
|
||||||
|
applyconfigurationgen_external_apis+=("k8s.io/apimachinery/pkg/apis/meta/v1")
|
||||||
|
applyconfigurationgen_external_apis_csv=$(IFS=,; echo "${applyconfigurationgen_external_apis[*]}")
|
||||||
|
applyconfigurations_package="k8s.io/client-go/applyconfigurations"
|
||||||
|
${applyconfigurationgen} \
|
||||||
|
--output-base "${KUBE_ROOT}/vendor" \
|
||||||
|
--output-package "${applyconfigurations_package}" \
|
||||||
|
--input-dirs "${applyconfigurationgen_external_apis_csv}" \
|
||||||
|
--go-header-file "${KUBE_ROOT}/hack/boilerplate/boilerplate.generatego.txt" \
|
||||||
|
"$@"
|
||||||
|
|
||||||
# This can be called with one flag, --verify-only, so it works for both the
|
# This can be called with one flag, --verify-only, so it works for both the
|
||||||
# update- and verify- scripts.
|
# update- and verify- scripts.
|
||||||
${clientgen} --output-base "${KUBE_ROOT}/vendor" --output-package="k8s.io/client-go" --clientset-name="kubernetes" --input-base="k8s.io/api" --input="${GV_DIRS_CSV}" --go-header-file "${KUBE_ROOT}/hack/boilerplate/boilerplate.generatego.txt" "$@"
|
${clientgen} --output-base "${KUBE_ROOT}/vendor" --output-package="k8s.io/client-go" --clientset-name="kubernetes" --input-base="k8s.io/api" --input="${GV_DIRS_CSV}" --go-header-file "${KUBE_ROOT}/hack/boilerplate/boilerplate.generatego.txt" "$@"
|
||||||
|
241
pkg/api/testing/applyconfiguration_test.go
Normal file
241
pkg/api/testing/applyconfiguration_test.go
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
/*
|
||||||
|
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 testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
fuzz "github.com/google/gofuzz"
|
||||||
|
"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
|
||||||
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/util/json"
|
||||||
|
"k8s.io/client-go/applyconfigurations"
|
||||||
|
v1mf "k8s.io/client-go/applyconfigurations/core/v1"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestUnstructuredRoundTripApplyConfigurations converts each known object type through unstructured
|
||||||
|
// to the apply configuration for that object type, then converts it back to the object type and
|
||||||
|
// verifies it is unchanged.
|
||||||
|
func TestUnstructuredRoundTripApplyConfigurations(t *testing.T) {
|
||||||
|
for gvk := range legacyscheme.Scheme.AllKnownTypes() {
|
||||||
|
if nonRoundTrippableTypes.Has(gvk.Kind) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if gvk.Version == runtime.APIVersionInternal {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if builder := applyconfigurations.ForKind(gvk); builder == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(gvk.String(), func(t *testing.T) {
|
||||||
|
for i := 0; i < 50; i++ {
|
||||||
|
item := fuzzObject(t, gvk)
|
||||||
|
builder := applyconfigurations.ForKind(gvk)
|
||||||
|
unstructuredRoundTripApplyConfiguration(t, item, builder)
|
||||||
|
if t.Failed() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestJsonRoundTripApplyConfigurations converts each known object type through JSON to the apply
|
||||||
|
// configuration for that object type, then converts it back to the object type and verifies it
|
||||||
|
// is unchanged.
|
||||||
|
func TestJsonRoundTripApplyConfigurations(t *testing.T) {
|
||||||
|
for gvk := range legacyscheme.Scheme.AllKnownTypes() {
|
||||||
|
if nonRoundTrippableTypes.Has(gvk.Kind) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if gvk.Version == runtime.APIVersionInternal {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if builder := applyconfigurations.ForKind(gvk); builder == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(gvk.String(), func(t *testing.T) {
|
||||||
|
for i := 0; i < 50; i++ {
|
||||||
|
item := fuzzObject(t, gvk)
|
||||||
|
builder := applyconfigurations.ForKind(gvk)
|
||||||
|
jsonRoundTripApplyConfiguration(t, item, builder)
|
||||||
|
if t.Failed() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unstructuredRoundTripApplyConfiguration(t *testing.T, item runtime.Object, applyConfig interface{}) {
|
||||||
|
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(item)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ToUnstructured failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = runtime.DefaultUnstructuredConverter.FromUnstructured(u, applyConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("FromUnstructured failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rtObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
|
||||||
|
u, err = runtime.DefaultUnstructuredConverter.ToUnstructured(applyConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ToUnstructured failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = runtime.DefaultUnstructuredConverter.FromUnstructured(u, rtObj)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("FromUnstructured failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !apiequality.Semantic.DeepEqual(item, rtObj) {
|
||||||
|
t.Errorf("Object changed, diff: %v", cmp.Diff(item, rtObj))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsonRoundTripApplyConfiguration(t *testing.T, item runtime.Object, applyConfig interface{}) {
|
||||||
|
|
||||||
|
objData, err := json.Marshal(item)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("json.Marshal failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(objData, applyConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("applyConfig.UnmarshalJSON failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rtObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
|
||||||
|
applyData, err := json.Marshal(applyConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("applyConfig.MarshalJSON failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(applyData, rtObj)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("json.Unmarshal failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !apiequality.Semantic.DeepEqual(item, rtObj) {
|
||||||
|
t.Errorf("Object changed, diff: %v", cmp.Diff(item, rtObj))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fuzzObject(t *testing.T, gvk schema.GroupVersionKind) runtime.Object {
|
||||||
|
internalVersion := schema.GroupVersion{Group: gvk.Group, Version: runtime.APIVersionInternal}
|
||||||
|
externalVersion := gvk.GroupVersion()
|
||||||
|
kind := gvk.Kind
|
||||||
|
|
||||||
|
// We do fuzzing on the internal version of the object, and only then
|
||||||
|
// convert to the external version. This is because custom fuzzing
|
||||||
|
// function are only supported for internal objects.
|
||||||
|
internalObj, err := legacyscheme.Scheme.New(internalVersion.WithKind(kind))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Couldn't create internal object %v: %v", kind, err)
|
||||||
|
}
|
||||||
|
seed := rand.Int63()
|
||||||
|
fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(seed), legacyscheme.Codecs).
|
||||||
|
Funcs(
|
||||||
|
// Ensure that InitContainers and their statuses are not generated. This
|
||||||
|
// is because in this test we are simply doing json operations, in which
|
||||||
|
// those disappear.
|
||||||
|
func(s *api.PodSpec, c fuzz.Continue) {
|
||||||
|
c.FuzzNoCustom(s)
|
||||||
|
s.InitContainers = nil
|
||||||
|
},
|
||||||
|
func(s *api.PodStatus, c fuzz.Continue) {
|
||||||
|
c.FuzzNoCustom(s)
|
||||||
|
s.InitContainerStatuses = nil
|
||||||
|
},
|
||||||
|
// Apply configuration types do not have managed fields, so we exclude
|
||||||
|
// them in our fuzz test cases.
|
||||||
|
func(s *v1.ObjectMeta, c fuzz.Continue) {
|
||||||
|
c.FuzzNoCustom(s)
|
||||||
|
s.ManagedFields = nil
|
||||||
|
},
|
||||||
|
).Fuzz(internalObj)
|
||||||
|
|
||||||
|
item, err := legacyscheme.Scheme.New(externalVersion.WithKind(kind))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Couldn't create external object %v: %v", kind, err)
|
||||||
|
}
|
||||||
|
if err := legacyscheme.Scheme.Convert(internalObj, item, nil); err != nil {
|
||||||
|
t.Fatalf("Conversion for %v failed: %v", kind, err)
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkApplyConfigurationsFromUnstructured(b *testing.B) {
|
||||||
|
items := benchmarkItems(b)
|
||||||
|
convertor := runtime.DefaultUnstructuredConverter
|
||||||
|
unstr := make([]map[string]interface{}, len(items))
|
||||||
|
for i := range items {
|
||||||
|
item, err := convertor.ToUnstructured(&items[i])
|
||||||
|
if err != nil || item == nil {
|
||||||
|
b.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
unstr = append(unstr, item)
|
||||||
|
}
|
||||||
|
size := len(items)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
builder := &v1mf.PodApplyConfiguration{}
|
||||||
|
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstr[i%size], builder); err != nil {
|
||||||
|
b.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.StopTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkApplyConfigurationsToUnstructured(b *testing.B) {
|
||||||
|
items := benchmarkItems(b)
|
||||||
|
convertor := runtime.DefaultUnstructuredConverter
|
||||||
|
builders := make([]*v1mf.PodApplyConfiguration, len(items))
|
||||||
|
for i := range items {
|
||||||
|
item, err := convertor.ToUnstructured(&items[i])
|
||||||
|
if err != nil || item == nil {
|
||||||
|
b.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
builder := &v1mf.PodApplyConfiguration{}
|
||||||
|
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(item, builder); err != nil {
|
||||||
|
b.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
builders[i] = builder
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
size := len(items)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
builder := builders[i%size]
|
||||||
|
if _, err := runtime.DefaultUnstructuredConverter.ToUnstructured(builder); err != nil {
|
||||||
|
b.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.StopTimer()
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
"k8s.io/gengo/args"
|
||||||
|
|
||||||
|
codegenutil "k8s.io/code-generator/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewDefaults returns default arguments for the generator.
|
||||||
|
func NewDefaults() *args.GeneratorArgs {
|
||||||
|
genericArgs := args.Default().WithoutDefaultFlagParsing()
|
||||||
|
|
||||||
|
if pkg := codegenutil.CurrentPackage(); len(pkg) != 0 {
|
||||||
|
genericArgs.OutputPackagePath = path.Join(pkg, "pkg/client/applyconfigurations")
|
||||||
|
}
|
||||||
|
|
||||||
|
return genericArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
@ -0,0 +1,308 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
|
||||||
|
g.generateStruct(sw, typeParams)
|
||||||
|
|
||||||
|
if typeParams.Tags.GenerateClient {
|
||||||
|
if typeParams.Tags.NonNamespaced {
|
||||||
|
sw.Do(clientgenTypeConstructorNonNamespaced, typeParams)
|
||||||
|
} else {
|
||||||
|
sw.Do(clientgenTypeConstructorNamespaced, typeParams)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sw.Do(constructor, typeParams)
|
||||||
|
}
|
||||||
|
g.generateWithFuncs(t, typeParams, sw, nil)
|
||||||
|
return sw.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func blocklisted(t *types.Type, member types.Member) bool {
|
||||||
|
if objectMeta.Name == t.Name && member.Name == "ManagedFields" {
|
||||||
|
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, 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, 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, memberParams 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)
|
||||||
|
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)
|
||||||
|
} 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 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 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${}
|
||||||
|
}
|
||||||
|
`
|
@ -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
|
||||||
|
}
|
@ -0,0 +1,215 @@
|
|||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"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"
|
||||||
|
|
||||||
|
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)
|
||||||
|
refs := refGraphForReachableTypes(pkgTypes)
|
||||||
|
|
||||||
|
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 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))
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
|
||||||
|
return packageList
|
||||||
|
}
|
||||||
|
|
||||||
|
func generatorForApplyConfigurationsPackage(outputPackagePath string, boilerplate []byte, packageName types.Name, gv clientgentypes.GroupVersion, typesToGenerate []applyConfig, refs refGraph) *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 {
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
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 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
|
||||||
|
}
|
||||||
|
gv := groupVersion(p)
|
||||||
|
pkg := filepath.Join(outputPath, gv.Group.PackageName(), strings.ToLower(gv.Version.NonEmpty()))
|
||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
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(pkgTypes map[string]*types.Package) refGraph {
|
||||||
|
refs := refGraph{}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
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")
|
||||||
|
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")
|
||||||
|
)
|
@ -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
|
||||||
|
}
|
||||||
|
`
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
"k8s.io/gengo/args"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
|
generatorargs "k8s.io/code-generator/cmd/applyconfiguration-gen/args"
|
||||||
|
"k8s.io/code-generator/cmd/applyconfiguration-gen/generators"
|
||||||
|
"k8s.io/code-generator/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
klog.InitFlags(nil)
|
||||||
|
genericArgs := generatorargs.NewDefaults()
|
||||||
|
genericArgs.GoHeaderFilePath = filepath.Join(args.DefaultSourceTree(), util.BoilerplatePath())
|
||||||
|
genericArgs.AddFlags(pflag.CommandLine)
|
||||||
|
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.")
|
||||||
|
}
|
@ -24,6 +24,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/code-generator/cmd/client-gen/generators/util"
|
||||||
"k8s.io/code-generator/cmd/client-gen/types"
|
"k8s.io/code-generator/cmd/client-gen/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -120,7 +121,7 @@ func NewGroupVersionsBuilder(groups *[]types.GroupVersions) *groupVersionsBuilde
|
|||||||
func (p *groupVersionsBuilder) update() error {
|
func (p *groupVersionsBuilder) update() error {
|
||||||
var seenGroups = make(map[types.Group]*types.GroupVersions)
|
var seenGroups = make(map[types.Group]*types.GroupVersions)
|
||||||
for _, v := range p.groups {
|
for _, v := range p.groups {
|
||||||
pth, gvString := parsePathGroupVersion(v)
|
pth, gvString := util.ParsePathGroupVersion(v)
|
||||||
gv, err := types.ToGroupVersion(gvString)
|
gv, err := types.ToGroupVersion(gvString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -151,17 +152,6 @@ func (p *groupVersionsBuilder) update() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePathGroupVersion(pgvString string) (gvPath string, gvString string) {
|
|
||||||
subs := strings.Split(pgvString, "/")
|
|
||||||
length := len(subs)
|
|
||||||
switch length {
|
|
||||||
case 0, 1, 2:
|
|
||||||
return "", pgvString
|
|
||||||
default:
|
|
||||||
return strings.Join(subs[:length-2], "/"), strings.Join(subs[length-2:], "/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func readAsCSV(val string) ([]string, error) {
|
func readAsCSV(val string) ([]string, error) {
|
||||||
if val == "" {
|
if val == "" {
|
||||||
return []string{}, nil
|
return []string{}, nil
|
||||||
|
@ -302,7 +302,7 @@ func generateInterface(tags util.Tags) string {
|
|||||||
// need an ordered list here to guarantee order of generated methods.
|
// need an ordered list here to guarantee order of generated methods.
|
||||||
out := []string{}
|
out := []string{}
|
||||||
for _, m := range util.SupportedVerbs {
|
for _, m := range util.SupportedVerbs {
|
||||||
if tags.HasVerb(m) {
|
if tags.HasVerb(m) && len(defaultVerbTemplates[m]) > 0 {
|
||||||
out = append(out, defaultVerbTemplates[m])
|
out = append(out, defaultVerbTemplates[m])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
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 util
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func ParsePathGroupVersion(pgvString string) (gvPath string, gvString string) {
|
||||||
|
subs := strings.Split(pgvString, "/")
|
||||||
|
length := len(subs)
|
||||||
|
switch length {
|
||||||
|
case 0, 1, 2:
|
||||||
|
return "", pgvString
|
||||||
|
default:
|
||||||
|
return strings.Join(subs[:length-2], "/"), strings.Join(subs[length-2:], "/")
|
||||||
|
}
|
||||||
|
}
|
@ -46,6 +46,8 @@ var SupportedVerbs = []string{
|
|||||||
"list",
|
"list",
|
||||||
"watch",
|
"watch",
|
||||||
"patch",
|
"patch",
|
||||||
|
"apply",
|
||||||
|
"applyStatus",
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadonlyVerbs represents a list of read-only verbs.
|
// ReadonlyVerbs represents a list of read-only verbs.
|
||||||
|
@ -57,11 +57,11 @@ func TestParseTags(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"genclient:onlyVerbs": {
|
"genclient:onlyVerbs": {
|
||||||
lines: []string{`+genclient`, `+genclient:onlyVerbs=create,delete`},
|
lines: []string{`+genclient`, `+genclient:onlyVerbs=create,delete`},
|
||||||
expectTags: Tags{GenerateClient: true, SkipVerbs: []string{"update", "updateStatus", "deleteCollection", "get", "list", "watch", "patch"}},
|
expectTags: Tags{GenerateClient: true, SkipVerbs: []string{"update", "updateStatus", "deleteCollection", "get", "list", "watch", "patch", "apply", "applyStatus"}},
|
||||||
},
|
},
|
||||||
"genclient:readonly": {
|
"genclient:readonly": {
|
||||||
lines: []string{`+genclient`, `+genclient:readonly`},
|
lines: []string{`+genclient`, `+genclient:readonly`},
|
||||||
expectTags: Tags{GenerateClient: true, SkipVerbs: []string{"create", "update", "updateStatus", "delete", "deleteCollection", "patch"}},
|
expectTags: Tags{GenerateClient: true, SkipVerbs: []string{"create", "update", "updateStatus", "delete", "deleteCollection", "patch", "apply", "applyStatus"}},
|
||||||
},
|
},
|
||||||
"genclient:conflict": {
|
"genclient:conflict": {
|
||||||
lines: []string{`+genclient`, `+genclient:onlyVerbs=create`, `+genclient:skipVerbs=create`},
|
lines: []string{`+genclient`, `+genclient:onlyVerbs=create`, `+genclient:skipVerbs=create`},
|
||||||
|
@ -16,6 +16,8 @@ limitations under the License.
|
|||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
type Version string
|
type Version string
|
||||||
|
|
||||||
func (v Version) String() string {
|
func (v Version) String() string {
|
||||||
@ -29,6 +31,10 @@ func (v Version) NonEmpty() string {
|
|||||||
return v.String()
|
return v.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v Version) PackageName() string {
|
||||||
|
return strings.ToLower(v.NonEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
type Group string
|
type Group string
|
||||||
|
|
||||||
func (g Group) String() string {
|
func (g Group) String() string {
|
||||||
@ -42,6 +48,14 @@ func (g Group) NonEmpty() string {
|
|||||||
return string(g)
|
return string(g)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g Group) PackageName() string {
|
||||||
|
parts := strings.Split(g.NonEmpty(), ".")
|
||||||
|
if parts[0] == "internal" && len(parts) > 1 {
|
||||||
|
return strings.ToLower(parts[1] + parts[0])
|
||||||
|
}
|
||||||
|
return strings.ToLower(parts[0])
|
||||||
|
}
|
||||||
|
|
||||||
type PackageVersion struct {
|
type PackageVersion struct {
|
||||||
Version
|
Version
|
||||||
// The fully qualified package, e.g. k8s.io/kubernetes/pkg/apis/apps, where the types.go is found.
|
// The fully qualified package, e.g. k8s.io/kubernetes/pkg/apis/apps, where the types.go is found.
|
||||||
@ -53,6 +67,14 @@ type GroupVersion struct {
|
|||||||
Version Version
|
Version Version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gv GroupVersion) ToAPIVersion() string {
|
||||||
|
if len(gv.Group) > 0 && gv.Group.NonEmpty() != "core" {
|
||||||
|
return gv.Group.String() + "/" + gv.Version.String()
|
||||||
|
} else {
|
||||||
|
return gv.Version.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type GroupVersions struct {
|
type GroupVersions struct {
|
||||||
// The name of the package for this group, e.g. apps.
|
// The name of the package for this group, e.g. apps.
|
||||||
PackageName string
|
PackageName string
|
||||||
|
Loading…
Reference in New Issue
Block a user