From 4a5fdad93bed55bcdde2fdb056f7a07d60c315f5 Mon Sep 17 00:00:00 2001 From: Kevin Wiesmueller Date: Thu, 14 Jan 2021 20:14:38 +0100 Subject: [PATCH] implement managedFields admission controller Kubernetes-commit: d5ae113e8dfba62709b1fccb8dbc26c6dde9b3e5 --- .../handlers/fieldmanager/admission.go | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 pkg/endpoints/handlers/fieldmanager/admission.go diff --git a/pkg/endpoints/handlers/fieldmanager/admission.go b/pkg/endpoints/handlers/fieldmanager/admission.go new file mode 100644 index 000000000..8727f47bc --- /dev/null +++ b/pkg/endpoints/handlers/fieldmanager/admission.go @@ -0,0 +1,105 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package fieldmanager + +import ( + "context" + "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" +) + +// InvalidManagedFieldsAfterMutatingAdmissionWarningFormat is the warning that a client receives +// when a create/update/patch request results in invalid managedFields after going through the admission chain. +const InvalidManagedFieldsAfterMutatingAdmissionWarningFormat = ".metadata.managedFields was in an invalid state after admission; this could be caused by an outdated mutating admission controller; please fix your requests: %v" + +// NewManagedFieldsValidatingAdmissionController validates the managedFields after calling +// the provided admission and resets them to their original state if they got changed to an invalid value +func NewManagedFieldsValidatingAdmissionController(wrap admission.Interface) admission.Interface { + return nil +} + +type managedFieldsValidatingAdmissionController struct { + wrap admission.Interface +} + +var _ admission.Interface = &managedFieldsValidatingAdmissionController{} +var _ admission.MutationInterface = &managedFieldsValidatingAdmissionController{} +var _ admission.ValidationInterface = &managedFieldsValidatingAdmissionController{} + +// Handles calls the wrapped admission.Interface if aplicable +func (admit *managedFieldsValidatingAdmissionController) Handles(operation admission.Operation) bool { + return admit.wrap.Handles(operation) +} + +// Admit calls the wrapped admission.Interface if aplicable and resets the managedFields to their state before admission if they +// got modified in an invalid way +func (admit *managedFieldsValidatingAdmissionController) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) { + mutationInterface, isMutationInterface := admit.wrap.(admission.MutationInterface) + if !isMutationInterface { + return nil + } + objectMeta, err := meta.Accessor(a.GetObject()) + if err != nil { + return err + } + managedFieldsBeforeAdmission := objectMeta.GetManagedFields() + if err := mutationInterface.Admit(ctx, a, o); err != nil { + return err + } + objectMeta, err = meta.Accessor(a.GetObject()) + if err != nil { + return err + } + managedFieldsAfterAdmission := objectMeta.GetManagedFields() + if err := validateManagedFields(managedFieldsAfterAdmission); err != nil { + objectMeta.SetManagedFields(managedFieldsBeforeAdmission) + warning.AddWarning(ctx, "", + fmt.Sprintf(InvalidManagedFieldsAfterMutatingAdmissionWarningFormat, + err.Error()), + ) + } + return nil +} + +// Validate calls the wrapped admission.Interface if aplicable +func (admit *managedFieldsValidatingAdmissionController) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) { + if validationInterface, isValidationInterface := admit.wrap.(admission.ValidationInterface); isValidationInterface { + return validationInterface.Validate(ctx, a, o) + } + 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 +}