Current section

Files

Jump to
bonny lib bonny controller.ex
Raw

lib/bonny/controller.ex

defmodule Bonny.Controller do
@moduledoc """
`Bonny.Controller` defines controller behaviours and generates boilerplate for generating Kubernetes manifests.
> A custom controller is a controller that users can deploy and update on a running cluster, independently of the cluster’s own lifecycle. Custom controllers can work with any kind of resource, but they are especially effective when combined with custom resources. The Operator pattern is one example of such a combination. It allows developers to encode domain knowledge for specific applications into an extension of the Kubernetes API.
Controllers allow for simple `add`, `modify`, `delete`, and `reconcile` handling of custom resources in the Kubernetes API.
"""
@doc """
Should return an operation to list resources for watching and reconciliation.
Bonny.Controller comes with a default implementation
"""
@callback list_operation() :: K8s.Operation.t()
@doc """
Bonny.Controller comes with a default implementation which returns Bonny.Config.config()
"""
@callback conn() :: K8s.Conn.t()
#  Action Callbacks
@callback add(map()) :: :ok | :error
@callback modify(map()) :: :ok | :error
@callback delete(map()) :: :ok | :error
@callback reconcile(map()) :: :ok | :error
@doc false
@deprecated "Migrate your operator to `Bonny.Operator` and `Bonny.ControllerV2`."
defmacro __using__(opts) do
quote bind_quoted: [opts: opts] do
Module.register_attribute(__MODULE__, :rule, accumulate: true)
@behaviour Bonny.Controller
# CRD defaults
@group Bonny.Config.group()
@kind Bonny.Naming.module_to_kind(__MODULE__)
@scope :namespaced
@version Bonny.Naming.module_version(__MODULE__)
@singular Macro.underscore(Bonny.Naming.module_to_kind(__MODULE__))
@plural "#{@singular}s"
@names %{}
@additional_printer_columns []
@before_compile Bonny.Controller
use Supervisor
import Bonny.Resource, only: [add_owner_reference: 2]
@spec start_link(term) :: {:ok, pid}
def start_link(_) do
Supervisor.start_link(__MODULE__, %{}, name: __MODULE__)
end
@impl true
def init(_init_arg) do
conn = conn()
list_operation = list_operation()
children = [
{Bonny.Server.AsyncStreamRunner,
id: __MODULE__.WatchServer,
name: __MODULE__.WatchServer,
stream:
Bonny.Server.Watcher.get_stream(
__MODULE__,
conn,
Bonny.Controller.ensure_watch_query(list_operation)
),
termination_delay: 5_000},
{Bonny.Server.AsyncStreamRunner,
id: __MODULE__.ReconcileServer,
name: __MODULE__.ReconcileServer,
stream:
Bonny.Server.Reconciler.get_stream(
__MODULE__,
conn,
Bonny.Controller.ensure_list_query(list_operation)
),
termination_delay: 30_000}
]
Supervisor.init(
children,
strategy: :one_for_one,
max_restarts: 20,
max_seconds: 120
)
end
@impl Bonny.Controller
def list_operation(), do: Bonny.Controller.list_operation(__MODULE__)
def resource_endpoint() do
crd = crd()
%Bonny.API.ResourceEndpoint{
group: crd.group,
version: crd.version,
scope: crd.scope,
resource_type: crd.names.plural
}
end
@impl Bonny.Controller
defdelegate conn(), to: Bonny.Config
defoverridable list_operation: 0, conn: 0
end
end
# coveralls-ignore-start trivial code
def ensure_list_query(%K8s.Operation{verb: :watch} = op) do
struct!(op, verb: :list)
end
def ensure_list_query(%K8s.Operation{verb: :watch_all_namespaces} = op) do
struct!(op, verb: :list_all_namespaces)
end
def ensure_list_query(op), do: op
def ensure_watch_query(%K8s.Operation{verb: :list} = op) do
struct!(op, verb: :watch)
end
def ensure_watch_query(%K8s.Operation{verb: :list_all_namespaces} = op) do
struct!(op, verb: :watch_all_namespaces)
end
def ensure_watch_query(op), do: op
# coveralls-ignore-end
@doc false
defmacro __before_compile__(_) do
quote unquote: false do
additional_printer_columns =
case Module.get_attribute(__MODULE__, :additional_printer_columns, []) do
[] -> quote do: []
_ -> quote do: @additional_printer_columns ++ Bonny.CRD.default_columns()
end
@doc """
Returns the `Bonny.CRD.t()` the controller manages the lifecycle of.
"""
@spec crd() :: Bonny.CRD.t()
def crd() do
%Bonny.CRD{
group: @group,
scope: @scope,
version: @version,
names: Map.merge(default_names(), @names),
additional_printer_columns: unquote(additional_printer_columns)
}
end
@doc """
A list of RBAC rules that this controller needs to operate.
This list will be serialized into the operator manifest when using `mix bonny.gen.manifest`.
"""
@spec rules() :: list(map())
def rules() do
Enum.reduce(@rule, [], fn {api, resources, verbs}, acc ->
rule = %{
apiGroups: [api],
resources: resources,
verbs: verbs
}
[rule | acc]
end)
end
@spec default_names() :: map()
defp default_names() do
%{
plural: @plural,
singular: @singular,
kind: @kind,
shortNames: nil
}
end
end
end
@spec list_operation(module()) :: K8s.Operation.t()
def list_operation(controller) do
crd = controller.crd()
api_version = Bonny.CRD.api_version(crd)
kind = Bonny.CRD.kind(crd)
case crd.scope do
:namespaced -> K8s.Client.watch(api_version, kind, namespace: Bonny.Config.namespace())
_ -> K8s.Client.watch(api_version, kind)
end
end
end