...

Package patch

import "edge-infra.dev/pkg/k8s/runtime/patch"
Overview
Index

Overview ▾

func ToUnstructured

func ToUnstructured(obj runtime.Object) (*unstructured.Unstructured, error)

ToUnstructured converts a runtime.Object into an Unstructured object.

type Helper

Helper is a utility for ensuring the proper patching of objects.

The Helper MUST be initialised before a set of modifications within the scope of an envisioned patch are made to an object, so that the difference in state can be utilised to calculate a patch that can be used on a new revision of the resource in case of conflicts.

A common pattern for reconcilers is to initialise a NewHelper at the beginning of their Reconcile method, after having fetched the latest revision for the resource from the API server, and then defer the call of Helper.Patch. This ensures any modifications made to the spec and the status (conditions) object of the resource are always persisted at the end of a reconcile run.

The example below assumes that you will use the Reconciling condition to signal that progress can be made; if it is not present, and the Ready condition is not true, the resource will be marked as stalled.

func (r *FooReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, retErr error) {
	// Retrieve the object from the API server
	obj := &v1.Foo{}
	if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
		return ctrl.Result{}, client.IgnoreNotFound(err)
	}

	// Initialise the patch helper
	patchHelper, err := patch.NewHelper(obj, r.Client)
	if err != nil {
		return ctrl.Result{}, err
	}

	// Always attempt to patch the object and status after each reconciliation
	defer func() {
		// Patch the object, ignoring conflicts on the conditions owned by this controller
		patchOpts := []patch.Option{
			patch.WithOwnedConditions{
				Conditions: []string{
					meta.ReadyCondition,
					meta.ReconcilingCondition,
					meta.StalledCondition,
					// any other "owned conditions"
				},
			},
		}

		// On a clean exit, determine if the resource is still being reconciled, or if it has stalled, and record this observation
		if retErr == nil && (result.IsZero() || !result.Requeue) {
			// We have now observed this generation
			patchOpts = append(patchOpts, patch.WithStatusObservedGeneration{})

			readyCondition := conditions.Get(obj, meta.ReadyCondition)
			switch {
			case readyCondition.Status == metav1.ConditionTrue:
				// As we are no longer reconciling and the end-state is ready, the reconciliation is no longer stalled or progressing, so clear these
				conditions.Delete(obj, meta.StalledCondition)
				conditions.Delete(obj, meta.ReconcilingCondition)
			case conditions.IsReconciling(obj):
				// This implies stalling is not set; nothing to do
				break
			case readyCondition.Status == metav1.ConditionFalse:
				// As we are no longer reconciling and the end-state is not ready, the reconciliation has stalled
				conditions.MarkTrue(obj, meta.StalledCondition, readyCondition.Reason, readyCondition.Message)
			}
		}

		// Finally, patch the resource
		if err := patchHelper.Patch(ctx, obj, patchOpts...); err != nil {
			retErr = kerrors.NewAggregate([]error{retErr, err})
		}
	}()

	// ...start with actual reconciliation logic
}

Using this pattern, one-off or scoped patches for a subset of a reconcile operation can be made by initialising a new Helper using NewHelper with the current state of the resource, making the modifications, and then directly applying the patch using Helper.Patch, for example:

func (r *FooReconciler) subsetReconcile(ctx context.Context, obj *v1.Foo) (ctrl.Result, error) {
	patchHelper, err := patch.NewHelper(obj, r.Client)
	if err != nil {
		return ctrl.Result{}, err
	}

	// Set CustomField in status object of resource
	obj.Status.CustomField = "value"

	// Patch now only attempts to persist CustomField
	patchHelper.Patch(ctx, obj, nil)
}
type Helper struct {
    // contains filtered or unexported fields
}

func NewHelper

func NewHelper(obj client.Object, crClient client.Client) (*Helper, error)

NewHelper returns an initialised Helper.

func (*Helper) Patch

func (h *Helper) Patch(ctx context.Context, obj client.Object, opts ...Option) error

Patch will attempt to patch the given object, including its status.

type HelperOptions

HelperOptions contains options for patch options.

type HelperOptions struct {
    // IncludeStatusObservedGeneration sets the status.observedGeneration field on the incoming object to match
    // metadata.generation, only if there is a change.
    IncludeStatusObservedGeneration bool

    // ForceOverwriteConditions allows the patch helper to overwrite conditions in case of conflicts.
    // This option should only ever be set in controller managing the object being patched.
    ForceOverwriteConditions bool

    // OwnedConditions defines condition types owned by the controller.
    // In case of conflicts for the owned conditions, the patch helper will always use the value provided by the
    // controller.
    OwnedConditions []string

    // FieldOwner defines the field owner configuration for Kubernetes patch operations.
    FieldOwner string
}

type Option

Option is some configuration that modifies options for a patch request.

type Option interface {
    // ApplyToHelper applies this configuration to the given Helper options.
    ApplyToHelper(*HelperOptions)
}

type SerialPatcher

SerialPatcher provides serial patching of object using the patch helper. It remembers the state of the last patched object and uses that to calculate the patch against a new object.

type SerialPatcher struct {
    // contains filtered or unexported fields
}

func NewSerialPatcher

func NewSerialPatcher(obj client.Object, c client.Client) *SerialPatcher

NewSerialPatcher returns a SerialPatcher with the given object as the initial base object for the patching operations.

func (*SerialPatcher) Patch

func (sp *SerialPatcher) Patch(ctx context.Context, obj client.Object, options ...Option) error

Patch performs patching operation of the SerialPatcher and updates the beforeObject after a successful patch for subsequent patching.

type WithFieldOwner

WithFieldOwner set the field manager name for the patch operations.

type WithFieldOwner string

func (WithFieldOwner) ApplyToHelper

func (w WithFieldOwner) ApplyToHelper(in *HelperOptions)

ApplyToHelper applies this configuration to the given HelperOptions.

type WithForceOverwriteConditions

WithForceOverwriteConditions allows the patch helper to overwrite conditions in case of conflicts. This option should only ever be set in controller managing the object being patched.

type WithForceOverwriteConditions struct{}

func (WithForceOverwriteConditions) ApplyToHelper

func (w WithForceOverwriteConditions) ApplyToHelper(in *HelperOptions)

ApplyToHelper applies this configuration to the given HelperOptions.

type WithOwnedConditions

WithOwnedConditions allows to define condition types owned by the controller. In case of conflicts for the owned conditions, the patch helper will always use the value provided by the controller.

type WithOwnedConditions struct {
    Conditions []string
}

func (WithOwnedConditions) ApplyToHelper

func (w WithOwnedConditions) ApplyToHelper(in *HelperOptions)

ApplyToHelper applies this configuration to the given HelperOptions.

type WithStatusObservedGeneration

WithStatusObservedGeneration sets the status.observedGeneration field on the incoming object to match metadata.generation, only if there is a change.

type WithStatusObservedGeneration struct{}

func (WithStatusObservedGeneration) ApplyToHelper

func (w WithStatusObservedGeneration) ApplyToHelper(in *HelperOptions)

ApplyToHelper applies this configuration to the given HelperOptions.