bunkerweb/src/autoconf/DockerController.py

124 lines
5.2 KiB
Python
Raw Normal View History

#!/usr/bin/python3
from typing import Any, Dict, List
2022-06-03 15:24:14 +00:00
from docker import DockerClient
2023-03-13 13:30:25 +00:00
from re import compile as re_compile
from traceback import format_exc
2022-06-03 15:24:14 +00:00
from docker.models.containers import Container
2022-06-03 15:24:14 +00:00
from Controller import Controller
class DockerController(Controller):
def __init__(self, docker_host):
super().__init__("docker")
2022-06-03 15:24:14 +00:00
self.__client = DockerClient(base_url=docker_host)
2023-10-03 10:01:24 +00:00
self.__custom_confs_rx = re_compile(r"^bunkerweb.CUSTOM_CONF_(SERVER_HTTP|MODSEC_CRS|MODSEC)_(.+)$")
def _get_controller_instances(self) -> List[Container]:
return self.__client.containers.list(filters={"label": "bunkerweb.INSTANCE"})
2022-06-03 15:24:14 +00:00
def _get_controller_services(self) -> List[Container]:
2023-03-13 13:30:25 +00:00
return self.__client.containers.list(filters={"label": "bunkerweb.SERVER_NAME"})
def _to_instances(self, controller_instance) -> List[dict]:
2022-06-03 15:24:14 +00:00
instance = {}
instance["name"] = controller_instance.name
instance["hostname"] = controller_instance.name
2023-10-03 10:01:24 +00:00
instance["health"] = controller_instance.status == "running" and controller_instance.attrs["State"]["Health"]["Status"] == "healthy"
2022-06-03 15:24:14 +00:00
instance["env"] = {}
for env in controller_instance.attrs["Config"]["Env"]:
2022-06-03 15:24:14 +00:00
variable = env.split("=")[0]
value = env.replace(f"{variable}=", "", 1)
if self._is_setting(variable):
instance["env"][variable] = value
2022-06-03 15:24:14 +00:00
return [instance]
def _to_services(self, controller_service) -> List[dict]:
2022-06-03 15:24:14 +00:00
service = {}
for variable, value in controller_service.labels.items():
if not variable.startswith("bunkerweb."):
2022-06-03 15:24:14 +00:00
continue
real_variable = variable.replace("bunkerweb.", "", 1)
if not self._is_setting_context(real_variable, "multisite"):
continue
service[real_variable] = value
2022-06-03 15:24:14 +00:00
return [service]
def _get_static_services(self) -> List[dict]:
services = []
variables = {}
2023-10-03 10:01:24 +00:00
for instance in self.__client.containers.list(filters={"label": "bunkerweb.INSTANCE"}):
2023-03-13 13:30:25 +00:00
if not instance.attrs or not instance.attrs.get("Config", {}).get("Env"):
continue
for env in instance.attrs["Config"]["Env"]:
variable = env.split("=")[0]
value = env.replace(f"{variable}=", "", 1)
variables[variable] = value
2023-03-13 13:30:25 +00:00
if "SERVER_NAME" in variables and variables["SERVER_NAME"].strip():
for server_name in variables["SERVER_NAME"].strip().split(" "):
service = {"SERVER_NAME": server_name}
for variable, value in variables.items():
prefix = variable.split("_")[0]
real_variable = variable.replace(f"{prefix}_", "", 1)
2023-10-03 10:01:24 +00:00
if prefix == server_name and self._is_setting_context(real_variable, "multisite"):
2023-03-13 13:30:25 +00:00
service[real_variable] = value
services.append(service)
return services
def get_configs(self) -> Dict[str, Dict[str, Any]]:
2023-03-13 13:30:25 +00:00
configs = {config_type: {} for config_type in self._supported_config_types}
# get site configs from labels
2023-10-03 10:01:24 +00:00
for container in self.__client.containers.list(filters={"label": "bunkerweb.SERVER_NAME"}):
2023-03-13 13:30:25 +00:00
labels = container.labels # type: ignore (labels is inside a container)
if isinstance(labels, list):
labels = {label: "" for label in labels}
# extract server_name
2023-03-13 13:30:25 +00:00
server_name = labels.get("bunkerweb.SERVER_NAME", "").split(" ")[0]
# extract configs
2023-03-13 13:30:25 +00:00
if not server_name:
continue
2023-03-13 13:30:25 +00:00
for variable, value in labels.items():
if not variable.startswith("bunkerweb."):
continue
2023-03-13 13:30:25 +00:00
result = self.__custom_confs_rx.search(variable)
if result is None:
continue
2023-10-03 10:01:24 +00:00
configs[result.group(1).lower().replace("_", "-")][f"{server_name}/{result.group(2)}"] = value
return configs
2022-06-03 15:24:14 +00:00
def apply_config(self) -> bool:
2023-08-31 14:50:16 +00:00
return self.apply(
self._instances,
self._services,
configs=self._configs,
first=not self._loaded,
)
2022-06-03 15:24:14 +00:00
def process_events(self):
self._set_autoconf_load_db()
2022-11-14 16:31:21 +00:00
for _ in self.__client.events(decode=True, filters={"type": "container"}):
try:
2023-10-03 11:05:26 +00:00
self._update_settings()
2022-11-14 16:31:21 +00:00
self._instances = self.get_instances()
self._services = self.get_services()
self._configs = self.get_configs()
2023-10-03 10:01:24 +00:00
if not self.update_needed(self._instances, self._services, configs=self._configs):
2022-11-14 16:31:21 +00:00
continue
2023-10-03 10:01:24 +00:00
self._logger.info("Caught Docker event, deploying new configuration ...")
2023-03-13 13:30:25 +00:00
if not self.apply_config():
self._logger.error("Error while deploying new configuration")
else:
self._logger.info(
"Successfully deployed new configuration 🚀",
)
self._set_autoconf_load_db()
except:
2023-10-03 10:01:24 +00:00
self._logger.error(f"Exception while processing events :\n{format_exc()}")