This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Actions

Overview

An action is a discrete set of behaviors defined in a single function that acts on a given Kubernetes GroupVersionKind (GVK) passed in during the admission controller lifecycle. Actions are the atomic operations that are performed on Kubernetes resources by Pepr.

For example, an action could be responsible for adding a specific label to a Kubernetes resource, or for modifying a specific field in a resource’s metadata. Actions can be grouped together within a Capability to provide a more comprehensive set of operations that can be performed on Kubernetes resources.

Actions are Mutate(), Validate(), Watch(), or Reconcile(). Both Mutate and Validate actions run during the admission controller lifecycle, while Watch and Reconcile actions run in a separate controller that tracks changes to resources, including existing resources.

Let’s look at some example actions that are included in the HelloPepr capability that is created for you when you npx pepr init:


In this first example, Pepr is adding a label and annotation to a ConfigMap with the name example-1 when it is created. Comments are added to each line to explain in more detail what is happening.

// When(a.<Kind>) filters which GroupVersionKind (GVK) this action should act on.
When(a.ConfigMap)
  // This limits the action to only act on new resources.
  .IsCreated()
  // This limits the action to only act on resources with the name "example-1".
  .WithName("example-1")
  // Mutate() is where we define the actual behavior of this action.
  .Mutate(request => {
    // The request object is a wrapper around the K8s resource that Pepr is acting on.
    request
      // Here we are adding a label to the ConfigMap.
      .SetLabel("pepr", "was-here")
      // And here we are adding an annotation.
      .SetAnnotation("pepr.dev", "annotations-work-too");

    // Note that we are not returning anything here. This is because Pepr is tracking the changes in each action automatically.
  });

In this example, a Validate action rejects any ConfigMap in the pepr-demo namespace that has no data.

When(a.ConfigMap)
  .IsCreated()
  .InNamespace("pepr-demo")
  // Validate() is where we define the actual behavior of this action.
  .Validate(request => {
    // If data exists, approve the request.
    if (request.Raw.data) {
      return request.Approve();
    }

    // Otherwise, reject the request with a message and optional code.
    return request.Deny("ConfigMap must have data");
  });

In this example, a Watch action on the name and phase of any ConfigMap.Watch actions run in a separate controller that tracks changes to resources, including existing resources so that you can react to changes in real-time. It is important to note that Watch actions are not run during the admission controller lifecycle, so they cannot be used to modify or validate resources. They also may run multiple times for the same resource, so it is important to make sure that your Watch actions are idempotent. In a future release, Pepr will provide a better way to control when a Watch action is run to avoid this issue.

When(a.ConfigMap)
  // Watch() is where we define the actual behavior of this action.
  .Watch((cm, phase) => {
    Log.info(cm, `ConfigMap ${cm.metadata.name} was ${phase}`);
  });

There are many more examples in the HelloPepr capability that you can use as a reference when creating your own actions. Note that each time you run npx pepr update, Pepr will automatically update the HelloPepr capability with the latest examples and best practices for you to reference and test directly in your Pepr Module.


In some scenarios involving Kubernetes Resource Controllers or Operator patterns, opting for a Reconcile action could be more fitting. Comparable to the Watch functionality, Reconcile is responsible for monitoring the name and phase of any Kubernetes Object. It operates within the Watch controller dedicated to observing modifications to resources, including those already existing, enabling responses to alterations as they occur. Unlike Watch, however, Reconcile employs a Queue to sequentially handle events once they are returned by the Kubernetes API. This allows the operator to handle bursts of events without overwhelming the system or the Kubernetes API. It provides a mechanism to back off when the system is under heavy load, enhancing overall stability and maintaining the state consistency of Kubernetes resources, as the order of operations can impact the final state of a resource.

