Refactoring to Python
This commit is contained in:
parent
cd5035602a
commit
7d959f892a
43 changed files with 454 additions and 34 deletions
|
|
@ -6,4 +6,4 @@ tasks:
|
|||
- command: |
|
||||
virtualenv -p /usr/bin/python3 venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
pip install -r src/requirements.txt
|
||||
|
|
|
|||
18
Dockerfile
18
Dockerfile
|
|
@ -6,18 +6,16 @@ ENV RELEASE_VERSION=$RELEASE_VERSION_ARG
|
|||
|
||||
WORKDIR /scripts
|
||||
|
||||
COPY requirements.txt /scripts
|
||||
COPY templates /scripts/templates/
|
||||
COPY easymapping /scripts/easymapping/
|
||||
COPY tests/ /scripts/tests/
|
||||
COPY src/ /scripts/
|
||||
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 \
|
||||
# && pytest -s tests/ \
|
||||
# && openssl dhparam -out /etc/haproxy/dhparam 2048 \
|
||||
# && openssl dhparam -out /etc/haproxy/dhparam-1024 1024
|
||||
|
||||
CMD ["/bin/bash", "-c", "/scripts/haproxy.sh" ]
|
||||
# CMD ["/bin/bash", "-c", "/scripts/haproxy.sh" ]
|
||||
|
|
|
|||
|
|
@ -170,6 +170,10 @@ services:
|
|||
...
|
||||
```
|
||||
|
||||
```bash
|
||||
docker stack deploy --compose-file docker-compose.yml mystack
|
||||
```
|
||||
|
||||
### Single Definition
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
_
|
||||
___ __ _ ____ _ ___| |_ __ _ _ __ _ _ _____ ___ _
|
||||
/ -_) _` (_-< || |___| ' \/ _` | '_ \ '_/ _ \ \ / || |
|
||||
\___\__,_/__/\_, | |_||_\__,_| .__/_| \___/_\_\\_, |
|
||||
|__/ |_| |__/
|
||||
|
|
@ -4,6 +4,11 @@ import json
|
|||
|
||||
# https://github.com/kubernetes-client/python/tree/master/kubernetes/docs
|
||||
|
||||
|
||||
# Fix error:
|
||||
# raise MaxRetryError(_pool, url, error or ResponseError(cause))
|
||||
# urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='10.152.183.1', port=443): Max retries exceeded with url: /api/v1/namespaces/parking/services/parking-valida-me (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:997)')))
|
||||
|
||||
def main():
|
||||
config.load_incluster_config()
|
||||
|
||||
|
|
|
|||
64
examples/swarm/docker-compose.yml
Normal file
64
examples/swarm/docker-compose.yml
Normal file
File diff suppressed because one or more lines are too long
79
resource-temp.yml
Normal file
79
resource-temp.yml
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: parking
|
||||
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: parking-valida-me
|
||||
namespace: parking
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: easyhaproxy
|
||||
spec:
|
||||
rules:
|
||||
- host: valida.me
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: parking-valida-me
|
||||
servicePort: 80
|
||||
- host: www.valida.me
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: parking-valida-me
|
||||
servicePort: 80
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: parking-valida-me
|
||||
namespace: parking
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8080
|
||||
name: http
|
||||
selector:
|
||||
app: parking-valida-me
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: parking-valida-me
|
||||
namespace: parking
|
||||
spec:
|
||||
replicas: 1
|
||||
revisionHistoryLimit: 10
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
type: RollingUpdate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: parking-valida-me
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: parking-valida-me
|
||||
spec:
|
||||
containers:
|
||||
- name: parking-valida-me
|
||||
image: byjg/static-httpserver
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
resources:
|
||||
limits:
|
||||
cpu: '0.05'
|
||||
memory: '20Mi'
|
||||
requests:
|
||||
cpu: '0.05'
|
||||
memory: '20Mi'
|
||||
env:
|
||||
- name: TITLE
|
||||
value: "valida.me"
|
||||
5
src/banner.txt
Normal file
5
src/banner.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
" _ "
|
||||
" ___ __ _ ____ _ ___| |_ __ _ _ __ _ _ _____ ___ _ "
|
||||
"/ -_) _` (_-< || |___| ' \/ _` | '_ \ '_/ _ \ \ / || |"
|
||||
"\___\__,_/__/\_, | |_||_\__,_| .__/_| \___/_\_\\_, |"
|
||||
" |__/ |_| |__/ "
|
||||
|
|
@ -55,15 +55,13 @@ class HaproxyConfigGenerator:
|
|||
self.ssl_cert_haproxy = ssl_cert_folder + "/haproxy"
|
||||
self.ssl_cert_letsecncrypt = ssl_cert_folder + "/letsencrypt"
|
||||
self.letsencrypt_hosts = []
|
||||
os.makedirs(self.ssl_cert_haproxy, exist_ok=True)
|
||||
os.makedirs(self.ssl_cert_letsecncrypt, exist_ok=True)
|
||||
self.certs = {}
|
||||
|
||||
def generate(self, line_list = []):
|
||||
def generate(self, container_metadata = None):
|
||||
self.mapping.setdefault("easymapping", [])
|
||||
|
||||
# static?
|
||||
if len(line_list) > 0:
|
||||
self.mapping["easymapping"] = self.parse(line_list)
|
||||
if container_metadata is not None:
|
||||
self.mapping["easymapping"] = self.parse(container_metadata)
|
||||
|
||||
file_loader = FileSystemLoader('templates')
|
||||
env = Environment(loader=file_loader)
|
||||
|
|
@ -74,15 +72,11 @@ class HaproxyConfigGenerator:
|
|||
return template.render(data=self.mapping)
|
||||
|
||||
|
||||
def parse(self, line_list):
|
||||
def parse(self, container_metadata):
|
||||
easymapping = dict()
|
||||
|
||||
for line in line_list:
|
||||
line = line.strip()
|
||||
i = line.find("=")
|
||||
container = line[:i]
|
||||
json_str = line[i+1:]
|
||||
d = json.loads(json_str)
|
||||
for container in container_metadata:
|
||||
d = container_metadata[container]
|
||||
|
||||
# Extract the definitions dynamically
|
||||
definitions = {}
|
||||
|
|
@ -176,10 +170,8 @@ class HaproxyConfigGenerator:
|
|||
self.ssl_cert_haproxy, d[host_label]
|
||||
)
|
||||
easymapping[port]["ssl"] = True
|
||||
with open(filename, 'wb') as file:
|
||||
file.write(
|
||||
base64.b64decode(d[ssl_label])
|
||||
)
|
||||
self.certs[filename] = base64.b64decode(d[ssl_label])
|
||||
|
||||
if self.label.get_bool(self.label.create([definition, "ssl"])):
|
||||
easymapping[port]["ssl"] = True
|
||||
|
||||
58
src/functions/__init__.py
Normal file
58
src/functions/__init__.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
from datetime import datetime
|
||||
import subprocess
|
||||
import shlex
|
||||
import numpy as np
|
||||
|
||||
class Functions:
|
||||
@staticmethod
|
||||
def load(filename):
|
||||
with open(filename, 'r') as content_file:
|
||||
return content_file.read()
|
||||
|
||||
@staticmethod
|
||||
def save(filename, contents):
|
||||
with open(filename, 'w') as file:
|
||||
file.write(contents)
|
||||
|
||||
@staticmethod
|
||||
def log(source, level, message):
|
||||
if message is None or message == "":
|
||||
return
|
||||
|
||||
if not isinstance(message, (list, tuple, np.ndarray)):
|
||||
message = [message]
|
||||
|
||||
for line in message:
|
||||
print("[%s] %s [%s]: %s" % (source, datetime.now().strftime("%x %X"), level, line.rstrip()))
|
||||
|
||||
@staticmethod
|
||||
def run_bash(source, command, log_output=True, return_result=True):
|
||||
if not isinstance(command, (list, tuple, np.ndarray)):
|
||||
command = shlex.split(command)
|
||||
|
||||
try:
|
||||
process = subprocess.Popen(command,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
|
||||
output = []
|
||||
|
||||
while True:
|
||||
line = process.stdout.readline().rstrip()
|
||||
output.append(line) if return_result else None
|
||||
Functions.log(source, "info", line) if log_output else None
|
||||
Functions.log(source, "error", process.stderr.readline())
|
||||
return_code = process.poll()
|
||||
if return_code is not None:
|
||||
lines = []
|
||||
for line in process.stdout.readlines():
|
||||
output.append(line.rstrip()) if return_result else None
|
||||
lines.append(line.rstrip())
|
||||
Functions.log(source, "info", lines) if log_output else None
|
||||
Functions.log(source, "error", process.stderr.readlines())
|
||||
break
|
||||
|
||||
return output
|
||||
except Exception as e:
|
||||
Functions.log(source, 'error', "%s" % (e))
|
||||
49
src/main.py
Normal file
49
src/main.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
from functions import Functions
|
||||
from processor import ProcessorInterface
|
||||
import os
|
||||
import time
|
||||
from threading import Thread
|
||||
|
||||
easyhaproxy_config = "/etc/haproxy/easyconfig.yml"
|
||||
haproxy_config = "/etc/haproxy/haproxy.cfg"
|
||||
certs_letsencrypt = "/certs/letsencrypt"
|
||||
certs_haproxy = "/certs/haproxy"
|
||||
|
||||
def start():
|
||||
processor_obj = ProcessorInterface.factory(os.getenv("EASYHAPROXY_DISCOVER"))
|
||||
if processor_obj is None:
|
||||
exit(1)
|
||||
|
||||
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))
|
||||
|
||||
#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))
|
||||
x.start()
|
||||
|
||||
while True:
|
||||
time.sleep(10)
|
||||
|
||||
|
||||
def main():
|
||||
Functions.run_bash('INIT', '/usr/sbin/haproxy -v')
|
||||
|
||||
Functions.log('INIT', 'info', " _ ")
|
||||
Functions.log('INIT', 'info', " ___ __ _ ____ _ ___| |_ __ _ _ __ _ _ _____ ___ _ ")
|
||||
Functions.log('INIT', 'info', "/ -_) _` (_-< || |___| ' \/ _` | '_ \ '_/ _ \ \ / || |")
|
||||
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:')
|
||||
for name, value in os.environ.items():
|
||||
if "HAPROXY" in name:
|
||||
print("- {0}: {1}".format(name, value))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
170
src/processor/__init__.py
Normal file
170
src/processor/__init__.py
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
from easymapping import HaproxyConfigGenerator
|
||||
from functions import Functions
|
||||
import yaml
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
from kubernetes import client, config
|
||||
from kubernetes.client.rest import ApiException
|
||||
|
||||
class ContainerEnv:
|
||||
@staticmethod
|
||||
def read():
|
||||
env_vars = {
|
||||
"customerrors": True if os.getenv("HAPROXY_CUSTOMERRORS") == "true" else False,
|
||||
"ssl_mode": os.getenv("EASYHAPROXY_SSL_MODE", "default")
|
||||
}
|
||||
|
||||
if os.getenv("HAPROXY_PASSWORD"):
|
||||
env_vars["stats"] = {
|
||||
"username": os.getenv("HAPROXY_USERNAME") if os.getenv("HAPROXY_USERNAME") else "admin",
|
||||
"password": os.getenv("HAPROXY_PASSWORD"),
|
||||
"port": os.getenv("HAPROXY_STATS_PORT") if os.getenv("HAPROXY_STATS_PORT") else "1936",
|
||||
}
|
||||
|
||||
env_vars["lookup_label"] = os.getenv("EASYHAPROXY_LABEL_PREFIX") if os.getenv("EASYHAPROXY_LABEL_PREFIX") else "easyhaproxy"
|
||||
if (os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL")):
|
||||
env_vars["letsencrypt"] = {
|
||||
"email": os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL")
|
||||
}
|
||||
|
||||
return env_vars
|
||||
|
||||
|
||||
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()
|
||||
|
||||
@staticmethod
|
||||
def factory(mode):
|
||||
if mode == "static":
|
||||
return Static("/etc/haproxy/easyconfig.yml")
|
||||
elif mode == "docker":
|
||||
return Docker()
|
||||
elif mode == "swarm":
|
||||
return Swarm()
|
||||
elif mode == "kubernetes":
|
||||
return Swarm()
|
||||
else:
|
||||
Functions.log("FACTORY", "error", "Expected mode to be 'static', 'docker', 'swarm' or 'kubernetes'. I got " + mode)
|
||||
return None
|
||||
|
||||
def inspect_network(self):
|
||||
#Abstract
|
||||
pass
|
||||
|
||||
def parse(self):
|
||||
# Abstract
|
||||
pass
|
||||
|
||||
def get_letsencrypt_hosts(self):
|
||||
return self.letsencrypt_hosts
|
||||
|
||||
def get_parsed_object(self):
|
||||
return self.parsed_object
|
||||
|
||||
def get_certs(self, key = None):
|
||||
if key is None:
|
||||
return self.cfg.certs
|
||||
else:
|
||||
return None if key not in self.cfg.certs else self.cfg.certs[key]
|
||||
|
||||
def get_haproxy_conf(self):
|
||||
conf = self.cfg.generate(self.parsed_object)
|
||||
self.letsencrypt_hosts = self.cfg.letsencrypt_hosts
|
||||
return conf
|
||||
|
||||
|
||||
|
||||
class Static(ProcessorInterface):
|
||||
def inspect_network(self):
|
||||
self.parsed_object = {}
|
||||
|
||||
def parse(self):
|
||||
static_content = yaml.load(Functions.load(self.filename), Loader=yaml.FullLoader)
|
||||
self.cfg = HaproxyConfigGenerator(static_content)
|
||||
|
||||
|
||||
class Docker(ProcessorInterface):
|
||||
def inspect_network(self):
|
||||
docker = "/usr/bin/docker"
|
||||
|
||||
containers_list = Functions.run_bash('DOCKER_PROCESSOR', docker + " ps -q", log_output=False)
|
||||
containers_list.sort()
|
||||
containers = list(set(containers_list))
|
||||
self.parsed_object = {}
|
||||
for container in containers:
|
||||
self.parsed_object[container] = json.loads(''.join(Functions.run_bash('DOCKER_PROCESSOR', docker + ' inspect --format "{{ json .Config.Labels }}" ' + container, log_output=False)))
|
||||
|
||||
def parse(self):
|
||||
self.cfg = HaproxyConfigGenerator(ContainerEnv.read())
|
||||
|
||||
|
||||
class Swarm(ProcessorInterface):
|
||||
def inspect_network(self):
|
||||
docker = "/usr/bin/docker"
|
||||
|
||||
node_list = ' '.join(Functions.run_bash('SWARM_PROCESSOR', docker + ' node ls -q', False))
|
||||
containers_list_raw = Functions.run_bash('SWARM_PROCESSOR', docker + ' node ps ' + node_list + ' --format "{{ .Name }}" --filter desired-state=running', log_output=False)
|
||||
containers_list = []
|
||||
for container in containers_list_raw:
|
||||
containers_list.append(container.split('.')[0])
|
||||
containers_list.sort()
|
||||
containers = list(set(containers_list))
|
||||
self.parsed_object = {}
|
||||
for container in containers:
|
||||
self.parsed_object[container] = json.loads(''.join(Functions.run_bash('SWARM_PROCESSOR', docker + ' service inspect --format "{{ json .Spec.Labels }}" ' + container, log_output=False)))
|
||||
|
||||
def parse(self):
|
||||
self.cfg = HaproxyConfigGenerator(ContainerEnv.read())
|
||||
|
||||
|
||||
class Kubernetes(ProcessorInterface):
|
||||
def inspect_network(self):
|
||||
config.load_incluster_config()
|
||||
|
||||
api_instance = client.CoreV1Api()
|
||||
v1 = client.NetworkingV1Api()
|
||||
|
||||
ret = v1.list_ingress_for_all_namespaces(watch=False)
|
||||
|
||||
self.parsed_object = {}
|
||||
for i in ret.items:
|
||||
if i.metadata.annotations['kubernetes.io/ingress.class'] != "easyhaproxy-ingress":
|
||||
continue
|
||||
|
||||
data = {}
|
||||
#ingress_name = i.metadata.name
|
||||
data["creation_timestamp"] = i.metadata.creation_timestamp.strftime("%x %X")
|
||||
data["resource_version"] = i.metadata.resource_version
|
||||
data["namespace"] = i.metadata.namespace
|
||||
for rule in i.spec.rules:
|
||||
rule_data = {}
|
||||
port_number = rule.http.paths[0].backend.service.port.number
|
||||
definition = rule.host.replace(".", "-")
|
||||
rule_data["easyhaproxy.%s_%s.host" % (definition, port_number)] = rule.host
|
||||
rule_data["easyhaproxy.%s_%s.port" % (definition, port_number)] = "80"
|
||||
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)
|
||||
cluster_ip = api_response.spec.cluster_ip
|
||||
except ApiException as e:
|
||||
cluster_ip = None
|
||||
# print("Exception when calling CoreV1Api->read_namespaced_service: %s\n" % e)
|
||||
|
||||
if cluster_ip is not None:
|
||||
if cluster_ip not in self.parsed_object.keys():
|
||||
self.parsed_object[cluster_ip] = data
|
||||
self.parsed_object[cluster_ip].update(rule_data)
|
||||
|
||||
def parse(self):
|
||||
self.cfg = HaproxyConfigGenerator(ContainerEnv.read())
|
||||
|
||||
|
||||
|
||||
|
|
@ -3,3 +3,4 @@ docker
|
|||
jinja2
|
||||
pytest
|
||||
kubernetes
|
||||
numpy
|
||||
Loading…
Add table
Add a link
Reference in a new issue