mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Merge pull request #114868 from apelisse/private-internal-managers
fieldmanager: Make internal managers private
This commit is contained in:
commit
7e97b4b322
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
|
@ -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)
|
||||||
}
|
}
|
@ -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,
|
@ -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 {
|
@ -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
|
||||||
|
}
|
@ -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
|
||||||
|
}()
|
@ -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"
|
@ -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)
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
@ -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
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
@ -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 {
|
@ -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)
|
||||||
|
}
|
@ -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"
|
@ -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"),
|
@ -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"
|
@ -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
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
@ -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
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user