Merge pull request #114868 from apelisse/private-internal-managers

fieldmanager: Make internal managers private
This commit is contained in:
Kubernetes Prow Robot 2023-01-10 16:33:19 -08:00 committed by GitHub
commit 7e97b4b322
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 597 additions and 419 deletions

View File

@ -679,7 +679,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
openAPIModels = nil openAPIModels = nil
} }
var typeConverter fieldmanager.TypeConverter = fieldmanager.DeducedTypeConverter{} var typeConverter fieldmanager.TypeConverter = fieldmanager.NewDeducedTypeConverter()
if openAPIModels != nil { if openAPIModels != nil {
typeConverter, err = fieldmanager.NewTypeConverter(openAPIModels, crd.Spec.PreserveUnknownFields) typeConverter, err = fieldmanager.NewTypeConverter(openAPIModels, crd.Spec.PreserveUnknownFields)
if err != nil { if err != nil {

View File

@ -22,6 +22,7 @@ import (
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"k8s.io/apiserver/pkg/warning" "k8s.io/apiserver/pkg/warning"
) )
@ -70,7 +71,7 @@ func (admit *managedFieldsValidatingAdmissionController) Admit(ctx context.Conte
return err return err
} }
managedFieldsAfterAdmission := objectMeta.GetManagedFields() managedFieldsAfterAdmission := objectMeta.GetManagedFields()
if _, err := DecodeManagedFields(managedFieldsAfterAdmission); err != nil { if _, err := internal.DecodeManagedFields(managedFieldsAfterAdmission); err != nil {
objectMeta.SetManagedFields(managedFieldsBeforeAdmission) objectMeta.SetManagedFields(managedFieldsBeforeAdmission)
warning.AddWarning(ctx, "", warning.AddWarning(ctx, "",
fmt.Sprintf(InvalidManagedFieldsAfterMutatingAdmissionWarningFormat, fmt.Sprintf(InvalidManagedFieldsAfterMutatingAdmissionWarningFormat,

View File

@ -18,247 +18,40 @@ package fieldmanager
import ( import (
"fmt" "fmt"
"reflect"
"time"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"k8s.io/klog/v2"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
"sigs.k8s.io/structured-merge-diff/v4/merge"
) )
// DefaultMaxUpdateManagers defines the default maximum retained number of managedFields entries from updates // FieldManager updates the managed fields and merges applied
// if the number of update managers exceeds this, the oldest entries will be merged until the number is below the maximum.
// TODO(jennybuckley): Determine if this is really the best value. Ideally we wouldn't unnecessarily merge too many entries.
const DefaultMaxUpdateManagers int = 10
// DefaultTrackOnCreateProbability defines the default probability that the field management of an object
// starts being tracked from the object's creation, instead of from the first time the object is applied to.
const DefaultTrackOnCreateProbability float32 = 1
var atMostEverySecond = internal.NewAtMostEvery(time.Second)
// Managed groups a fieldpath.ManagedFields together with the timestamps associated with each operation.
type Managed interface {
// Fields gets the fieldpath.ManagedFields.
Fields() fieldpath.ManagedFields
// Times gets the timestamps associated with each operation.
Times() map[string]*metav1.Time
}
// Manager updates the managed fields and merges applied configurations.
type Manager interface {
// Update is used when the object has already been merged (non-apply
// use-case), and simply updates the managed fields in the output
// object.
// * `liveObj` is not mutated by this function
// * `newObj` may be mutated by this function
// Returns the new object with managedFields removed, and the object's new
// proposed managedFields separately.
Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error)
// Apply is used when server-side apply is called, as it merges the
// object and updates the managed fields.
// * `liveObj` is not mutated by this function
// * `newObj` may be mutated by this function
// Returns the new object with managedFields removed, and the object's new
// proposed managedFields separately.
Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error)
}
// FieldManager updates the managed fields and merge applied
// configurations. // configurations.
type FieldManager struct { type FieldManager = internal.FieldManager
fieldManager Manager
subresource string
}
// NewFieldManager creates a new FieldManager that decodes, manages, then re-encodes managedFields
// on update and apply requests.
func NewFieldManager(f Manager, subresource string) *FieldManager {
return &FieldManager{fieldManager: f, subresource: subresource}
}
// NewDefaultFieldManager creates a new FieldManager that merges apply requests // NewDefaultFieldManager creates a new FieldManager that merges apply requests
// and update managed fields for other types of requests. // and update managed fields for other types of requests.
func NewDefaultFieldManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion, subresource string, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (*FieldManager, error) { func NewDefaultFieldManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion, subresource string, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (*FieldManager, error) {
f, err := NewStructuredMergeManager(typeConverter, objectConverter, objectDefaulter, kind.GroupVersion(), hub, resetFields) f, err := internal.NewStructuredMergeManager(typeConverter, objectConverter, objectDefaulter, kind.GroupVersion(), hub, resetFields)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create field manager: %v", err) return nil, fmt.Errorf("failed to create field manager: %v", err)
} }
return newDefaultFieldManager(f, typeConverter, objectConverter, objectCreater, kind, subresource), nil return internal.NewDefaultFieldManager(f, typeConverter, objectConverter, objectCreater, kind, subresource), nil
} }
// NewDefaultCRDFieldManager creates a new FieldManager specifically for // NewDefaultCRDFieldManager creates a new FieldManager specifically for
// CRDs. This allows for the possibility of fields which are not defined // CRDs. This allows for the possibility of fields which are not defined
// in models, as well as having no models defined at all. // in models, as well as having no models defined at all.
func NewDefaultCRDFieldManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion, subresource string, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (_ *FieldManager, err error) { func NewDefaultCRDFieldManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion, subresource string, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (_ *FieldManager, err error) {
f, err := NewCRDStructuredMergeManager(typeConverter, objectConverter, objectDefaulter, kind.GroupVersion(), hub, resetFields) f, err := internal.NewCRDStructuredMergeManager(typeConverter, objectConverter, objectDefaulter, kind.GroupVersion(), hub, resetFields)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create field manager: %v", err) return nil, fmt.Errorf("failed to create field manager: %v", err)
} }
return newDefaultFieldManager(f, typeConverter, objectConverter, objectCreater, kind, subresource), nil return internal.NewDefaultFieldManager(f, typeConverter, objectConverter, objectCreater, kind, subresource), nil
} }
// newDefaultFieldManager is a helper function which wraps a Manager with certain default logic. func ValidateManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) error {
func newDefaultFieldManager(f Manager, typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, subresource string) *FieldManager { _, err := internal.DecodeManagedFields(encodedManagedFields)
return NewFieldManager( return err
NewLastAppliedUpdater(
NewLastAppliedManager(
NewProbabilisticSkipNonAppliedManager(
NewCapManagersManager(
NewBuildManagerInfoManager(
NewManagedFieldsUpdater(
NewStripMetaManager(f),
), kind.GroupVersion(), subresource,
), DefaultMaxUpdateManagers,
), objectCreater, kind, DefaultTrackOnCreateProbability,
), typeConverter, objectConverter, kind.GroupVersion()),
), subresource,
)
}
// DecodeManagedFields converts ManagedFields from the wire format (api format)
// to the format used by sigs.k8s.io/structured-merge-diff
func DecodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (Managed, error) {
return internal.DecodeManagedFields(encodedManagedFields)
}
func decodeLiveOrNew(liveObj, newObj runtime.Object, ignoreManagedFieldsFromRequestObject bool) (Managed, error) {
liveAccessor, err := meta.Accessor(liveObj)
if err != nil {
return nil, err
}
// We take the managedFields of the live object in case the request tries to
// manually set managedFields via a subresource.
if ignoreManagedFieldsFromRequestObject {
return emptyManagedFieldsOnErr(DecodeManagedFields(liveAccessor.GetManagedFields()))
}
// If the object doesn't have metadata, we should just return without trying to
// set the managedFields at all, so creates/updates/patches will work normally.
newAccessor, err := meta.Accessor(newObj)
if err != nil {
return nil, err
}
if isResetManagedFields(newAccessor.GetManagedFields()) {
return internal.NewEmptyManaged(), nil
}
// If the managed field is empty or we failed to decode it,
// let's try the live object. This is to prevent clients who
// don't understand managedFields from deleting it accidentally.
managed, err := DecodeManagedFields(newAccessor.GetManagedFields())
if err != nil || len(managed.Fields()) == 0 {
return emptyManagedFieldsOnErr(DecodeManagedFields(liveAccessor.GetManagedFields()))
}
return managed, nil
}
func emptyManagedFieldsOnErr(managed Managed, err error) (Managed, error) {
if err != nil {
return internal.NewEmptyManaged(), nil
}
return managed, nil
}
// Update is used when the object has already been merged (non-apply
// use-case), and simply updates the managed fields in the output
// object.
func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (object runtime.Object, err error) {
// First try to decode the managed fields provided in the update,
// This is necessary to allow directly updating managed fields.
isSubresource := f.subresource != ""
managed, err := decodeLiveOrNew(liveObj, newObj, isSubresource)
if err != nil {
return newObj, nil
}
internal.RemoveObjectManagedFields(newObj)
if object, managed, err = f.fieldManager.Update(liveObj, newObj, managed, manager); err != nil {
return nil, err
}
if err = internal.EncodeObjectManagedFields(object, managed); err != nil {
return nil, fmt.Errorf("failed to encode managed fields: %v", err)
}
return object, nil
}
// UpdateNoErrors is the same as Update, but it will not return
// errors. If an error happens, the object is returned with
// managedFields cleared.
func (f *FieldManager) UpdateNoErrors(liveObj, newObj runtime.Object, manager string) runtime.Object {
obj, err := f.Update(liveObj, newObj, manager)
if err != nil {
atMostEverySecond.Do(func() {
ns, name := "unknown", "unknown"
if accessor, err := meta.Accessor(newObj); err == nil {
ns = accessor.GetNamespace()
name = accessor.GetName()
}
klog.ErrorS(err, "[SHOULD NOT HAPPEN] failed to update managedFields", "VersionKind",
newObj.GetObjectKind().GroupVersionKind(), "namespace", ns, "name", name)
})
// Explicitly remove managedFields on failure, so that
// we can't have garbage in it.
internal.RemoveObjectManagedFields(newObj)
return newObj
}
return obj
}
// Returns true if the managedFields indicate that the user is trying to
// reset the managedFields, i.e. if the list is non-nil but empty, or if
// the list has one empty item.
func isResetManagedFields(managedFields []metav1.ManagedFieldsEntry) bool {
if len(managedFields) == 0 {
return managedFields != nil
}
if len(managedFields) == 1 {
return reflect.DeepEqual(managedFields[0], metav1.ManagedFieldsEntry{})
}
return false
}
// Apply is used when server-side apply is called, as it merges the
// object and updates the managed fields.
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.
accessor, err := meta.Accessor(liveObj)
if err != nil {
return nil, fmt.Errorf("couldn't get accessor: %v", err)
}
// Decode the managed fields in the live object, since it isn't allowed in the patch.
managed, err := DecodeManagedFields(accessor.GetManagedFields())
if err != nil {
return nil, fmt.Errorf("failed to decode managed fields: %v", err)
}
object, managed, err = f.fieldManager.Apply(liveObj, appliedObj, managed, manager, force)
if err != nil {
if conflicts, ok := err.(merge.Conflicts); ok {
return nil, internal.NewConflictError(conflicts)
}
return nil, err
}
if err = internal.EncodeObjectManagedFields(object, managed); err != nil {
return nil, fmt.Errorf("failed to encode managed fields: %v", err)
}
return object, nil
} }

