func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, namespace, name string) ([]byte, error) { // Serialize the current configuration of the object from the server. current, err := runtime.Encode(p.encoder, obj) if err != nil { return nil, cmdutil.AddSourceToErr(fmt.Sprintf("serializing current configuration from:\n%v\nfor:", obj), source, err) } // Retrieve the original configuration of the object from the annotation. original, err := kubectl.GetOriginalConfiguration(p.mapping, obj) if err != nil { return nil, cmdutil.AddSourceToErr(fmt.Sprintf("retrieving original configuration from:\n%v\nfor:", obj), source, err) } // Create the versioned struct from the original from the server for // strategic patch. // TODO: Move all structs in apply to use raw data. Can be done once // builder has a RawResult method which delivers raw data instead of // internal objects. versionedObject, _, err := p.decoder.Decode(current, nil, nil) if err != nil { return nil, cmdutil.AddSourceToErr(fmt.Sprintf("converting encoded server-side object back to versioned struct:\n%v\nfor:", obj), source, err) } // Compute a three way strategic merge patch to send to server. patch, err := strategicpatch.CreateThreeWayMergePatch(original, modified, current, versionedObject, p.overwrite, strategicpatch.SMPatchVersion_1_5) // If creating a patch fails, retrying with SMPatchVersion_1_0 is not helpful. So we return the error. if err != nil { format := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:" return nil, cmdutil.AddSourceToErr(fmt.Sprintf(format, original, modified, current), source, err) } _, err = p.helper.Patch(namespace, name, api.StrategicMergePatchType, patch) if errors.IsInternalError(err) { // Retry SMPatchVersion_1_0 when applying the SMPatchVersion_1_5 patch returns an Internal Error (500). // Because the failure may be due to the server not supporting the SMPatchVersion_1_5 patch. patch, err = strategicpatch.CreateThreeWayMergePatch(original, modified, current, versionedObject, p.overwrite, strategicpatch.SMPatchVersion_1_0) if err != nil { format := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:" return nil, cmdutil.AddSourceToErr(fmt.Sprintf(format, original, modified, current), source, err) } _, err = p.helper.Patch(namespace, name, api.StrategicMergePatchType, patch) } return patch, err }
// WithExponentialBackoff will retry webhookFn() up to 5 times with exponentially increasing backoff when // it returns an error for which apierrors.SuggestsClientDelay() or apierrors.IsInternalError() returns true. func WithExponentialBackoff(initialBackoff time.Duration, webhookFn func() error) error { backoff := wait.Backoff{ Duration: initialBackoff, Factor: 1.5, Jitter: 0.2, Steps: 5, } var err error wait.ExponentialBackoff(backoff, func() (bool, error) { err = webhookFn() if _, shouldRetry := apierrors.SuggestsClientDelay(err); shouldRetry { return false, nil } if apierrors.IsInternalError(err) { return false, nil } if err != nil { return false, err } return true, nil }) return err }
func visitToPatch(originalObj runtime.Object, updates *resource.Info, f cmdutil.Factory, mapper meta.RESTMapper, resourceMapper *resource.Mapper, encoder runtime.Encoder, out, errOut io.Writer, defaultVersion unversioned.GroupVersion, results *editResults, file string) error { patchVisitor := resource.NewFlattenListVisitor(updates, resourceMapper) err := patchVisitor.Visit(func(info *resource.Info, incomingErr error) error { currOriginalObj := originalObj // if we're editing a list, then navigate the list to find the item that we're currently trying to edit if meta.IsListType(originalObj) { currOriginalObj = nil editObjUID, err := meta.NewAccessor().UID(info.Object) if err != nil { return err } listItems, err := meta.ExtractList(originalObj) if err != nil { return err } // iterate through the list to find the item with the matching UID for i := range listItems { originalObjUID, err := meta.NewAccessor().UID(listItems[i]) if err != nil { return err } if editObjUID == originalObjUID { currOriginalObj = listItems[i] break } } if currOriginalObj == nil { return fmt.Errorf("no original object found for %#v", info.Object) } } originalSerialization, err := runtime.Encode(encoder, currOriginalObj) if err != nil { return err } editedSerialization, err := runtime.Encode(encoder, info.Object) if err != nil { return err } // compute the patch on a per-item basis // use strategic merge to create a patch originalJS, err := yaml.ToJSON(originalSerialization) if err != nil { return err } editedJS, err := yaml.ToJSON(editedSerialization) if err != nil { return err } if reflect.DeepEqual(originalJS, editedJS) { // no edit, so just skip it. cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, false, "skipped") return nil } preconditions := []strategicpatch.PreconditionFunc{strategicpatch.RequireKeyUnchanged("apiVersion"), strategicpatch.RequireKeyUnchanged("kind"), strategicpatch.RequireMetadataKeyUnchanged("name")} patch, err := strategicpatch.CreateTwoWayMergePatch(originalJS, editedJS, currOriginalObj, strategicpatch.SMPatchVersion_1_5, preconditions...) // If creating a patch fails, retrying with SMPatchVersion_1_0 is not helpful. So we return the error. if err != nil { glog.V(4).Infof("Unable to calculate diff, no merge is possible: %v", err) if strategicpatch.IsPreconditionFailed(err) { return fmt.Errorf("%s", "At least one of apiVersion, kind and name was changed") } return err } results.version = defaultVersion patched, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch) if err != nil { // Retry SMPatchVersion_1_0 when applying the SMPatchVersion_1_5 patch returns an Internal Error (500). // Because the failure may be due to the server not supporting the SMPatchVersion_1_5 patch. if errors.IsInternalError(err) { patch, err = strategicpatch.CreateTwoWayMergePatch(originalJS, editedJS, currOriginalObj, strategicpatch.SMPatchVersion_1_0) if err != nil { glog.V(4).Infof("Unable to calculate diff, no merge is possible: %v", err) return err } patched, err = resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch) } if err != nil { fmt.Fprintln(out, results.addError(err, info)) return nil } } info.Refresh(patched, true) cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, false, "edited") return nil }) return err }