// patchResource divides PatchResource for easier unit testing func patchResource(ctx api.Context, timeout time.Duration, versionedObj runtime.Object, patcher rest.Patcher, name string, patchType api.PatchType, patchJS []byte, namer ScopeNamer, codec runtime.Codec) (runtime.Object, error) { namespace := api.NamespaceValue(ctx) original, err := patcher.Get(ctx, name) if err != nil { return nil, err } originalObjJS, err := codec.Encode(original) if err != nil { return nil, err } originalPatchedObjJS, err := getPatchedJS(patchType, originalObjJS, patchJS, versionedObj) if err != nil { return nil, err } objToUpdate := patcher.New() if err := codec.DecodeInto(originalPatchedObjJS, objToUpdate); err != nil { return nil, err } if err := checkName(objToUpdate, name, namespace, namer); err != nil { return nil, err } return finishRequest(timeout, func() (runtime.Object, error) { // update should never create as previous get would fail updateObject, _, updateErr := patcher.Update(ctx, objToUpdate) for i := 0; i < MaxPatchConflicts && (errors.IsConflict(updateErr)); i++ { // on a conflict, // 1. build a strategic merge patch from originalJS and the patchedJS. Different patch types can // be specified, but a strategic merge patch should be expressive enough handle them. Build the // patch with this type to handle those cases. // 2. build a strategic merge patch from originalJS and the currentJS // 3. ensure no conflicts between the two patches // 4. apply the #1 patch to the currentJS object // 5. retry the update currentObject, err := patcher.Get(ctx, name) if err != nil { return nil, err } currentObjectJS, err := codec.Encode(currentObject) if err != nil { return nil, err } currentPatch, err := strategicpatch.CreateStrategicMergePatch(originalObjJS, currentObjectJS, patcher.New()) if err != nil { return nil, err } originalPatch, err := strategicpatch.CreateStrategicMergePatch(originalObjJS, originalPatchedObjJS, patcher.New()) if err != nil { return nil, err } diff1 := make(map[string]interface{}) if err := json.Unmarshal(originalPatch, &diff1); err != nil { return nil, err } diff2 := make(map[string]interface{}) if err := json.Unmarshal(currentPatch, &diff2); err != nil { return nil, err } hasConflicts, err := strategicpatch.HasConflicts(diff1, diff2) if err != nil { return nil, err } if hasConflicts { return updateObject, updateErr } newlyPatchedObjJS, err := getPatchedJS(api.StrategicMergePatchType, currentObjectJS, originalPatch, versionedObj) if err != nil { return nil, err } if err := codec.DecodeInto(newlyPatchedObjJS, objToUpdate); err != nil { return nil, err } updateObject, _, updateErr = patcher.Update(ctx, objToUpdate) } return updateObject, updateErr }) }
// PatchResource returns a function that will handle a resource patch // TODO: Eventually PatchResource should just use GuaranteedUpdate and this routine should be a bit cleaner func PatchResource(r rest.Patcher, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface, converter runtime.ObjectConvertor) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter // 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.Request.URL.Query().Get("timeout")) namespace, name, err := scope.Namer.Name(req) if err != nil { errorJSON(err, scope.Codec, w) return } obj := r.New() ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) // PATCH requires same permission as UPDATE if admit.Handles(admission.Update) { userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)) if err != nil { errorJSON(err, scope.Codec, w) return } } versionedObj, err := converter.ConvertToVersion(obj, scope.APIVersion) if err != nil { errorJSON(err, scope.Codec, w) return } original, err := r.Get(ctx, name) if err != nil { errorJSON(err, scope.Codec, w) return } originalObjJS, err := scope.Codec.Encode(original) if err != nil { errorJSON(err, scope.Codec, w) return } patchJS, err := readBody(req.Request) if err != nil { errorJSON(err, scope.Codec, w) return } contentType := req.HeaderParameter("Content-Type") patchedObjJS, err := getPatchedJS(contentType, originalObjJS, patchJS, versionedObj) if err != nil { errorJSON(err, scope.Codec, w) return } if err := scope.Codec.DecodeInto(patchedObjJS, obj); err != nil { errorJSON(err, scope.Codec, w) return } if err := checkName(obj, name, namespace, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } result, err := finishRequest(timeout, func() (runtime.Object, error) { // update should never create as previous get would fail obj, _, err := r.Update(ctx, obj) return obj, err }) if err != nil { errorJSON(err, scope.Codec, w) return } if err := setSelfLink(result, req, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } write(http.StatusOK, scope.APIVersion, scope.Codec, result, w, req.Request) } }