k8s.io/apiserver@v0.31.1/pkg/endpoints/handlers/patch.go (about) 1 /* 2 Copyright 2017 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 handlers 18 19 import ( 20 "context" 21 "fmt" 22 "net/http" 23 "strings" 24 "time" 25 26 "go.opentelemetry.io/otel/attribute" 27 jsonpatch "gopkg.in/evanphx/json-patch.v4" 28 kjson "sigs.k8s.io/json" 29 30 "k8s.io/apimachinery/pkg/api/errors" 31 "k8s.io/apimachinery/pkg/api/meta" 32 metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 35 "k8s.io/apimachinery/pkg/apis/meta/v1/validation" 36 "k8s.io/apimachinery/pkg/runtime" 37 "k8s.io/apimachinery/pkg/runtime/schema" 38 "k8s.io/apimachinery/pkg/types" 39 "k8s.io/apimachinery/pkg/util/managedfields" 40 "k8s.io/apimachinery/pkg/util/mergepatch" 41 "k8s.io/apimachinery/pkg/util/sets" 42 "k8s.io/apimachinery/pkg/util/strategicpatch" 43 "k8s.io/apimachinery/pkg/util/validation/field" 44 "k8s.io/apimachinery/pkg/util/yaml" 45 "k8s.io/apiserver/pkg/admission" 46 "k8s.io/apiserver/pkg/audit" 47 "k8s.io/apiserver/pkg/authorization/authorizer" 48 "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager" 49 "k8s.io/apiserver/pkg/endpoints/handlers/finisher" 50 requestmetrics "k8s.io/apiserver/pkg/endpoints/handlers/metrics" 51 "k8s.io/apiserver/pkg/endpoints/handlers/negotiation" 52 "k8s.io/apiserver/pkg/endpoints/request" 53 "k8s.io/apiserver/pkg/registry/rest" 54 "k8s.io/apiserver/pkg/util/dryrun" 55 "k8s.io/component-base/tracing" 56 ) 57 58 const ( 59 // maximum number of operations a single json patch may contain. 60 maxJSONPatchOperations = 10000 61 ) 62 63 // PatchResource returns a function that will handle a resource patch. 64 func PatchResource(r rest.Patcher, scope *RequestScope, admit admission.Interface, patchTypes []string) http.HandlerFunc { 65 return func(w http.ResponseWriter, req *http.Request) { 66 ctx := req.Context() 67 // For performance tracking purposes. 68 ctx, span := tracing.Start(ctx, "Patch", traceFields(req)...) 69 defer span.End(500 * time.Millisecond) 70 71 // Do this first, otherwise name extraction can fail for unrecognized content types 72 // TODO: handle this in negotiation 73 contentType := req.Header.Get("Content-Type") 74 // Remove "; charset=" if included in header. 75 if idx := strings.Index(contentType, ";"); idx > 0 { 76 contentType = contentType[:idx] 77 } 78 patchType := types.PatchType(contentType) 79 80 // Ensure the patchType is one we support 81 if !sets.NewString(patchTypes...).Has(contentType) { 82 scope.err(negotiation.NewUnsupportedMediaTypeError(patchTypes), w, req) 83 return 84 } 85 86 namespace, name, err := scope.Namer.Name(req) 87 if err != nil { 88 scope.err(err, w, req) 89 return 90 } 91 92 // enforce a timeout of at most requestTimeoutUpperBound (34s) or less if the user-provided 93 // timeout inside the parent context is lower than requestTimeoutUpperBound. 94 ctx, cancel := context.WithTimeout(ctx, requestTimeoutUpperBound) 95 defer cancel() 96 97 ctx = request.WithNamespace(ctx, namespace) 98 99 outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope) 100 if err != nil { 101 scope.err(err, w, req) 102 return 103 } 104 105 patchBytes, err := limitedReadBodyWithRecordMetric(ctx, req, scope.MaxRequestBodyBytes, scope.Resource.GroupResource().String(), requestmetrics.Patch) 106 if err != nil { 107 span.AddEvent("limitedReadBody failed", attribute.Int("len", len(patchBytes)), attribute.String("err", err.Error())) 108 scope.err(err, w, req) 109 return 110 } 111 span.AddEvent("limitedReadBody succeeded", attribute.Int("len", len(patchBytes))) 112 113 options := &metav1.PatchOptions{} 114 if err := metainternalversionscheme.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, options); err != nil { 115 err = errors.NewBadRequest(err.Error()) 116 scope.err(err, w, req) 117 return 118 } 119 if errs := validation.ValidatePatchOptions(options, patchType); len(errs) > 0 { 120 err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "PatchOptions"}, "", errs) 121 scope.err(err, w, req) 122 return 123 } 124 options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("PatchOptions")) 125 126 admit = admission.WithAudit(admit) 127 128 audit.LogRequestPatch(req.Context(), patchBytes) 129 span.AddEvent("Recorded the audit event") 130 131 baseContentType := runtime.ContentTypeJSON 132 if patchType == types.ApplyPatchType { 133 baseContentType = runtime.ContentTypeYAML 134 } 135 s, ok := runtime.SerializerInfoForMediaType(scope.Serializer.SupportedMediaTypes(), baseContentType) 136 if !ok { 137 scope.err(fmt.Errorf("no serializer defined for %v", baseContentType), w, req) 138 return 139 } 140 gv := scope.Kind.GroupVersion() 141 142 validationDirective := fieldValidation(options.FieldValidation) 143 decodeSerializer := s.Serializer 144 if validationDirective == metav1.FieldValidationWarn || validationDirective == metav1.FieldValidationStrict { 145 decodeSerializer = s.StrictSerializer 146 } 147 148 codec := runtime.NewCodec( 149 scope.Serializer.EncoderForVersion(s.Serializer, gv), 150 scope.Serializer.DecoderToVersion(decodeSerializer, scope.HubGroupVersion), 151 ) 152 153 userInfo, _ := request.UserFrom(ctx) 154 staticCreateAttributes := admission.NewAttributesRecord( 155 nil, 156 nil, 157 scope.Kind, 158 namespace, 159 name, 160 scope.Resource, 161 scope.Subresource, 162 admission.Create, 163 patchToCreateOptions(options), 164 dryrun.IsDryRun(options.DryRun), 165 userInfo) 166 staticUpdateAttributes := admission.NewAttributesRecord( 167 nil, 168 nil, 169 scope.Kind, 170 namespace, 171 name, 172 scope.Resource, 173 scope.Subresource, 174 admission.Update, 175 patchToUpdateOptions(options), 176 dryrun.IsDryRun(options.DryRun), 177 userInfo, 178 ) 179 180 admit = fieldmanager.NewManagedFieldsValidatingAdmissionController(admit) 181 182 mutatingAdmission, _ := admit.(admission.MutationInterface) 183 createAuthorizerAttributes := authorizer.AttributesRecord{ 184 User: userInfo, 185 ResourceRequest: true, 186 Path: req.URL.Path, 187 Verb: "create", 188 APIGroup: scope.Resource.Group, 189 APIVersion: scope.Resource.Version, 190 Resource: scope.Resource.Resource, 191 Subresource: scope.Subresource, 192 Namespace: namespace, 193 Name: name, 194 } 195 196 p := patcher{ 197 namer: scope.Namer, 198 creater: scope.Creater, 199 defaulter: scope.Defaulter, 200 typer: scope.Typer, 201 unsafeConvertor: scope.UnsafeConvertor, 202 kind: scope.Kind, 203 resource: scope.Resource, 204 subresource: scope.Subresource, 205 dryRun: dryrun.IsDryRun(options.DryRun), 206 validationDirective: validationDirective, 207 208 objectInterfaces: scope, 209 210 hubGroupVersion: scope.HubGroupVersion, 211 212 createValidation: withAuthorization(rest.AdmissionToValidateObjectFunc(admit, staticCreateAttributes, scope), scope.Authorizer, createAuthorizerAttributes), 213 updateValidation: rest.AdmissionToValidateObjectUpdateFunc(admit, staticUpdateAttributes, scope), 214 admissionCheck: mutatingAdmission, 215 216 codec: codec, 217 218 options: options, 219 220 restPatcher: r, 221 name: name, 222 patchType: patchType, 223 patchBytes: patchBytes, 224 userAgent: req.UserAgent(), 225 } 226 227 result, wasCreated, err := p.patchResource(ctx, scope) 228 if err != nil { 229 scope.err(err, w, req) 230 return 231 } 232 span.AddEvent("Object stored in database") 233 234 status := http.StatusOK 235 if wasCreated { 236 status = http.StatusCreated 237 } 238 239 span.AddEvent("About to write a response") 240 defer span.AddEvent("Writing http response done") 241 transformResponseObject(ctx, scope, req, w, status, outputMediaType, result) 242 } 243 } 244 245 type mutateObjectUpdateFunc func(ctx context.Context, obj, old runtime.Object) error 246 247 // patcher breaks the process of patch application and retries into smaller 248 // pieces of functionality. 249 // TODO: Use builder pattern to construct this object? 250 // TODO: As part of that effort, some aspects of PatchResource above could be 251 // moved into this type. 252 type patcher struct { 253 // Pieces of RequestScope 254 namer ScopeNamer 255 creater runtime.ObjectCreater 256 defaulter runtime.ObjectDefaulter 257 typer runtime.ObjectTyper 258 unsafeConvertor runtime.ObjectConvertor 259 resource schema.GroupVersionResource 260 kind schema.GroupVersionKind 261 subresource string 262 dryRun bool 263 validationDirective string 264 265 objectInterfaces admission.ObjectInterfaces 266 267 hubGroupVersion schema.GroupVersion 268 269 // Validation functions 270 createValidation rest.ValidateObjectFunc 271 updateValidation rest.ValidateObjectUpdateFunc 272 admissionCheck admission.MutationInterface 273 274 codec runtime.Codec 275 276 options *metav1.PatchOptions 277 278 // Operation information 279 restPatcher rest.Patcher 280 name string 281 patchType types.PatchType 282 patchBytes []byte 283 userAgent string 284 285 // Set at invocation-time (by applyPatch) and immutable thereafter 286 namespace string 287 updatedObjectInfo rest.UpdatedObjectInfo 288 mechanism patchMechanism 289 forceAllowCreate bool 290 } 291 292 type patchMechanism interface { 293 applyPatchToCurrentObject(requextContext context.Context, currentObject runtime.Object) (runtime.Object, error) 294 createNewObject(requestContext context.Context) (runtime.Object, error) 295 } 296 297 type jsonPatcher struct { 298 *patcher 299 300 fieldManager *managedfields.FieldManager 301 } 302 303 func (p *jsonPatcher) applyPatchToCurrentObject(requestContext context.Context, currentObject runtime.Object) (runtime.Object, error) { 304 // Encode will convert & return a versioned object in JSON. 305 currentObjJS, err := runtime.Encode(p.codec, currentObject) 306 if err != nil { 307 return nil, err 308 } 309 310 // Apply the patch. 311 patchedObjJS, appliedStrictErrs, err := p.applyJSPatch(currentObjJS) 312 if err != nil { 313 return nil, err 314 } 315 316 // Construct the resulting typed, unversioned object. 317 objToUpdate := p.restPatcher.New() 318 if err := runtime.DecodeInto(p.codec, patchedObjJS, objToUpdate); err != nil { 319 strictError, isStrictError := runtime.AsStrictDecodingError(err) 320 switch { 321 case !isStrictError: 322 // disregard any appliedStrictErrs, because it's an incomplete 323 // list of strict errors given that we don't know what fields were 324 // unknown because DecodeInto failed. Non-strict errors trump in this case. 325 return nil, errors.NewInvalid(schema.GroupKind{}, "", field.ErrorList{ 326 field.Invalid(field.NewPath("patch"), string(patchedObjJS), err.Error()), 327 }) 328 case p.validationDirective == metav1.FieldValidationWarn: 329 addStrictDecodingWarnings(requestContext, append(appliedStrictErrs, strictError.Errors()...)) 330 default: 331 strictDecodingError := runtime.NewStrictDecodingError(append(appliedStrictErrs, strictError.Errors()...)) 332 return nil, errors.NewInvalid(schema.GroupKind{}, "", field.ErrorList{ 333 field.Invalid(field.NewPath("patch"), string(patchedObjJS), strictDecodingError.Error()), 334 }) 335 } 336 } else if len(appliedStrictErrs) > 0 { 337 switch { 338 case p.validationDirective == metav1.FieldValidationWarn: 339 addStrictDecodingWarnings(requestContext, appliedStrictErrs) 340 default: 341 return nil, errors.NewInvalid(schema.GroupKind{}, "", field.ErrorList{ 342 field.Invalid(field.NewPath("patch"), string(patchedObjJS), runtime.NewStrictDecodingError(appliedStrictErrs).Error()), 343 }) 344 } 345 } 346 347 if p.options == nil { 348 // Provide a more informative error for the crash that would 349 // happen on the next line 350 panic("PatchOptions required but not provided") 351 } 352 objToUpdate = p.fieldManager.UpdateNoErrors(currentObject, objToUpdate, managerOrUserAgent(p.options.FieldManager, p.userAgent)) 353 return objToUpdate, nil 354 } 355 356 func (p *jsonPatcher) createNewObject(_ context.Context) (runtime.Object, error) { 357 return nil, errors.NewNotFound(p.resource.GroupResource(), p.name) 358 } 359 360 type jsonPatchOp struct { 361 Op string `json:"op"` 362 Path string `json:"path"` 363 From string `json:"from"` 364 Value interface{} `json:"value"` 365 } 366 367 // applyJSPatch applies the patch. Input and output objects must both have 368 // the external version, since that is what the patch must have been constructed against. 369 func (p *jsonPatcher) applyJSPatch(versionedJS []byte) (patchedJS []byte, strictErrors []error, retErr error) { 370 switch p.patchType { 371 case types.JSONPatchType: 372 if p.validationDirective == metav1.FieldValidationStrict || p.validationDirective == metav1.FieldValidationWarn { 373 var v []jsonPatchOp 374 var err error 375 if strictErrors, err = kjson.UnmarshalStrict(p.patchBytes, &v); err != nil { 376 return nil, nil, errors.NewBadRequest(fmt.Sprintf("error decoding patch: %v", err)) 377 } 378 for i, e := range strictErrors { 379 strictErrors[i] = fmt.Errorf("json patch %v", e) 380 } 381 } 382 383 patchObj, err := jsonpatch.DecodePatch(p.patchBytes) 384 if err != nil { 385 return nil, nil, errors.NewBadRequest(err.Error()) 386 } 387 if len(patchObj) > maxJSONPatchOperations { 388 return nil, nil, errors.NewRequestEntityTooLargeError( 389 fmt.Sprintf("The allowed maximum operations in a JSON patch is %d, got %d", 390 maxJSONPatchOperations, len(patchObj))) 391 } 392 patchedJS, err := patchObj.Apply(versionedJS) 393 if err != nil { 394 return nil, nil, errors.NewGenericServerResponse(http.StatusUnprocessableEntity, "", schema.GroupResource{}, "", err.Error(), 0, false) 395 } 396 return patchedJS, strictErrors, nil 397 case types.MergePatchType: 398 if p.validationDirective == metav1.FieldValidationStrict || p.validationDirective == metav1.FieldValidationWarn { 399 v := map[string]interface{}{} 400 var err error 401 strictErrors, err = kjson.UnmarshalStrict(p.patchBytes, &v) 402 if err != nil { 403 return nil, nil, errors.NewBadRequest(fmt.Sprintf("error decoding patch: %v", err)) 404 } 405 } 406 407 patchedJS, retErr = jsonpatch.MergePatch(versionedJS, p.patchBytes) 408 if retErr == jsonpatch.ErrBadJSONPatch { 409 return nil, nil, errors.NewBadRequest(retErr.Error()) 410 } 411 return patchedJS, strictErrors, retErr 412 default: 413 // only here as a safety net - go-restful filters content-type 414 return nil, nil, fmt.Errorf("unknown Content-Type header for patch: %v", p.patchType) 415 } 416 } 417 418 type smpPatcher struct { 419 *patcher 420 421 // Schema 422 schemaReferenceObj runtime.Object 423 fieldManager *managedfields.FieldManager 424 } 425 426 func (p *smpPatcher) applyPatchToCurrentObject(requestContext context.Context, currentObject runtime.Object) (runtime.Object, error) { 427 // Since the patch is applied on versioned objects, we need to convert the 428 // current object to versioned representation first. 429 currentVersionedObject, err := p.unsafeConvertor.ConvertToVersion(currentObject, p.kind.GroupVersion()) 430 if err != nil { 431 return nil, err 432 } 433 versionedObjToUpdate, err := p.creater.New(p.kind) 434 if err != nil { 435 return nil, err 436 } 437 if err := strategicPatchObject(requestContext, p.defaulter, currentVersionedObject, p.patchBytes, versionedObjToUpdate, p.schemaReferenceObj, p.validationDirective); err != nil { 438 return nil, err 439 } 440 // Convert the object back to the hub version 441 newObj, err := p.unsafeConvertor.ConvertToVersion(versionedObjToUpdate, p.hubGroupVersion) 442 if err != nil { 443 return nil, err 444 } 445 446 newObj = p.fieldManager.UpdateNoErrors(currentObject, newObj, managerOrUserAgent(p.options.FieldManager, p.userAgent)) 447 return newObj, nil 448 } 449 450 func (p *smpPatcher) createNewObject(_ context.Context) (runtime.Object, error) { 451 return nil, errors.NewNotFound(p.resource.GroupResource(), p.name) 452 } 453 454 type applyPatcher struct { 455 patch []byte 456 options *metav1.PatchOptions 457 creater runtime.ObjectCreater 458 kind schema.GroupVersionKind 459 fieldManager *managedfields.FieldManager 460 userAgent string 461 validationDirective string 462 } 463 464 func (p *applyPatcher) applyPatchToCurrentObject(requestContext context.Context, obj runtime.Object) (runtime.Object, error) { 465 force := false 466 if p.options.Force != nil { 467 force = *p.options.Force 468 } 469 if p.fieldManager == nil { 470 panic("FieldManager must be installed to run apply") 471 } 472 473 patchObj := &unstructured.Unstructured{Object: map[string]interface{}{}} 474 if err := yaml.Unmarshal(p.patch, &patchObj.Object); err != nil { 475 return nil, errors.NewBadRequest(fmt.Sprintf("error decoding YAML: %v", err)) 476 } 477 478 obj, err := p.fieldManager.Apply(obj, patchObj, p.options.FieldManager, force) 479 if err != nil { 480 return obj, err 481 } 482 483 // TODO: spawn something to track deciding whether a fieldValidation=Strict 484 // fatal error should return before an error from the apply operation 485 if p.validationDirective == metav1.FieldValidationStrict || p.validationDirective == metav1.FieldValidationWarn { 486 if err := yaml.UnmarshalStrict(p.patch, &map[string]interface{}{}); err != nil { 487 if p.validationDirective == metav1.FieldValidationStrict { 488 return nil, errors.NewBadRequest(fmt.Sprintf("error strict decoding YAML: %v", err)) 489 } 490 addStrictDecodingWarnings(requestContext, []error{err}) 491 } 492 } 493 return obj, nil 494 } 495 496 func (p *applyPatcher) createNewObject(requestContext context.Context) (runtime.Object, error) { 497 obj, err := p.creater.New(p.kind) 498 if err != nil { 499 return nil, fmt.Errorf("failed to create new object: %v", err) 500 } 501 return p.applyPatchToCurrentObject(requestContext, obj) 502 } 503 504 // strategicPatchObject applies a strategic merge patch of `patchBytes` to 505 // `originalObject` and stores the result in `objToUpdate`. 506 // It additionally returns the map[string]interface{} representation of the 507 // `originalObject` and `patchBytes`. 508 // NOTE: Both `originalObject` and `objToUpdate` are supposed to be versioned. 509 func strategicPatchObject( 510 requestContext context.Context, 511 defaulter runtime.ObjectDefaulter, 512 originalObject runtime.Object, 513 patchBytes []byte, 514 objToUpdate runtime.Object, 515 schemaReferenceObj runtime.Object, 516 validationDirective string, 517 ) error { 518 originalObjMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(originalObject) 519 if err != nil { 520 return err 521 } 522 523 patchMap := make(map[string]interface{}) 524 var strictErrs []error 525 if validationDirective == metav1.FieldValidationWarn || validationDirective == metav1.FieldValidationStrict { 526 strictErrs, err = kjson.UnmarshalStrict(patchBytes, &patchMap) 527 if err != nil { 528 return errors.NewBadRequest(err.Error()) 529 } 530 } else { 531 if err = kjson.UnmarshalCaseSensitivePreserveInts(patchBytes, &patchMap); err != nil { 532 return errors.NewBadRequest(err.Error()) 533 } 534 } 535 536 if err := applyPatchToObject(requestContext, defaulter, originalObjMap, patchMap, objToUpdate, schemaReferenceObj, strictErrs, validationDirective); err != nil { 537 return err 538 } 539 return nil 540 } 541 542 // applyPatch is called every time GuaranteedUpdate asks for the updated object, 543 // and is given the currently persisted object as input. 544 // TODO: rename this function because the name implies it is related to applyPatcher 545 func (p *patcher) applyPatch(ctx context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) { 546 // Make sure we actually have a persisted currentObject 547 tracing.SpanFromContext(ctx).AddEvent("About to apply patch") 548 currentObjectHasUID, err := hasUID(currentObject) 549 if err != nil { 550 return nil, err 551 } else if !currentObjectHasUID { 552 objToUpdate, patchErr = p.mechanism.createNewObject(ctx) 553 } else { 554 objToUpdate, patchErr = p.mechanism.applyPatchToCurrentObject(ctx, currentObject) 555 } 556 557 if patchErr != nil { 558 return nil, patchErr 559 } 560 561 objToUpdateHasUID, err := hasUID(objToUpdate) 562 if err != nil { 563 return nil, err 564 } 565 if objToUpdateHasUID && !currentObjectHasUID { 566 accessor, err := meta.Accessor(objToUpdate) 567 if err != nil { 568 return nil, err 569 } 570 return nil, errors.NewConflict(p.resource.GroupResource(), p.name, fmt.Errorf("uid mismatch: the provided object specified uid %s, and no existing object was found", accessor.GetUID())) 571 } 572 573 // if this object supports namespace info 574 if objectMeta, err := meta.Accessor(objToUpdate); err == nil { 575 // ensure namespace on the object is correct, or error if a conflicting namespace was set in the object 576 if err := rest.EnsureObjectNamespaceMatchesRequestNamespace(rest.ExpectedNamespaceForResource(p.namespace, p.resource), objectMeta); err != nil { 577 return nil, err 578 } 579 } 580 581 if err := checkName(objToUpdate, p.name, p.namespace, p.namer); err != nil { 582 return nil, err 583 } 584 return objToUpdate, nil 585 } 586 587 func (p *patcher) admissionAttributes(ctx context.Context, updatedObject runtime.Object, currentObject runtime.Object, operation admission.Operation, operationOptions runtime.Object) admission.Attributes { 588 userInfo, _ := request.UserFrom(ctx) 589 return admission.NewAttributesRecord(updatedObject, currentObject, p.kind, p.namespace, p.name, p.resource, p.subresource, operation, operationOptions, p.dryRun, userInfo) 590 } 591 592 // applyAdmission is called every time GuaranteedUpdate asks for the updated object, 593 // and is given the currently persisted object and the patched object as input. 594 // TODO: rename this function because the name implies it is related to applyPatcher 595 func (p *patcher) applyAdmission(ctx context.Context, patchedObject runtime.Object, currentObject runtime.Object) (runtime.Object, error) { 596 tracing.SpanFromContext(ctx).AddEvent("About to check admission control") 597 var operation admission.Operation 598 var options runtime.Object 599 if hasUID, err := hasUID(currentObject); err != nil { 600 return nil, err 601 } else if !hasUID { 602 operation = admission.Create 603 currentObject = nil 604 options = patchToCreateOptions(p.options) 605 } else { 606 operation = admission.Update 607 options = patchToUpdateOptions(p.options) 608 } 609 if p.admissionCheck != nil && p.admissionCheck.Handles(operation) { 610 attributes := p.admissionAttributes(ctx, patchedObject, currentObject, operation, options) 611 return patchedObject, p.admissionCheck.Admit(ctx, attributes, p.objectInterfaces) 612 } 613 return patchedObject, nil 614 } 615 616 // patchResource divides PatchResource for easier unit testing 617 func (p *patcher) patchResource(ctx context.Context, scope *RequestScope) (runtime.Object, bool, error) { 618 p.namespace = request.NamespaceValue(ctx) 619 switch p.patchType { 620 case types.JSONPatchType, types.MergePatchType: 621 p.mechanism = &jsonPatcher{ 622 patcher: p, 623 fieldManager: scope.FieldManager, 624 } 625 case types.StrategicMergePatchType: 626 schemaReferenceObj, err := p.unsafeConvertor.ConvertToVersion(p.restPatcher.New(), p.kind.GroupVersion()) 627 if err != nil { 628 return nil, false, err 629 } 630 p.mechanism = &smpPatcher{ 631 patcher: p, 632 schemaReferenceObj: schemaReferenceObj, 633 fieldManager: scope.FieldManager, 634 } 635 // this case is unreachable if ServerSideApply is not enabled because we will have already rejected the content type 636 case types.ApplyPatchType: 637 p.mechanism = &applyPatcher{ 638 fieldManager: scope.FieldManager, 639 patch: p.patchBytes, 640 options: p.options, 641 creater: p.creater, 642 kind: p.kind, 643 userAgent: p.userAgent, 644 validationDirective: p.validationDirective, 645 } 646 p.forceAllowCreate = true 647 default: 648 return nil, false, fmt.Errorf("%v: unimplemented patch type", p.patchType) 649 } 650 dedupOwnerReferencesTransformer := func(_ context.Context, obj, _ runtime.Object) (runtime.Object, error) { 651 // Dedup owner references after mutating admission happens 652 dedupOwnerReferencesAndAddWarning(obj, ctx, true) 653 return obj, nil 654 } 655 656 transformers := []rest.TransformFunc{p.applyPatch, p.applyAdmission, dedupOwnerReferencesTransformer} 657 658 wasCreated := false 659 p.updatedObjectInfo = rest.DefaultUpdatedObjectInfo(nil, transformers...) 660 requestFunc := func() (runtime.Object, error) { 661 // Pass in UpdateOptions to override UpdateStrategy.AllowUpdateOnCreate 662 options := patchToUpdateOptions(p.options) 663 updateObject, created, updateErr := p.restPatcher.Update(ctx, p.name, p.updatedObjectInfo, p.createValidation, p.updateValidation, p.forceAllowCreate, options) 664 wasCreated = created 665 return updateObject, updateErr 666 } 667 result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) { 668 669 result, err := requestFunc() 670 // If the object wasn't committed to storage because it's serialized size was too large, 671 // it is safe to remove managedFields (which can be large) and try again. 672 if isTooLargeError(err) && p.patchType != types.ApplyPatchType { 673 if _, accessorErr := meta.Accessor(p.restPatcher.New()); accessorErr == nil { 674 p.updatedObjectInfo = rest.DefaultUpdatedObjectInfo(nil, 675 p.applyPatch, 676 p.applyAdmission, 677 dedupOwnerReferencesTransformer, 678 func(_ context.Context, obj, _ runtime.Object) (runtime.Object, error) { 679 accessor, _ := meta.Accessor(obj) 680 accessor.SetManagedFields(nil) 681 return obj, nil 682 }) 683 result, err = requestFunc() 684 } 685 } 686 return result, err 687 }) 688 return result, wasCreated, err 689 } 690 691 // applyPatchToObject applies a strategic merge patch of <patchMap> to 692 // <originalMap> and stores the result in <objToUpdate>. 693 // NOTE: <objToUpdate> must be a versioned object. 694 func applyPatchToObject( 695 requestContext context.Context, 696 defaulter runtime.ObjectDefaulter, 697 originalMap map[string]interface{}, 698 patchMap map[string]interface{}, 699 objToUpdate runtime.Object, 700 schemaReferenceObj runtime.Object, 701 strictErrs []error, 702 validationDirective string, 703 ) error { 704 patchedObjMap, err := strategicpatch.StrategicMergeMapPatch(originalMap, patchMap, schemaReferenceObj) 705 if err != nil { 706 return interpretStrategicMergePatchError(err) 707 } 708 709 // Rather than serialize the patched map to JSON, then decode it to an object, we go directly from a map to an object 710 converter := runtime.DefaultUnstructuredConverter 711 returnUnknownFields := validationDirective == metav1.FieldValidationWarn || validationDirective == metav1.FieldValidationStrict 712 if err := converter.FromUnstructuredWithValidation(patchedObjMap, objToUpdate, returnUnknownFields); err != nil { 713 strictError, isStrictError := runtime.AsStrictDecodingError(err) 714 switch { 715 case !isStrictError: 716 // disregard any sttrictErrs, because it's an incomplete 717 // list of strict errors given that we don't know what fields were 718 // unknown because StrategicMergeMapPatch failed. 719 // Non-strict errors trump in this case. 720 return errors.NewInvalid(schema.GroupKind{}, "", field.ErrorList{ 721 field.Invalid(field.NewPath("patch"), fmt.Sprintf("%+v", patchMap), err.Error()), 722 }) 723 case validationDirective == metav1.FieldValidationWarn: 724 addStrictDecodingWarnings(requestContext, append(strictErrs, strictError.Errors()...)) 725 default: 726 strictDecodingError := runtime.NewStrictDecodingError(append(strictErrs, strictError.Errors()...)) 727 return errors.NewInvalid(schema.GroupKind{}, "", field.ErrorList{ 728 field.Invalid(field.NewPath("patch"), fmt.Sprintf("%+v", patchMap), strictDecodingError.Error()), 729 }) 730 } 731 } else if len(strictErrs) > 0 { 732 switch { 733 case validationDirective == metav1.FieldValidationWarn: 734 addStrictDecodingWarnings(requestContext, strictErrs) 735 default: 736 return errors.NewInvalid(schema.GroupKind{}, "", field.ErrorList{ 737 field.Invalid(field.NewPath("patch"), fmt.Sprintf("%+v", patchMap), runtime.NewStrictDecodingError(strictErrs).Error()), 738 }) 739 } 740 } 741 742 // Decoding from JSON to a versioned object would apply defaults, so we do the same here 743 defaulter.Default(objToUpdate) 744 745 return nil 746 } 747 748 // interpretStrategicMergePatchError interprets the error type and returns an error with appropriate HTTP code. 749 func interpretStrategicMergePatchError(err error) error { 750 switch err { 751 case mergepatch.ErrBadJSONDoc, mergepatch.ErrBadPatchFormatForPrimitiveList, mergepatch.ErrBadPatchFormatForRetainKeys, mergepatch.ErrBadPatchFormatForSetElementOrderList, mergepatch.ErrUnsupportedStrategicMergePatchFormat: 752 return errors.NewBadRequest(err.Error()) 753 case mergepatch.ErrNoListOfLists, mergepatch.ErrPatchContentNotMatchRetainKeys: 754 return errors.NewGenericServerResponse(http.StatusUnprocessableEntity, "", schema.GroupResource{}, "", err.Error(), 0, false) 755 default: 756 return err 757 } 758 } 759 760 // patchToUpdateOptions creates an UpdateOptions with the same field values as the provided PatchOptions. 761 func patchToUpdateOptions(po *metav1.PatchOptions) *metav1.UpdateOptions { 762 if po == nil { 763 return nil 764 } 765 uo := &metav1.UpdateOptions{ 766 DryRun: po.DryRun, 767 FieldManager: po.FieldManager, 768 FieldValidation: po.FieldValidation, 769 } 770 uo.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("UpdateOptions")) 771 return uo 772 } 773 774 // patchToCreateOptions creates an CreateOptions with the same field values as the provided PatchOptions. 775 func patchToCreateOptions(po *metav1.PatchOptions) *metav1.CreateOptions { 776 if po == nil { 777 return nil 778 } 779 co := &metav1.CreateOptions{ 780 DryRun: po.DryRun, 781 FieldManager: po.FieldManager, 782 FieldValidation: po.FieldValidation, 783 } 784 co.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("CreateOptions")) 785 return co 786 }