diff --git a/lib/charms/kubernetes_charm_libraries/v0/multus.py b/lib/charms/kubernetes_charm_libraries/v0/multus.py index 7bcf7694..c4256193 100644 --- a/lib/charms/kubernetes_charm_libraries/v0/multus.py +++ b/lib/charms/kubernetes_charm_libraries/v0/multus.py @@ -94,7 +94,7 @@ def _on_config_changed(self, event: EventBase): import logging from dataclasses import asdict, dataclass from json.decoder import JSONDecodeError -from typing import Callable, Union +from typing import Callable, List, Optional, Union import httpx from lightkube import Client @@ -123,7 +123,7 @@ def _on_config_changed(self, event: EventBase): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 8 +LIBPATCH = 10 logger = logging.getLogger(__name__) @@ -150,8 +150,16 @@ class NetworkAnnotation: name: str interface: str + mac: Optional[str] = None + ips: Optional[List[str]] = None - dict = asdict + def dict(self) -> dict: + """Returns a NetworkAnnotation in the form of a dictionary. + + Returns: + dict: Dictionary representation of the NetworkAnnotation + """ + return {key: value for key, value in asdict(self).items() if value} class KubernetesMultusError(Exception): @@ -376,6 +384,54 @@ def patch_statefulset( raise KubernetesMultusError(f"Could not patch statefulset {name}") logger.info("Multus annotation added to %s statefulset", name) + def unpatch_statefulset( + self, + name: str, + container_name: str, + ) -> None: + """Removes annotations, security privilege and NET_ADMIN capability from stateful set. + + Args: + name: Statefulset name + container_name: Container name + """ + try: + statefulset = self.client.get(res=StatefulSet, name=name, namespace=self.namespace) + except ApiError: + raise KubernetesMultusError(f"Could not get statefulset {name}") + + container = Container(name=container_name) + container.securityContext = SecurityContext( + capabilities=Capabilities( + drop=[ + "NET_ADMIN", + ] + ) + ) + container.securityContext.privileged = False + statefulset_delta = StatefulSet( + spec=StatefulSetSpec( + selector=statefulset.spec.selector, # type: ignore[attr-defined] + serviceName=statefulset.spec.serviceName, # type: ignore[attr-defined] + template=PodTemplateSpec( + metadata=ObjectMeta(annotations={"k8s.v1.cni.cncf.io/networks": "[]"}), + spec=PodSpec(containers=[container]), + ), + ) + ) + try: + self.client.patch( + res=StatefulSet, + name=name, + obj=statefulset_delta, + patch_type=PatchType.APPLY, + namespace=self.namespace, + field_manager=self.__class__.__name__, + ) + except ApiError: + raise KubernetesMultusError(f"Could not remove patches from statefulset {name}") + logger.info("Multus annotation removed from %s statefulset", name) + def statefulset_is_patched( self, name: str, @@ -446,8 +502,9 @@ def _pod_is_patched( return False return True + @staticmethod def _annotations_contains_multus_networks( - self, annotations: dict, network_annotations: list[NetworkAnnotation] + annotations: dict, network_annotations: list[NetworkAnnotation] ) -> bool: if "k8s.v1.cni.cncf.io/networks" not in annotations: return False @@ -460,8 +517,8 @@ def _annotations_contains_multus_networks( return False return True + @staticmethod def _container_security_context_is_set( - self, containers: list[Container], container_name: str, cap_net_admin: bool, @@ -649,11 +706,15 @@ def _pod(self) -> str: return "-".join(self.model.unit.name.rsplit("/", 1)) def _on_remove(self, event: RemoveEvent) -> None: - """Deletes network attachment definitions. + """Deletes network attachment definitions and removes patch. Args: event: RemoveEvent """ + self.kubernetes.unpatch_statefulset( + name=self.model.app.name, + container_name=self.container_name, + ) for network_attachment_definition in self.network_attachment_definitions_func(): if self.kubernetes.network_attachment_definition_is_created( network_attachment_definition=network_attachment_definition