authentik.outposts.controllers.k8s.service_monitor

Kubernetes Prometheus ServiceMonitor Reconciler

  1"""Kubernetes Prometheus ServiceMonitor Reconciler"""
  2
  3from dataclasses import asdict, dataclass, field
  4from typing import TYPE_CHECKING
  5
  6from dacite.core import from_dict
  7from kubernetes.client import ApiextensionsV1Api, CustomObjectsApi
  8
  9from authentik.outposts.controllers.base import FIELD_MANAGER
 10from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler
 11from authentik.outposts.controllers.k8s.service import MetricsServiceReconciler
 12from authentik.outposts.controllers.k8s.triggers import NeedsUpdate
 13
 14if TYPE_CHECKING:
 15    from authentik.outposts.controllers.kubernetes import KubernetesController
 16
 17
 18@dataclass(slots=True)
 19class PrometheusServiceMonitorSpecEndpoint:
 20    """Prometheus ServiceMonitor endpoint spec"""
 21
 22    port: str
 23    path: str = field(default="/metrics")
 24
 25
 26@dataclass(slots=True)
 27class PrometheusServiceMonitorSpecSelector:
 28    """Prometheus ServiceMonitor selector spec"""
 29
 30    matchLabels: dict
 31
 32
 33@dataclass(slots=True)
 34class PrometheusServiceMonitorSpec:
 35    """Prometheus ServiceMonitor spec"""
 36
 37    endpoints: list[PrometheusServiceMonitorSpecEndpoint]
 38
 39    selector: PrometheusServiceMonitorSpecSelector
 40
 41
 42@dataclass(slots=True)
 43class PrometheusServiceMonitorMetadata:
 44    """Prometheus ServiceMonitor metadata"""
 45
 46    name: str
 47    namespace: str
 48    labels: dict = field(default_factory=dict)
 49
 50
 51@dataclass(slots=True)
 52class PrometheusServiceMonitor:
 53    """Prometheus ServiceMonitor"""
 54
 55    apiVersion: str
 56    kind: str
 57    metadata: PrometheusServiceMonitorMetadata
 58    spec: PrometheusServiceMonitorSpec
 59
 60    def to_dict(self):
 61        """`to_dict` to conform to how the kubernetes client converts objects to dicts"""
 62        return asdict(self)
 63
 64
 65CRD_NAME = "servicemonitors.monitoring.coreos.com"
 66CRD_GROUP = "monitoring.coreos.com"
 67CRD_VERSION = "v1"
 68CRD_PLURAL = "servicemonitors"
 69
 70
 71class PrometheusServiceMonitorReconciler(KubernetesObjectReconciler[PrometheusServiceMonitor]):
 72    """Kubernetes Prometheus ServiceMonitor Reconciler"""
 73
 74    def __init__(self, controller: KubernetesController) -> None:
 75        super().__init__(controller)
 76        self.api_ex = ApiextensionsV1Api(controller.client)
 77        self.api = CustomObjectsApi(controller.client)
 78
 79    @staticmethod
 80    def reconciler_name() -> str:
 81        return "prometheus servicemonitor"
 82
 83    def reconcile(self, current: PrometheusServiceMonitor, reference: PrometheusServiceMonitor):
 84        if current.spec.selector.matchLabels != reference.spec.selector.matchLabels:
 85            raise NeedsUpdate()
 86        super().reconcile(current, reference)
 87
 88    @property
 89    def noop(self) -> bool:
 90        if not self._crd_exists():
 91            self.logger.debug("CRD doesn't exist")
 92            return True
 93        return self.is_embedded
 94
 95    def _crd_exists(self) -> bool:
 96        """Check if the Prometheus ServiceMonitor exists"""
 97        return bool(
 98            len(
 99                self.api_ex.list_custom_resource_definition(
100                    field_selector=f"metadata.name={CRD_NAME}"
101                ).items
102            )
103        )
104
105    def get_reference_object(self) -> PrometheusServiceMonitor:
106        """Get service monitor object for outpost"""
107        return PrometheusServiceMonitor(
108            apiVersion=f"{CRD_GROUP}/{CRD_VERSION}",
109            kind="ServiceMonitor",
110            metadata=PrometheusServiceMonitorMetadata(
111                name=self.name,
112                namespace=self.namespace,
113                labels=self.get_object_meta().labels,
114            ),
115            spec=PrometheusServiceMonitorSpec(
116                endpoints=[
117                    PrometheusServiceMonitorSpecEndpoint(
118                        port="http-metrics",
119                    )
120                ],
121                selector=PrometheusServiceMonitorSpecSelector(
122                    matchLabels=MetricsServiceReconciler(self.controller)
123                    .get_object_meta(name=self.name)
124                    .labels,
125                ),
126            ),
127        )
128
129    def create(self, reference: PrometheusServiceMonitor):
130        return self.api.create_namespaced_custom_object(
131            group=CRD_GROUP,
132            version=CRD_VERSION,
133            plural=CRD_PLURAL,
134            namespace=self.namespace,
135            body=asdict(reference),
136            field_manager=FIELD_MANAGER,
137        )
138
139    def delete(self, reference: PrometheusServiceMonitor):
140        return self.api.delete_namespaced_custom_object(
141            group=CRD_GROUP,
142            version=CRD_VERSION,
143            namespace=self.namespace,
144            plural=CRD_PLURAL,
145            name=self.name,
146        )
147
148    def retrieve(self) -> PrometheusServiceMonitor:
149        return from_dict(
150            PrometheusServiceMonitor,
151            self.api.get_namespaced_custom_object(
152                group=CRD_GROUP,
153                version=CRD_VERSION,
154                namespace=self.namespace,
155                plural=CRD_PLURAL,
156                name=self.name,
157            ),
158        )
159
160    def update(self, current: PrometheusServiceMonitor, reference: PrometheusServiceMonitor):
161        return self.api.patch_namespaced_custom_object(
162            group=CRD_GROUP,
163            version=CRD_VERSION,
164            namespace=self.namespace,
165            plural=CRD_PLURAL,
166            name=self.name,
167            body=asdict(reference),
168            field_manager=FIELD_MANAGER,
169        )
@dataclass(slots=True)
class PrometheusServiceMonitorSpecEndpoint:
19@dataclass(slots=True)
20class PrometheusServiceMonitorSpecEndpoint:
21    """Prometheus ServiceMonitor endpoint spec"""
22
23    port: str
24    path: str = field(default="/metrics")

