1/* 2Copyright 2017 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 handlers 18 19import ( 20 "context" 21 "fmt" 22 "net/http" 23 "strings" 24 "time" 25 26 jsonpatch "github.com/evanphx/json-patch" 27 "k8s.io/apimachinery/pkg/api/errors" 28 "k8s.io/apimachinery/pkg/api/meta" 29 metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 32 "k8s.io/apimachinery/pkg/apis/meta/v1/validation" 33 "k8s.io/apimachinery/pkg/runtime" 34 "k8s.io/apimachinery/pkg/runtime/schema" 35 "k8s.io/apimachinery/pkg/types" 36 "k8s.io/apimachinery/pkg/util/json" 37 "k8s.io/apimachinery/pkg/util/mergepatch" 38 "k8s.io/apimachinery/pkg/util/sets" 39 "k8s.io/apimachinery/pkg/util/strategicpatch" 40 "k8s.io/apimachinery/pkg/util/validation/field" 41 "k8s.io/apimachinery/pkg/util/yaml" 42 "k8s.io/apiserver/pkg/admission" 43 "k8s.io/apiserver/pkg/audit" 44 "k8s.io/apiserver/pkg/authorization/authorizer" 45 "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager" 46 "k8s.io/apiserver/pkg/endpoints/handlers/finisher" 47 "k8s.io/apiserver/pkg/endpoints/handlers/negotiation" 48 "k8s.io/apiserver/pkg/endpoints/request" 49 "k8s.io/apiserver/pkg/features" 50 "k8s.io/apiserver/pkg/registry/rest" 51 "k8s.io/apiserver/pkg/util/dryrun" 52 utilfeature "k8s.io/apiserver/pkg/util/feature" 53 utiltrace "k8s.io/utils/trace" 54) 55 56const ( 57 // maximum number of operations a single json patch may contain. 58 maxJSONPatchOperations = 10000 59) 60 61// PatchResource returns a function that will handle a resource patch. 62func PatchResource(r rest.Patcher, scope *RequestScope, admit admission.Interface, patchTypes []string) http.HandlerFunc { 63 return func(w http.ResponseWriter, req *http.Request) { 64 // For performance tracking purposes. 65 trace := utiltrace.New("Patch", traceFields(req)...) 66 defer trace.LogIfLong(500 * time.Millisecond) 67 68 if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) { 69 scope.err(errors.NewBadRequest("the dryRun feature is disabled"), w, req) 70 return 71 } 72 73 // Do this first, otherwise name extraction can fail for unrecognized content types 74 // TODO: handle this in negotiation 75 contentType := req.Header.Get("Content-Type") 76 // Remove "; charset=" if included in header. 77 if idx := strings.Index(contentType, ";"); idx > 0 { 78 contentType = contentType[:idx] 79 } 80 patchType := types.PatchType(contentType) 81 82 // Ensure the patchType is one we support 83 if !sets.NewString(patchTypes...).Has(contentType) { 84 scope.err(negotiation.NewUnsupportedMediaTypeError(patchTypes), w, req) 85 return 86 } 87 88 namespace, name, err := scope.Namer.Name(req) 89 if err != nil { 90 scope.err(err, w, req) 91 return 92 } 93 94 // enforce a timeout of at most requestTimeoutUpperBound (34s) or less if the user-provided 95 // timeout inside the parent context is lower than requestTimeoutUpperBound. 96 ctx, cancel := context.WithTimeout(req.Context(), requestTimeoutUpperBound) 97 defer cancel() 98 99 ctx = request.WithNamespace(ctx, namespace) 100 101 outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope) 102 if err != nil { 103 scope.err(err, w, req) 104 return 105 } 106 107 patchBytes, err := limitedReadBody(req, scope.MaxRequestBodyBytes) 108 if err != nil { 109 scope.err(err, w, req) 110 return 111 } 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 ae := request.AuditEventFrom(ctx) 127 admit = admission.WithAudit(admit, ae) 128 129 audit.LogRequestPatch(ae, patchBytes) 130 trace.Step("Recorded the audit event") 131 132 baseContentType := runtime.ContentTypeJSON 133 if patchType == types.ApplyPatchType { 134 baseContentType = runtime.ContentTypeYAML 135 } 136 s, ok := runtime.SerializerInfoForMediaType(scope.Serializer.SupportedMediaTypes(), baseContentType) 137 if !ok { 138 scope.err(fmt.Errorf("no serializer defined for %v", baseContentType), w, req) 139 return 140 } 141 gv := scope.Kind.GroupVersion() 142 143 codec := runtime.NewCodec( 144 scope.Serializer.EncoderForVersion(s.Serializer, gv), 145 scope.Serializer.DecoderToVersion(s.Serializer, scope.HubGroupVersion), 146 ) 147 148 userInfo, _ := request.UserFrom(ctx) 149 staticCreateAttributes := admission.NewAttributesRecord( 150 nil, 151 nil, 152 scope.Kind, 153 namespace, 154 name, 155 scope.Resource, 156 scope.Subresource, 157 admission.Create, 158 patchToCreateOptions(options), 159 dryrun.IsDryRun(options.DryRun), 160 userInfo) 161 staticUpdateAttributes := admission.NewAttributesRecord( 162 nil, 163 nil, 164 scope.Kind, 165 namespace, 166 name, 167 scope.Resource, 168 scope.Subresource, 169 admission.Update, 170 patchToUpdateOptions(options), 171 dryrun.IsDryRun(options.DryRun), 172 userInfo, 173 ) 174 175 if scope.FieldManager != nil { 176 admit = fieldmanager.NewManagedFieldsValidatingAdmissionController(admit) 177 } 178 mutatingAdmission, _ := admit.(admission.MutationInterface) 179 createAuthorizerAttributes := authorizer.AttributesRecord{ 180 User: userInfo, 181 ResourceRequest: true, 182 Path: req.URL.Path, 183 Verb: "create", 184 APIGroup: scope.Resource.Group, 185 APIVersion: scope.Resource.Version, 186 Resource: scope.Resource.Resource, 187 Subresource: scope.Subresource, 188 Namespace: namespace, 189 Name: name, 190 } 191 192 p := patcher{ 193 namer: scope.Namer, 194 creater: scope.Creater, 195 defaulter: scope.Defaulter, 196 typer: scope.Typer, 197 unsafeConvertor: scope.UnsafeConvertor, 198 kind: scope.Kind, 199 resource: scope.Resource, 200 subresource: scope.Subresource, 201 dryRun: dryrun.IsDryRun(options.DryRun), 202 203 objectInterfaces: scope, 204 205 hubGroupVersion: scope.HubGroupVersion, 206 207 createValidation: withAuthorization(rest.AdmissionToValidateObjectFunc(admit, staticCreateAttributes, scope), scope.Authorizer, createAuthorizerAttributes), 208 updateValidation: rest.AdmissionToValidateObjectUpdateFunc(admit, staticUpdateAttributes, scope), 209 admissionCheck: mutatingAdmission, 210 211 codec: codec, 212 213 options: options, 214 215 restPatcher: r, 216 name: name, 217 patchType: patchType, 218 patchBytes: patchBytes, 219 userAgent: req.UserAgent(), 220 221 trace: trace, 222 } 223 224 result, wasCreated, err := p.patchResource(ctx, scope) 225 if err != nil { 226 scope.err(err, w, req) 227 return 228 } 229 trace.Step("Object stored in database") 230 231 if err := setObjectSelfLink(ctx, result, req, scope.Namer); err != nil { 232 scope.err(err, w, req) 233 return 234 } 235 trace.Step("Self-link added") 236 237 status := http.StatusOK 238 if wasCreated { 239 status = http.StatusCreated 240 } 241 transformResponseObject(ctx, scope, trace, req, w, status, outputMediaType, result) 242 } 243} 244 245type 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. 252type 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 264 objectInterfaces admission.ObjectInterfaces 265 266 hubGroupVersion schema.GroupVersion 267 268 // Validation functions 269 createValidation rest.ValidateObjectFunc 270 updateValidation rest.ValidateObjectUpdateFunc 271 admissionCheck admission.MutationInterface 272 273 codec runtime.Codec 274 275 options *metav1.PatchOptions 276 277 // Operation information 278 restPatcher rest.Patcher 279 name string 280 patchType types.PatchType 281 patchBytes []byte 282 userAgent string 283 284 trace *utiltrace.Trace 285 286 // Set at invocation-time (by applyPatch) and immutable thereafter 287 namespace string 288 updatedObjectInfo rest.UpdatedObjectInfo 289 mechanism patchMechanism 290 forceAllowCreate bool 291} 292 293type patchMechanism interface { 294 applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) 295 createNewObject() (runtime.Object, error) 296} 297 298type jsonPatcher struct { 299 *patcher 300 301 fieldManager *fieldmanager.FieldManager 302} 303 304func (p *jsonPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) { 305 // Encode will convert & return a versioned object in JSON. 306 currentObjJS, err := runtime.Encode(p.codec, currentObject) 307 if err != nil { 308 return nil, err 309 } 310 311 // Apply the patch. 312 patchedObjJS, err := p.applyJSPatch(currentObjJS) 313 if err != nil { 314 return nil, err 315 } 316 317 // Construct the resulting typed, unversioned object. 318 objToUpdate := p.restPatcher.New() 319 if err := runtime.DecodeInto(p.codec, patchedObjJS, objToUpdate); err != nil { 320 return nil, errors.NewInvalid(schema.GroupKind{}, "", field.ErrorList{ 321 field.Invalid(field.NewPath("patch"), string(patchedObjJS), err.Error()), 322 }) 323 } 324 325 if p.fieldManager != nil { 326 objToUpdate = p.fieldManager.UpdateNoErrors(currentObject, objToUpdate, managerOrUserAgent(p.options.FieldManager, p.userAgent)) 327 } 328 return objToUpdate, nil 329} 330 331func (p *jsonPatcher) createNewObject() (runtime.Object, error) { 332 return nil, errors.NewNotFound(p.resource.GroupResource(), p.name) 333} 334 335// applyJSPatch applies the patch. Input and output objects must both have 336// the external version, since that is what the patch must have been constructed against. 337func (p *jsonPatcher) applyJSPatch(versionedJS []byte) (patchedJS []byte, retErr error) { 338 switch p.patchType { 339 case types.JSONPatchType: 340 // sanity check potentially abusive patches 341 // TODO(liggitt): drop this once golang json parser limits stack depth (https://github.com/golang/go/issues/31789) 342 if len(p.patchBytes) > 1024*1024 { 343 v := []interface{}{} 344 if err := json.Unmarshal(p.patchBytes, &v); err != nil { 345 return nil, errors.NewBadRequest(fmt.Sprintf("error decoding patch: %v", err)) 346 } 347 } 348 349 patchObj, err := jsonpatch.DecodePatch(p.patchBytes) 350 if err != nil { 351 return nil, errors.NewBadRequest(err.Error()) 352 } 353 if len(patchObj) > maxJSONPatchOperations { 354 return nil, errors.NewRequestEntityTooLargeError( 355 fmt.Sprintf("The allowed maximum operations in a JSON patch is %d, got %d", 356 maxJSONPatchOperations, len(patchObj))) 357 } 358 patchedJS, err := patchObj.Apply(versionedJS) 359 if err != nil { 360 return nil, errors.NewGenericServerResponse(http.StatusUnprocessableEntity, "", schema.GroupResource{}, "", err.Error(), 0, false) 361 } 362 return patchedJS, nil 363 case types.MergePatchType: 364 // sanity check potentially abusive patches 365 // TODO(liggitt): drop this once golang json parser limits stack depth (https://github.com/golang/go/issues/31789) 366 if len(p.patchBytes) > 1024*1024 { 367 v := map[string]interface{}{} 368 if err := json.Unmarshal(p.patchBytes, &v); err != nil { 369 return nil, errors.NewBadRequest(fmt.Sprintf("error decoding patch: %v", err)) 370 } 371 } 372 373 return jsonpatch.MergePatch(versionedJS, p.patchBytes) 374 default: 375 // only here as a safety net - go-restful filters content-type 376 return nil, fmt.Errorf("unknown Content-Type header for patch: %v", p.patchType) 377 } 378} 379 380type smpPatcher struct { 381 *patcher 382 383 // Schema 384 schemaReferenceObj runtime.Object 385 fieldManager *fieldmanager.FieldManager 386} 387 388func (p *smpPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) { 389 // Since the patch is applied on versioned objects, we need to convert the 390 // current object to versioned representation first. 391 currentVersionedObject, err := p.unsafeConvertor.ConvertToVersion(currentObject, p.kind.GroupVersion()) 392 if err != nil { 393 return nil, err 394 } 395 versionedObjToUpdate, err := p.creater.New(p.kind) 396 if err != nil { 397 return nil, err 398 } 399 if err := strategicPatchObject(p.defaulter, currentVersionedObject, p.patchBytes, versionedObjToUpdate, p.schemaReferenceObj); err != nil { 400 return nil, err 401 } 402 // Convert the object back to the hub version 403 newObj, err := p.unsafeConvertor.ConvertToVersion(versionedObjToUpdate, p.hubGroupVersion) 404 if err != nil { 405 return nil, err 406 } 407 408 if p.fieldManager != nil { 409 newObj = p.fieldManager.UpdateNoErrors(currentObject, newObj, managerOrUserAgent(p.options.FieldManager, p.userAgent)) 410 } 411 return newObj, nil 412} 413 414func (p *smpPatcher) createNewObject() (runtime.Object, error) { 415 return nil, errors.NewNotFound(p.resource.GroupResource(), p.name) 416} 417 418type applyPatcher struct { 419 patch []byte 420 options *metav1.PatchOptions 421 creater runtime.ObjectCreater 422 kind schema.GroupVersionKind 423 fieldManager *fieldmanager.FieldManager 424 userAgent string 425} 426 427func (p *applyPatcher) applyPatchToCurrentObject(obj runtime.Object) (runtime.Object, error) { 428 force := false 429 if p.options.Force != nil { 430 force = *p.options.Force 431 } 432 if p.fieldManager == nil { 433 panic("FieldManager must be installed to run apply") 434 } 435 436 patchObj := &unstructured.Unstructured{Object: map[string]interface{}{}} 437 if err := yaml.Unmarshal(p.patch, &patchObj.Object); err != nil { 438 return nil, errors.NewBadRequest(fmt.Sprintf("error decoding YAML: %v", err)) 439 } 440 441 return p.fieldManager.Apply(obj, patchObj, p.options.FieldManager, force) 442} 443 444func (p *applyPatcher) createNewObject() (runtime.Object, error) { 445 obj, err := p.creater.New(p.kind) 446 if err != nil { 447 return nil, fmt.Errorf("failed to create new object: %v", err) 448 } 449 return p.applyPatchToCurrentObject(obj) 450} 451 452// strategicPatchObject applies a strategic merge patch of <patchBytes> to 453// <originalObject> and stores the result in <objToUpdate>. 454// It additionally returns the map[string]interface{} representation of the 455// <originalObject> and <patchBytes>. 456// NOTE: Both <originalObject> and <objToUpdate> are supposed to be versioned. 457func strategicPatchObject( 458 defaulter runtime.ObjectDefaulter, 459 originalObject runtime.Object, 460 patchBytes []byte, 461 objToUpdate runtime.Object, 462 schemaReferenceObj runtime.Object, 463) error { 464 originalObjMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(originalObject) 465 if err != nil { 466 return err 467 } 468 469 patchMap := make(map[string]interface{}) 470 if err := json.Unmarshal(patchBytes, &patchMap); err != nil { 471 return errors.NewBadRequest(err.Error()) 472 } 473 474 if err := applyPatchToObject(defaulter, originalObjMap, patchMap, objToUpdate, schemaReferenceObj); err != nil { 475 return err 476 } 477 return nil 478} 479 480// applyPatch is called every time GuaranteedUpdate asks for the updated object, 481// and is given the currently persisted object as input. 482// TODO: rename this function because the name implies it is related to applyPatcher 483func (p *patcher) applyPatch(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) { 484 // Make sure we actually have a persisted currentObject 485 p.trace.Step("About to apply patch") 486 currentObjectHasUID, err := hasUID(currentObject) 487 if err != nil { 488 return nil, err 489 } else if !currentObjectHasUID { 490 objToUpdate, patchErr = p.mechanism.createNewObject() 491 } else { 492 objToUpdate, patchErr = p.mechanism.applyPatchToCurrentObject(currentObject) 493 } 494 495 if patchErr != nil { 496 return nil, patchErr 497 } 498 499 objToUpdateHasUID, err := hasUID(objToUpdate) 500 if err != nil { 501 return nil, err 502 } 503 if objToUpdateHasUID && !currentObjectHasUID { 504 accessor, err := meta.Accessor(objToUpdate) 505 if err != nil { 506 return nil, err 507 } 508 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())) 509 } 510 511 if err := checkName(objToUpdate, p.name, p.namespace, p.namer); err != nil { 512 return nil, err 513 } 514 return objToUpdate, nil 515} 516 517func (p *patcher) admissionAttributes(ctx context.Context, updatedObject runtime.Object, currentObject runtime.Object, operation admission.Operation, operationOptions runtime.Object) admission.Attributes { 518 userInfo, _ := request.UserFrom(ctx) 519 return admission.NewAttributesRecord(updatedObject, currentObject, p.kind, p.namespace, p.name, p.resource, p.subresource, operation, operationOptions, p.dryRun, userInfo) 520} 521 522// applyAdmission is called every time GuaranteedUpdate asks for the updated object, 523// and is given the currently persisted object and the patched object as input. 524// TODO: rename this function because the name implies it is related to applyPatcher 525func (p *patcher) applyAdmission(ctx context.Context, patchedObject runtime.Object, currentObject runtime.Object) (runtime.Object, error) { 526 p.trace.Step("About to check admission control") 527 var operation admission.Operation 528 var options runtime.Object 529 if hasUID, err := hasUID(currentObject); err != nil { 530 return nil, err 531 } else if !hasUID { 532 operation = admission.Create 533 currentObject = nil 534 options = patchToCreateOptions(p.options) 535 } else { 536 operation = admission.Update 537 options = patchToUpdateOptions(p.options) 538 } 539 if p.admissionCheck != nil && p.admissionCheck.Handles(operation) { 540 attributes := p.admissionAttributes(ctx, patchedObject, currentObject, operation, options) 541 return patchedObject, p.admissionCheck.Admit(ctx, attributes, p.objectInterfaces) 542 } 543 return patchedObject, nil 544} 545 546// patchResource divides PatchResource for easier unit testing 547func (p *patcher) patchResource(ctx context.Context, scope *RequestScope) (runtime.Object, bool, error) { 548 p.namespace = request.NamespaceValue(ctx) 549 switch p.patchType { 550 case types.JSONPatchType, types.MergePatchType: 551 p.mechanism = &jsonPatcher{ 552 patcher: p, 553 fieldManager: scope.FieldManager, 554 } 555 case types.StrategicMergePatchType: 556 schemaReferenceObj, err := p.unsafeConvertor.ConvertToVersion(p.restPatcher.New(), p.kind.GroupVersion()) 557 if err != nil { 558 return nil, false, err 559 } 560 p.mechanism = &smpPatcher{ 561 patcher: p, 562 schemaReferenceObj: schemaReferenceObj, 563 fieldManager: scope.FieldManager, 564 } 565 // this case is unreachable if ServerSideApply is not enabled because we will have already rejected the content type 566 case types.ApplyPatchType: 567 p.mechanism = &applyPatcher{ 568 fieldManager: scope.FieldManager, 569 patch: p.patchBytes, 570 options: p.options, 571 creater: p.creater, 572 kind: p.kind, 573 userAgent: p.userAgent, 574 } 575 p.forceAllowCreate = true 576 default: 577 return nil, false, fmt.Errorf("%v: unimplemented patch type", p.patchType) 578 } 579 dedupOwnerReferencesTransformer := func(_ context.Context, obj, _ runtime.Object) (runtime.Object, error) { 580 // Dedup owner references after mutating admission happens 581 dedupOwnerReferencesAndAddWarning(obj, ctx, true) 582 return obj, nil 583 } 584 585 wasCreated := false 586 p.updatedObjectInfo = rest.DefaultUpdatedObjectInfo(nil, p.applyPatch, p.applyAdmission, dedupOwnerReferencesTransformer) 587 requestFunc := func() (runtime.Object, error) { 588 // Pass in UpdateOptions to override UpdateStrategy.AllowUpdateOnCreate 589 options := patchToUpdateOptions(p.options) 590 updateObject, created, updateErr := p.restPatcher.Update(ctx, p.name, p.updatedObjectInfo, p.createValidation, p.updateValidation, p.forceAllowCreate, options) 591 wasCreated = created 592 return updateObject, updateErr 593 } 594 result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) { 595 result, err := requestFunc() 596 // If the object wasn't committed to storage because it's serialized size was too large, 597 // it is safe to remove managedFields (which can be large) and try again. 598 if isTooLargeError(err) && p.patchType != types.ApplyPatchType { 599 if _, accessorErr := meta.Accessor(p.restPatcher.New()); accessorErr == nil { 600 p.updatedObjectInfo = rest.DefaultUpdatedObjectInfo(nil, 601 p.applyPatch, 602 p.applyAdmission, 603 dedupOwnerReferencesTransformer, 604 func(_ context.Context, obj, _ runtime.Object) (runtime.Object, error) { 605 accessor, _ := meta.Accessor(obj) 606 accessor.SetManagedFields(nil) 607 return obj, nil 608 }) 609 result, err = requestFunc() 610 } 611 } 612 return result, err 613 }) 614 return result, wasCreated, err 615} 616 617// applyPatchToObject applies a strategic merge patch of <patchMap> to 618// <originalMap> and stores the result in <objToUpdate>. 619// NOTE: <objToUpdate> must be a versioned object. 620func applyPatchToObject( 621 defaulter runtime.ObjectDefaulter, 622 originalMap map[string]interface{}, 623 patchMap map[string]interface{}, 624 objToUpdate runtime.Object, 625 schemaReferenceObj runtime.Object, 626) error { 627 patchedObjMap, err := strategicpatch.StrategicMergeMapPatch(originalMap, patchMap, schemaReferenceObj) 628 if err != nil { 629 return interpretStrategicMergePatchError(err) 630 } 631 632 // Rather than serialize the patched map to JSON, then decode it to an object, we go directly from a map to an object 633 if err := runtime.DefaultUnstructuredConverter.FromUnstructured(patchedObjMap, objToUpdate); err != nil { 634 return errors.NewInvalid(schema.GroupKind{}, "", field.ErrorList{ 635 field.Invalid(field.NewPath("patch"), fmt.Sprintf("%+v", patchMap), err.Error()), 636 }) 637 } 638 // Decoding from JSON to a versioned object would apply defaults, so we do the same here 639 defaulter.Default(objToUpdate) 640 641 return nil 642} 643 644// interpretStrategicMergePatchError interprets the error type and returns an error with appropriate HTTP code. 645func interpretStrategicMergePatchError(err error) error { 646 switch err { 647 case mergepatch.ErrBadJSONDoc, mergepatch.ErrBadPatchFormatForPrimitiveList, mergepatch.ErrBadPatchFormatForRetainKeys, mergepatch.ErrBadPatchFormatForSetElementOrderList, mergepatch.ErrUnsupportedStrategicMergePatchFormat: 648 return errors.NewBadRequest(err.Error()) 649 case mergepatch.ErrNoListOfLists, mergepatch.ErrPatchContentNotMatchRetainKeys: 650 return errors.NewGenericServerResponse(http.StatusUnprocessableEntity, "", schema.GroupResource{}, "", err.Error(), 0, false) 651 default: 652 return err 653 } 654} 655 656// patchToUpdateOptions creates an UpdateOptions with the same field values as the provided PatchOptions. 657func patchToUpdateOptions(po *metav1.PatchOptions) *metav1.UpdateOptions { 658 if po == nil { 659 return nil 660 } 661 uo := &metav1.UpdateOptions{ 662 DryRun: po.DryRun, 663 FieldManager: po.FieldManager, 664 } 665 uo.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("UpdateOptions")) 666 return uo 667} 668 669// patchToCreateOptions creates an CreateOptions with the same field values as the provided PatchOptions. 670func patchToCreateOptions(po *metav1.PatchOptions) *metav1.CreateOptions { 671 if po == nil { 672 return nil 673 } 674 co := &metav1.CreateOptions{ 675 DryRun: po.DryRun, 676 FieldManager: po.FieldManager, 677 } 678 co.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("CreateOptions")) 679 return co 680} 681