1/* 2Copyright 2014 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package rest 18 19import ( 20 "context" 21 "fmt" 22 23 "k8s.io/apimachinery/pkg/api/errors" 24 "k8s.io/apimachinery/pkg/api/meta" 25 genericvalidation "k8s.io/apimachinery/pkg/api/validation" 26 "k8s.io/apimachinery/pkg/api/validation/path" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/apimachinery/pkg/util/validation/field" 30 "k8s.io/apiserver/pkg/admission" 31 "k8s.io/apiserver/pkg/features" 32 utilfeature "k8s.io/apiserver/pkg/util/feature" 33) 34 35// RESTUpdateStrategy defines the minimum validation, accepted input, and 36// name generation behavior to update an object that follows Kubernetes 37// API conventions. A resource may have many UpdateStrategies, depending on 38// the call pattern in use. 39type RESTUpdateStrategy interface { 40 runtime.ObjectTyper 41 // NamespaceScoped returns true if the object must be within a namespace. 42 NamespaceScoped() bool 43 // AllowCreateOnUpdate returns true if the object can be created by a PUT. 44 AllowCreateOnUpdate() bool 45 // PrepareForUpdate is invoked on update before validation to normalize 46 // the object. For example: remove fields that are not to be persisted, 47 // sort order-insensitive list fields, etc. This should not remove fields 48 // whose presence would be considered a validation error. 49 PrepareForUpdate(ctx context.Context, obj, old runtime.Object) 50 // ValidateUpdate is invoked after default fields in the object have been 51 // filled in before the object is persisted. This method should not mutate 52 // the object. 53 ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList 54 // Canonicalize allows an object to be mutated into a canonical form. This 55 // ensures that code that operates on these objects can rely on the common 56 // form for things like comparison. Canonicalize is invoked after 57 // validation has succeeded but before the object has been persisted. 58 // This method may mutate the object. 59 Canonicalize(obj runtime.Object) 60 // AllowUnconditionalUpdate returns true if the object can be updated 61 // unconditionally (irrespective of the latest resource version), when 62 // there is no resource version specified in the object. 63 AllowUnconditionalUpdate() bool 64} 65 66// TODO: add other common fields that require global validation. 67func validateCommonFields(obj, old runtime.Object, strategy RESTUpdateStrategy) (field.ErrorList, error) { 68 allErrs := field.ErrorList{} 69 objectMeta, err := meta.Accessor(obj) 70 if err != nil { 71 return nil, fmt.Errorf("failed to get new object metadata: %v", err) 72 } 73 oldObjectMeta, err := meta.Accessor(old) 74 if err != nil { 75 return nil, fmt.Errorf("failed to get old object metadata: %v", err) 76 } 77 allErrs = append(allErrs, genericvalidation.ValidateObjectMetaAccessor(objectMeta, strategy.NamespaceScoped(), path.ValidatePathSegmentName, field.NewPath("metadata"))...) 78 allErrs = append(allErrs, genericvalidation.ValidateObjectMetaAccessorUpdate(objectMeta, oldObjectMeta, field.NewPath("metadata"))...) 79 80 return allErrs, nil 81} 82 83// BeforeUpdate ensures that common operations for all resources are performed on update. It only returns 84// errors that can be converted to api.Status. It will invoke update validation with the provided existing 85// and updated objects. 86// It sets zero values only if the object does not have a zero value for the respective field. 87func BeforeUpdate(strategy RESTUpdateStrategy, ctx context.Context, obj, old runtime.Object) error { 88 objectMeta, kind, kerr := objectMetaAndKind(strategy, obj) 89 if kerr != nil { 90 return kerr 91 } 92 if strategy.NamespaceScoped() { 93 if !ValidNamespace(ctx, objectMeta) { 94 return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request") 95 } 96 } else if len(objectMeta.GetNamespace()) > 0 { 97 objectMeta.SetNamespace(metav1.NamespaceNone) 98 } 99 100 // Ensure requests cannot update generation 101 oldMeta, err := meta.Accessor(old) 102 if err != nil { 103 return err 104 } 105 objectMeta.SetGeneration(oldMeta.GetGeneration()) 106 107 // Ensure managedFields state is removed unless ServerSideApply is enabled 108 if !utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) { 109 oldMeta.SetManagedFields(nil) 110 objectMeta.SetManagedFields(nil) 111 } 112 113 strategy.PrepareForUpdate(ctx, obj, old) 114 115 // ClusterName is ignored and should not be saved 116 if len(objectMeta.GetClusterName()) > 0 { 117 objectMeta.SetClusterName("") 118 } 119 // Use the existing UID if none is provided 120 if len(objectMeta.GetUID()) == 0 { 121 objectMeta.SetUID(oldMeta.GetUID()) 122 } 123 // ignore changes to timestamp 124 if oldCreationTime := oldMeta.GetCreationTimestamp(); !oldCreationTime.IsZero() { 125 objectMeta.SetCreationTimestamp(oldMeta.GetCreationTimestamp()) 126 } 127 // an update can never remove/change a deletion timestamp 128 if !oldMeta.GetDeletionTimestamp().IsZero() { 129 objectMeta.SetDeletionTimestamp(oldMeta.GetDeletionTimestamp()) 130 } 131 // an update can never remove/change grace period seconds 132 if oldMeta.GetDeletionGracePeriodSeconds() != nil && objectMeta.GetDeletionGracePeriodSeconds() == nil { 133 objectMeta.SetDeletionGracePeriodSeconds(oldMeta.GetDeletionGracePeriodSeconds()) 134 } 135 136 // Ensure some common fields, like UID, are validated for all resources. 137 errs, err := validateCommonFields(obj, old, strategy) 138 if err != nil { 139 return errors.NewInternalError(err) 140 } 141 142 errs = append(errs, strategy.ValidateUpdate(ctx, obj, old)...) 143 if len(errs) > 0 { 144 return errors.NewInvalid(kind.GroupKind(), objectMeta.GetName(), errs) 145 } 146 147 strategy.Canonicalize(obj) 148 149 return nil 150} 151 152// TransformFunc is a function to transform and return newObj 153type TransformFunc func(ctx context.Context, newObj runtime.Object, oldObj runtime.Object) (transformedNewObj runtime.Object, err error) 154 155// defaultUpdatedObjectInfo implements UpdatedObjectInfo 156type defaultUpdatedObjectInfo struct { 157 // obj is the updated object 158 obj runtime.Object 159 160 // transformers is an optional list of transforming functions that modify or 161 // replace obj using information from the context, old object, or other sources. 162 transformers []TransformFunc 163} 164 165// DefaultUpdatedObjectInfo returns an UpdatedObjectInfo impl based on the specified object. 166func DefaultUpdatedObjectInfo(obj runtime.Object, transformers ...TransformFunc) UpdatedObjectInfo { 167 return &defaultUpdatedObjectInfo{obj, transformers} 168} 169 170// Preconditions satisfies the UpdatedObjectInfo interface. 171func (i *defaultUpdatedObjectInfo) Preconditions() *metav1.Preconditions { 172 // Attempt to get the UID out of the object 173 accessor, err := meta.Accessor(i.obj) 174 if err != nil { 175 // If no UID can be read, no preconditions are possible 176 return nil 177 } 178 179 // If empty, no preconditions needed 180 uid := accessor.GetUID() 181 if len(uid) == 0 { 182 return nil 183 } 184 185 return &metav1.Preconditions{UID: &uid} 186} 187 188// UpdatedObject satisfies the UpdatedObjectInfo interface. 189// It returns a copy of the held obj, passed through any configured transformers. 190func (i *defaultUpdatedObjectInfo) UpdatedObject(ctx context.Context, oldObj runtime.Object) (runtime.Object, error) { 191 var err error 192 // Start with the configured object 193 newObj := i.obj 194 195 // If the original is non-nil (might be nil if the first transformer builds the object from the oldObj), make a copy, 196 // so we don't return the original. BeforeUpdate can mutate the returned object, doing things like clearing ResourceVersion. 197 // If we're re-called, we need to be able to return the pristine version. 198 if newObj != nil { 199 newObj = newObj.DeepCopyObject() 200 } 201 202 // Allow any configured transformers to update the new object 203 for _, transformer := range i.transformers { 204 newObj, err = transformer(ctx, newObj, oldObj) 205 if err != nil { 206 return nil, err 207 } 208 } 209 210 return newObj, nil 211} 212 213// wrappedUpdatedObjectInfo allows wrapping an existing objInfo and 214// chaining additional transformations/checks on the result of UpdatedObject() 215type wrappedUpdatedObjectInfo struct { 216 // obj is the updated object 217 objInfo UpdatedObjectInfo 218 219 // transformers is an optional list of transforming functions that modify or 220 // replace obj using information from the context, old object, or other sources. 221 transformers []TransformFunc 222} 223 224// WrapUpdatedObjectInfo returns an UpdatedObjectInfo impl that delegates to 225// the specified objInfo, then calls the passed transformers 226func WrapUpdatedObjectInfo(objInfo UpdatedObjectInfo, transformers ...TransformFunc) UpdatedObjectInfo { 227 return &wrappedUpdatedObjectInfo{objInfo, transformers} 228} 229 230// Preconditions satisfies the UpdatedObjectInfo interface. 231func (i *wrappedUpdatedObjectInfo) Preconditions() *metav1.Preconditions { 232 return i.objInfo.Preconditions() 233} 234 235// UpdatedObject satisfies the UpdatedObjectInfo interface. 236// It delegates to the wrapped objInfo and passes the result through any configured transformers. 237func (i *wrappedUpdatedObjectInfo) UpdatedObject(ctx context.Context, oldObj runtime.Object) (runtime.Object, error) { 238 newObj, err := i.objInfo.UpdatedObject(ctx, oldObj) 239 if err != nil { 240 return newObj, err 241 } 242 243 // Allow any configured transformers to update the new object or error 244 for _, transformer := range i.transformers { 245 newObj, err = transformer(ctx, newObj, oldObj) 246 if err != nil { 247 return nil, err 248 } 249 } 250 251 return newObj, nil 252} 253 254// AdmissionToValidateObjectUpdateFunc converts validating admission to a rest validate object update func 255func AdmissionToValidateObjectUpdateFunc(admit admission.Interface, staticAttributes admission.Attributes, o admission.ObjectInterfaces) ValidateObjectUpdateFunc { 256 validatingAdmission, ok := admit.(admission.ValidationInterface) 257 if !ok { 258 return func(ctx context.Context, obj, old runtime.Object) error { return nil } 259 } 260 return func(ctx context.Context, obj, old runtime.Object) error { 261 finalAttributes := admission.NewAttributesRecord( 262 obj, 263 old, 264 staticAttributes.GetKind(), 265 staticAttributes.GetNamespace(), 266 staticAttributes.GetName(), 267 staticAttributes.GetResource(), 268 staticAttributes.GetSubresource(), 269 staticAttributes.GetOperation(), 270 staticAttributes.GetOperationOptions(), 271 staticAttributes.IsDryRun(), 272 staticAttributes.GetUserInfo(), 273 ) 274 if !validatingAdmission.Handles(finalAttributes.GetOperation()) { 275 return nil 276 } 277 return validatingAdmission.Validate(ctx, finalAttributes, o) 278 } 279} 280