From 7d959f892a2e7031d825c0361f82e7700c264a1a Mon Sep 17 00:00:00 2001 From: Joao M Date: Wed, 24 Aug 2022 01:00:26 +0000 Subject: [PATCH] Refactoring to Python --- .gitpod.yml | 2 +- Dockerfile | 18 +- README.md | 4 + assets/scripts/banner.txt | 5 - assets/scripts/k8s.py | 5 + examples/swarm/docker-compose.yml | 64 +++++++ resource-temp.yml | 79 ++++++++ src/banner.txt | 5 + {easymapping => src/easymapping}/__init__.py | 26 +-- src/functions/__init__.py | 58 ++++++ src/main.py | 49 +++++ src/processor/__init__.py | 170 ++++++++++++++++++ requirements.txt => src/requirements.txt | 3 +- {templates => src/templates}/bind.j2 | 0 .../templates}/frontend-mode-http.j2 | 0 .../templates}/frontend-mode-tcp.j2 | 0 {templates => src/templates}/haproxy.cfg.j2 | 0 {templates => src/templates}/ssl_default.j2 | 0 {templates => src/templates}/ssl_loose.j2 | 0 {templates => src/templates}/ssl_strict.j2 | 0 {tests => src/tests}/__init__.py | 0 {tests => src/tests}/context.py | 0 {tests => src/tests}/expected/no-services.txt | 0 .../tests}/expected/services-letsencrypt.txt | 0 .../expected/services-multi-containers.txt | 0 .../expected/services-multiple-hosts.txt | 0 .../tests}/expected/services-redirect-ssl.txt | 0 .../tests}/expected/services-tcp.txt | 0 {tests => src/tests}/expected/services.txt | 0 {tests => src/tests}/expected/ssl-loose.txt | 0 {tests => src/tests}/expected/ssl-strict.txt | 0 {tests => src/tests}/expected/static.txt | 0 {tests => src/tests}/fixtures/no-services | 0 {tests => src/tests}/fixtures/services | 0 .../tests}/fixtures/services-changed-label | 0 .../tests}/fixtures/services-letsencrypt | 0 .../tests}/fixtures/services-multi-containers | 0 .../tests}/fixtures/services-multiple-hosts | 0 .../tests}/fixtures/services-redirect-ssl | 0 {tests => src/tests}/fixtures/services-tcp | 0 {tests => src/tests}/fixtures/static.yml | 0 {tests => src/tests}/test_labels.py | 0 {tests => src/tests}/test_parser.py | 0 43 files changed, 454 insertions(+), 34 deletions(-) delete mode 100644 assets/scripts/banner.txt create mode 100644 examples/swarm/docker-compose.yml create mode 100644 resource-temp.yml create mode 100644 src/banner.txt rename {easymapping => src/easymapping}/__init__.py (90%) create mode 100644 src/functions/__init__.py create mode 100644 src/main.py create mode 100644 src/processor/__init__.py rename requirements.txt => src/requirements.txt (63%) rename {templates => src/templates}/bind.j2 (100%) rename {templates => src/templates}/frontend-mode-http.j2 (100%) rename {templates => src/templates}/frontend-mode-tcp.j2 (100%) rename {templates => src/templates}/haproxy.cfg.j2 (100%) rename {templates => src/templates}/ssl_default.j2 (100%) rename {templates => src/templates}/ssl_loose.j2 (100%) rename {templates => src/templates}/ssl_strict.j2 (100%) rename {tests => src/tests}/__init__.py (100%) rename {tests => src/tests}/context.py (100%) rename {tests => src/tests}/expected/no-services.txt (100%) rename {tests => src/tests}/expected/services-letsencrypt.txt (100%) rename {tests => src/tests}/expected/services-multi-containers.txt (100%) rename {tests => src/tests}/expected/services-multiple-hosts.txt (100%) rename {tests => src/tests}/expected/services-redirect-ssl.txt (100%) rename {tests => src/tests}/expected/services-tcp.txt (100%) rename {tests => src/tests}/expected/services.txt (100%) rename {tests => src/tests}/expected/ssl-loose.txt (100%) rename {tests => src/tests}/expected/ssl-strict.txt (100%) rename {tests => src/tests}/expected/static.txt (100%) rename {tests => src/tests}/fixtures/no-services (100%) rename {tests => src/tests}/fixtures/services (100%) rename {tests => src/tests}/fixtures/services-changed-label (100%) rename {tests => src/tests}/fixtures/services-letsencrypt (100%) rename {tests => src/tests}/fixtures/services-multi-containers (100%) rename {tests => src/tests}/fixtures/services-multiple-hosts (100%) rename {tests => src/tests}/fixtures/services-redirect-ssl (100%) rename {tests => src/tests}/fixtures/services-tcp (100%) rename {tests => src/tests}/fixtures/static.yml (100%) rename {tests => src/tests}/test_labels.py (100%) rename {tests => src/tests}/test_parser.py (100%) diff --git a/.gitpod.yml b/.gitpod.yml index 2b37d88..b763650 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -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 diff --git a/Dockerfile b/Dockerfile index 9cb2339..f8c8fcc 100644 --- a/Dockerfile +++ b/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" ] diff --git a/README.md b/README.md index ec14cbf..0f457b4 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,10 @@ services: ... ``` +```bash +docker stack deploy --compose-file docker-compose.yml mystack +``` + ### Single Definition ```bash diff --git a/assets/scripts/banner.txt b/assets/scripts/banner.txt deleted file mode 100644 index f897aab..0000000 --- a/assets/scripts/banner.txt +++ /dev/null @@ -1,5 +0,0 @@ - _ - ___ __ _ ____ _ ___| |_ __ _ _ __ _ _ _____ ___ _ -/ -_) _` (_-< || |___| ' \/ _` | '_ \ '_/ _ \ \ / || | -\___\__,_/__/\_, | |_||_\__,_| .__/_| \___/_\_\\_, | - |__/ |_| |__/ diff --git a/assets/scripts/k8s.py b/assets/scripts/k8s.py index 8ebc19e..a1b2cf4 100644 --- a/assets/scripts/k8s.py +++ b/assets/scripts/k8s.py @@ -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() diff --git a/examples/swarm/docker-compose.yml b/examples/swarm/docker-compose.yml new file mode 100644 index 0000000..5c9627c --- /dev/null +++ b/examples/swarm/docker-compose.yml @@ -0,0 +1,64 @@ +# To test: +# curl -k -H "Host: host1.local" https://127.0.0.1/ +# curl -k -H "Host: host2.local" https://127.0.0.1/ +# +# curl -I -H Host:host1.local http://127.0.0.1 +# HTTP/1.1 301 Moved Permanently +# content-length: 0 +# location: https://host1.local/ +# +# curl -I -H Host:host2.local http://127.0.0.1 +# HTTP/1.1 301 Moved Permanently +# content-length: 0 +# location: https://host1.local/ +# +# Test SSL: +# openssl s_client -showcerts -connect 127.0.0.1:443 --servername host1.local + +version: "3" + +services: + haproxy: + image: byjg/easy-haproxy + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./host2.local.pem:/certs/haproxy/host2.local.pem + environment: + EASYHAPROXY_DISCOVER: docker + EASYHAPROXY_SSL_MODE: "loose" + HAPROXY_CUSTOMERRORS: "true" + HAPROXY_USERNAME: admin + HAPROXY_PASSWORD: password + HAPROXY_STATS_PORT: 1936 + + ports: + - "80:80/tcp" + - "443:443/tcp" + - "1936:1936/tcp" + + container: + image: byjg/static-httpserver + deploy: + labels: + easyhaproxy.http.redirect_ssl: "true" + easyhaproxy.http.host: host1.local + easyhaproxy.http.port: 80 + + easyhaproxy.https.port: 443 + easyhaproxy.https.localport: 8080 + easyhaproxy.https.host: host1.local + easyhaproxy.https.sslcert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZEVENDQXZXZ0F3SUJBZ0lVUmkrdzFaVmdlZWRUbE5JQXdxUUJNSnY2ZFhzd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0ZqRVVNQklHQTFVRUF3d0xhRzl6ZERFdWJHOWpZV3d3SGhjTk1qRXdPREV3TVRnME9UQTJXaGNOTXpFdwpPREE0TVRnME9UQTJXakFXTVJRd0VnWURWUVFEREF0b2IzTjBNUzVzYjJOaGJEQ0NBaUl3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFNQkRBaExBeWdKdWFXNnc2ZmZpZ3pUQUFHWHBtRXowdEl4bjFrNFoKeDV3TjVycHYvcXUwUU1ZeitBdjJ1MWVPS0VLWmVhRlJWcFQwcjkzZFg3SXZiRVpIdDI1R1BpQnZsTEdxaGpLUgpQblNrLzdVOFhtc250dFVBVjdyVkVLMVVyZEZ3OC9Jd3JpUUMrZGhyMG1uWWZTRE1rdkJvTUZwZGhWTlRyYkFaCjFUQjZyUWpFN0FyME10OG15OTZYSm13cmNqSzJUaitFMnJnUElVejFlNWNla0ZZSURTQmF0bXcrMyt2citUNXgKRk5Ga0oybzMwVzVvOFpmbENKSnpyVmFpaHFRaWNzNlpLRGdwZjdpcVhNRml3V0lsaGRRcEd2eDVHZi9LRlRLOQpVYU9uUlp6L1grMkNlYkFGYVRIUjNrL1BZcHBXVGdCQkJ1UnZscEN3K3dkbmttdGVDMFNRUkY5MVFXVnI3ZWpvCjdLYU9sR0k1VnR2TVVzV3ZUZUFabXBheW1JYUFURVR1T0phWTBKVTExT21MZUQ5RE9qNUUyU1E3cUlYL3BGY3AKeHB6RzVqNGMrTWxndnhQMlZBa05UZUFYQ2FZaVBCUUg1WlpnMEhFMlduQjFLaExSRmxIZDRpSFFEMkdKNXlOLwo2ZkNGQmZaZktTZUs4SmF1d3hnV2tyYTUzT2NEcS9tS2QrREEvZEsrL3J1Rzd0cXdWZ0lhMDRIT3Bsek03TFlSCkdCMElyczkrbHI1L1BKYlFabVUwNzNNZG42Y1hBZzNwKzZ3dndGbERrUzV2MTNnQkRZTkh0RjYyYmM1NTFlZEYKWjZrR3pKN3dtR1JvODRhQlA3TXVSWmVSZUxPclNTNjdhMXdMZHpac01uUDFUSjd4OUxmcjlNS2wydURuUWRuWQpleDhEQWdNQkFBR2pVekJSTUIwR0ExVWREZ1FXQkJTUS9tdFpkNmg4ZW45WVFWSDZITzFQbFdXaXF6QWZCZ05WCkhTTUVHREFXZ0JTUS9tdFpkNmg4ZW45WVFWSDZITzFQbFdXaXF6QVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BMEcKQ1NxR1NJYjNEUUVCQ3dVQUE0SUNBUUNoUVlOdWFoMyttVHBJQkRZeEdyalRKTnVPVElNYVd6TXlpMXRrZitMMApzRUd3cGJtQU8ybVdXUVlGN1dWTHNpOThQVUxoM2FkanQyaml1ZDlWbGFhQzZnbnduNVpvMStQaWxvOXNOTExXCjZpajArck40a3dJbS9wTnFpK2pEdXUyY3ZBdUhJd1pXZWg4YkVlLzVVQ3hvNGlobVdGUU44ZUo2VFVLQ3BoUkMKNkVvci9TU1paQlFIZ1BsMEJjaHpIT2t3dTdSM0xDbmRScXhqaEFvVmI5eVFPVitac21UZUpYdWx3TnpKMXVMdApUOE9JZ0lpRHBtQm83SFNOMkgwazNjaHgwMEFzalV5SjltbUFXUGVqRmUvS1hMUlBjVlpSMTdqaHpnZklCRXpzCk01V3RXRm0xYUhEalZ2Nk02aXRlVm02MUU5VCtrL00xMXJ1MWUyWXdzeFREdmI2eDA0bWNyTnU5c29xZGRCYnIKVmZwbHV1b1EvaEVBYlh0Rk5Qb1R5U3B6MGN3T3djSENvd1ZPTG1kS2d2SW1zelppTXlISEc4VkdHbVBoODhuNwp3VnhiMGdWMFA0Uk1yY01MZGVUZG41NVlRcjFDcUJyMzRlQjZvbDZBc2JUbTNWekJIUlZtRk5rc2wxbzVKQjV0CnRYTGdGL0c4L3J6Si80bTFQYVZ1eHJCN0R4VW1JazhFUGJTSVZrdlp2ZDdMQnpLd1E2SWZWYXVjZXdIZkVhalEKVklpZXhTTWlGYzdsdzNLbnhqT0haamY2Rk05VllnM05vKytHZEM5OXM3TGtJdUp3QU1MTnFUUTdIdmhuN1l2UAo0RmxTSWdjNnhqMFlrR1pFUWxiNW8vNW5hdUVxUVUwQUJndzZqdEk0TnhyTkxUNmNwN0NPNE0weElERWcvM1lECmFBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQotLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS0KTUlJSlF3SUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NDUzB3Z2drcEFnRUFBb0lDQVFEQVF3SVN3TW9DYm1sdQpzT24zNG9NMHdBQmw2WmhNOUxTTVo5Wk9HY2VjRGVhNmIvNnJ0RURHTS9nTDlydFhqaWhDbVhtaFVWYVU5Sy9kCjNWK3lMMnhHUjdkdVJqNGdiNVN4cW9ZeWtUNTBwUCsxUEY1cko3YlZBRmU2MVJDdFZLM1JjUFB5TUs0a0F2blkKYTlKcDJIMGd6Skx3YURCYVhZVlRVNjJ3R2RVd2VxMEl4T3dLOURMZkpzdmVseVpzSzNJeXRrNC9oTnE0RHlGTQo5WHVYSHBCV0NBMGdXclpzUHQvcjYvaytjUlRSWkNkcU45RnVhUEdYNVFpU2M2MVdvb2FrSW5MT21TZzRLWCs0CnFsekJZc0ZpSllYVUtScjhlUm4veWhVeXZWR2pwMFdjLzEvdGdubXdCV2t4MGQ1UHoyS2FWazRBUVFia2I1YVEKc1BzSFo1SnJYZ3RFa0VSZmRVRmxhKzNvNk95bWpwUmlPVmJiekZMRnIwM2dHWnFXc3BpR2dFeEU3amlXbU5DVgpOZFRwaTNnL1F6bytSTmtrTzZpRi82UlhLY2FjeHVZK0hQakpZTDhUOWxRSkRVM2dGd21tSWp3VUIrV1dZTkJ4Ck5scHdkU29TMFJaUjNlSWgwQTloaWVjamYrbndoUVgyWHlrbml2Q1dyc01ZRnBLMnVkem5BNnY1aW5mZ3dQM1MKdnY2N2h1N2FzRllDR3RPQnpxWmN6T3kyRVJnZENLN1BmcGErZnp5VzBHWmxOTzl6SForbkZ3SU42ZnVzTDhCWgpRNUV1YjlkNEFRMkRSN1JldG0zT2VkWG5SV2VwQnN5ZThKaGthUE9HZ1QrekxrV1hrWGl6cTBrdXUydGNDM2MyCmJESno5VXllOGZTMzYvVENwZHJnNTBIWjJIc2ZBd0lEQVFBQkFvSUNBUUMveFpiWjBjY3RxYWdzcXZhVk5URWUKZXExcStoZmFHdlBFWVFhWUhJcklFKzJpNVhjbkdjTEtjS2ZvZHhEakFuOFIvemdkT3A2Y01YMENWbi9Qb2hIawpBRUR0RTgrQVZ3d0FNMUZzT3dnTEhWR2FHejhxcnhCbFlkUWdIY3BtdWVJdTJQWGJDOGVIVUJpYVVPSXVoYXc1Ci9SUk1EQUMvQWkyc3NmaTdnT2p2VkU0b1F4UVcwUUcxS0dPT0FVSm4vdVlIdzJSRlkyVXUxcGlteE8ya0RPNTMKZ2N4bUMxV09ueUNIbUhhaVcvVWg3ejZKYW1mU000ZFh0VEpac2x5aDM3ZGhIS05iZzlWa1A3Q1FLQTRoTHpvcApoYmY1cVk2cmFyZ09OaW55MUhnTVB4cm13S3VVb3VKeU90TjB5QnR4akRDVU5hWFVCd2l5N3NOR1MrSDR2c3lCCjVQOUhoSUhTdHUrRlp0M0hHN0VJcUNuZGlhU0tEUzRqV2FWUUFiYm80bloyWnMyQkQreERlUFJDUlVxWDdyTTQKNFh6UElSV1dYbW1XZi83SWcyOUhicnA0YTlMY09tUTJsZUNKdGJhVEZTTjk2T0xVSjVFK2hRMHVsQ1pnQlZtUQpSQ1VZa0pQNGxPemJhS2R6anhnSE1ySHptNDVlVUZmOExpck94aTJ1eXhYSFFtRE51NGIzWDE4a3QzUGdVbVVtCjNkWHBsM2ZxU3lKYTdTQ1Y4Wk5CcnNyRHExRSt0aFl0dTkxUWJWU0d4SGQ5SHJOVmUzWGRMYk9DZFU5Q3VDNjkKTmdsem5hYTdzWkxxbXlLZWpUZkdzWTd4cldkTmNNUGw0cDRmY0lEL080RXBBU1pmb3JwVGVLTlQwWklmWlpldwpiMG1BUWVZWnFRTThpL3FNWU4vdUFRS0NBUUVBNXFnMXNSTk1jNlZkTS90UmdsYXNHWW94amdSQzJPcUFEWmdzCm1BWE1VSjNrRXJweXh0K2VDaW15OGlidVlwelJUSVE4ZkJUV1JrQ3RSWlhKNytLY0xWdGs5UVpJb0xiaHlOd2QKNEl4RVFaRnVVbGpEYnZTalRMU3ljc0h2bzY1aWJXSWZUTDdiZ1dsTEdnR3EvVU96ZkdzZ0g2Uzl3THA1RzMwRwo4RUx5akk1ZVRJWUlDcmZUbVZMK2M0NU1ScEVNS28rY3Z6OFB5c2lhT0ZUbjNjeXN3UFZkWWFlRUVxTVFqVTh3CklHTnNHWkx5dFk3QkFCQmNZMGxkcnRiYS9PK0Z2LytSSDd1VXR6UDd4cENJd0ZDeDgwWnpOK1dSeTlOdkk2M1UKenEzeUlCb1c5R3lBcEQyK1BMYVBOeGY3UUxUVUNoWTFaei9kWVJsdEtPeHYyQWE1Z1FLQ0FRRUExV0xXTnFwMApmaEIvWnRmU0VTaHhGTU04OWNqTjZBYXoxV0tMN3VUQm91OW9TSm54amtoa2FWNzZhY25UL2lxWHR4TU5nSGkxCmZJbURwVTNQdk0wWTRVZDJUNDdvSGM2UDFCclpQTi9HbVh5L3M2QkFFZFB3TGU3Sis0blRJU0hBZEdtcmgrYS8KNXBrdHUzMmc5bFdxZnR4ZWNGSVZTTFBXa3hUMFhLaU14cDFmZmtMK09hdnBNZ01GWks0MWlLczNkTlNoS1BvZwpMOEdTUGNQOXgveW43OFAyZUszTitQR2psQTZwUHpyQU55V1U3TjAvYm1IY0I5VEtQK3VkWVdjalZocnU3TVlOCndOckU0a0tkQzh2OGk3eDd0RGJ2Yjc5VCtGbzZQSWg1M3AwT3NuWnpBOFVSMFFOUit2RFF1ZlF1eWFqOFJFQysKWkc4WXlDS3N2azh5Z3dLQ0FRQS9mc1N4QjBmL2VlRXJZeDZ3QzUzNnRlRW9ZQ0hxeHJzVGd2V2JyOVRyeUZzMQprSi95QVRMblIwMWNmYjBYNW1WemM5K1dwTUhMdXhnMzFLRXZhU2xuRHdhK3NNa2pmTlN3ejI5bUZoYmdHZUhOCngyT2RVcmoxYjdURUJJRXNoTi9SanJaaEVSVXFEY3MvMEgrNmtuMkJYWmdOUGZPQ2I1TFJMMXpPblE5YUJBTVAKZThJUStVUEZyR1FoZVdXajgxL3ZBM081N2VreUFJRDd5dHU5WWcrWVdyTW5JODhtdGo3ak40NWZEQitBOXNQYgptUDJtUDlxKzlqNVUyQTZXbkhVc1FuVTMwQktEVUVzYUFVV3o4MExaWG1adlY4SUg0eDl3S2ZVd0pCQklLQVp6CnFMN005N1k3em1Ha1gvU3BmbDMwbk9KOGxzY2hhTGQxRVlsRVphMkJBb0lCQVFDeWUyNVQ0VFY1TUpGdjB6dVoKTUd1TmcxU2MvTzRGa24yZkVVT2NlV2pod1VCSDRjUGpUL2YxRHdXRHNOYUo5TlJieENyNTkzMU9BclBEYzVjOApBNDA0K1k0ak01UkJRa0tabGk5NHRIQW9kK2pjOVVCQjZUVXZKbGw1OVNsTXdDOTY3OXdTMjFaT0tuZlBLR0NYClNzWkdRRXNaeGY2Wmhoc0hnWEozZ2wvbHpVSlBtUGVPQTVZVlIrT2Q5LzA5S0lGRlRvalNmb3luaFZDdUt4NDkKeGI0dVZZbjJIT0o0eEowZlBUZ2hkQ0hNdnJtWGVlUVJqdmI4OGVhTm1xVlVFSEhGRnRnYjRma2xBNWZFN1JUeApCaGxpUkRCd1o3YlVrSU5LNnlWazluNkJUbnM1bU12UkxtZ2RuSnBZdkU3S0MwMkxUYlpiM0krajhDMFpVYStOCnF5N0RBb0lCQUFpZXJpYlM3V1VjbDJhQmxrbTUrVzdxTm0vSU5tNXp2bm9TUG82VjN3YTVoczZmOStDL2tiZEYKODdqUVBBL1lGZTN1UjJzQUo3c2xYNWV1Wks4V21mcEZtZ3psdTBzRXo4MU1MUS9XeXBadFp5dHlWdFd6QjJQdQpYQ1cxdGRTSDllSTJCbWhYZ29rSE5UTTQ4TmsveE9FTnJQL3NlWHJJeDVMSzBobkRIWm90dS96NitZU2tCOWhGCmNtMmZaeWdEMWRNTFg2bGlSaW14eUZZK2RJQ0pOQjk1SmlmVExXWW5XZUdkZGt3UHRYVWVHWEUxb2x6dk5rTEQKek16RTA5dWhreC9sUkpudGVPQkVaYWY4ME9CLzA5T2k5YjkvcnhZNTlkd3NINkdheExvVGZFS3VQbnZCVk1OUgpZa1UxNFd6UUtsZUZraUJKSTlsVnZuZmdHbk9sZ2cwPQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t + + container2: + image: byjg/static-httpserver + deploy: + labels: + easyhaproxy.http.host: host2.local + easyhaproxy.http.port: 80 + easyhaproxy.http.redirect_ssl: "true" + + easyhaproxy.https.port: 443 + easyhaproxy.https.localport: 8080 + easyhaproxy.https.host: host2.local + easyhaproxy.https.ssl: "true" + diff --git a/resource-temp.yml b/resource-temp.yml new file mode 100644 index 0000000..2954285 --- /dev/null +++ b/resource-temp.yml @@ -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" diff --git a/src/banner.txt b/src/banner.txt new file mode 100644 index 0000000..8f09599 --- /dev/null +++ b/src/banner.txt @@ -0,0 +1,5 @@ +" _ " +" ___ __ _ ____ _ ___| |_ __ _ _ __ _ _ _____ ___ _ " +"/ -_) _` (_-< || |___| ' \/ _` | '_ \ '_/ _ \ \ / || |" +"\___\__,_/__/\_, | |_||_\__,_| .__/_| \___/_\_\\_, |" +" |__/ |_| |__/ " diff --git a/easymapping/__init__.py b/src/easymapping/__init__.py similarity index 90% rename from easymapping/__init__.py rename to src/easymapping/__init__.py index a10fa2f..fe4e145 100644 --- a/easymapping/__init__.py +++ b/src/easymapping/__init__.py @@ -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 diff --git a/src/functions/__init__.py b/src/functions/__init__.py new file mode 100644 index 0000000..e14e8b5 --- /dev/null +++ b/src/functions/__init__.py @@ -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)) diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..a06a3fa --- /dev/null +++ b/src/main.py @@ -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() \ No newline at end of file diff --git a/src/processor/__init__.py b/src/processor/__init__.py new file mode 100644 index 0000000..db95bb0 --- /dev/null +++ b/src/processor/__init__.py @@ -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()) + + + diff --git a/requirements.txt b/src/requirements.txt similarity index 63% rename from requirements.txt rename to src/requirements.txt index 7959d6f..1709aa1 100644 --- a/requirements.txt +++ b/src/requirements.txt @@ -2,4 +2,5 @@ pyyaml docker jinja2 pytest -kubernetes \ No newline at end of file +kubernetes +numpy \ No newline at end of file diff --git a/templates/bind.j2 b/src/templates/bind.j2 similarity index 100% rename from templates/bind.j2 rename to src/templates/bind.j2 diff --git a/templates/frontend-mode-http.j2 b/src/templates/frontend-mode-http.j2 similarity index 100% rename from templates/frontend-mode-http.j2 rename to src/templates/frontend-mode-http.j2 diff --git a/templates/frontend-mode-tcp.j2 b/src/templates/frontend-mode-tcp.j2 similarity index 100% rename from templates/frontend-mode-tcp.j2 rename to src/templates/frontend-mode-tcp.j2 diff --git a/templates/haproxy.cfg.j2 b/src/templates/haproxy.cfg.j2 similarity index 100% rename from templates/haproxy.cfg.j2 rename to src/templates/haproxy.cfg.j2 diff --git a/templates/ssl_default.j2 b/src/templates/ssl_default.j2 similarity index 100% rename from templates/ssl_default.j2 rename to src/templates/ssl_default.j2 diff --git a/templates/ssl_loose.j2 b/src/templates/ssl_loose.j2 similarity index 100% rename from templates/ssl_loose.j2 rename to src/templates/ssl_loose.j2 diff --git a/templates/ssl_strict.j2 b/src/templates/ssl_strict.j2 similarity index 100% rename from templates/ssl_strict.j2 rename to src/templates/ssl_strict.j2 diff --git a/tests/__init__.py b/src/tests/__init__.py similarity index 100% rename from tests/__init__.py rename to src/tests/__init__.py diff --git a/tests/context.py b/src/tests/context.py similarity index 100% rename from tests/context.py rename to src/tests/context.py diff --git a/tests/expected/no-services.txt b/src/tests/expected/no-services.txt similarity index 100% rename from tests/expected/no-services.txt rename to src/tests/expected/no-services.txt diff --git a/tests/expected/services-letsencrypt.txt b/src/tests/expected/services-letsencrypt.txt similarity index 100% rename from tests/expected/services-letsencrypt.txt rename to src/tests/expected/services-letsencrypt.txt diff --git a/tests/expected/services-multi-containers.txt b/src/tests/expected/services-multi-containers.txt similarity index 100% rename from tests/expected/services-multi-containers.txt rename to src/tests/expected/services-multi-containers.txt diff --git a/tests/expected/services-multiple-hosts.txt b/src/tests/expected/services-multiple-hosts.txt similarity index 100% rename from tests/expected/services-multiple-hosts.txt rename to src/tests/expected/services-multiple-hosts.txt diff --git a/tests/expected/services-redirect-ssl.txt b/src/tests/expected/services-redirect-ssl.txt similarity index 100% rename from tests/expected/services-redirect-ssl.txt rename to src/tests/expected/services-redirect-ssl.txt diff --git a/tests/expected/services-tcp.txt b/src/tests/expected/services-tcp.txt similarity index 100% rename from tests/expected/services-tcp.txt rename to src/tests/expected/services-tcp.txt diff --git a/tests/expected/services.txt b/src/tests/expected/services.txt similarity index 100% rename from tests/expected/services.txt rename to src/tests/expected/services.txt diff --git a/tests/expected/ssl-loose.txt b/src/tests/expected/ssl-loose.txt similarity index 100% rename from tests/expected/ssl-loose.txt rename to src/tests/expected/ssl-loose.txt diff --git a/tests/expected/ssl-strict.txt b/src/tests/expected/ssl-strict.txt similarity index 100% rename from tests/expected/ssl-strict.txt rename to src/tests/expected/ssl-strict.txt diff --git a/tests/expected/static.txt b/src/tests/expected/static.txt similarity index 100% rename from tests/expected/static.txt rename to src/tests/expected/static.txt diff --git a/tests/fixtures/no-services b/src/tests/fixtures/no-services similarity index 100% rename from tests/fixtures/no-services rename to src/tests/fixtures/no-services diff --git a/tests/fixtures/services b/src/tests/fixtures/services similarity index 100% rename from tests/fixtures/services rename to src/tests/fixtures/services diff --git a/tests/fixtures/services-changed-label b/src/tests/fixtures/services-changed-label similarity index 100% rename from tests/fixtures/services-changed-label rename to src/tests/fixtures/services-changed-label diff --git a/tests/fixtures/services-letsencrypt b/src/tests/fixtures/services-letsencrypt similarity index 100% rename from tests/fixtures/services-letsencrypt rename to src/tests/fixtures/services-letsencrypt diff --git a/tests/fixtures/services-multi-containers b/src/tests/fixtures/services-multi-containers similarity index 100% rename from tests/fixtures/services-multi-containers rename to src/tests/fixtures/services-multi-containers diff --git a/tests/fixtures/services-multiple-hosts b/src/tests/fixtures/services-multiple-hosts similarity index 100% rename from tests/fixtures/services-multiple-hosts rename to src/tests/fixtures/services-multiple-hosts diff --git a/tests/fixtures/services-redirect-ssl b/src/tests/fixtures/services-redirect-ssl similarity index 100% rename from tests/fixtures/services-redirect-ssl rename to src/tests/fixtures/services-redirect-ssl diff --git a/tests/fixtures/services-tcp b/src/tests/fixtures/services-tcp similarity index 100% rename from tests/fixtures/services-tcp rename to src/tests/fixtures/services-tcp diff --git a/tests/fixtures/static.yml b/src/tests/fixtures/static.yml similarity index 100% rename from tests/fixtures/static.yml rename to src/tests/fixtures/static.yml diff --git a/tests/test_labels.py b/src/tests/test_labels.py similarity index 100% rename from tests/test_labels.py rename to src/tests/test_labels.py diff --git a/tests/test_parser.py b/src/tests/test_parser.py similarity index 100% rename from tests/test_parser.py rename to src/tests/test_parser.py