Add extract apply function generation

This commit is contained in:
Joe Betz 2021-02-26 14:17:39 -08:00
parent 5bc72f37a4
commit 987657a80f
16 changed files with 1216 additions and 8 deletions

View File

@ -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}" \

View File

@ -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, ".")
}

View File

@ -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)
}
}

View File

@ -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
`)

View File

@ -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
)

View File

@ -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 <type-package>.<type-name>:<applyconfiguration-package> 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.

View File

@ -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)
}

View File

@ -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}}`" + `)
`

View File

@ -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<type>() 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)
}
}
}

View File

@ -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 {

View File

@ -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")
)

View File

@ -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=

View File

@ -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

View File

@ -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=

View File

@ -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<fieldname>() 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<fieldname>() 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<fieldname>() 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<fieldname>() 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))
}
}

2
vendor/modules.txt vendored
View File

@ -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