Prometheus ServiceMonitor endpoint spec

PrometheusServiceMonitorSpecEndpoint(port: str, path: str = '/metrics')
port: str
path: str
@dataclass(slots=True)
class PrometheusServiceMonitorSpecSelector:
27@dataclass(slots=True)
28class PrometheusServiceMonitorSpecSelector:
29    """Prometheus ServiceMonitor selector spec"""
30
31    matchLabels: dict

Prometheus ServiceMonitor selector spec

PrometheusServiceMonitorSpecSelector(matchLabels: dict)
matchLabels: dict
@dataclass(slots=True)
class PrometheusServiceMonitorSpec:
34@dataclass(slots=True)
35class PrometheusServiceMonitorSpec:
36    """Prometheus ServiceMonitor spec"""
37
38    endpoints: list[PrometheusServiceMonitorSpecEndpoint]
39
40    selector: PrometheusServiceMonitorSpecSelector

Prometheus ServiceMonitor spec

PrometheusServiceMonitorSpec( endpoints: list[PrometheusServiceMonitorSpecEndpoint], selector: PrometheusServiceMonitorSpecSelector)
@dataclass(slots=True)
class PrometheusServiceMonitorMetadata:
43@dataclass(slots=True)
44class PrometheusServiceMonitorMetadata:
45    """Prometheus ServiceMonitor metadata"""
46
47    name: str
48    namespace: str
49    labels: dict = field(default_factory=dict)

Prometheus ServiceMonitor metadata

PrometheusServiceMonitorMetadata(name: str, namespace: str, labels: dict = <factory>)
name: str
namespace: str
labels: dict
@dataclass(slots=True)
class PrometheusServiceMonitor:
52@dataclass(slots=True)
53class PrometheusServiceMonitor:
54    """Prometheus ServiceMonitor"""
55
56    apiVersion: str
57    kind: str
58    metadata: PrometheusServiceMonitorMetadata
59    spec: PrometheusServiceMonitorSpec
60
61    def to_dict(self):
62        """`to_dict` to conform to how the kubernetes client converts objects to dicts"""
63        return asdict(self)

