diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/BUILD b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/BUILD index b2b1827545f..e67f98c588e 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/BUILD @@ -6,6 +6,7 @@ go_library( "buildmanagerinfo.go", "capmanagers.go", "fieldmanager.go", + "managedfieldsupdater.go", "skipnonapplied.go", "stripmeta.go", "structuredmerge.go", diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go index 4e86cca18b1..bd47efe2227 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go @@ -94,6 +94,7 @@ func NewDefaultCRDFieldManager(models openapiproto.Models, objectConverter runti // newDefaultFieldManager is a helper function which wraps a Manager with certain default logic. func newDefaultFieldManager(f Manager, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind) *FieldManager { f = NewStripMetaManager(f) + f = NewManagedFieldsUpdater(f) f = NewBuildManagerInfoManager(f, kind.GroupVersion()) f = NewCapManagersManager(f, DefaultMaxUpdateManagers) f = NewProbabilisticSkipNonAppliedManager(f, objectCreater, kind, DefaultTrackOnCreateProbability) diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go index 549a198e4b9..8890b8168cf 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go @@ -104,6 +104,7 @@ func NewTestFieldManager(gvk schema.GroupVersionKind) TestFieldManager { live.SetKind(gvk.Kind) live.SetAPIVersion(gvk.GroupVersion().String()) f = fieldmanager.NewStripMetaManager(f) + f = fieldmanager.NewManagedFieldsUpdater(f) f = fieldmanager.NewBuildManagerInfoManager(f, gvk.GroupVersion()) return TestFieldManager{ fieldManager: f, diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/managedfieldsupdater.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/managedfieldsupdater.go new file mode 100644 index 00000000000..c4d6241c4e9 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/managedfieldsupdater.go @@ -0,0 +1,76 @@ +/* +Copyright 2020 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 fieldmanager + +import ( + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/structured-merge-diff/v3/fieldpath" +) + +type managedFieldsUpdater struct { + fieldManager Manager +} + +var _ Manager = &managedFieldsUpdater{} + +// NewManagedFieldsUpdater is responsible for updating the managedfields +// in the object, updating the time of the operation as necessary. For +// updates, it uses a hard-coded manager to detect if things have +// changed, and swaps back the correct manager after the operation is +// done. +func NewManagedFieldsUpdater(fieldManager Manager) Manager { + return &managedFieldsUpdater{ + fieldManager: fieldManager, + } +} + +// Update implements Manager. +func (f *managedFieldsUpdater) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { + self := "current-operation" + object, managed, err := f.fieldManager.Update(liveObj, newObj, managed, self) + if err != nil { + return object, managed, err + } + + // If the current operation took any fields from anything, it means the object changed, + // so update the timestamp of the managedFieldsEntry and merge with any previous updates from the same manager + if vs, ok := managed.Fields()[self]; ok { + delete(managed.Fields(), self) + + managed.Times()[manager] = &metav1.Time{Time: time.Now().UTC()} + if previous, ok := managed.Fields()[manager]; ok { + managed.Fields()[manager] = fieldpath.NewVersionedSet(vs.Set().Union(previous.Set()), vs.APIVersion(), vs.Applied()) + } else { + managed.Fields()[manager] = vs + } + } + + return object, managed, nil +} + +// Apply implements Manager. +func (f *managedFieldsUpdater) Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) { + object, managed, err := f.fieldManager.Apply(liveObj, appliedObj, managed, fieldManager, force) + if err != nil { + return object, managed, err + } + managed.Times()[fieldManager] = &metav1.Time{Time: time.Now().UTC()} + return object, managed, nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/structuredmerge.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/structuredmerge.go index 5cb4a723e32..e21910a3198 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/structuredmerge.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/structuredmerge.go @@ -22,7 +22,6 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal" @@ -116,26 +115,12 @@ func (f *structuredMergeManager) Update(liveObj, newObj runtime.Object, managed apiVersion := fieldpath.APIVersion(f.groupVersion.String()) // TODO(apelisse) use the first return value when unions are implemented - self := "current-operation" - _, managedFields, err := f.updater.Update(liveObjTyped, newObjTyped, apiVersion, managed.Fields(), self) + _, managedFields, err := f.updater.Update(liveObjTyped, newObjTyped, apiVersion, managed.Fields(), manager) if err != nil { return nil, nil, fmt.Errorf("failed to update ManagedFields: %v", err) } managed = internal.NewManaged(managedFields, managed.Times()) - // If the current operation took any fields from anything, it means the object changed, - // so update the timestamp of the managedFieldsEntry and merge with any previous updates from the same manager - if vs, ok := managed.Fields()[self]; ok { - delete(managed.Fields(), self) - - managed.Times()[manager] = &metav1.Time{Time: time.Now().UTC()} - if previous, ok := managed.Fields()[manager]; ok { - managed.Fields()[manager] = fieldpath.NewVersionedSet(vs.Set().Union(previous.Set()), vs.APIVersion(), vs.Applied()) - } else { - managed.Fields()[manager] = vs - } - } - return newObj, managed, nil } @@ -182,9 +167,6 @@ func (f *structuredMergeManager) Apply(liveObj, patchObj runtime.Object, managed } managed = internal.NewManaged(managedFields, managed.Times()) - // Update the time in the managedFieldsEntry for this operation - managed.Times()[manager] = &metav1.Time{Time: time.Now().UTC()} - newObj, err := f.typeConverter.TypedToObject(newObjTyped) if err != nil { return nil, nil, fmt.Errorf("failed to convert new typed object to object: %v", err)