diff --git a/Dockerfile b/Dockerfile index f8c8fcc..3fa515e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,11 +11,11 @@ COPY assets / RUN apk add --no-cache haproxy bash python3 py3-pip py-yaml docker certbot openssl \ && ln -s /usr/bin/python3 /usr/bin/python \ - && pip3 install --upgrade pip -# \ -# && pip install -r requirements.txt \ -# && pytest -s tests/ \ -# && openssl dhparam -out /etc/haproxy/dhparam 2048 \ -# && openssl dhparam -out /etc/haproxy/dhparam-1024 1024 + && pip3 install --upgrade pip \ + && pip install -r requirements.txt \ + && openssl dhparam -out /etc/haproxy/dhparam 2048 \ + && openssl dhparam -out /etc/haproxy/dhparam-1024 1024 -# CMD ["/bin/bash", "-c", "/scripts/haproxy.sh" ] +# && pytest -s tests/ \ + +CMD ["/usr/bin/python", "-u", "/scripts/main.py" ] diff --git a/src/easymapping/__init__.py b/src/easymapping/__init__.py index fe4e145..95649a4 100644 --- a/src/easymapping/__init__.py +++ b/src/easymapping/__init__.py @@ -52,9 +52,8 @@ class HaproxyConfigGenerator: self.mapping.setdefault("letsencrypt", {"email": ""}) self.mapping["ssl_mode"] = self.mapping["ssl_mode"].lower() self.label = DockerLabelHandler(mapping['lookup_label'] if 'lookup_label' in mapping else "easyhaproxy") - self.ssl_cert_haproxy = ssl_cert_folder + "/haproxy" - self.ssl_cert_letsecncrypt = ssl_cert_folder + "/letsencrypt" self.letsencrypt_hosts = [] + self.serving_hosts = [] self.certs = {} def generate(self, container_metadata = None): @@ -134,6 +133,7 @@ class HaproxyConfigGenerator: for hostname in d[host_label].split(","): hostname = hostname.strip() + self.serving_hosts.append("%s:%s" % (hostname, port)) easymapping[port]["hosts"].setdefault(hostname, {}) easymapping[port]["hosts"][hostname].setdefault("containers", []) easymapping[port]["hosts"][hostname].setdefault("letsencrypt", False) @@ -166,11 +166,9 @@ class HaproxyConfigGenerator: # handle SSL ssl_label = self.label.create([definition, "sslcert"]) if self.label.has_label(ssl_label): - filename = "{}/{}.pem".format( - self.ssl_cert_haproxy, d[host_label] - ) + filename = "{}.pem".format(d[host_label]) easymapping[port]["ssl"] = True - self.certs[filename] = base64.b64decode(d[ssl_label]) + self.certs[filename] = base64.b64decode(d[ssl_label]).decode('ascii') if self.label.get_bool(self.label.create([definition, "ssl"])): easymapping[port]["ssl"] = True diff --git a/src/functions/__init__.py b/src/functions/__init__.py index e14e8b5..b0614a8 100644 --- a/src/functions/__init__.py +++ b/src/functions/__init__.py @@ -1,7 +1,6 @@ from datetime import datetime import subprocess import shlex -import numpy as np class Functions: @staticmethod @@ -19,7 +18,7 @@ class Functions: if message is None or message == "": return - if not isinstance(message, (list, tuple, np.ndarray)): + if not isinstance(message, (list, tuple)): message = [message] for line in message: @@ -27,7 +26,7 @@ class Functions: @staticmethod def run_bash(source, command, log_output=True, return_result=True): - if not isinstance(command, (list, tuple, np.ndarray)): + if not isinstance(command, (list, tuple)): command = shlex.split(command) try: diff --git a/src/main.py b/src/main.py index a06a3fa..c1e9b0c 100644 --- a/src/main.py +++ b/src/main.py @@ -3,6 +3,7 @@ from processor import ProcessorInterface import os import time from threading import Thread +from deepdiff import DeepDiff easyhaproxy_config = "/etc/haproxy/easyconfig.yml" haproxy_config = "/etc/haproxy/haproxy.cfg" @@ -17,9 +18,9 @@ def start(): os.makedirs(certs_letsencrypt, exist_ok=True) os.makedirs(certs_haproxy, exist_ok=True) - Functions.save(haproxy_config, processor_obj.get_haproxy_conf()) - for cert in processor_obj.get_certs(): - Functions.save(certs_haproxy, processor_obj.get_certs(cert)) + processor_obj.save_config(haproxy_config) + processor_obj.save_certs(certs_haproxy) + Functions.log('EASYHAPROXY', 'info', 'Found hosts: %s' % ", ".join(processor_obj.get_hosts())) # Needs to after save_config #configs = Functions.run_bash('HAPROXY', 'ls /etc/haproxy/conf.d/*.cfg', log_output=False) x = Thread(target=Functions.run_bash, args=("HAPROXY", "/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /var/run/haproxy.sock", True, False)) @@ -27,6 +28,22 @@ def start(): while True: time.sleep(10) + try: + old_parsed = processor_obj.get_parsed_object() + processor_obj.refresh() + if DeepDiff(old_parsed, processor_obj.get_parsed_object()) != {} or not x.is_alive(): + Functions.log('EASYHAPROXY', 'info', 'New configuration found. Reloading...') + processor_obj.save_config(haproxy_config) + processor_obj.save_certs(certs_haproxy) + Functions.log('EASYHAPROXY', 'info', 'Found hosts: %s' % ", ".join(processor_obj.get_hosts())) # Needs to after save_config + pid = "".join(Functions().run_bash("HAPROXY", "cat /run/haproxy.pid", log_output=False)) + x = Thread(target=Functions.run_bash, args=("HAPROXY", "/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -x /var/run/haproxy.sock -sf %s" % (pid), True, False)) + x.start() + except Exception as e: + Functions.log('EASYHAPROXY', 'error', "Err: %s" % (e)) + Functions.log('EASYHAPROXY', 'info', 'Heartbeat') + + def main(): @@ -38,12 +55,13 @@ def main(): Functions.log('INIT', 'info', "\___\__,_/__/\_, | |_||_\__,_| .__/_| \___/_\_\\_, |") Functions.log('INIT', 'info', " |__/ |_| |__/ ") - Functions.log('INIT', 'INFO', os.getenv("RELEASE_VERSION")) - Functions.log('INIT', 'INFO', "") - Functions.log('INIT', 'INFO', 'Environment:') + Functions.log('INIT', 'info', "Release: %s" % (os.getenv("RELEASE_VERSION"))) + Functions.log('INIT', 'info', 'Environment:') for name, value in os.environ.items(): if "HAPROXY" in name: - print("- {0}: {1}".format(name, value)) + Functions.log('INIT', 'info', "- {0}: {1}".format(name, value)) + + start() if __name__ == '__main__': main() \ No newline at end of file diff --git a/src/processor/__init__.py b/src/processor/__init__.py index db95bb0..386f05d 100644 --- a/src/processor/__init__.py +++ b/src/processor/__init__.py @@ -34,11 +34,7 @@ class ContainerEnv: class ProcessorInterface: def __init__(self, filename = None): self.filename = filename - self.letsencrypt_hosts = None - self.parsed_object = None - self.cfg = None - self.inspect_network() - self.parse() + self.refresh() @staticmethod def factory(mode): @@ -51,9 +47,17 @@ class ProcessorInterface: elif mode == "kubernetes": return Swarm() else: - Functions.log("FACTORY", "error", "Expected mode to be 'static', 'docker', 'swarm' or 'kubernetes'. I got " + mode) + Functions.log("FACTORY", "error", "Expected mode to be 'static', 'docker', 'swarm' or 'kubernetes'. I got '%s'" % (mode)) return None + def refresh(self): + self.letsencrypt_hosts = None + self.parsed_object = None + self.cfg = None + self.hosts = None + self.inspect_network() + self.parse() + def inspect_network(self): #Abstract pass @@ -65,6 +69,9 @@ class ProcessorInterface: def get_letsencrypt_hosts(self): return self.letsencrypt_hosts + def get_hosts(self): + return self.hosts + def get_parsed_object(self): return self.parsed_object @@ -77,8 +84,16 @@ class ProcessorInterface: def get_haproxy_conf(self): conf = self.cfg.generate(self.parsed_object) self.letsencrypt_hosts = self.cfg.letsencrypt_hosts + self.hosts = self.cfg.serving_hosts return conf + def save_config(self, filename): + Functions.save(filename, self.get_haproxy_conf()) + + def save_certs(self, path): + for cert in self.get_certs(): + Functions.save("{0}/{1}".format(path, cert), self.get_certs(cert)) + class Static(ProcessorInterface): @@ -125,13 +140,15 @@ class Swarm(ProcessorInterface): class Kubernetes(ProcessorInterface): - def inspect_network(self): + def __init__(self, filename = None): config.load_incluster_config() - - api_instance = client.CoreV1Api() - v1 = client.NetworkingV1Api() + self.api_instance = client.CoreV1Api() + self.v1 = client.NetworkingV1Api() + super().__init__() - ret = v1.list_ingress_for_all_namespaces(watch=False) + def inspect_network(self): + + ret = self.v1.list_ingress_for_all_namespaces(watch=False) self.parsed_object = {} for i in ret.items: @@ -152,7 +169,7 @@ class Kubernetes(ProcessorInterface): rule_data["easyhaproxy.%s_%s.localport" % (definition, port_number)] = port_number service_name = rule.http.paths[0].backend.service.name try: - api_response = api_instance.read_namespaced_service(service_name, i.metadata.namespace) + api_response = self.api_instance.read_namespaced_service(service_name, i.metadata.namespace) cluster_ip = api_response.spec.cluster_ip except ApiException as e: cluster_ip = None diff --git a/src/requirements.txt b/src/requirements.txt index 1709aa1..8327169 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -3,4 +3,4 @@ docker jinja2 pytest kubernetes -numpy \ No newline at end of file +deepdiff \ No newline at end of file diff --git a/src/templates/haproxy.cfg.j2 b/src/templates/haproxy.cfg.j2 index eda4b8a..a1842b1 100644 --- a/src/templates/haproxy.cfg.j2 +++ b/src/templates/haproxy.cfg.j2 @@ -78,3 +78,4 @@ backend srv_{{ host }} backend letsencrypt_backend mode http server certbot 127.0.0.1:2080 +