From 10ed037d94a83a944c3f0d52a164f7895885dd3b Mon Sep 17 00:00:00 2001 From: Joao M Date: Mon, 15 Aug 2022 00:34:45 +0000 Subject: [PATCH] First part letsencrypt --- Dockerfile | 2 +- README.md | 26 +++-- assets/etc/crontabs/root | 1 + assets/scripts/certbot.sh | 17 +++ assets/scripts/certbot_to_haproxy.sh | 11 ++ assets/scripts/static.py | 6 + assets/scripts/swarm.py | 4 +- easymapping/__init__.py | 30 +++-- examples/static/config.yml | 4 +- templates/frontend-mode-http.j2 | 8 +- templates/haproxy.cfg.j2 | 5 +- tests/expected/no-services.txt | 3 + tests/expected/services-multi-containers.txt | 3 + tests/expected/services-tcp.txt | 3 + tests/expected/services.txt | 5 + tests/expected/static.txt | 5 + tests/fixtures/services | 2 +- tests/fixtures/services-changed-label | 2 +- tests/fixtures/static.yml | 16 ++- tests/test_parser.py | 114 ++++++++++++++++--- 20 files changed, 222 insertions(+), 45 deletions(-) create mode 100755 assets/scripts/certbot.sh create mode 100755 assets/scripts/certbot_to_haproxy.sh diff --git a/Dockerfile b/Dockerfile index 77c80c5..bcd44a5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM alpine:3.16 WORKDIR /scripts -RUN apk add --no-cache haproxy bash python3 py3-pip py-yaml supervisor docker \ +RUN apk add --no-cache haproxy bash python3 py3-pip py-yaml supervisor docker certbot \ && ln -s /usr/bin/python3 /usr/bin/python COPY requirements.txt /scripts diff --git a/README.md b/README.md index 668acd2..266ea04 100644 --- a/README.md +++ b/README.md @@ -88,13 +88,14 @@ Important: easyhaproxy needs to be in the same network of the containers or othe | Tag | Description | Example | |---------------------------------------|---------------------------------------------------------------------------------------------------------|--------------| +| easyhaproxy.[definition].host | Host HAProxy is listening | somehost.com | | easyhaproxy.[definition].mode | (Optional) Is this `http` or `tcp` mode in HAProxy. (Defaults to http) | http | -| easyhaproxy.[definition].port | (Optional) What is the port that the HAProxy will listen to. (Defaults to 80) | 80 | -| easyhaproxy.[definition].localport. | (Optional) What is the port that the container is listening. (Defaults to 80) | 8080 | -| easyhaproxy.[definition].host | What is the host that the HAProxy will listen to. | somehost.com | +| easyhaproxy.[definition].port | (Optional) Port HAProxy will listen for the host. (Defaults to 80) | 80 | +| easyhaproxy.[definition].localport | (Optional) Port container is listening. (Defaults to 80) | 8080 | | easyhaproxy.[definition].redirect | (Optional) Host redirects from connections in the port defined above. | foo.com--https://bla.com,bar.com--https://bar.org | -| easyhaproxy.[definition].sslcert | (Optional) Cert PEM Base64 encoded. | | +| easyhaproxy.[definition].sslcert | (Optional) Cert PEM Base64 encoded. Do not use this if letsencrypt is enabled. | | | easyhaproxy.[definition].health-check | (Optional) `ssl`, enable health check via SSL in `mode tcp` (Defaults to "empty") | | +| easyhaproxy.[definition].letsencrypt | (Optional) Generate certificate with letsencrypt. Do not use with sslcert | true, yes | ### Defining the labels in Docker Swarm @@ -171,19 +172,28 @@ customerrors: true # Optional (default false) easymapping: - port: 80 hosts: - host1.com.br: container:5000 - host2.com.br: other:3000 + host1.com.br: + containers: + - container:5000 + letsencrypt: true + host2.com.br: + containers: + - other:3000 redirect: www.host1.com.br: http://host1.com.br - port: 443 ssl_cert: /path/to/ssl/certificate hosts: - host1.com.br: container:80 + host1.com.br: + containers: + - container:80 - port: 8080 hosts: - host3.com.br: domain:8181 + host3.com.br: + containers: + - domain:8181 ``` Running: diff --git a/assets/etc/crontabs/root b/assets/etc/crontabs/root index cabb0aa..9811e66 100644 --- a/assets/etc/crontabs/root +++ b/assets/etc/crontabs/root @@ -6,4 +6,5 @@ 0 3 * * 6 run-parts /etc/periodic/weekly 0 5 1 * * run-parts /etc/periodic/monthly * * * * * /scripts/haproxy-reload.sh +#*/2 * * * * /scripts/certbot.sh diff --git a/assets/scripts/certbot.sh b/assets/scripts/certbot.sh new file mode 100755 index 0000000..4756b66 --- /dev/null +++ b/assets/scripts/certbot.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +@todo + +ln -sf /dev/stdout /var/log/letsencrypt/letsencrypt.log + +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 && systemctl reload haproxy.service" + -d dev.globalnetguide.com -d other.domain.com --email info@xpto.us diff --git a/assets/scripts/certbot_to_haproxy.sh b/assets/scripts/certbot_to_haproxy.sh new file mode 100755 index 0000000..ab2c53a --- /dev/null +++ b/assets/scripts/certbot_to_haproxy.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# @todo + +# 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 > /etc/haproxy/certs/$CERTIFICATE.pem +done diff --git a/assets/scripts/static.py b/assets/scripts/static.py index 07054ef..2e36cc6 100644 --- a/assets/scripts/static.py +++ b/assets/scripts/static.py @@ -1,5 +1,7 @@ import yaml import sys +import os + from easymapping import HaproxyConfigGenerator if len(sys.argv) != 2: @@ -13,4 +15,8 @@ with open(sys.argv[1], 'r') as content_file: 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 index 425535a..b746146 100644 --- a/assets/scripts/swarm.py +++ b/assets/scripts/swarm.py @@ -1,7 +1,6 @@ import os from easymapping import HaproxyConfigGenerator -# path = os.path.dirname(os.path.realpath(__file__)) with open("/tmp/.docker_data", 'r') as content_file: lineList = content_file.readlines() @@ -21,6 +20,9 @@ result["lookup_label"] = os.getenv("EASYHAPROXY_LABEL_PREFIX") if os.getenv("EAS cfg = HaproxyConfigGenerator(result) print(cfg.generate(lineList)) +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) diff --git a/easymapping/__init__.py b/easymapping/__init__.py index c414bb2..fa5a81c 100644 --- a/easymapping/__init__.py +++ b/easymapping/__init__.py @@ -25,6 +25,12 @@ class DockerLabelHandler: return default_value + def get_bool(self, label, default_value = False): + if self.has_label(label): + return self.__data[label].lower() in ["True", "true", "1", "yes"] + return default_value + + def set_data(self, data): self.__data = data @@ -41,21 +47,16 @@ class HaproxyConfigGenerator: self.label = DockerLabelHandler(mapping['lookup_label'] if 'lookup_label' in mapping else "easyhaproxy") self.ssl_cert_folder = ssl_cert_folder self.ssl_cert_increment = 0 + self.letsencrypt_hosts = [] os.makedirs(self.ssl_cert_folder, exist_ok=True) def generate(self, line_list = []): + self.mapping.setdefault("easymapping", []) + # static? if len(line_list) > 0: self.mapping["easymapping"] = self.parse(line_list) - else: - for d in self.mapping["easymapping"]: - for name, hosts in d.get('hosts', {}).items(): - if type(hosts) != list: - d['hosts'][name] = [hosts] - # still 'None' -> default to [] for jinja2 - if self.mapping["easymapping"] is None: - self.mapping["easymapping"] = [] file_loader = FileSystemLoader('templates') env = Environment(loader=file_loader) @@ -105,6 +106,12 @@ class HaproxyConfigGenerator: "80" ) + letsencrypt = self.label.get_bool( + self.label.create([definition, "letsencrypt"]), + False + ) + self.letsencrypt_hosts.append(d[host_label]) if letsencrypt and d[host_label] not in self.letsencrypt_hosts else self.letsencrypt_hosts + hash = "" if self.label.create([definition, "sslcert"]) in d: hash = hashlib.md5( @@ -133,8 +140,11 @@ class HaproxyConfigGenerator: "" ) - easymapping[key]["hosts"].setdefault(d[host_label], []) - easymapping[key]["hosts"][d[host_label]] += ["{}:{}".format(container, ct_port)] + easymapping[key]["hosts"].setdefault(d[host_label], {}) + easymapping[key]["hosts"][d[host_label]].setdefault("containers", []) + easymapping[key]["hosts"][d[host_label]].setdefault("letsencrypt", False) + easymapping[key]["hosts"][d[host_label]]["containers"] += ["{}:{}".format(container, ct_port)] + easymapping[key]["hosts"][d[host_label]]["letsencrypt"] = letsencrypt # handle SSL ssl_label = self.label.create([definition, "sslcert"]) diff --git a/examples/static/config.yml b/examples/static/config.yml index 2e75011..3ededd6 100644 --- a/examples/static/config.yml +++ b/examples/static/config.yml @@ -14,4 +14,6 @@ easymapping: - port: 443 ssl_cert: /etc/certs/host1.local.pem hosts: - host1.local: container:8080 + host1.local: + containers: + - container:8080 diff --git a/templates/frontend-mode-http.j2 b/templates/frontend-mode-http.j2 index 5d903e6..fa4a73a 100644 --- a/templates/frontend-mode-http.j2 +++ b/templates/frontend-mode-http.j2 @@ -2,10 +2,16 @@ {% for k in o["redirect"] %} redirect prefix {{ o["redirect"][k] }} code 301 if { hdr(host) -i {{ k }} } {% endfor %} - {% for k in o["hosts"] %} + + {%- for k in o["hosts"] %} {% set host = k.replace(".", "_") + "_{0}_{1}".format(o["port"], salt) %} + {% set letsencrypt = o["hosts"][k]["letsencrypt"] %} acl is_rule_{{ host }}_1 hdr(host) -i {{ k }} acl is_rule_{{ host }}_2 hdr(host) -i {{ k }}:{{ o["port"] }} + {% if letsencrypt %} + acl is_letsencrypt_{{ host }} path_beg /.well-known/acme-challenge/ + use_backend letsencrypt_backend if (is_rule_{{ host }}_1 OR is_rule_{{ host }}_2) AND is_letsencrypt_{{ host }} + {% endif %} use_backend srv_{{ host }} if is_rule_{{ host }}_1 OR is_rule_{{ host }}_2 {% endfor %} diff --git a/templates/haproxy.cfg.j2 b/templates/haproxy.cfg.j2 index 2d70d04..df5adad 100644 --- a/templates/haproxy.cfg.j2 +++ b/templates/haproxy.cfg.j2 @@ -62,8 +62,11 @@ backend srv_{{ host }} option tcp-check tcp-check connect{{ " ssl" if o["health-check"] == "ssl" }} {% endif %} - {% for c in o["hosts"][k] %} + {% for c in o["hosts"][k]["containers"] %} server srv-{{ loop.index0 }} {{ c }} check weight 1{{ " verify none" if o["health-check"] == "ssl" }} {% endfor %} {% endfor %} {% endfor %} + +backend letsencrypt_backend + server certbot 127.0.0.1:2080 \ No newline at end of file diff --git a/tests/expected/no-services.txt b/tests/expected/no-services.txt index f5ecac3..42767c1 100644 --- a/tests/expected/no-services.txt +++ b/tests/expected/no-services.txt @@ -10,3 +10,6 @@ defaults timeout client 10s timeout server 10m + +backend letsencrypt_backend + server certbot 127.0.0.1:2080 \ No newline at end of file diff --git a/tests/expected/services-multi-containers.txt b/tests/expected/services-multi-containers.txt index 3afa9ef..6eebe88 100644 --- a/tests/expected/services-multi-containers.txt +++ b/tests/expected/services-multi-containers.txt @@ -27,3 +27,6 @@ backend srv_www_helloworld_com_19901_1 http-request add-header X-Forwarded-Proto https if { ssl_fc } server srv-0 test_nginx.2.t5r94mjlced7m3t5orfjbowmm:80 check weight 1 server srv-1 test_nginx.1.p552hqxkdx88narjrp5kouwb2:80 check weight 1 + +backend letsencrypt_backend + server certbot 127.0.0.1:2080 \ No newline at end of file diff --git a/tests/expected/services-tcp.txt b/tests/expected/services-tcp.txt index bc1fcc2..1e8fb77 100644 --- a/tests/expected/services-tcp.txt +++ b/tests/expected/services-tcp.txt @@ -24,3 +24,6 @@ backend srv_agent_quantum_local_31339_1 option tcp-check tcp-check connect ssl server srv-0 test_agent:9001 check weight 1 verify none + +backend letsencrypt_backend + server certbot 127.0.0.1:2080 \ No newline at end of file diff --git a/tests/expected/services.txt b/tests/expected/services.txt index 9ae2b3e..0f88169 100644 --- a/tests/expected/services.txt +++ b/tests/expected/services.txt @@ -35,6 +35,8 @@ frontend http_in_31337_2 acl is_rule_node-exporter_quantum_example_org_31337_2_1 hdr(host) -i node-exporter.quantum.example.org acl is_rule_node-exporter_quantum_example_org_31337_2_2 hdr(host) -i node-exporter.quantum.example.org:31337 + acl is_letsencrypt_node-exporter_quantum_example_org_31337_2 path_beg /.well-known/acme-challenge/ + use_backend letsencrypt_backend if (is_rule_node-exporter_quantum_example_org_31337_2_1 OR is_rule_node-exporter_quantum_example_org_31337_2_2) AND is_letsencrypt_node-exporter_quantum_example_org_31337_2 use_backend srv_node-exporter_quantum_example_org_31337_2 if is_rule_node-exporter_quantum_example_org_31337_2_1 OR is_rule_node-exporter_quantum_example_org_31337_2_2 backend srv_cadvisor_quantum_example_org_31337_2 @@ -93,3 +95,6 @@ backend srv_www_somehost_com_br_443_4 http-request set-header X-Forwarded-Port %[dst_port] http-request add-header X-Forwarded-Proto https if { ssl_fc } server srv-0 some-service:80 check weight 1 + +backend letsencrypt_backend + server certbot 127.0.0.1:2080 \ No newline at end of file diff --git a/tests/expected/static.txt b/tests/expected/static.txt index e1feb38..3741168 100644 --- a/tests/expected/static.txt +++ b/tests/expected/static.txt @@ -41,6 +41,8 @@ frontend http_in_80_1 acl is_rule_host1_com_br_80_1_1 hdr(host) -i host1.com.br acl is_rule_host1_com_br_80_1_2 hdr(host) -i host1.com.br:80 + acl is_letsencrypt_host1_com_br_80_1 path_beg /.well-known/acme-challenge/ + use_backend letsencrypt_backend if (is_rule_host1_com_br_80_1_1 OR is_rule_host1_com_br_80_1_2) AND is_letsencrypt_host1_com_br_80_1 use_backend srv_host1_com_br_80_1 if is_rule_host1_com_br_80_1_1 OR is_rule_host1_com_br_80_1_2 acl is_rule_host2_com_br_80_1_1 hdr(host) -i host2.com.br @@ -93,3 +95,6 @@ backend srv_host3_com_br_8080_3 http-request set-header X-Forwarded-Port %[dst_port] http-request add-header X-Forwarded-Proto https if { ssl_fc } server srv-0 domain:8181 check weight 1 + +backend letsencrypt_backend + server certbot 127.0.0.1:2080 \ No newline at end of file diff --git a/tests/fixtures/services b/tests/fixtures/services index 47ed2b0..c120d53 100644 --- a/tests/fixtures/services +++ b/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"} +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/tests/fixtures/services-changed-label b/tests/fixtures/services-changed-label index a6d5cc7..bb20aec 100644 --- a/tests/fixtures/services-changed-label +++ b/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"} +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/tests/fixtures/static.yml b/tests/fixtures/static.yml index 82ea2e2..51498d5 100644 --- a/tests/fixtures/static.yml +++ b/tests/fixtures/static.yml @@ -8,16 +8,24 @@ customerrors: true # Optional (default false) easymapping: - port: 80 hosts: - host1.com.br: container:5000 - host2.com.br: other:3000 + host1.com.br: + containers: + - container:5000 + letsencrypt: true + host2.com.br: + containers: + - other:3000 redirect: www.host1.com.br: http://host1.com.br - port: 443 ssl_cert: /etc/haproxy/certs/mycert.pem hosts: - host1.com.br: container:80 + host1.com.br: + containers: + - container:80 - port: 8080 hosts: - host3.com.br: domain:8181 + host3.com.br: + containers: [ "domain:8181" ] diff --git a/tests/test_parser.py b/tests/test_parser.py index 556d75b..5298e98 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -26,7 +26,7 @@ def test_parser_doesnt_crash(): path = os.path.dirname(os.path.realpath(__file__)) with open(path + "/expected/no-services.txt", 'r') as expected_file: assert expected_file.read() == haproxy_config - + assert [] == cfg.letsencrypt_hosts def test_parser_finds_services(): line_list = load_fixture("services") @@ -50,6 +50,8 @@ def test_parser_finds_services(): with open(cert_file, 'r') as expected_file: assert expected_file.read() == "Some PEM Certificate" + assert ['node-exporter.quantum.example.org'] == cfg.letsencrypt_hosts + def test_parser_finds_services_changed_label(): line_list = load_fixture("services-changed-label") @@ -73,6 +75,8 @@ def test_parser_finds_services_changed_label(): with open(cert_file, 'r') as expected_file: assert expected_file.read() == "Some PEM Certificate" + assert ['node-exporter.quantum.example.org'] == cfg.letsencrypt_hosts + def test_parser_finds_services_raw(): line_list = load_fixture("services") @@ -92,9 +96,12 @@ def test_parser_finds_services_raw(): "health-check":"", "port":"31339", "hosts":{ - "agent.quantum.example.org":[ - "my-stack_agent:9001" - ] + "agent.quantum.example.org": { + "containers": [ + "my-stack_agent:9001" + ], + "letsencrypt": False + } }, "redirect":{ @@ -105,12 +112,18 @@ def test_parser_finds_services_raw(): "health-check":"", "port":"31337", "hosts":{ - "cadvisor.quantum.example.org":[ - "my-stack_cadvisor:8080" - ], - "node-exporter.quantum.example.org":[ - "my-stack_node-exporter:9100" - ] + "cadvisor.quantum.example.org":{ + "containers": [ + "my-stack_cadvisor:8080" + ], + "letsencrypt": False + }, + "node-exporter.quantum.example.org":{ + "containers": [ + "my-stack_node-exporter:9100" + ], + "letsencrypt": True + } }, "redirect":{ @@ -121,9 +134,12 @@ def test_parser_finds_services_raw(): "health-check":"", "port":"80", "hosts":{ - "www.somehost.com.br":[ - "some-service:80" - ] + "www.somehost.com.br":{ + "containers": [ + "some-service:80" + ], + "letsencrypt": False + } }, "redirect":{ "somehost.com.br":"https://www.somehost.com.br", @@ -138,9 +154,12 @@ def test_parser_finds_services_raw(): "health-check":"", "port":"443", "hosts":{ - "www.somehost.com.br":[ - "some-service:80" - ] + "www.somehost.com.br":{ + "containers": [ + "some-service:80" + ], + "letsencrypt": False + } }, "redirect":{ "somehost.com.br":"https://www.somehost.com.br", @@ -156,6 +175,7 @@ def test_parser_finds_services_raw(): processed = list(cfg.parse(line_list)) assert parsed_object == processed + assert ['node-exporter.quantum.example.org'] == cfg.letsencrypt_hosts @@ -170,6 +190,66 @@ def test_parser_static(): with open(path + "/expected/static.txt", 'r') as expected_file: assert expected_file.read() == haproxy_config + assert [] == cfg.letsencrypt_hosts + +def test_parser_static_raw(): + path = os.path.dirname(os.path.realpath(__file__)) + with open(path + "/fixtures/static.yml", 'r') as content_file: + parsed = yaml.load(content_file.read(), Loader=yaml.FullLoader) + + expected = { + "stats": { + "username": "admin", + "password": "test123", + "port": 1936 + }, + "customerrors": True, + "easymapping": [ + { + "port": 80, + "hosts": { + "host1.com.br": { + "containers": [ + "container:5000" + ], + "letsencrypt": True + }, + "host2.com.br": { + "containers": [ + "other:3000" + ] + } + }, + "redirect": { + "www.host1.com.br": "http://host1.com.br" + } + }, + { + "port": 443, + "ssl_cert": "/etc/haproxy/certs/mycert.pem", + "hosts": { + "host1.com.br": { + "containers": [ + "container:80" + ] + } + } + }, + { + "port": 8080, + "hosts": { + "host3.com.br": { + "containers": [ + "domain:8181" + ] + } + } + } + ] + } + + assert expected == parsed + def test_parser_tcp(): @@ -187,6 +267,7 @@ def test_parser_tcp(): path = os.path.dirname(os.path.realpath(__file__)) with open(path + "/expected/services-tcp.txt", 'r') as expected_file: assert expected_file.read() == haproxy_config + assert [] == cfg.letsencrypt_hosts def test_parser_multi_containers(): line_list = load_fixture("services-multi-containers") @@ -202,6 +283,7 @@ def test_parser_multi_containers(): path = os.path.dirname(os.path.realpath(__file__)) with open(path + "/expected/services-multi-containers.txt", 'r') as expected_file: assert expected_file.read() == haproxy_config + assert [] == cfg.letsencrypt_hosts #test_parser_finds_services_raw()