diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/admission.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/admission.go index d489fda6502..a276be9a7d2 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/admission.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/admission.go @@ -21,7 +21,6 @@ import ( "fmt" "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/warning" ) @@ -65,7 +64,7 @@ func (admit *managedFieldsValidatingAdmissionController) Admit(ctx context.Conte return err } managedFieldsAfterAdmission := objectMeta.GetManagedFields() - if err := validateManagedFields(managedFieldsAfterAdmission); err != nil { + if err := ValidateManagedFields(managedFieldsAfterAdmission); err != nil { objectMeta.SetManagedFields(managedFieldsBeforeAdmission) warning.AddWarning(ctx, "", fmt.Sprintf(InvalidManagedFieldsAfterMutatingAdmissionWarningFormat, @@ -82,21 +81,3 @@ func (admit *managedFieldsValidatingAdmissionController) Validate(ctx context.Co } return nil } - -func validateManagedFields(managedFields []metav1.ManagedFieldsEntry) error { - for i, managed := range managedFields { - if len(managed.APIVersion) < 1 { - return fmt.Errorf(".metadata.managedFields[%d]: missing apiVersion", i) - } - if len(managed.FieldsType) < 1 { - return fmt.Errorf(".metadata.managedFields[%d]: missing fieldsType", i) - } - if len(managed.Manager) < 1 { - return fmt.Errorf(".metadata.managedFields[%d]: missing manager", i) - } - if managed.FieldsV1 == nil { - return fmt.Errorf(".metadata.managedFields[%d]: missing fieldsV1", i) - } - } - return nil -} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/admission_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/admission_test.go new file mode 100644 index 00000000000..1f1c0ca1269 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/admission_test.go @@ -0,0 +1,115 @@ +package fieldmanager_test + +import ( + "context" + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager" + api "k8s.io/kubernetes/pkg/apis/core" +) + +func TestAdmission(t *testing.T) { + wrap := &mockAdmissionController{} + ac := fieldmanager.NewManagedFieldsValidatingAdmissionController(wrap) + + tests := []struct { + beforeAdmission []metav1.ManagedFieldsEntry + afterAdmission []metav1.ManagedFieldsEntry + expected []metav1.ManagedFieldsEntry + }{ + { + beforeAdmission: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + }, + }, + afterAdmission: []metav1.ManagedFieldsEntry{ + { + Manager: "", + }, + }, + expected: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + }, + }, + }, + { + beforeAdmission: []metav1.ManagedFieldsEntry{ + { + APIVersion: "test", + }, + }, + afterAdmission: []metav1.ManagedFieldsEntry{ + { + APIVersion: "", + }, + }, + expected: []metav1.ManagedFieldsEntry{ + { + APIVersion: "test", + }, + }, + }, + { + beforeAdmission: []metav1.ManagedFieldsEntry{ + { + FieldsType: "FieldsV1", + }, + }, + afterAdmission: []metav1.ManagedFieldsEntry{ + { + FieldsType: "test", + }, + }, + expected: []metav1.ManagedFieldsEntry{ + { + FieldsType: "FieldsV1", + }, + }, + }, + } + + for _, test := range tests { + obj := &unstructured.Unstructured{} + obj.SetManagedFields(test.beforeAdmission) + wrap.admit = replaceManagedFields(test.afterAdmission) + + attrs := admission.NewAttributesRecord(obj, obj, api.Kind("ConfigMap").WithVersion("version"), "default", "", api.Resource("configmaps").WithVersion("version"), "", admission.Update, nil, false, nil) + if err := ac.(admission.MutationInterface).Admit(context.TODO(), attrs, nil); err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(obj.GetManagedFields(), test.expected) { + t.Fatalf("expected: \n%v\ngot:\n%v", test.expected, obj.GetManagedFields()) + } + } +} + +func replaceManagedFields(with []metav1.ManagedFieldsEntry) func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error { + return func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error { + objectMeta, err := meta.Accessor(a.GetObject()) + if err != nil { + return err + } + objectMeta.SetManagedFields(with) + return nil + } +} + +type mockAdmissionController struct { + admit func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error +} + +func (c *mockAdmissionController) Handles(operation admission.Operation) bool { + return true +} + +func (c *mockAdmissionController) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error { + return c.admit(ctx, a, o) +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/validation.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/validation.go new file mode 100644 index 00000000000..c862a7745bb --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/validation.go @@ -0,0 +1,22 @@ +package fieldmanager + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// ValidateManagedFields by checking the integrity of every entry and trying to decode +// them to the internal format +func ValidateManagedFields(managedFields []metav1.ManagedFieldsEntry) error { + validationErrs := v1validation.ValidateManagedFields(managedFields, field.NewPath("metadata").Child("managedFields")) + if len(validationErrs) > 0 { + return validationErrs.ToAggregate() + } + + if _, err := DecodeManagedFields(managedFields); err != nil { + return err + } + + return nil +}