1
0
Fork 0

Refactoring to Python

This commit is contained in:
Joao M 2022-08-24 01:00:26 +00:00
parent cd5035602a
commit 7d959f892a
43 changed files with 454 additions and 34 deletions

View file

@ -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

View file

@ -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" ]

View file

@ -170,6 +170,10 @@ services:
...
```
```bash
docker stack deploy --compose-file docker-compose.yml mystack
```
### Single Definition
```bash

View file

@ -1,5 +0,0 @@
_
___ __ _ ____ _ ___| |_ __ _ _ __ _ _ _____ ___ _
/ -_) _` (_-< || |___| ' \/ _` | '_ \ '_/ _ \ \ / || |
\___\__,_/__/\_, | |_||_\__,_| .__/_| \___/_\_\\_, |
|__/ |_| |__/

View file

@ -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()

File diff suppressed because one or more lines are too long

79
resource-temp.yml Normal file
View 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
View file

@ -0,0 +1,5 @@
" _ "
" ___ __ _ ____ _ ___| |_ __ _ _ __ _ _ _____ ___ _ "
"/ -_) _` (_-< || |___| ' \/ _` | '_ \ '_/ _ \ \ / || |"
"\___\__,_/__/\_, | |_||_\__,_| .__/_| \___/_\_\\_, |"
" |__/ |_| |__/ "

View file

@ -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
View 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
View 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
View 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())

View file

@ -3,3 +3,4 @@ docker
jinja2
pytest
kubernetes
numpy