commit
7057965da4
36 changed files with 905 additions and 149 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -3,3 +3,4 @@
|
||||||
venv
|
venv
|
||||||
.docker_data
|
.docker_data
|
||||||
__pycache__
|
__pycache__
|
||||||
|
.pytest_cache
|
||||||
26
.travis.yml
Normal file
26
.travis.yml
Normal file
|
|
@ -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"
|
||||||
11
Dockerfile
11
Dockerfile
|
|
@ -1,18 +1,21 @@
|
||||||
FROM haproxy:2.1-alpine
|
FROM alpine:3.14
|
||||||
|
|
||||||
WORKDIR /scripts
|
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
|
COPY requirements.txt /scripts
|
||||||
|
|
||||||
RUN pip3 install --upgrade pip \
|
RUN pip3 install --upgrade pip \
|
||||||
&& pip install -r requirements.txt
|
&& pip install -r requirements.txt
|
||||||
|
|
||||||
COPY swarm.* /scripts/
|
|
||||||
COPY static.* /scripts/
|
|
||||||
COPY templates /scripts/templates/
|
COPY templates /scripts/templates/
|
||||||
COPY easymapping /scripts/easymapping/
|
COPY easymapping /scripts/easymapping/
|
||||||
|
COPY tests/ /scripts/tests/
|
||||||
|
|
||||||
COPY assets /
|
COPY assets /
|
||||||
|
|
||||||
|
RUN pytest -s tests/
|
||||||
|
|
||||||
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisord.conf" ]
|
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisord.conf" ]
|
||||||
|
|
|
||||||
7
Makefile
Normal file
7
Makefile
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
docker build -t byjg/easy-haproxy -t byjg/easy-haproxy:local .
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
pytest tests/
|
||||||
93
README.md
93
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
|
This Docker image will create dynamically the `haproxy.cfg` based on the labels defined in docker containers or from
|
||||||
a simple Yaml instead docker
|
a simple Yaml instead docker
|
||||||
|
|
||||||
# Features
|
## Features
|
||||||
|
|
||||||
- Enable or disable Stats on port 1936 with custom password
|
- 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
|
- 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:
|
The basic command line to run is:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d \
|
docker run -d \
|
||||||
--name easy-haproxy-container \
|
--name easy-haproxy-container \
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
-e DISCOVER="swarm|docker|static" \
|
-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 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 |
|
| Environment Variable | Description |
|
||||||
|----------------------|-------------------------------------------------------------------------------|
|
|----------------------|-------------------------------------------------------------------------------|
|
||||||
| DISCOVER | How `haproxy.cfg` will be created: `static`, `docker` or `swarm` |
|
| 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
|
- swarm
|
||||||
- static
|
- 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.
|
The discover will occur every minute.
|
||||||
|
|
||||||
|
|
@ -65,27 +64,27 @@ docker run --network easyhaproxy byjg/easyhaproxy
|
||||||
docker run --network easyhaproxy myimage
|
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
|
This method requires a functional Docker Swarm Cluster. The system will search for the labels in all containers on all
|
||||||
swarm nodes.
|
swarm nodes.
|
||||||
|
|
||||||
The discover will occur every minute.
|
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
|
### Tags to be attached in the Docker Container (Swarm or Docker)
|
||||||
|
|
||||||
{:.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. |
|
|
||||||
|
|
||||||
|
| 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:
|
Note: if you are deploying a stack set labels at the `deploy` level:
|
||||||
|
|
||||||
|
|
@ -126,6 +125,22 @@ docker run \
|
||||||
some/myimage
|
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:
|
### Redirect Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -133,7 +148,7 @@ docker run \
|
||||||
-l com.byjg.easyhaproxy.redirect.<defintion>=www.byjg.com.br--http://byjg.com.br,byjg.com--http://byjg.com.br
|
-l com.byjg.easyhaproxy.redirect.<defintion>=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`
|
This method expects a YAML file to setup the `haproxy.cfg`
|
||||||
|
|
||||||
|
|
@ -149,14 +164,14 @@ customerrors: true # Optional (default false)
|
||||||
|
|
||||||
easymapping:
|
easymapping:
|
||||||
- port: 80
|
- port: 80
|
||||||
hosts:
|
hosts:
|
||||||
host1.com.br: container:5000
|
host1.com.br: container:5000
|
||||||
host2.com.br: other:3000
|
host2.com.br: other:3000
|
||||||
redirect:
|
redirect:
|
||||||
www.host1.com.br: http://host1.com.br
|
www.host1.com.br: http://host1.com.br
|
||||||
|
|
||||||
- port: 443
|
- port: 443
|
||||||
ssl_cert: BASE64_PEM_CERTIFICATE
|
ssl_cert: /path/to/ssl/certificate
|
||||||
hosts:
|
hosts:
|
||||||
host1.com.br: container:80
|
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
|
# 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
|
```bash
|
||||||
docker run \
|
docker run \
|
||||||
/* other parameters */
|
/* other parameters */
|
||||||
-v /your/local/conf.d:/etc/haproxy/conf.d \
|
-v /your/local/conf.d:/etc/haproxy/conf.d \
|
||||||
-d byjg/easy-haproxy
|
-d byjg/easy-haproxy
|
||||||
|
|
@ -185,9 +200,9 @@ docker run \
|
||||||
|
|
||||||
# Handling SSL
|
# 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
|
```bash
|
||||||
cat example.com.crt example.com.key > single.pem
|
cat example.com.crt example.com.key > single.pem
|
||||||
|
|
@ -215,8 +230,8 @@ cat single.pem | base64 -w0
|
||||||
|
|
||||||
# Setting Custom Errors
|
# Setting Custom Errors
|
||||||
|
|
||||||
If enabled, map the volume : `/etc/haproxy/errors-custom/` to your container and put a file named `ERROR_NUMBER.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)
|
where ERROR_NUMBER is the http error code (e.g. 503.http)
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import signal
|
||||||
|
|
||||||
from supervisor import childutils
|
from supervisor import childutils
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
while True:
|
while True:
|
||||||
headers, payload = childutils.listener.wait()
|
headers, payload = childutils.listener.wait()
|
||||||
|
|
@ -12,7 +13,11 @@ def main():
|
||||||
events = ['PROCESS_STATE_FATAL', 'PROCESS_STATE_EXITED', 'PROCESS_STATE_STOPPED']
|
events = ['PROCESS_STATE_FATAL', 'PROCESS_STATE_EXITED', 'PROCESS_STATE_STOPPED']
|
||||||
if not (headers['eventname'] in events):
|
if not (headers['eventname'] in events):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
print(headers)
|
||||||
|
print(payload)
|
||||||
os.kill(os.getppid(), signal.SIGTERM)
|
os.kill(os.getppid(), signal.SIGTERM)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,18 @@ else
|
||||||
if [[ "$DISCOVER" == "docker" ]]; then
|
if [[ "$DISCOVER" == "docker" ]]; then
|
||||||
CONTAINERS=$(docker ps -q)
|
CONTAINERS=$(docker ps -q)
|
||||||
LABEL_PATH=".Config.Labels"
|
LABEL_PATH=".Config.Labels"
|
||||||
|
|
||||||
|
for container in ${CONTAINERS}; do
|
||||||
|
docker inspect --format "{{ json $LABEL_PATH }}" ${container} | xargs -I % echo ${container}=% >> ${CONTROL_FILE}
|
||||||
|
done
|
||||||
else
|
else
|
||||||
CONTAINERS=$(docker node ps $(docker node ls -q) --format "{{ .Name }}" --filter desired-state=running | cut -d. -f1 | sort | uniq)
|
CONTAINERS=$(docker node ps $(docker node ls -q) --format "{{ .Name }}" --filter desired-state=running | cut -d. -f1 | sort | uniq)
|
||||||
LABEL_PATH=".Spec.Labels"
|
LABEL_PATH=".Spec.Labels"
|
||||||
fi
|
|
||||||
|
|
||||||
for container in ${CONTAINERS}; do
|
for container in ${CONTAINERS}; do
|
||||||
docker inspect --format "{{ json $LABEL_PATH }}" ${container} | xargs -I % echo ${container}=% >> ${CONTROL_FILE}
|
docker service inspect --format "{{ json $LABEL_PATH }}" ${container} | xargs -I % echo ${container}=% >> ${CONTROL_FILE}
|
||||||
done
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
if cmp -s ${CONTROL_FILE} ${CONTROL_FILE}.old ; then
|
if cmp -s ${CONTROL_FILE} ${CONTROL_FILE}.old ; then
|
||||||
RELOAD="false"
|
RELOAD="false"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
source /scripts/haproxy-reload.sh initial
|
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
|
while true; do
|
||||||
sleep 60
|
sleep 60
|
||||||
|
|
|
||||||
24
assets/scripts/swarm.py
Normal file
24
assets/scripts/swarm.py
Normal file
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
33
build-multiarch.sh
Executable file
33
build-multiarch.sh
Executable file
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -1,12 +1,146 @@
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
from jinja2 import Environment, FileSystemLoader
|
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:
|
class HaproxyConfigGenerator:
|
||||||
def __init__(self, mapping):
|
def __init__(self, mapping, ssl_cert_folder="/etc/haproxy/certs"):
|
||||||
self.mapping = mapping
|
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')
|
file_loader = FileSystemLoader('templates')
|
||||||
env = Environment(loader=file_loader)
|
env = Environment(loader=file_loader)
|
||||||
|
env.trim_blocks = True
|
||||||
|
env.lstrip_blocks = True
|
||||||
|
env.rstrip_blocks = True
|
||||||
template = env.get_template('haproxy.cfg.j2')
|
template = env.get_template('haproxy.cfg.j2')
|
||||||
return template.render(data=self.mapping)
|
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()
|
||||||
|
|
|
||||||
32
examples/docker/docker-compose.yml
Normal file
32
examples/docker/docker-compose.yml
Normal file
File diff suppressed because one or more lines are too long
17
examples/static/config.yml
Normal file
17
examples/static/config.yml
Normal file
|
|
@ -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
|
||||||
18
examples/static/docker-compose.yml
Normal file
18
examples/static/docker-compose.yml
Normal file
|
|
@ -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
|
||||||
82
examples/static/host1.local.pem
Normal file
82
examples/static/host1.local.pem
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
aA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
YkU14WzQKleFkiBJI9lVvnfgGnOlgg0=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
2
pytest.ini
Normal file
2
pytest.ini
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
[pytest]
|
||||||
|
addopts = -v -p no:warnings
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
pyyaml
|
pyyaml
|
||||||
docker
|
docker
|
||||||
jinja2
|
jinja2
|
||||||
|
pytest
|
||||||
20
setup.py
Normal file
20
setup.py
Normal file
|
|
@ -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'))
|
||||||
|
)
|
||||||
74
swarm.py
74
swarm.py
|
|
@ -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)
|
|
||||||
|
|
||||||
|
|
||||||
8
templates/bind.j2
Normal file
8
templates/bind.j2
Normal file
|
|
@ -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 %}
|
||||||
11
templates/frontend-mode-http.j2
Normal file
11
templates/frontend-mode-http.j2
Normal file
|
|
@ -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 %}
|
||||||
6
templates/frontend-mode-tcp.j2
Normal file
6
templates/frontend-mode-tcp.j2
Normal file
|
|
@ -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) }}
|
||||||
|
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
|
global
|
||||||
|
log stdout format raw local0 info
|
||||||
|
maxconn 2000
|
||||||
|
tune.ssl.default-dh-param 2048
|
||||||
|
|
||||||
defaults
|
defaults
|
||||||
log global
|
log global
|
||||||
|
|
||||||
|
|
@ -14,11 +19,6 @@ defaults
|
||||||
errorfile 504 /etc/haproxy/errors-custom/504.http
|
errorfile 504 /etc/haproxy/errors-custom/504.http
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
global
|
|
||||||
log /dev/log local0
|
|
||||||
maxconn 2000
|
|
||||||
tune.ssl.default-dh-param 2048
|
|
||||||
|
|
||||||
{% if "stats" in data %}
|
{% if "stats" in data %}
|
||||||
frontend stats
|
frontend stats
|
||||||
bind *:{{ data["stats"]["port"] | default(1936) }}
|
bind *:{{ data["stats"]["port"] | default(1936) }}
|
||||||
|
|
@ -37,32 +37,31 @@ backend srv_stats
|
||||||
mode http
|
mode http
|
||||||
server Local 127.0.0.1:{{ data["stats"]["port"] | default(1936) }}
|
server Local 127.0.0.1:{{ data["stats"]["port"] | default(1936) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% for o in data["easymapping"] -%}
|
||||||
{% for o in data["easymapping"] %}
|
{% set mode = o["mode"] or "http" %}
|
||||||
{% set salt = loop.index %}
|
{% 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"] -%}
|
frontend {{ mode }}_in_{{ o["port"] }}_{{ salt }}
|
||||||
redirect prefix {{ o["redirect"][k] }} code 301 if { hdr(host) -i {{ k }} }
|
{% include "bind.j2" %}
|
||||||
{% endfor -%}
|
{% if mode == "http" %}
|
||||||
|
{% include "frontend-mode-http.j2" %}
|
||||||
|
{% else %}
|
||||||
|
{% include "frontend-mode-tcp.j2" %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% for k in o["hosts"] %}
|
{% 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"] %}
|
|
||||||
{% set host = k.replace(".", "_") + "_{0}_{1}".format(o["port"], salt) %}
|
{% set host = k.replace(".", "_") + "_{0}_{1}".format(o["port"], salt) %}
|
||||||
backend srv_{{ host }}
|
backend srv_{{ host }}
|
||||||
balance roundrobin
|
balance roundrobin
|
||||||
mode http
|
mode {{ mode }}
|
||||||
|
{% if mode == "http" %}
|
||||||
option forwardfor
|
option forwardfor
|
||||||
http-request set-header X-Forwarded-Port %[dst_port]
|
http-request set-header X-Forwarded-Port %[dst_port]
|
||||||
http-request add-header X-Forwarded-Proto https if { ssl_fc }
|
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 %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
||||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
5
tests/context.py
Normal file
5
tests/context.py
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||||
|
|
||||||
|
import easymapping
|
||||||
12
tests/expected/no-services.txt
Normal file
12
tests/expected/no-services.txt
Normal file
|
|
@ -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
|
||||||
|
|
||||||
26
tests/expected/services-tcp.txt
Normal file
26
tests/expected/services-tcp.txt
Normal file
|
|
@ -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
|
||||||
95
tests/expected/services.txt
Normal file
95
tests/expected/services.txt
Normal file
|
|
@ -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
|
||||||
95
tests/expected/static.txt
Normal file
95
tests/expected/static.txt
Normal file
|
|
@ -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
|
||||||
5
tests/fixtures/no-services
vendored
Normal file
5
tests/fixtures/no-services
vendored
Normal file
|
|
@ -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"}
|
||||||
6
tests/fixtures/services
vendored
Normal file
6
tests/fixtures/services
vendored
Normal file
|
|
@ -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="}
|
||||||
2
tests/fixtures/services-tcp
vendored
Normal file
2
tests/fixtures/services-tcp
vendored
Normal file
|
|
@ -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"}
|
||||||
23
tests/fixtures/static.yml
vendored
Normal file
23
tests/fixtures/static.yml
vendored
Normal file
|
|
@ -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
|
||||||
32
tests/test_labels.py
Normal file
32
tests/test_labels.py
Normal file
|
|
@ -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"
|
||||||
81
tests/test_parser.py
Normal file
81
tests/test_parser.py
Normal file
|
|
@ -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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue