bunkerweb/autoconf/SwarmController.py
2022-11-04 18:14:44 +01:00

155 lines
6.1 KiB
Python

from os import getenv
from traceback import format_exc
from threading import Thread, Lock
from docker import DockerClient
from base64 import b64decode
from Controller import Controller
from ConfigCaller import ConfigCaller
from logger import setup_logger
class SwarmController(Controller, ConfigCaller):
def __init__(self, docker_host):
super().__init__("swarm")
ConfigCaller.__init__(self)
self.__client = DockerClient(base_url=docker_host)
self.__internal_lock = Lock()
self.__logger = setup_logger("Swarm-controller", getenv("LOG_LEVEL", "INFO"))
def _get_controller_instances(self):
return self.__client.services.list(filters={"label": "bunkerweb.INSTANCE"})
def _to_instances(self, controller_instance):
instances = []
instance_env = {}
for env in controller_instance.attrs["Spec"]["TaskTemplate"]["ContainerSpec"][
"Env"
]:
variable = env.split("=")[0]
value = env.replace(f"{variable}=", "", 1)
if self._is_setting(variable):
instance_env[variable] = value
for task in controller_instance.tasks():
instance = {}
instance["name"] = task["ID"]
instance[
"hostname"
] = f"{controller_instance.name}.{task['NodeID']}.{task['ID']}"
instance["health"] = task["Status"]["State"] == "running"
instance["env"] = instance_env
instances.append(instance)
return instances
def _get_controller_services(self):
return self.__client.services.list(filters={"label": "bunkerweb.SERVER_NAME"})
def _to_services(self, controller_service):
service = {}
for variable, value in controller_service.attrs["Spec"]["Labels"].items():
if not variable.startswith("bunkerweb."):
continue
real_variable = variable.replace("bunkerweb.", "", 1)
if not self._is_multisite_setting(real_variable):
continue
service[real_variable] = value
return [service]
def _get_static_services(self):
services = []
variables = {}
for instance in self.__client.services.list(
filters={"label": "bunkerweb.INSTANCE"}
):
for env in instance.attrs["Spec"]["TaskTemplate"]["ContainerSpec"]["Env"]:
variable = env.split("=")[0]
value = env.replace(f"{variable}=", "", 1)
variables[variable] = value
server_names = []
if "SERVER_NAME" in variables and variables["SERVER_NAME"] != "":
server_names = variables["SERVER_NAME"].split(" ")
for server_name in server_names:
service = {}
service["SERVER_NAME"] = server_name
for variable, value in variables.items():
prefix = variable.split("_")[0]
real_variable = variable.replace(f"{prefix}_", "", 1)
if prefix == server_name and self._is_multisite_setting(real_variable):
service[real_variable] = value
services.append(service)
return services
def get_configs(self):
configs = {}
for config_type in self._supported_config_types:
configs[config_type] = {}
for config in self.__client.configs.list(
filters={"label": "bunkerweb.CONFIG_TYPE"}
):
config_type = config.attrs["Spec"]["Labels"]["bunkerweb.CONFIG_TYPE"]
config_name = config.name
if config_type not in self._supported_config_types:
self.__logger.warning(
f"Ignoring unsupported CONFIG_TYPE {config_type} for Config {config_name}",
)
continue
config_site = ""
if "bunkerweb.CONFIG_SITE" in config.attrs["Spec"]["Labels"]:
config_site = (
f"{config.attrs['Spec']['Labels']['bunkerweb.CONFIG_SITE']}/"
)
configs[config_type][f"{config_site}{config_name}"] = b64decode(
config.attrs["Spec"]["Data"]
)
return configs
def apply_config(self):
ret = self._config.apply(self._instances, self._services, configs=self._configs)
return ret
def __event(self, event_type):
for event in self.__client.events(decode=True, filters={"type": event_type}):
self.__internal_lock.acquire()
self._instances = self.get_instances()
self._services = self.get_services()
self._configs = self.get_configs()
if not self._config.update_needed(
self._instances, self._services, configs=self._configs
):
self.__internal_lock.release()
continue
self.__logger.info(
"Catched Swarm event, deploying new configuration ...",
)
try:
ret = self.apply_config()
if not ret:
self.__logger.error(
"Error while deploying new configuration ...",
)
else:
self.__logger.info(
"Successfully deployed new configuration 🚀",
)
if not self._config._db.is_autoconf_loaded():
ret = self._config._db.set_autoconf_load(True)
if ret:
self.__logger.error(
f"Can't set autoconf loaded metadata to true in database: {ret}",
)
except:
self.__logger.error(
f"Exception while deploying new configuration :\n{format_exc()}",
)
self.__internal_lock.release()
def process_events(self):
event_types = ["service", "config"]
threads = []
for event_type in event_types:
threads.append(Thread(target=self.__event, args=(event_type,)))
for thread in threads:
thread.start()
for thread in threads:
thread.join()