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