k3s/vendor/k8s.io/apiserver/pkg/endpoints/handlers/patch.go

650 lines
22 KiB
Go
Raw Normal View History

2019-01-12 04:58:27 +00:00
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package handlers
import (
"context"
"fmt"
"net/http"
"strings"
"time"
2019-08-30 18:33:25 +00:00
jsonpatch "github.com/evanphx/json-patch"
2019-01-12 04:58:27 +00:00
"k8s.io/apimachinery/pkg/api/errors"
2019-04-07 17:07:55 +00:00
"k8s.io/apimachinery/pkg/api/meta"
2019-01-12 04:58:27 +00:00
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/mergepatch"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch"
2019-09-27 21:51:53 +00:00
"k8s.io/apimachinery/pkg/util/validation/field"
2019-01-12 04:58:27 +00:00
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/audit"
2019-04-07 17:07:55 +00:00
"k8s.io/apiserver/pkg/authorization/authorizer"
2019-08-30 18:33:25 +00:00
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
2019-01-12 04:58:27 +00:00
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/util/dryrun"
utilfeature "k8s.io/apiserver/pkg/util/feature"
2019-04-07 17:07:55 +00:00
utiltrace "k8s.io/utils/trace"
2019-01-12 04:58:27 +00:00
)
2019-03-04 01:22:32 +00:00
const (
// maximum number of operations a single json patch may contain.
maxJSONPatchOperations = 10000
)
2019-01-12 04:58:27 +00:00
// PatchResource returns a function that will handle a resource patch.
2019-08-30 18:33:25 +00:00
func PatchResource(r rest.Patcher, scope *RequestScope, admit admission.Interface, patchTypes []string) http.HandlerFunc {
2019-01-12 04:58:27 +00:00
return func(w http.ResponseWriter, req *http.Request) {
// For performance tracking purposes.
2019-09-27 21:51:53 +00:00
trace := utiltrace.New("Patch", utiltrace.Field{"url", req.URL.Path})
2019-01-12 04:58:27 +00:00
defer trace.LogIfLong(500 * time.Millisecond)
if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {
scope.err(errors.NewBadRequest("the dryRun alpha feature is disabled"), w, req)
return
}
// Do this first, otherwise name extraction can fail for unrecognized content types
// TODO: handle this in negotiation
contentType := req.Header.Get("Content-Type")
// Remove "; charset=" if included in header.
if idx := strings.Index(contentType, ";"); idx > 0 {
contentType = contentType[:idx]
}
patchType := types.PatchType(contentType)
// Ensure the patchType is one we support
if !sets.NewString(patchTypes...).Has(contentType) {
scope.err(negotiation.NewUnsupportedMediaTypeError(patchTypes), w, req)
return
}
// TODO: we either want to remove timeout or document it (if we
// document, move timeout out of this function and declare it in
// api_installer)
timeout := parseTimeout(req.URL.Query().Get("timeout"))
namespace, name, err := scope.Namer.Name(req)
if err != nil {
scope.err(err, w, req)
return
}
2019-09-27 21:51:53 +00:00
ctx, cancel := context.WithTimeout(req.Context(), timeout)
defer cancel()
2019-01-12 04:58:27 +00:00
ctx = request.WithNamespace(ctx, namespace)
2019-08-30 18:33:25 +00:00
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
2019-01-12 04:58:27 +00:00
if err != nil {
scope.err(err, w, req)
return
}
2019-04-07 17:07:55 +00:00
patchBytes, err := limitedReadBody(req, scope.MaxRequestBodyBytes)
if err != nil {
scope.err(err, w, req)
return
}
options := &metav1.PatchOptions{}
2019-01-12 04:58:27 +00:00
if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, options); err != nil {
err = errors.NewBadRequest(err.Error())
scope.err(err, w, req)
return
}
2019-04-07 17:07:55 +00:00
if errs := validation.ValidatePatchOptions(options, patchType); len(errs) > 0 {
err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "PatchOptions"}, "", errs)
2019-01-12 04:58:27 +00:00
scope.err(err, w, req)
return
}
2019-08-30 18:33:25 +00:00
options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("PatchOptions"))
2019-01-12 04:58:27 +00:00
ae := request.AuditEventFrom(ctx)
admit = admission.WithAudit(admit, ae)
2019-04-07 17:07:55 +00:00
audit.LogRequestPatch(ae, patchBytes)
2019-01-12 04:58:27 +00:00
trace.Step("Recorded the audit event")
2019-04-07 17:07:55 +00:00
baseContentType := runtime.ContentTypeJSON
2019-08-30 18:33:25 +00:00
if patchType == types.ApplyPatchType {
baseContentType = runtime.ContentTypeYAML
}
2019-04-07 17:07:55 +00:00
s, ok := runtime.SerializerInfoForMediaType(scope.Serializer.SupportedMediaTypes(), baseContentType)
2019-01-12 04:58:27 +00:00
if !ok {
2019-04-07 17:07:55 +00:00
scope.err(fmt.Errorf("no serializer defined for %v", baseContentType), w, req)
2019-01-12 04:58:27 +00:00
return
}
gv := scope.Kind.GroupVersion()
codec := runtime.NewCodec(
scope.Serializer.EncoderForVersion(s.Serializer, gv),
scope.Serializer.DecoderToVersion(s.Serializer, scope.HubGroupVersion),
)
userInfo, _ := request.UserFrom(ctx)
2019-04-07 17:07:55 +00:00
staticCreateAttributes := admission.NewAttributesRecord(
nil,
nil,
scope.Kind,
namespace,
name,
scope.Resource,
scope.Subresource,
admission.Create,
2019-08-30 18:33:25 +00:00
patchToCreateOptions(options),
2019-04-07 17:07:55 +00:00
dryrun.IsDryRun(options.DryRun),
userInfo)
staticUpdateAttributes := admission.NewAttributesRecord(
2019-01-12 04:58:27 +00:00
nil,
nil,
scope.Kind,
namespace,
name,
scope.Resource,
scope.Subresource,
admission.Update,
2019-08-30 18:33:25 +00:00
patchToUpdateOptions(options),
2019-01-12 04:58:27 +00:00
dryrun.IsDryRun(options.DryRun),
userInfo,
)
2019-04-07 17:07:55 +00:00
mutatingAdmission, _ := admit.(admission.MutationInterface)
createAuthorizerAttributes := authorizer.AttributesRecord{
User: userInfo,
ResourceRequest: true,
Path: req.URL.Path,
Verb: "create",
APIGroup: scope.Resource.Group,
APIVersion: scope.Resource.Version,
Resource: scope.Resource.Resource,
Subresource: scope.Subresource,
Namespace: namespace,
Name: name,
2019-01-12 04:58:27 +00:00
}
p := patcher{
namer: scope.Namer,
creater: scope.Creater,
defaulter: scope.Defaulter,
2019-04-07 17:07:55 +00:00
typer: scope.Typer,
2019-01-12 04:58:27 +00:00
unsafeConvertor: scope.UnsafeConvertor,
kind: scope.Kind,
resource: scope.Resource,
2019-04-07 17:07:55 +00:00
subresource: scope.Subresource,
dryRun: dryrun.IsDryRun(options.DryRun),
2019-08-30 18:33:25 +00:00
objectInterfaces: scope,
2019-01-12 04:58:27 +00:00
hubGroupVersion: scope.HubGroupVersion,
2019-08-30 18:33:25 +00:00
createValidation: withAuthorization(rest.AdmissionToValidateObjectFunc(admit, staticCreateAttributes, scope), scope.Authorizer, createAuthorizerAttributes),
updateValidation: rest.AdmissionToValidateObjectUpdateFunc(admit, staticUpdateAttributes, scope),
2019-04-07 17:07:55 +00:00
admissionCheck: mutatingAdmission,
2019-01-12 04:58:27 +00:00
codec: codec,
timeout: timeout,
options: options,
restPatcher: r,
name: name,
patchType: patchType,
2019-04-07 17:07:55 +00:00
patchBytes: patchBytes,
userAgent: req.UserAgent(),
2019-01-12 04:58:27 +00:00
trace: trace,
}
2019-04-07 17:07:55 +00:00
result, wasCreated, err := p.patchResource(ctx, scope)
2019-01-12 04:58:27 +00:00
if err != nil {
scope.err(err, w, req)
return
}
trace.Step("Object stored in database")
2019-09-27 21:51:53 +00:00
if err := setObjectSelfLink(ctx, result, req, scope.Namer); err != nil {
2019-01-12 04:58:27 +00:00
scope.err(err, w, req)
return
}
trace.Step("Self-link added")
2019-04-07 17:07:55 +00:00
status := http.StatusOK
if wasCreated {
status = http.StatusCreated
}
2019-08-30 18:33:25 +00:00
transformResponseObject(ctx, scope, trace, req, w, status, outputMediaType, result)
2019-01-12 04:58:27 +00:00
}
}
2019-09-27 21:51:53 +00:00
type mutateObjectUpdateFunc func(ctx context.Context, obj, old runtime.Object) error
2019-01-12 04:58:27 +00:00
// patcher breaks the process of patch application and retries into smaller
// pieces of functionality.
// TODO: Use builder pattern to construct this object?
// TODO: As part of that effort, some aspects of PatchResource above could be
// moved into this type.
type patcher struct {
// Pieces of RequestScope
namer ScopeNamer
creater runtime.ObjectCreater
defaulter runtime.ObjectDefaulter
2019-04-07 17:07:55 +00:00
typer runtime.ObjectTyper
2019-01-12 04:58:27 +00:00
unsafeConvertor runtime.ObjectConvertor
resource schema.GroupVersionResource
kind schema.GroupVersionKind
2019-04-07 17:07:55 +00:00
subresource string
dryRun bool
objectInterfaces admission.ObjectInterfaces
2019-01-12 04:58:27 +00:00
hubGroupVersion schema.GroupVersion
// Validation functions
createValidation rest.ValidateObjectFunc
updateValidation rest.ValidateObjectUpdateFunc
2019-04-07 17:07:55 +00:00
admissionCheck admission.MutationInterface
2019-01-12 04:58:27 +00:00
codec runtime.Codec
timeout time.Duration
2019-04-07 17:07:55 +00:00
options *metav1.PatchOptions
2019-01-12 04:58:27 +00:00
// Operation information
restPatcher rest.Patcher
name string
patchType types.PatchType
2019-04-07 17:07:55 +00:00
patchBytes []byte
userAgent string
2019-01-12 04:58:27 +00:00
trace *utiltrace.Trace
// Set at invocation-time (by applyPatch) and immutable thereafter
namespace string
updatedObjectInfo rest.UpdatedObjectInfo
mechanism patchMechanism
2019-04-07 17:07:55 +00:00
forceAllowCreate bool
2019-01-12 04:58:27 +00:00
}
type patchMechanism interface {
applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error)
2019-04-07 17:07:55 +00:00
createNewObject() (runtime.Object, error)
2019-01-12 04:58:27 +00:00
}
type jsonPatcher struct {
*patcher
2019-08-30 18:33:25 +00:00
fieldManager *fieldmanager.FieldManager
2019-01-12 04:58:27 +00:00
}
func (p *jsonPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) {
// Encode will convert & return a versioned object in JSON.
currentObjJS, err := runtime.Encode(p.codec, currentObject)
if err != nil {
return nil, err
}
// Apply the patch.
patchedObjJS, err := p.applyJSPatch(currentObjJS)
if err != nil {
return nil, err
}
// Construct the resulting typed, unversioned object.
objToUpdate := p.restPatcher.New()
if err := runtime.DecodeInto(p.codec, patchedObjJS, objToUpdate); err != nil {
2019-09-27 21:51:53 +00:00
return nil, errors.NewInvalid(schema.GroupKind{}, "", field.ErrorList{
field.Invalid(field.NewPath("patch"), string(patchedObjJS), err.Error()),
})
2019-01-12 04:58:27 +00:00
}
2019-08-30 18:33:25 +00:00
if p.fieldManager != nil {
if objToUpdate, err = p.fieldManager.Update(currentObject, objToUpdate, managerOrUserAgent(p.options.FieldManager, p.userAgent)); err != nil {
return nil, fmt.Errorf("failed to update object (json PATCH for %v) managed fields: %v", p.kind, err)
}
}
2019-01-12 04:58:27 +00:00
return objToUpdate, nil
}
2019-04-07 17:07:55 +00:00
func (p *jsonPatcher) createNewObject() (runtime.Object, error) {
return nil, errors.NewNotFound(p.resource.GroupResource(), p.name)
}
// applyJSPatch applies the patch. Input and output objects must both have
2019-01-12 04:58:27 +00:00
// the external version, since that is what the patch must have been constructed against.
func (p *jsonPatcher) applyJSPatch(versionedJS []byte) (patchedJS []byte, retErr error) {
switch p.patchType {
case types.JSONPatchType:
2019-10-16 05:42:28 +00:00
// sanity check potentially abusive patches
// TODO(liggitt): drop this once golang json parser limits stack depth (https://github.com/golang/go/issues/31789)
if len(p.patchBytes) > 1024*1024 {
v := []interface{}{}
if err := json.Unmarshal(p.patchBytes, v); err != nil {
return nil, errors.NewBadRequest(fmt.Sprintf("error decoding patch: %v", err))
}
}
2019-04-07 17:07:55 +00:00
patchObj, err := jsonpatch.DecodePatch(p.patchBytes)
2019-01-12 04:58:27 +00:00
if err != nil {
return nil, errors.NewBadRequest(err.Error())
}
2019-03-04 01:22:32 +00:00
if len(patchObj) > maxJSONPatchOperations {
return nil, errors.NewRequestEntityTooLargeError(
fmt.Sprintf("The allowed maximum operations in a JSON patch is %d, got %d",
maxJSONPatchOperations, len(patchObj)))
}
2019-01-12 04:58:27 +00:00
patchedJS, err := patchObj.Apply(versionedJS)
if err != nil {
return nil, errors.NewGenericServerResponse(http.StatusUnprocessableEntity, "", schema.GroupResource{}, "", err.Error(), 0, false)
}
return patchedJS, nil
case types.MergePatchType:
2019-10-16 05:42:28 +00:00
// sanity check potentially abusive patches
// TODO(liggitt): drop this once golang json parser limits stack depth (https://github.com/golang/go/issues/31789)
if len(p.patchBytes) > 1024*1024 {
v := map[string]interface{}{}
if err := json.Unmarshal(p.patchBytes, v); err != nil {
return nil, errors.NewBadRequest(fmt.Sprintf("error decoding patch: %v", err))
}
}
2019-04-07 17:07:55 +00:00
return jsonpatch.MergePatch(versionedJS, p.patchBytes)
2019-01-12 04:58:27 +00:00
default:
// only here as a safety net - go-restful filters content-type
return nil, fmt.Errorf("unknown Content-Type header for patch: %v", p.patchType)
}
}
type smpPatcher struct {
*patcher
// Schema
schemaReferenceObj runtime.Object
2019-08-30 18:33:25 +00:00
fieldManager *fieldmanager.FieldManager
2019-01-12 04:58:27 +00:00
}
func (p *smpPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) {
// Since the patch is applied on versioned objects, we need to convert the
// current object to versioned representation first.
currentVersionedObject, err := p.unsafeConvertor.ConvertToVersion(currentObject, p.kind.GroupVersion())
if err != nil {
return nil, err
}
versionedObjToUpdate, err := p.creater.New(p.kind)
if err != nil {
return nil, err
}
2019-04-07 17:07:55 +00:00
if err := strategicPatchObject(p.defaulter, currentVersionedObject, p.patchBytes, versionedObjToUpdate, p.schemaReferenceObj); err != nil {
2019-01-12 04:58:27 +00:00
return nil, err
}
// Convert the object back to the hub version
2019-04-07 17:07:55 +00:00
newObj, err := p.unsafeConvertor.ConvertToVersion(versionedObjToUpdate, p.hubGroupVersion)
if err != nil {
return nil, err
}
2019-08-30 18:33:25 +00:00
if p.fieldManager != nil {
if newObj, err = p.fieldManager.Update(currentObject, newObj, managerOrUserAgent(p.options.FieldManager, p.userAgent)); err != nil {
return nil, fmt.Errorf("failed to update object (smp PATCH for %v) managed fields: %v", p.kind, err)
}
}
2019-04-07 17:07:55 +00:00
return newObj, nil
2019-01-12 04:58:27 +00:00
}
2019-04-07 17:07:55 +00:00
func (p *smpPatcher) createNewObject() (runtime.Object, error) {
return nil, errors.NewNotFound(p.resource.GroupResource(), p.name)
}
2019-08-30 18:33:25 +00:00
type applyPatcher struct {
patch []byte
options *metav1.PatchOptions
creater runtime.ObjectCreater
kind schema.GroupVersionKind
fieldManager *fieldmanager.FieldManager
}
func (p *applyPatcher) applyPatchToCurrentObject(obj runtime.Object) (runtime.Object, error) {
force := false
if p.options.Force != nil {
force = *p.options.Force
}
if p.fieldManager == nil {
panic("FieldManager must be installed to run apply")
}
return p.fieldManager.Apply(obj, p.patch, p.options.FieldManager, force)
}
func (p *applyPatcher) createNewObject() (runtime.Object, error) {
obj, err := p.creater.New(p.kind)
if err != nil {
2019-09-27 21:51:53 +00:00
return nil, fmt.Errorf("failed to create new object: %v", err)
2019-08-30 18:33:25 +00:00
}
return p.applyPatchToCurrentObject(obj)
}
2019-04-07 17:07:55 +00:00
// strategicPatchObject applies a strategic merge patch of <patchBytes> to
2019-01-12 04:58:27 +00:00
// <originalObject> and stores the result in <objToUpdate>.
// It additionally returns the map[string]interface{} representation of the
2019-04-07 17:07:55 +00:00
// <originalObject> and <patchBytes>.
2019-01-12 04:58:27 +00:00
// NOTE: Both <originalObject> and <objToUpdate> are supposed to be versioned.
func strategicPatchObject(
defaulter runtime.ObjectDefaulter,
originalObject runtime.Object,
2019-04-07 17:07:55 +00:00
patchBytes []byte,
2019-01-12 04:58:27 +00:00
objToUpdate runtime.Object,
schemaReferenceObj runtime.Object,
) error {
originalObjMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(originalObject)
if err != nil {
return err
}
patchMap := make(map[string]interface{})
2019-04-07 17:07:55 +00:00
if err := json.Unmarshal(patchBytes, &patchMap); err != nil {
2019-01-12 04:58:27 +00:00
return errors.NewBadRequest(err.Error())
}
if err := applyPatchToObject(defaulter, originalObjMap, patchMap, objToUpdate, schemaReferenceObj); err != nil {
return err
}
return nil
}
// applyPatch is called every time GuaranteedUpdate asks for the updated object,
// and is given the currently persisted object as input.
2019-04-07 17:07:55 +00:00
// TODO: rename this function because the name implies it is related to applyPatcher
func (p *patcher) applyPatch(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) {
2019-01-12 04:58:27 +00:00
// Make sure we actually have a persisted currentObject
p.trace.Step("About to apply patch")
2019-04-07 17:07:55 +00:00
currentObjectHasUID, err := hasUID(currentObject)
if err != nil {
2019-01-12 04:58:27 +00:00
return nil, err
2019-04-07 17:07:55 +00:00
} else if !currentObjectHasUID {
objToUpdate, patchErr = p.mechanism.createNewObject()
} else {
objToUpdate, patchErr = p.mechanism.applyPatchToCurrentObject(currentObject)
}
if patchErr != nil {
return nil, patchErr
2019-01-12 04:58:27 +00:00
}
2019-04-07 17:07:55 +00:00
objToUpdateHasUID, err := hasUID(objToUpdate)
2019-01-12 04:58:27 +00:00
if err != nil {
return nil, err
}
2019-04-07 17:07:55 +00:00
if objToUpdateHasUID && !currentObjectHasUID {
accessor, err := meta.Accessor(objToUpdate)
if err != nil {
return nil, err
}
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()))
}
2019-01-12 04:58:27 +00:00
if err := checkName(objToUpdate, p.name, p.namespace, p.namer); err != nil {
return nil, err
}
return objToUpdate, nil
}
2019-08-30 18:33:25 +00:00
func (p *patcher) admissionAttributes(ctx context.Context, updatedObject runtime.Object, currentObject runtime.Object, operation admission.Operation, operationOptions runtime.Object) admission.Attributes {
2019-04-07 17:07:55 +00:00
userInfo, _ := request.UserFrom(ctx)
2019-08-30 18:33:25 +00:00
return admission.NewAttributesRecord(updatedObject, currentObject, p.kind, p.namespace, p.name, p.resource, p.subresource, operation, operationOptions, p.dryRun, userInfo)
2019-04-07 17:07:55 +00:00
}
2019-01-12 04:58:27 +00:00
// applyAdmission is called every time GuaranteedUpdate asks for the updated object,
// and is given the currently persisted object and the patched object as input.
2019-04-07 17:07:55 +00:00
// TODO: rename this function because the name implies it is related to applyPatcher
2019-01-12 04:58:27 +00:00
func (p *patcher) applyAdmission(ctx context.Context, patchedObject runtime.Object, currentObject runtime.Object) (runtime.Object, error) {
p.trace.Step("About to check admission control")
2019-04-07 17:07:55 +00:00
var operation admission.Operation
2019-08-30 18:33:25 +00:00
var options runtime.Object
2019-04-07 17:07:55 +00:00
if hasUID, err := hasUID(currentObject); err != nil {
return nil, err
} else if !hasUID {
operation = admission.Create
currentObject = nil
2019-08-30 18:33:25 +00:00
options = patchToCreateOptions(p.options)
2019-04-07 17:07:55 +00:00
} else {
operation = admission.Update
2019-08-30 18:33:25 +00:00
options = patchToUpdateOptions(p.options)
2019-04-07 17:07:55 +00:00
}
if p.admissionCheck != nil && p.admissionCheck.Handles(operation) {
2019-08-30 18:33:25 +00:00
attributes := p.admissionAttributes(ctx, patchedObject, currentObject, operation, options)
2019-09-27 21:51:53 +00:00
return patchedObject, p.admissionCheck.Admit(ctx, attributes, p.objectInterfaces)
2019-04-07 17:07:55 +00:00
}
return patchedObject, nil
2019-01-12 04:58:27 +00:00
}
// patchResource divides PatchResource for easier unit testing
2019-08-30 18:33:25 +00:00
func (p *patcher) patchResource(ctx context.Context, scope *RequestScope) (runtime.Object, bool, error) {
2019-01-12 04:58:27 +00:00
p.namespace = request.NamespaceValue(ctx)
switch p.patchType {
case types.JSONPatchType, types.MergePatchType:
2019-04-07 17:07:55 +00:00
p.mechanism = &jsonPatcher{
patcher: p,
2019-08-30 18:33:25 +00:00
fieldManager: scope.FieldManager,
2019-04-07 17:07:55 +00:00
}
2019-01-12 04:58:27 +00:00
case types.StrategicMergePatchType:
schemaReferenceObj, err := p.unsafeConvertor.ConvertToVersion(p.restPatcher.New(), p.kind.GroupVersion())
if err != nil {
2019-04-07 17:07:55 +00:00
return nil, false, err
}
p.mechanism = &smpPatcher{
patcher: p,
schemaReferenceObj: schemaReferenceObj,
2019-08-30 18:33:25 +00:00
fieldManager: scope.FieldManager,
2019-01-12 04:58:27 +00:00
}
2019-04-07 17:07:55 +00:00
// this case is unreachable if ServerSideApply is not enabled because we will have already rejected the content type
2019-08-30 18:33:25 +00:00
case types.ApplyPatchType:
p.mechanism = &applyPatcher{
fieldManager: scope.FieldManager,
patch: p.patchBytes,
options: p.options,
creater: p.creater,
kind: p.kind,
}
p.forceAllowCreate = true
2019-01-12 04:58:27 +00:00
default:
2019-04-07 17:07:55 +00:00
return nil, false, fmt.Errorf("%v: unimplemented patch type", p.patchType)
2019-01-12 04:58:27 +00:00
}
2019-04-07 17:07:55 +00:00
wasCreated := false
2019-01-12 04:58:27 +00:00
p.updatedObjectInfo = rest.DefaultUpdatedObjectInfo(nil, p.applyPatch, p.applyAdmission)
2019-04-07 17:07:55 +00:00
result, err := finishRequest(p.timeout, func() (runtime.Object, error) {
2019-08-30 18:33:25 +00:00
// Pass in UpdateOptions to override UpdateStrategy.AllowUpdateOnCreate
options := patchToUpdateOptions(p.options)
2019-04-07 17:07:55 +00:00
updateObject, created, updateErr := p.restPatcher.Update(ctx, p.name, p.updatedObjectInfo, p.createValidation, p.updateValidation, p.forceAllowCreate, options)
wasCreated = created
2019-01-12 04:58:27 +00:00
return updateObject, updateErr
})
2019-04-07 17:07:55 +00:00
return result, wasCreated, err
2019-01-12 04:58:27 +00:00
}
// applyPatchToObject applies a strategic merge patch of <patchMap> to
// <originalMap> and stores the result in <objToUpdate>.
// NOTE: <objToUpdate> must be a versioned object.
func applyPatchToObject(
defaulter runtime.ObjectDefaulter,
originalMap map[string]interface{},
patchMap map[string]interface{},
objToUpdate runtime.Object,
schemaReferenceObj runtime.Object,
) error {
patchedObjMap, err := strategicpatch.StrategicMergeMapPatch(originalMap, patchMap, schemaReferenceObj)
if err != nil {
return interpretStrategicMergePatchError(err)
}
// Rather than serialize the patched map to JSON, then decode it to an object, we go directly from a map to an object
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(patchedObjMap, objToUpdate); err != nil {
2019-09-27 21:51:53 +00:00
return errors.NewInvalid(schema.GroupKind{}, "", field.ErrorList{
field.Invalid(field.NewPath("patch"), fmt.Sprintf("%+v", patchMap), err.Error()),
})
2019-01-12 04:58:27 +00:00
}
// Decoding from JSON to a versioned object would apply defaults, so we do the same here
defaulter.Default(objToUpdate)
return nil
}
// interpretStrategicMergePatchError interprets the error type and returns an error with appropriate HTTP code.
func interpretStrategicMergePatchError(err error) error {
switch err {
case mergepatch.ErrBadJSONDoc, mergepatch.ErrBadPatchFormatForPrimitiveList, mergepatch.ErrBadPatchFormatForRetainKeys, mergepatch.ErrBadPatchFormatForSetElementOrderList, mergepatch.ErrUnsupportedStrategicMergePatchFormat:
return errors.NewBadRequest(err.Error())
case mergepatch.ErrNoListOfLists, mergepatch.ErrPatchContentNotMatchRetainKeys:
return errors.NewGenericServerResponse(http.StatusUnprocessableEntity, "", schema.GroupResource{}, "", err.Error(), 0, false)
default:
return err
}
}
2019-04-07 17:07:55 +00:00
2019-08-30 18:33:25 +00:00
// patchToUpdateOptions creates an UpdateOptions with the same field values as the provided PatchOptions.
func patchToUpdateOptions(po *metav1.PatchOptions) *metav1.UpdateOptions {
if po == nil {
return nil
}
uo := &metav1.UpdateOptions{
DryRun: po.DryRun,
FieldManager: po.FieldManager,
}
uo.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("UpdateOptions"))
return uo
}
// patchToCreateOptions creates an CreateOptions with the same field values as the provided PatchOptions.
func patchToCreateOptions(po *metav1.PatchOptions) *metav1.CreateOptions {
if po == nil {
return nil
}
co := &metav1.CreateOptions{
DryRun: po.DryRun,
FieldManager: po.FieldManager,
2019-04-07 17:07:55 +00:00
}
2019-08-30 18:33:25 +00:00
co.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("CreateOptions"))
return co
2019-04-07 17:07:55 +00:00
}