Example #1
0
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
}
Example #2
0
// 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
}
Example #3
0
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
}