This repository has been archived on 2024-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
authentik/passbook/outposts/controllers/k8s/base.py

129 lines
4.3 KiB
Python

"""Base Kubernetes Reconciler"""
from typing import TYPE_CHECKING, Generic, TypeVar
from kubernetes.client import V1ObjectMeta
from kubernetes.client.rest import ApiException
from structlog import get_logger, wrap_logger
from passbook import __version__
from passbook.lib.sentry import SentryIgnoredException
if TYPE_CHECKING:
from passbook.outposts.controllers.kubernetes import KubernetesController
# pylint: disable=invalid-name
T = TypeVar("T")
class ReconcileTrigger(SentryIgnoredException):
"""Base trigger raised by child classes to notify us"""
class NeedsRecreate(ReconcileTrigger):
"""Exception to trigger a complete recreate of the Kubernetes Object"""
class NeedsUpdate(ReconcileTrigger):
"""Exception to trigger an update to the Kubernetes Object"""
class KubernetesObjectReconciler(Generic[T]):
"""Base Kubernetes Reconciler, handles the basic logic."""
controller: "KubernetesController"
def __init__(self, controller: "KubernetesController"):
self.controller = controller
self.namespace = controller.outpost.config.kubernetes_namespace
self.logger = wrap_logger(
get_logger(), controller=self.__class__.__name__, outpost=controller.outpost
)
@property
def name(self) -> str:
"""Get the name of the object this reconciler manages"""
raise NotImplementedError
def up(self):
"""Create object if it doesn't exist, update if needed or recreate if needed."""
current = None
reference = self.get_reference_object()
try:
try:
current = self.retrieve()
except ApiException as exc:
if exc.status == 404:
self.logger.debug("Failed to get current, triggering recreate")
raise NeedsRecreate from exc
self.logger.debug("Other unhandled error", exc=exc)
raise exc
else:
self.logger.debug("Got current, running reconcile")
self.reconcile(current, reference)
except NeedsRecreate:
self.logger.debug("Recreate requested")
if current:
self.logger.debug("Deleted old")
self.delete(current)
else:
self.logger.debug("No old found, creating")
self.logger.debug("Created")
self.create(reference)
except NeedsUpdate:
self.logger.debug("Updating")
self.update(current, reference)
else:
self.logger.debug("Nothing to do...")
def down(self):
"""Delete object if found"""
try:
current = self.retrieve()
self.delete(current)
self.logger.debug("Removing")
except ApiException as exc:
if exc.status == 404:
self.logger.debug("Failed to get current, assuming non-existant")
return
self.logger.debug("Other unhandled error", exc=exc)
raise exc
def get_reference_object(self) -> T:
"""Return object as it should be"""
raise NotImplementedError
def reconcile(self, current: T, reference: T):
"""Check what operations should be done, should be raised as
ReconcileTrigger"""
raise NotImplementedError
def create(self, reference: T):
"""API Wrapper to create object"""
raise NotImplementedError
def retrieve(self) -> T:
"""API Wrapper to retrive object"""
raise NotImplementedError
def delete(self, reference: T):
"""API Wrapper to delete object"""
raise NotImplementedError
def update(self, current: T, reference: T):
"""API Wrapper to update object"""
raise NotImplementedError
def get_object_meta(self, **kwargs) -> V1ObjectMeta:
"""Get common object metadata"""
return V1ObjectMeta(
namespace=self.namespace,
labels={
"app.kubernetes.io/name": f"passbook-{self.controller.outpost.type.lower()}",
"app.kubernetes.io/instance": self.controller.outpost.name,
"app.kubernetes.io/version": __version__,
"app.kubernetes.io/managed-by": "passbook.beryju.org",
"passbook.beryju.org/outpost-uuid": self.controller.outpost.uuid.hex,
},
**kwargs,
)