View File

@ -26,6 +26,7 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
"sigs.k8s.io/structured-merge-diff/v4/merge" "sigs.k8s.io/structured-merge-diff/v4/merge"
"sigs.k8s.io/structured-merge-diff/v4/typed" "sigs.k8s.io/structured-merge-diff/v4/typed"
@ -95,7 +96,7 @@ func (f *fakeObjectCreater) New(gvk schema.GroupVersionKind) (runtime.Object, er
// to specify either a sub-resource, or a set of modified Manager to // to specify either a sub-resource, or a set of modified Manager to
// test them specifically. // test them specifically.
type TestFieldManager struct { type TestFieldManager struct {
fieldManager *fieldmanager.FieldManager fieldManager *internal.FieldManager
apiVersion string apiVersion string
emptyObj runtime.Object emptyObj runtime.Object
liveObj runtime.Object liveObj runtime.Object
@ -108,10 +109,10 @@ func NewDefaultTestFieldManager(typeConverter fieldmanager.TypeConverter, gvk sc
} }
// NewTestFieldManager creates a new manager for the given GVK. // NewTestFieldManager creates a new manager for the given GVK.
func NewTestFieldManager(typeConverter fieldmanager.TypeConverter, gvk schema.GroupVersionKind, subresource string, chainFieldManager func(fieldmanager.Manager) fieldmanager.Manager) TestFieldManager { func NewTestFieldManager(typeConverter fieldmanager.TypeConverter, gvk schema.GroupVersionKind, subresource string, chainFieldManager func(internal.Manager) internal.Manager) TestFieldManager {
apiVersion := fieldpath.APIVersion(gvk.GroupVersion().String()) apiVersion := fieldpath.APIVersion(gvk.GroupVersion().String())
objectConverter := &fakeObjectConvertor{sameVersionConverter{}, apiVersion} objectConverter := &fakeObjectConvertor{sameVersionConverter{}, apiVersion}
f, err := fieldmanager.NewStructuredMergeManager( f, err := internal.NewStructuredMergeManager(
typeConverter, typeConverter,
objectConverter, objectConverter,
&fakeObjectDefaulter{}, &fakeObjectDefaulter{},
@ -125,14 +126,18 @@ func NewTestFieldManager(typeConverter fieldmanager.TypeConverter, gvk schema.Gr
live := &unstructured.Unstructured{} live := &unstructured.Unstructured{}
live.SetKind(gvk.Kind) live.SetKind(gvk.Kind)
live.SetAPIVersion(gvk.GroupVersion().String()) live.SetAPIVersion(gvk.GroupVersion().String())
f = fieldmanager.NewLastAppliedUpdater( // This is different from `internal.NewDefaultFieldManager` because:
fieldmanager.NewLastAppliedManager( // 1. We don't want to create a `internal.FieldManager`
fieldmanager.NewProbabilisticSkipNonAppliedManager( // 2. We don't want to use the CapManager that is tested separately with
fieldmanager.NewBuildManagerInfoManager( // a smaller than the default cap.
fieldmanager.NewManagedFieldsUpdater( f = internal.NewLastAppliedUpdater(
fieldmanager.NewStripMetaManager(f), internal.NewLastAppliedManager(
internal.NewProbabilisticSkipNonAppliedManager(
internal.NewBuildManagerInfoManager(
internal.NewManagedFieldsUpdater(
internal.NewStripMetaManager(f),
), gvk.GroupVersion(), subresource, ), gvk.GroupVersion(), subresource,
), NewFakeObjectCreater(), gvk, fieldmanager.DefaultTrackOnCreateProbability, ), NewFakeObjectCreater(), gvk, internal.DefaultTrackOnCreateProbability,
), typeConverter, objectConverter, gvk.GroupVersion(), ), typeConverter, objectConverter, gvk.GroupVersion(),
), ),
) )
@ -140,7 +145,7 @@ func NewTestFieldManager(typeConverter fieldmanager.TypeConverter, gvk schema.Gr
f = chainFieldManager(f) f = chainFieldManager(f)
} }
return TestFieldManager{ return TestFieldManager{
fieldManager: fieldmanager.NewFieldManager(f, subresource), fieldManager: internal.NewFieldManager(f, subresource),
apiVersion: gvk.GroupVersion().String(), apiVersion: gvk.GroupVersion().String(),
emptyObj: live, emptyObj: live,
liveObj: live.DeepCopyObject(), liveObj: live.DeepCopyObject(),

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager package internal
import ( import (
"fmt" "fmt"
@ -22,7 +22,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
) )
type buildManagerInfoManager struct { type buildManagerInfoManager struct {
@ -71,5 +70,5 @@ func (f *buildManagerInfoManager) buildManagerInfo(prefix string, operation meta
if managerInfo.Manager == "" { if managerInfo.Manager == "" {
managerInfo.Manager = "unknown" managerInfo.Manager = "unknown"
} }
return internal.BuildManagerIdentifier(&managerInfo) return BuildManagerIdentifier(&managerInfo)
} }

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager package internal
import ( import (
"fmt" "fmt"
@ -22,7 +22,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
) )
@ -100,7 +99,7 @@ func (f *capManagersManager) capUpdateManagers(managed Managed) (newManaged Mana
// Create a new manager identifier for the versioned bucket entry. // Create a new manager identifier for the versioned bucket entry.
// The version for this manager comes from the version of the update being merged into the bucket. // The version for this manager comes from the version of the update being merged into the bucket.
bucket, err := internal.BuildManagerIdentifier(&metav1.ManagedFieldsEntry{ bucket, err := BuildManagerIdentifier(&metav1.ManagedFieldsEntry{
Manager: f.oldUpdatesManagerName, Manager: f.oldUpdatesManagerName,
Operation: metav1.ManagedFieldsOperationUpdate, Operation: metav1.ManagedFieldsOperationUpdate,
APIVersion: version, APIVersion: version,

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager_test package internal_test
import ( import (
"bytes" "bytes"
@ -29,20 +29,20 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanagertest" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanagertest"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
) )
type fakeManager struct{} type fakeManager struct{}
var _ fieldmanager.Manager = &fakeManager{} var _ internal.Manager = &fakeManager{}
func (*fakeManager) Update(_, newObj runtime.Object, managed fieldmanager.Managed, _ string) (runtime.Object, fieldmanager.Managed, error) { func (*fakeManager) Update(_, newObj runtime.Object, managed internal.Managed, _ string) (runtime.Object, internal.Managed, error) {
return newObj, managed, nil return newObj, managed, nil
} }
func (*fakeManager) Apply(_, _ runtime.Object, _ fieldmanager.Managed, _ string, _ bool) (runtime.Object, fieldmanager.Managed, error) { func (*fakeManager) Apply(_, _ runtime.Object, _ internal.Managed, _ string, _ bool) (runtime.Object, internal.Managed, error) {
panic("not implemented") panic("not implemented")
return nil, nil, nil return nil, nil, nil
} }
@ -50,8 +50,8 @@ func (*fakeManager) Apply(_, _ runtime.Object, _ fieldmanager.Managed, _ string,
func TestCapManagersManagerMergesEntries(t *testing.T) { func TestCapManagersManagerMergesEntries(t *testing.T) {
f := fieldmanagertest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"), f := fieldmanagertest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"),
"", "",
func(m fieldmanager.Manager) fieldmanager.Manager { func(m internal.Manager) internal.Manager {
return fieldmanager.NewCapManagersManager(m, 3) return internal.NewCapManagersManager(m, 3)
}) })
podWithLabels := func(labels ...string) runtime.Object { podWithLabels := func(labels ...string) runtime.Object {
@ -116,8 +116,8 @@ func TestCapManagersManagerMergesEntries(t *testing.T) {
func TestCapUpdateManagers(t *testing.T) { func TestCapUpdateManagers(t *testing.T) {
f := fieldmanagertest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"), f := fieldmanagertest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"),
"", "",
func(m fieldmanager.Manager) fieldmanager.Manager { func(m internal.Manager) internal.Manager {
return fieldmanager.NewCapManagersManager(m, 3) return internal.NewCapManagersManager(m, 3)
}) })
set := func(fields ...string) *metav1.FieldsV1 { set := func(fields ...string) *metav1.FieldsV1 {

View File

@ -0,0 +1,206 @@
/*
Copyright 2022 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 internal
import (
"fmt"
"reflect"
"time"
"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/klog/v2"
"sigs.k8s.io/structured-merge-diff/v4/merge"
)
// DefaultMaxUpdateManagers defines the default maximum retained number of managedFields entries from updates
// if the number of update managers exceeds this, the oldest entries will be merged until the number is below the maximum.
// TODO(jennybuckley): Determine if this is really the best value. Ideally we wouldn't unnecessarily merge too many entries.
const DefaultMaxUpdateManagers int = 10
// DefaultTrackOnCreateProbability defines the default probability that the field management of an object
// starts being tracked from the object's creation, instead of from the first time the object is applied to.
const DefaultTrackOnCreateProbability float32 = 1
var atMostEverySecond = NewAtMostEvery(time.Second)
// FieldManager updates the managed fields and merges applied
// configurations.
type FieldManager struct {
fieldManager Manager
subresource string
}
// NewFieldManager creates a new FieldManager that decodes, manages, then re-encodes managedFields
// on update and apply requests.
func NewFieldManager(f Manager, subresource string) *FieldManager {
return &FieldManager{fieldManager: f, subresource: subresource}
}
// newDefaultFieldManager is a helper function which wraps a Manager with certain default logic.
func NewDefaultFieldManager(f Manager, typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, subresource string) *FieldManager {
return NewFieldManager(
NewLastAppliedUpdater(
NewLastAppliedManager(
NewProbabilisticSkipNonAppliedManager(
NewCapManagersManager(
NewBuildManagerInfoManager(
NewManagedFieldsUpdater(
NewStripMetaManager(f),
), kind.GroupVersion(), subresource,
), DefaultMaxUpdateManagers,
), objectCreater, kind, DefaultTrackOnCreateProbability,
), typeConverter, objectConverter, kind.GroupVersion()),
), subresource,
)
}
func decodeLiveOrNew(liveObj, newObj runtime.Object, ignoreManagedFieldsFromRequestObject bool) (Managed, error) {
liveAccessor, err := meta.Accessor(liveObj)
if err != nil {
return nil, err
}
// We take the managedFields of the live object in case the request tries to
// manually set managedFields via a subresource.
if ignoreManagedFieldsFromRequestObject {
return emptyManagedFieldsOnErr(DecodeManagedFields(liveAccessor.GetManagedFields()))
}
// If the object doesn't have metadata, we should just return without trying to
// set the managedFields at all, so creates/updates/patches will work normally.
newAccessor, err := meta.Accessor(newObj)
if err != nil {
return nil, err
}
if isResetManagedFields(newAccessor.GetManagedFields()) {
return NewEmptyManaged(), nil
}
// If the managed field is empty or we failed to decode it,
// let's try the live object. This is to prevent clients who
// don't understand managedFields from deleting it accidentally.
managed, err := DecodeManagedFields(newAccessor.GetManagedFields())
if err != nil || len(managed.Fields()) == 0 {
return emptyManagedFieldsOnErr(DecodeManagedFields(liveAccessor.GetManagedFields()))
}
return managed, nil
}
func emptyManagedFieldsOnErr(managed Managed, err error) (Managed, error) {
if err != nil {
return NewEmptyManaged(), nil
}
return managed, nil
}
// Update is used when the object has already been merged (non-apply
// use-case), and simply updates the managed fields in the output
// object.
func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (object runtime.Object, err error) {
// First try to decode the managed fields provided in the update,
// This is necessary to allow directly updating managed fields.
isSubresource := f.subresource != ""
managed, err := decodeLiveOrNew(liveObj, newObj, isSubresource)
if err != nil {
return newObj, nil
}
RemoveObjectManagedFields(newObj)
if object, managed, err = f.fieldManager.Update(liveObj, newObj, managed, manager); err != nil {
return nil, err
}
if err = EncodeObjectManagedFields(object, managed); err != nil {
return nil, fmt.Errorf("failed to encode managed fields: %v", err)
}
return object, nil
}
// UpdateNoErrors is the same as Update, but it will not return
// errors. If an error happens, the object is returned with
// managedFields cleared.
func (f *FieldManager) UpdateNoErrors(liveObj, newObj runtime.Object, manager string) runtime.Object {
obj, err := f.Update(liveObj, newObj, manager)
if err != nil {
atMostEverySecond.Do(func() {
ns, name := "unknown", "unknown"
if accessor, err := meta.Accessor(newObj); err == nil {
ns = accessor.GetNamespace()
name = accessor.GetName()
}
klog.ErrorS(err, "[SHOULD NOT HAPPEN] failed to update managedFields", "VersionKind",
newObj.GetObjectKind().GroupVersionKind(), "namespace", ns, "name", name)
})
// Explicitly remove managedFields on failure, so that
// we can't have garbage in it.
RemoveObjectManagedFields(newObj)
return newObj
}
return obj
}
// Returns true if the managedFields indicate that the user is trying to
// reset the managedFields, i.e. if the list is non-nil but empty, or if
// the list has one empty item.
func isResetManagedFields(managedFields []metav1.ManagedFieldsEntry) bool {
if len(managedFields) == 0 {
return managedFields != nil
}
if len(managedFields) == 1 {
return reflect.DeepEqual(managedFields[0], metav1.ManagedFieldsEntry{})
}
return false
}
// Apply is used when server-side apply is called, as it merges the
// object and updates the managed fields.
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.
accessor, err := meta.Accessor(liveObj)
if err != nil {
return nil, fmt.Errorf("couldn't get accessor: %v", err)
}
// Decode the managed fields in the live object, since it isn't allowed in the patch.
managed, err := DecodeManagedFields(accessor.GetManagedFields())
if err != nil {
return nil, fmt.Errorf("failed to decode managed fields: %v", err)
}
object, managed, err = f.fieldManager.Apply(liveObj, appliedObj, managed, manager, force)
if err != nil {
if conflicts, ok := err.(merge.Conflicts); ok {
return nil, NewConflictError(conflicts)
}
return nil, err
}
if err = EncodeObjectManagedFields(object, managed); err != nil {
return nil, fmt.Errorf("failed to encode managed fields: %v", err)
}
return object, nil
}

View File

@ -0,0 +1,61 @@
/*
Copyright 2019 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 internal_test
import (
"encoding/json"
"io/ioutil"
"path/filepath"
"strings"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"k8s.io/kube-openapi/pkg/validation/spec"
)
var fakeTypeConverter = func() internal.TypeConverter {
data, err := ioutil.ReadFile(filepath.Join(
strings.Repeat(".."+string(filepath.Separator), 9),
"api", "openapi-spec", "swagger.json"))
if err != nil {
panic(err)
}
spec := spec.Swagger{}
if err := json.Unmarshal(data, &spec); err != nil {
panic(err)
}
typeConverter, err := internal.NewTypeConverter(&spec, false)
if err != nil {
panic(err)
}
return typeConverter
}()
var testTypeConverter = func() internal.TypeConverter {
data, err := ioutil.ReadFile(filepath.Join("testdata", "swagger.json"))
if err != nil {
panic(err)
}
spec := spec.Swagger{}
if err := json.Unmarshal(data, &spec); err != nil {
panic(err)
}
typeConverter, err := internal.NewTypeConverter(&spec, false)
if err != nil {
panic(err)
}
return typeConverter
}()

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager package internal
import ( import (
"encoding/json" "encoding/json"

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager_test package internal_test
import ( import (
"fmt" "fmt"
@ -24,7 +24,9 @@ import (
apiequality "k8s.io/apimachinery/pkg/api/equality" apiequality "k8s.io/apimachinery/pkg/api/equality"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
yamlutil "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanagertest" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanagertest"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
@ -979,3 +981,27 @@ func testConflicts(t *testing.T, f fieldmanagertest.TestFieldManager, tests []te
}) })
} }
} }
func yamlToJSON(y []byte) (string, error) {
obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal(y, &obj.Object); err != nil {
return "", fmt.Errorf("error decoding YAML: %v", err)
}
serialization, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return "", fmt.Errorf("error encoding object: %v", err)
}
json, err := yamlutil.ToJSON(serialization)
if err != nil {
return "", fmt.Errorf("error converting to json: %v", err)
}
return string(json), nil
}
func setLastAppliedFromEncoded(obj runtime.Object, lastApplied []byte) error {
lastAppliedJSON, err := yamlToJSON(lastApplied)
if err != nil {
return err
}
return internal.SetLastApplied(obj, lastAppliedJSON)
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager package internal
import ( import (
"fmt" "fmt"
@ -23,7 +23,6 @@ import (
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
) )
type lastAppliedUpdater struct { type lastAppliedUpdater struct {
@ -62,7 +61,7 @@ func (f *lastAppliedUpdater) Apply(liveObj, newObj runtime.Object, managed Manag
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to build last-applied annotation: %v", err) return nil, nil, fmt.Errorf("failed to build last-applied annotation: %v", err)
} }
err = internal.SetLastApplied(liveObj, lastAppliedValue) err = SetLastApplied(liveObj, lastAppliedValue)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to set last-applied annotation: %v", err) return nil, nil, fmt.Errorf("failed to set last-applied annotation: %v", err)
} }

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager_test package internal_test
import ( import (
"fmt" "fmt"
@ -25,17 +25,18 @@ import (
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanagertest" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanagertest"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
) )
func TestLastAppliedUpdater(t *testing.T) { func TestLastAppliedUpdater(t *testing.T) {
f := fieldmanagertest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment"), f := fieldmanagertest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment"),
"", "",
func(m fieldmanager.Manager) fieldmanager.Manager { func(m internal.Manager) internal.Manager {
return fieldmanager.NewLastAppliedUpdater(m) return internal.NewLastAppliedUpdater(m)
}) })
originalLastApplied := `nonempty` originalLastApplied := `nonempty`
@ -191,8 +192,8 @@ func TestLargeLastApplied(t *testing.T) {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
f := fieldmanagertest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap"), f := fieldmanagertest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap"),
"", "",
func(m fieldmanager.Manager) fieldmanager.Manager { func(m internal.Manager) internal.Manager {
return fieldmanager.NewLastAppliedUpdater(m) return internal.NewLastAppliedUpdater(m)
}) })
if err := f.Apply(test.oldObject, "kubectl", false); err != nil { if err := f.Apply(test.oldObject, "kubectl", false); err != nil {
@ -226,3 +227,20 @@ func TestLargeLastApplied(t *testing.T) {
}) })
} }
} }
func getLastApplied(obj runtime.Object) (string, error) {
accessor := meta.NewAccessor()
annotations, err := accessor.Annotations(obj)
if err != nil {
return "", fmt.Errorf("failed to access annotations: %v", err)
}
if annotations == nil {
return "", fmt.Errorf("no annotations on obj: %v", obj)
}
lastApplied, ok := annotations[corev1.LastAppliedConfigAnnotation]
if !ok {
return "", fmt.Errorf("expected last applied annotation, but got none for object: %v", obj)
}
return lastApplied, nil
}

View File

@ -14,14 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager package internal
import ( import (
"time" "time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
) )
@ -77,7 +76,7 @@ func (f *managedFieldsUpdater) Apply(liveObj, appliedObj runtime.Object, managed
managed.Times()[fieldManager] = &metav1.Time{Time: time.Now().UTC()} managed.Times()[fieldManager] = &metav1.Time{Time: time.Now().UTC()}
} else { } else {
object = liveObj.DeepCopyObject() object = liveObj.DeepCopyObject()
internal.RemoveObjectManagedFields(object) RemoveObjectManagedFields(object)
} }
return object, managed, nil return object, managed, nil
} }

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager_test package internal_test
import ( import (
"fmt" "fmt"
@ -28,7 +28,6 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanagertest" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanagertest"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
@ -428,11 +427,11 @@ func TestTakingOverManagedFieldsDuringApplyDoesNotModifyPreviousManagerTime(t *t
type NoopManager struct{} type NoopManager struct{}
func (NoopManager) Apply(liveObj, appliedObj runtime.Object, managed fieldmanager.Managed, fieldManager string, force bool) (runtime.Object, fieldmanager.Managed, error) { func (NoopManager) Apply(liveObj, appliedObj runtime.Object, managed internal.Managed, fieldManager string, force bool) (runtime.Object, internal.Managed, error) {
return nil, managed, nil return nil, managed, nil
} }
func (NoopManager) Update(liveObj, newObj runtime.Object, managed fieldmanager.Managed, manager string) (runtime.Object, fieldmanager.Managed, error) { func (NoopManager) Update(liveObj, newObj runtime.Object, managed internal.Managed, manager string) (runtime.Object, internal.Managed, error) {
return nil, nil, nil return nil, nil, nil
} }
@ -498,12 +497,12 @@ func TestNilNewObjectReplacedWithDeepCopyExcludingManagedFields(t *testing.T) {
} }
// Decode the managed fields in the live object, since it isn't allowed in the patch. // Decode the managed fields in the live object, since it isn't allowed in the patch.
managed, err := fieldmanager.DecodeManagedFields(accessor.GetManagedFields()) managed, err := internal.DecodeManagedFields(accessor.GetManagedFields())
if err != nil { if err != nil {
t.Fatalf("failed to decode managed fields: %v", err) t.Fatalf("failed to decode managed fields: %v", err)
} }
updater := fieldmanager.NewManagedFieldsUpdater(NoopManager{}) updater := internal.NewManagedFieldsUpdater(NoopManager{})
newObject, _, err := updater.Apply(obj, obj.DeepCopyObject(), managed, "some_manager", false) newObject, _, err := updater.Apply(obj, obj.DeepCopyObject(), managed, "some_manager", false)
if err != nil { if err != nil {

View File

@ -0,0 +1,52 @@
/*
Copyright 2022 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 internal
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// Managed groups a fieldpath.ManagedFields together with the timestamps associated with each operation.
type Managed interface {
// Fields gets the fieldpath.ManagedFields.
Fields() fieldpath.ManagedFields
// Times gets the timestamps associated with each operation.
Times() map[string]*metav1.Time
}
// Manager updates the managed fields and merges applied configurations.
type Manager interface {
// Update is used when the object has already been merged (non-apply
// use-case), and simply updates the managed fields in the output
// object.
// * `liveObj` is not mutated by this function
// * `newObj` may be mutated by this function
// Returns the new object with managedFields removed, and the object's new
// proposed managedFields separately.
Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error)
// Apply is used when server-side apply is called, as it merges the
// object and updates the managed fields.
// * `liveObj` is not mutated by this function
// * `newObj` may be mutated by this function
// Returns the new object with managedFields removed, and the object's new
// proposed managedFields separately.
Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error)
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager package internal
import ( import (
"fmt" "fmt"

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager_test package internal_test
import ( import (
"strings" "strings"
@ -24,14 +24,14 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanagertest" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanagertest"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
) )
func TestNoUpdateBeforeFirstApply(t *testing.T) { func TestNoUpdateBeforeFirstApply(t *testing.T) {
f := fieldmanagertest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"), "", func(m fieldmanager.Manager) fieldmanager.Manager { f := fieldmanagertest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"), "", func(m internal.Manager) internal.Manager {
return fieldmanager.NewSkipNonAppliedManager( return internal.NewSkipNonAppliedManager(
m, m,
fieldmanagertest.NewFakeObjectCreater(), fieldmanagertest.NewFakeObjectCreater(),
schema.FromAPIVersionAndKind("v1", "Pod"), schema.FromAPIVersionAndKind("v1", "Pod"),
@ -70,8 +70,8 @@ func TestNoUpdateBeforeFirstApply(t *testing.T) {
} }
func TestUpdateBeforeFirstApply(t *testing.T) { func TestUpdateBeforeFirstApply(t *testing.T) {
f := fieldmanagertest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"), "", func(m fieldmanager.Manager) fieldmanager.Manager { f := fieldmanagertest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"), "", func(m internal.Manager) internal.Manager {
return fieldmanager.NewSkipNonAppliedManager( return internal.NewSkipNonAppliedManager(
m, m,
fieldmanagertest.NewFakeObjectCreater(), fieldmanagertest.NewFakeObjectCreater(),
schema.FromAPIVersionAndKind("v1", "Pod"), schema.FromAPIVersionAndKind("v1", "Pod"),

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager package internal
import ( import (
"fmt" "fmt"

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager package internal
import ( import (
"fmt" "fmt"
@ -23,7 +23,6 @@ import (
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
"sigs.k8s.io/structured-merge-diff/v4/merge" "sigs.k8s.io/structured-merge-diff/v4/merge"
) )
@ -108,7 +107,7 @@ func (f *structuredMergeManager) Update(liveObj, newObj runtime.Object, managed
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to update ManagedFields (%v): %v", objectGVKNN(newObjVersioned), err) return nil, nil, fmt.Errorf("failed to update ManagedFields (%v): %v", objectGVKNN(newObjVersioned), err)
} }
managed = internal.NewManaged(managedFields, managed.Times()) managed = NewManaged(managedFields, managed.Times())
return newObj, managed, nil return newObj, managed, nil
} }
@ -151,7 +150,7 @@ func (f *structuredMergeManager) Apply(liveObj, patchObj runtime.Object, managed
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
managed = internal.NewManaged(managedFields, managed.Times()) managed = NewManaged(managedFields, managed.Times())
if newObjTyped == nil { if newObjTyped == nil {
return nil, managed, nil return nil, managed, nil

View File

@ -0,0 +1,112 @@
/*
Copyright 2022 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 internal
import (
"fmt"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/managedfields"
utilopenapi "k8s.io/apiserver/pkg/util/openapi"
"k8s.io/kube-openapi/pkg/validation/spec"
"sigs.k8s.io/structured-merge-diff/v4/typed"
"sigs.k8s.io/structured-merge-diff/v4/value"
)
// TypeConverter allows you to convert from runtime.Object to
// typed.TypedValue and the other way around.
type TypeConverter interface {
ObjectToTyped(runtime.Object) (*typed.TypedValue, error)
TypedToObject(*typed.TypedValue) (runtime.Object, error)
}
type typeConverter struct {
parser *managedfields.GvkParser
}
var _ TypeConverter = &typeConverter{}
// NewTypeConverter builds a TypeConverter from a proto.Models. This
// will automatically find the proper version of the object, and the
// corresponding schema information.
func NewTypeConverter(openapiSpec *spec.Swagger, preserveUnknownFields bool) (TypeConverter, error) {
models, err := utilopenapi.ToProtoModels(openapiSpec)
if err != nil {
return nil, err
}
parser, err := managedfields.NewGVKParser(models, preserveUnknownFields)
if err != nil {
return nil, err
}
return &typeConverter{parser: parser}, nil
}
func (c *typeConverter) ObjectToTyped(obj runtime.Object) (*typed.TypedValue, error) {
gvk := obj.GetObjectKind().GroupVersionKind()
t := c.parser.Type(gvk)
if t == nil {
return nil, NewNoCorrespondingTypeError(gvk)
}
switch o := obj.(type) {
case *unstructured.Unstructured:
return t.FromUnstructured(o.UnstructuredContent())
default:
return t.FromStructured(obj)
}
}
func (c *typeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) {
return valueToObject(value.AsValue())
}
type deducedTypeConverter struct{}
// DeducedTypeConverter is a TypeConverter for CRDs that don't have a
// schema. It does implement the same interface though (and create the
// same types of objects), so that everything can still work the same.
// CRDs are merged with all their fields being "atomic" (lists
// included).
func NewDeducedTypeConverter() TypeConverter {
return deducedTypeConverter{}
}
// ObjectToTyped converts an object into a TypedValue with a "deduced type".
func (deducedTypeConverter) ObjectToTyped(obj runtime.Object) (*typed.TypedValue, error) {
switch o := obj.(type) {
case *unstructured.Unstructured:
return typed.DeducedParseableType.FromUnstructured(o.UnstructuredContent())
default:
return typed.DeducedParseableType.FromStructured(obj)
}
}
// TypedToObject transforms the typed value into a runtime.Object. That
// is not specific to deduced type.
func (deducedTypeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) {
return valueToObject(value.AsValue())
}
func valueToObject(val value.Value) (runtime.Object, error) {
vu := val.Unstructured()
switch o := vu.(type) {
case map[string]interface{}:
return &unstructured.Unstructured{Object: o}, nil
default:
return nil, fmt.Errorf("failed to convert value to unstructured for type %T", vu)
}
}

View File

@ -14,13 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager_test package internal_test
import ( import (
"encoding/json"
"fmt" "fmt"
"io/ioutil"
"path/filepath"
"reflect" "reflect"
"testing" "testing"
@ -28,28 +25,11 @@ import (
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"k8s.io/kube-openapi/pkg/validation/spec"
) )
var testTypeConverter = func() fieldmanager.TypeConverter {
data, err := ioutil.ReadFile(filepath.Join("testdata", "swagger.json"))
if err != nil {
panic(err)
}
spec := spec.Swagger{}
if err := json.Unmarshal(data, &spec); err != nil {
panic(err)
}
typeConverter, err := fieldmanager.NewTypeConverter(&spec, false)
if err != nil {
panic(err)
}
return typeConverter
}()
func TestTypeConverter(t *testing.T) { func TestTypeConverter(t *testing.T) {
dtc := fieldmanager.DeducedTypeConverter{} dtc := internal.NewDeducedTypeConverter()
testCases := []struct { testCases := []struct {
name string name string
@ -126,7 +106,7 @@ spec:
} }
} }
func testObjectToTyped(t *testing.T, tc fieldmanager.TypeConverter, y string) { func testObjectToTyped(t *testing.T, tc internal.TypeConverter, y string) {
obj := &unstructured.Unstructured{Object: map[string]interface{}{}} obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(y), &obj.Object); err != nil { if err := yaml.Unmarshal([]byte(y), &obj.Object); err != nil {
t.Fatalf("Failed to parse yaml object: %v", err) t.Fatalf("Failed to parse yaml object: %v", err)

View File

@ -14,9 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager package internal
import ( import (
"fmt"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
@ -99,3 +101,23 @@ func (v *versionConverter) Convert(object *typed.TypedValue, version fieldpath.A
func (v *versionConverter) IsMissingVersionError(err error) bool { func (v *versionConverter) IsMissingVersionError(err error) bool {
return runtime.IsNotRegisteredError(err) || isNoCorrespondingTypeError(err) return runtime.IsNotRegisteredError(err) || isNoCorrespondingTypeError(err)
} }
type noCorrespondingTypeErr struct {
gvk schema.GroupVersionKind
}
func NewNoCorrespondingTypeError(gvk schema.GroupVersionKind) error {
return &noCorrespondingTypeErr{gvk: gvk}
}
func (k *noCorrespondingTypeErr) Error() string {
return fmt.Sprintf("no corresponding type for %v", k.gvk)
}
func isNoCorrespondingTypeError(err error) bool {
if err == nil {
return false
}
_, ok := err.(*noCorrespondingTypeErr)
return ok
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager package internal
import ( import (
"encoding/json" "encoding/json"
@ -31,7 +31,7 @@ import (
"sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
) )
var testSchema = func() *spec.Swagger { var testTypeConverter = func() TypeConverter {
data, err := ioutil.ReadFile(filepath.Join("testdata", "swagger.json")) data, err := ioutil.ReadFile(filepath.Join("testdata", "swagger.json"))
if err != nil { if err != nil {
panic(err) panic(err)
@ -40,22 +40,22 @@ var testSchema = func() *spec.Swagger {
if err := json.Unmarshal(data, &spec); err != nil { if err := json.Unmarshal(data, &spec); err != nil {
panic(err) panic(err)
} }
return &spec typeConverter, err := NewTypeConverter(&spec, false)
if err != nil {
panic(err)
}
return typeConverter
}() }()
// TestVersionConverter tests the version converter // TestVersionConverter tests the version converter
func TestVersionConverter(t *testing.T) { func TestVersionConverter(t *testing.T) {
tc, err := NewTypeConverter(testSchema, false)
if err != nil {
t.Fatalf("Failed to build TypeConverter: %v", err)
}
oc := fakeObjectConvertorForTestSchema{ oc := fakeObjectConvertorForTestSchema{
gvkForVersion("v1beta1"): objForGroupVersion("apps/v1beta1"), gvkForVersion("v1beta1"): objForGroupVersion("apps/v1beta1"),
gvkForVersion("v1"): objForGroupVersion("apps/v1"), gvkForVersion("v1"): objForGroupVersion("apps/v1"),
} }
vc := newVersionConverter(tc, oc, schema.GroupVersion{Group: "apps", Version: runtime.APIVersionInternal}) vc := newVersionConverter(testTypeConverter, oc, schema.GroupVersion{Group: "apps", Version: runtime.APIVersionInternal})
input, err := tc.ObjectToTyped(objForGroupVersion("apps/v1beta1")) input, err := testTypeConverter.ObjectToTyped(objForGroupVersion("apps/v1beta1"))
if err != nil { if err != nil {
t.Fatalf("error creating converting input object to a typed value: %v", err) t.Fatalf("error creating converting input object to a typed value: %v", err)
} }
@ -64,7 +64,7 @@ func TestVersionConverter(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("expected err to be nil but got %v", err) t.Fatalf("expected err to be nil but got %v", err)
} }
actual, err := tc.TypedToObject(output) actual, err := testTypeConverter.TypedToObject(output)
if err != nil { if err != nil {
t.Fatalf("error converting output typed value to an object %v", err) t.Fatalf("error converting output typed value to an object %v", err)
} }

View File

@ -60,7 +60,7 @@ func NewScaleHandler(parentEntries []metav1.ManagedFieldsEntry, groupVersion sch
// 2. Replicas path of the main resource is transformed to the replicas path of // 2. Replicas path of the main resource is transformed to the replicas path of
// the scale subresource // the scale subresource
func (h *ScaleHandler) ToSubresource() ([]metav1.ManagedFieldsEntry, error) { func (h *ScaleHandler) ToSubresource() ([]metav1.ManagedFieldsEntry, error) {
managed, err := DecodeManagedFields(h.parentEntries) managed, err := internal.DecodeManagedFields(h.parentEntries)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -92,13 +92,13 @@ func (h *ScaleHandler) ToSubresource() ([]metav1.ManagedFieldsEntry, error) {
// ToParent merges `scaleEntries` with the entries of the main resource and // ToParent merges `scaleEntries` with the entries of the main resource and
// transforms them accordingly // transforms them accordingly
func (h *ScaleHandler) ToParent(scaleEntries []metav1.ManagedFieldsEntry) ([]metav1.ManagedFieldsEntry, error) { func (h *ScaleHandler) ToParent(scaleEntries []metav1.ManagedFieldsEntry) ([]metav1.ManagedFieldsEntry, error) {
decodedParentEntries, err := DecodeManagedFields(h.parentEntries) decodedParentEntries, err := internal.DecodeManagedFields(h.parentEntries)
if err != nil { if err != nil {
return nil, err return nil, err
} }
parentFields := decodedParentEntries.Fields() parentFields := decodedParentEntries.Fields()
decodedScaleEntries, err := DecodeManagedFields(scaleEntries) decodedScaleEntries, err := internal.DecodeManagedFields(scaleEntries)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -17,119 +17,26 @@ limitations under the License.
package fieldmanager package fieldmanager
import ( import (
"fmt" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/managedfields"
utilopenapi "k8s.io/apiserver/pkg/util/openapi"
"k8s.io/kube-openapi/pkg/validation/spec" "k8s.io/kube-openapi/pkg/validation/spec"
"sigs.k8s.io/structured-merge-diff/v4/typed"
"sigs.k8s.io/structured-merge-diff/v4/value"
) )
// TypeConverter allows you to convert from runtime.Object to // TypeConverter allows you to convert from runtime.Object to
// typed.TypedValue and the other way around. // typed.TypedValue and the other way around.
type TypeConverter interface { type TypeConverter = internal.TypeConverter
ObjectToTyped(runtime.Object) (*typed.TypedValue, error)
TypedToObject(*typed.TypedValue) (runtime.Object, error)
}
// DeducedTypeConverter is a TypeConverter for CRDs that don't have a // NewDeducedTypeConverter creates a TypeConverter for CRDs that don't
// schema. It does implement the same interface though (and create the // have a schema. It does implement the same interface though (and
// same types of objects), so that everything can still work the same. // create the same types of objects), so that everything can still work
// CRDs are merged with all their fields being "atomic" (lists // the same. CRDs are merged with all their fields being "atomic" (lists
// included). // included).
// func NewDeducedTypeConverter() TypeConverter {
// Note that this is not going to be sufficient for converting to/from return internal.NewDeducedTypeConverter()
// CRDs that have a schema defined (we don't support that schema yet).
// TODO(jennybuckley): Use the schema provided by a CRD if it exists.
type DeducedTypeConverter struct{}
var _ TypeConverter = DeducedTypeConverter{}
// ObjectToTyped converts an object into a TypedValue with a "deduced type".
func (DeducedTypeConverter) ObjectToTyped(obj runtime.Object) (*typed.TypedValue, error) {
switch o := obj.(type) {
case *unstructured.Unstructured:
return typed.DeducedParseableType.FromUnstructured(o.UnstructuredContent())
default:
return typed.DeducedParseableType.FromStructured(obj)
}
} }
// TypedToObject transforms the typed value into a runtime.Object. That // NewTypeConverter builds a TypeConverter from a proto.Models. This
// is not specific to deduced type.
func (DeducedTypeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) {
return valueToObject(value.AsValue())
}
type typeConverter struct {
parser *managedfields.GvkParser
}
var _ TypeConverter = &typeConverter{}
// NewTypeConverter builds a TypeConverter from a spec.Swagger. This
// will automatically find the proper version of the object, and the // will automatically find the proper version of the object, and the
// corresponding schema information. // corresponding schema information.
func NewTypeConverter(openapiSpec *spec.Swagger, preserveUnknownFields bool) (TypeConverter, error) { func NewTypeConverter(openapiSpec *spec.Swagger, preserveUnknownFields bool) (TypeConverter, error) {
models, err := utilopenapi.ToProtoModels(openapiSpec) return internal.NewTypeConverter(openapiSpec, preserveUnknownFields)
if err != nil {
return nil, err
}
parser, err := managedfields.NewGVKParser(models, preserveUnknownFields)
if err != nil {
return nil, err
}
return &typeConverter{parser: parser}, nil
}
func (c *typeConverter) ObjectToTyped(obj runtime.Object) (*typed.TypedValue, error) {
gvk := obj.GetObjectKind().GroupVersionKind()
t := c.parser.Type(gvk)
if t == nil {
return nil, newNoCorrespondingTypeError(gvk)
}
switch o := obj.(type) {
case *unstructured.Unstructured:
return t.FromUnstructured(o.UnstructuredContent())
default:
return t.FromStructured(obj)
}
}
func (c *typeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) {
return valueToObject(value.AsValue())
}
func valueToObject(val value.Value) (runtime.Object, error) {
vu := val.Unstructured()
switch o := vu.(type) {
case map[string]interface{}:
return &unstructured.Unstructured{Object: o}, nil
default:
return nil, fmt.Errorf("failed to convert value to unstructured for type %T", vu)
}
}
type noCorrespondingTypeErr struct {
gvk schema.GroupVersionKind
}
func newNoCorrespondingTypeError(gvk schema.GroupVersionKind) error {
return &noCorrespondingTypeErr{gvk: gvk}
}
func (k *noCorrespondingTypeErr) Error() string {
return fmt.Sprintf("no corresponding type for %v", k.gvk)
}
func isNoCorrespondingTypeError(err error) bool {
if err == nil {
return false
}
_, ok := err.(*noCorrespondingTypeErr)
return ok
} }

View File

@ -169,8 +169,9 @@ func TestMutatingWebhookResetsInvalidManagedFields(t *testing.T) {
// validate against both decoding and validation to make sure we use the hardest rule between the both to reset // validate against both decoding and validation to make sure we use the hardest rule between the both to reset
// with decoding being as strict as it gets, only using it should be enough in admission // with decoding being as strict as it gets, only using it should be enough in admission
func validateManagedFieldsAndDecode(managedFields []metav1.ManagedFieldsEntry) error { func validateManagedFieldsAndDecode(managedFields []metav1.ManagedFieldsEntry) error {
if _, err := fieldmanager.DecodeManagedFields(managedFields); err != nil { if err := fieldmanager.ValidateManagedFields(managedFields); err != nil {
return err return err
} }
validationErrs := v1validation.ValidateManagedFields(managedFields, field.NewPath("metadata").Child("managedFields")) validationErrs := v1validation.ValidateManagedFields(managedFields, field.NewPath("metadata").Child("managedFields"))
return validationErrs.ToAggregate() return validationErrs.ToAggregate()

View File

@ -34,7 +34,7 @@ import (
) )
func convertToUnstructured(b *testing.B, obj runtime.Object) runtime.Object { func convertToUnstructured(b *testing.B, obj runtime.Object) runtime.Object {
converter := fieldmanager.DeducedTypeConverter{} converter := fieldmanager.NewDeducedTypeConverter()
typed, err := converter.ObjectToTyped(obj) typed, err := converter.ObjectToTyped(obj)
require.NoError(b, err) require.NoError(b, err)
res, err := converter.TypedToObject(typed) res, err := converter.TypedToObject(typed)