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-12-12 01:27:03 +00:00
metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
2019-01-12 04:58:27 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2020-03-26 21:07:15 +00:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2019-01-12 04:58:27 +00:00
"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"
2020-03-26 21:07:15 +00:00
"sigs.k8s.io/yaml"
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-12-12 01:27:03 +00:00
trace := utiltrace . New ( "Patch" , utiltrace . Field { Key : "url" , Value : req . URL . Path } , utiltrace . Field { Key : "user-agent" , Value : & lazyTruncatedUserAgent { req } } , utiltrace . Field { Key : "client" , Value : & lazyClientIP { req } } )
2019-01-12 04:58:27 +00:00
defer trace . LogIfLong ( 500 * time . Millisecond )
if isDryRun ( req . URL ) && ! utilfeature . DefaultFeatureGate . Enabled ( features . DryRun ) {
2020-03-26 21:07:15 +00:00
scope . err ( errors . NewBadRequest ( "the dryRun feature is disabled" ) , w , req )
2019-01-12 04:58:27 +00:00
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-12-12 01:27:03 +00:00
if err := metainternalversionscheme . ParameterCodec . DecodeParameters ( req . URL . Query ( ) , scope . MetaGroupVersion , options ) ; err != nil {
2019-01-12 04:58:27 +00:00
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 { } { }
2019-11-14 18:56:24 +00:00
if err := json . Unmarshal ( p . patchBytes , & v ) ; err != nil {
2019-10-16 05:42:28 +00:00
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 { } { }
2019-11-14 18:56:24 +00:00
if err := json . Unmarshal ( p . patchBytes , & v ) ; err != nil {
2019-10-16 05:42:28 +00:00
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" )
}
2020-03-26 21:07:15 +00:00
patchObj := & unstructured . Unstructured { Object : map [ string ] interface { } { } }
if err := yaml . Unmarshal ( p . patch , & patchObj . Object ) ; err != nil {
return nil , errors . NewBadRequest ( fmt . Sprintf ( "error decoding YAML: %v" , err ) )
}
return p . fieldManager . Apply ( obj , patchObj , p . options . FieldManager , force )
2019-08-30 18:33:25 +00:00
}
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 )
2020-03-26 21:07:15 +00:00
requestFunc := 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
2020-03-26 21:07:15 +00:00
}
result , err := finishRequest ( p . timeout , func ( ) ( runtime . Object , error ) {
result , err := requestFunc ( )
// If the object wasn't committed to storage because it's serialized size was too large,
// it is safe to remove managedFields (which can be large) and try again.
if isTooLargeError ( err ) && p . patchType != types . ApplyPatchType {
if _ , accessorErr := meta . Accessor ( p . restPatcher . New ( ) ) ; accessorErr == nil {
p . updatedObjectInfo = rest . DefaultUpdatedObjectInfo ( nil , p . applyPatch , p . applyAdmission , func ( _ context . Context , obj , _ runtime . Object ) ( runtime . Object , error ) {
accessor , _ := meta . Accessor ( obj )
accessor . SetManagedFields ( nil )
return obj , nil
} )
result , err = requestFunc ( )
}
}
return result , err
2019-01-12 04:58:27 +00:00
} )
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
}