# `Bonny.Axn`
[🔗](https://github.com/coryodaniel/bonny/blob/v1.5.0/lib/bonny/axn.ex#L1)

Describes a resource action event.

This is the token passed to all steps of your operator and controller
pipeline.

This module gets imported to your controllers where you should use the
functions `register_descendant/3`, `update_status/2` and the ones to register
events: `success_event/2`, `failure_event/2` and/or `register_event/6`. Note
that these functions raise exceptions if those resources have already been
applied to the cluster.

The `register_before_*` functions can be used in `Pluggable` steps in order
to register callbacks that are called before applying resources to the
cluster. Have a look at `Bonny.Pluggable.Logger` for a use case.

## Action event fields

These fields contain information on the action event that occurred.

  * `action` - the action that triggered this event
  * `resource` - the resource the action was applied to
  * `conn` - the connection to the cluster the event occurred
  * `operator` - the operator that discovered and dispatched the event
  * `controller` - the controller handling the event and its init opts

## Reaction fields

  * `descendants` - descending resources defined by the handling controller
  * `status` - the data to be applied to the status subresource
  * `events` - Kubernetes events regarding the resource to be applied to the cluster

## Pipeline fields

  * `halted` - the boolean status on whether the pipeline was halted
  * `assigns` - shared user data as a map
  * `private` - shared library data as a map
  * `states` - The states for status, events and descendants

# `assigns`

```elixir
@type assigns() :: %{optional(atom()) =&gt; any()}
```

# `states`

```elixir
@type states() :: integer()
```

# `t`

```elixir
@type t() :: %Bonny.Axn{
  action: :add | :modify | :reconcile | :delete,
  assigns: assigns(),
  conn: K8s.Conn.t(),
  controller: {controller :: module(), init_opts :: keyword()} | nil,
  descendants: %{
    required({name :: binary(), namespace :: binary()}) =&gt;
      {integer(), Bonny.Resource.t()}
  },
  events: [Bonny.Event.t()],
  halted: boolean(),
  operator: module() | nil,
  private: assigns(),
  resource: Bonny.Resource.t(),
  states: states(),
  status: map() | nil
}
```

# `apply_descendants`

```elixir
@spec apply_descendants(t(), Keyword.t()) :: t()
```

Applies the dependants to the cluster in groups.
If `:create_events` is true, will create an event for each successful apply.
Always creates events upon failed applies.

## Options

`:create_events` - Whether events should be created upon success. Defaults to `true`

All further options are passed to `K8s.Client.apply/2`

# `apply_status`

```elixir
@spec apply_status(t(), Keyword.t()) :: t()
```

Applies the status to the resource's status subresource in the cluster.
If no status was specified, :noop is returned.

# `are_descendants_applied`
*macro* 

# `are_events_emitted`
*macro* 

# `clear_events`

```elixir
@spec clear_events(t()) :: t()
```

Empties the list of events without emitting them.

# `emit_events`

```elixir
@spec emit_events(t()) :: t()
```

Emits the events created for this Axn.

# `failure_event`

```elixir
@spec failure_event(t(), Keyword.t()) :: t()
```

Registers a failure event to the `%Axn{}` token to be emitted by Bonny.

# `identifier`

```elixir
@spec identifier(t()) ::
  {namespace_name :: binary(), api_version :: binary(), kind_action :: binary()}
```

Returns an identifier of an action event (resource and action) as tuple.
Can be used in logs and similar.

# `is_status_applied`
*macro* 

# `new!`

```elixir
@spec new!(Keyword.t()) :: t()
```

# `register_after_processed`

```elixir
@spec register_after_processed(t(), (t() -&gt; t())) :: t()
```

Registers a callback to be invoked at the very end of an  action event's
processing by the operator.

Callbacks are invoked in the reverse order they are defined (callbacks
defined first are invoked last).

## Examples

To log a message after the an event was processed by the operator:

    Bonny.Axn.register_after_processed(axn, fn axn ->
      Logger.info("done")
      axn
    end)

# `register_before_apply_descendants`

```elixir
@spec register_before_apply_descendants(t(), ([Bonny.Resource.t()], t() -&gt;
                                          [Bonny.Resource.t()])) :: t()
```

Registers a callback to be invoked before descendants are applied to the
cluster.

Callbacks are invoked in the reverse order they are defined (callbacks
defined first are invoked last).

## Examples

To log a message:

    require Logger
    Bonny.Axn.register_before_apply_status(axn, fn descendants, axn ->
      Enum.each(descendants, &Logger.info("Descending #{&1["kind"]} named #{&1["name"]} is applied to namespace #{&1["metadata"]["namespace"]}"))
      descendants
    end)

# `register_before_apply_status`

```elixir
@spec register_before_apply_status(t(), (Bonny.Resource.t(), t() -&gt;
                                     Bonny.Resource.t())) :: t()
```

Registers a callback to be invoked before a status is applied to the
status subresource.

Callbacks are invoked in the reverse order they are defined (callbacks
defined first are invoked last).

## Examples

To log a message for the status being applied:

    require Logger
    Bonny.Axn.register_before_apply_status(axn, fn resource, axn ->
      Logger.info("Status of the #{resource["kind"]} named #{resource["metadata"]["name"]} is applied to namespace #{resource["metadata"]["namespace"]}")
      resource
    end)

# `register_before_emit_event`

```elixir
@spec register_before_emit_event(t(), (Bonny.Event.t(), t() -&gt; Bonny.Event.t())) ::
  t()
```

Registers a callback to be invoked before events are emitted to the
cluster.

Callbacks are invoked in the reverse order they are defined (callbacks
defined first are invoked last).

## Examples

To log a message:

    require Logger
    Bonny.Axn.register_before_apply_status(axn, fn events, axn ->
      Logger.info("Event of type #{event.event_type} is emitted")
      events
    end)

# `register_descendant`

```elixir
@spec register_descendant(t(), Bonny.Resource.t(), Keyword.t()) :: t()
```

Registers a decending object to be applied.
Owner reference will be added automatically unless disabled through
the option `omit_owner_ref` or if the resource is cluster-scoped.

> #### No Owner References for Cluster-scoped Resources {: .info}
>
> Cluster-scoped resources (resources without a namespace) will
> automatically have owner references omitted, regardless of the
> `omit_owner_ref` option, as they should never have owner references
> according to Kubernetes best practices.

If you need some resources to be applied before others, use the `group`
option to indicate which group a resourceis added. Groups are applied in
ascending order. Resources within a group are applied simultaneously.

## Options

* `omit_owner_ref` - when `true`, adding the owner reference is omitted.
  Default: `false`. Note: This is automatically set to `true` for
  cluster-scoped resources.
* `group` - Integer (posittive or negative). Controls the order in which
  descendants are applied to the cluster. Default: `0`

## Examples

The following code registers a namespace and a pod as descendants. The
namespace is assigned to group `-1` as it needs to exist when the pod
is created within it. The namespace is cluster-scoped so owner references
are automatically omitted.

```
ns = %{
  "apiVersion" => "v1",
  "kind" => "Namespace",
  "metadata" => %{"name" => "my_ns"}
}
pod = %{
  "apiVersion" => "v1",
  "kind" => "Pod",
  "metadata" => %{"namespace" => "my_ns", "name" => "my_pod"},
  ...
}

axn
|> Bonny.Axn.register_descendant(resource, ns, group: -1)
|> Bonny.Axn.register_descendant(resource, pod)

# Cluster-scoped resource like PersistentVolume automatically omits owner ref
pv = %{
  "apiVersion" => "v1",
  "kind" => "PersistentVolume",
  "metadata" => %{"name" => "my-pv"},
  "spec" => %{"capacity" => %{"storage" => "1Gi"}}
}

axn |> Bonny.Axn.register_descendant(resource, pv)  # No owner ref added
```

# `register_event`

```elixir
@spec register_event(
  t(),
  Bonny.Resource.t() | nil,
  Bonny.Event.event_type(),
  binary(),
  binary(),
  binary()
) :: t()
```

Registers a Kubernetes event to the `%Axn{}` token to be emitted by Bonny.

# `safe_apply_status`

```elixir
@spec safe_apply_status(t(), Keyword.t()) :: t()
```

Applies the status to the resource's status subresource in the cluster,
gracefully handling "NotFound" errors.

This is useful for operators where a resource may be deleted while
reconciliation is still in progress. When the status update fails because
the resource no longer exists, a warning is logged and the unmodified
`axn` is returned instead of raising.

All other errors still raise as in `apply_status/2`.

## Options

Same as `apply_status/2`: `:field_manager`, `:force`, etc.

## Examples

    # In a controller step
    Bonny.Axn.safe_apply_status(axn, field_manager: "MyOperator", force: true)

# `set_condition`

```elixir
@spec set_condition(
  axn :: t(),
  type :: binary(),
  status :: boolean(),
  message :: binary() | nil
) :: t()
```

Sets the condition in the resource status.

The field `.status.conditions`, if configured in the CRD, nolds a list of
conditions, their `status` with a `message` and two timestamps. On the
resource this could look something like this (taken from a Pod):

```
kind: Pod
status:
  conditions:
    - lastTransitionTime: "2019-10-22T16:29:24Z"
      status: "True"
      type: PodScheduled
    - lastTransitionTime: "2019-10-22T16:29:24Z"
      status: "True"
      type: Initialized
    - lastTransitionTime: "2019-10-22T16:29:31Z"
      status: "True"
      type: ContainersReady
    - lastTransitionTime: "2019-10-22T16:29:31Z"
      status: "True"
      type: Ready
```

# `success_event`

```elixir
@spec success_event(t(), Keyword.t()) :: t()
```

Registers a asuccess event to the `%Axn{}` token to be emitted by Bonny.

# `update_status`

```elixir
@spec update_status(t(), (map() -&gt; map())) :: t()
```

Executes `fun` for the resource status and applies the new status
subresource. This can be called multiple times.

`fun` should be a function of arity 1. It will be passed the
current status object and expected to return the updated one.

If no current status exists, an empty map is passed to `fun`

---

*Consult [api-reference.md](api-reference.md) for complete listing*
