From 7b83facc2133c136c2e6117f4c7e56b824058c81 Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 15 Jan 2024 22:59:08 +0000 Subject: [PATCH] much cleanup --- src/command/command.py | 12 +++ src/command/commandManager.py | 27 +++++ src/command/impl/addCommand.py | 31 ++++++ src/command/impl/listCommand.py | 19 ++++ src/command/impl/removeCommand.py | 29 +++++ src/manage.py | 170 +++--------------------------- src/traefik/traefikConfig.py | 96 +++++++++++++++++ src/utils/dockerUtils.py | 18 ++++ 8 files changed, 247 insertions(+), 155 deletions(-) create mode 100644 src/command/command.py create mode 100644 src/command/commandManager.py create mode 100644 src/command/impl/addCommand.py create mode 100644 src/command/impl/listCommand.py create mode 100644 src/command/impl/removeCommand.py create mode 100644 src/traefik/traefikConfig.py create mode 100644 src/utils/dockerUtils.py diff --git a/src/command/command.py b/src/command/command.py new file mode 100644 index 0000000..cde2c6d --- /dev/null +++ b/src/command/command.py @@ -0,0 +1,12 @@ +class Command: + def __init__(self, name:str, description:str, usage:str): + self.name = name + self.description = description + self.usage = usage + + async def execute(self, traefikConfig, args): + pass + + def printUsage(self): + print("Usage: %s" % self.usage) + pass \ No newline at end of file diff --git a/src/command/commandManager.py b/src/command/commandManager.py new file mode 100644 index 0000000..74e2e48 --- /dev/null +++ b/src/command/commandManager.py @@ -0,0 +1,27 @@ +from command.impl.addCommand import AddCommand +from command.impl.listCommand import ListCommand +from command.impl.removeCommand import RemoveCommand + +class CommandManager: + + commands = [] + + def __init__(self): + self.addCommand(AddCommand()) + self.addCommand(RemoveCommand()) + self.addCommand(ListCommand()) + pass + + def addCommand(self, command): + self.commands.append(command) + pass + + def getCommand(self, name): + for command in self.commands: + if command.name == name: + return command + return None + + def commandExists(self, name): + return self.getCommand(name) != None + \ No newline at end of file diff --git a/src/command/impl/addCommand.py b/src/command/impl/addCommand.py new file mode 100644 index 0000000..7da8662 --- /dev/null +++ b/src/command/impl/addCommand.py @@ -0,0 +1,31 @@ +from command.command import Command +from traefik.traefikConfig import TraefikConfig +from utils.dockerUtils import restartTraefik + +class AddCommand(Command): + def __init__(self): + super().__init__("add", "Add a domain", "add ") + + def execute(self, traefikConfig:TraefikConfig, args): + if len(args) < 3: + self.printUsage() + return + + name = args[0] + domain = args[1] + serviceHost = args[2] + + if traefikConfig.hasRouter(name): + print(f"Router \"{name}\" already exists") + return + + print(f"Adding \"{domain}\" -> \"{serviceHost}\"") + + traefikConfig.addRouter(name, domain, serviceHost) + traefikConfig.addService(name, serviceHost) + + traefikConfig.save() + + restartTraefik() + + print("Done!") \ No newline at end of file diff --git a/src/command/impl/listCommand.py b/src/command/impl/listCommand.py new file mode 100644 index 0000000..8959385 --- /dev/null +++ b/src/command/impl/listCommand.py @@ -0,0 +1,19 @@ +from colorama import Fore +from command.command import Command +from traefik.traefikConfig import TraefikConfig + +class ListCommand(Command): + def __init__(self): + super().__init__("list", "List all services", "list") + + def execute(self, traefikConfig:TraefikConfig, args): + print("Listing all services:") + + domains = traefikConfig.getAll() + + # Print domains + for name, domain in domains.items(): + print(f" - {Fore.CYAN}[{name}] {Fore.GREEN}http://{domain['domain']} {Fore.RESET}-> {Fore.YELLOW}{domain['serviceHost']}{Fore.RESET}") + + print("") + print("Total: %s" % len(domains)) \ No newline at end of file diff --git a/src/command/impl/removeCommand.py b/src/command/impl/removeCommand.py new file mode 100644 index 0000000..38638d4 --- /dev/null +++ b/src/command/impl/removeCommand.py @@ -0,0 +1,29 @@ +from colorama import Fore +from command.command import Command +from traefik.traefikConfig import TraefikConfig +from utils.dockerUtils import restartTraefik + +class RemoveCommand(Command): + def __init__(self): + super().__init__("remove", "Remove a domain", "remove ") + + def execute(self, traefikConfig:TraefikConfig, args): + if len(args) < 0: + self.printUsage() + return + + name = args[0] + + if not traefikConfig.hasRouter(name): + print(f"Router \"{name}\" does not exist") + return + + print(f"Removing \"{name}\"") + + traefikConfig.removeRouter(name) + + traefikConfig.save() + + restartTraefik() + + print(f"Removed \"{name}\"") \ No newline at end of file diff --git a/src/manage.py b/src/manage.py index 304c1f9..ec8f78c 100644 --- a/src/manage.py +++ b/src/manage.py @@ -1,171 +1,31 @@ import sys -import subprocess -import yaml import os -from colorama import Fore + +from command.commandManager import CommandManager +from traefik.traefikConfig import TraefikConfig # Variables configFile = "./config.yml" -containerName = "traefik" # Are we running in a Docker container? if os.environ.get("CONFIG_FILE"): configFile = os.environ.get("CONFIG_FILE") -if os.environ.get("CONTAINER_NAME"): - containerName = os.environ.get("CONTAINER_NAME") -# DO NOT TOUCH -commands = ["add", "remove", "list", "update"] +traefikConfig = TraefikConfig(configFile) +if traefikConfig.isValid() == False: + print("Invalid traefik config file, please check your config.yml file") + exit(1) + command = len(sys.argv) > 1 and sys.argv[1] +commandManager = CommandManager() -if command not in commands: - print("") - print("Usage: manage.py [command]") +if not commandManager.commandExists(command): + print("Usage: manage [command]") print("") print("Commands:") - print(" - add [name] [domain] [service host]") - print(" - remove [name]") - print(" - update [name] [new service host]") - print(" - list") + for command in commandManager.commands: + print(f" - {command.usage}") exit() -with open(configFile, "r") as config: - configYml = yaml.safe_load(config) - -http = configYml["http"] -routers = http["routers"] -services = http["services"] - -def restartTraefik(): - print("Restarting Traefik, please wait this can take a while...") - - # Restart Traefik in the base directory - subprocess.run(["docker", "restart", "traefik"]) - - print(f"{Fore.GREEN}Done!{Fore.RESET}") - -def addDomain(name, domain, serviceHost): - # Check if name already exists - if name in routers: - print(f"Name \"{Fore.RED}{name}{Fore.RESET}\" already exists") - exit() - - print(f"Adding domain \"{Fore.CYAN}{name}{Fore.RESET}\" -> \"{Fore.YELLOW}{serviceHost}{Fore.RESET}\"") - print(f"Domain: {Fore.GREEN}http://{domain}{Fore.RESET}") - - # Add router - routers[name] = { - "entryPoints": ["https"], - "rule": "Host(`%s`)" % domain, - "middlewares": ["default-headers", "https-redirectscheme"], - "tls": {}, - "service": name - } - - # Add service - services[name] = { - "loadBalancer": { - "servers": [ - { - "url": serviceHost - } - ] - } - } - - # Write to file - with open(configFile, "w") as config: - yaml.dump(configYml, config) - - # Restart Traefik - restartTraefik() - -def removeDomain(name): - # Check if name exists - if name not in routers: - print(f"Name \"{Fore.RED}{name}{Fore.RESET}\" does not exist") - exit() - - print(f"Removing domain \"{Fore.CYAN}{name}{Fore.RESET}\"") - - # Remove router - del routers[name] - - # Remove service - del services[name] - - # Write to file - with open(configFile, "w") as config: - yaml.dump(configYml, config) - - # Restart Traefik - restartTraefik() - -def listDomains(): - print("Listing domains:") - - # name and domain -> service host - domains = {} - - # Loop through routers - for name, router in routers.items(): - # Get domain - domain = router["rule"].split("`")[1] - - # Get service host - serviceHost = services[name]["loadBalancer"]["servers"][0]["url"] - - # Add to domains - domains[name] = { - "domain": domain, - "serviceHost": serviceHost - } - - # Print domains - for name, domain in domains.items(): - print(f" - {Fore.CYAN}[{name}] {Fore.GREEN}http://{domain['domain']} {Fore.RESET}-> {Fore.YELLOW}{domain['serviceHost']}{Fore.RESET}") - - print("") - print("Total: %s" % len(domains)) - -def updateDomain(name, serviceHost): - # Check if name exists - if name not in routers: - print("Name \"%s\" does not exist" % name) - exit() - - print(f"Updating domain \"{Fore.CYAN}{name}{Fore.RESET}\" -> \"{Fore.YELLOW}{serviceHost}{Fore.RESET}\"") - - # Update service - services[name] = { - "loadBalancer": { - "servers": [ - { - "url": serviceHost - } - ] - } - } - - # Write to file - with open(configFile, "w") as config: - yaml.dump(configYml, config) - - # Restart Traefik - restartTraefik() - -match command: - case "add": - name = sys.argv[2] - domain = sys.argv[3] - serviceHost = sys.argv[4] - addDomain(name, domain, serviceHost) - case "remove": - name = sys.argv[2] - removeDomain(name) - case "update": - name = sys.argv[2] - serviceHost = sys.argv[3] - updateDomain(name, serviceHost) - case "list": - listDomains() \ No newline at end of file +args = sys.argv[2:] +commandManager.getCommand(command).execute(traefikConfig, args) diff --git a/src/traefik/traefikConfig.py b/src/traefik/traefikConfig.py new file mode 100644 index 0000000..b0f9e8a --- /dev/null +++ b/src/traefik/traefikConfig.py @@ -0,0 +1,96 @@ +import yaml + + +class TraefikConfig: + + def __init__(self, configFile) -> None: + self.configFile = configFile + with open(configFile, "r") as config: + self.configYml = yaml.safe_load(config) + + def isValid(self) -> bool: + return "http" in self.configYml and "routers" in self.configYml["http"] and "services" in self.configYml["http"] + + def getRouter(self, name): + return self.configYml["http"]["routers"][name] + + def getService(self, name): + return self.configYml["http"]["services"][name] + + def addRouter(self, name, domain, serviceHost): + # Add router + self.configYml["http"]["routers"][name] = { + "entryPoints": ["https"], + "rule": "Host(`%s`)" % domain, + "middlewares": ["default-headers", "https-redirectscheme"], + "tls": {}, + "service": name + } + + # Add service + self.configYml["http"]["services"][name] = { + "loadBalancer": { + "servers": [ + { + "url": serviceHost + } + ] + } + } + + def removeRouter(self, name): + # Remove router + del self.configYml["http"]["routers"][name] + + # Remove service + del self.configYml["http"]["services"][name] + + def hasRouter(self, name): + return name in self.configYml["http"]["routers"] + + def addService(self, name, serviceHost): + # Add service + self.configYml["http"]["services"][name] = { + "loadBalancer": { + "servers": [ + { + "url": serviceHost + } + ] + } + } + + def hasService(self, name): + return name in self.configYml["http"]["services"] + + def getRouters(self): + return self.configYml["http"]["routers"] + + def getServices(self): + return self.configYml["http"]["services"] + + def getAll(self): + domains = {} + + routers = self.getRouters() + services = self.getServices() + + # Loop through routers + for name, router in routers.items(): + # Get domain + domain = router["rule"].split("`")[1] + + # Get service host + serviceHost = services[name]["loadBalancer"]["servers"][0]["url"] + + # Add to domains + domains[name] = { + "domain": domain, + "serviceHost": serviceHost + } + + return domains + + def save(self): + with open(self.configFile, "w") as config: + yaml.dump(self.configYml, config) \ No newline at end of file diff --git a/src/utils/dockerUtils.py b/src/utils/dockerUtils.py new file mode 100644 index 0000000..eba1874 --- /dev/null +++ b/src/utils/dockerUtils.py @@ -0,0 +1,18 @@ +import subprocess +import os + +containerName = "traefik" +if os.environ.get("CONTAINER_NAME"): + containerName = os.environ.get("CONTAINER_NAME") + +def restartTraefik(): + # Check if we're in Windows, if so, don't restart Traefik + if os.name == "nt": + print("Restarting Traefik is not supported on Windows.") + return + print("Restarting Traefik, please wait this can take a while...") + + # Restart Traefik in the base directory + subprocess.run(["docker", "restart", containerName]) + + print("Done!")