commit 1f5a1baf953ca169b4bab023709f2be9d4bf72de Author: Liam Date: Sat Sep 21 05:38:15 2024 +0100 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c90b6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Ignore python bytecode files +__pycache__/ + +# Ignore pip's log file +pip-log.txt + +# Ignore the .egg-info directory created by setuptools +*.egg-info/ + +# Ignore the .tox directory created by tox +.tox/ + +# Ignore the cache directory created by pip +pip-cache/ + +# Ignore the .pytest_cache directory created by pytest +.pytest_cache/ + +# Ignore the .mypy_cache directory created by mypy +.mypy_cache/ + +# Ignore the .venv directory created by venv +.venv/ + +# Ignore the .vscode directory created by vscode +.vscode/ + +# Ignore the .idea directory created by intellij +.idea/ + diff --git a/app.py b/app.py new file mode 100644 index 0000000..f64b345 --- /dev/null +++ b/app.py @@ -0,0 +1,25 @@ +import sys + +from cliff.app import App +from cliff.commandmanager import CommandManager + +from commands import addService +from commands import deleteService + +class TraefikHelper(App): + def __init__(self): + super().__init__( + description='Traefik Helper', + version='0.1', + command_manager=CommandManager('commands'), + deferred_help=False, + + ) +def main(argv=sys.argv[1:]): + app = TraefikHelper() + app.command_manager.add_command('add', addService.AddService) + app.command_manager.add_command('delete', deleteService.DeleteService) + return app.run(argv) + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) \ No newline at end of file diff --git a/commands/addService.py b/commands/addService.py new file mode 100644 index 0000000..cadd4b3 --- /dev/null +++ b/commands/addService.py @@ -0,0 +1,59 @@ +import logging +from urllib.parse import urlparse +from cliff.command import Command +from utils import utils +import os + +class AddService(Command): + "Adds a new service to Traefik" + + log = logging.getLogger(__name__) + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument('name', help='The name of the service') + parser.add_argument('domain', help='The domain for the service') + parser.add_argument('service', help='The service of the service') + return parser + + def take_action(self, parsed_args): + name = parsed_args.name + host = parsed_args.domain + service = parsed_args.service + + parsedUrl = urlparse(service) + protocol = parsedUrl.scheme + service = parsedUrl.hostname + port = parsedUrl.port + + if not port: + if protocol == "https": + port = 443 + elif protocol == "http": + port = 80 + + # The name of the TLS secret + # Examples: + # - fascinated.cc -> fascinated-cc + # - bob.fascinated.cc -> fascinated-cc + # - bob.local.fascinated.cc -> local-fascinated-cc + host_parts = host.split('.') + if len(host_parts) > 2: + tls_secret = '-'.join(host_parts[1:]) # Join everything from the second part onward + else: + tls_secret = '-'.join(host_parts) # Use the entire host for two-part domains + + # Define the path to save the YAML file + filePath = f'{utils.getServicesPath()}/{host}.yml' + + # Check if the service file already exists, if so exit + if os.path.exists(filePath): + print(f"Service \"{name}\" already exists ({filePath})") + return + + isInstalled = utils.checkIfKubectlInstalled() + if not isInstalled: + self.log.error("kubectl is not installed") + return 1 + + utils.createService(filePath, name, host, service, protocol, port, tls_secret) \ No newline at end of file diff --git a/commands/deleteService.py b/commands/deleteService.py new file mode 100644 index 0000000..df483d0 --- /dev/null +++ b/commands/deleteService.py @@ -0,0 +1,33 @@ +import logging +from cliff.command import Command +from utils import utils +import os + + +class DeleteService(Command): + "Deletes a service from Traefik" + + log = logging.getLogger(__name__) + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument('domain', help='The domain for the service') + return parser + + def take_action(self, parsed_args): + host = parsed_args.domain + + # Define the path to the YAML file + filePath = f'{utils.getServicesPath()}/{host}.yml' + + # Check if the service file exists, if not exit + if not os.path.exists(filePath): + print(f"Service \"{host}\" does not exist") + return + + isInstalled = utils.checkIfKubectlInstalled() + if not isInstalled: + self.log.error("kubectl is not installed") + return 1 + + utils.deleteService(filePath, host) \ No newline at end of file diff --git a/utils/utils.py b/utils/utils.py new file mode 100644 index 0000000..210ae82 --- /dev/null +++ b/utils/utils.py @@ -0,0 +1,117 @@ +import subprocess +import logging +import os + +log = logging.getLogger(__name__) + +baseServices = """kind: Service +apiVersion: v1 +metadata: + name: {name}-external + namespace: traefik +spec: + type: ExternalName + externalName: {service} + ports: + - name: {protocol} + port: {port} +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: {name}-external-ingress + namespace: traefik + annotations: + kubernetes.io/ingress.class: traefik-external +spec: + entryPoints: + - websecure + routes: + - match: Host(`{host}`) + kind: Rule + services: + - name: {name}-external + port: {port} + tls: + secretName: {tls_secret} +""" + +def getServicesPath(): + """ + Get the path to the services directory + """ + + # Get from the environment variable + if os.getenv("SERVICES_PATH"): + return os.getenv("SERVICES_PATH") + + # Fallback to my pc cause im lazy + return "C:/Users/Liam/Nextcloud/Kubernetes/traefik/external" + +def checkIfKubectlInstalled(): + """ + Check if kubectl is installed + """ + try: + subprocess.check_output(['kubectl', 'version']) + return True + except subprocess.CalledProcessError: + return False + +def runKubectl(command): + """ + Run kubectl with the given command + """ + args = command.split(' ') + result = subprocess.run(['kubectl'] + args, capture_output=True, text=True) + + if result.returncode != 0: + print(f"Error running kubectl: \n{result.stderr}") + else: + print(f"kubectl output: \n{result.stdout}") + +def createService(filePath, name, host, service, protocol, port, tls_secret): + """ + Create a new service and apply it using kubectl + """ + # Generate the YAML content for the service and ingress route + serviceContent = baseServices.format( + name=name, + host=host, + service=service, + protocol=protocol, + port=port, + tls_secret=tls_secret + ) + + # Write the YAML content to the file and check for errors + try: + with open(filePath, 'w') as f: + f.write(serviceContent) + print(f"Service definition written to {filePath}") + except Exception as e: + print(f"Error writing service file: {e}") + return + + # Apply the service using kubectl + runKubectl(f'apply -f {filePath}') + + log.info(f"Service {name} created") + log.info(f"Domain: http://{host}") + +def deleteService(filePath, name): + """ + Delete a service and apply it using kubectl + """ + # Delete the service using kubectl + runKubectl(f'delete -f {filePath}') + + # Remove the service file + try: + os.remove(filePath) + print(f"Service definition deleted from {filePath}") + except Exception as e: + print(f"Error deleting service file: {e}") + return + + log.info(f"Service {name} deleted") \ No newline at end of file