Make tcpsocket readiness and liveness configurable
[osm/devops.git] / installers / charm / kafka-exporter / src / charm.py
index a15abc8..07a854f 100755 (executable)
@@ -28,10 +28,10 @@ from pathlib import Path
 from typing import NoReturn, Optional
 from urllib.parse import urlparse
 
+from charms.kafka_k8s.v0.kafka import KafkaEvents, KafkaRequires
 from ops.main import main
 from opslib.osm.charm import CharmedOsmBase, RelationsMissing
 from opslib.osm.interfaces.grafana import GrafanaDashboardTarget
-from opslib.osm.interfaces.kafka import KafkaClient
 from opslib.osm.interfaces.prometheus import PrometheusScrapeTarget
 from opslib.osm.pod import (
     ContainerV3Builder,
@@ -52,6 +52,9 @@ class ConfigModel(ModelValidator):
     ingress_class: Optional[str]
     ingress_whitelist_source_range: Optional[str]
     tls_secret_name: Optional[str]
+    image_pull_policy: str
+    security_context: bool
+    kafka_endpoint: Optional[str]
 
     @validator("site_url")
     def validate_site_url(cls, v):
@@ -67,15 +70,41 @@ class ConfigModel(ModelValidator):
             ip_network(v)
         return v
 
+    @validator("image_pull_policy")
+    def validate_image_pull_policy(cls, v):
+        values = {
+            "always": "Always",
+            "ifnotpresent": "IfNotPresent",
+            "never": "Never",
+        }
+        v = v.lower()
+        if v not in values.keys():
+            raise ValueError("value must be always, ifnotpresent or never")
+        return values[v]
+
+    @validator("kafka_endpoint")
+    def validate_kafka_endpoint(cls, v):
+        if v and len(v.split(":")) != 2:
+            raise ValueError("value must be in the format <host>:<port>")
+        return v
+
+
+class KafkaEndpoint:
+    def __init__(self, host: str, port: str) -> None:
+        self.host = host
+        self.port = port
+
 
 class KafkaExporterCharm(CharmedOsmBase):
+    on = KafkaEvents()
+
     def __init__(self, *args) -> NoReturn:
         super().__init__(*args, oci_image="image")
 
         # Provision Kafka relation to exchange information
-        self.kafka_client = KafkaClient(self, "kafka")
-        self.framework.observe(self.on["kafka"].relation_changed, self.configure_pod)
-        self.framework.observe(self.on["kafka"].relation_broken, self.configure_pod)
+        self.kafka = KafkaRequires(self)
+        self.framework.observe(self.on.kafka_available, self.configure_pod)
+        self.framework.observe(self.on.kafka_broken, self.configure_pod)
 
         # Register relation to provide a Scraping Target
         self.scrape_target = PrometheusScrapeTarget(self, "prometheus-scrape")
@@ -124,25 +153,26 @@ class KafkaExporterCharm(CharmedOsmBase):
         if self.unit.is_leader():
             self.dashboard_target.publish_info(
                 name="osm-kafka",
-                dashboard=Path("files/kafka_exporter_dashboard.json").read_text(),
+                dashboard=Path("templates/kafka_exporter_dashboard.json").read_text(),
             )
 
-    def _check_missing_dependencies(self, config: ConfigModel):
-        """Check if there is any relation missing.
+    def _is_kafka_endpoint_set(self, config: ConfigModel) -> bool:
+        """Check if Kafka endpoint is set."""
+        return config.kafka_endpoint or self._is_kafka_relation_set()
 
-        Args:
-            config (ConfigModel): object with configuration information.
+    def _is_kafka_relation_set(self) -> bool:
+        """Check if the Kafka relation is set or not."""
+        return self.kafka.host and self.kafka.port
 
-        Raises:
-            RelationsMissing: if kafka is missing.
-        """
-        missing_relations = []
-
-        if self.kafka_client.is_missing_data_in_unit():
-            missing_relations.append("kafka")
-
-        if missing_relations:
-            raise RelationsMissing(missing_relations)
+    @property
+    def kafka_endpoint(self) -> KafkaEndpoint:
+        config = ConfigModel(**dict(self.config))
+        if config.kafka_endpoint:
+            host, port = config.kafka_endpoint.split(":")
+        else:
+            host = self.kafka.host
+            port = self.kafka.port
+        return KafkaEndpoint(host, port)
 
     def build_pod_spec(self, image_info):
         """Build the PodSpec to be used.
@@ -157,14 +187,22 @@ class KafkaExporterCharm(CharmedOsmBase):
         config = ConfigModel(**dict(self.config))
 
         # Check relations
-        self._check_missing_dependencies(config)
+        if not self._is_kafka_endpoint_set(config):
+            raise RelationsMissing(["kafka"])
 
         # Create Builder for the PodSpec
-        pod_spec_builder = PodSpecV3Builder()
+        pod_spec_builder = PodSpecV3Builder(
+            enable_security_context=config.security_context
+        )
 
         # Build container
-        container_builder = ContainerV3Builder(self.app.name, image_info)
-        container_builder.add_port(name=self.app.name, port=PORT)
+        container_builder = ContainerV3Builder(
+            self.app.name,
+            image_info,
+            config.image_pull_policy,
+            run_as_non_root=config.security_context,
+        )
+        container_builder.add_port(name="exporter", port=PORT)
         container_builder.add_http_readiness_probe(
             path="/api/health",
             port=PORT,
@@ -184,7 +222,7 @@ class KafkaExporterCharm(CharmedOsmBase):
         container_builder.add_command(
             [
                 "kafka_exporter",
-                f"--kafka.server={self.kafka_client.host}:{self.kafka_client.port}",
+                f"--kafka.server={self.kafka_endpoint.host}:{self.kafka_endpoint.port}",
             ]
         )
         container = container_builder.build()
@@ -221,8 +259,6 @@ class KafkaExporterCharm(CharmedOsmBase):
             ingress_resource = ingress_resource_builder.build()
             pod_spec_builder.add_ingress_resource(ingress_resource)
 
-        logger.debug(pod_spec_builder.build())
-
         return pod_spec_builder.build()