change Apply signature and move decoding into handlers

Kubernetes-commit: ddf0d4b8034697a8dca23a3c8bc5620629bd691b
This commit is contained in:
Kevin Wiesmüller 2020-01-09 22:34:33 +01:00 committed by Kubernetes Publisher
parent 2f8e613d81
commit 41bde5b991
10 changed files with 142 additions and 49 deletions

View File

@ -51,12 +51,12 @@ func (f *buildManagerInfoManager) Update(liveObj, newObj runtime.Object, managed
}
// Apply implements Manager.
func (f *buildManagerInfoManager) Apply(liveObj runtime.Object, patch []byte, managed Managed, manager string, force bool) (runtime.Object, Managed, error) {
func (f *buildManagerInfoManager) Apply(liveObj, appliedObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) {
manager, err := f.buildManagerInfo(manager, metav1.ManagedFieldsOperationApply)
if err != nil {
return nil, nil, fmt.Errorf("failed to build manager identifier: %v", err)
}
return f.fieldManager.Apply(liveObj, patch, managed, manager, force)
return f.fieldManager.Apply(liveObj, appliedObj, managed, manager, force)
}
func (f *buildManagerInfoManager) buildManagerInfo(prefix string, operation metav1.ManagedFieldsOperationType) (string, error) {

View File

@ -58,8 +58,8 @@ func (f *capManagersManager) Update(liveObj, newObj runtime.Object, managed Mana
}
// Apply implements Manager.
func (f *capManagersManager) Apply(liveObj runtime.Object, patch []byte, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) {
return f.fieldManager.Apply(liveObj, patch, managed, fieldManager, force)
func (f *capManagersManager) Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) {
return f.fieldManager.Apply(liveObj, appliedObj, managed, fieldManager, force)
}
// capUpdateManagers merges a number of the oldest update entries into versioned buckets,

View File

@ -41,7 +41,7 @@ func (*fakeManager) Update(_, newObj runtime.Object, managed fieldmanager.Manage
return newObj, managed, nil
}
func (*fakeManager) Apply(_ runtime.Object, _ []byte, _ fieldmanager.Managed, _ string, force bool) (runtime.Object, fieldmanager.Managed, error) {
func (*fakeManager) Apply(_, _ runtime.Object, _ fieldmanager.Managed, _ string, force bool) (runtime.Object, fieldmanager.Managed, error) {
panic("not implemented")
return nil, nil, nil
}

View File

@ -51,7 +51,7 @@ type Manager interface {
// Apply is used when server-side apply is called, as it merges the
// object and updates the managed fields.
Apply(liveObj runtime.Object, patch []byte, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error)
Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error)
}
// FieldManager updates the managed fields and merge applied
@ -135,7 +135,7 @@ func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (o
// Apply is used when server-side apply is called, as it merges the
// object and updates the managed fields.
func (f *FieldManager) Apply(liveObj runtime.Object, patch []byte, manager string, force bool) (object runtime.Object, err error) {
func (f *FieldManager) Apply(liveObj, appliedObj runtime.Object, manager string, force bool) (object runtime.Object, err error) {
// If the object doesn't have metadata, apply isn't allowed.
if _, err = meta.Accessor(liveObj); err != nil {
return nil, fmt.Errorf("couldn't get accessor: %v", err)
@ -149,7 +149,7 @@ func (f *FieldManager) Apply(liveObj runtime.Object, patch []byte, manager strin
internal.RemoveObjectManagedFields(liveObj)
if object, managed, err = f.fieldManager.Apply(liveObj, patch, managed, manager, force); err != nil {
if object, managed, err = f.fieldManager.Apply(liveObj, appliedObj, managed, manager, force); err != nil {
return nil, err
}

View File

@ -106,7 +106,7 @@ func (f *TestFieldManager) Reset() {
f.liveObj = f.emptyObj.DeepCopyObject()
}
func (f *TestFieldManager) Apply(obj []byte, manager string, force bool) error {
func (f *TestFieldManager) Apply(obj runtime.Object, manager string, force bool) error {
out, err := fieldmanager.NewFieldManager(f.fieldManager).Apply(f.liveObj, obj, manager, force)
if err == nil {
f.liveObj = out
@ -174,7 +174,8 @@ func TestUpdateApplyConflict(t *testing.T) {
t.Fatalf("failed to apply object: %v", err)
}
err := f.Apply([]byte(`{
appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
@ -183,7 +184,11 @@ func TestUpdateApplyConflict(t *testing.T) {
"spec": {
"replicas": 101,
}
}`), "fieldmanager_conflict", false)
}`), &appliedObj.Object); err != nil {
t.Fatalf("error decoding YAML: %v", err)
}
err := f.Apply(appliedObj, "fieldmanager_conflict", false)
if err == nil || !apierrors.IsConflict(err) {
t.Fatalf("Expecting to get conflicts but got %v", err)
}
@ -225,20 +230,68 @@ func TestApplyStripsFields(t *testing.T) {
func TestVersionCheck(t *testing.T) {
f := NewTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment"))
// patch has 'apiVersion: apps/v1' and live version is apps/v1 -> no errors
err := f.Apply([]byte(`{
appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
}`), "fieldmanager_test", false)
}`), &appliedObj.Object); err != nil {
t.Fatalf("error decoding YAML: %v", err)
}
// patch has 'apiVersion: apps/v1' and live version is apps/v1 -> no errors
err := f.Apply(appliedObj, "fieldmanager_test", false)
if err != nil {
t.Fatalf("failed to apply object: %v", err)
}
// patch has 'apiVersion: apps/v2' but live version is apps/v1 -> error
err = f.Apply([]byte(`{
appliedObj = &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{
"apiVersion": "apps/v1beta1",
"kind": "Deployment",
}`), "fieldmanager_test", false)
}`), &appliedObj.Object); err != nil {
t.Fatalf("error decoding YAML: %v", err)
}
// patch has 'apiVersion: apps/v2' but live version is apps/v1 -> error
err = f.Apply(appliedObj, "fieldmanager_test", false)
if err == nil {
t.Fatalf("expected an error from mismatched patch and live versions")
}
switch typ := err.(type) {
default:
t.Fatalf("expected error to be of type %T was %T", apierrors.StatusError{}, typ)
case apierrors.APIStatus:
if typ.Status().Code != http.StatusBadRequest {
t.Fatalf("expected status code to be %d but was %d",
http.StatusBadRequest, typ.Status().Code)
}
}
}
func TestVersionCheckDoesNotPanic(t *testing.T) {
f := NewTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment"))
appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
}`), &appliedObj.Object); err != nil {
t.Fatalf("error decoding YAML: %v", err)
}
// patch has 'apiVersion: apps/v1' and live version is apps/v1 -> no errors
err := f.Apply(appliedObj, "fieldmanager_test", false)
if err != nil {
t.Fatalf("failed to apply object: %v", err)
}
appliedObj = &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{
}`), &appliedObj.Object); err != nil {
t.Fatalf("error decoding YAML: %v", err)
}
// patch has 'apiVersion: apps/v2' but live version is apps/v1 -> error
err = f.Apply(appliedObj, "fieldmanager_test", false)
if err == nil {
t.Fatalf("expected an error from mismatched patch and live versions")
}
@ -256,7 +309,8 @@ func TestVersionCheck(t *testing.T) {
func TestApplyDoesNotStripLabels(t *testing.T) {
f := NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"))
err := f.Apply([]byte(`{
appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
@ -264,7 +318,11 @@ func TestApplyDoesNotStripLabels(t *testing.T) {
"a": "b"
},
}
}`), "fieldmanager_test", false)
}`), &appliedObj.Object); err != nil {
t.Fatalf("error decoding YAML: %v", err)
}
err := f.Apply(appliedObj, "fieldmanager_test", false)
if err != nil {
t.Fatalf("failed to apply object: %v", err)
}
@ -305,7 +363,12 @@ func TestApplyNewObject(t *testing.T) {
t.Run(test.gvk.String(), func(t *testing.T) {
f := NewTestFieldManager(test.gvk)
if err := f.Apply(test.obj, "fieldmanager_test", false); err != nil {
appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal(test.obj, &appliedObj.Object); err != nil {
t.Fatalf("error decoding YAML: %v", err)
}
if err := f.Apply(appliedObj, "fieldmanager_test", false); err != nil {
t.Fatal(err)
}
})
@ -362,7 +425,11 @@ func BenchmarkNewObject(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
err := f.Apply(test.obj, "fieldmanager_test", false)
appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal(test.obj, &appliedObj.Object); err != nil {
b.Fatalf("error decoding YAML: %v", err)
}
err := f.Apply(appliedObj, "fieldmanager_test", false)
if err != nil {
b.Fatal(err)
}
@ -387,7 +454,12 @@ func BenchmarkRepeatedUpdate(b *testing.B) {
obj.Spec.Containers[0].Image = "nginx:4.3"
objs = append(objs, obj)
err := f.Apply(podBytes, "fieldmanager_apply", false)
appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal(podBytes, &appliedObj.Object); err != nil {
b.Fatalf("error decoding YAML: %v", err)
}
err := f.Apply(appliedObj, "fieldmanager_apply", false)
if err != nil {
b.Fatal(err)
}
@ -410,7 +482,8 @@ func BenchmarkRepeatedUpdate(b *testing.B) {
func TestApplyFailsWithManagedFields(t *testing.T) {
f := NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"))
err := f.Apply([]byte(`{
appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
@ -420,7 +493,11 @@ func TestApplyFailsWithManagedFields(t *testing.T) {
}
]
}
}`), "fieldmanager_test", false)
}`), &appliedObj.Object); err != nil {
t.Fatalf("error decoding YAML: %v", err)
}
err := f.Apply(appliedObj, "fieldmanager_test", false)
if err == nil {
t.Fatalf("successfully applied with set managed fields")
@ -430,7 +507,8 @@ func TestApplyFailsWithManagedFields(t *testing.T) {
func TestApplySuccessWithNoManagedFields(t *testing.T) {
f := NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"))
err := f.Apply([]byte(`{
appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
@ -438,7 +516,10 @@ func TestApplySuccessWithNoManagedFields(t *testing.T) {
"a": "b"
},
}
}`), "fieldmanager_test", false)
}`), &appliedObj.Object); err != nil {
t.Fatalf("error decoding YAML: %v", err)
}
err := f.Apply(appliedObj, "fieldmanager_test", false)
if err != nil {
t.Fatalf("failed to apply object: %v", err)

View File

@ -51,7 +51,7 @@ func (f *skipNonAppliedManager) Update(liveObj, newObj runtime.Object, managed M
}
// Apply implements Manager.
func (f *skipNonAppliedManager) Apply(liveObj runtime.Object, patch []byte, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) {
func (f *skipNonAppliedManager) Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) {
if len(managed.Fields()) == 0 {
emptyObj, err := f.objectCreater.New(f.gvk)
if err != nil {
@ -62,5 +62,5 @@ func (f *skipNonAppliedManager) Apply(liveObj runtime.Object, patch []byte, mana
return nil, nil, fmt.Errorf("failed to create manager for existing fields: %v", err)
}
}
return f.fieldManager.Apply(liveObj, patch, managed, fieldManager, force)
return f.fieldManager.Apply(liveObj, appliedObj, managed, fieldManager, force)
}

View File

@ -26,6 +26,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
"sigs.k8s.io/yaml"
)
type fakeObjectCreater struct {
@ -49,7 +50,8 @@ func TestNoUpdateBeforeFirstApply(t *testing.T) {
schema.GroupVersionKind{},
)
if err := f.Apply([]byte(`{
appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
@ -62,7 +64,11 @@ func TestNoUpdateBeforeFirstApply(t *testing.T) {
"image": "nginx:latest"
}]
}
}`), "fieldmanager_test_apply", false); err != nil {
}`), &appliedObj.Object); err != nil {
t.Fatalf("error decoding YAML: %v", err)
}
if err := f.Apply(appliedObj, "fieldmanager_test_apply", false); err != nil {
t.Fatalf("failed to update object: %v", err)
}
@ -96,7 +102,8 @@ func TestUpdateBeforeFirstApply(t *testing.T) {
t.Fatalf("managedFields were tracked on update only: %v", m)
}
appliedBytes := []byte(`{
appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
@ -109,9 +116,11 @@ func TestUpdateBeforeFirstApply(t *testing.T) {
"image": "nginx:latest"
}]
}
}`)
}`), &appliedObj.Object); err != nil {
t.Fatalf("error decoding YAML: %v", err)
}
err := f.Apply(appliedBytes, "fieldmanager_test_apply", false)
err := f.Apply(appliedObj, "fieldmanager_test_apply", false)
apiStatus, _ := err.(apierrors.APIStatus)
if err == nil || !apierrors.IsConflict(err) || len(apiStatus.Status().Details.Causes) != 1 {
t.Fatalf("Expecting to get one conflict but got %v", err)
@ -125,7 +134,7 @@ func TestUpdateBeforeFirstApply(t *testing.T) {
t.Fatalf("Expecting conflict message to contain %q but got %q: %v", e, a, err)
}
if err := f.Apply(appliedBytes, "fieldmanager_test_apply", true); err != nil {
if err := f.Apply(appliedObj, "fieldmanager_test_apply", true); err != nil {
t.Fatalf("failed to update object: %v", err)
}

View File

@ -64,13 +64,13 @@ func (f *stripMetaManager) Update(liveObj, newObj runtime.Object, managed Manage
}
// Apply implements Manager.
func (f *stripMetaManager) Apply(liveObj runtime.Object, patch []byte, managed Managed, manager string, force bool) (runtime.Object, Managed, error) {
newObj, managed, err := f.fieldManager.Apply(liveObj, patch, managed, manager, force)
func (f *stripMetaManager) Apply(liveObj, appliedObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) {
appliedObj, managed, err := f.fieldManager.Apply(liveObj, appliedObj, managed, manager, force)
if err != nil {
return nil, nil, err
}
f.stripFields(managed.Fields(), manager)
return newObj, managed, nil
return appliedObj, managed, nil
}
// stripFields removes a predefined set of paths found in typed from managed

View File

@ -21,8 +21,8 @@ import (
"time"
"k8s.io/apimachinery/pkg/api/errors"
"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"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
@ -30,7 +30,6 @@ import (
openapiproto "k8s.io/kube-openapi/pkg/util/proto"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/merge"
"sigs.k8s.io/yaml"
)
type structuredMergeManager struct {
@ -136,22 +135,18 @@ func (f *structuredMergeManager) Update(liveObj, newObj runtime.Object, managed
}
// Apply implements Manager.
func (f *structuredMergeManager) Apply(liveObj runtime.Object, patch []byte, managed Managed, manager string, force bool) (runtime.Object, Managed, error) {
patchObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal(patch, &patchObj.Object); err != nil {
return nil, nil, errors.NewBadRequest(fmt.Sprintf("error decoding YAML: %v", err))
}
func (f *structuredMergeManager) Apply(liveObj, patchObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) {
// Check that the patch object has the same version as the live object
if patchObj.GetAPIVersion() != f.groupVersion.String() {
if patchObj.GetObjectKind().GroupVersionKind().GroupVersion() != f.groupVersion {
return nil, nil,
errors.NewBadRequest(
fmt.Sprintf("Incorrect version specified in apply patch. "+
"Specified patch version: %s, expected: %s",
patchObj.GetAPIVersion(), f.groupVersion.String()))
patchObj.GetObjectKind().GroupVersionKind().GroupVersion(), f.groupVersion))
}
if patchObj.GetManagedFields() != nil {
patchObjMeta, err := meta.Accessor(patchObj)
if patchObjMeta.GetManagedFields() != nil {
return nil, nil, errors.NewBadRequest(fmt.Sprintf("metadata.managedFields must be nil"))
}

View File

@ -28,6 +28,7 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -48,6 +49,7 @@ import (
"k8s.io/apiserver/pkg/util/dryrun"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utiltrace "k8s.io/utils/trace"
"sigs.k8s.io/yaml"
)
const (
@ -433,7 +435,13 @@ func (p *applyPatcher) applyPatchToCurrentObject(obj runtime.Object) (runtime.Ob
if p.fieldManager == nil {
panic("FieldManager must be installed to run apply")
}
return p.fieldManager.Apply(obj, p.patch, p.options.FieldManager, force)
patchObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal(p.patch, &patchObj.Object); err != nil {
return nil, errors.NewBadRequest(fmt.Sprintf("error decoding YAML: %v", err))
}
return p.fieldManager.Apply(obj, patchObj, p.options.FieldManager, force)
}
func (p *applyPatcher) createNewObject() (runtime.Object, error) {