k8s.io/apiserver@v0.31.1/pkg/registry/rest/update.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package rest 18 19 import ( 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 genericapirequest "k8s.io/apiserver/pkg/endpoints/request" 32 "k8s.io/apiserver/pkg/warning" 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. 39 type 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 // WarningsOnUpdate returns warnings to the client performing the update. 55 // WarningsOnUpdate is invoked after default fields in the object have been filled in 56 // and after ValidateUpdate has passed, before Canonicalize is called, and before the object is persisted. 57 // This method must not mutate either object. 58 // 59 // Be brief; limit warnings to 120 characters if possible. 60 // Don't include a "Warning:" prefix in the message (that is added by clients on output). 61 // Warnings returned about a specific field should be formatted as "path.to.field: message". 62 // For example: `spec.imagePullSecrets[0].name: invalid empty name ""` 63 // 64 // Use warning messages to describe problems the client making the API request should correct or be aware of. 65 // For example: 66 // - use of deprecated fields/labels/annotations that will stop working in a future release 67 // - use of obsolete fields/labels/annotations that are non-functional 68 // - malformed or invalid specifications that prevent successful handling of the submitted object, 69 // but are not rejected by validation for compatibility reasons 70 // 71 // Warnings should not be returned for fields which cannot be resolved by the caller. 72 // For example, do not warn about spec fields in a status update. 73 WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string 74 // Canonicalize allows an object to be mutated into a canonical form. This 75 // ensures that code that operates on these objects can rely on the common 76 // form for things like comparison. Canonicalize is invoked after 77 // validation has succeeded but before the object has been persisted. 78 // This method may mutate the object. 79 Canonicalize(obj runtime.Object) 80 // AllowUnconditionalUpdate returns true if the object can be updated 81 // unconditionally (irrespective of the latest resource version), when 82 // there is no resource version specified in the object. 83 AllowUnconditionalUpdate() bool 84 } 85 86 // TODO: add other common fields that require global validation. 87 func validateCommonFields(obj, old runtime.Object, strategy RESTUpdateStrategy) (field.ErrorList, error) { 88 allErrs := field.ErrorList{} 89 objectMeta, err := meta.Accessor(obj) 90 if err != nil { 91 return nil, fmt.Errorf("failed to get new object metadata: %v", err) 92 } 93 oldObjectMeta, err := meta.Accessor(old) 94 if err != nil { 95 return nil, fmt.Errorf("failed to get old object metadata: %v", err) 96 } 97 allErrs = append(allErrs, genericvalidation.ValidateObjectMetaAccessor(objectMeta, strategy.NamespaceScoped(), path.ValidatePathSegmentName, field.NewPath("metadata"))...) 98 allErrs = append(allErrs, genericvalidation.ValidateObjectMetaAccessorUpdate(objectMeta, oldObjectMeta, field.NewPath("metadata"))...) 99 100 return allErrs, nil 101 } 102 103 // BeforeUpdate ensures that common operations for all resources are performed on update. It only returns 104 // errors that can be converted to api.Status. It will invoke update validation with the provided existing 105 // and updated objects. 106 // It sets zero values only if the object does not have a zero value for the respective field. 107 func BeforeUpdate(strategy RESTUpdateStrategy, ctx context.Context, obj, old runtime.Object) error { 108 objectMeta, kind, kerr := objectMetaAndKind(strategy, obj) 109 if kerr != nil { 110 return kerr 111 } 112 113 // ensure namespace on the object is correct, or error if a conflicting namespace was set in the object 114 requestNamespace, ok := genericapirequest.NamespaceFrom(ctx) 115 if !ok { 116 return errors.NewInternalError(fmt.Errorf("no namespace information found in request context")) 117 } 118 if err := EnsureObjectNamespaceMatchesRequestNamespace(ExpectedNamespaceForScope(requestNamespace, strategy.NamespaceScoped()), objectMeta); err != nil { 119 return err 120 } 121 122 // Ensure requests cannot update generation 123 oldMeta, err := meta.Accessor(old) 124 if err != nil { 125 return err 126 } 127 objectMeta.SetGeneration(oldMeta.GetGeneration()) 128 129 strategy.PrepareForUpdate(ctx, obj, old) 130 131 // Use the existing UID if none is provided 132 if len(objectMeta.GetUID()) == 0 { 133 objectMeta.SetUID(oldMeta.GetUID()) 134 } 135 // ignore changes to timestamp 136 if oldCreationTime := oldMeta.GetCreationTimestamp(); !oldCreationTime.IsZero() { 137 objectMeta.SetCreationTimestamp(oldMeta.GetCreationTimestamp()) 138 } 139 // an update can never remove/change a deletion timestamp 140 if !oldMeta.GetDeletionTimestamp().IsZero() { 141 objectMeta.SetDeletionTimestamp(oldMeta.GetDeletionTimestamp()) 142 } 143 // an update can never remove/change grace period seconds 144 if oldMeta.GetDeletionGracePeriodSeconds() != nil && objectMeta.GetDeletionGracePeriodSeconds() == nil { 145 objectMeta.SetDeletionGracePeriodSeconds(oldMeta.GetDeletionGracePeriodSeconds()) 146 } 147 148 // Ensure some common fields, like UID, are validated for all resources. 149 errs, err := validateCommonFields(obj, old, strategy) 150 if err != nil { 151 return errors.NewInternalError(err) 152 } 153 154 errs = append(errs, strategy.ValidateUpdate(ctx, obj, old)...) 155 if len(errs) > 0 { 156 return errors.NewInvalid(kind.GroupKind(), objectMeta.GetName(), errs) 157 } 158 159 for _, w := range strategy.WarningsOnUpdate(ctx, obj, old) { 160 warning.AddWarning(ctx, "", w) 161 } 162 163 strategy.Canonicalize(obj) 164 165 return nil 166 } 167 168 // TransformFunc is a function to transform and return newObj 169 type TransformFunc func(ctx context.Context, newObj runtime.Object, oldObj runtime.Object) (transformedNewObj runtime.Object, err error) 170 171 // defaultUpdatedObjectInfo implements UpdatedObjectInfo 172 type defaultUpdatedObjectInfo struct { 173 // obj is the updated object 174 obj runtime.Object 175 176 // transformers is an optional list of transforming functions that modify or 177 // replace obj using information from the context, old object, or other sources. 178 transformers []TransformFunc 179 } 180 181 // DefaultUpdatedObjectInfo returns an UpdatedObjectInfo impl based on the specified object. 182 func DefaultUpdatedObjectInfo(obj runtime.Object, transformers ...TransformFunc) UpdatedObjectInfo { 183 return &defaultUpdatedObjectInfo{obj, transformers} 184 } 185 186 // Preconditions satisfies the UpdatedObjectInfo interface. 187 func (i *defaultUpdatedObjectInfo) Preconditions() *metav1.Preconditions { 188 // Attempt to get the UID out of the object 189 accessor, err := meta.Accessor(i.obj) 190 if err != nil { 191 // If no UID can be read, no preconditions are possible 192 return nil 193 } 194 195 // If empty, no preconditions needed 196 uid := accessor.GetUID() 197 if len(uid) == 0 { 198 return nil 199 } 200 201 return &metav1.Preconditions{UID: &uid} 202 } 203 204 // UpdatedObject satisfies the UpdatedObjectInfo interface. 205 // It returns a copy of the held obj, passed through any configured transformers. 206 func (i *defaultUpdatedObjectInfo) UpdatedObject(ctx context.Context, oldObj runtime.Object) (runtime.Object, error) { 207 var err error 208 // Start with the configured object 209 newObj := i.obj 210 211 // If the original is non-nil (might be nil if the first transformer builds the object from the oldObj), make a copy, 212 // so we don't return the original. BeforeUpdate can mutate the returned object, doing things like clearing ResourceVersion. 213 // If we're re-called, we need to be able to return the pristine version. 214 if newObj != nil { 215 newObj = newObj.DeepCopyObject() 216 } 217 218 // Allow any configured transformers to update the new object 219 for _, transformer := range i.transformers { 220 newObj, err = transformer(ctx, newObj, oldObj) 221 if err != nil { 222 return nil, err 223 } 224 } 225 226 return newObj, nil 227 } 228 229 // wrappedUpdatedObjectInfo allows wrapping an existing objInfo and 230 // chaining additional transformations/checks on the result of UpdatedObject() 231 type wrappedUpdatedObjectInfo struct { 232 // obj is the updated object 233 objInfo UpdatedObjectInfo 234 235 // transformers is an optional list of transforming functions that modify or 236 // replace obj using information from the context, old object, or other sources. 237 transformers []TransformFunc 238 } 239 240 // WrapUpdatedObjectInfo returns an UpdatedObjectInfo impl that delegates to 241 // the specified objInfo, then calls the passed transformers 242 func WrapUpdatedObjectInfo(objInfo UpdatedObjectInfo, transformers ...TransformFunc) UpdatedObjectInfo { 243 return &wrappedUpdatedObjectInfo{objInfo, transformers} 244 } 245 246 // Preconditions satisfies the UpdatedObjectInfo interface. 247 func (i *wrappedUpdatedObjectInfo) Preconditions() *metav1.Preconditions { 248 return i.objInfo.Preconditions() 249 } 250 251 // UpdatedObject satisfies the UpdatedObjectInfo interface. 252 // It delegates to the wrapped objInfo and passes the result through any configured transformers. 253 func (i *wrappedUpdatedObjectInfo) UpdatedObject(ctx context.Context, oldObj runtime.Object) (runtime.Object, error) { 254 newObj, err := i.objInfo.UpdatedObject(ctx, oldObj) 255 if err != nil { 256 return newObj, err 257 } 258 259 // Allow any configured transformers to update the new object or error 260 for _, transformer := range i.transformers { 261 newObj, err = transformer(ctx, newObj, oldObj) 262 if err != nil { 263 return nil, err 264 } 265 } 266 267 return newObj, nil 268 } 269 270 // AdmissionToValidateObjectUpdateFunc converts validating admission to a rest validate object update func 271 func AdmissionToValidateObjectUpdateFunc(admit admission.Interface, staticAttributes admission.Attributes, o admission.ObjectInterfaces) ValidateObjectUpdateFunc { 272 validatingAdmission, ok := admit.(admission.ValidationInterface) 273 if !ok { 274 return func(ctx context.Context, obj, old runtime.Object) error { return nil } 275 } 276 return func(ctx context.Context, obj, old runtime.Object) error { 277 finalAttributes := admission.NewAttributesRecord( 278 obj, 279 old, 280 staticAttributes.GetKind(), 281 staticAttributes.GetNamespace(), 282 staticAttributes.GetName(), 283 staticAttributes.GetResource(), 284 staticAttributes.GetSubresource(), 285 staticAttributes.GetOperation(), 286 staticAttributes.GetOperationOptions(), 287 staticAttributes.IsDryRun(), 288 staticAttributes.GetUserInfo(), 289 ) 290 if !validatingAdmission.Handles(finalAttributes.GetOperation()) { 291 return nil 292 } 293 return validatingAdmission.Validate(ctx, finalAttributes, o) 294 } 295 }