first commit
This commit is contained in:
commit
1f5a1baf95
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
@ -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/
|
||||||
|
|
25
app.py
Normal file
25
app.py
Normal file
@ -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:]))
|
59
commands/addService.py
Normal file
59
commands/addService.py
Normal file
@ -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)
|
33
commands/deleteService.py
Normal file
33
commands/deleteService.py
Normal file
@ -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)
|
117
utils/utils.py
Normal file
117
utils/utils.py
Normal file
@ -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")
|
Loading…
Reference in New Issue
Block a user