Prometheus ServiceMonitor

PrometheusServiceMonitor( apiVersion: str, kind: str, metadata: PrometheusServiceMonitorMetadata, spec: PrometheusServiceMonitorSpec)
apiVersion: str
kind: str
def to_dict(self):
61    def to_dict(self):
62        """`to_dict` to conform to how the kubernetes client converts objects to dicts"""
63        return asdict(self)

to_dict to conform to how the kubernetes client converts objects to dicts

CRD_NAME = 'servicemonitors.monitoring.coreos.com'
CRD_GROUP = 'monitoring.coreos.com'
CRD_VERSION = 'v1'
CRD_PLURAL = 'servicemonitors'
 72class PrometheusServiceMonitorReconciler(KubernetesObjectReconciler[PrometheusServiceMonitor]):
 73    """Kubernetes Prometheus ServiceMonitor Reconciler"""
 74
 75    def __init__(self, controller: KubernetesController) -> None:
 76        super().__init__(controller)
 77        self.api_ex = ApiextensionsV1Api(controller.client)
 78        self.api = CustomObjectsApi(controller.client)
 79
 80    @staticmethod
 81    def reconciler_name() -> str:
 82        return "prometheus servicemonitor"
 83
 84    def reconcile(self, current: PrometheusServiceMonitor, reference: PrometheusServiceMonitor):
 85        if current.spec.selector.matchLabels != reference.spec.selector.matchLabels:
 86            raise NeedsUpdate()
 87        super().reconcile(current, reference)
 88
 89    @property
 90    def noop(self) -> bool:
 91        if not self._crd_exists():
 92            self.logger.debug("CRD doesn't exist")
 93            return True
 94        return self.is_embedded
 95
 96    def _crd_exists(self) -> bool:
 97        """Check if the Prometheus ServiceMonitor exists"""
 98        return bool(
 99            len(
100                self.api_ex.list_custom_resource_definition(
101                    field_selector=f"metadata.name={CRD_NAME}"
102                ).items
103            )
104        )
105
106    def get_reference_object(self) -> PrometheusServiceMonitor:
107        """Get service monitor object for outpost"""
108        return PrometheusServiceMonitor(
109            apiVersion=f"{CRD_GROUP}/{CRD_VERSION}",
110            kind="ServiceMonitor",
111            metadata=PrometheusServiceMonitorMetadata(
112                name=self.name,
113                namespace=self.namespace,
114                labels=self.get_object_meta().labels,
115            ),
116            spec=PrometheusServiceMonitorSpec(
117                endpoints=[
118                    PrometheusServiceMonitorSpecEndpoint(
119                        port="http-metrics",
120                    )
121                ],
122                selector=PrometheusServiceMonitorSpecSelector(
123                    matchLabels=MetricsServiceReconciler(self.controller)
124                    .get_object_meta(name=self.name)
125                    .labels,
126                ),
127            ),
128        )
129
130    def create(self, reference: PrometheusServiceMonitor):
131        return self.api.create_namespaced_custom_object(
132            group=CRD_GROUP,
133            version=CRD_VERSION,
134            plural=CRD_PLURAL,
135            namespace=self.namespace,
136            body=asdict(reference),
137            field_manager=FIELD_MANAGER,
138        )
139
140    def delete(self, reference: PrometheusServiceMonitor):
141        return self.api.delete_namespaced_custom_object(
142            group=CRD_GROUP,
143            version=CRD_VERSION,
144            namespace=self.namespace,
145            plural=CRD_PLURAL,
146            name=self.name,
147        )
148
149    def retrieve(self) -> PrometheusServiceMonitor:
150        return from_dict(
151            PrometheusServiceMonitor,
152            self.api.get_namespaced_custom_object(
153                group=CRD_GROUP,
154                version=CRD_VERSION,
155                namespace=self.namespace,
156                plural=CRD_PLURAL,
157                name=self.name,
158            ),
159        )
160
161    def update(self, current: PrometheusServiceMonitor, reference: PrometheusServiceMonitor):
162        return self.api.patch_namespaced_custom_object(
163            group=CRD_GROUP,
164            version=CRD_VERSION,
165            namespace=self.namespace,
166            plural=CRD_PLURAL,
167            name=self.name,
168            body=asdict(reference),
169            field_manager=FIELD_MANAGER,
170        )

