diff --git a/.gitignore b/.gitignore index d760fc3..d2768a5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ venv .docker_data __pycache__ +.pytest_cache \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ed1f6e6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,26 @@ +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/Dockerfile b/Dockerfile index 4827b7d..86f11ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,21 @@ -FROM haproxy:2.1-alpine +FROM alpine:3.14 WORKDIR /scripts -RUN apk add --no-cache bash python3 py-yaml supervisor docker +RUN apk add --no-cache haproxy bash python3 py3-pip py-yaml supervisor docker \ + && ln -s /usr/bin/python3 /usr/bin/python COPY requirements.txt /scripts + RUN pip3 install --upgrade pip \ && pip install -r requirements.txt -COPY swarm.* /scripts/ -COPY static.* /scripts/ COPY templates /scripts/templates/ COPY easymapping /scripts/easymapping/ +COPY tests/ /scripts/tests/ COPY assets / +RUN pytest -s tests/ + CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisord.conf" ] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d721ef9 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +.PHONY: build +build: + docker build -t byjg/easy-haproxy -t byjg/easy-haproxy:local . + +.PHONY: test +test: + pytest tests/ diff --git a/README.md b/README.md index 28cb482..8f040f2 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,24 @@ -# Easy HAProxy +# Easy HAProxy -This Docker image will create dynamically the `haproxy.cfg` based on the labels defined in docker containers or from -a simple Yaml instead docker +This Docker image will create dynamically the `haproxy.cfg` based on the labels defined in docker containers or from +a simple Yaml instead docker -# Features +## Features - Enable or disable Stats on port 1936 with custom password -- Discover and setup haproxy from Docker Tag +- Discover and setup haproxy from Docker Tag - Discover and setup haproxy redirect from Docker Tag -- Setup HAProxy CFG from a Yaml file. +- Setup HAProxy CFG from a Yaml file. -# Basic Usage +## Basic Usage -The Easy HAProxy will create the `haproxy.cfg` automatically based on the containers or from a YAML provided. +The Easy HAProxy will create the `haproxy.cfg` automatically based on the containers or from a YAML provided. The basic command line to run is: ```bash -docker run -d \ +docker run -d \ --name easy-haproxy-container \ -v /var/run/docker.sock:/var/run/docker.sock \ -e DISCOVER="swarm|docker|static" \ @@ -29,9 +29,8 @@ docker run -d \ The mapping to `/var/run/docker.sock` is necessary to discover the docker containers and get the labels; -The environment variables will setup the HAProxy. +The environment variables will setup the HAProxy. -{:.table} | Environment Variable | Description | |----------------------|-------------------------------------------------------------------------------| | DISCOVER | How `haproxy.cfg` will be created: `static`, `docker` or `swarm` | @@ -47,11 +46,11 @@ The environment variable `DISCOVER` will define where is located your containers - swarm - static -# DISCOVER: docker +## DISCOVER: docker -This method will use a regular docker installation to discover the containers and configure the HAProxy. +This method will use a regular docker installation to discover the containers and configure the HAProxy. -The only requirement is that containers and easy-haproxy must be in the same docker network. +The only requirement is that containers and easy-haproxy must be in the same docker network. The discover will occur every minute. @@ -65,27 +64,27 @@ docker run --network easyhaproxy byjg/easyhaproxy docker run --network easyhaproxy myimage ``` -# DISCOVER: swarm +## DISCOVER: swarm -This method requires a functional Docker Swarm Cluster. The system will search for the labels in all containers on all -swarm nodes. +This method requires a functional Docker Swarm Cluster. The system will search for the labels in all containers on all +swarm nodes. The discover will occur every minute. -Important: easyhaproxy needs to be in the same network of the containers or otherwise will not access. +Important: easyhaproxy needs to be in the same network of the containers or otherwise will not access. -## Tags to be attached in the Docker Container - -{:.table} -| Tag | Description | -|---------------------------------------------|---------------------------------------------------------------------------------------------------------| -| com.byjg.easyhaproxy.definitions | A Comma delimited list with the definitions. Each name requires the definition of the parameters below. | -| com.byjg.easyhaproxy.port.[definition] | (Optional) What is the port that the HAProxy will listen to. (Defaults to 80) | -| com.byjg.easyhaproxy.localport.[definition] | (Optional) What is the port that the container is listening. (Defaults to 80) | -| com.byjg.easyhaproxy.host.[definition] | What is the host that the HAProxy will listen to. | -| com.byjg.easyhaproxy.redirect.[definition] | (Optional) Host redirects from connections in the port defined above. | -| com.byjg.easyhaproxy.sslcert.[definition] | (Optional) Cert PEM Base64 encoded. | +### Tags to be attached in the Docker Container (Swarm or Docker) +| Tag | Description | Example | +|---------------------------------------------|---------------------------------------------------------------------------------------------------------|--------------| +| com.byjg.easyhaproxy.definitions | A Comma delimited list with the definitions. Each name requires the definition of the parameters below. | http,https | +| com.byjg.easyhaproxy.mode.[definition] | (Optional) Is this http or tcp mode in HAProxy. (Defaults to http) | http | +| com.byjg.easyhaproxy.port.[definition] | (Optional) What is the port that the HAProxy will listen to. (Defaults to 80) | 80 | +| com.byjg.easyhaproxy.localport.[definition] | (Optional) What is the port that the container is listening. (Defaults to 80) | 8080 | +| com.byjg.easyhaproxy.host.[definition] | What is the host that the HAProxy will listen to. | somehost.com | +| com.byjg.easyhaproxy.redirect.[definition] | (Optional) Host redirects from connections in the port defined above. | foo.com--https://bla.com,bar.com--https://bar.org | +| com.byjg.easyhaproxy.sslcert.[definition] | (Optional) Cert PEM Base64 encoded. | | +| com.byjg.easyhaproxy.health-check.[definition] | (Optional) `ssl`, enable health check via SSL in `mode tcp` (Defaults to "empty") | | Note: if you are deploying a stack set labels at the `deploy` level: @@ -126,6 +125,22 @@ docker run \ some/myimage ``` +### TLS passthrough + +Used to pass on SSL-termination to a backend: + +```bash +docker run \ + -l com.byjg.easyhaproxy.defintions=tcp-service \ + -l com.byjg.easyhaproxy.mode.tcp-service=tcp \ + -l com.byjg.easyhaproxy.health-check.tcp-service=ssl \ + -l com.byjg.easyhaproxy.port.tcp-service=443 + .... \ + some/tcp-service +``` + + - enable health-check via SSL on the backend with the optional `health-check` label + ### Redirect Example: ```bash @@ -133,7 +148,7 @@ docker run \ -l com.byjg.easyhaproxy.redirect.=www.byjg.com.br--http://byjg.com.br,byjg.com--http://byjg.com.br ``` -# DISCOVER: static +## DISCOVER: static This method expects a YAML file to setup the `haproxy.cfg` @@ -149,14 +164,14 @@ customerrors: true # Optional (default false) easymapping: - port: 80 - hosts: + hosts: host1.com.br: container:5000 host2.com.br: other:3000 redirect: www.host1.com.br: http://host1.com.br - + - port: 443 - ssl_cert: BASE64_PEM_CERTIFICATE + ssl_cert: /path/to/ssl/certificate hosts: host1.com.br: container:80 @@ -173,10 +188,10 @@ docker run -v /my/config.yml:/etc/haproxy/easyconfig.yml .... byjg/easyhaproxy # Mapping custom .cfg files -Map a folder containing valid HAProxy `.cfg` files to `/etc/haproxy/conf.d`. It will be concatenated to your HAProxy CFG. +Map a folder containing valid HAProxy `.cfg` files to `/etc/haproxy/conf.d`. It will be concatenated to your HAProxy CFG. ```bash -docker run \ +docker run \ /* other parameters */ -v /your/local/conf.d:/etc/haproxy/conf.d \ -d byjg/easy-haproxy @@ -185,9 +200,9 @@ docker run \ # Handling SSL -You can attach a valid SSL certificate to the request. +You can attach a valid SSL certificate to the request. -1. First Create a single PEM file including CA. +1. First Create a single PEM file including CA. ```bash cat example.com.crt example.com.key > single.pem @@ -215,8 +230,8 @@ cat single.pem | base64 -w0 # Setting Custom Errors -If enabled, map the volume : `/etc/haproxy/errors-custom/` to your container and put a file named `ERROR_NUMBER.http` -where ERROR_NUMBER is the http error code (e.g. 503.http) +If enabled, map the volume : `/etc/haproxy/errors-custom/` to your container and put a file named `ERROR_NUMBER.http` +where ERROR_NUMBER is the http error code (e.g. 503.http) # Build diff --git a/assets/scripts/exit-event-listener.py b/assets/scripts/exit-event-listener.py index 9b1826f..4f64778 100755 --- a/assets/scripts/exit-event-listener.py +++ b/assets/scripts/exit-event-listener.py @@ -5,6 +5,7 @@ import signal from supervisor import childutils + def main(): while True: headers, payload = childutils.listener.wait() @@ -12,7 +13,11 @@ def main(): events = ['PROCESS_STATE_FATAL', 'PROCESS_STATE_EXITED', 'PROCESS_STATE_STOPPED'] if not (headers['eventname'] in events): continue + + print(headers) + print(payload) os.kill(os.getppid(), signal.SIGTERM) + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/assets/scripts/haproxy-reload.sh b/assets/scripts/haproxy-reload.sh index df9f603..ecee161 100755 --- a/assets/scripts/haproxy-reload.sh +++ b/assets/scripts/haproxy-reload.sh @@ -18,14 +18,18 @@ else if [[ "$DISCOVER" == "docker" ]]; then CONTAINERS=$(docker ps -q) LABEL_PATH=".Config.Labels" + + for container in ${CONTAINERS}; do + docker inspect --format "{{ json $LABEL_PATH }}" ${container} | xargs -I % echo ${container}=% >> ${CONTROL_FILE} + done else CONTAINERS=$(docker node ps $(docker node ls -q) --format "{{ .Name }}" --filter desired-state=running | cut -d. -f1 | sort | uniq) LABEL_PATH=".Spec.Labels" - fi - for container in ${CONTAINERS}; do - docker inspect --format "{{ json $LABEL_PATH }}" ${container} | xargs -I % echo ${container}=% >> ${CONTROL_FILE} - done + for container in ${CONTAINERS}; do + docker service inspect --format "{{ json $LABEL_PATH }}" ${container} | xargs -I % echo ${container}=% >> ${CONTROL_FILE} + done + fi if cmp -s ${CONTROL_FILE} ${CONTROL_FILE}.old ; then RELOAD="false" diff --git a/assets/scripts/haproxy.sh b/assets/scripts/haproxy.sh index 89dd3a9..2af313f 100755 --- a/assets/scripts/haproxy.sh +++ b/assets/scripts/haproxy.sh @@ -2,7 +2,7 @@ source /scripts/haproxy-reload.sh initial -/usr/local/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /var/run/haproxy.sock +/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /var/run/haproxy.sock while true; do sleep 60 diff --git a/static.py b/assets/scripts/static.py similarity index 100% rename from static.py rename to assets/scripts/static.py diff --git a/assets/scripts/swarm.py b/assets/scripts/swarm.py new file mode 100644 index 0000000..c7741ec --- /dev/null +++ b/assets/scripts/swarm.py @@ -0,0 +1,24 @@ +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() + +result = { + "customerrors": True if os.getenv("HAPROXY_CUSTOMERRORS") == "true" else False +} + +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", + } + +cfg = HaproxyConfigGenerator(result) +print(cfg.generate(lineList)) + +# print(jsonStr) + + diff --git a/build-multiarch.sh b/build-multiarch.sh new file mode 100755 index 0000000..6a5ae0e --- /dev/null +++ b/build-multiarch.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +set -e + +# Start k8s-ci before run this command +# docker run --privileged -v /tmp/z:/var/lib/containers -it --rm -v $PWD:/work -w /work byjg/k8s-ci + +if [ -z "$DOCKER_USERNAME" ] || [ -z "$DOCKER_PASSWORD" ] || [ -z "$DOCKER_REGISTRY" ] +then + echo You need to setup \$DOCKER_USERNAME, \$DOCKER_PASSWORD and \$DOCKER_REGISTRY before run this command. + exit 1 +fi + +buildah login --username $DOCKER_USERNAME --password $DOCKER_PASSWORD $DOCKER_REGISTRY + +podman run --rm --events-backend=file --cgroup-manager=cgroupfs --privileged docker://multiarch/qemu-user-static --reset -p yes + +VERSIONS="latest $TRAVIS_TAG" +for VERSION in $VERSIONS +do + DOCKERFILE=Dockerfile + + buildah manifest create byjg/easy-haproxy:$VERSION + + buildah bud --arch arm64 --os linux --iidfile /tmp/iid-arm64 -f $DOCKERFILE -t byjg/easy-haproxy:$VERSION-arm64 . + buildah bud --arch amd64 --os linux --iidfile /tmp/iid-amd64 -f $DOCKERFILE -t byjg/easy-haproxy:$VERSION-amd64 . + + buildah manifest add byjg/easy-haproxy:$VERSION --arch arm64 --os linux --variant v8 $(cat /tmp/iid-arm64) + buildah manifest add byjg/easy-haproxy:$VERSION --arch amd64 --os linux --os=linux $(cat /tmp/iid-amd64) + + buildah manifest push --all --format v2s2 byjg/easy-haproxy:$VERSION docker://byjg/easy-haproxy:$VERSION +done + diff --git a/easymapping/__init__.py b/easymapping/__init__.py index 1b54af2..d570085 100644 --- a/easymapping/__init__.py +++ b/easymapping/__init__.py @@ -1,12 +1,146 @@ +import base64 +import hashlib from jinja2 import Environment, FileSystemLoader +import json +import os + +class DockerLabelHandler: + def __init__(self, label): + self.__label_base = label + + + def create(self, key): + if isinstance(key, str): + return "{}.{}".format(self.__label_base, key) + + return "{}.{}".format(self.__label_base, ".".join(key)) + + + def get(self, label, default_value = ""): + if self.has_label(label): + return self.__data[label] + return default_value + + + def set_data(self, data): + self.__data = data + + + def has_label(self, label): + if label in self.__data: + return True + return False class HaproxyConfigGenerator: - def __init__(self, mapping): + def __init__(self, mapping, ssl_cert_folder="/etc/haproxy/certs"): self.mapping = mapping + self.label = DockerLabelHandler("com.byjg.easyhaproxy") + self.ssl_cert_folder = ssl_cert_folder + self.ssl_cert_increment = 0 + os.makedirs(self.ssl_cert_folder, exist_ok=True) + + + def generate(self, lineList = []): + # static? + if len(lineList) > 0: + self.mapping["easymapping"] = self.__parse(lineList) + + # still 'None' -> default to [] for jinja2 + if self.mapping["easymapping"] is None: + self.mapping["easymapping"] = [] - def generate(self): file_loader = FileSystemLoader('templates') env = Environment(loader=file_loader) + env.trim_blocks = True + env.lstrip_blocks = True + env.rstrip_blocks = True template = env.get_template('haproxy.cfg.j2') return template.render(data=self.mapping) + + + def __parse(self, lineList): + easymapping = dict() + + for line in lineList: + line = line.strip() + i = line.find("=") + container = line[:i] + jsonStr = line[i+1:] + d = json.loads(jsonStr) + + if self.label.create("definitions") not in d.keys(): + continue + + self.label.set_data(d) + + definitions = d[self.label.create("definitions")].split(",") + for definition in definitions: + mode = self.label.get( + self.label.create(["mode", definition]), + "http" + ) + + # TODO: we can ignore "host" in TCP, but it would break the template + host_label = self.label.create(["host", definition]) + if not self.label.has_label(host_label): + continue + + port = self.label.get( + self.label.create(["port", definition]), + "80" + ) + + hash = "" + if self.label.create(["sslcert", definition]) in d: + hash = hashlib.md5( + d[self.label.create(["sslcert", definition])].encode('utf-8') + ).hexdigest() + + key = port if not hash else port + "_" + hash + + if key not in easymapping: + easymapping[key] = { + "mode": mode, + "health-check": "", + "port": port, + "hosts": dict(), + "redirect": dict(), + } + + # TODO: this could use `EXPOSE` from `Dockerfile`? + ct_port = self.label.get( + self.label.create(["localport", definition]), + "80" + ) + + easymapping[key]["health-check"] = self.label.get( + self.label.create(["health-check", definition]), + "" + ) + + easymapping[key]["hosts"][d[host_label]] = "{}:{}".format(container, ct_port) + + # handle SSL + ssl_label = self.label.create(["sslcert", definition]) + if self.label.has_label(ssl_label): + self.ssl_cert_increment += 1 + filename = "{}/{}.{}.pem".format( + self.ssl_cert_folder, d[host_label], str(self.ssl_cert_increment) + ) + easymapping[key]["ssl_cert"] = filename + with open(filename, 'wb') as file: + file.write( + base64.b64decode(d[ssl_label]) + ) + + # handle redirects + redirect = self.label.get( + self.label.create(["redirect", definition]) + ) + if len(redirect) > 0: + for r in redirect.split(","): + r_parts = r.split("--") + easymapping[key]["redirect"][r_parts[0]] = r_parts[1] + + return easymapping.values() diff --git a/examples/docker/docker-compose.yml b/examples/docker/docker-compose.yml new file mode 100644 index 0000000..e1c697b --- /dev/null +++ b/examples/docker/docker-compose.yml @@ -0,0 +1,32 @@ +version: "3" + +services: + haproxy: + image: byjg/easy-haproxy + volumes: + - /var/run/docker.sock:/var/run/docker.sock + environment: + DISCOVER: docker + 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 + labels: + com.byjg.easyhaproxy.definitions: "http,https" + + com.byjg.easyhaproxy.redirect.http: host1.local--https://host1.local + com.byjg.easyhaproxy.host.http: host1.local + com.byjg.easyhaproxy.port.http: 80 + + com.byjg.easyhaproxy.port.https: 443 + com.byjg.easyhaproxy.localport.https: 8080 + com.byjg.easyhaproxy.host.https: host1.local + com.byjg.easyhaproxy.sslcert.https: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZEVENDQXZXZ0F3SUJBZ0lVUmkrdzFaVmdlZWRUbE5JQXdxUUJNSnY2ZFhzd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0ZqRVVNQklHQTFVRUF3d0xhRzl6ZERFdWJHOWpZV3d3SGhjTk1qRXdPREV3TVRnME9UQTJXaGNOTXpFdwpPREE0TVRnME9UQTJXakFXTVJRd0VnWURWUVFEREF0b2IzTjBNUzVzYjJOaGJEQ0NBaUl3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFNQkRBaExBeWdKdWFXNnc2ZmZpZ3pUQUFHWHBtRXowdEl4bjFrNFoKeDV3TjVycHYvcXUwUU1ZeitBdjJ1MWVPS0VLWmVhRlJWcFQwcjkzZFg3SXZiRVpIdDI1R1BpQnZsTEdxaGpLUgpQblNrLzdVOFhtc250dFVBVjdyVkVLMVVyZEZ3OC9Jd3JpUUMrZGhyMG1uWWZTRE1rdkJvTUZwZGhWTlRyYkFaCjFUQjZyUWpFN0FyME10OG15OTZYSm13cmNqSzJUaitFMnJnUElVejFlNWNla0ZZSURTQmF0bXcrMyt2citUNXgKRk5Ga0oybzMwVzVvOFpmbENKSnpyVmFpaHFRaWNzNlpLRGdwZjdpcVhNRml3V0lsaGRRcEd2eDVHZi9LRlRLOQpVYU9uUlp6L1grMkNlYkFGYVRIUjNrL1BZcHBXVGdCQkJ1UnZscEN3K3dkbmttdGVDMFNRUkY5MVFXVnI3ZWpvCjdLYU9sR0k1VnR2TVVzV3ZUZUFabXBheW1JYUFURVR1T0phWTBKVTExT21MZUQ5RE9qNUUyU1E3cUlYL3BGY3AKeHB6RzVqNGMrTWxndnhQMlZBa05UZUFYQ2FZaVBCUUg1WlpnMEhFMlduQjFLaExSRmxIZDRpSFFEMkdKNXlOLwo2ZkNGQmZaZktTZUs4SmF1d3hnV2tyYTUzT2NEcS9tS2QrREEvZEsrL3J1Rzd0cXdWZ0lhMDRIT3Bsek03TFlSCkdCMElyczkrbHI1L1BKYlFabVUwNzNNZG42Y1hBZzNwKzZ3dndGbERrUzV2MTNnQkRZTkh0RjYyYmM1NTFlZEYKWjZrR3pKN3dtR1JvODRhQlA3TXVSWmVSZUxPclNTNjdhMXdMZHpac01uUDFUSjd4OUxmcjlNS2wydURuUWRuWQpleDhEQWdNQkFBR2pVekJSTUIwR0ExVWREZ1FXQkJTUS9tdFpkNmg4ZW45WVFWSDZITzFQbFdXaXF6QWZCZ05WCkhTTUVHREFXZ0JTUS9tdFpkNmg4ZW45WVFWSDZITzFQbFdXaXF6QVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BMEcKQ1NxR1NJYjNEUUVCQ3dVQUE0SUNBUUNoUVlOdWFoMyttVHBJQkRZeEdyalRKTnVPVElNYVd6TXlpMXRrZitMMApzRUd3cGJtQU8ybVdXUVlGN1dWTHNpOThQVUxoM2FkanQyaml1ZDlWbGFhQzZnbnduNVpvMStQaWxvOXNOTExXCjZpajArck40a3dJbS9wTnFpK2pEdXUyY3ZBdUhJd1pXZWg4YkVlLzVVQ3hvNGlobVdGUU44ZUo2VFVLQ3BoUkMKNkVvci9TU1paQlFIZ1BsMEJjaHpIT2t3dTdSM0xDbmRScXhqaEFvVmI5eVFPVitac21UZUpYdWx3TnpKMXVMdApUOE9JZ0lpRHBtQm83SFNOMkgwazNjaHgwMEFzalV5SjltbUFXUGVqRmUvS1hMUlBjVlpSMTdqaHpnZklCRXpzCk01V3RXRm0xYUhEalZ2Nk02aXRlVm02MUU5VCtrL00xMXJ1MWUyWXdzeFREdmI2eDA0bWNyTnU5c29xZGRCYnIKVmZwbHV1b1EvaEVBYlh0Rk5Qb1R5U3B6MGN3T3djSENvd1ZPTG1kS2d2SW1zelppTXlISEc4VkdHbVBoODhuNwp3VnhiMGdWMFA0Uk1yY01MZGVUZG41NVlRcjFDcUJyMzRlQjZvbDZBc2JUbTNWekJIUlZtRk5rc2wxbzVKQjV0CnRYTGdGL0c4L3J6Si80bTFQYVZ1eHJCN0R4VW1JazhFUGJTSVZrdlp2ZDdMQnpLd1E2SWZWYXVjZXdIZkVhalEKVklpZXhTTWlGYzdsdzNLbnhqT0haamY2Rk05VllnM05vKytHZEM5OXM3TGtJdUp3QU1MTnFUUTdIdmhuN1l2UAo0RmxTSWdjNnhqMFlrR1pFUWxiNW8vNW5hdUVxUVUwQUJndzZqdEk0TnhyTkxUNmNwN0NPNE0weElERWcvM1lECmFBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQotLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS0KTUlJSlF3SUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NDUzB3Z2drcEFnRUFBb0lDQVFEQVF3SVN3TW9DYm1sdQpzT24zNG9NMHdBQmw2WmhNOUxTTVo5Wk9HY2VjRGVhNmIvNnJ0RURHTS9nTDlydFhqaWhDbVhtaFVWYVU5Sy9kCjNWK3lMMnhHUjdkdVJqNGdiNVN4cW9ZeWtUNTBwUCsxUEY1cko3YlZBRmU2MVJDdFZLM1JjUFB5TUs0a0F2blkKYTlKcDJIMGd6Skx3YURCYVhZVlRVNjJ3R2RVd2VxMEl4T3dLOURMZkpzdmVseVpzSzNJeXRrNC9oTnE0RHlGTQo5WHVYSHBCV0NBMGdXclpzUHQvcjYvaytjUlRSWkNkcU45RnVhUEdYNVFpU2M2MVdvb2FrSW5MT21TZzRLWCs0CnFsekJZc0ZpSllYVUtScjhlUm4veWhVeXZWR2pwMFdjLzEvdGdubXdCV2t4MGQ1UHoyS2FWazRBUVFia2I1YVEKc1BzSFo1SnJYZ3RFa0VSZmRVRmxhKzNvNk95bWpwUmlPVmJiekZMRnIwM2dHWnFXc3BpR2dFeEU3amlXbU5DVgpOZFRwaTNnL1F6bytSTmtrTzZpRi82UlhLY2FjeHVZK0hQakpZTDhUOWxRSkRVM2dGd21tSWp3VUIrV1dZTkJ4Ck5scHdkU29TMFJaUjNlSWgwQTloaWVjamYrbndoUVgyWHlrbml2Q1dyc01ZRnBLMnVkem5BNnY1aW5mZ3dQM1MKdnY2N2h1N2FzRllDR3RPQnpxWmN6T3kyRVJnZENLN1BmcGErZnp5VzBHWmxOTzl6SForbkZ3SU42ZnVzTDhCWgpRNUV1YjlkNEFRMkRSN1JldG0zT2VkWG5SV2VwQnN5ZThKaGthUE9HZ1QrekxrV1hrWGl6cTBrdXUydGNDM2MyCmJESno5VXllOGZTMzYvVENwZHJnNTBIWjJIc2ZBd0lEQVFBQkFvSUNBUUMveFpiWjBjY3RxYWdzcXZhVk5URWUKZXExcStoZmFHdlBFWVFhWUhJcklFKzJpNVhjbkdjTEtjS2ZvZHhEakFuOFIvemdkT3A2Y01YMENWbi9Qb2hIawpBRUR0RTgrQVZ3d0FNMUZzT3dnTEhWR2FHejhxcnhCbFlkUWdIY3BtdWVJdTJQWGJDOGVIVUJpYVVPSXVoYXc1Ci9SUk1EQUMvQWkyc3NmaTdnT2p2VkU0b1F4UVcwUUcxS0dPT0FVSm4vdVlIdzJSRlkyVXUxcGlteE8ya0RPNTMKZ2N4bUMxV09ueUNIbUhhaVcvVWg3ejZKYW1mU000ZFh0VEpac2x5aDM3ZGhIS05iZzlWa1A3Q1FLQTRoTHpvcApoYmY1cVk2cmFyZ09OaW55MUhnTVB4cm13S3VVb3VKeU90TjB5QnR4akRDVU5hWFVCd2l5N3NOR1MrSDR2c3lCCjVQOUhoSUhTdHUrRlp0M0hHN0VJcUNuZGlhU0tEUzRqV2FWUUFiYm80bloyWnMyQkQreERlUFJDUlVxWDdyTTQKNFh6UElSV1dYbW1XZi83SWcyOUhicnA0YTlMY09tUTJsZUNKdGJhVEZTTjk2T0xVSjVFK2hRMHVsQ1pnQlZtUQpSQ1VZa0pQNGxPemJhS2R6anhnSE1ySHptNDVlVUZmOExpck94aTJ1eXhYSFFtRE51NGIzWDE4a3QzUGdVbVVtCjNkWHBsM2ZxU3lKYTdTQ1Y4Wk5CcnNyRHExRSt0aFl0dTkxUWJWU0d4SGQ5SHJOVmUzWGRMYk9DZFU5Q3VDNjkKTmdsem5hYTdzWkxxbXlLZWpUZkdzWTd4cldkTmNNUGw0cDRmY0lEL080RXBBU1pmb3JwVGVLTlQwWklmWlpldwpiMG1BUWVZWnFRTThpL3FNWU4vdUFRS0NBUUVBNXFnMXNSTk1jNlZkTS90UmdsYXNHWW94amdSQzJPcUFEWmdzCm1BWE1VSjNrRXJweXh0K2VDaW15OGlidVlwelJUSVE4ZkJUV1JrQ3RSWlhKNytLY0xWdGs5UVpJb0xiaHlOd2QKNEl4RVFaRnVVbGpEYnZTalRMU3ljc0h2bzY1aWJXSWZUTDdiZ1dsTEdnR3EvVU96ZkdzZ0g2Uzl3THA1RzMwRwo4RUx5akk1ZVRJWUlDcmZUbVZMK2M0NU1ScEVNS28rY3Z6OFB5c2lhT0ZUbjNjeXN3UFZkWWFlRUVxTVFqVTh3CklHTnNHWkx5dFk3QkFCQmNZMGxkcnRiYS9PK0Z2LytSSDd1VXR6UDd4cENJd0ZDeDgwWnpOK1dSeTlOdkk2M1UKenEzeUlCb1c5R3lBcEQyK1BMYVBOeGY3UUxUVUNoWTFaei9kWVJsdEtPeHYyQWE1Z1FLQ0FRRUExV0xXTnFwMApmaEIvWnRmU0VTaHhGTU04OWNqTjZBYXoxV0tMN3VUQm91OW9TSm54amtoa2FWNzZhY25UL2lxWHR4TU5nSGkxCmZJbURwVTNQdk0wWTRVZDJUNDdvSGM2UDFCclpQTi9HbVh5L3M2QkFFZFB3TGU3Sis0blRJU0hBZEdtcmgrYS8KNXBrdHUzMmc5bFdxZnR4ZWNGSVZTTFBXa3hUMFhLaU14cDFmZmtMK09hdnBNZ01GWks0MWlLczNkTlNoS1BvZwpMOEdTUGNQOXgveW43OFAyZUszTitQR2psQTZwUHpyQU55V1U3TjAvYm1IY0I5VEtQK3VkWVdjalZocnU3TVlOCndOckU0a0tkQzh2OGk3eDd0RGJ2Yjc5VCtGbzZQSWg1M3AwT3NuWnpBOFVSMFFOUit2RFF1ZlF1eWFqOFJFQysKWkc4WXlDS3N2azh5Z3dLQ0FRQS9mc1N4QjBmL2VlRXJZeDZ3QzUzNnRlRW9ZQ0hxeHJzVGd2V2JyOVRyeUZzMQprSi95QVRMblIwMWNmYjBYNW1WemM5K1dwTUhMdXhnMzFLRXZhU2xuRHdhK3NNa2pmTlN3ejI5bUZoYmdHZUhOCngyT2RVcmoxYjdURUJJRXNoTi9SanJaaEVSVXFEY3MvMEgrNmtuMkJYWmdOUGZPQ2I1TFJMMXpPblE5YUJBTVAKZThJUStVUEZyR1FoZVdXajgxL3ZBM081N2VreUFJRDd5dHU5WWcrWVdyTW5JODhtdGo3ak40NWZEQitBOXNQYgptUDJtUDlxKzlqNVUyQTZXbkhVc1FuVTMwQktEVUVzYUFVV3o4MExaWG1adlY4SUg0eDl3S2ZVd0pCQklLQVp6CnFMN005N1k3em1Ha1gvU3BmbDMwbk9KOGxzY2hhTGQxRVlsRVphMkJBb0lCQVFDeWUyNVQ0VFY1TUpGdjB6dVoKTUd1TmcxU2MvTzRGa24yZkVVT2NlV2pod1VCSDRjUGpUL2YxRHdXRHNOYUo5TlJieENyNTkzMU9BclBEYzVjOApBNDA0K1k0ak01UkJRa0tabGk5NHRIQW9kK2pjOVVCQjZUVXZKbGw1OVNsTXdDOTY3OXdTMjFaT0tuZlBLR0NYClNzWkdRRXNaeGY2Wmhoc0hnWEozZ2wvbHpVSlBtUGVPQTVZVlIrT2Q5LzA5S0lGRlRvalNmb3luaFZDdUt4NDkKeGI0dVZZbjJIT0o0eEowZlBUZ2hkQ0hNdnJtWGVlUVJqdmI4OGVhTm1xVlVFSEhGRnRnYjRma2xBNWZFN1JUeApCaGxpUkRCd1o3YlVrSU5LNnlWazluNkJUbnM1bU12UkxtZ2RuSnBZdkU3S0MwMkxUYlpiM0krajhDMFpVYStOCnF5N0RBb0lCQUFpZXJpYlM3V1VjbDJhQmxrbTUrVzdxTm0vSU5tNXp2bm9TUG82VjN3YTVoczZmOStDL2tiZEYKODdqUVBBL1lGZTN1UjJzQUo3c2xYNWV1Wks4V21mcEZtZ3psdTBzRXo4MU1MUS9XeXBadFp5dHlWdFd6QjJQdQpYQ1cxdGRTSDllSTJCbWhYZ29rSE5UTTQ4TmsveE9FTnJQL3NlWHJJeDVMSzBobkRIWm90dS96NitZU2tCOWhGCmNtMmZaeWdEMWRNTFg2bGlSaW14eUZZK2RJQ0pOQjk1SmlmVExXWW5XZUdkZGt3UHRYVWVHWEUxb2x6dk5rTEQKek16RTA5dWhreC9sUkpudGVPQkVaYWY4ME9CLzA5T2k5YjkvcnhZNTlkd3NINkdheExvVGZFS3VQbnZCVk1OUgpZa1UxNFd6UUtsZUZraUJKSTlsVnZuZmdHbk9sZ2cwPQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t \ No newline at end of file diff --git a/examples/static/config.yml b/examples/static/config.yml new file mode 100644 index 0000000..2e75011 --- /dev/null +++ b/examples/static/config.yml @@ -0,0 +1,17 @@ +stats: + username: admin + password: password + port: 1936 # Optional (default 1936) + +customerrors: true # Optional (default false) + +easymapping: + - port: 80 + redirect: + host1.local: https://host1.local + www.host1.local: https://host1.local + + - port: 443 + ssl_cert: /etc/certs/host1.local.pem + hosts: + host1.local: container:8080 diff --git a/examples/static/docker-compose.yml b/examples/static/docker-compose.yml new file mode 100644 index 0000000..59aaeb5 --- /dev/null +++ b/examples/static/docker-compose.yml @@ -0,0 +1,18 @@ +version: "3" + +services: + haproxy: + image: byjg/easy-haproxy + volumes: + - ./config.yml:/etc/haproxy/easyconfig.yml + - ./host1.local.pem:/etc/certs/host1.local.pem + - /var/run/docker.sock:/var/run/docker.sock + environment: + DISCOVER: static + ports: + - "80:80/tcp" + - "443:443/tcp" + - "1936:1936/tcp" + + container: + image: byjg/static-httpserver diff --git a/examples/static/host1.local.pem b/examples/static/host1.local.pem new file mode 100644 index 0000000..0d7eb39 --- /dev/null +++ b/examples/static/host1.local.pem @@ -0,0 +1,82 @@ +-----BEGIN CERTIFICATE----- +MIIFDTCCAvWgAwIBAgIURi+w1ZVgeedTlNIAwqQBMJv6dXswDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAwwLaG9zdDEubG9jYWwwHhcNMjEwODEwMTg0OTA2WhcNMzEw +ODA4MTg0OTA2WjAWMRQwEgYDVQQDDAtob3N0MS5sb2NhbDCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAMBDAhLAygJuaW6w6ffigzTAAGXpmEz0tIxn1k4Z +x5wN5rpv/qu0QMYz+Av2u1eOKEKZeaFRVpT0r93dX7IvbEZHt25GPiBvlLGqhjKR +PnSk/7U8XmsnttUAV7rVEK1UrdFw8/IwriQC+dhr0mnYfSDMkvBoMFpdhVNTrbAZ +1TB6rQjE7Ar0Mt8my96XJmwrcjK2Tj+E2rgPIUz1e5cekFYIDSBatmw+3+vr+T5x +FNFkJ2o30W5o8ZflCJJzrVaihqQics6ZKDgpf7iqXMFiwWIlhdQpGvx5Gf/KFTK9 +UaOnRZz/X+2CebAFaTHR3k/PYppWTgBBBuRvlpCw+wdnkmteC0SQRF91QWVr7ejo +7KaOlGI5VtvMUsWvTeAZmpaymIaATETuOJaY0JU11OmLeD9DOj5E2SQ7qIX/pFcp +xpzG5j4c+MlgvxP2VAkNTeAXCaYiPBQH5ZZg0HE2WnB1KhLRFlHd4iHQD2GJ5yN/ +6fCFBfZfKSeK8JauwxgWkra53OcDq/mKd+DA/dK+/ruG7tqwVgIa04HOplzM7LYR +GB0Irs9+lr5/PJbQZmU073Mdn6cXAg3p+6wvwFlDkS5v13gBDYNHtF62bc551edF +Z6kGzJ7wmGRo84aBP7MuRZeReLOrSS67a1wLdzZsMnP1TJ7x9Lfr9MKl2uDnQdnY +ex8DAgMBAAGjUzBRMB0GA1UdDgQWBBSQ/mtZd6h8en9YQVH6HO1PlWWiqzAfBgNV +HSMEGDAWgBSQ/mtZd6h8en9YQVH6HO1PlWWiqzAPBgNVHRMBAf8EBTADAQH/MA0G +CSqGSIb3DQEBCwUAA4ICAQChQYNuah3+mTpIBDYxGrjTJNuOTIMaWzMyi1tkf+L0 +sEGwpbmAO2mWWQYF7WVLsi98PULh3adjt2jiud9VlaaC6gnwn5Zo1+Pilo9sNLLW +6ij0+rN4kwIm/pNqi+jDuu2cvAuHIwZWeh8bEe/5UCxo4ihmWFQN8eJ6TUKCphRC +6Eor/SSZZBQHgPl0BchzHOkwu7R3LCndRqxjhAoVb9yQOV+ZsmTeJXulwNzJ1uLt +T8OIgIiDpmBo7HSN2H0k3chx00AsjUyJ9mmAWPejFe/KXLRPcVZR17jhzgfIBEzs +M5WtWFm1aHDjVv6M6iteVm61E9T+k/M11ru1e2YwsxTDvb6x04mcrNu9soqddBbr +VfpluuoQ/hEAbXtFNPoTySpz0cwOwcHCowVOLmdKgvImszZiMyHHG8VGGmPh88n7 +wVxb0gV0P4RMrcMLdeTdn55YQr1CqBr34eB6ol6AsbTm3VzBHRVmFNksl1o5JB5t +tXLgF/G8/rzJ/4m1PaVuxrB7DxUmIk8EPbSIVkvZvd7LBzKwQ6IfVaucewHfEajQ +VIiexSMiFc7lw3KnxjOHZjf6FM9VYg3No++GdC99s7LkIuJwAMLNqTQ7Hvhn7YvP +4FlSIgc6xj0YkGZEQlb5o/5nauEqQU0ABgw6jtI4NxrNLT6cp7CO4M0xIDEg/3YD +aA== +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +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 +YkU14WzQKleFkiBJI9lVvnfgGnOlgg0= +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..3acaa4f --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = -v -p no:warnings diff --git a/requirements.txt b/requirements.txt index fa90f6f..364b4b7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ pyyaml docker -jinja2 \ No newline at end of file +jinja2 +pytest \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f19e91e --- /dev/null +++ b/setup.py @@ -0,0 +1,20 @@ +from setuptools import setup, find_packages + + +with open('README.md') as f: + readme = f.read() + +with open('LICENSE') as f: + license = f.read() + +setup( + name='easymapping', + version='0.1.0', + description='HAProxy label based routing', + long_description=readme, + author='', + author_email='', + url='', + license=license, + packages=find_packages(exclude=('tests', 'docs')) +) diff --git a/swarm.py b/swarm.py deleted file mode 100644 index a74cef1..0000000 --- a/swarm.py +++ /dev/null @@ -1,74 +0,0 @@ -import os -import json -import time -import base64 -import hashlib -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() - -result = { - "easymapping": [], - "customerrors": True if os.getenv("HAPROXY_CUSTOMERRORS") == "true" else False -} -easymapping = dict() - -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", - } - -for line in lineList: - line = line.strip() - i = line.find("=") - container = line[:i] - jsonStr = line[i+1:] - d = json.loads(jsonStr) - - if "com.byjg.easyhaproxy.definitions" in d.keys(): - definitions = d["com.byjg.easyhaproxy.definitions"].split(",") - - for definition in definitions: - if "com.byjg.easyhaproxy.host." + definition not in d: - continue - - port = d["com.byjg.easyhaproxy.port." + definition] if "com.byjg.easyhaproxy.port." + definition in d else "80" - hash = hashlib.md5(d["com.byjg.easyhaproxy.sslcert." + definition].encode('utf-8')).hexdigest() if "com.byjg.easyhaproxy.sslcert." + definition in d else "" - - key = port+hash - - if key not in easymapping: - easymapping[key] = { - "port": port, - "hosts": dict(), - "redirect": dict(), - # "ssl_cert": "" - } - - easymapping[key]["hosts"][d["com.byjg.easyhaproxy.host." + definition]] = container + ":" + (d["com.byjg.easyhaproxy.localport." + definition] if "com.byjg.easyhaproxy.localport." + definition in d else "80") - - if "com.byjg.easyhaproxy.sslcert." + definition in d: - filename = '/etc/haproxy/certs/' + d["com.byjg.easyhaproxy.host." + definition] + "." + str(time.time()) + ".pem" - easymapping[key]["ssl_cert"] = filename - with open(filename, 'wb') as file: - file.write(base64.b64decode(d["com.byjg.easyhaproxy.sslcert." + definition])) - - if "com.byjg.easyhaproxy.redirect." + definition in d: - redirect = d["com.byjg.easyhaproxy.redirect." + definition] if "com.byjg.easyhaproxy.redirect." + definition in d else "" - for r in redirect.split(","): - r_parts = r.split("--") - easymapping[key]["redirect"][r_parts[0]] = r_parts[1] - - result["easymapping"] = easymapping.values() - - -cfg = HaproxyConfigGenerator(result) -print(cfg.generate()) - -# print(jsonStr) - - diff --git a/templates/bind.j2 b/templates/bind.j2 new file mode 100644 index 0000000..6cad5ff --- /dev/null +++ b/templates/bind.j2 @@ -0,0 +1,8 @@ + {% if "ssl_cert" in o %} + bind *:{{ o["port"] }} ssl crt {{ o["ssl_cert"] }} + {% elif "h2" in o and o["h2"] %} + bind *:{{ o["port"] }} proto h2 + option http-use-htx + {% else %} + bind *:{{ o["port"] }} + {% endif %} diff --git a/templates/frontend-mode-http.j2 b/templates/frontend-mode-http.j2 new file mode 100644 index 0000000..5d903e6 --- /dev/null +++ b/templates/frontend-mode-http.j2 @@ -0,0 +1,11 @@ + mode http + {% for k in o["redirect"] %} + redirect prefix {{ o["redirect"][k] }} code 301 if { hdr(host) -i {{ k }} } + {% endfor %} + {% for k in o["hosts"] %} + {% set host = k.replace(".", "_") + "_{0}_{1}".format(o["port"], salt) %} + + acl is_rule_{{ host }}_1 hdr(host) -i {{ k }} + acl is_rule_{{ host }}_2 hdr(host) -i {{ k }}:{{ o["port"] }} + use_backend srv_{{ host }} if is_rule_{{ host }}_1 OR is_rule_{{ host }}_2 + {% endfor %} diff --git a/templates/frontend-mode-tcp.j2 b/templates/frontend-mode-tcp.j2 new file mode 100644 index 0000000..012d865 --- /dev/null +++ b/templates/frontend-mode-tcp.j2 @@ -0,0 +1,6 @@ + mode tcp + option tcplog + log global +{% set backend = (o["hosts"]|first) %} + default_backend srv_{{ backend.replace(".", "_") + "_{0}_{1}".format(o["port"], salt) }} + diff --git a/templates/haproxy.cfg.j2 b/templates/haproxy.cfg.j2 index 96862f3..e04b1e5 100644 --- a/templates/haproxy.cfg.j2 +++ b/templates/haproxy.cfg.j2 @@ -1,3 +1,8 @@ +global + log stdout format raw local0 info + maxconn 2000 + tune.ssl.default-dh-param 2048 + defaults log global @@ -14,11 +19,6 @@ defaults errorfile 504 /etc/haproxy/errors-custom/504.http {% endif %} -global - log /dev/log local0 - maxconn 2000 - tune.ssl.default-dh-param 2048 - {% if "stats" in data %} frontend stats bind *:{{ data["stats"]["port"] | default(1936) }} @@ -37,32 +37,31 @@ backend srv_stats mode http server Local 127.0.0.1:{{ data["stats"]["port"] | default(1936) }} {% endif %} - -{% for o in data["easymapping"] %} +{% for o in data["easymapping"] -%} + {% set mode = o["mode"] or "http" %} {% set salt = loop.index %} -frontend http_in_{{ o["port"] }}_{{ salt }} - bind *:{{ o["port"] }} {{ " ssl crt " + o["ssl_cert"] if "ssl_cert" in o else "" }} - mode http - {% for k in o["redirect"] -%} - redirect prefix {{ o["redirect"][k] }} code 301 if { hdr(host) -i {{ k }} } - {% endfor -%} +frontend {{ mode }}_in_{{ o["port"] }}_{{ salt }} + {% include "bind.j2" %} + {% if mode == "http" %} + {% include "frontend-mode-http.j2" %} + {% else %} + {% include "frontend-mode-tcp.j2" %} + {% endif %} - {% for k in o["hosts"] %} - {% set host = k.replace(".", "_") + "_{0}_{1}".format(o["port"], salt) %} - acl is_rule_{{ host }}_1 hdr(host) -i {{ k }} - acl is_rule_{{ host }}_2 hdr(host) -i {{ k }}:{{ o["port"] }} - use_backend srv_{{ host }} if is_rule_{{ host }}_1 OR is_rule_{{ host }}_2 - {% endfor %} - - {% for k in o["hosts"] %} + {% for k in o["hosts"] -%} {% set host = k.replace(".", "_") + "_{0}_{1}".format(o["port"], salt) %} backend srv_{{ host }} balance roundrobin - mode http + mode {{ mode }} + {% if mode == "http" %} option forwardfor http-request set-header X-Forwarded-Port %[dst_port] http-request add-header X-Forwarded-Proto https if { ssl_fc } - server srv {{ o["hosts"][k] }} check weight 1 + {% elif mode == "tcp" %} + option tcp-check + tcp-check connect{{ " ssl" if o["health-check"] == "ssl" }} + {% endif %} + server srv {{ o["hosts"][k] }} check weight 1{{ " verify none" if o["health-check"] == "ssl" }} {% endfor %} {% endfor %} diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/context.py b/tests/context.py new file mode 100644 index 0000000..67dda65 --- /dev/null +++ b/tests/context.py @@ -0,0 +1,5 @@ +import os +import sys +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +import easymapping diff --git a/tests/expected/no-services.txt b/tests/expected/no-services.txt new file mode 100644 index 0000000..f5ecac3 --- /dev/null +++ b/tests/expected/no-services.txt @@ -0,0 +1,12 @@ +global + log stdout format raw local0 info + maxconn 2000 + tune.ssl.default-dh-param 2048 + +defaults + log global + + timeout connect 3s + timeout client 10s + timeout server 10m + diff --git a/tests/expected/services-tcp.txt b/tests/expected/services-tcp.txt new file mode 100644 index 0000000..66cb16d --- /dev/null +++ b/tests/expected/services-tcp.txt @@ -0,0 +1,26 @@ +global + log stdout format raw local0 info + maxconn 2000 + tune.ssl.default-dh-param 2048 + +defaults + log global + + timeout connect 3s + timeout client 10s + timeout server 10m + + +frontend tcp_in_31339_1 + bind *:31339 + mode tcp + option tcplog + log global + default_backend srv_agent_quantum_local_31339_1 + +backend srv_agent_quantum_local_31339_1 + balance roundrobin + mode tcp + option tcp-check + tcp-check connect ssl + server srv test_agent:9001 check weight 1 verify none diff --git a/tests/expected/services.txt b/tests/expected/services.txt new file mode 100644 index 0000000..3e38ed6 --- /dev/null +++ b/tests/expected/services.txt @@ -0,0 +1,95 @@ +global + log stdout format raw local0 info + maxconn 2000 + tune.ssl.default-dh-param 2048 + +defaults + log global + + timeout connect 3s + timeout client 10s + timeout server 10m + + +frontend tcp_in_31339_1 + bind *:31339 + mode tcp + option tcplog + log global + default_backend srv_agent_quantum_example_org_31339_1 + +backend srv_agent_quantum_example_org_31339_1 + balance roundrobin + mode tcp + option tcp-check + tcp-check connect + server srv my-stack_agent:9001 check weight 1 + +frontend http_in_31337_2 + bind *:31337 + mode http + + acl is_rule_cadvisor_quantum_example_org_31337_2_1 hdr(host) -i cadvisor.quantum.example.org + acl is_rule_cadvisor_quantum_example_org_31337_2_2 hdr(host) -i cadvisor.quantum.example.org:31337 + use_backend srv_cadvisor_quantum_example_org_31337_2 if is_rule_cadvisor_quantum_example_org_31337_2_1 OR is_rule_cadvisor_quantum_example_org_31337_2_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 + 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 + balance roundrobin + mode http + option forwardfor + http-request set-header X-Forwarded-Port %[dst_port] + http-request add-header X-Forwarded-Proto https if { ssl_fc } + server srv my-stack_cadvisor:8080 check weight 1 +backend srv_node-exporter_quantum_example_org_31337_2 + balance roundrobin + mode http + option forwardfor + http-request set-header X-Forwarded-Port %[dst_port] + http-request add-header X-Forwarded-Proto https if { ssl_fc } + server srv my-stack_node-exporter:9100 check weight 1 + +frontend http_in_80_3 + bind *:80 + mode http + redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i somehost.com.br } + redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i somehost.com } + redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i www.somehost.com } + redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i byjg.ca } + redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i www.byjg.ca } + + acl is_rule_www_somehost_com_br_80_3_1 hdr(host) -i www.somehost.com.br + acl is_rule_www_somehost_com_br_80_3_2 hdr(host) -i www.somehost.com.br:80 + use_backend srv_www_somehost_com_br_80_3 if is_rule_www_somehost_com_br_80_3_1 OR is_rule_www_somehost_com_br_80_3_2 + +backend srv_www_somehost_com_br_80_3 + balance roundrobin + mode http + option forwardfor + http-request set-header X-Forwarded-Port %[dst_port] + http-request add-header X-Forwarded-Proto https if { ssl_fc } + server srv some-service:80 check weight 1 + +frontend http_in_443_4 + bind *:443 ssl crt /tmp/www.somehost.com.br.1.pem + mode http + redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i somehost.com.br } + redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i somehost.com } + redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i www.somehost.com } + redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i byjg.ca } + redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i www.byjg.ca } + + acl is_rule_www_somehost_com_br_443_4_1 hdr(host) -i www.somehost.com.br + acl is_rule_www_somehost_com_br_443_4_2 hdr(host) -i www.somehost.com.br:443 + use_backend srv_www_somehost_com_br_443_4 if is_rule_www_somehost_com_br_443_4_1 OR is_rule_www_somehost_com_br_443_4_2 + +backend srv_www_somehost_com_br_443_4 + balance roundrobin + mode http + option forwardfor + http-request set-header X-Forwarded-Port %[dst_port] + http-request add-header X-Forwarded-Proto https if { ssl_fc } + server srv some-service:80 check weight 1 diff --git a/tests/expected/static.txt b/tests/expected/static.txt new file mode 100644 index 0000000..52dd0b6 --- /dev/null +++ b/tests/expected/static.txt @@ -0,0 +1,95 @@ +global + log stdout format raw local0 info + maxconn 2000 + tune.ssl.default-dh-param 2048 + +defaults + log global + + timeout connect 3s + timeout client 10s + timeout server 10m + errorfile 400 /etc/haproxy/errors-custom/400.http + errorfile 403 /etc/haproxy/errors-custom/403.http + errorfile 408 /etc/haproxy/errors-custom/408.http + errorfile 500 /etc/haproxy/errors-custom/500.http + errorfile 502 /etc/haproxy/errors-custom/502.http + errorfile 503 /etc/haproxy/errors-custom/503.http + errorfile 504 /etc/haproxy/errors-custom/504.http + +frontend stats + bind *:1936 + mode http + stats enable + stats hide-version + stats realm Haproxy\ Statistics + stats uri / + stats auth admin:test123 +# acl is_proxystats hdr(host) -i some.host.com +# default_backend srv_stats +# use_backend srv_stats if is_proxystats + default_backend srv_stats + +backend srv_stats + mode http + server Local 127.0.0.1:1936 + +frontend http_in_80_1 + bind *:80 + mode http + redirect prefix http://host1.com.br code 301 if { hdr(host) -i www.host1.com.br } + + 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 + 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 + acl is_rule_host2_com_br_80_1_2 hdr(host) -i host2.com.br:80 + use_backend srv_host2_com_br_80_1 if is_rule_host2_com_br_80_1_1 OR is_rule_host2_com_br_80_1_2 + +backend srv_host1_com_br_80_1 + balance roundrobin + mode http + option forwardfor + http-request set-header X-Forwarded-Port %[dst_port] + http-request add-header X-Forwarded-Proto https if { ssl_fc } + server srv container:5000 check weight 1 +backend srv_host2_com_br_80_1 + balance roundrobin + mode http + option forwardfor + http-request set-header X-Forwarded-Port %[dst_port] + http-request add-header X-Forwarded-Proto https if { ssl_fc } + server srv other:3000 check weight 1 + +frontend http_in_443_2 + bind *:443 ssl crt /etc/haproxy/certs/mycert.pem + mode http + + acl is_rule_host1_com_br_443_2_1 hdr(host) -i host1.com.br + acl is_rule_host1_com_br_443_2_2 hdr(host) -i host1.com.br:443 + use_backend srv_host1_com_br_443_2 if is_rule_host1_com_br_443_2_1 OR is_rule_host1_com_br_443_2_2 + +backend srv_host1_com_br_443_2 + balance roundrobin + mode http + option forwardfor + http-request set-header X-Forwarded-Port %[dst_port] + http-request add-header X-Forwarded-Proto https if { ssl_fc } + server srv container:80 check weight 1 + +frontend http_in_8080_3 + bind *:8080 + mode http + + acl is_rule_host3_com_br_8080_3_1 hdr(host) -i host3.com.br + acl is_rule_host3_com_br_8080_3_2 hdr(host) -i host3.com.br:8080 + use_backend srv_host3_com_br_8080_3 if is_rule_host3_com_br_8080_3_1 OR is_rule_host3_com_br_8080_3_2 + +backend srv_host3_com_br_8080_3 + balance roundrobin + mode http + option forwardfor + http-request set-header X-Forwarded-Port %[dst_port] + http-request add-header X-Forwarded-Proto https if { ssl_fc } + server srv domain:8181 check weight 1 diff --git a/tests/fixtures/no-services b/tests/fixtures/no-services new file mode 100644 index 0000000..a0f782d --- /dev/null +++ b/tests/fixtures/no-services @@ -0,0 +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"} diff --git a/tests/fixtures/services b/tests/fixtures/services new file mode 100644 index 0000000..c213a8d --- /dev/null +++ b/tests/fixtures/services @@ -0,0 +1,6 @@ +portainer-agent_agent={"com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"portainer-agent"} +my-stack_agent={"com.byjg.easyhaproxy.definitions":"agent","com.byjg.easyhaproxy.host.agent":"agent.quantum.example.org","com.byjg.easyhaproxy.localport.agent":"9001","com.byjg.easyhaproxy.mode.agent":"tcp","com.byjg.easyhaproxy.port.agent":"31339","com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"} +my-stack_cadvisor={"com.byjg.easyhaproxy.definitions":"cadvisor","com.byjg.easyhaproxy.host.cadvisor":"cadvisor.quantum.example.org","com.byjg.easyhaproxy.localport.cadvisor":"8080","com.byjg.easyhaproxy.port.cadvisor":"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={"com.byjg.easyhaproxy.definitions":"exp","com.byjg.easyhaproxy.host.exp":"node-exporter.quantum.example.org","com.byjg.easyhaproxy.localport.exp":"9100","com.byjg.easyhaproxy.port.exp":"31337","com.docker.stack.image":"stefanprodan/swarmprom-node-exporter:v0.16.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"} +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={"com.byjg.easyhaproxy.definitions":"http,https","com.byjg.easyhaproxy.port.http":"80","com.byjg.easyhaproxy.host.http":"www.somehost.com.br","com.byjg.easyhaproxy.localport.http":"80","com.byjg.easyhaproxy.redirect.http":"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","com.byjg.easyhaproxy.port.https":"443","com.byjg.easyhaproxy.host.https":"www.somehost.com.br","com.byjg.easyhaproxy.localport.https":"80","com.byjg.easyhaproxy.redirect.https":"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","com.byjg.easyhaproxy.sslcert.https":"U29tZSBQRU0gQ2VydGlmaWNhdGU="} \ No newline at end of file diff --git a/tests/fixtures/services-tcp b/tests/fixtures/services-tcp new file mode 100644 index 0000000..64bb7a6 --- /dev/null +++ b/tests/fixtures/services-tcp @@ -0,0 +1,2 @@ +test_agent={"com.byjg.easyhaproxy.definitions":"agent","com.byjg.easyhaproxy.host.agent":"agent.quantum.local","com.byjg.easyhaproxy.localport.agent":"9001","com.byjg.easyhaproxy.mode.agent":"tcp","com.byjg.easyhaproxy.port.agent":"31339","com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"test", "com.byjg.easyhaproxy.health-check.agent":"ssl"} +test_proxy={"com.docker.stack.image":"byjg/easy-haproxy:local","com.docker.stack.namespace":"test"} diff --git a/tests/fixtures/static.yml b/tests/fixtures/static.yml new file mode 100644 index 0000000..82ea2e2 --- /dev/null +++ b/tests/fixtures/static.yml @@ -0,0 +1,23 @@ +stats: + username: admin + password: test123 + port: 1936 # Optional (default 1936) + +customerrors: true # Optional (default false) + +easymapping: + - port: 80 + hosts: + host1.com.br: container:5000 + host2.com.br: 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 + + - port: 8080 + hosts: + host3.com.br: domain:8181 diff --git a/tests/test_labels.py b/tests/test_labels.py new file mode 100644 index 0000000..f56c212 --- /dev/null +++ b/tests/test_labels.py @@ -0,0 +1,32 @@ +from .context import easymapping +import json +import pytest + +def test_label_generation(): + label = easymapping.DockerLabelHandler("foo") + + assert label.create("bar") == "foo.bar" + assert label.create(["bar", "foobar"]) == "foo.bar.foobar" + + +def test_label_data(): + label = easymapping.DockerLabelHandler("base") + label.set_data(json.loads('{"base.definitions":"h2"}')) + + label_name = label.create("definitions") + assert label_name == "base.definitions" + assert label.has_label(label_name) + assert label.get(label_name) == "h2" + + +def test_label_complex_key(): + label = easymapping.DockerLabelHandler("till") + + data = dict() + data["till.definitions"] = "h2" + data["till.host.h2"] = "fqdn.example.org" + data["till.mode.h2"] = "tcp" + label.set_data(json.loads(json.dumps(data))) + + assert label.get(label.create(["host", "h2"])) == "fqdn.example.org" + assert label.get(label.create(["mode", "h2"])) == "tcp" diff --git a/tests/test_parser.py b/tests/test_parser.py new file mode 100644 index 0000000..4f076be --- /dev/null +++ b/tests/test_parser.py @@ -0,0 +1,81 @@ +from .context import easymapping +import pytest +import os +import yaml + + +def load_fixture(file): + path = os.path.dirname(os.path.realpath(__file__)) + with open(path + "/fixtures/" + file, 'r') as content_file: + lineList = content_file.readlines() + + return lineList + + +def test_parser_doesnt_crash(): + lineList = load_fixture("no-services") + + result = { + "customerrors": False + } + + cfg = easymapping.HaproxyConfigGenerator(result, "/tmp") + haproxy_config = cfg.generate(lineList) + + assert len(haproxy_config) > 0 + 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 + + +def test_parser_finds_services(): + lineList = load_fixture("services") + + result = { + "customerrors": False + } + + cert_file = "/tmp/www.somehost.com.br.1.pem" + if os.path.exists(cert_file): + os.remove(cert_file) + + cfg = easymapping.HaproxyConfigGenerator(result, "/tmp") + haproxy_config = cfg.generate(lineList) + + assert len(haproxy_config) > 0 + path = os.path.dirname(os.path.realpath(__file__)) + 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" + + +def test_parser_static(): + 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) + + cfg = easymapping.HaproxyConfigGenerator(parsed, "/tmp") + haproxy_config = cfg.generate() + assert len(haproxy_config) > 0 + + with open(path + "/expected/static.txt", 'r') as expected_file: + assert expected_file.read() == haproxy_config + + +def test_parser_tcp(): + lineList = load_fixture("services-tcp") + + result = { + "customerrors": False + } + + cfg = easymapping.HaproxyConfigGenerator(result, "/tmp") + haproxy_config = cfg.generate(lineList) + # print(haproxy_config) + + assert len(haproxy_config) > 0 + 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