From 7d959f892a2e7031d825c0361f82e7700c264a1a Mon Sep 17 00:00:00 2001 From: Joao M Date: Wed, 24 Aug 2022 01:00:26 +0000 Subject: [PATCH 01/15] 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 From 877a4b3383f4fe0f6747f7d8f2fe1132b504a2ad Mon Sep 17 00:00:00 2001 From: Joao M Date: Wed, 24 Aug 2022 02:33:21 +0000 Subject: [PATCH 02/15] More adjustmemts --- Dockerfile | 14 ++++++------ src/easymapping/__init__.py | 10 ++++----- src/functions/__init__.py | 5 ++--- src/main.py | 32 ++++++++++++++++++++++------ src/processor/__init__.py | 41 +++++++++++++++++++++++++----------- src/requirements.txt | 2 +- src/templates/haproxy.cfg.j2 | 1 + 7 files changed, 69 insertions(+), 36 deletions(-) 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 + From 303d8181f858c272948ff2bb33659e98a23436a7 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Date: Wed, 24 Aug 2022 03:02:12 +0000 Subject: [PATCH 03/15] Fix factory --- src/processor/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/processor/__init__.py b/src/processor/__init__.py index 386f05d..aea754f 100644 --- a/src/processor/__init__.py +++ b/src/processor/__init__.py @@ -45,7 +45,7 @@ class ProcessorInterface: elif mode == "swarm": return Swarm() elif mode == "kubernetes": - return Swarm() + return Kubernetes() else: Functions.log("FACTORY", "error", "Expected mode to be 'static', 'docker', 'swarm' or 'kubernetes'. I got '%s'" % (mode)) return None From 5c5f01df2b90adcf3a71bcd4e597af314a47f690 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Tue, 23 Aug 2022 23:01:07 -0500 Subject: [PATCH 04/15] Using docker python --- Dockerfile | 2 +- .../kubernetes}/easyhaproxy.yml | 12 +-- examples/kubernetes/service.yml | 80 +++++++++++++++++++ examples/swarm/docker-compose.yml | 7 +- examples/swarm/host2.local.pem | 50 ++++++++++++ src/processor/__init__.py | 31 +++---- src/requirements.txt | 1 + 7 files changed, 151 insertions(+), 32 deletions(-) rename {kubernetes => examples/kubernetes}/easyhaproxy.yml (80%) create mode 100644 examples/kubernetes/service.yml create mode 100644 examples/swarm/host2.local.pem diff --git a/Dockerfile b/Dockerfile index 3fa515e..225c00a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ WORKDIR /scripts COPY src/ /scripts/ COPY assets / -RUN apk add --no-cache haproxy bash python3 py3-pip py-yaml docker certbot openssl \ +RUN apk add --no-cache haproxy bash python3 py3-pip py-yaml certbot openssl \ && ln -s /usr/bin/python3 /usr/bin/python \ && pip3 install --upgrade pip \ && pip install -r requirements.txt \ diff --git a/kubernetes/easyhaproxy.yml b/examples/kubernetes/easyhaproxy.yml similarity index 80% rename from kubernetes/easyhaproxy.yml rename to examples/kubernetes/easyhaproxy.yml index 000c238..2aba608 100644 --- a/kubernetes/easyhaproxy.yml +++ b/examples/kubernetes/easyhaproxy.yml @@ -38,17 +38,7 @@ spec: requests: cpu: "100m" memory: "128Mi" - #limits: - # cpu: "1" - # memory: "1Gi" - #securityContext: - # allowPrivilegeEscalation: true - # #runAsUser: 101 #nginx - # capabilities: - # drop: - # - ALL - # add: - # - NET_BIND_SERVICE + env: - name: EASYHAPROXY_DISCOVER value: kubernetes diff --git a/examples/kubernetes/service.yml b/examples/kubernetes/service.yml new file mode 100644 index 0000000..d98399f --- /dev/null +++ b/examples/kubernetes/service.yml @@ -0,0 +1,80 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + kubernetes.io/ingress.class: easyhaproxy-ingress + name: container-example + namespace: parking +spec: + rules: + - host: example.org + http: + paths: + - backend: + service: + name: container-example + port: + number: 8080 + pathType: ImplementationSpecific + - host: www.example.org + http: + paths: + - backend: + service: + name: container-example + port: + number: 8080 + pathType: ImplementationSpecific + +--- +apiVersion: v1 +kind: Service +metadata: + name: container-example + namespace: parking +spec: + ports: + - name: http + port: 8080 + selector: + app: container-example + type: ClusterIP + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: container-example + namespace: parking +spec: + replicas: 1 + revisionHistoryLimit: 10 + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: container-example + template: + metadata: + labels: + app: container-example + spec: + containers: + - name: container-example + image: byjg/static-httpserver + ports: + - containerPort: 8080 + resources: + limits: + cpu: '0.05' + memory: '20Mi' + requests: + cpu: '0.05' + memory: '20Mi' + env: + - name: TITLE + value: "My Host Example" diff --git a/examples/swarm/docker-compose.yml b/examples/swarm/docker-compose.yml index 5c9627c..d9000bf 100644 --- a/examples/swarm/docker-compose.yml +++ b/examples/swarm/docker-compose.yml @@ -23,14 +23,15 @@ services: volumes: - /var/run/docker.sock:/var/run/docker.sock - ./host2.local.pem:/certs/haproxy/host2.local.pem + deploy: + replicas: 1 environment: - EASYHAPROXY_DISCOVER: docker + EASYHAPROXY_DISCOVER: swarm EASYHAPROXY_SSL_MODE: "loose" HAPROXY_CUSTOMERRORS: "true" HAPROXY_USERNAME: admin HAPROXY_PASSWORD: password HAPROXY_STATS_PORT: 1936 - ports: - "80:80/tcp" - "443:443/tcp" @@ -39,6 +40,7 @@ services: container: image: byjg/static-httpserver deploy: + replicas: 1 labels: easyhaproxy.http.redirect_ssl: "true" easyhaproxy.http.host: host1.local @@ -52,6 +54,7 @@ services: container2: image: byjg/static-httpserver deploy: + replicas: 1 labels: easyhaproxy.http.host: host2.local easyhaproxy.http.port: 80 diff --git a/examples/swarm/host2.local.pem b/examples/swarm/host2.local.pem new file mode 100644 index 0000000..917062a --- /dev/null +++ b/examples/swarm/host2.local.pem @@ -0,0 +1,50 @@ +-----BEGIN CERTIFICATE----- +MIIDqTCCApGgAwIBAgIUId5b9t9uqH78g02EzbWF6FKVw3gwDQYJKoZIhvcNAQEL +BQAwZDELMAkGA1UEBhMCQlIxFzAVBgNVBAgMDlJpbyBkZSBKYW5laXJvMRcwFQYD +VQQHDA5SaW8gZGUgSmFuZWlybzENMAsGA1UECgwEQUNNRTEUMBIGA1UEAwwLaG9z +dDIubG9jYWwwHhcNMjIwODE1MDQyNzA1WhcNMjMwODE1MDQyNzA1WjBkMQswCQYD +VQQGEwJCUjEXMBUGA1UECAwOUmlvIGRlIEphbmVpcm8xFzAVBgNVBAcMDlJpbyBk +ZSBKYW5laXJvMQ0wCwYDVQQKDARBQ01FMRQwEgYDVQQDDAtob3N0Mi5sb2NhbDCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMSKvrOahavCXnvSF5131hpo +6k65C57jgRQ84FaDj5MbJOVlYQVFtMG0XOk7a+hh5v1fe4wH0R7I6FDo0V9sS+ss +ko5bsElc1xYlg5HbuKq89vRSKg6EDlztx3BKbi912Pmt5vFGNJ16zcw77DUrQIXo +4I/b4a3pmBiWj43NoTIrmSWHtsGwwOj3iDvSweqdYXJIr3hpHH5u6pohjDoQvqDz +K6Mu8p6mhCUKNs7KFJnNInNG25oQT6O0n4OGtmgRjLWopdEnOhMkKsfIoI1XtlXB +LBDv7huICk3t5ywtfCQyO09kX7lFIgd5rn7+MjwH5WNeqbQJxuaqjoXQnNZUgUsC +AwEAAaNTMFEwHQYDVR0OBBYEFNhMBG8q6a+iK2nECwVTn6B9EXZOMB8GA1UdIwQY +MBaAFNhMBG8q6a+iK2nECwVTn6B9EXZOMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI +hvcNAQELBQADggEBAJmudvx8+p5iIUsT8fm/fbVM0DA6qWALDYUJnTn3j6Lq4vpf +PFC+q1LmuWfBQMyqKrHrP3e493EctXoiSKZO6iN5dVJIur02OjGuiAEcsYuY1nLn +s9piiI+UEwxH6ux1NaHUnzsWauoBvRhzjXvO6SAVSZJYa9dY5mizXklDyDNuG5U0 +lXv9egMGBsy0dG6eFXkU5CPdxWU540yI2sCtSAj7z+WRUD5k7gJ7tVoY3//jHQZG +5STTmm5t9kpIZTWkptyJos9oZJFYMIXqW2Fc6tyLZpRp31R78tDs6ETIkToDc0RR +jz66th6HI+ZlgIBQhw09+hYAhBDe9+Dmd/SzQZc= +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDEir6zmoWrwl57 +0hedd9YaaOpOuQue44EUPOBWg4+TGyTlZWEFRbTBtFzpO2voYeb9X3uMB9EeyOhQ +6NFfbEvrLJKOW7BJXNcWJYOR27iqvPb0UioOhA5c7cdwSm4vddj5rebxRjSdes3M +O+w1K0CF6OCP2+Gt6ZgYlo+NzaEyK5klh7bBsMDo94g70sHqnWFySK94aRx+buqa +IYw6EL6g8yujLvKepoQlCjbOyhSZzSJzRtuaEE+jtJ+DhrZoEYy1qKXRJzoTJCrH +yKCNV7ZVwSwQ7+4biApN7ecsLXwkMjtPZF+5RSIHea5+/jI8B+VjXqm0Ccbmqo6F +0JzWVIFLAgMBAAECggEBAKceitVROQQ5e/mxRR9CfK1sNH/H3Ne3/1PkB6XIrFab +qB3evEatZOuon7A6NKEeTjl37Se+pdSVZOUXcqC/BzbraZre3+EhrkpIj72ApV+Y +2iwZiWVaaJQgI4uZ3mNAw8RaWJsj5S1a9I8LDOiQ5IZ45CmvABDPJeMScvJSvRRY +e5N0L6stqS7Z+IoyGVKUfp1iNO0YyywOUiSkIRXgscuRXZGYpiGPomsJ+Js1ejzW +jyStlZJEr4L1285rGPrmHqjTwFd+hG80Wc4179xL+WRE6HBEUZSiy95fe6kcPHXX +BgiVYtcFKmiBi2dTbxl4e94ut239i0HtlJ1ZJtLh+BECgYEA8a298K2zXkHosxhN +tRrH7XfMPTkHDDd3rxM21LT+fIXqinGUp9LYaDcbjuTPs8e33uKMd7R6Q40x89yW +IXNka/VL0PXUeV67aCVLLqgXDLGudluJinH0XvmI0CmBecFSMqIFmQlgqERoGGs3 +UMac0p876T4XkGQQJdf62bFpE4UCgYEA0DBH0PDlOpwXccDgXrMfayr8HAhI+G5R +yWQ//9iirtU83chwIWwkh53eLLMzLgdqJnPiWyaUW5BqzmYuD23nhxcQ7PNdIqOO +H1sE6zqLNshv46t5QKlh1Q4qjd7UqtgrSrY63RXJCMWTwnNMeDLtj8gaKbjkrG3R +BM2ildt6Uo8CgYBX7NDUli1SloH1XlsvD047S8FHaM7yl994F3J0UmDfpszcj1P4 +9pF64Mmq4/3Yt0li0mMuTb/Jgb3xrYgFJXkcecKahEVH3ropup+umsLAAIirUMQq +VSkFwJ0Qtnj/deDUwPNuaOX8cd65O5CFV6zIR9xBEDD8fBsP2ZLOzmefDQKBgQDF +m24vVthd/1cJdCgD+0VxNYXDHeIVXLFo1S0iLYCNLn3tjZlRQBKUXzZJe3ay0/rf +sNND7aSYHMYkTzydDJbc1PoNzxmyDUiTXpOWqyUExM/fbB1VUPE5h47AxqdZ2oGN +EtdgjpMZLmCIC2SkGsL+3NJok8UKHdpuErmmQIMk5QKBgQCgEWcYtLXC3YDYMFdI +UgcTebFqSs3mLYgub1xekW3IXR2yom4V5fQTLiF7Yfn2dpDW4IcMU0UJFYVsUlhK +aGtet4Vm5Nn8+Mghot5yAjqO9yAUaub7wgifKIe99tQKd8uZyCvJ0hhvmDDSfx4m +B/TEiFAO99yF49iSxEVSAS6pqQ== +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/src/processor/__init__.py b/src/processor/__init__.py index aea754f..c7ab3be 100644 --- a/src/processor/__init__.py +++ b/src/processor/__init__.py @@ -4,6 +4,7 @@ import yaml import sys import os import json +import docker from kubernetes import client, config from kubernetes.client.rest import ApiException @@ -106,34 +107,28 @@ class Static(ProcessorInterface): class Docker(ProcessorInterface): + def __init__(self, filename = None): + self.client = docker.from_env() + super().__init__() + 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))) + for container in self.client.containers.list(): + self.parsed_object[container.name] = container.labels def parse(self): self.cfg = HaproxyConfigGenerator(ContainerEnv.read()) class Swarm(ProcessorInterface): - def inspect_network(self): - docker = "/usr/bin/docker" + def __init__(self, filename = None): + self.client = docker.from_env() + super().__init__() - 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)) + def inspect_network(self): 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))) + for container in self.client.services.list(): + self.parsed_object[container.attrs["Spec"]["Name"]] = container.attrs["Spec"]["Labels"] def parse(self): self.cfg = HaproxyConfigGenerator(ContainerEnv.read()) diff --git a/src/requirements.txt b/src/requirements.txt index 8327169..5c4ba98 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -2,5 +2,6 @@ pyyaml docker jinja2 pytest +docker kubernetes deepdiff \ No newline at end of file From 50fae1c6590a1c34dbf835356254bff2020eebc1 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Tue, 23 Aug 2022 23:04:20 -0500 Subject: [PATCH 05/15] Adjust method parse --- src/processor/__init__.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/processor/__init__.py b/src/processor/__init__.py index c7ab3be..726b14b 100644 --- a/src/processor/__init__.py +++ b/src/processor/__init__.py @@ -64,8 +64,7 @@ class ProcessorInterface: pass def parse(self): - # Abstract - pass + self.cfg = HaproxyConfigGenerator(ContainerEnv.read()) def get_letsencrypt_hosts(self): return self.letsencrypt_hosts @@ -116,9 +115,6 @@ class Docker(ProcessorInterface): for container in self.client.containers.list(): self.parsed_object[container.name] = container.labels - def parse(self): - self.cfg = HaproxyConfigGenerator(ContainerEnv.read()) - class Swarm(ProcessorInterface): def __init__(self, filename = None): @@ -130,9 +126,6 @@ class Swarm(ProcessorInterface): for container in self.client.services.list(): self.parsed_object[container.attrs["Spec"]["Name"]] = container.attrs["Spec"]["Labels"] - def parse(self): - self.cfg = HaproxyConfigGenerator(ContainerEnv.read()) - class Kubernetes(ProcessorInterface): def __init__(self, filename = None): @@ -175,8 +168,6 @@ class Kubernetes(ProcessorInterface): self.parsed_object[cluster_ip] = data self.parsed_object[cluster_ip].update(rule_data) - def parse(self): - self.cfg = HaproxyConfigGenerator(ContainerEnv.read()) From 0687ba60a22ba4e2a451fc0828482212eee0bc55 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Wed, 24 Aug 2022 17:38:06 -0500 Subject: [PATCH 06/15] Fixing multithread issue --- src/functions/__init__.py | 107 +++++++++++++++++++++++++++++++++++++- src/main.py | 21 ++++---- 2 files changed, 117 insertions(+), 11 deletions(-) diff --git a/src/functions/__init__.py b/src/functions/__init__.py index b0614a8..154a035 100644 --- a/src/functions/__init__.py +++ b/src/functions/__init__.py @@ -1,4 +1,5 @@ from datetime import datetime +from multiprocessing import Process, Lock import subprocess import shlex @@ -41,7 +42,7 @@ class Functions: 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()) + Functions.log(source, "warning", process.stderr.readline()) return_code = process.poll() if return_code is not None: lines = [] @@ -49,9 +50,111 @@ class Functions: 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()) + Functions.log(source, "warning", process.stderr.readlines()) break return output except Exception as e: Functions.log(source, 'error', "%s" % (e)) + + + + @staticmethod + def log_ts(source, level, message, lock): + if message is None or message == "": + return + + if not isinstance(message, (list, tuple)): + message = [message] + + lock.acquire() + try: + for line in message: + print("[%s] %s [%s]: %s" % (source, datetime.now().strftime("%x %X"), level, line.rstrip())) + finally: + lock.release() + + @staticmethod + def run_bash_ts(source, command, lock): + if not isinstance(command, (list, tuple)): + command = shlex.split(command) + + try: + process = subprocess.Popen(command, + shell=False, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + bufsize=-1, + universal_newlines=True) + + with process.stdout: + for line in iter(process.stdout.readline, b''): + Functions.log(source, "info", line) + + returncode = process.wait() + Functions.log(source, "info", "Return code %s" % (returncode)) + + except Exception as e: + Functions.log_ts(source, 'error', "%s" % (e), lock) + + +class DaemonizeHAProxy: + def __init__(self): + self.process = None + self.thread = None + + def haproxy(self, action): + if action == "start": + self.__prepare("/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /var/run/haproxy.sock") + else: + pid = "".join(Functions().run_bash("HAPROXY", "cat /run/haproxy.pid", log_output=False)) + self.__prepare("/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -x /var/run/haproxy.sock -sf %s" % (pid)) + + if self.process is None: + return + + self.thread = Process(target=self.__start, args=()) + self.thread.start() + + def __prepare(self, command): + source = "HAPROXY" + if not isinstance(command, (list, tuple)): + command = shlex.split(command) + + try: + self.process = subprocess.Popen(command, + shell=False, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + bufsize=-1, + universal_newlines=True) + + except Exception as e: + Functions.log_ts(source, 'error', "%s" % (e), lock) + + + def __start(self): + source = "HAPROXY" + try: + with self.process.stdout: + for line in iter(self.process.stdout.readline, b''): + Functions.log(source, "info", line) + + returncode = self.process.wait() + Functions.log(source, "info", "Return code %s" % (returncode)) + + except Exception as e: + Functions.log_ts(source, 'error', "%s" % (e), lock) + + def is_alive(self): + return self.thread.is_alive() + + def kill(self): + self.process.kill() + self.thread.kill() + + def terminate(self): + self.process.terminate() + self.thread.terminate() + + \ No newline at end of file diff --git a/src/main.py b/src/main.py index c1e9b0c..c2749f9 100644 --- a/src/main.py +++ b/src/main.py @@ -1,8 +1,7 @@ -from functions import Functions +from functions import Functions, DaemonizeHAProxy from processor import ProcessorInterface import os import time -from threading import Thread from deepdiff import DeepDiff easyhaproxy_config = "/etc/haproxy/easyconfig.yml" @@ -22,23 +21,27 @@ def start(): 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)) - x.start() + old_haproxy = None + haproxy = DaemonizeHAProxy() + haproxy.haproxy("start") while True: time.sleep(10) + if old_haproxy is not None: + old_haproxy.kill() + old_haproxy = None 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(): + if DeepDiff(old_parsed, processor_obj.get_parsed_object()) != {} or not haproxy.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() + old_haproxy = haproxy + haproxy = DaemonizeHAProxy() + haproxy.haproxy("reload") + old_haproxy.terminate() except Exception as e: Functions.log('EASYHAPROXY', 'error', "Err: %s" % (e)) Functions.log('EASYHAPROXY', 'info', 'Heartbeat') From 3a0b33041c507c9a1c108377167b91c21d00a390 Mon Sep 17 00:00:00 2001 From: Joao M Date: Thu, 25 Aug 2022 00:35:16 +0000 Subject: [PATCH 07/15] Fix unit tests --- .vscode/launch.json | 4 +- Dockerfile | 3 +- src/easymapping/__init__.py | 2 +- src/tests/expected/no-services.txt | 2 +- src/tests/expected/services-letsencrypt.txt | 2 +- .../expected/services-multi-containers.txt | 2 +- .../expected/services-multiple-hosts.txt | 2 +- src/tests/expected/services-redirect-ssl.txt | 2 +- src/tests/expected/services-tcp.txt | 2 +- src/tests/expected/services.txt | 2 +- src/tests/expected/ssl-loose.txt | 2 +- src/tests/expected/ssl-strict.txt | 2 +- src/tests/expected/static.txt | 2 +- src/tests/fixtures/no-services | 10 ++--- src/tests/fixtures/services | 12 +++--- src/tests/fixtures/services-changed-label | 12 +++--- src/tests/fixtures/services-letsencrypt | 8 ++-- src/tests/fixtures/services-multi-containers | 4 +- src/tests/fixtures/services-multiple-hosts | 6 +-- src/tests/fixtures/services-redirect-ssl | 6 +-- src/tests/fixtures/services-tcp | 4 +- src/tests/test_parser.py | 39 +++++++++---------- 22 files changed, 63 insertions(+), 67 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 428608e..01d56c1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,9 +10,9 @@ "request": "launch", "program": "${file}", "console": "integratedTerminal", - "cwd": "${workspaceFolder}", + "cwd": "${workspaceFolder}/src", "env": { - "PYTHONPATH": "${cwd}" + "=": "${cwd}/src" } } ] diff --git a/Dockerfile b/Dockerfile index 225c00a..fb29e07 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,9 +13,8 @@ RUN apk add --no-cache haproxy bash python3 py3-pip py-yaml certbot openssl \ && ln -s /usr/bin/python3 /usr/bin/python \ && pip3 install --upgrade pip \ && pip install -r requirements.txt \ + && pytest -s -vv tests/ \ && openssl dhparam -out /etc/haproxy/dhparam 2048 \ && openssl dhparam -out /etc/haproxy/dhparam-1024 1024 -# && pytest -s tests/ \ - CMD ["/usr/bin/python", "-u", "/scripts/main.py" ] diff --git a/src/easymapping/__init__.py b/src/easymapping/__init__.py index 95649a4..042ca61 100644 --- a/src/easymapping/__init__.py +++ b/src/easymapping/__init__.py @@ -46,7 +46,7 @@ class DockerLabelHandler: class HaproxyConfigGenerator: - def __init__(self, mapping, ssl_cert_folder="/certs"): + def __init__(self, mapping): self.mapping = mapping self.mapping.setdefault("ssl_mode", 'default') self.mapping.setdefault("letsencrypt", {"email": ""}) diff --git a/src/tests/expected/no-services.txt b/src/tests/expected/no-services.txt index 428cbca..a95c434 100644 --- a/src/tests/expected/no-services.txt +++ b/src/tests/expected/no-services.txt @@ -25,4 +25,4 @@ defaults backend letsencrypt_backend mode http - server certbot 127.0.0.1:2080 \ No newline at end of file + server certbot 127.0.0.1:2080 diff --git a/src/tests/expected/services-letsencrypt.txt b/src/tests/expected/services-letsencrypt.txt index e0093d1..3e2eafb 100644 --- a/src/tests/expected/services-letsencrypt.txt +++ b/src/tests/expected/services-letsencrypt.txt @@ -92,4 +92,4 @@ backend srv_test_example_org_443 backend letsencrypt_backend mode http - server certbot 127.0.0.1:2080 \ No newline at end of file + server certbot 127.0.0.1:2080 diff --git a/src/tests/expected/services-multi-containers.txt b/src/tests/expected/services-multi-containers.txt index 00e9196..5910691 100644 --- a/src/tests/expected/services-multi-containers.txt +++ b/src/tests/expected/services-multi-containers.txt @@ -42,4 +42,4 @@ backend srv_www_helloworld_com_19901 backend letsencrypt_backend mode http - server certbot 127.0.0.1:2080 \ No newline at end of file + server certbot 127.0.0.1:2080 diff --git a/src/tests/expected/services-multiple-hosts.txt b/src/tests/expected/services-multiple-hosts.txt index cc9c39e..8c356cd 100644 --- a/src/tests/expected/services-multiple-hosts.txt +++ b/src/tests/expected/services-multiple-hosts.txt @@ -75,4 +75,4 @@ backend srv_www_helloworld_com_19901 backend letsencrypt_backend mode http - server certbot 127.0.0.1:2080 \ No newline at end of file + server certbot 127.0.0.1:2080 diff --git a/src/tests/expected/services-redirect-ssl.txt b/src/tests/expected/services-redirect-ssl.txt index d0dbb07..077123b 100644 --- a/src/tests/expected/services-redirect-ssl.txt +++ b/src/tests/expected/services-redirect-ssl.txt @@ -77,4 +77,4 @@ backend srv_host1_local_443 backend letsencrypt_backend mode http - server certbot 127.0.0.1:2080 \ No newline at end of file + server certbot 127.0.0.1:2080 diff --git a/src/tests/expected/services-tcp.txt b/src/tests/expected/services-tcp.txt index 6df1a26..fa8fd7a 100644 --- a/src/tests/expected/services-tcp.txt +++ b/src/tests/expected/services-tcp.txt @@ -39,4 +39,4 @@ backend srv_agent_quantum_local_31339 backend letsencrypt_backend mode http - server certbot 127.0.0.1:2080 \ No newline at end of file + server certbot 127.0.0.1:2080 diff --git a/src/tests/expected/services.txt b/src/tests/expected/services.txt index 43765e6..bf2cecd 100644 --- a/src/tests/expected/services.txt +++ b/src/tests/expected/services.txt @@ -121,4 +121,4 @@ backend srv_www_somehost_com_br_80 backend letsencrypt_backend mode http - server certbot 127.0.0.1:2080 \ No newline at end of file + server certbot 127.0.0.1:2080 diff --git a/src/tests/expected/ssl-loose.txt b/src/tests/expected/ssl-loose.txt index 674c9c6..45ac853 100644 --- a/src/tests/expected/ssl-loose.txt +++ b/src/tests/expected/ssl-loose.txt @@ -35,4 +35,4 @@ backend srv_stats backend letsencrypt_backend mode http - server certbot 127.0.0.1:2080 \ No newline at end of file + server certbot 127.0.0.1:2080 diff --git a/src/tests/expected/ssl-strict.txt b/src/tests/expected/ssl-strict.txt index 815fd2f..f4e00b4 100644 --- a/src/tests/expected/ssl-strict.txt +++ b/src/tests/expected/ssl-strict.txt @@ -20,4 +20,4 @@ defaults backend letsencrypt_backend mode http - server certbot 127.0.0.1:2080 \ No newline at end of file + server certbot 127.0.0.1:2080 diff --git a/src/tests/expected/static.txt b/src/tests/expected/static.txt index f3cd653..ea0362f 100644 --- a/src/tests/expected/static.txt +++ b/src/tests/expected/static.txt @@ -107,4 +107,4 @@ backend srv_host3_com_br_8080 backend letsencrypt_backend mode http - server certbot 127.0.0.1:2080 \ No newline at end of file + server certbot 127.0.0.1:2080 diff --git a/src/tests/fixtures/no-services b/src/tests/fixtures/no-services index a0f782d..9e1b3c9 100644 --- a/src/tests/fixtures/no-services +++ b/src/tests/fixtures/no-services @@ -1,5 +1,5 @@ -swarm-prom_caddy={"com.docker.stack.image":"stefanprodan/caddy","com.docker.stack.namespace":"swarm-prom"} -swarm-prom_cadvisor={"com.docker.stack.image":"google/cadvisor","com.docker.stack.namespace":"swarm-prom"} -swarm-prom_dockerd-exporter={"com.docker.stack.image":"stefanprodan/caddy","com.docker.stack.namespace":"swarm-prom"} -swarm-prom_unsee={"com.docker.stack.image":"cloudflare/unsee:v0.8.0","com.docker.stack.namespace":"swarm-prom"} -test_proxy={"com.docker.stack.image":"byjg/easy-haproxy","com.docker.stack.namespace":"test"} +{"swarm-prom_caddy": {"com.docker.stack.image":"stefanprodan/caddy","com.docker.stack.namespace":"swarm-prom"}, +"swarm-prom_cadvisor": {"com.docker.stack.image":"google/cadvisor","com.docker.stack.namespace":"swarm-prom"}, +"swarm-prom_dockerd-exporter": {"com.docker.stack.image":"stefanprodan/caddy","com.docker.stack.namespace":"swarm-prom"}, +"swarm-prom_unsee": {"com.docker.stack.image":"cloudflare/unsee:v0.8.0","com.docker.stack.namespace":"swarm-prom"}, +"test_proxy": {"com.docker.stack.image":"byjg/easy-haproxy","com.docker.stack.namespace":"test"}} diff --git a/src/tests/fixtures/services b/src/tests/fixtures/services index c77af21..56ef60a 100644 --- a/src/tests/fixtures/services +++ b/src/tests/fixtures/services @@ -1,6 +1,6 @@ -portainer-agent_agent={"com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"portainer-agent"} -my-stack_agent={"easyhaproxy.agent.host":"agent.quantum.example.org","easyhaproxy.agent.localport":"9001","easyhaproxy.agent.mode":"tcp","easyhaproxy.agent.port":"31339","com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"} -my-stack_cadvisor={"easyhaproxy.cadvisor.host":"cadvisor.quantum.example.org","easyhaproxy.cadvisor.localport":"8080","easyhaproxy.cadvisor.port":"31337","com.docker.stack.image":"gcr.io/google-containers/cadvisor:v0.34.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"} -my-stack_node-exporter={"easyhaproxy.exp.host":"node-exporter.quantum.example.org","easyhaproxy.exp.localport":"9100","easyhaproxy.exp.port":"31337","com.docker.stack.image":"stefanprodan/swarmprom-node-exporter:v0.16.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring","easyhaproxy.exp.letsencrypt":"true"} -my-stack_reverse-proxy={"com.docker.stack.image":"quay.io/pngmbh/easy-haproxy:tcp-mode","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"} -some-service={"easyhaproxy.http.port":"80","easyhaproxy.http.host":"www.somehost.com.br","easyhaproxy.http.localport":"80","easyhaproxy.http.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","easyhaproxy.https.port":"443","easyhaproxy.https.host":"www.somehost.com.br","easyhaproxy.https.localport":"80","easyhaproxy.https.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","easyhaproxy.https.sslcert":"U29tZSBQRU0gQ2VydGlmaWNhdGU="} \ No newline at end of file +{"portainer-agent_agent": {"com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"portainer-agent"}, +"my-stack_agent": {"easyhaproxy.agent.host":"agent.quantum.example.org","easyhaproxy.agent.localport":"9001","easyhaproxy.agent.mode":"tcp","easyhaproxy.agent.port":"31339","com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"}, +"my-stack_cadvisor": {"easyhaproxy.cadvisor.host":"cadvisor.quantum.example.org","easyhaproxy.cadvisor.localport":"8080","easyhaproxy.cadvisor.port":"31337","com.docker.stack.image":"gcr.io/google-containers/cadvisor:v0.34.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"}, +"my-stack_node-exporter": {"easyhaproxy.exp.host":"node-exporter.quantum.example.org","easyhaproxy.exp.localport":"9100","easyhaproxy.exp.port":"31337","com.docker.stack.image":"stefanprodan/swarmprom-node-exporter:v0.16.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring","easyhaproxy.exp.letsencrypt":"true"}, +"my-stack_reverse-proxy": {"com.docker.stack.image":"quay.io/pngmbh/easy-haproxy:tcp-mode","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"}, +"some-service": {"easyhaproxy.http.port":"80","easyhaproxy.http.host":"www.somehost.com.br","easyhaproxy.http.localport":"80","easyhaproxy.http.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","easyhaproxy.https.port":"443","easyhaproxy.https.host":"www.somehost.com.br","easyhaproxy.https.localport":"80","easyhaproxy.https.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","easyhaproxy.https.sslcert":"U29tZSBQRU0gQ2VydGlmaWNhdGU="}} \ No newline at end of file diff --git a/src/tests/fixtures/services-changed-label b/src/tests/fixtures/services-changed-label index f4b0b14..2a23c24 100644 --- a/src/tests/fixtures/services-changed-label +++ b/src/tests/fixtures/services-changed-label @@ -1,6 +1,6 @@ -portainer-agent_agent={"com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"portainer-agent"} -my-stack_agent={"haproxy.agent.host":"agent.quantum.example.org","haproxy.agent.localport":"9001","haproxy.agent.mode":"tcp","haproxy.agent.port":"31339","com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"} -my-stack_cadvisor={"haproxy.cadvisor.host":"cadvisor.quantum.example.org","haproxy.cadvisor.localport":"8080","haproxy.cadvisor.port":"31337","com.docker.stack.image":"gcr.io/google-containers/cadvisor:v0.34.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"} -my-stack_node-exporter={"haproxy.exp.host":"node-exporter.quantum.example.org","haproxy.exp.localport":"9100","haproxy.exp.port":"31337","com.docker.stack.image":"stefanprodan/swarmprom-node-exporter:v0.16.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring","haproxy.exp.letsencrypt":"yes"} -my-stack_reverse-proxy={"com.docker.stack.image":"quay.io/pngmbh/easy-haproxy:tcp-mode","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"} -some-service={"haproxy.http.port":"80","haproxy.http.host":"www.somehost.com.br","haproxy.http.localport":"80","haproxy.http.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","haproxy.https.port":"443","haproxy.https.host":"www.somehost.com.br","haproxy.https.localport":"80","haproxy.https.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","haproxy.https.sslcert":"U29tZSBQRU0gQ2VydGlmaWNhdGU="} \ No newline at end of file +{"portainer-agent_agent": {"com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"portainer-agent"}, +"my-stack_agent": {"haproxy.agent.host":"agent.quantum.example.org","haproxy.agent.localport":"9001","haproxy.agent.mode":"tcp","haproxy.agent.port":"31339","com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"}, +"my-stack_cadvisor": {"haproxy.cadvisor.host":"cadvisor.quantum.example.org","haproxy.cadvisor.localport":"8080","haproxy.cadvisor.port":"31337","com.docker.stack.image":"gcr.io/google-containers/cadvisor:v0.34.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"}, +"my-stack_node-exporter": {"haproxy.exp.host":"node-exporter.quantum.example.org","haproxy.exp.localport":"9100","haproxy.exp.port":"31337","com.docker.stack.image":"stefanprodan/swarmprom-node-exporter:v0.16.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring","haproxy.exp.letsencrypt":"yes"}, +"my-stack_reverse-proxy": {"com.docker.stack.image":"quay.io/pngmbh/easy-haproxy:tcp-mode","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"}, +"some-service": {"haproxy.http.port":"80","haproxy.http.host":"www.somehost.com.br","haproxy.http.localport":"80","haproxy.http.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","haproxy.https.port":"443","haproxy.https.host":"www.somehost.com.br","haproxy.https.localport":"80","haproxy.https.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","haproxy.https.sslcert":"U29tZSBQRU0gQ2VydGlmaWNhdGU="}} \ No newline at end of file diff --git a/src/tests/fixtures/services-letsencrypt b/src/tests/fixtures/services-letsencrypt index 2d616ff..9c83781 100644 --- a/src/tests/fixtures/services-letsencrypt +++ b/src/tests/fixtures/services-letsencrypt @@ -1,4 +1,4 @@ -f5c645a0dfc6={"com.docker.compose.config-hash":"b95ebc27d0e61caa418cdfa632e05a656da9bbc3ea0d4603651971015f10a1f0","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test.example.org","easyhaproxy.http.letsencrypt":"true","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"80","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}","easyhaproxy.http.redirect_ssl":"true"} -bbd4d1854155={"com.docker.compose.config-hash":"3dc790bf2bea944359c75a40c45655bd868f1d85beb599d1ca797e8ea2c95ee4","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:0fd95b1512c207048ab3fcc74032354f38143fbb8235ac2a47da903c98a58205","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"haproxy","com.docker.compose.version":"2.8.0"} -b63438410b6a={"com.docker.compose.config-hash":"b95ebc27d0e61caa418cdfa632e05a656da9bbc3ea0d4603651971015f10a1f0","com.docker.compose.container-number":"2","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test.example.org","easyhaproxy.http.letsencrypt":"true","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"80","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}","easyhaproxy.http.redirect_ssl":"true"} -83d57d592e26={"com.docker.compose.config-hash":"8c5871144f1e8a3aeca037207c02f011ab2c6e6c311a3773602b63541762dab5","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:c4232396c715f3d568816c666e6d9b4a68ef6c36f6243b4007c4ee1d8335fd65","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"static","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test2.example.org","easyhaproxy.http.localport":"8080","easyhaproxy.http.port":"80","io.buildah.version":"1.21.0"} +{"f5c645a0dfc6": {"com.docker.compose.config-hash":"b95ebc27d0e61caa418cdfa632e05a656da9bbc3ea0d4603651971015f10a1f0","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test.example.org","easyhaproxy.http.letsencrypt":"true","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"80","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}","easyhaproxy.http.redirect_ssl":"true"}, +"bbd4d1854155": {"com.docker.compose.config-hash":"3dc790bf2bea944359c75a40c45655bd868f1d85beb599d1ca797e8ea2c95ee4","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:0fd95b1512c207048ab3fcc74032354f38143fbb8235ac2a47da903c98a58205","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"haproxy","com.docker.compose.version":"2.8.0"}, +"b63438410b6a": {"com.docker.compose.config-hash":"b95ebc27d0e61caa418cdfa632e05a656da9bbc3ea0d4603651971015f10a1f0","com.docker.compose.container-number":"2","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test.example.org","easyhaproxy.http.letsencrypt":"true","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"80","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}","easyhaproxy.http.redirect_ssl":"true"}, +"83d57d592e26": {"com.docker.compose.config-hash":"8c5871144f1e8a3aeca037207c02f011ab2c6e6c311a3773602b63541762dab5","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:c4232396c715f3d568816c666e6d9b4a68ef6c36f6243b4007c4ee1d8335fd65","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"static","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test2.example.org","easyhaproxy.http.localport":"8080","easyhaproxy.http.port":"80","io.buildah.version":"1.21.0"}} \ No newline at end of file diff --git a/src/tests/fixtures/services-multi-containers b/src/tests/fixtures/services-multi-containers index 7e58c70..4257caa 100644 --- a/src/tests/fixtures/services-multi-containers +++ b/src/tests/fixtures/services-multi-containers @@ -1,2 +1,2 @@ -test_nginx.2.t5r94mjlced7m3t5orfjbowmm={"easyhaproxy.http.host":"www.helloworld.com","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","com.docker.stack.image":"stenote/nginx-hostname","com.docker.stack.namespace":"test"} -test_nginx.1.p552hqxkdx88narjrp5kouwb2={"easyhaproxy.http.host":"www.helloworld.com","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","com.docker.stack.image":"stenote/nginx-hostname","com.docker.stack.namespace":"test"} \ No newline at end of file +{"test_nginx.2.t5r94mjlced7m3t5orfjbowmm": {"easyhaproxy.http.host":"www.helloworld.com","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","com.docker.stack.image":"stenote/nginx-hostname","com.docker.stack.namespace":"test"}, +"test_nginx.1.p552hqxkdx88narjrp5kouwb2": {"easyhaproxy.http.host":"www.helloworld.com","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","com.docker.stack.image":"stenote/nginx-hostname","com.docker.stack.namespace":"test"}} \ No newline at end of file diff --git a/src/tests/fixtures/services-multiple-hosts b/src/tests/fixtures/services-multiple-hosts index c04b29a..66db013 100644 --- a/src/tests/fixtures/services-multiple-hosts +++ b/src/tests/fixtures/services-multiple-hosts @@ -1,3 +1,3 @@ -db79d3a910f4={"com.docker.compose.config-hash":"5bde40f52451521ad201e70de1291397376a0498a7c955624a609da3b60e7e8e","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:ea39067705590557dd0cd951664a10970ceefcb725a3c1f43690d6d6d4ed5fce","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-multi-containers.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"haproxy","com.docker.compose.version":"2.8.0"} -3e63154954b0={"com.docker.compose.config-hash":"4e0cbdd8372c6779863799e5021ed8178f74b55bd8e070abcdffaf87eb7baa36","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-multi-containers.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"hello.com\n, www.helloworld.com\n","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}"} -eb294c110eb1={"com.docker.compose.config-hash":"4e0cbdd8372c6779863799e5021ed8178f74b55bd8e070abcdffaf87eb7baa36","com.docker.compose.container-number":"2","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-multi-containers.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"hello.com\n, www.helloworld.com\n","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}"} \ No newline at end of file +{"db79d3a910f4": {"com.docker.compose.config-hash":"5bde40f52451521ad201e70de1291397376a0498a7c955624a609da3b60e7e8e","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:ea39067705590557dd0cd951664a10970ceefcb725a3c1f43690d6d6d4ed5fce","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-multi-containers.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"haproxy","com.docker.compose.version":"2.8.0"}, +"3e63154954b0": {"com.docker.compose.config-hash":"4e0cbdd8372c6779863799e5021ed8178f74b55bd8e070abcdffaf87eb7baa36","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-multi-containers.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"hello.com\n, www.helloworld.com\n","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}"}, +"eb294c110eb1": {"com.docker.compose.config-hash":"4e0cbdd8372c6779863799e5021ed8178f74b55bd8e070abcdffaf87eb7baa36","com.docker.compose.container-number":"2","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-multi-containers.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"hello.com\n, www.helloworld.com\n","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}"}} \ No newline at end of file diff --git a/src/tests/fixtures/services-redirect-ssl b/src/tests/fixtures/services-redirect-ssl index 718450b..1e4a539 100644 --- a/src/tests/fixtures/services-redirect-ssl +++ b/src/tests/fixtures/services-redirect-ssl @@ -1,3 +1,3 @@ -3571640c480a={"com.docker.compose.config-hash":"b3089bebef089144accf08bc189c2dbd527cb2ff2b44f0fd57f031b3258d9389","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:c4232396c715f3d568816c666e6d9b4a68ef6c36f6243b4007c4ee1d8335fd65","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"container2","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"host2.local","easyhaproxy.http.port":"80","easyhaproxy.http.redirect_ssl":"true","easyhaproxy.https.host":"host2.local","easyhaproxy.https.localport":"8080","easyhaproxy.https.port":"443","easyhaproxy.https.ssl":"true","io.buildah.version":"1.21.0"} -5b69bc7fea1b={"com.docker.compose.config-hash":"e2e9424b6ef2adfa33f750e3894446c9200f24674f5cf601ad60a87430e14314","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:c4232396c715f3d568816c666e6d9b4a68ef6c36f6243b4007c4ee1d8335fd65","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"container","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"host1.local","easyhaproxy.http.port":"80","easyhaproxy.http.redirect_ssl":"true","easyhaproxy.https.host":"host1.local","easyhaproxy.https.localport":"8080","easyhaproxy.https.port":"443","easyhaproxy.https.sslcert":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZEVENDQXZXZ0F3SUJBZ0lVUmkrdzFaVmdlZWRUbE5JQXdxUUJNSnY2ZFhzd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0ZqRVVNQklHQTFVRUF3d0xhRzl6ZERFdWJHOWpZV3d3SGhjTk1qRXdPREV3TVRnME9UQTJXaGNOTXpFdwpPREE0TVRnME9UQTJXakFXTVJRd0VnWURWUVFEREF0b2IzTjBNUzVzYjJOaGJEQ0NBaUl3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFNQkRBaExBeWdKdWFXNnc2ZmZpZ3pUQUFHWHBtRXowdEl4bjFrNFoKeDV3TjVycHYvcXUwUU1ZeitBdjJ1MWVPS0VLWmVhRlJWcFQwcjkzZFg3SXZiRVpIdDI1R1BpQnZsTEdxaGpLUgpQblNrLzdVOFhtc250dFVBVjdyVkVLMVVyZEZ3OC9Jd3JpUUMrZGhyMG1uWWZTRE1rdkJvTUZwZGhWTlRyYkFaCjFUQjZyUWpFN0FyME10OG15OTZYSm13cmNqSzJUaitFMnJnUElVejFlNWNla0ZZSURTQmF0bXcrMyt2citUNXgKRk5Ga0oybzMwVzVvOFpmbENKSnpyVmFpaHFRaWNzNlpLRGdwZjdpcVhNRml3V0lsaGRRcEd2eDVHZi9LRlRLOQpVYU9uUlp6L1grMkNlYkFGYVRIUjNrL1BZcHBXVGdCQkJ1UnZscEN3K3dkbmttdGVDMFNRUkY5MVFXVnI3ZWpvCjdLYU9sR0k1VnR2TVVzV3ZUZUFabXBheW1JYUFURVR1T0phWTBKVTExT21MZUQ5RE9qNUUyU1E3cUlYL3BGY3AKeHB6RzVqNGMrTWxndnhQMlZBa05UZUFYQ2FZaVBCUUg1WlpnMEhFMlduQjFLaExSRmxIZDRpSFFEMkdKNXlOLwo2ZkNGQmZaZktTZUs4SmF1d3hnV2tyYTUzT2NEcS9tS2QrREEvZEsrL3J1Rzd0cXdWZ0lhMDRIT3Bsek03TFlSCkdCMElyczkrbHI1L1BKYlFabVUwNzNNZG42Y1hBZzNwKzZ3dndGbERrUzV2MTNnQkRZTkh0RjYyYmM1NTFlZEYKWjZrR3pKN3dtR1JvODRhQlA3TXVSWmVSZUxPclNTNjdhMXdMZHpac01uUDFUSjd4OUxmcjlNS2wydURuUWRuWQpleDhEQWdNQkFBR2pVekJSTUIwR0ExVWREZ1FXQkJTUS9tdFpkNmg4ZW45WVFWSDZITzFQbFdXaXF6QWZCZ05WCkhTTUVHREFXZ0JTUS9tdFpkNmg4ZW45WVFWSDZITzFQbFdXaXF6QVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BMEcKQ1NxR1NJYjNEUUVCQ3dVQUE0SUNBUUNoUVlOdWFoMyttVHBJQkRZeEdyalRKTnVPVElNYVd6TXlpMXRrZitMMApzRUd3cGJtQU8ybVdXUVlGN1dWTHNpOThQVUxoM2FkanQyaml1ZDlWbGFhQzZnbnduNVpvMStQaWxvOXNOTExXCjZpajArck40a3dJbS9wTnFpK2pEdXUyY3ZBdUhJd1pXZWg4YkVlLzVVQ3hvNGlobVdGUU44ZUo2VFVLQ3BoUkMKNkVvci9TU1paQlFIZ1BsMEJjaHpIT2t3dTdSM0xDbmRScXhqaEFvVmI5eVFPVitac21UZUpYdWx3TnpKMXVMdApUOE9JZ0lpRHBtQm83SFNOMkgwazNjaHgwMEFzalV5SjltbUFXUGVqRmUvS1hMUlBjVlpSMTdqaHpnZklCRXpzCk01V3RXRm0xYUhEalZ2Nk02aXRlVm02MUU5VCtrL00xMXJ1MWUyWXdzeFREdmI2eDA0bWNyTnU5c29xZGRCYnIKVmZwbHV1b1EvaEVBYlh0Rk5Qb1R5U3B6MGN3T3djSENvd1ZPTG1kS2d2SW1zelppTXlISEc4VkdHbVBoODhuNwp3VnhiMGdWMFA0Uk1yY01MZGVUZG41NVlRcjFDcUJyMzRlQjZvbDZBc2JUbTNWekJIUlZtRk5rc2wxbzVKQjV0CnRYTGdGL0c4L3J6Si80bTFQYVZ1eHJCN0R4VW1JazhFUGJTSVZrdlp2ZDdMQnpLd1E2SWZWYXVjZXdIZkVhalEKVklpZXhTTWlGYzdsdzNLbnhqT0haamY2Rk05VllnM05vKytHZEM5OXM3TGtJdUp3QU1MTnFUUTdIdmhuN1l2UAo0RmxTSWdjNnhqMFlrR1pFUWxiNW8vNW5hdUVxUVUwQUJndzZqdEk0TnhyTkxUNmNwN0NPNE0weElERWcvM1lECmFBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQotLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS0KTUlJSlF3SUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NDUzB3Z2drcEFnRUFBb0lDQVFEQVF3SVN3TW9DYm1sdQpzT24zNG9NMHdBQmw2WmhNOUxTTVo5Wk9HY2VjRGVhNmIvNnJ0RURHTS9nTDlydFhqaWhDbVhtaFVWYVU5Sy9kCjNWK3lMMnhHUjdkdVJqNGdiNVN4cW9ZeWtUNTBwUCsxUEY1cko3YlZBRmU2MVJDdFZLM1JjUFB5TUs0a0F2blkKYTlKcDJIMGd6Skx3YURCYVhZVlRVNjJ3R2RVd2VxMEl4T3dLOURMZkpzdmVseVpzSzNJeXRrNC9oTnE0RHlGTQo5WHVYSHBCV0NBMGdXclpzUHQvcjYvaytjUlRSWkNkcU45RnVhUEdYNVFpU2M2MVdvb2FrSW5MT21TZzRLWCs0CnFsekJZc0ZpSllYVUtScjhlUm4veWhVeXZWR2pwMFdjLzEvdGdubXdCV2t4MGQ1UHoyS2FWazRBUVFia2I1YVEKc1BzSFo1SnJYZ3RFa0VSZmRVRmxhKzNvNk95bWpwUmlPVmJiekZMRnIwM2dHWnFXc3BpR2dFeEU3amlXbU5DVgpOZFRwaTNnL1F6bytSTmtrTzZpRi82UlhLY2FjeHVZK0hQakpZTDhUOWxRSkRVM2dGd21tSWp3VUIrV1dZTkJ4Ck5scHdkU29TMFJaUjNlSWgwQTloaWVjamYrbndoUVgyWHlrbml2Q1dyc01ZRnBLMnVkem5BNnY1aW5mZ3dQM1MKdnY2N2h1N2FzRllDR3RPQnpxWmN6T3kyRVJnZENLN1BmcGErZnp5VzBHWmxOTzl6SForbkZ3SU42ZnVzTDhCWgpRNUV1YjlkNEFRMkRSN1JldG0zT2VkWG5SV2VwQnN5ZThKaGthUE9HZ1QrekxrV1hrWGl6cTBrdXUydGNDM2MyCmJESno5VXllOGZTMzYvVENwZHJnNTBIWjJIc2ZBd0lEQVFBQkFvSUNBUUMveFpiWjBjY3RxYWdzcXZhVk5URWUKZXExcStoZmFHdlBFWVFhWUhJcklFKzJpNVhjbkdjTEtjS2ZvZHhEakFuOFIvemdkT3A2Y01YMENWbi9Qb2hIawpBRUR0RTgrQVZ3d0FNMUZzT3dnTEhWR2FHejhxcnhCbFlkUWdIY3BtdWVJdTJQWGJDOGVIVUJpYVVPSXVoYXc1Ci9SUk1EQUMvQWkyc3NmaTdnT2p2VkU0b1F4UVcwUUcxS0dPT0FVSm4vdVlIdzJSRlkyVXUxcGlteE8ya0RPNTMKZ2N4bUMxV09ueUNIbUhhaVcvVWg3ejZKYW1mU000ZFh0VEpac2x5aDM3ZGhIS05iZzlWa1A3Q1FLQTRoTHpvcApoYmY1cVk2cmFyZ09OaW55MUhnTVB4cm13S3VVb3VKeU90TjB5QnR4akRDVU5hWFVCd2l5N3NOR1MrSDR2c3lCCjVQOUhoSUhTdHUrRlp0M0hHN0VJcUNuZGlhU0tEUzRqV2FWUUFiYm80bloyWnMyQkQreERlUFJDUlVxWDdyTTQKNFh6UElSV1dYbW1XZi83SWcyOUhicnA0YTlMY09tUTJsZUNKdGJhVEZTTjk2T0xVSjVFK2hRMHVsQ1pnQlZtUQpSQ1VZa0pQNGxPemJhS2R6anhnSE1ySHptNDVlVUZmOExpck94aTJ1eXhYSFFtRE51NGIzWDE4a3QzUGdVbVVtCjNkWHBsM2ZxU3lKYTdTQ1Y4Wk5CcnNyRHExRSt0aFl0dTkxUWJWU0d4SGQ5SHJOVmUzWGRMYk9DZFU5Q3VDNjkKTmdsem5hYTdzWkxxbXlLZWpUZkdzWTd4cldkTmNNUGw0cDRmY0lEL080RXBBU1pmb3JwVGVLTlQwWklmWlpldwpiMG1BUWVZWnFRTThpL3FNWU4vdUFRS0NBUUVBNXFnMXNSTk1jNlZkTS90UmdsYXNHWW94amdSQzJPcUFEWmdzCm1BWE1VSjNrRXJweXh0K2VDaW15OGlidVlwelJUSVE4ZkJUV1JrQ3RSWlhKNytLY0xWdGs5UVpJb0xiaHlOd2QKNEl4RVFaRnVVbGpEYnZTalRMU3ljc0h2bzY1aWJXSWZUTDdiZ1dsTEdnR3EvVU96ZkdzZ0g2Uzl3THA1RzMwRwo4RUx5akk1ZVRJWUlDcmZUbVZMK2M0NU1ScEVNS28rY3Z6OFB5c2lhT0ZUbjNjeXN3UFZkWWFlRUVxTVFqVTh3CklHTnNHWkx5dFk3QkFCQmNZMGxkcnRiYS9PK0Z2LytSSDd1VXR6UDd4cENJd0ZDeDgwWnpOK1dSeTlOdkk2M1UKenEzeUlCb1c5R3lBcEQyK1BMYVBOeGY3UUxUVUNoWTFaei9kWVJsdEtPeHYyQWE1Z1FLQ0FRRUExV0xXTnFwMApmaEIvWnRmU0VTaHhGTU04OWNqTjZBYXoxV0tMN3VUQm91OW9TSm54amtoa2FWNzZhY25UL2lxWHR4TU5nSGkxCmZJbURwVTNQdk0wWTRVZDJUNDdvSGM2UDFCclpQTi9HbVh5L3M2QkFFZFB3TGU3Sis0blRJU0hBZEdtcmgrYS8KNXBrdHUzMmc5bFdxZnR4ZWNGSVZTTFBXa3hUMFhLaU14cDFmZmtMK09hdnBNZ01GWks0MWlLczNkTlNoS1BvZwpMOEdTUGNQOXgveW43OFAyZUszTitQR2psQTZwUHpyQU55V1U3TjAvYm1IY0I5VEtQK3VkWVdjalZocnU3TVlOCndOckU0a0tkQzh2OGk3eDd0RGJ2Yjc5VCtGbzZQSWg1M3AwT3NuWnpBOFVSMFFOUit2RFF1ZlF1eWFqOFJFQysKWkc4WXlDS3N2azh5Z3dLQ0FRQS9mc1N4QjBmL2VlRXJZeDZ3QzUzNnRlRW9ZQ0hxeHJzVGd2V2JyOVRyeUZzMQprSi95QVRMblIwMWNmYjBYNW1WemM5K1dwTUhMdXhnMzFLRXZhU2xuRHdhK3NNa2pmTlN3ejI5bUZoYmdHZUhOCngyT2RVcmoxYjdURUJJRXNoTi9SanJaaEVSVXFEY3MvMEgrNmtuMkJYWmdOUGZPQ2I1TFJMMXpPblE5YUJBTVAKZThJUStVUEZyR1FoZVdXajgxL3ZBM081N2VreUFJRDd5dHU5WWcrWVdyTW5JODhtdGo3ak40NWZEQitBOXNQYgptUDJtUDlxKzlqNVUyQTZXbkhVc1FuVTMwQktEVUVzYUFVV3o4MExaWG1adlY4SUg0eDl3S2ZVd0pCQklLQVp6CnFMN005N1k3em1Ha1gvU3BmbDMwbk9KOGxzY2hhTGQxRVlsRVphMkJBb0lCQVFDeWUyNVQ0VFY1TUpGdjB6dVoKTUd1TmcxU2MvTzRGa24yZkVVT2NlV2pod1VCSDRjUGpUL2YxRHdXRHNOYUo5TlJieENyNTkzMU9BclBEYzVjOApBNDA0K1k0ak01UkJRa0tabGk5NHRIQW9kK2pjOVVCQjZUVXZKbGw1OVNsTXdDOTY3OXdTMjFaT0tuZlBLR0NYClNzWkdRRXNaeGY2Wmhoc0hnWEozZ2wvbHpVSlBtUGVPQTVZVlIrT2Q5LzA5S0lGRlRvalNmb3luaFZDdUt4NDkKeGI0dVZZbjJIT0o0eEowZlBUZ2hkQ0hNdnJtWGVlUVJqdmI4OGVhTm1xVlVFSEhGRnRnYjRma2xBNWZFN1JUeApCaGxpUkRCd1o3YlVrSU5LNnlWazluNkJUbnM1bU12UkxtZ2RuSnBZdkU3S0MwMkxUYlpiM0krajhDMFpVYStOCnF5N0RBb0lCQUFpZXJpYlM3V1VjbDJhQmxrbTUrVzdxTm0vSU5tNXp2bm9TUG82VjN3YTVoczZmOStDL2tiZEYKODdqUVBBL1lGZTN1UjJzQUo3c2xYNWV1Wks4V21mcEZtZ3psdTBzRXo4MU1MUS9XeXBadFp5dHlWdFd6QjJQdQpYQ1cxdGRTSDllSTJCbWhYZ29rSE5UTTQ4TmsveE9FTnJQL3NlWHJJeDVMSzBobkRIWm90dS96NitZU2tCOWhGCmNtMmZaeWdEMWRNTFg2bGlSaW14eUZZK2RJQ0pOQjk1SmlmVExXWW5XZUdkZGt3UHRYVWVHWEUxb2x6dk5rTEQKek16RTA5dWhreC9sUkpudGVPQkVaYWY4ME9CLzA5T2k5YjkvcnhZNTlkd3NINkdheExvVGZFS3VQbnZCVk1OUgpZa1UxNFd6UUtsZUZraUJKSTlsVnZuZmdHbk9sZ2cwPQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t","io.buildah.version":"1.21.0"} -77094604be33={"com.docker.compose.config-hash":"b671dd783e367918c604400fe83cd2450c845a555bda6769bd1351bdcce3c565","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:4e24fb65b607dc3b105ab5ec5945707ccbf50ac32605e7f4935440b453b7c45c","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"haproxy","com.docker.compose.version":"2.8.0"} +{"3571640c480a": {"com.docker.compose.config-hash":"b3089bebef089144accf08bc189c2dbd527cb2ff2b44f0fd57f031b3258d9389","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:c4232396c715f3d568816c666e6d9b4a68ef6c36f6243b4007c4ee1d8335fd65","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"container2","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"host2.local","easyhaproxy.http.port":"80","easyhaproxy.http.redirect_ssl":"true","easyhaproxy.https.host":"host2.local","easyhaproxy.https.localport":"8080","easyhaproxy.https.port":"443","easyhaproxy.https.ssl":"true","io.buildah.version":"1.21.0"}, +"5b69bc7fea1b": {"com.docker.compose.config-hash":"e2e9424b6ef2adfa33f750e3894446c9200f24674f5cf601ad60a87430e14314","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:c4232396c715f3d568816c666e6d9b4a68ef6c36f6243b4007c4ee1d8335fd65","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"container","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"host1.local","easyhaproxy.http.port":"80","easyhaproxy.http.redirect_ssl":"true","easyhaproxy.https.host":"host1.local","easyhaproxy.https.localport":"8080","easyhaproxy.https.port":"443","easyhaproxy.https.sslcert":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZEVENDQXZXZ0F3SUJBZ0lVUmkrdzFaVmdlZWRUbE5JQXdxUUJNSnY2ZFhzd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0ZqRVVNQklHQTFVRUF3d0xhRzl6ZERFdWJHOWpZV3d3SGhjTk1qRXdPREV3TVRnME9UQTJXaGNOTXpFdwpPREE0TVRnME9UQTJXakFXTVJRd0VnWURWUVFEREF0b2IzTjBNUzVzYjJOaGJEQ0NBaUl3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFNQkRBaExBeWdKdWFXNnc2ZmZpZ3pUQUFHWHBtRXowdEl4bjFrNFoKeDV3TjVycHYvcXUwUU1ZeitBdjJ1MWVPS0VLWmVhRlJWcFQwcjkzZFg3SXZiRVpIdDI1R1BpQnZsTEdxaGpLUgpQblNrLzdVOFhtc250dFVBVjdyVkVLMVVyZEZ3OC9Jd3JpUUMrZGhyMG1uWWZTRE1rdkJvTUZwZGhWTlRyYkFaCjFUQjZyUWpFN0FyME10OG15OTZYSm13cmNqSzJUaitFMnJnUElVejFlNWNla0ZZSURTQmF0bXcrMyt2citUNXgKRk5Ga0oybzMwVzVvOFpmbENKSnpyVmFpaHFRaWNzNlpLRGdwZjdpcVhNRml3V0lsaGRRcEd2eDVHZi9LRlRLOQpVYU9uUlp6L1grMkNlYkFGYVRIUjNrL1BZcHBXVGdCQkJ1UnZscEN3K3dkbmttdGVDMFNRUkY5MVFXVnI3ZWpvCjdLYU9sR0k1VnR2TVVzV3ZUZUFabXBheW1JYUFURVR1T0phWTBKVTExT21MZUQ5RE9qNUUyU1E3cUlYL3BGY3AKeHB6RzVqNGMrTWxndnhQMlZBa05UZUFYQ2FZaVBCUUg1WlpnMEhFMlduQjFLaExSRmxIZDRpSFFEMkdKNXlOLwo2ZkNGQmZaZktTZUs4SmF1d3hnV2tyYTUzT2NEcS9tS2QrREEvZEsrL3J1Rzd0cXdWZ0lhMDRIT3Bsek03TFlSCkdCMElyczkrbHI1L1BKYlFabVUwNzNNZG42Y1hBZzNwKzZ3dndGbERrUzV2MTNnQkRZTkh0RjYyYmM1NTFlZEYKWjZrR3pKN3dtR1JvODRhQlA3TXVSWmVSZUxPclNTNjdhMXdMZHpac01uUDFUSjd4OUxmcjlNS2wydURuUWRuWQpleDhEQWdNQkFBR2pVekJSTUIwR0ExVWREZ1FXQkJTUS9tdFpkNmg4ZW45WVFWSDZITzFQbFdXaXF6QWZCZ05WCkhTTUVHREFXZ0JTUS9tdFpkNmg4ZW45WVFWSDZITzFQbFdXaXF6QVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BMEcKQ1NxR1NJYjNEUUVCQ3dVQUE0SUNBUUNoUVlOdWFoMyttVHBJQkRZeEdyalRKTnVPVElNYVd6TXlpMXRrZitMMApzRUd3cGJtQU8ybVdXUVlGN1dWTHNpOThQVUxoM2FkanQyaml1ZDlWbGFhQzZnbnduNVpvMStQaWxvOXNOTExXCjZpajArck40a3dJbS9wTnFpK2pEdXUyY3ZBdUhJd1pXZWg4YkVlLzVVQ3hvNGlobVdGUU44ZUo2VFVLQ3BoUkMKNkVvci9TU1paQlFIZ1BsMEJjaHpIT2t3dTdSM0xDbmRScXhqaEFvVmI5eVFPVitac21UZUpYdWx3TnpKMXVMdApUOE9JZ0lpRHBtQm83SFNOMkgwazNjaHgwMEFzalV5SjltbUFXUGVqRmUvS1hMUlBjVlpSMTdqaHpnZklCRXpzCk01V3RXRm0xYUhEalZ2Nk02aXRlVm02MUU5VCtrL00xMXJ1MWUyWXdzeFREdmI2eDA0bWNyTnU5c29xZGRCYnIKVmZwbHV1b1EvaEVBYlh0Rk5Qb1R5U3B6MGN3T3djSENvd1ZPTG1kS2d2SW1zelppTXlISEc4VkdHbVBoODhuNwp3VnhiMGdWMFA0Uk1yY01MZGVUZG41NVlRcjFDcUJyMzRlQjZvbDZBc2JUbTNWekJIUlZtRk5rc2wxbzVKQjV0CnRYTGdGL0c4L3J6Si80bTFQYVZ1eHJCN0R4VW1JazhFUGJTSVZrdlp2ZDdMQnpLd1E2SWZWYXVjZXdIZkVhalEKVklpZXhTTWlGYzdsdzNLbnhqT0haamY2Rk05VllnM05vKytHZEM5OXM3TGtJdUp3QU1MTnFUUTdIdmhuN1l2UAo0RmxTSWdjNnhqMFlrR1pFUWxiNW8vNW5hdUVxUVUwQUJndzZqdEk0TnhyTkxUNmNwN0NPNE0weElERWcvM1lECmFBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQotLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS0KTUlJSlF3SUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NDUzB3Z2drcEFnRUFBb0lDQVFEQVF3SVN3TW9DYm1sdQpzT24zNG9NMHdBQmw2WmhNOUxTTVo5Wk9HY2VjRGVhNmIvNnJ0RURHTS9nTDlydFhqaWhDbVhtaFVWYVU5Sy9kCjNWK3lMMnhHUjdkdVJqNGdiNVN4cW9ZeWtUNTBwUCsxUEY1cko3YlZBRmU2MVJDdFZLM1JjUFB5TUs0a0F2blkKYTlKcDJIMGd6Skx3YURCYVhZVlRVNjJ3R2RVd2VxMEl4T3dLOURMZkpzdmVseVpzSzNJeXRrNC9oTnE0RHlGTQo5WHVYSHBCV0NBMGdXclpzUHQvcjYvaytjUlRSWkNkcU45RnVhUEdYNVFpU2M2MVdvb2FrSW5MT21TZzRLWCs0CnFsekJZc0ZpSllYVUtScjhlUm4veWhVeXZWR2pwMFdjLzEvdGdubXdCV2t4MGQ1UHoyS2FWazRBUVFia2I1YVEKc1BzSFo1SnJYZ3RFa0VSZmRVRmxhKzNvNk95bWpwUmlPVmJiekZMRnIwM2dHWnFXc3BpR2dFeEU3amlXbU5DVgpOZFRwaTNnL1F6bytSTmtrTzZpRi82UlhLY2FjeHVZK0hQakpZTDhUOWxRSkRVM2dGd21tSWp3VUIrV1dZTkJ4Ck5scHdkU29TMFJaUjNlSWgwQTloaWVjamYrbndoUVgyWHlrbml2Q1dyc01ZRnBLMnVkem5BNnY1aW5mZ3dQM1MKdnY2N2h1N2FzRllDR3RPQnpxWmN6T3kyRVJnZENLN1BmcGErZnp5VzBHWmxOTzl6SForbkZ3SU42ZnVzTDhCWgpRNUV1YjlkNEFRMkRSN1JldG0zT2VkWG5SV2VwQnN5ZThKaGthUE9HZ1QrekxrV1hrWGl6cTBrdXUydGNDM2MyCmJESno5VXllOGZTMzYvVENwZHJnNTBIWjJIc2ZBd0lEQVFBQkFvSUNBUUMveFpiWjBjY3RxYWdzcXZhVk5URWUKZXExcStoZmFHdlBFWVFhWUhJcklFKzJpNVhjbkdjTEtjS2ZvZHhEakFuOFIvemdkT3A2Y01YMENWbi9Qb2hIawpBRUR0RTgrQVZ3d0FNMUZzT3dnTEhWR2FHejhxcnhCbFlkUWdIY3BtdWVJdTJQWGJDOGVIVUJpYVVPSXVoYXc1Ci9SUk1EQUMvQWkyc3NmaTdnT2p2VkU0b1F4UVcwUUcxS0dPT0FVSm4vdVlIdzJSRlkyVXUxcGlteE8ya0RPNTMKZ2N4bUMxV09ueUNIbUhhaVcvVWg3ejZKYW1mU000ZFh0VEpac2x5aDM3ZGhIS05iZzlWa1A3Q1FLQTRoTHpvcApoYmY1cVk2cmFyZ09OaW55MUhnTVB4cm13S3VVb3VKeU90TjB5QnR4akRDVU5hWFVCd2l5N3NOR1MrSDR2c3lCCjVQOUhoSUhTdHUrRlp0M0hHN0VJcUNuZGlhU0tEUzRqV2FWUUFiYm80bloyWnMyQkQreERlUFJDUlVxWDdyTTQKNFh6UElSV1dYbW1XZi83SWcyOUhicnA0YTlMY09tUTJsZUNKdGJhVEZTTjk2T0xVSjVFK2hRMHVsQ1pnQlZtUQpSQ1VZa0pQNGxPemJhS2R6anhnSE1ySHptNDVlVUZmOExpck94aTJ1eXhYSFFtRE51NGIzWDE4a3QzUGdVbVVtCjNkWHBsM2ZxU3lKYTdTQ1Y4Wk5CcnNyRHExRSt0aFl0dTkxUWJWU0d4SGQ5SHJOVmUzWGRMYk9DZFU5Q3VDNjkKTmdsem5hYTdzWkxxbXlLZWpUZkdzWTd4cldkTmNNUGw0cDRmY0lEL080RXBBU1pmb3JwVGVLTlQwWklmWlpldwpiMG1BUWVZWnFRTThpL3FNWU4vdUFRS0NBUUVBNXFnMXNSTk1jNlZkTS90UmdsYXNHWW94amdSQzJPcUFEWmdzCm1BWE1VSjNrRXJweXh0K2VDaW15OGlidVlwelJUSVE4ZkJUV1JrQ3RSWlhKNytLY0xWdGs5UVpJb0xiaHlOd2QKNEl4RVFaRnVVbGpEYnZTalRMU3ljc0h2bzY1aWJXSWZUTDdiZ1dsTEdnR3EvVU96ZkdzZ0g2Uzl3THA1RzMwRwo4RUx5akk1ZVRJWUlDcmZUbVZMK2M0NU1ScEVNS28rY3Z6OFB5c2lhT0ZUbjNjeXN3UFZkWWFlRUVxTVFqVTh3CklHTnNHWkx5dFk3QkFCQmNZMGxkcnRiYS9PK0Z2LytSSDd1VXR6UDd4cENJd0ZDeDgwWnpOK1dSeTlOdkk2M1UKenEzeUlCb1c5R3lBcEQyK1BMYVBOeGY3UUxUVUNoWTFaei9kWVJsdEtPeHYyQWE1Z1FLQ0FRRUExV0xXTnFwMApmaEIvWnRmU0VTaHhGTU04OWNqTjZBYXoxV0tMN3VUQm91OW9TSm54amtoa2FWNzZhY25UL2lxWHR4TU5nSGkxCmZJbURwVTNQdk0wWTRVZDJUNDdvSGM2UDFCclpQTi9HbVh5L3M2QkFFZFB3TGU3Sis0blRJU0hBZEdtcmgrYS8KNXBrdHUzMmc5bFdxZnR4ZWNGSVZTTFBXa3hUMFhLaU14cDFmZmtMK09hdnBNZ01GWks0MWlLczNkTlNoS1BvZwpMOEdTUGNQOXgveW43OFAyZUszTitQR2psQTZwUHpyQU55V1U3TjAvYm1IY0I5VEtQK3VkWVdjalZocnU3TVlOCndOckU0a0tkQzh2OGk3eDd0RGJ2Yjc5VCtGbzZQSWg1M3AwT3NuWnpBOFVSMFFOUit2RFF1ZlF1eWFqOFJFQysKWkc4WXlDS3N2azh5Z3dLQ0FRQS9mc1N4QjBmL2VlRXJZeDZ3QzUzNnRlRW9ZQ0hxeHJzVGd2V2JyOVRyeUZzMQprSi95QVRMblIwMWNmYjBYNW1WemM5K1dwTUhMdXhnMzFLRXZhU2xuRHdhK3NNa2pmTlN3ejI5bUZoYmdHZUhOCngyT2RVcmoxYjdURUJJRXNoTi9SanJaaEVSVXFEY3MvMEgrNmtuMkJYWmdOUGZPQ2I1TFJMMXpPblE5YUJBTVAKZThJUStVUEZyR1FoZVdXajgxL3ZBM081N2VreUFJRDd5dHU5WWcrWVdyTW5JODhtdGo3ak40NWZEQitBOXNQYgptUDJtUDlxKzlqNVUyQTZXbkhVc1FuVTMwQktEVUVzYUFVV3o4MExaWG1adlY4SUg0eDl3S2ZVd0pCQklLQVp6CnFMN005N1k3em1Ha1gvU3BmbDMwbk9KOGxzY2hhTGQxRVlsRVphMkJBb0lCQVFDeWUyNVQ0VFY1TUpGdjB6dVoKTUd1TmcxU2MvTzRGa24yZkVVT2NlV2pod1VCSDRjUGpUL2YxRHdXRHNOYUo5TlJieENyNTkzMU9BclBEYzVjOApBNDA0K1k0ak01UkJRa0tabGk5NHRIQW9kK2pjOVVCQjZUVXZKbGw1OVNsTXdDOTY3OXdTMjFaT0tuZlBLR0NYClNzWkdRRXNaeGY2Wmhoc0hnWEozZ2wvbHpVSlBtUGVPQTVZVlIrT2Q5LzA5S0lGRlRvalNmb3luaFZDdUt4NDkKeGI0dVZZbjJIT0o0eEowZlBUZ2hkQ0hNdnJtWGVlUVJqdmI4OGVhTm1xVlVFSEhGRnRnYjRma2xBNWZFN1JUeApCaGxpUkRCd1o3YlVrSU5LNnlWazluNkJUbnM1bU12UkxtZ2RuSnBZdkU3S0MwMkxUYlpiM0krajhDMFpVYStOCnF5N0RBb0lCQUFpZXJpYlM3V1VjbDJhQmxrbTUrVzdxTm0vSU5tNXp2bm9TUG82VjN3YTVoczZmOStDL2tiZEYKODdqUVBBL1lGZTN1UjJzQUo3c2xYNWV1Wks4V21mcEZtZ3psdTBzRXo4MU1MUS9XeXBadFp5dHlWdFd6QjJQdQpYQ1cxdGRTSDllSTJCbWhYZ29rSE5UTTQ4TmsveE9FTnJQL3NlWHJJeDVMSzBobkRIWm90dS96NitZU2tCOWhGCmNtMmZaeWdEMWRNTFg2bGlSaW14eUZZK2RJQ0pOQjk1SmlmVExXWW5XZUdkZGt3UHRYVWVHWEUxb2x6dk5rTEQKek16RTA5dWhreC9sUkpudGVPQkVaYWY4ME9CLzA5T2k5YjkvcnhZNTlkd3NINkdheExvVGZFS3VQbnZCVk1OUgpZa1UxNFd6UUtsZUZraUJKSTlsVnZuZmdHbk9sZ2cwPQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t","io.buildah.version":"1.21.0"}, +"77094604be33": {"com.docker.compose.config-hash":"b671dd783e367918c604400fe83cd2450c845a555bda6769bd1351bdcce3c565","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:4e24fb65b607dc3b105ab5ec5945707ccbf50ac32605e7f4935440b453b7c45c","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"haproxy","com.docker.compose.version":"2.8.0"}} \ No newline at end of file diff --git a/src/tests/fixtures/services-tcp b/src/tests/fixtures/services-tcp index 41f7410..5b5c2b0 100644 --- a/src/tests/fixtures/services-tcp +++ b/src/tests/fixtures/services-tcp @@ -1,2 +1,2 @@ -test_agent={"easyhaproxy.agent.host":"agent.quantum.local","easyhaproxy.agent.localport":"9001","easyhaproxy.agent.mode":"tcp","easyhaproxy.agent.port":"31339","com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"test", "easyhaproxy.agent.health-check":"ssl"} -test_proxy={"com.docker.stack.image":"byjg/easy-haproxy:local","com.docker.stack.namespace":"test"} +{"test_agent": {"easyhaproxy.agent.host":"agent.quantum.local","easyhaproxy.agent.localport":"9001","easyhaproxy.agent.mode":"tcp","easyhaproxy.agent.port":"31339","com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"test", "easyhaproxy.agent.health-check":"ssl"}, +"test_proxy": {"com.docker.stack.image":"byjg/easy-haproxy:local","com.docker.stack.namespace":"test"}} \ No newline at end of file diff --git a/src/tests/test_parser.py b/src/tests/test_parser.py index 81b4dfc..f46e6bf 100644 --- a/src/tests/test_parser.py +++ b/src/tests/test_parser.py @@ -2,6 +2,7 @@ import easymapping import pytest import os import yaml +import json CERTS_FOLDER="/tmp/certs" CERT_FILE="/tmp/certs/haproxy/www.somehost.com.br.pem" @@ -10,7 +11,7 @@ LETSENCRYPT_EMAIL="some@email.com" def load_fixture(file): path = os.path.dirname(os.path.realpath(__file__)) with open(path + "/fixtures/" + file, 'r') as content_file: - line_list = content_file.readlines() + line_list = json.loads("".join(content_file.readlines())) return line_list @@ -25,7 +26,7 @@ def test_parser_doesnt_crash(): } } - cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) + cfg = easymapping.HaproxyConfigGenerator(result) haproxy_config = cfg.generate(line_list) assert len(haproxy_config) > 0 @@ -47,10 +48,7 @@ def test_parser_finds_services(): } } - if os.path.exists(CERT_FILE): - os.remove(CERT_FILE) - - cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) + cfg = easymapping.HaproxyConfigGenerator(result) haproxy_config = cfg.generate(line_list) assert len(haproxy_config) > 0 @@ -58,8 +56,7 @@ def test_parser_finds_services(): with open(path + "/expected/services.txt", 'r') as expected_file: assert expected_file.read() == haproxy_config - with open(CERT_FILE, 'r') as expected_file: - assert expected_file.read() == "Some PEM Certificate" + assert {"www.somehost.com.br.pem":"Some PEM Certificate"} == cfg.certs assert ['node-exporter.quantum.example.org'] == cfg.letsencrypt_hosts @@ -80,7 +77,7 @@ def test_parser_finds_services_changed_label(): if os.path.exists(CERT_FILE): os.remove(CERT_FILE) - cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) + cfg = easymapping.HaproxyConfigGenerator(result) haproxy_config = cfg.generate(line_list) assert len(haproxy_config) > 0 @@ -88,8 +85,7 @@ def test_parser_finds_services_changed_label(): with open(path + "/expected/services.txt", 'r') as expected_file: assert expected_file.read() == haproxy_config - with open(CERT_FILE, 'r') as expected_file: - assert expected_file.read() == "Some PEM Certificate" + assert {"www.somehost.com.br.pem":"Some PEM Certificate"} == cfg.certs assert ['node-exporter.quantum.example.org'] == cfg.letsencrypt_hosts @@ -109,7 +105,7 @@ def test_parser_finds_services_raw(): if os.path.exists(CERT_FILE): os.remove(CERT_FILE) - cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) + cfg = easymapping.HaproxyConfigGenerator(result) parsed_object = [ { @@ -217,7 +213,7 @@ def test_parser_static(): with open(path + "/fixtures/static.yml", 'r') as content_file: parsed = yaml.load(content_file.read(), Loader=yaml.FullLoader) - cfg = easymapping.HaproxyConfigGenerator(parsed, CERTS_FOLDER) + cfg = easymapping.HaproxyConfigGenerator(parsed) haproxy_config = cfg.generate() assert len(haproxy_config) > 0 @@ -295,7 +291,7 @@ def test_parser_tcp(): } } - cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) + cfg = easymapping.HaproxyConfigGenerator(result) haproxy_config = cfg.generate(line_list) # print(haproxy_config) @@ -315,7 +311,7 @@ def test_parser_multi_containers(): } } - cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) + cfg = easymapping.HaproxyConfigGenerator(result) haproxy_config = cfg.generate(line_list) assert len(haproxy_config) > 0 @@ -337,7 +333,7 @@ def test_parser_multiple_hosts(): } } - cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) + cfg = easymapping.HaproxyConfigGenerator(result) haproxy_config = cfg.generate(line_list) assert len(haproxy_config) > 0 @@ -358,7 +354,7 @@ def test_parser_redirect_ssl(): } } - cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) + cfg = easymapping.HaproxyConfigGenerator(result) haproxy_config = cfg.generate(line_list) assert len(haproxy_config) > 0 @@ -379,7 +375,7 @@ def test_parser_ssl_strict(): } } - cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) + cfg = easymapping.HaproxyConfigGenerator(result) haproxy_config = cfg.generate(line_list) assert len(haproxy_config) > 0 @@ -396,7 +392,7 @@ def test_parser_ssl_loose(): "ssl_mode": "loose", } - cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) + cfg = easymapping.HaproxyConfigGenerator(result) haproxy_config = cfg.generate(line_list) assert len(haproxy_config) > 0 @@ -418,7 +414,7 @@ def test_parser_ssl_letsencrypt(): } } - cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) + cfg = easymapping.HaproxyConfigGenerator(result) haproxy_config = cfg.generate(line_list) assert len(haproxy_config) > 0 @@ -430,4 +426,5 @@ def test_parser_ssl_letsencrypt(): #test_parser_finds_services_raw() #test_parser_tcp() #test_parser_multiple_hosts() -#test_parser_ssl_letsencrypt() \ No newline at end of file +#test_parser_ssl_letsencrypt() +#test_parser_finds_services() \ No newline at end of file From d8f1931f193492cec7a84dc84cdcd497780e0db4 Mon Sep 17 00:00:00 2001 From: Joao M Date: Thu, 25 Aug 2022 00:40:54 +0000 Subject: [PATCH 08/15] Remove obsolete files --- assets/scripts/functions.sh | 9 ---- assets/scripts/haproxy-reload.sh | 81 -------------------------------- assets/scripts/haproxy.sh | 29 ------------ assets/scripts/k8s.py | 55 ---------------------- assets/scripts/static.py | 22 --------- assets/scripts/swarm.py | 34 -------------- 6 files changed, 230 deletions(-) delete mode 100644 assets/scripts/functions.sh delete mode 100755 assets/scripts/haproxy-reload.sh delete mode 100755 assets/scripts/haproxy.sh delete mode 100644 assets/scripts/k8s.py delete mode 100644 assets/scripts/static.py delete mode 100644 assets/scripts/swarm.py diff --git a/assets/scripts/functions.sh b/assets/scripts/functions.sh deleted file mode 100644 index e90e730..0000000 --- a/assets/scripts/functions.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -function log() { - # ARGS: - # - loglevel - # - app - # - message - echo [$2] $(date +"$EASYHAPROXY_DATEFORMAT") [$1]: $3 -} diff --git a/assets/scripts/haproxy-reload.sh b/assets/scripts/haproxy-reload.sh deleted file mode 100755 index 7cd3822..0000000 --- a/assets/scripts/haproxy-reload.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env bash - -source /scripts/functions.sh - -cd /scripts - -RELOAD="true" - -if [[ "$EASYHAPROXY_DISCOVER" == "static" ]]; then - CONTROL_FILE="/etc/haproxy/haproxy.cfg" - touch ${CONTROL_FILE} - cp ${CONTROL_FILE} ${CONTROL_FILE}.old - python3 static.py /etc/haproxy/easyconfig.yml > ${CONTROL_FILE} -else - CONTROL_FILE="/tmp/.docker_data" - touch ${CONTROL_FILE} - mv ${CONTROL_FILE} ${CONTROL_FILE}.old - touch ${CONTROL_FILE} - - case "$EASYHAPROXY_DISCOVER" in - docker) - CONTAINERS=$(docker ps -q | sort | uniq) - LABEL_PATH=".Config.Labels" - - for container in ${CONTAINERS}; do - docker inspect --format "{{ json $LABEL_PATH }}" ${container} | xargs -I % echo ${container}=% >> ${CONTROL_FILE} - done - ;; - - swarm) - CONTAINERS=$(docker node ps $(docker node ls -q) --format "{{ .Name }}" --filter desired-state=running | cut -d. -f1 | sort | uniq) - LABEL_PATH=".Spec.Labels" - - for container in ${CONTAINERS}; do - docker service inspect --format "{{ json $LABEL_PATH }}" ${container} | xargs -I % echo ${container}=% >> ${CONTROL_FILE} - done - ;; - - kubernetes) - python3 /scripts/k8s.py > ${CONTROL_FILE} - ;; - - *) - log "error" "CONF_CHECK" "EASYHAPROXY_DISCOVER should be 'static', 'docker', 'swarm' or kubernetes. I got '$EASYHAPROXY_DISCOVER' instead." - exit 1 - ;; - esac - - if cmp -s ${CONTROL_FILE} ${CONTROL_FILE}.old ; then - RELOAD="false" - else - python3 swarm.py > /etc/haproxy/haproxy.cfg - log "info" "CONF_CHECK" "New configuration found" - fi -fi - -if cmp -s ${CONTROL_FILE} ${CONTROL_FILE}.old ; then - RELOAD="false" -fi - -if [[ ! -z "$1" ]]; then - log "info" "CONF_CHECK" "Initial configuration. Skip certbot." -else - /scripts/certbot.sh -fi - -# If Certbot reloads successfully will create the file /tmp/force-reload -if [ -f /tmp/force-reload ]; then - log "info" "CONF_CHECK" "New certificates found..." - RELOAD="true" - rm /tmp/force-reload -fi - -if [[ ! -z "$1" ]]; then - log "info" "CONF_CHECK" "Starting haproxy..." - /usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg $(ls /etc/haproxy/conf.d/*.cfg 2>/dev/null | xargs -I{} echo -f {}) -p /run/haproxy.pid -S /var/run/haproxy.sock & - -elif [[ "$RELOAD" == "true" ]]; then - log "info" "CONF_CHECK" "Reloading..." - /usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg $(ls /etc/haproxy/conf.d/*.cfg 2>/dev/null | xargs -I{} echo -f {}) -p /run/haproxy.pid -x /var/run/haproxy.sock -sf $(cat /run/haproxy.pid) & -fi diff --git a/assets/scripts/haproxy.sh b/assets/scripts/haproxy.sh deleted file mode 100755 index 92c15e5..0000000 --- a/assets/scripts/haproxy.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -source /scripts/functions.sh - -/usr/sbin/haproxy -v - -if [ -z "$EASYHAPROXY_DATEFORMAT" ]; then - export EASYHAPROXY_DATEFORMAT="%Y-%m-%d %H:%M:%S %Z" -fi - -if [ -z "$EASYHAPROXY_REFRESH_CONF" ]; then - export EASYHAPROXY_REFRESH_CONF=10 -fi - - -cat banner.txt -echo Release: $RELEASE_VERSION -echo -echo "Environment" -env | sort | grep 'HAPROXY' | xargs -I{} echo " - {} " -echo - -/scripts/haproxy-reload.sh initial - -while true; do - sleep $EASYHAPROXY_REFRESH_CONF - log "info" "CONF_CHECK" "Heartbeat." - /scripts/haproxy-reload.sh -done diff --git a/assets/scripts/k8s.py b/assets/scripts/k8s.py deleted file mode 100644 index a1b2cf4..0000000 --- a/assets/scripts/k8s.py +++ /dev/null @@ -1,55 +0,0 @@ -from kubernetes import client, config -from kubernetes.client.rest import ApiException -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() - - api_instance = client.CoreV1Api() - v1 = client.NetworkingV1Api() - - ret = v1.list_ingress_for_all_namespaces(watch=False) - - discover = {} - 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 discover.keys(): - discover[cluster_ip] = data - discover[cluster_ip].update(rule_data) - - for k in discover.keys(): - print("%s=%s" % (k, json.dumps(discover[k]))) - -if __name__ == '__main__': - main() - diff --git a/assets/scripts/static.py b/assets/scripts/static.py deleted file mode 100644 index 2e36cc6..0000000 --- a/assets/scripts/static.py +++ /dev/null @@ -1,22 +0,0 @@ -import yaml -import sys -import os - -from easymapping import HaproxyConfigGenerator - -if len(sys.argv) != 2: - print("You need to pass the easyconfig.yml path") - exit(1) - - -with open(sys.argv[1], 'r') as content_file: - parsed = yaml.load(content_file.read(), Loader=yaml.FullLoader) - -cfg = HaproxyConfigGenerator(parsed) -print(cfg.generate()) - -path = os.path.dirname(os.path.realpath(__file__)) -with open(path + "/letsencrypt_hosts.txt", 'w') as fp: - fp.write('\n'.join(cfg.letsencrypt_hosts)) - -exit(0) diff --git a/assets/scripts/swarm.py b/assets/scripts/swarm.py deleted file mode 100644 index 52c1a73..0000000 --- a/assets/scripts/swarm.py +++ /dev/null @@ -1,34 +0,0 @@ -import os -from easymapping import HaproxyConfigGenerator - -with open("/tmp/.docker_data", 'r') as content_file: - line_list = content_file.readlines() - -result = { - "customerrors": True if os.getenv("HAPROXY_CUSTOMERRORS") == "true" else False, - "ssl_mode": os.getenv("EASYHAPROXY_SSL_MODE", "default") -} - -if os.getenv("HAPROXY_PASSWORD"): - result["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", - } - -result["lookup_label"] = os.getenv("EASYHAPROXY_LABEL_PREFIX") if os.getenv("EASYHAPROXY_LABEL_PREFIX") else "easyhaproxy" -if (os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL")): - result["letsencrypt"] = { - "email": os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL") - } - - -cfg = HaproxyConfigGenerator(result) -print(cfg.generate(line_list)) - -path = os.path.dirname(os.path.realpath(__file__)) -with open(path + "/letsencrypt_hosts.txt", 'w') as fp: - fp.write('\n'.join(cfg.letsencrypt_hosts)) -# print(jsonStr) - - From 96f344118b9950f886fa4f651d90cb6718da0ee0 Mon Sep 17 00:00:00 2001 From: Joao M Date: Thu, 25 Aug 2022 00:47:22 +0000 Subject: [PATCH 09/15] Remove more obsolete files --- .travis.yml | 26 ------------ _config.yml | 66 ------------------------------ resource-temp.yml | 79 ------------------------------------ pytest.ini => src/pytest.ini | 0 4 files changed, 171 deletions(-) delete mode 100644 .travis.yml delete mode 100644 _config.yml delete mode 100644 resource-temp.yml rename pytest.ini => src/pytest.ini (100%) diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ed1f6e6..0000000 --- a/.travis.yml +++ /dev/null @@ -1,26 +0,0 @@ -language: python - -services: - - docker - - -jobs: - include: - - stage: test - if: (type IN (pull_request)) - install: - - pip install -r requirements.txt - script: - - pytest -s tests/ - - - stage: build docker - if: (branch = master) AND (NOT (type IN (pull_request))) - install: - - docker pull byjg/k8s-ci - script: - - docker run --privileged -v /tmp/z:/var/lib/containers -it --rm -v $PWD:/work -w /work -e DOCKER_USERNAME=$DOCKER_USERNAME -e DOCKER_PASSWORD=$DOCKER_PASSWORD -e DOCKER_REGISTRY=$DOCKER_REGISTRY byjg/k8s-ci /work/build-multiarch.sh - - - stage: documentation - if: (branch = master) AND (NOT (type IN (pull_request))) - install: skip - script: "curl https://opensource.byjg.com/add-doc.sh | bash /dev/stdin devops docker-easy-haproxy" diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 3935400..0000000 --- a/_config.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: docker-easy-haproxy - -project: - version: 1.0.0 - download_url: https://github.com/byjg/docker-easy-haproxy/releases - -license: - software: MIT - software_url: https://opensource.org/licenses/MIT - - docs: MIT - docs_url: https://opensource.org/licenses/MIT - -git_edit_address: https://github.com/byjg/docker-easy-haproxy/blob/master/ - -links: - header: - - title: GitHub - url: https://github.com/byjg/docker-easy-haproxy - - title: ByJG - url: https://opensource.byjg.com/ - footer: - - title: GitHub - url: https://github.com/byjg/docker-easy-haproxy - - title: Issues - url: https://github.com/byjg/docker-easy-haproxy/issues - -ui: - header: - color1: "#080331" - color2: "#0033cc" - trianglify: true - -social: - github: - user: byjg - repo: docker-easy-haproxy - twitter: - enabled: false - via: - hash: opensourcebyjg - account: - facebook: - enabled: true - url: https://opensource.byjg.com/ - profileUrl: - -author: - twitter: byjg - -twitter: - card: summary - username: byjg - -logo: https://opensource.byjg.com/images/logo_byjg.png - -analytics: - google: UA-130014324-1 - -plugins: - - jekyll-seo-tag - -# Build settings -markdown: kramdown -remote_theme: byjg/jekyll-docs-theme - diff --git a/resource-temp.yml b/resource-temp.yml deleted file mode 100644 index 2954285..0000000 --- a/resource-temp.yml +++ /dev/null @@ -1,79 +0,0 @@ ---- -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/pytest.ini b/src/pytest.ini similarity index 100% rename from pytest.ini rename to src/pytest.ini From 3259a1b66a92405e17d55185b3e031dcccb21396 Mon Sep 17 00:00:00 2001 From: Joao M Date: Thu, 25 Aug 2022 01:52:13 +0000 Subject: [PATCH 10/15] Add python certbot --- src/functions/__init__.py | 118 ++++++++++++++++++++++++-------------- src/main.py | 6 +- 2 files changed, 80 insertions(+), 44 deletions(-) diff --git a/src/functions/__init__.py b/src/functions/__init__.py index 154a035..c33c161 100644 --- a/src/functions/__init__.py +++ b/src/functions/__init__.py @@ -2,6 +2,8 @@ from datetime import datetime from multiprocessing import Process, Lock import subprocess import shlex +import time +import os class Functions: @staticmethod @@ -58,46 +60,6 @@ class Functions: Functions.log(source, 'error', "%s" % (e)) - - @staticmethod - def log_ts(source, level, message, lock): - if message is None or message == "": - return - - if not isinstance(message, (list, tuple)): - message = [message] - - lock.acquire() - try: - for line in message: - print("[%s] %s [%s]: %s" % (source, datetime.now().strftime("%x %X"), level, line.rstrip())) - finally: - lock.release() - - @staticmethod - def run_bash_ts(source, command, lock): - if not isinstance(command, (list, tuple)): - command = shlex.split(command) - - try: - process = subprocess.Popen(command, - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - bufsize=-1, - universal_newlines=True) - - with process.stdout: - for line in iter(process.stdout.readline, b''): - Functions.log(source, "info", line) - - returncode = process.wait() - Functions.log(source, "info", "Return code %s" % (returncode)) - - except Exception as e: - Functions.log_ts(source, 'error', "%s" % (e), lock) - - class DaemonizeHAProxy: def __init__(self): self.process = None @@ -130,7 +92,7 @@ class DaemonizeHAProxy: universal_newlines=True) except Exception as e: - Functions.log_ts(source, 'error', "%s" % (e), lock) + Functions.log(source, 'error', "%s" % (e)) def __start(self): @@ -144,7 +106,7 @@ class DaemonizeHAProxy: Functions.log(source, "info", "Return code %s" % (returncode)) except Exception as e: - Functions.log_ts(source, 'error', "%s" % (e), lock) + Functions.log(source, 'error', "%s" % (e)) def is_alive(self): return self.thread.is_alive() @@ -157,4 +119,74 @@ class DaemonizeHAProxy: self.process.terminate() self.thread.terminate() - \ No newline at end of file + +class Certbot: + def __init__(self, certs, email): + self.certs = certs + self.email = email + + def check_certificates(self, hosts): + if self.email == "" or len(hosts) == 0: + return False + + try: + request_certs = [] + renew_certs = [] + current_time = time.time() + for host in hosts: + filename = "%s/%s.pem" % (self.certs, host) + host_arg = '-d %s' % (host) + if not os.path.exists(filename): + Functions.log("CERTBOT", "info", "Request new certificate for %s" % (host)) + request_certs.append(host_arg) + else: + creation_time = os.path.getctime(filename) + if (current_time - creation_time) // (24 * 3600) > 90: + Functions.log("CERTBOT", "info", "Request expired certificate for %s" % (host)) + request_certs.append(host_arg) + if (current_time - creation_time) // (24 * 3600) >= 45: + Functions.log("CERTBOT", "info", "Renew certificate for %s" % (host)) + renew_certs.append(host_arg) + + certbot_certonly = ('/usr/bin/certbot certonly ' + ' --standalone' + ' --preferred-challenges http' + ' --http-01-port 2080' + ' --agree-tos' + ' --issuance-timeout 90' + ' --no-eff-email' + ' --non-interactive' + ' --max-log-backups=0' + ' --post-hook "/scripts/certbot_to_haproxy.sh"' + ' %s --email %s' % (' '.join(request_certs), self.email) + ) + + ret_reload = False + if len(request_certs) > 0: + Functions.run_bash("CERTBOT", certbot_certonly, return_result=False) + ret_reload = true + + if len(renew_certs) > 0: + Functions.run_bash("CERTBOT", "/usb/bin/certbot renew", return_result=False) + ret_reload = true + + if ret_reload: + self.find_live_certificates() + + return ret_reload + except Excepton as e: + Functions.log("CERTBOT", "error", "%s" % (e)) + return False + + def merge_certificate(self, cert, key, filename): + Functions.save(filename, cert + key) + + def find_live_certificates(self): + letsencrypt_certs = "/etc/letsencrypt/live/" + for item in os.listdir(letsencrypt_certs): + path = os.path.join(letsencrypt_certs, item) + if os.path.isdir(path): + cert = Functions.load(os.path.join(path, "fullchain.pem")) + key = Functions.load(os.path.join(path, "privkey.pem")) + filename = "%s/%s.pem" % (self.certs, item) + self.merge_certificate(cert, key, filename) diff --git a/src/main.py b/src/main.py index c2749f9..17e1130 100644 --- a/src/main.py +++ b/src/main.py @@ -1,4 +1,4 @@ -from functions import Functions, DaemonizeHAProxy +from functions import Functions, DaemonizeHAProxy, Certbot from processor import ProcessorInterface import os import time @@ -25,6 +25,8 @@ def start(): haproxy = DaemonizeHAProxy() haproxy.haproxy("start") + certbot = Certbot(certs_letsencrypt, os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL")) + while True: time.sleep(10) if old_haproxy is not None: @@ -42,6 +44,8 @@ def start(): haproxy = DaemonizeHAProxy() haproxy.haproxy("reload") old_haproxy.terminate() + + certbot.check_certificates(processor_obj.get_letsencrypt_hosts()) except Exception as e: Functions.log('EASYHAPROXY', 'error', "Err: %s" % (e)) Functions.log('EASYHAPROXY', 'info', 'Heartbeat') From 28342cdc211744b582f7c1e3a95edb6445ccb031 Mon Sep 17 00:00:00 2001 From: Joao M Date: Thu, 25 Aug 2022 02:03:47 +0000 Subject: [PATCH 11/15] Lets encrypt --- src/main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.py b/src/main.py index 17e1130..69bd1c3 100644 --- a/src/main.py +++ b/src/main.py @@ -19,6 +19,7 @@ def start(): processor_obj.save_config(haproxy_config) processor_obj.save_certs(certs_haproxy) + letsencrypt_certs_found = processor_obj.get_letsencrypt_hosts() Functions.log('EASYHAPROXY', 'info', 'Found hosts: %s' % ", ".join(processor_obj.get_hosts())) # Needs to after save_config old_haproxy = None @@ -39,13 +40,14 @@ def start(): Functions.log('EASYHAPROXY', 'info', 'New configuration found. Reloading...') processor_obj.save_config(haproxy_config) processor_obj.save_certs(certs_haproxy) + letsencrypt_certs_found = processor_obj.get_letsencrypt_hosts() Functions.log('EASYHAPROXY', 'info', 'Found hosts: %s' % ", ".join(processor_obj.get_hosts())) # Needs to after save_config old_haproxy = haproxy haproxy = DaemonizeHAProxy() haproxy.haproxy("reload") old_haproxy.terminate() - certbot.check_certificates(processor_obj.get_letsencrypt_hosts()) + certbot.check_certificates(letsencrypt_certs_found) except Exception as e: Functions.log('EASYHAPROXY', 'error', "Err: %s" % (e)) Functions.log('EASYHAPROXY', 'info', 'Heartbeat') From 2442b25440b2a13d1498fd937d0affeb0724dda2 Mon Sep 17 00:00:00 2001 From: Joao M Date: Thu, 25 Aug 2022 02:10:14 +0000 Subject: [PATCH 12/15] Fix typo --- src/functions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/functions/__init__.py b/src/functions/__init__.py index c33c161..0464cfa 100644 --- a/src/functions/__init__.py +++ b/src/functions/__init__.py @@ -174,7 +174,7 @@ class Certbot: self.find_live_certificates() return ret_reload - except Excepton as e: + except Exception as e: Functions.log("CERTBOT", "error", "%s" % (e)) return False From bcb62513770c2e69e0b1335e0f98d278499c3f72 Mon Sep 17 00:00:00 2001 From: Joao M Date: Thu, 25 Aug 2022 02:15:49 +0000 Subject: [PATCH 13/15] Fix typo --- src/functions/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/functions/__init__.py b/src/functions/__init__.py index 0464cfa..e9525e6 100644 --- a/src/functions/__init__.py +++ b/src/functions/__init__.py @@ -164,11 +164,11 @@ class Certbot: ret_reload = False if len(request_certs) > 0: Functions.run_bash("CERTBOT", certbot_certonly, return_result=False) - ret_reload = true + ret_reload = True if len(renew_certs) > 0: - Functions.run_bash("CERTBOT", "/usb/bin/certbot renew", return_result=False) - ret_reload = true + Functions.run_bash("CERTBOT", "/usb/bin/certbot renew --post-hook /scripts/certbot_to_haproxy.sh", return_result=False) + ret_reload = True if ret_reload: self.find_live_certificates() From 91a02166f12be226a974d1bc9f2dafad9711460b Mon Sep 17 00:00:00 2001 From: Joao Magalhaes Date: Thu, 25 Aug 2022 02:26:02 +0000 Subject: [PATCH 14/15] Removing last bash --- assets/scripts/certbot.sh | 55 ---------------------------- assets/scripts/certbot_to_haproxy.sh | 14 ------- src/functions/__init__.py | 3 +- 3 files changed, 1 insertion(+), 71 deletions(-) delete mode 100755 assets/scripts/certbot.sh delete mode 100755 assets/scripts/certbot_to_haproxy.sh diff --git a/assets/scripts/certbot.sh b/assets/scripts/certbot.sh deleted file mode 100755 index 8678231..0000000 --- a/assets/scripts/certbot.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash - -source /scripts/functions.sh - -if [ ! -f /scripts/letsencrypt_hosts.txt ]; then - exit 0 -fi - -# Semaphore -if [ -f /tmp/certbot-lock ]; then - log "notice" "CERTBOT_JOB" "Another process is running" - exit 0 -fi - -touch /tmp/certbot-lock - -mkdir -p /var/log/letsencrypt -ln -sf /dev/stdout /var/log/letsencrypt/letsencrypt.log - -REQUEST_CERTS="" -RENEW_CERTS="" - -for domain in $(cat /scripts/letsencrypt_hosts.txt); do - if [ ! -f "/certs/letsencrypt/$domain.pem" ]; then - REQUEST_CERTS="$REQUES_CERTS -d $domain" - continue - fi - - if [[ $(find "/certs/letsencrypt/$domain.pem" -mtime +30 -print) ]]; then - RENEW_CERTS="$RENEW_CERTS -d $domain" - fi -done - -if [ -n "$REQUEST_CERTS" ]; then - log "info" "CERTBOT_JOB" "Requesting certificates for $REQUEST_CERTS" - certbot certonly \ - --standalone \ - --preferred-challenges http \ - --http-01-port 2080 \ - --agree-tos \ - --issuance-timeout 90 \ - --no-eff-email \ - --non-interactive \ - --max-log-backups=0 \ - --post-hook "/scripts/certbot_to_haproxy.sh" \ - $REQUEST_CERTS --email $EASYHAPROXY_LETSENCRYPT_EMAIL -fi - -if [ -n "$RENEW_CERTS" ]; then - log "info" "CERTBOT_JOB" "Resquesting renew certificated fort $RENEW_CERTS" - certbot renew --post-hook "/scripts/certbot_to_haproxy.sh" -fi - -# Release semaphore -rm /tmp/certbot-lock diff --git a/assets/scripts/certbot_to_haproxy.sh b/assets/scripts/certbot_to_haproxy.sh deleted file mode 100755 index 532a265..0000000 --- a/assets/scripts/certbot_to_haproxy.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -source /scripts/functions.sh - -# Loop through all Let's Encrypt certificates -for CERTIFICATE in `find /etc/letsencrypt/live/* -type d`; do - CERTIFICATE=`basename $CERTIFICATE` - - # Combine certificate and private key to single file - cat /etc/letsencrypt/live/$CERTIFICATE/fullchain.pem /etc/letsencrypt/live/$CERTIFICATE/privkey.pem > /certs/letsencrypt/$CERTIFICATE.pem -done - -# It will be checked on haproxy-reload.sh -touch /tmp/force-reload \ No newline at end of file diff --git a/src/functions/__init__.py b/src/functions/__init__.py index e9525e6..5d62154 100644 --- a/src/functions/__init__.py +++ b/src/functions/__init__.py @@ -157,7 +157,6 @@ class Certbot: ' --no-eff-email' ' --non-interactive' ' --max-log-backups=0' - ' --post-hook "/scripts/certbot_to_haproxy.sh"' ' %s --email %s' % (' '.join(request_certs), self.email) ) @@ -167,7 +166,7 @@ class Certbot: ret_reload = True if len(renew_certs) > 0: - Functions.run_bash("CERTBOT", "/usb/bin/certbot renew --post-hook /scripts/certbot_to_haproxy.sh", return_result=False) + Functions.run_bash("CERTBOT", "/usb/bin/certbot renew", return_result=False) ret_reload = True if ret_reload: From 16b69febc259e1a8e3111a4d06e1b9c8f7aa1dea Mon Sep 17 00:00:00 2001 From: Joao M Date: Thu, 25 Aug 2022 02:32:54 +0000 Subject: [PATCH 15/15] Minor typo --- .vscode/launch.json | 2 +- src/banner.txt | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 src/banner.txt diff --git a/.vscode/launch.json b/.vscode/launch.json index 01d56c1..9cb10c6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,7 +12,7 @@ "console": "integratedTerminal", "cwd": "${workspaceFolder}/src", "env": { - "=": "${cwd}/src" + "PYTHONPATH": "${cwd}/src" } } ] diff --git a/src/banner.txt b/src/banner.txt deleted file mode 100644 index 8f09599..0000000 --- a/src/banner.txt +++ /dev/null @@ -1,5 +0,0 @@ -" _ " -" ___ __ _ ____ _ ___| |_ __ _ _ __ _ _ _____ ___ _ " -"/ -_) _` (_-< || |___| ' \/ _` | '_ \ '_/ _ \ \ / || |" -"\___\__,_/__/\_, | |_||_\__,_| .__/_| \___/_\_\\_, |" -" |__/ |_| |__/ "