Kubernetes Prometheus ServiceMonitor Reconciler

api_ex
api
@staticmethod
def reconciler_name() -> str:
80    @staticmethod
81    def reconciler_name() -> str:
82        return "prometheus servicemonitor"

A name this reconciler is identified by in the configuration

def reconcile( self, current: PrometheusServiceMonitor, reference: PrometheusServiceMonitor):
84    def reconcile(self, current: PrometheusServiceMonitor, reference: PrometheusServiceMonitor):
85        if current.spec.selector.matchLabels != reference.spec.selector.matchLabels:
86            raise NeedsUpdate()
87        super().reconcile(current, reference)

Check what operations should be done, should be raised as ReconcileTrigger

noop: bool
89    @property
90    def noop(self) -> bool:
91        if not self._crd_exists():
92            self.logger.debug("CRD doesn't exist")
93            return True
94        return self.is_embedded

Return true if this object should not be created/updated/deleted in this cluster

def get_reference_object( self) -> PrometheusServiceMonitor:
106    def get_reference_object(self) -> PrometheusServiceMonitor:
107        """Get service monitor object for outpost"""
108        return PrometheusServiceMonitor(
109            apiVersion=f"{CRD_GROUP}/{CRD_VERSION}",
110            kind="ServiceMonitor",
111            metadata=PrometheusServiceMonitorMetadata(
112                name=self.name,
113                namespace=self.namespace,
114                labels=self.get_object_meta().labels,
115            ),
116            spec=PrometheusServiceMonitorSpec(
117                endpoints=[
118                    PrometheusServiceMonitorSpecEndpoint(
119                        port="http-metrics",
120                    )
121                ],
122                selector=PrometheusServiceMonitorSpecSelector(
123                    matchLabels=MetricsServiceReconciler(self.controller)
124                    .get_object_meta(name=self.name)
125                    .labels,
126                ),
127            ),
128        )

Get service monitor object for outpost

def create( self, reference: PrometheusServiceMonitor):
130    def create(self, reference: PrometheusServiceMonitor):
131        return self.api.create_namespaced_custom_object(
132            group=CRD_GROUP,
133            version=CRD_VERSION,
134            plural=CRD_PLURAL,
135            namespace=self.namespace,
136            body=asdict(reference),
137            field_manager=FIELD_MANAGER,
138        )

API Wrapper to create object

def delete( self, reference: PrometheusServiceMonitor):
140    def delete(self, reference: PrometheusServiceMonitor):
141        return self.api.delete_namespaced_custom_object(
142            group=CRD_GROUP,
143            version=CRD_VERSION,
144            namespace=self.namespace,
145            plural=CRD_PLURAL,
146            name=self.name,
147        )

API Wrapper to delete object

def retrieve( self) -> PrometheusServiceMonitor:
149    def retrieve(self) -> PrometheusServiceMonitor:
150        return from_dict(
151            PrometheusServiceMonitor,
152            self.api.get_namespaced_custom_object(
153                group=CRD_GROUP,
154                version=CRD_VERSION,
155                namespace=self.namespace,
156                plural=CRD_PLURAL,
157                name=self.name,
158            ),
159        )

API Wrapper to retrieve object

def update( self, current: PrometheusServiceMonitor, reference: PrometheusServiceMonitor):
161    def update(self, current: PrometheusServiceMonitor, reference: PrometheusServiceMonitor):
162        return self.api.patch_namespaced_custom_object(
163            group=CRD_GROUP,
164            version=CRD_VERSION,
165            namespace=self.namespace,
166            plural=CRD_PLURAL,
167            name=self.name,
168            body=asdict(reference),
169            field_manager=FIELD_MANAGER,
170        )

API Wrapper to update object