When(WebApp)
  .IsCreatedOrUpdated()
  .Validate(validator)
  .Reconcile(async instance => {

    const { namespace, name, generation } = instance.metadata;

    if (!instance.metadata?.namespace) {
      Log.error(instance, `Invalid WebApp definition`);
      return;
    }

    const isPending = instance.status?.phase === Phase.Pending;
    const isCurrentGeneration = generation === instance.status?.observedGeneration;

    if (isPending || isCurrentGeneration) {
      Log.debug(instance, `Skipping pending or completed instance`);
      return;
    }

    Log.debug(instance, `Processing instance ${namespace}/${name}`);


    try {
      // Set Status to pending
      await updateStatus(instance, { phase: Phase.Pending });

      // Deploy Deployment, ConfigMap, Service, ServiceAccount, and RBAC based on instance
      await Deploy(instance);

      // Set Status to ready
      await updateStatus(instance, {
        phase: Phase.Ready,
        observedGeneration: instance.metadata.generation,
      });
    } catch (e) {
      Log.error(e, `Error configuring for ${namespace}/${name}`);

      // Set Status to failed
      void updateStatus(instance, {
        phase: Phase.Failed,
        observedGeneration: instance.metadata.generation,
      });
    }
  });

1 - Mutate

Mutating admission webhooks are invoked first and can modify objects sent to the API server to enforce custom defaults. After an object is sent to Pepr’s Mutating Admission Webhook, Pepr will annotate the object to indicate the status.

After a successful mutation of an object in a module with UUID static-test, and capability name hello-pepr, expect to see this annotation: static-test.pepr.dev/hello-pepr: succeeded.

Helpers

SetLabel

SetLabel is used to set a lable on a Kubernetes object as part of a Pepr Mutate action.

For example, to add a label when a ConfigMap is created:

When(a.ConfigMap)
  .IsCreated()
  .Mutate(request => {
    request
      // Here we are adding a label to the ConfigMap.
      .SetLabel("pepr", "was-here")

    // Note that we are not returning anything here. This is because Pepr is tracking the changes in each action automatically.
  });

RemoveLabel

RemoveLabel is used to remove a label on a Kubernetes object as part of a Pepr Mutate action.

For example, to remove a label when a ConfigMap is updated:

When(a.ConfigMap)
  .IsCreated()
  .Mutate(request => {
    request
      // Here we are removing a label from the ConfigMap.
      .RemoveLabel("remove-me")

    // Note that we are not returning anything here. This is because Pepr is tracking the changes in each action automatically.
  });

SetAnnotation

SetAnnotation is used to set an annotation on a Kubernetes object as part of a Pepr Mutate action.

For example, to add an annotation when a ConfigMap is created:

When(a.ConfigMap)
  .IsCreated()
  .Mutate(request => {
    request
      // Here we are adding an annotation to the ConfigMap.
      .SetAnnotation("pepr.dev", "annotations-work-too");

    // Note that we are not returning anything here. This is because Pepr is tracking the changes in each action automatically.
  });

RemoveAnnotation

RemoveAnnotation is used to remove an annotation on a Kubernetes object as part of a Pepr Mutate action.

For example, to remove an annotation when a ConfigMap is updated:

When(a.ConfigMap)
  .IsUpdated()
  .Mutate(request => {
    request
      // Here we are removing an annotation from the ConfigMap.
      .RemoveAnnotation("remove-me");

    // Note that we are not returning anything here. This is because Pepr is tracking the changes in each action automatically.
  });

See Also

Looking for some more generic helpers? Check out the Module Author SDK for information on other things that Pepr can help with.

2 - Validate

After the Mutation phase comes the Validation phase where the validating admission webhooks are invoked and can reject requests to enforce custom policies.

Validate does not annotate the objects that are allowed into the cluster, but the validation webhook can be audited with npx pepr monitor. Read the monitoring docs for more information.

3 - Reconcile

Reconcile functions the same as Watch but is tailored for building Kubernetes Controllers and Operators because it processes callback operations in a Queue, guaranteeing ordered and synchronous processing of events, even when the system may be under heavy load.

4 - Watch

Kubernetes supports efficient change notifications on resources via watches. Pepr uses the Watch action for monitoring resources that previously existed in the cluster and for performing long-running asynchronous events upon receiving change notifications on resources, as watches are not limited by timeouts.