diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index e90944065ae..aabe0f4d80e 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -26,11 +26,13 @@ source "${KUBE_ROOT}/hack/lib/init.sh" kube::golang::setup_env +go install k8s.io/kubernetes/pkg/generated/openapi/cmd/models-schema 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/informer-gen go install k8s.io/kubernetes/vendor/k8s.io/code-generator/cmd/applyconfiguration-gen +modelsschema=$(kube::util::find-binary "models-schema") clientgen=$(kube::util::find-binary "client-gen") listergen=$(kube::util::find-binary "lister-gen") informergen=$(kube::util::find-binary "informer-gen") @@ -66,6 +68,7 @@ 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} \ + --openapi-schema <(${modelsschema}) \ --output-base "${KUBE_ROOT}/vendor" \ --output-package "${applyconfigurations_package}" \ --input-dirs "${applyconfigurationgen_external_apis_csv}" \ diff --git a/pkg/generated/openapi/cmd/models-schema/main.go b/pkg/generated/openapi/cmd/models-schema/main.go new file mode 100644 index 00000000000..9b4ce607a4d --- /dev/null +++ b/pkg/generated/openapi/cmd/models-schema/main.go @@ -0,0 +1,79 @@ +/* +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 main + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/go-openapi/spec" + + "k8s.io/kubernetes/pkg/generated/openapi" +) + +// Outputs openAPI schema JSON containing the schema definitions in zz_generated.openapi.go. +func main() { + err := output() + if err != nil { + os.Stderr.WriteString(fmt.Sprintf("Failed: %v", err)) + os.Exit(1) + } +} + +func output() error { + refFunc := func(name string) spec.Ref { + return spec.MustCreateRef(fmt.Sprintf("#/definitions/%s", friendlyName(name))) + } + defs := openapi.GetOpenAPIDefinitions(refFunc) + schemaDefs := make(map[string]spec.Schema, len(defs)) + for k, v := range defs { + schemaDefs[friendlyName(k)] = v.Schema + } + data, err := json.Marshal(&spec.Swagger{ + SwaggerProps: spec.SwaggerProps{ + Definitions: schemaDefs, + Info: &spec.Info{ + InfoProps: spec.InfoProps{ + Title: "Kubernetes", + Version: "unversioned", + }, + }, + Swagger: "2.0", + }, + }) + if err != nil { + return fmt.Errorf("error serializing api definitions: %w", err) + } + _, err = os.Stdout.Write(data) + return nil +} + +// From vendor/k8s.io/apiserver/pkg/endpoints/openapi/openapi.go +func friendlyName(name string) string { + nameParts := strings.Split(name, "/") + // Reverse first part. e.g., io.k8s... instead of k8s.io... + if len(nameParts) > 0 && strings.Contains(nameParts[0], ".") { + parts := strings.Split(nameParts[0], ".") + for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 { + parts[i], parts[j] = parts[j], parts[i] + } + nameParts[0] = strings.Join(parts, ".") + } + return strings.Join(nameParts, ".") +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/managedfields/extract.go b/staging/src/k8s.io/apimachinery/pkg/util/managedfields/extract.go new file mode 100644 index 00000000000..b7c95322577 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/managedfields/extract.go @@ -0,0 +1,86 @@ +/* +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 managedfields + +import ( + "bytes" + "fmt" + + "sigs.k8s.io/structured-merge-diff/v4/fieldpath" + "sigs.k8s.io/structured-merge-diff/v4/typed" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + +// ExtractInto extracts the applied configuration state from object for fieldManager +// into applyConfiguration. If no managed fields are found for the given fieldManager, +// no error is returned, but applyConfiguration is left unpopulated. It is possible +// that no managed fields were found for the fieldManager because other field managers +// have taken ownership of all the fields previously owned by the fieldManager. It is +// also possible the fieldManager never owned fields. +func ExtractInto(object runtime.Object, objectType typed.ParseableType, fieldManager string, applyConfiguration interface{}) error { + typedObj, err := toTyped(object, objectType) + if err != nil { + return fmt.Errorf("error converting obj to typed: %w", err) + } + + accessor, err := meta.Accessor(object) + if err != nil { + return fmt.Errorf("error accessing metadata: %w", err) + } + fieldsEntry, ok := findManagedFields(accessor, fieldManager) + if !ok { + return nil + } + fieldset := &fieldpath.Set{} + err = fieldset.FromJSON(bytes.NewReader(fieldsEntry.FieldsV1.Raw)) + if err != nil { + return fmt.Errorf("error marshalling FieldsV1 to JSON: %w", err) + } + + u := typedObj.ExtractItems(fieldset.Leaves()).AsValue().Unstructured() + m, ok := u.(map[string]interface{}) + if !ok { + return fmt.Errorf("unable to convert managed fields for %s to unstructured, expected map, got %T", fieldManager, u) + } + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(m, applyConfiguration); err != nil { + return fmt.Errorf("error extracting into obj from unstructured: %w", err) + } + return nil +} + +func findManagedFields(accessor metav1.Object, fieldManager string) (metav1.ManagedFieldsEntry, bool) { + objManagedFields := accessor.GetManagedFields() + for _, mf := range objManagedFields { + if mf.Manager == fieldManager && mf.Operation == metav1.ManagedFieldsOperationApply { + return mf, true + } + } + return metav1.ManagedFieldsEntry{}, false +} + +func toTyped(obj runtime.Object, objectType typed.ParseableType) (*typed.TypedValue, error) { + switch o := obj.(type) { + case *unstructured.Unstructured: + return objectType.FromUnstructured(o.Object) + default: + return objectType.FromStructured(o) + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/managedfields/extract_test.go b/staging/src/k8s.io/apimachinery/pkg/util/managedfields/extract_test.go new file mode 100644 index 00000000000..0a9c019c789 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/managedfields/extract_test.go @@ -0,0 +1,242 @@ +/* +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 managedfields + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "sigs.k8s.io/structured-merge-diff/v4/typed" + + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + runtimeschema "k8s.io/apimachinery/pkg/runtime/schema" +) + +func TestExtractInto(t *testing.T) { + one := int32(1) + parser, err := typed.NewParser(schemaYAML) + if err != nil { + t.Fatalf("Failed to parse schema: %v", err) + } + cases := []struct { + name string + obj runtime.Object + objType typed.ParseableType + managedFields []metav1.ManagedFieldsEntry // written to object before test is run + fieldManager string + expectedOut interface{} + }{ + { + name: "unstructured, no matching manager", + obj: &unstructured.Unstructured{Object: map[string]interface{}{"spec": map[string]interface{}{"replicas": 1}}}, + objType: parser.Type("io.k8s.api.apps.v1.Deployment"), + managedFields: []metav1.ManagedFieldsEntry{ + applyFieldsEntry("mgr999", `{ "f:spec": { "f:replicas": {}}}`), + }, + fieldManager: "mgr1", + expectedOut: map[string]interface{}{}, + }, + { + name: "unstructured, one manager", + obj: &unstructured.Unstructured{Object: map[string]interface{}{"spec": map[string]interface{}{"replicas": 1}}}, + objType: parser.Type("io.k8s.api.apps.v1.Deployment"), + managedFields: []metav1.ManagedFieldsEntry{ + applyFieldsEntry("mgr1", `{ "f:spec": { "f:replicas": {}}}`), + }, + fieldManager: "mgr1", + expectedOut: map[string]interface{}{"spec": map[string]interface{}{"replicas": 1}}, + }, + { + name: "unstructured, multiple manager", + obj: &unstructured.Unstructured{Object: map[string]interface{}{"spec": map[string]interface{}{"paused": true}}}, + objType: parser.Type("io.k8s.api.apps.v1.Deployment"), + managedFields: []metav1.ManagedFieldsEntry{ + applyFieldsEntry("mgr1", `{ "f:spec": { "f:replicas": {}}}`), + applyFieldsEntry("mgr2", `{ "f:spec": { "f:paused": {}}}`), + }, + fieldManager: "mgr2", + expectedOut: map[string]interface{}{"spec": map[string]interface{}{"paused": true}}, + }, + { + name: "structured, no matching manager", + obj: &fakeDeployment{Spec: fakeDeploymentSpec{Replicas: &one}}, + objType: parser.Type("io.k8s.api.apps.v1.Deployment"), + managedFields: []metav1.ManagedFieldsEntry{ + applyFieldsEntry("mgr999", `{ "f:spec": { "f:replicas": {}}}`), + }, + fieldManager: "mgr1", + expectedOut: map[string]interface{}{}, + }, + { + name: "structured, one manager", + obj: &fakeDeployment{Spec: fakeDeploymentSpec{Replicas: &one}}, + objType: parser.Type("io.k8s.api.apps.v1.Deployment"), + managedFields: []metav1.ManagedFieldsEntry{ + applyFieldsEntry("mgr1", `{ "f:spec": { "f:replicas": {}}}`), + }, + fieldManager: "mgr1", + expectedOut: map[string]interface{}{"spec": map[string]interface{}{"replicas": int64(1)}}, + }, + { + name: "structured, multiple manager", + obj: &fakeDeployment{Spec: fakeDeploymentSpec{Replicas: &one, Paused: true}}, + objType: parser.Type("io.k8s.api.apps.v1.Deployment"), + managedFields: []metav1.ManagedFieldsEntry{ + applyFieldsEntry("mgr1", `{ "f:spec": { "f:replicas": {}}}`), + applyFieldsEntry("mgr2", `{ "f:spec": { "f:paused": {}}}`), + }, + fieldManager: "mgr2", + expectedOut: map[string]interface{}{"spec": map[string]interface{}{"paused": true}}, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + out := map[string]interface{}{} + accessor, err := meta.Accessor(tc.obj) + if err != nil { + t.Fatalf("Error accessing object: %v", err) + } + accessor.SetManagedFields(tc.managedFields) + err = ExtractInto(tc.obj, tc.objType, tc.fieldManager, &out) + if err != nil { + t.Fatalf("Unexpected extract error: %v", err) + } + if !equality.Semantic.DeepEqual(out, tc.expectedOut) { + t.Fatalf("Expected output did not match actual output: %s", cmp.Diff(out, tc.expectedOut)) + } + }) + } +} + +func applyFieldsEntry(fieldManager string, fieldsJSON string) metav1.ManagedFieldsEntry { + return metav1.ManagedFieldsEntry{ + Manager: fieldManager, + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "v1", + FieldsType: "FieldsV1", + FieldsV1: &metav1.FieldsV1{Raw: []byte(fieldsJSON)}, + } +} + +type fakeDeployment struct { + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec fakeDeploymentSpec `json:"spec"` +} + +type fakeDeploymentSpec struct { + Replicas *int32 `json:"replicas"` + Paused bool `json:"paused,omitempty"` +} + +func (o *fakeDeployment) GetObjectMeta() metav1.ObjectMeta { + return o.ObjectMeta +} +func (o *fakeDeployment) GetObjectKind() runtimeschema.ObjectKind { + return runtimeschema.EmptyObjectKind +} +func (o *fakeDeployment) DeepCopyObject() runtime.Object { + return o +} + +// trimmed up schema for test purposes +const schemaYAML = typed.YAMLObject(`types: +- name: io.k8s.api.apps.v1.Deployment + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + - name: spec + type: + namedType: io.k8s.api.apps.v1.DeploymentSpec +- name: io.k8s.api.apps.v1.DeploymentSpec + map: + fields: + - name: paused + type: + scalar: boolean + - name: replicas + type: + scalar: numeric +- name: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + map: + fields: + - name: creationTimestamp + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: managedFields + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry + elementRelationship: atomic +- name: io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry + map: + fields: + - name: apiVersion + type: + scalar: string + - name: fieldsType + type: + scalar: string + - name: fieldsV1 + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1 + - name: manager + type: + scalar: string + - name: operation + type: + scalar: string + - name: time + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time +- name: io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1 + map: + elementType: + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic +- name: io.k8s.apimachinery.pkg.apis.meta.v1.Time + scalar: untyped +- name: __untyped_atomic_ + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic +`) diff --git a/staging/src/k8s.io/client-go/go.mod b/staging/src/k8s.io/client-go/go.mod index 75014b0230f..6b376e99568 100644 --- a/staging/src/k8s.io/client-go/go.mod +++ b/staging/src/k8s.io/client-go/go.mod @@ -31,6 +31,7 @@ require ( k8s.io/apimachinery v0.0.0 k8s.io/klog/v2 v2.5.0 k8s.io/utils v0.0.0-20201110183641-67b214c5f920 + sigs.k8s.io/structured-merge-diff/v4 v4.1.0 sigs.k8s.io/yaml v1.2.0 ) diff --git a/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/args/args.go b/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/args/args.go index a1a528671fd..58e0d742824 100644 --- a/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/args/args.go +++ b/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/args/args.go @@ -40,6 +40,8 @@ type CustomArgs struct { // // meta/v1 types (TypeMeta and ObjectMeta) are always included and do not need to be passed in. ExternalApplyConfigurations map[types.Name]string + + OpenAPISchemaFilePath string } // NewDefaults returns default arguments for the generator. @@ -65,6 +67,8 @@ 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 .: form."+ "For example: k8s.io/api/apps/v1.Deployment:k8s.io/client-go/applyconfigurations/apps/v1") + pflag.StringVar(&ca.OpenAPISchemaFilePath, "openapi-schema", "", + "path to the openapi schema containing all the types that apply configurations will be generated for") } // Validate checks the given arguments. diff --git a/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/applyconfiguration.go b/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/applyconfiguration.go index bed276b9b77..464bd5d9e52 100644 --- a/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/applyconfiguration.go +++ b/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/applyconfiguration.go @@ -37,6 +37,7 @@ type applyConfigurationGenerator struct { applyConfig applyConfig imports namer.ImportTracker refGraph refGraph + openAPIType *string // if absent, extraction function cannot be generated } var _ generator.Generator = &applyConfigurationGenerator{} @@ -64,6 +65,9 @@ type TypeParams struct { ApplyConfig applyConfig Tags util.Tags APIVersion string + ExtractInto *types.Type + ParserFunc *types.Type + OpenAPIType *string } type memberParams struct { @@ -84,6 +88,9 @@ func (g *applyConfigurationGenerator) GenerateType(c *generator.Context, t *type ApplyConfig: g.applyConfig, Tags: genclientTags(t), APIVersion: g.groupVersion.ToAPIVersion(), + ExtractInto: extractInto, + ParserFunc: types.Ref(g.outputPackage+"/internal", "Parser"), + OpenAPIType: g.openAPIType, } g.generateStruct(sw, typeParams) @@ -94,6 +101,9 @@ func (g *applyConfigurationGenerator) GenerateType(c *generator.Context, t *type } else { sw.Do(clientgenTypeConstructorNamespaced, typeParams) } + if typeParams.OpenAPIType != nil { + g.generateClientgenExtract(sw, typeParams) + } } else { sw.Do(constructor, typeParams) } @@ -306,3 +316,35 @@ func $.ApplyConfig.Type|public$() *$.ApplyConfig.ApplyConfiguration|public$ { return &$.ApplyConfig.ApplyConfiguration|public${} } ` + +func (g *applyConfigurationGenerator) generateClientgenExtract(sw *generator.SnippetWriter, typeParams TypeParams) { + sw.Do(` +// Extract$.ApplyConfig.Type|public$ extracts the applied configuration owned by fieldManager from +// $.Struct|private$. If no managedFields are found in $.Struct|private$ for fieldManager, a +// $.ApplyConfig.ApplyConfiguration|public$ is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. Is is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// $.Struct|private$ must be a unmodified $.Struct|public$ API object that was retrieved from the Kubernetes API. +// Extract$.ApplyConfig.Type|public$ provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +// Experimental! +func Extract$.ApplyConfig.Type|public$($.Struct|private$ *$.Struct|raw$, fieldManager string) (*$.ApplyConfig.ApplyConfiguration|public$ , error) { + b := &$.ApplyConfig.ApplyConfiguration|public${} + err := $.ExtractInto|raw$($.Struct|private$, $.ParserFunc|raw$().Type("$.OpenAPIType$"), fieldManager, b) + if err != nil { + return nil, err + } + b.WithName($.Struct|private$.Name) +`, typeParams) + if !typeParams.Tags.NonNamespaced { + sw.Do("b.WithNamespace($.Struct|private$.Namespace)\n", typeParams) + } + sw.Do(` + b.WithKind("$.ApplyConfig.Type|singularKind$") + b.WithAPIVersion("$.APIVersion$") + return b, nil +} +`, typeParams) +} diff --git a/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/internal.go b/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/internal.go new file mode 100644 index 00000000000..2871b9d7f5e --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/internal.go @@ -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 ( + "io" + + "gopkg.in/yaml.v2" + + "k8s.io/kube-openapi/pkg/schemaconv" + + "k8s.io/gengo/generator" + "k8s.io/gengo/namer" + "k8s.io/gengo/types" +) + +// utilGenerator generates the ForKind() utility function. +type internalGenerator struct { + generator.DefaultGen + outputPackage string + imports namer.ImportTracker + typeModels *typeModels + filtered bool +} + +var _ generator.Generator = &internalGenerator{} + +func (g *internalGenerator) Filter(*generator.Context, *types.Type) bool { + // generate file exactly once + if !g.filtered { + g.filtered = true + return true + } + return false +} + +func (g *internalGenerator) Namers(*generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + "singularKind": namer.NewPublicNamer(0), + } +} + +func (g *internalGenerator) Imports(*generator.Context) (imports []string) { + return g.imports.ImportLines() +} + +func (g *internalGenerator) GenerateType(c *generator.Context, _ *types.Type, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "{{", "}}") + + schema, err := schemaconv.ToSchema(g.typeModels.models) + if err != nil { + return err + } + schemaYAML, err := yaml.Marshal(schema) + if err != nil { + return err + } + sw.Do(schemaBlock, map[string]interface{}{ + "schemaYAML": string(schemaYAML), + "smdParser": smdParser, + "smdNewParser": smdNewParser, + "yamlObject": yamlObject, + "yamlUnmarshal": yamlUnmarshal, + }) + + return sw.Error() +} + +var schemaBlock = ` +func Parser() *{{.smdParser|raw}} { + parserOnce.Do(func() { + var err error + parser, err = {{.smdNewParser|raw}}(schemaYAML) + if err != nil { + panic(fmt.Sprintf("Failed to parse schema: %v", err)) + } + }) + return parser +} + +var parserOnce sync.Once +var parser *{{.smdParser|raw}} +var schemaYAML = {{.yamlObject|raw}}(` + "`{{.schemaYAML}}`" + `) +` diff --git a/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/openapi.go b/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/openapi.go new file mode 100644 index 00000000000..317ab7d993f --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/openapi.go @@ -0,0 +1,204 @@ +/* +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 ( + "encoding/json" + "fmt" + "io/ioutil" + "strings" + + "github.com/go-openapi/spec" + "github.com/googleapis/gnostic/compiler" + openapiv2 "github.com/googleapis/gnostic/openapiv2" + "gopkg.in/yaml.v2" + "k8s.io/gengo/types" + utilproto "k8s.io/kube-openapi/pkg/util/proto" +) + +type typeModels struct { + models utilproto.Models + gvkToOpenAPIType map[gvk]string +} + +type gvk struct { + group, version, kind string +} + +func newTypeModels(openAPISchemaFilePath string, pkgTypes map[string]*types.Package) (*typeModels, error) { + if len(openAPISchemaFilePath) == 0 { + return emptyModels, nil // No Extract() functions will be generated. + } + + rawOpenAPISchema, err := ioutil.ReadFile(openAPISchemaFilePath) + if err != nil { + return nil, fmt.Errorf("failed to read openapi-schema file: %w", err) + } + + // Read in the provided openAPI schema. + openAPISchema := &spec.Swagger{} + err = json.Unmarshal(rawOpenAPISchema, openAPISchema) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal typeModels JSON: %w", err) + } + + // Build a mapping from openAPI type name to GVK. + // Find the root types needed by by client-go for apply. + gvkToOpenAPIType := map[gvk]string{} + rootDefs := map[string]spec.Schema{} + for _, p := range pkgTypes { + gv := groupVersion(p) + for _, t := range p.Types { + tags := genclientTags(t) + hasApply := tags.HasVerb("apply") || tags.HasVerb("applyStatus") + if tags.GenerateClient && hasApply { + openAPIType := friendlyName(typeName(t)) + gvk := gvk{ + group: gv.Group.String(), + version: gv.Version.String(), + kind: t.Name.Name, + } + rootDefs[openAPIType] = openAPISchema.Definitions[openAPIType] + gvkToOpenAPIType[gvk] = openAPIType + } + } + } + + // Trim the schema down to just the types needed by client-go for apply. + requiredDefs := make(map[string]spec.Schema) + for name, def := range rootDefs { + requiredDefs[name] = def + findReferenced(&def, openAPISchema.Definitions, requiredDefs) + } + openAPISchema.Definitions = requiredDefs + + // Convert the openAPI schema to the models format and validate it. + models, err := toValidatedModels(openAPISchema) + if err != nil { + return nil, err + } + return &typeModels{models: models, gvkToOpenAPIType: gvkToOpenAPIType}, nil +} + +var emptyModels = &typeModels{ + models: &utilproto.Definitions{}, + gvkToOpenAPIType: map[gvk]string{}, +} + +func toValidatedModels(openAPISchema *spec.Swagger) (utilproto.Models, error) { + // openapi_v2.NewDocument only accepts a yaml.MapSlice + // so we do an inefficient marshal back to json and then read it back in as yaml + // but get the benefit of running the models through utilproto.NewOpenAPIData to + // validate all the references between types + rawMinimalOpenAPISchema, err := json.Marshal(openAPISchema) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal openAPI as JSON: %w", err) + } + var info yaml.MapSlice + err = yaml.Unmarshal(rawMinimalOpenAPISchema, &info) + if err != nil { + return nil, fmt.Errorf("failed to parse OpenAPI file: %w", err) + } + document, err := openapiv2.NewDocument(info, compiler.NewContext("$root", nil)) + if err != nil { + return nil, fmt.Errorf("failed to OpenAPI document for file: %w", err) + } + // Construct the models and validate all references are valid. + models, err := utilproto.NewOpenAPIData(document) + if err != nil { + return nil, fmt.Errorf("failed to create OpenAPI models for file: %w", err) + } + return models, nil +} + +// findReferenced recursively finds all schemas referenced from the given def. +// toValidatedModels makes sure no references get missed. +func findReferenced(def *spec.Schema, allSchemas, referencedOut map[string]spec.Schema) { + // follow $ref, if any + refPtr := def.Ref.GetPointer() + if refPtr != nil && !refPtr.IsEmpty() { + name := refPtr.String() + if !strings.HasPrefix(name, "/definitions/") { + return + } + name = strings.TrimPrefix(name, "/definitions/") + schema, ok := allSchemas[name] + if !ok { + panic(fmt.Sprintf("allSchemas schema is missing referenced type: %s", name)) + } + if _, ok := referencedOut[name]; !ok { + referencedOut[name] = schema + findReferenced(&schema, allSchemas, referencedOut) + } + } + + // follow any nested schemas + if def.Items != nil { + if def.Items.Schema != nil { + findReferenced(def.Items.Schema, allSchemas, referencedOut) + } + for _, item := range def.Items.Schemas { + findReferenced(&item, allSchemas, referencedOut) + } + } + if def.AllOf != nil { + for _, s := range def.AllOf { + findReferenced(&s, allSchemas, referencedOut) + } + } + if def.AnyOf != nil { + for _, s := range def.AnyOf { + findReferenced(&s, allSchemas, referencedOut) + } + } + if def.OneOf != nil { + for _, s := range def.OneOf { + findReferenced(&s, allSchemas, referencedOut) + } + } + if def.Not != nil { + findReferenced(def.Not, allSchemas, referencedOut) + } + if def.Properties != nil { + for _, prop := range def.Properties { + findReferenced(&prop, allSchemas, referencedOut) + } + } + if def.AdditionalProperties != nil && def.AdditionalProperties.Schema != nil { + findReferenced(def.AdditionalProperties.Schema, allSchemas, referencedOut) + } + if def.PatternProperties != nil { + for _, s := range def.PatternProperties { + findReferenced(&s, allSchemas, referencedOut) + } + } + if def.Dependencies != nil { + for _, d := range def.Dependencies { + if d.Schema != nil { + findReferenced(d.Schema, allSchemas, referencedOut) + } + } + } + if def.AdditionalItems != nil && def.AdditionalItems.Schema != nil { + findReferenced(def.AdditionalItems.Schema, allSchemas, referencedOut) + } + if def.Definitions != nil { + for _, s := range def.Definitions { + findReferenced(&s, allSchemas, referencedOut) + } + } +} diff --git a/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/packages.go b/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/packages.go index d4de946fc03..409419431bf 100644 --- a/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/packages.go +++ b/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/packages.go @@ -17,6 +17,7 @@ limitations under the License. package generators import ( + "fmt" "path" "path/filepath" "sort" @@ -60,9 +61,13 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat } pkgTypes := packageTypesForInputDirs(context, arguments.InputDirs, arguments.OutputPackagePath) - initialTypes := arguments.CustomArgs.(*applygenargs.CustomArgs).ExternalApplyConfigurations + customArgs := arguments.CustomArgs.(*applygenargs.CustomArgs) + initialTypes := customArgs.ExternalApplyConfigurations refs := refGraphForReachableTypes(pkgTypes, initialTypes) - + typeModels, err := newTypeModels(customArgs.OpenAPISchemaFilePath, pkgTypes) + if err != nil { + klog.Fatalf("Failed build type models from typeModels %s: %v", customArgs.OpenAPISchemaFilePath, err) + } groupVersions := make(map[string]clientgentypes.GroupVersions) groupGoNames := make(map[string]string) applyConfigsForGroupVersion := make(map[clientgentypes.GroupVersion][]applyConfig) @@ -88,7 +93,7 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat sort.Sort(applyConfigSort(toGenerate)) // generate the apply configurations - packageList = append(packageList, generatorForApplyConfigurationsPackage(arguments.OutputPackagePath, boilerplate, pkgType, gv, toGenerate, refs)) + packageList = append(packageList, generatorForApplyConfigurationsPackage(arguments.OutputPackagePath, boilerplate, pkgType, gv, toGenerate, refs, typeModels)) // group all the generated apply configurations by gv so ForKind() can be generated groupPackageName := gv.Group.NonEmpty() @@ -111,17 +116,50 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat // generate ForKind() utility function packageList = append(packageList, generatorForUtils(arguments.OutputPackagePath, boilerplate, groupVersions, applyConfigsForGroupVersion, groupGoNames)) + // generate internal embedded schema, required for generated Extract functions + packageList = append(packageList, generatorForInternal(filepath.Join(arguments.OutputPackagePath, "internal"), boilerplate, typeModels)) return packageList } -func generatorForApplyConfigurationsPackage(outputPackagePath string, boilerplate []byte, packageName types.Name, gv clientgentypes.GroupVersion, typesToGenerate []applyConfig, refs refGraph) *generator.DefaultPackage { +func friendlyName(name string) string { + nameParts := strings.Split(name, "/") + // Reverse first part. e.g., io.k8s... instead of k8s.io... + if len(nameParts) > 0 && strings.Contains(nameParts[0], ".") { + parts := strings.Split(nameParts[0], ".") + for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 { + parts[i], parts[j] = parts[j], parts[i] + } + nameParts[0] = strings.Join(parts, ".") + } + return strings.Join(nameParts, ".") +} + +func typeName(t *types.Type) string { + typePackage := t.Name.Package + if strings.Contains(typePackage, "/vendor/") { + typePackage = typePackage[strings.Index(typePackage, "/vendor/")+len("/vendor/"):] + } + return fmt.Sprintf("%s.%s", typePackage, t.Name.Name) +} + +func generatorForApplyConfigurationsPackage(outputPackagePath string, boilerplate []byte, packageName types.Name, gv clientgentypes.GroupVersion, typesToGenerate []applyConfig, refs refGraph, models *typeModels) *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 { + var openAPIType *string + gvk := gvk{ + group: gv.Group.String(), + version: gv.Version.String(), + kind: toGenerate.Type.Name.Name, + } + if v, ok := models.gvkToOpenAPIType[gvk]; ok { + openAPIType = &v + } + generators = append(generators, &applyConfigurationGenerator{ DefaultGen: generator.DefaultGen{ OptionalName: strings.ToLower(toGenerate.Type.Name.Name), @@ -132,6 +170,7 @@ func generatorForApplyConfigurationsPackage(outputPackagePath string, boilerplat applyConfig: toGenerate, imports: generator.NewImportTracker(), refGraph: refs, + openAPIType: openAPIType, }) } return generators @@ -160,6 +199,25 @@ func generatorForUtils(outPackagePath string, boilerplate []byte, groupVersions } } +func generatorForInternal(outPackagePath string, boilerplate []byte, models *typeModels) *generator.DefaultPackage { + return &generator.DefaultPackage{ + PackageName: filepath.Base(outPackagePath), + PackagePath: outPackagePath, + HeaderText: boilerplate, + GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) { + generators = append(generators, &internalGenerator{ + DefaultGen: generator.DefaultGen{ + OptionalName: "internal", + }, + outputPackage: outPackagePath, + imports: generator.NewImportTracker(), + typeModels: models, + }) + 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 { diff --git a/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/types.go b/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/types.go index 28b4c3007e5..70ea37cc75a 100644 --- a/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/types.go +++ b/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/types.go @@ -24,4 +24,9 @@ var ( 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") + extractInto = types.Ref("k8s.io/apimachinery/pkg/util/managedfields", "ExtractInto") + smdNewParser = types.Ref("sigs.k8s.io/structured-merge-diff/v4/typed", "NewParser") + smdParser = types.Ref("sigs.k8s.io/structured-merge-diff/v4/typed", "Parser") + yamlObject = types.Ref("sigs.k8s.io/structured-merge-diff/v4/typed", "YAMLObject") + yamlUnmarshal = types.Ref("gopkg.in/yaml.v2", "Unmarshal") ) diff --git a/staging/src/k8s.io/code-generator/examples/go.sum b/staging/src/k8s.io/code-generator/examples/go.sum index 167452d0661..ae1265035d6 100644 --- a/staging/src/k8s.io/code-generator/examples/go.sum +++ b/staging/src/k8s.io/code-generator/examples/go.sum @@ -435,7 +435,7 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.0.3 h1:4oyYo8NREp49LBBhKxEqCulFjg26rawYKrnCmg+Sr6c= -sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.0 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8= +sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/staging/src/k8s.io/code-generator/go.mod b/staging/src/k8s.io/code-generator/go.mod index d794f13aa8f..f05ec2196f3 100644 --- a/staging/src/k8s.io/code-generator/go.mod +++ b/staging/src/k8s.io/code-generator/go.mod @@ -6,13 +6,17 @@ go 1.16 require ( github.com/emicklei/go-restful v2.9.5+incompatible // indirect - github.com/go-openapi/spec v0.19.5 // indirect + github.com/go-openapi/spec v0.19.5 github.com/gogo/protobuf v1.3.2 + github.com/golang/protobuf v1.4.3 // indirect github.com/google/go-cmp v0.5.2 // indirect + github.com/googleapis/gnostic v0.4.1 github.com/json-iterator/go v1.1.10 // indirect github.com/kr/text v0.2.0 // indirect github.com/mailru/easyjson v0.7.0 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/onsi/ginkgo v1.11.0 // indirect + github.com/onsi/gomega v1.7.0 // indirect github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.6.1 // indirect golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449 // indirect @@ -20,11 +24,13 @@ require ( golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073 // indirect golang.org/x/text v0.3.4 // indirect golang.org/x/tools v0.1.0 // indirect + google.golang.org/protobuf v1.25.0 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v2 v2.4.0 k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027 k8s.io/klog/v2 v2.5.0 k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 + sigs.k8s.io/structured-merge-diff/v4 v4.1.0 // indirect ) replace k8s.io/code-generator => ../code-generator diff --git a/staging/src/k8s.io/code-generator/go.sum b/staging/src/k8s.io/code-generator/go.sum index e62c1d21ffd..fdc9937521d 100644 --- a/staging/src/k8s.io/code-generator/go.sum +++ b/staging/src/k8s.io/code-generator/go.sum @@ -1,9 +1,13 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -12,6 +16,9 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3 github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= @@ -30,14 +37,34 @@ github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tF github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -65,9 +92,15 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -82,10 +115,18 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449 h1:xUIPaMhvROX9dhPvRCenIJtU78+lbEenGbgqB5hfHCQ= golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -96,9 +137,14 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210224082022-3d97a244fca7 h1:OgUuv8lsRpBibGNbSizVwKWlysjaNzmC9gYMhPVfqFM= golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -115,6 +161,10 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -127,11 +177,33 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -140,6 +212,8 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027 h1:Uusb3oh8XcdzDF/ndlI4ToKTYVlkCSJP39SRY2mfRAw= k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= @@ -150,5 +224,7 @@ k8s.io/klog/v2 v2.5.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 h1:vEx13qjvaZ4yfObSSXW7BrMc/KQBBT/Jyee8XtLf4x0= k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.0 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8= +sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/test/integration/client/client_test.go b/test/integration/client/client_test.go index 65f3213799f..1437ded8b88 100644 --- a/test/integration/client/client_test.go +++ b/test/integration/client/client_test.go @@ -26,8 +26,10 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -882,3 +884,302 @@ func TestApplyWithApplyConfiguration(t *testing.T) { t.Error("expected status to contain DeploymentReplicaFailure condition set by apply") } } + +func TestExtractModifyApply(t *testing.T) { + testCases := []struct { + name string + // modifyFunc modifies deployApply, defined below, after it is applied and "extracted" + // apply is skipped if this func is nil + modifyFunc func(apply *appsv1ac.DeploymentApplyConfiguration) + modifyStatusFunc func(apply *appsv1ac.DeploymentApplyConfiguration) // same but for status + // verifyAppliedFunc verifies the results of applying the applied + // configuration after modifyFunc modifies it. Only called if modifyFunc is provided. + verifyAppliedFunc func(applied *appsv1ac.DeploymentApplyConfiguration) + verifyStatusAppliedFunc func(applied *appsv1ac.DeploymentApplyConfiguration) // same but for status + }{ + { + // With() on a scalar field replaces it with the given value + name: "modify-scalar", + modifyFunc: func(apply *appsv1ac.DeploymentApplyConfiguration) { + apply.Spec.WithReplicas(2) + }, + verifyAppliedFunc: func(applied *appsv1ac.DeploymentApplyConfiguration) { + if *applied.Spec.Replicas != 2 { + t.Errorf("Expected 2 replicas but got: %d", *applied.Spec.Replicas) + } + }, + }, + { + // With() on a non-empty struct field replaces the entire struct + name: "modify-struct", + modifyFunc: func(apply *appsv1ac.DeploymentApplyConfiguration) { + apply.Spec.Template.WithSpec(corev1ac.PodSpec(). // replace the Spec of the existing Template + WithContainers( + corev1ac.Container(). + WithName("modify-struct"). + WithImage("nginx:1.14.3"), + ), + ) + }, + verifyAppliedFunc: func(applied *appsv1ac.DeploymentApplyConfiguration) { + containers := applied.Spec.Template.Spec.Containers + if len(containers) != 1 { + t.Errorf("Expected 1 container but got %d", len(containers)) + } + if *containers[0].Name != "modify-struct" { + t.Errorf("Expected container name modify-struct but got: %s", *containers[0].Name) + } + }, + }, + { + // With() on a non-empty map field puts all the given entries into the existing map + name: "modify-map", + modifyFunc: func(apply *appsv1ac.DeploymentApplyConfiguration) { + apply.WithLabels(map[string]string{"label2": "value2"}) + }, + verifyAppliedFunc: func(applied *appsv1ac.DeploymentApplyConfiguration) { + labels := applied.Labels + if len(labels) != 2 { + t.Errorf("Expected 2 label but got %d", len(labels)) + } + if labels["label2"] != "value2" { + t.Errorf("Expected container name value2 but got: %s", labels["label2"]) + } + }, + }, + { + // With() on a non-empty slice field appends all the given items to the existing slice + name: "modify-slice", + modifyFunc: func(apply *appsv1ac.DeploymentApplyConfiguration) { + apply.Spec.Template.Spec.WithContainers(corev1ac.Container(). + WithName("modify-slice"). + WithImage("nginx:1.14.2"), + ) + }, + verifyAppliedFunc: func(applied *appsv1ac.DeploymentApplyConfiguration) { + containers := applied.Spec.Template.Spec.Containers + if len(containers) != 2 { + t.Errorf("Expected 2 containers but got %d", len(containers)) + } + if *containers[0].Name != "initial-container" { + t.Errorf("Expected container name initial-container but got: %s", *containers[0].Name) + } + if *containers[1].Name != "modify-slice" { + t.Errorf("Expected container name modify-slice but got: %s", *containers[1].Name) + } + }, + }, + { + // Append a condition to the status if the object + name: "modify-status-conditions", + modifyStatusFunc: func(apply *appsv1ac.DeploymentApplyConfiguration) { + apply.WithStatus(appsv1ac.DeploymentStatus(). + WithConditions(appsv1ac.DeploymentCondition(). + WithType(appsv1.DeploymentProgressing). + WithStatus(v1.ConditionUnknown). + WithLastTransitionTime(metav1.Now()). + WithLastUpdateTime(metav1.Now()). + WithMessage("progressing"). + WithReason("TestExtractModifyApply_Status"), + ), + ) + }, + verifyStatusAppliedFunc: func(applied *appsv1ac.DeploymentApplyConfiguration) { + conditions := applied.Status.Conditions + if len(conditions) != 1 { + t.Errorf("Expected 1 conditions but got %d", len(conditions)) + } + if *conditions[0].Type != appsv1.DeploymentProgressing { + t.Errorf("Expected condition name DeploymentProgressing but got: %s", *conditions[0].Type) + } + }, + }, + } + + testServer := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins", "ServiceAccount"}, framework.SharedEtcd()) + defer testServer.TearDownFn() + c := clientset.NewForConfigOrDie(testServer.ClientConfig) + deploymentClient := c.AppsV1().Deployments("default") + fieldMgr := "test-mgr" + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Applied at the started of each test + deployApply := appsv1ac.Deployment(tc.name, "default"). + WithLabels(map[string]string{"label1": "value1"}). + WithSpec(appsv1ac.DeploymentSpec(). + WithSelector(metav1ac.LabelSelector(). + WithMatchLabels(map[string]string{"app": tc.name}), + ). + WithTemplate(corev1ac.PodTemplateSpec(). + WithLabels(map[string]string{"app": tc.name}). + WithSpec(corev1ac.PodSpec(). + WithContainers( + corev1ac.Container(). + WithName("initial-container"). + WithImage("nginx:1.14.2"), + ), + ), + ), + ) + actual, err := deploymentClient.Apply(context.TODO(), deployApply, metav1.ApplyOptions{FieldManager: fieldMgr}) + if err != nil { + t.Fatalf("Failed to apply: %v", err) + } + + extractedDeployment, err := appsv1ac.ExtractDeployment(actual, fieldMgr) + if err != nil { + t.Fatalf("Failed to extract: %v", err) + } + + if tc.modifyFunc != nil { + tc.modifyFunc(extractedDeployment) + result, err := deploymentClient.Apply(context.TODO(), extractedDeployment, metav1.ApplyOptions{FieldManager: fieldMgr}) + if err != nil { + t.Fatalf("Failed to apply extracted apply configuration: %v", err) + } + extractedResult, err := appsv1ac.ExtractDeployment(result, fieldMgr) + if err != nil { + t.Fatalf("Failed to extract: %v", err) + } + if tc.verifyAppliedFunc != nil { + tc.verifyAppliedFunc(extractedResult) + } + } + + if tc.modifyStatusFunc != nil { + tc.modifyStatusFunc(extractedDeployment) + result, err := deploymentClient.ApplyStatus(context.TODO(), extractedDeployment, metav1.ApplyOptions{FieldManager: fieldMgr}) + if err != nil { + t.Fatalf("Failed to apply extracted apply configuration to status: %v", err) + } + extractedResult, err := appsv1ac.ExtractDeployment(result, fieldMgr) + if err != nil { + t.Fatalf("Failed to extract: %v", err) + } + if tc.verifyStatusAppliedFunc != nil { + tc.verifyStatusAppliedFunc(extractedResult) + } + } + }) + } +} + +func TestExtractModifyApply_ForceOwnership(t *testing.T) { + testServer := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins", "ServiceAccount"}, framework.SharedEtcd()) + defer testServer.TearDownFn() + c := clientset.NewForConfigOrDie(testServer.ClientConfig) + deploymentClient := c.AppsV1().Deployments("default") + + // apply an initial state with one field manager + createApply := appsv1ac.Deployment("nginx-apply", "default"). + WithSpec(appsv1ac.DeploymentSpec(). + WithSelector(metav1ac.LabelSelector(). + WithMatchLabels(map[string]string{"app": "nginx"}), + ). + WithTemplate(corev1ac.PodTemplateSpec(). + WithLabels(map[string]string{"app": "nginx"}). + WithSpec(corev1ac.PodSpec(). + WithContainers( + corev1ac.Container(). + WithName("nginx"). + WithImage("nginx:1.14.2"). + WithWorkingDir("/tmp/v1"), + ), + ), + ), + ) + + _, err := deploymentClient.Apply(context.TODO(), createApply, metav1.ApplyOptions{FieldManager: "create-mgr", Force: true}) + if err != nil { + t.Fatalf("Error creating createApply: %v", err) + } + + // apply some non-overlapping fields with another field manager + sidecarApply := appsv1ac.Deployment("nginx-apply", "default"). + WithSpec(appsv1ac.DeploymentSpec(). + WithTemplate(corev1ac.PodTemplateSpec(). + WithSpec(corev1ac.PodSpec(). + WithContainers( + corev1ac.Container(). + WithName("sidecar"). + WithImage("nginx:1.14.2"), + ), + ), + ), + ) + + applied, err := deploymentClient.Apply(context.TODO(), sidecarApply, metav1.ApplyOptions{FieldManager: "sidecar-mgr", Force: true}) + if err != nil { + t.Fatalf("Error applying createApply: %v", err) + } + sidecarExtracted, err := appsv1ac.ExtractDeployment(applied, "sidecar-mgr") + if err != nil { + t.Fatalf("Error extracting createApply apply configuration: %v", err) + } + if !equality.Semantic.DeepEqual(sidecarApply, sidecarExtracted) { + t.Errorf("Expected sidecarExtracted apply configuration to match original, but got:\n%s\n", cmp.Diff(sidecarApply, sidecarExtracted)) + } + + // modify the extracted apply configuration that was just applied and add some fields that overlap + // with the fields owned by the other field manager to force ownership of them + sidecarExtracted.Spec.Template.Spec.Containers[0].WithImage("nginx:1.14.3") + sidecarExtracted.Spec.Template.Spec.WithContainers(corev1ac.Container(). + WithName("nginx"). + WithWorkingDir("/tmp/v2"), + ) + reapplied, err := deploymentClient.Apply(context.TODO(), sidecarExtracted, metav1.ApplyOptions{FieldManager: "sidecar-mgr", Force: true}) + if err != nil { + t.Fatalf("Unexpected error when applying manifest for Deployment: %v", err) + } + + // extract apply configurations for both field managers and check that they are what we expect + reappliedExtracted, err := appsv1ac.ExtractDeployment(reapplied, "sidecar-mgr") + if err != nil { + t.Fatalf("Error extracting sidecarExtracted apply configuration: %v", err) + } + + expectedReappliedExtracted := appsv1ac.Deployment("nginx-apply", "default"). + WithSpec(appsv1ac.DeploymentSpec(). + WithTemplate(corev1ac.PodTemplateSpec(). + WithSpec(corev1ac.PodSpec(). + WithContainers( + corev1ac.Container(). + WithName("nginx"). + WithWorkingDir("/tmp/v2"), + corev1ac.Container(). + WithName("sidecar"). + WithImage("nginx:1.14.3"), + ), + ), + ), + ) + if !equality.Semantic.DeepEqual(expectedReappliedExtracted, reappliedExtracted) { + t.Errorf("Reapplied apply configuration did not match expected, got:\n%s\n", cmp.Diff(expectedReappliedExtracted, reappliedExtracted)) + } + + createMgrExtracted, err := appsv1ac.ExtractDeployment(reapplied, "create-mgr") + if err != nil { + t.Fatalf("Error extracting createApply apply configuration: %v", err) + } + + expectedCreateExtracted := appsv1ac.Deployment("nginx-apply", "default"). + WithSpec(appsv1ac.DeploymentSpec(). + WithSelector(metav1ac.LabelSelector(). + WithMatchLabels(map[string]string{"app": "nginx"}), + ). + WithTemplate(corev1ac.PodTemplateSpec(). + WithLabels(map[string]string{"app": "nginx"}). + WithSpec(corev1ac.PodSpec(). + WithContainers( + corev1ac.Container(). + WithName("nginx"). + WithImage("nginx:1.14.2"), + ), + ), + ), + ) + if !equality.Semantic.DeepEqual(expectedCreateExtracted, createMgrExtracted) { + t.Errorf("createMgrExtracted apply configuration did not match expected, got:\n%s\n", cmp.Diff(expectedCreateExtracted, createMgrExtracted)) + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 642aa00c014..a9c34df590e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1722,6 +1722,7 @@ k8s.io/apimachinery/pkg/util/httpstream/spdy k8s.io/apimachinery/pkg/util/intstr k8s.io/apimachinery/pkg/util/json k8s.io/apimachinery/pkg/util/jsonmergepatch +k8s.io/apimachinery/pkg/util/managedfields k8s.io/apimachinery/pkg/util/mergepatch k8s.io/apimachinery/pkg/util/naming k8s.io/apimachinery/pkg/util/net @@ -1927,6 +1928,7 @@ k8s.io/client-go/applyconfigurations/extensions/v1beta1 k8s.io/client-go/applyconfigurations/flowcontrol/v1alpha1 k8s.io/client-go/applyconfigurations/flowcontrol/v1beta1 k8s.io/client-go/applyconfigurations/imagepolicy/v1alpha1 +k8s.io/client-go/applyconfigurations/internal k8s.io/client-go/applyconfigurations/meta/v1 k8s.io/client-go/applyconfigurations/networking/v1 k8s.io/client-go/applyconfigurations/networking/v1beta1