class DockerController(Controller, ConfigCaller): def __init__(self, docker_host): super().__init__("docker") ConfigCaller.__init__(self) self.__client = DockerClient(base_url=docker_host) def _get_controller_instances(self): return self.__client.containers.list( filters={"label": "bunkerweb.AUTOCONF"}) def _to_instances(self, controller_instance): instance = {} instance["name"] = controller_instance.name instance["hostname"] = controller_instance.name instance[ "health"] = controller_instance.status == "running" and controller_instance.attrs[ "State"]["Health"]["Status"] == "healthy" instance["env"] = {} for env in controller_instance.attrs["Config"]["Env"]: variable = env.split("=")[0] value = env.replace(variable + "=", "", 1) if self._is_setting(variable): instance["env"][variable] = value return [instance] def _get_controller_services(self): return self.__client.containers.list( filters={"label": "bunkerweb.SERVER_NAME"}) def _to_services(self, controller_service): service = {} for variable, value in controller_service.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.containers.list( filters={"label": "bunkerweb.AUTOCONF"}): for env in instance.attrs["Config"]["Env"]: variable = env.split("=")[0] value = env.replace(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(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] = {} # get site configs from labels for container in self.__client.containers.list( filters={"label": "bunkerweb.SERVER_NAME"}): # extract server_name server_name = "" for variable, value in container.labels.items(): if not variable.startswith("bunkerweb."): continue real_variable = variable.replace("bunkerweb.", "", 1) if real_variable == "SERVER_NAME": server_name = value.split(" ")[0] break # extract configs if server_name == "": continue for variable, value in container.labels.items(): if not variable.startswith("bunkerweb."): continue real_variable = variable.replace("bunkerweb.", "", 1) result = search( r"^CUSTOM_CONF_(SERVER_HTTP|MODSEC|MODSEC_CRS)_(.+)$", real_variable) if result is None: continue cfg_type = result.group(1).lower().replace("_", "-") cfg_name = result.group(2) configs[cfg_type][server_name + "/" + cfg_name] = value return configs def apply_config(self): self._config.stop_scheduler() ret = self._config.apply(self._instances, self._services, configs=self._configs) self._config.start_scheduler() return ret def process_events(self): for event in self.__client.events(decode=True, filters={"type": "container"}): 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): continue log("DOCKER-CONTROLLER", "ℹ️", "Catched docker event, deploying new configuration ...") try: ret = self.apply_config() if not ret: log("DOCKER-CONTROLLER", "❌", "Error while deploying new configuration") else: log("DOCKER-CONTROLLER", "ℹ️", "Successfully deployed new configuration 🚀") except: log("DOCKER-CONTROLLER", "❌", "Exception while deploying new configuration :") print(traceback.format_exc())
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() def _get_controller_instances(self): return self.__client.services.list( filters={"label": "bunkerweb.AUTOCONF"}) 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(variable + "=", "", 1) if self._is_setting(variable): instance_env[variable] = value for task in controller_instance.tasks(): instance = {} instance["name"] = task["ID"] instance["hostname"] = 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.AUTOCONF"}): for env in instance.attrs["Spec"]["TaskTemplate"]["ContainerSpec"][ "Env"]: variable = env.split("=")[0] value = env.replace(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(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: log( "SWARM-CONTROLLER", "⚠️", "Ignoring unsupported CONFIG_TYPE " + config_type + " for Config " + config_name) continue config_site = "" if "bunkerweb.CONFIG_SITE" in config.attrs["Spec"]["Labels"]: config_site = config.attrs["Spec"]["Labels"][ "bunkerweb.CONFIG_SITE"] + "/" configs[config_type][config_site + config_name] = b64decode( config.attrs["Spec"]["Data"]) return configs def apply_config(self): self._config.stop_scheduler() ret = self._config.apply(self._instances, self._services, configs=self._configs) self._config.start_scheduler() 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 log("SWARM-CONTROLLER", "ℹ️", "Catched Swarm event, deploying new configuration ...") try: ret = self.apply_config() if not ret: log("SWARM-CONTROLLER", "❌", "Error while deploying new configuration ...") else: log("SWARM-CONTROLLER", "ℹ️", "Successfully deployed new configuration 🚀") except: log("SWARM-CONTROLLER", "❌", "Exception while deploying new configuration :") print(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()