Merge pull request #27 from byjg/kubernetes-refactory
Kubernetes refactory
This commit is contained in:
commit
edee20c5bd
63 changed files with 725 additions and 498 deletions
|
|
@ -6,4 +6,4 @@ tasks:
|
||||||
- command: |
|
- command: |
|
||||||
virtualenv -p /usr/bin/python3 venv
|
virtualenv -p /usr/bin/python3 venv
|
||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
pip install -r requirements.txt
|
pip install -r src/requirements.txt
|
||||||
|
|
|
||||||
26
.travis.yml
26
.travis.yml
|
|
@ -1,26 +0,0 @@
|
||||||
language: python
|
|
||||||
|
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
include:
|
|
||||||
- stage: test
|
|
||||||
if: (type IN (pull_request))
|
|
||||||
install:
|
|
||||||
- pip install -r requirements.txt
|
|
||||||
script:
|
|
||||||
- pytest -s tests/
|
|
||||||
|
|
||||||
- stage: build docker
|
|
||||||
if: (branch = master) AND (NOT (type IN (pull_request)))
|
|
||||||
install:
|
|
||||||
- docker pull byjg/k8s-ci
|
|
||||||
script:
|
|
||||||
- docker run --privileged -v /tmp/z:/var/lib/containers -it --rm -v $PWD:/work -w /work -e DOCKER_USERNAME=$DOCKER_USERNAME -e DOCKER_PASSWORD=$DOCKER_PASSWORD -e DOCKER_REGISTRY=$DOCKER_REGISTRY byjg/k8s-ci /work/build-multiarch.sh
|
|
||||||
|
|
||||||
- stage: documentation
|
|
||||||
if: (branch = master) AND (NOT (type IN (pull_request)))
|
|
||||||
install: skip
|
|
||||||
script: "curl https://opensource.byjg.com/add-doc.sh | bash /dev/stdin devops docker-easy-haproxy"
|
|
||||||
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
|
|
@ -10,9 +10,9 @@
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${file}",
|
"program": "${file}",
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}/src",
|
||||||
"env": {
|
"env": {
|
||||||
"PYTHONPATH": "${cwd}"
|
"PYTHONPATH": "${cwd}/src"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
11
Dockerfile
11
Dockerfile
|
|
@ -6,18 +6,15 @@ ENV RELEASE_VERSION=$RELEASE_VERSION_ARG
|
||||||
|
|
||||||
WORKDIR /scripts
|
WORKDIR /scripts
|
||||||
|
|
||||||
COPY requirements.txt /scripts
|
COPY src/ /scripts/
|
||||||
COPY templates /scripts/templates/
|
|
||||||
COPY easymapping /scripts/easymapping/
|
|
||||||
COPY tests/ /scripts/tests/
|
|
||||||
COPY assets /
|
COPY assets /
|
||||||
|
|
||||||
RUN apk add --no-cache haproxy bash python3 py3-pip py-yaml docker certbot openssl \
|
RUN apk add --no-cache haproxy bash python3 py3-pip py-yaml certbot openssl \
|
||||||
&& ln -s /usr/bin/python3 /usr/bin/python \
|
&& ln -s /usr/bin/python3 /usr/bin/python \
|
||||||
&& pip3 install --upgrade pip \
|
&& pip3 install --upgrade pip \
|
||||||
&& pip install -r requirements.txt \
|
&& pip install -r requirements.txt \
|
||||||
&& pytest -s tests/ \
|
&& pytest -s -vv tests/ \
|
||||||
&& openssl dhparam -out /etc/haproxy/dhparam 2048 \
|
&& openssl dhparam -out /etc/haproxy/dhparam 2048 \
|
||||||
&& openssl dhparam -out /etc/haproxy/dhparam-1024 1024
|
&& openssl dhparam -out /etc/haproxy/dhparam-1024 1024
|
||||||
|
|
||||||
CMD ["/bin/bash", "-c", "/scripts/haproxy.sh" ]
|
CMD ["/usr/bin/python", "-u", "/scripts/main.py" ]
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,10 @@ services:
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker stack deploy --compose-file docker-compose.yml mystack
|
||||||
|
```
|
||||||
|
|
||||||
### Single Definition
|
### Single Definition
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
66
_config.yml
66
_config.yml
|
|
@ -1,66 +0,0 @@
|
||||||
name: docker-easy-haproxy
|
|
||||||
|
|
||||||
project:
|
|
||||||
version: 1.0.0
|
|
||||||
download_url: https://github.com/byjg/docker-easy-haproxy/releases
|
|
||||||
|
|
||||||
license:
|
|
||||||
software: MIT
|
|
||||||
software_url: https://opensource.org/licenses/MIT
|
|
||||||
|
|
||||||
docs: MIT
|
|
||||||
docs_url: https://opensource.org/licenses/MIT
|
|
||||||
|
|
||||||
git_edit_address: https://github.com/byjg/docker-easy-haproxy/blob/master/
|
|
||||||
|
|
||||||
links:
|
|
||||||
header:
|
|
||||||
- title: GitHub
|
|
||||||
url: https://github.com/byjg/docker-easy-haproxy
|
|
||||||
- title: ByJG
|
|
||||||
url: https://opensource.byjg.com/
|
|
||||||
footer:
|
|
||||||
- title: GitHub
|
|
||||||
url: https://github.com/byjg/docker-easy-haproxy
|
|
||||||
- title: Issues
|
|
||||||
url: https://github.com/byjg/docker-easy-haproxy/issues
|
|
||||||
|
|
||||||
ui:
|
|
||||||
header:
|
|
||||||
color1: "#080331"
|
|
||||||
color2: "#0033cc"
|
|
||||||
trianglify: true
|
|
||||||
|
|
||||||
social:
|
|
||||||
github:
|
|
||||||
user: byjg
|
|
||||||
repo: docker-easy-haproxy
|
|
||||||
twitter:
|
|
||||||
enabled: false
|
|
||||||
via:
|
|
||||||
hash: opensourcebyjg
|
|
||||||
account:
|
|
||||||
facebook:
|
|
||||||
enabled: true
|
|
||||||
url: https://opensource.byjg.com/
|
|
||||||
profileUrl:
|
|
||||||
|
|
||||||
author:
|
|
||||||
twitter: byjg
|
|
||||||
|
|
||||||
twitter:
|
|
||||||
card: summary
|
|
||||||
username: byjg
|
|
||||||
|
|
||||||
logo: https://opensource.byjg.com/images/logo_byjg.png
|
|
||||||
|
|
||||||
analytics:
|
|
||||||
google: UA-130014324-1
|
|
||||||
|
|
||||||
plugins:
|
|
||||||
- jekyll-seo-tag
|
|
||||||
|
|
||||||
# Build settings
|
|
||||||
markdown: kramdown
|
|
||||||
remote_theme: byjg/jekyll-docs-theme
|
|
||||||
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
_
|
|
||||||
___ __ _ ____ _ ___| |_ __ _ _ __ _ _ _____ ___ _
|
|
||||||
/ -_) _` (_-< || |___| ' \/ _` | '_ \ '_/ _ \ \ / || |
|
|
||||||
\___\__,_/__/\_, | |_||_\__,_| .__/_| \___/_\_\\_, |
|
|
||||||
|__/ |_| |__/
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
source /scripts/functions.sh
|
|
||||||
|
|
||||||
if [ ! -f /scripts/letsencrypt_hosts.txt ]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Semaphore
|
|
||||||
if [ -f /tmp/certbot-lock ]; then
|
|
||||||
log "notice" "CERTBOT_JOB" "Another process is running"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
touch /tmp/certbot-lock
|
|
||||||
|
|
||||||
mkdir -p /var/log/letsencrypt
|
|
||||||
ln -sf /dev/stdout /var/log/letsencrypt/letsencrypt.log
|
|
||||||
|
|
||||||
REQUEST_CERTS=""
|
|
||||||
RENEW_CERTS=""
|
|
||||||
|
|
||||||
for domain in $(cat /scripts/letsencrypt_hosts.txt); do
|
|
||||||
if [ ! -f "/certs/letsencrypt/$domain.pem" ]; then
|
|
||||||
REQUEST_CERTS="$REQUES_CERTS -d $domain"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $(find "/certs/letsencrypt/$domain.pem" -mtime +30 -print) ]]; then
|
|
||||||
RENEW_CERTS="$RENEW_CERTS -d $domain"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -n "$REQUEST_CERTS" ]; then
|
|
||||||
log "info" "CERTBOT_JOB" "Requesting certificates for $REQUEST_CERTS"
|
|
||||||
certbot certonly \
|
|
||||||
--standalone \
|
|
||||||
--preferred-challenges http \
|
|
||||||
--http-01-port 2080 \
|
|
||||||
--agree-tos \
|
|
||||||
--issuance-timeout 90 \
|
|
||||||
--no-eff-email \
|
|
||||||
--non-interactive \
|
|
||||||
--max-log-backups=0 \
|
|
||||||
--post-hook "/scripts/certbot_to_haproxy.sh" \
|
|
||||||
$REQUEST_CERTS --email $EASYHAPROXY_LETSENCRYPT_EMAIL
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$RENEW_CERTS" ]; then
|
|
||||||
log "info" "CERTBOT_JOB" "Resquesting renew certificated fort $RENEW_CERTS"
|
|
||||||
certbot renew --post-hook "/scripts/certbot_to_haproxy.sh"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Release semaphore
|
|
||||||
rm /tmp/certbot-lock
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
source /scripts/functions.sh
|
|
||||||
|
|
||||||
# Loop through all Let's Encrypt certificates
|
|
||||||
for CERTIFICATE in `find /etc/letsencrypt/live/* -type d`; do
|
|
||||||
CERTIFICATE=`basename $CERTIFICATE`
|
|
||||||
|
|
||||||
# Combine certificate and private key to single file
|
|
||||||
cat /etc/letsencrypt/live/$CERTIFICATE/fullchain.pem /etc/letsencrypt/live/$CERTIFICATE/privkey.pem > /certs/letsencrypt/$CERTIFICATE.pem
|
|
||||||
done
|
|
||||||
|
|
||||||
# It will be checked on haproxy-reload.sh
|
|
||||||
touch /tmp/force-reload
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
function log() {
|
|
||||||
# ARGS:
|
|
||||||
# - loglevel
|
|
||||||
# - app
|
|
||||||
# - message
|
|
||||||
echo [$2] $(date +"$EASYHAPROXY_DATEFORMAT") [$1]: $3
|
|
||||||
}
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
source /scripts/functions.sh
|
|
||||||
|
|
||||||
cd /scripts
|
|
||||||
|
|
||||||
RELOAD="true"
|
|
||||||
|
|
||||||
if [[ "$EASYHAPROXY_DISCOVER" == "static" ]]; then
|
|
||||||
CONTROL_FILE="/etc/haproxy/haproxy.cfg"
|
|
||||||
touch ${CONTROL_FILE}
|
|
||||||
cp ${CONTROL_FILE} ${CONTROL_FILE}.old
|
|
||||||
python3 static.py /etc/haproxy/easyconfig.yml > ${CONTROL_FILE}
|
|
||||||
else
|
|
||||||
CONTROL_FILE="/tmp/.docker_data"
|
|
||||||
touch ${CONTROL_FILE}
|
|
||||||
mv ${CONTROL_FILE} ${CONTROL_FILE}.old
|
|
||||||
touch ${CONTROL_FILE}
|
|
||||||
|
|
||||||
case "$EASYHAPROXY_DISCOVER" in
|
|
||||||
docker)
|
|
||||||
CONTAINERS=$(docker ps -q | sort | uniq)
|
|
||||||
LABEL_PATH=".Config.Labels"
|
|
||||||
|
|
||||||
for container in ${CONTAINERS}; do
|
|
||||||
docker inspect --format "{{ json $LABEL_PATH }}" ${container} | xargs -I % echo ${container}=% >> ${CONTROL_FILE}
|
|
||||||
done
|
|
||||||
;;
|
|
||||||
|
|
||||||
swarm)
|
|
||||||
CONTAINERS=$(docker node ps $(docker node ls -q) --format "{{ .Name }}" --filter desired-state=running | cut -d. -f1 | sort | uniq)
|
|
||||||
LABEL_PATH=".Spec.Labels"
|
|
||||||
|
|
||||||
for container in ${CONTAINERS}; do
|
|
||||||
docker service inspect --format "{{ json $LABEL_PATH }}" ${container} | xargs -I % echo ${container}=% >> ${CONTROL_FILE}
|
|
||||||
done
|
|
||||||
;;
|
|
||||||
|
|
||||||
kubernetes)
|
|
||||||
python3 /scripts/k8s.py > ${CONTROL_FILE}
|
|
||||||
;;
|
|
||||||
|
|
||||||
*)
|
|
||||||
log "error" "CONF_CHECK" "EASYHAPROXY_DISCOVER should be 'static', 'docker', 'swarm' or kubernetes. I got '$EASYHAPROXY_DISCOVER' instead."
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if cmp -s ${CONTROL_FILE} ${CONTROL_FILE}.old ; then
|
|
||||||
RELOAD="false"
|
|
||||||
else
|
|
||||||
python3 swarm.py > /etc/haproxy/haproxy.cfg
|
|
||||||
log "info" "CONF_CHECK" "New configuration found"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if cmp -s ${CONTROL_FILE} ${CONTROL_FILE}.old ; then
|
|
||||||
RELOAD="false"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! -z "$1" ]]; then
|
|
||||||
log "info" "CONF_CHECK" "Initial configuration. Skip certbot."
|
|
||||||
else
|
|
||||||
/scripts/certbot.sh
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If Certbot reloads successfully will create the file /tmp/force-reload
|
|
||||||
if [ -f /tmp/force-reload ]; then
|
|
||||||
log "info" "CONF_CHECK" "New certificates found..."
|
|
||||||
RELOAD="true"
|
|
||||||
rm /tmp/force-reload
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! -z "$1" ]]; then
|
|
||||||
log "info" "CONF_CHECK" "Starting haproxy..."
|
|
||||||
/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg $(ls /etc/haproxy/conf.d/*.cfg 2>/dev/null | xargs -I{} echo -f {}) -p /run/haproxy.pid -S /var/run/haproxy.sock &
|
|
||||||
|
|
||||||
elif [[ "$RELOAD" == "true" ]]; then
|
|
||||||
log "info" "CONF_CHECK" "Reloading..."
|
|
||||||
/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg $(ls /etc/haproxy/conf.d/*.cfg 2>/dev/null | xargs -I{} echo -f {}) -p /run/haproxy.pid -x /var/run/haproxy.sock -sf $(cat /run/haproxy.pid) &
|
|
||||||
fi
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
source /scripts/functions.sh
|
|
||||||
|
|
||||||
/usr/sbin/haproxy -v
|
|
||||||
|
|
||||||
if [ -z "$EASYHAPROXY_DATEFORMAT" ]; then
|
|
||||||
export EASYHAPROXY_DATEFORMAT="%Y-%m-%d %H:%M:%S %Z"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$EASYHAPROXY_REFRESH_CONF" ]; then
|
|
||||||
export EASYHAPROXY_REFRESH_CONF=10
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
cat banner.txt
|
|
||||||
echo Release: $RELEASE_VERSION
|
|
||||||
echo
|
|
||||||
echo "Environment"
|
|
||||||
env | sort | grep 'HAPROXY' | xargs -I{} echo " - {} "
|
|
||||||
echo
|
|
||||||
|
|
||||||
/scripts/haproxy-reload.sh initial
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
sleep $EASYHAPROXY_REFRESH_CONF
|
|
||||||
log "info" "CONF_CHECK" "Heartbeat."
|
|
||||||
/scripts/haproxy-reload.sh
|
|
||||||
done
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
from kubernetes import client, config
|
|
||||||
from kubernetes.client.rest import ApiException
|
|
||||||
import json
|
|
||||||
|
|
||||||
# https://github.com/kubernetes-client/python/tree/master/kubernetes/docs
|
|
||||||
|
|
||||||
def main():
|
|
||||||
config.load_incluster_config()
|
|
||||||
|
|
||||||
api_instance = client.CoreV1Api()
|
|
||||||
v1 = client.NetworkingV1Api()
|
|
||||||
|
|
||||||
ret = v1.list_ingress_for_all_namespaces(watch=False)
|
|
||||||
|
|
||||||
discover = {}
|
|
||||||
for i in ret.items:
|
|
||||||
if i.metadata.annotations['kubernetes.io/ingress.class'] != "easyhaproxy-ingress":
|
|
||||||
continue
|
|
||||||
|
|
||||||
data = {}
|
|
||||||
#ingress_name = i.metadata.name
|
|
||||||
data["creation_timestamp"] = i.metadata.creation_timestamp.strftime("%x %X")
|
|
||||||
data["resource_version"] = i.metadata.resource_version
|
|
||||||
data["namespace"] = i.metadata.namespace
|
|
||||||
for rule in i.spec.rules:
|
|
||||||
rule_data = {}
|
|
||||||
port_number = rule.http.paths[0].backend.service.port.number
|
|
||||||
definition = rule.host.replace(".", "-")
|
|
||||||
rule_data["easyhaproxy.%s_%s.host" % (definition, port_number)] = rule.host
|
|
||||||
rule_data["easyhaproxy.%s_%s.port" % (definition, port_number)] = "80"
|
|
||||||
rule_data["easyhaproxy.%s_%s.localport" % (definition, port_number)] = port_number
|
|
||||||
service_name = rule.http.paths[0].backend.service.name
|
|
||||||
try:
|
|
||||||
api_response = api_instance.read_namespaced_service(service_name, i.metadata.namespace)
|
|
||||||
cluster_ip = api_response.spec.cluster_ip
|
|
||||||
except ApiException as e:
|
|
||||||
cluster_ip = None
|
|
||||||
# print("Exception when calling CoreV1Api->read_namespaced_service: %s\n" % e)
|
|
||||||
|
|
||||||
if cluster_ip is not None:
|
|
||||||
if cluster_ip not in discover.keys():
|
|
||||||
discover[cluster_ip] = data
|
|
||||||
discover[cluster_ip].update(rule_data)
|
|
||||||
|
|
||||||
for k in discover.keys():
|
|
||||||
print("%s=%s" % (k, json.dumps(discover[k])))
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
import yaml
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
from easymapping import HaproxyConfigGenerator
|
|
||||||
|
|
||||||
if len(sys.argv) != 2:
|
|
||||||
print("You need to pass the easyconfig.yml path")
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
with open(sys.argv[1], 'r') as content_file:
|
|
||||||
parsed = yaml.load(content_file.read(), Loader=yaml.FullLoader)
|
|
||||||
|
|
||||||
cfg = HaproxyConfigGenerator(parsed)
|
|
||||||
print(cfg.generate())
|
|
||||||
|
|
||||||
path = os.path.dirname(os.path.realpath(__file__))
|
|
||||||
with open(path + "/letsencrypt_hosts.txt", 'w') as fp:
|
|
||||||
fp.write('\n'.join(cfg.letsencrypt_hosts))
|
|
||||||
|
|
||||||
exit(0)
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
import os
|
|
||||||
from easymapping import HaproxyConfigGenerator
|
|
||||||
|
|
||||||
with open("/tmp/.docker_data", 'r') as content_file:
|
|
||||||
line_list = content_file.readlines()
|
|
||||||
|
|
||||||
result = {
|
|
||||||
"customerrors": True if os.getenv("HAPROXY_CUSTOMERRORS") == "true" else False,
|
|
||||||
"ssl_mode": os.getenv("EASYHAPROXY_SSL_MODE", "default")
|
|
||||||
}
|
|
||||||
|
|
||||||
if os.getenv("HAPROXY_PASSWORD"):
|
|
||||||
result["stats"] = {
|
|
||||||
"username": os.getenv("HAPROXY_USERNAME") if os.getenv("HAPROXY_USERNAME") else "admin",
|
|
||||||
"password": os.getenv("HAPROXY_PASSWORD"),
|
|
||||||
"port": os.getenv("HAPROXY_STATS_PORT") if os.getenv("HAPROXY_STATS_PORT") else "1936",
|
|
||||||
}
|
|
||||||
|
|
||||||
result["lookup_label"] = os.getenv("EASYHAPROXY_LABEL_PREFIX") if os.getenv("EASYHAPROXY_LABEL_PREFIX") else "easyhaproxy"
|
|
||||||
if (os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL")):
|
|
||||||
result["letsencrypt"] = {
|
|
||||||
"email": os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
cfg = HaproxyConfigGenerator(result)
|
|
||||||
print(cfg.generate(line_list))
|
|
||||||
|
|
||||||
path = os.path.dirname(os.path.realpath(__file__))
|
|
||||||
with open(path + "/letsencrypt_hosts.txt", 'w') as fp:
|
|
||||||
fp.write('\n'.join(cfg.letsencrypt_hosts))
|
|
||||||
# print(jsonStr)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -38,17 +38,7 @@ spec:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "100m"
|
||||||
memory: "128Mi"
|
memory: "128Mi"
|
||||||
#limits:
|
|
||||||
# cpu: "1"
|
|
||||||
# memory: "1Gi"
|
|
||||||
#securityContext:
|
|
||||||
# allowPrivilegeEscalation: true
|
|
||||||
# #runAsUser: 101 #nginx
|
|
||||||
# capabilities:
|
|
||||||
# drop:
|
|
||||||
# - ALL
|
|
||||||
# add:
|
|
||||||
# - NET_BIND_SERVICE
|
|
||||||
env:
|
env:
|
||||||
- name: EASYHAPROXY_DISCOVER
|
- name: EASYHAPROXY_DISCOVER
|
||||||
value: kubernetes
|
value: kubernetes
|
||||||
80
examples/kubernetes/service.yml
Normal file
80
examples/kubernetes/service.yml
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: easyhaproxy-ingress
|
||||||
|
name: container-example
|
||||||
|
namespace: parking
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: example.org
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
service:
|
||||||
|
name: container-example
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
- host: www.example.org
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
service:
|
||||||
|
name: container-example
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: container-example
|
||||||
|
namespace: parking
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 8080
|
||||||
|
selector:
|
||||||
|
app: container-example
|
||||||
|
type: ClusterIP
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: container-example
|
||||||
|
namespace: parking
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
revisionHistoryLimit: 10
|
||||||
|
strategy:
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 1
|
||||||
|
maxUnavailable: 0
|
||||||
|
type: RollingUpdate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: container-example
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: container-example
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: container-example
|
||||||
|
image: byjg/static-httpserver
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: '0.05'
|
||||||
|
memory: '20Mi'
|
||||||
|
requests:
|
||||||
|
cpu: '0.05'
|
||||||
|
memory: '20Mi'
|
||||||
|
env:
|
||||||
|
- name: TITLE
|
||||||
|
value: "My Host Example"
|
||||||
67
examples/swarm/docker-compose.yml
Normal file
67
examples/swarm/docker-compose.yml
Normal file
File diff suppressed because one or more lines are too long
50
examples/swarm/host2.local.pem
Normal file
50
examples/swarm/host2.local.pem
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDqTCCApGgAwIBAgIUId5b9t9uqH78g02EzbWF6FKVw3gwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwZDELMAkGA1UEBhMCQlIxFzAVBgNVBAgMDlJpbyBkZSBKYW5laXJvMRcwFQYD
|
||||||
|
VQQHDA5SaW8gZGUgSmFuZWlybzENMAsGA1UECgwEQUNNRTEUMBIGA1UEAwwLaG9z
|
||||||
|
dDIubG9jYWwwHhcNMjIwODE1MDQyNzA1WhcNMjMwODE1MDQyNzA1WjBkMQswCQYD
|
||||||
|
VQQGEwJCUjEXMBUGA1UECAwOUmlvIGRlIEphbmVpcm8xFzAVBgNVBAcMDlJpbyBk
|
||||||
|
ZSBKYW5laXJvMQ0wCwYDVQQKDARBQ01FMRQwEgYDVQQDDAtob3N0Mi5sb2NhbDCC
|
||||||
|
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMSKvrOahavCXnvSF5131hpo
|
||||||
|
6k65C57jgRQ84FaDj5MbJOVlYQVFtMG0XOk7a+hh5v1fe4wH0R7I6FDo0V9sS+ss
|
||||||
|
ko5bsElc1xYlg5HbuKq89vRSKg6EDlztx3BKbi912Pmt5vFGNJ16zcw77DUrQIXo
|
||||||
|
4I/b4a3pmBiWj43NoTIrmSWHtsGwwOj3iDvSweqdYXJIr3hpHH5u6pohjDoQvqDz
|
||||||
|
K6Mu8p6mhCUKNs7KFJnNInNG25oQT6O0n4OGtmgRjLWopdEnOhMkKsfIoI1XtlXB
|
||||||
|
LBDv7huICk3t5ywtfCQyO09kX7lFIgd5rn7+MjwH5WNeqbQJxuaqjoXQnNZUgUsC
|
||||||
|
AwEAAaNTMFEwHQYDVR0OBBYEFNhMBG8q6a+iK2nECwVTn6B9EXZOMB8GA1UdIwQY
|
||||||
|
MBaAFNhMBG8q6a+iK2nECwVTn6B9EXZOMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
|
||||||
|
hvcNAQELBQADggEBAJmudvx8+p5iIUsT8fm/fbVM0DA6qWALDYUJnTn3j6Lq4vpf
|
||||||
|
PFC+q1LmuWfBQMyqKrHrP3e493EctXoiSKZO6iN5dVJIur02OjGuiAEcsYuY1nLn
|
||||||
|
s9piiI+UEwxH6ux1NaHUnzsWauoBvRhzjXvO6SAVSZJYa9dY5mizXklDyDNuG5U0
|
||||||
|
lXv9egMGBsy0dG6eFXkU5CPdxWU540yI2sCtSAj7z+WRUD5k7gJ7tVoY3//jHQZG
|
||||||
|
5STTmm5t9kpIZTWkptyJos9oZJFYMIXqW2Fc6tyLZpRp31R78tDs6ETIkToDc0RR
|
||||||
|
jz66th6HI+ZlgIBQhw09+hYAhBDe9+Dmd/SzQZc=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDEir6zmoWrwl57
|
||||||
|
0hedd9YaaOpOuQue44EUPOBWg4+TGyTlZWEFRbTBtFzpO2voYeb9X3uMB9EeyOhQ
|
||||||
|
6NFfbEvrLJKOW7BJXNcWJYOR27iqvPb0UioOhA5c7cdwSm4vddj5rebxRjSdes3M
|
||||||
|
O+w1K0CF6OCP2+Gt6ZgYlo+NzaEyK5klh7bBsMDo94g70sHqnWFySK94aRx+buqa
|
||||||
|
IYw6EL6g8yujLvKepoQlCjbOyhSZzSJzRtuaEE+jtJ+DhrZoEYy1qKXRJzoTJCrH
|
||||||
|
yKCNV7ZVwSwQ7+4biApN7ecsLXwkMjtPZF+5RSIHea5+/jI8B+VjXqm0Ccbmqo6F
|
||||||
|
0JzWVIFLAgMBAAECggEBAKceitVROQQ5e/mxRR9CfK1sNH/H3Ne3/1PkB6XIrFab
|
||||||
|
qB3evEatZOuon7A6NKEeTjl37Se+pdSVZOUXcqC/BzbraZre3+EhrkpIj72ApV+Y
|
||||||
|
2iwZiWVaaJQgI4uZ3mNAw8RaWJsj5S1a9I8LDOiQ5IZ45CmvABDPJeMScvJSvRRY
|
||||||
|
e5N0L6stqS7Z+IoyGVKUfp1iNO0YyywOUiSkIRXgscuRXZGYpiGPomsJ+Js1ejzW
|
||||||
|
jyStlZJEr4L1285rGPrmHqjTwFd+hG80Wc4179xL+WRE6HBEUZSiy95fe6kcPHXX
|
||||||
|
BgiVYtcFKmiBi2dTbxl4e94ut239i0HtlJ1ZJtLh+BECgYEA8a298K2zXkHosxhN
|
||||||
|
tRrH7XfMPTkHDDd3rxM21LT+fIXqinGUp9LYaDcbjuTPs8e33uKMd7R6Q40x89yW
|
||||||
|
IXNka/VL0PXUeV67aCVLLqgXDLGudluJinH0XvmI0CmBecFSMqIFmQlgqERoGGs3
|
||||||
|
UMac0p876T4XkGQQJdf62bFpE4UCgYEA0DBH0PDlOpwXccDgXrMfayr8HAhI+G5R
|
||||||
|
yWQ//9iirtU83chwIWwkh53eLLMzLgdqJnPiWyaUW5BqzmYuD23nhxcQ7PNdIqOO
|
||||||
|
H1sE6zqLNshv46t5QKlh1Q4qjd7UqtgrSrY63RXJCMWTwnNMeDLtj8gaKbjkrG3R
|
||||||
|
BM2ildt6Uo8CgYBX7NDUli1SloH1XlsvD047S8FHaM7yl994F3J0UmDfpszcj1P4
|
||||||
|
9pF64Mmq4/3Yt0li0mMuTb/Jgb3xrYgFJXkcecKahEVH3ropup+umsLAAIirUMQq
|
||||||
|
VSkFwJ0Qtnj/deDUwPNuaOX8cd65O5CFV6zIR9xBEDD8fBsP2ZLOzmefDQKBgQDF
|
||||||
|
m24vVthd/1cJdCgD+0VxNYXDHeIVXLFo1S0iLYCNLn3tjZlRQBKUXzZJe3ay0/rf
|
||||||
|
sNND7aSYHMYkTzydDJbc1PoNzxmyDUiTXpOWqyUExM/fbB1VUPE5h47AxqdZ2oGN
|
||||||
|
EtdgjpMZLmCIC2SkGsL+3NJok8UKHdpuErmmQIMk5QKBgQCgEWcYtLXC3YDYMFdI
|
||||||
|
UgcTebFqSs3mLYgub1xekW3IXR2yom4V5fQTLiF7Yfn2dpDW4IcMU0UJFYVsUlhK
|
||||||
|
aGtet4Vm5Nn8+Mghot5yAjqO9yAUaub7wgifKIe99tQKd8uZyCvJ0hhvmDDSfx4m
|
||||||
|
B/TEiFAO99yF49iSxEVSAS6pqQ==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
|
@ -46,24 +46,21 @@ class DockerLabelHandler:
|
||||||
|
|
||||||
|
|
||||||
class HaproxyConfigGenerator:
|
class HaproxyConfigGenerator:
|
||||||
def __init__(self, mapping, ssl_cert_folder="/certs"):
|
def __init__(self, mapping):
|
||||||
self.mapping = mapping
|
self.mapping = mapping
|
||||||
self.mapping.setdefault("ssl_mode", 'default')
|
self.mapping.setdefault("ssl_mode", 'default')
|
||||||
self.mapping.setdefault("letsencrypt", {"email": ""})
|
self.mapping.setdefault("letsencrypt", {"email": ""})
|
||||||
self.mapping["ssl_mode"] = self.mapping["ssl_mode"].lower()
|
self.mapping["ssl_mode"] = self.mapping["ssl_mode"].lower()
|
||||||
self.label = DockerLabelHandler(mapping['lookup_label'] if 'lookup_label' in mapping else "easyhaproxy")
|
self.label = DockerLabelHandler(mapping['lookup_label'] if 'lookup_label' in mapping else "easyhaproxy")
|
||||||
self.ssl_cert_haproxy = ssl_cert_folder + "/haproxy"
|
|
||||||
self.ssl_cert_letsecncrypt = ssl_cert_folder + "/letsencrypt"
|
|
||||||
self.letsencrypt_hosts = []
|
self.letsencrypt_hosts = []
|
||||||
os.makedirs(self.ssl_cert_haproxy, exist_ok=True)
|
self.serving_hosts = []
|
||||||
os.makedirs(self.ssl_cert_letsecncrypt, exist_ok=True)
|
self.certs = {}
|
||||||
|
|
||||||
def generate(self, line_list = []):
|
def generate(self, container_metadata = None):
|
||||||
self.mapping.setdefault("easymapping", [])
|
self.mapping.setdefault("easymapping", [])
|
||||||
|
|
||||||
# static?
|
if container_metadata is not None:
|
||||||
if len(line_list) > 0:
|
self.mapping["easymapping"] = self.parse(container_metadata)
|
||||||
self.mapping["easymapping"] = self.parse(line_list)
|
|
||||||
|
|
||||||
file_loader = FileSystemLoader('templates')
|
file_loader = FileSystemLoader('templates')
|
||||||
env = Environment(loader=file_loader)
|
env = Environment(loader=file_loader)
|
||||||
|
|
@ -74,15 +71,11 @@ class HaproxyConfigGenerator:
|
||||||
return template.render(data=self.mapping)
|
return template.render(data=self.mapping)
|
||||||
|
|
||||||
|
|
||||||
def parse(self, line_list):
|
def parse(self, container_metadata):
|
||||||
easymapping = dict()
|
easymapping = dict()
|
||||||
|
|
||||||
for line in line_list:
|
for container in container_metadata:
|
||||||
line = line.strip()
|
d = container_metadata[container]
|
||||||
i = line.find("=")
|
|
||||||
container = line[:i]
|
|
||||||
json_str = line[i+1:]
|
|
||||||
d = json.loads(json_str)
|
|
||||||
|
|
||||||
# Extract the definitions dynamically
|
# Extract the definitions dynamically
|
||||||
definitions = {}
|
definitions = {}
|
||||||
|
|
@ -140,6 +133,7 @@ class HaproxyConfigGenerator:
|
||||||
|
|
||||||
for hostname in d[host_label].split(","):
|
for hostname in d[host_label].split(","):
|
||||||
hostname = hostname.strip()
|
hostname = hostname.strip()
|
||||||
|
self.serving_hosts.append("%s:%s" % (hostname, port))
|
||||||
easymapping[port]["hosts"].setdefault(hostname, {})
|
easymapping[port]["hosts"].setdefault(hostname, {})
|
||||||
easymapping[port]["hosts"][hostname].setdefault("containers", [])
|
easymapping[port]["hosts"][hostname].setdefault("containers", [])
|
||||||
easymapping[port]["hosts"][hostname].setdefault("letsencrypt", False)
|
easymapping[port]["hosts"][hostname].setdefault("letsencrypt", False)
|
||||||
|
|
@ -172,14 +166,10 @@ class HaproxyConfigGenerator:
|
||||||
# handle SSL
|
# handle SSL
|
||||||
ssl_label = self.label.create([definition, "sslcert"])
|
ssl_label = self.label.create([definition, "sslcert"])
|
||||||
if self.label.has_label(ssl_label):
|
if self.label.has_label(ssl_label):
|
||||||
filename = "{}/{}.pem".format(
|
filename = "{}.pem".format(d[host_label])
|
||||||
self.ssl_cert_haproxy, d[host_label]
|
|
||||||
)
|
|
||||||
easymapping[port]["ssl"] = True
|
easymapping[port]["ssl"] = True
|
||||||
with open(filename, 'wb') as file:
|
self.certs[filename] = base64.b64decode(d[ssl_label]).decode('ascii')
|
||||||
file.write(
|
|
||||||
base64.b64decode(d[ssl_label])
|
|
||||||
)
|
|
||||||
if self.label.get_bool(self.label.create([definition, "ssl"])):
|
if self.label.get_bool(self.label.create([definition, "ssl"])):
|
||||||
easymapping[port]["ssl"] = True
|
easymapping[port]["ssl"] = True
|
||||||
|
|
||||||
191
src/functions/__init__.py
Normal file
191
src/functions/__init__.py
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from multiprocessing import Process, Lock
|
||||||
|
import subprocess
|
||||||
|
import shlex
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
|
||||||
|
class Functions:
|
||||||
|
@staticmethod
|
||||||
|
def load(filename):
|
||||||
|
with open(filename, 'r') as content_file:
|
||||||
|
return content_file.read()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def save(filename, contents):
|
||||||
|
with open(filename, 'w') as file:
|
||||||
|
file.write(contents)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def log(source, level, message):
|
||||||
|
if message is None or message == "":
|
||||||
|
return
|
||||||
|
|
||||||
|
if not isinstance(message, (list, tuple)):
|
||||||
|
message = [message]
|
||||||
|
|
||||||
|
for line in message:
|
||||||
|
print("[%s] %s [%s]: %s" % (source, datetime.now().strftime("%x %X"), level, line.rstrip()))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run_bash(source, command, log_output=True, return_result=True):
|
||||||
|
if not isinstance(command, (list, tuple)):
|
||||||
|
command = shlex.split(command)
|
||||||
|
|
||||||
|
try:
|
||||||
|
process = subprocess.Popen(command,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
universal_newlines=True)
|
||||||
|
|
||||||
|
output = []
|
||||||
|
|
||||||
|
while True:
|
||||||
|
line = process.stdout.readline().rstrip()
|
||||||
|
output.append(line) if return_result else None
|
||||||
|
Functions.log(source, "info", line) if log_output else None
|
||||||
|
Functions.log(source, "warning", process.stderr.readline())
|
||||||
|
return_code = process.poll()
|
||||||
|
if return_code is not None:
|
||||||
|
lines = []
|
||||||
|
for line in process.stdout.readlines():
|
||||||
|
output.append(line.rstrip()) if return_result else None
|
||||||
|
lines.append(line.rstrip())
|
||||||
|
Functions.log(source, "info", lines) if log_output else None
|
||||||
|
Functions.log(source, "warning", process.stderr.readlines())
|
||||||
|
break
|
||||||
|
|
||||||
|
return output
|
||||||
|
except Exception as e:
|
||||||
|
Functions.log(source, 'error', "%s" % (e))
|
||||||
|
|
||||||
|
|
||||||
|
class DaemonizeHAProxy:
|
||||||
|
def __init__(self):
|
||||||
|
self.process = None
|
||||||
|
self.thread = None
|
||||||
|
|
||||||
|
def haproxy(self, action):
|
||||||
|
if action == "start":
|
||||||
|
self.__prepare("/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /var/run/haproxy.sock")
|
||||||
|
else:
|
||||||
|
pid = "".join(Functions().run_bash("HAPROXY", "cat /run/haproxy.pid", log_output=False))
|
||||||
|
self.__prepare("/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -x /var/run/haproxy.sock -sf %s" % (pid))
|
||||||
|
|
||||||
|
if self.process is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.thread = Process(target=self.__start, args=())
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
|
def __prepare(self, command):
|
||||||
|
source = "HAPROXY"
|
||||||
|
if not isinstance(command, (list, tuple)):
|
||||||
|
command = shlex.split(command)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.process = subprocess.Popen(command,
|
||||||
|
shell=False,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
bufsize=-1,
|
||||||
|
universal_newlines=True)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
Functions.log(source, 'error', "%s" % (e))
|
||||||
|
|
||||||
|
|
||||||
|
def __start(self):
|
||||||
|
source = "HAPROXY"
|
||||||
|
try:
|
||||||
|
with self.process.stdout:
|
||||||
|
for line in iter(self.process.stdout.readline, b''):
|
||||||
|
Functions.log(source, "info", line)
|
||||||
|
|
||||||
|
returncode = self.process.wait()
|
||||||
|
Functions.log(source, "info", "Return code %s" % (returncode))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
Functions.log(source, 'error', "%s" % (e))
|
||||||
|
|
||||||
|
def is_alive(self):
|
||||||
|
return self.thread.is_alive()
|
||||||
|
|
||||||
|
def kill(self):
|
||||||
|
self.process.kill()
|
||||||
|
self.thread.kill()
|
||||||
|
|
||||||
|
def terminate(self):
|
||||||
|
self.process.terminate()
|
||||||
|
self.thread.terminate()
|
||||||
|
|
||||||
|
|
||||||
|
class Certbot:
|
||||||
|
def __init__(self, certs, email):
|
||||||
|
self.certs = certs
|
||||||
|
self.email = email
|
||||||
|
|
||||||
|
def check_certificates(self, hosts):
|
||||||
|
if self.email == "" or len(hosts) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
request_certs = []
|
||||||
|
renew_certs = []
|
||||||
|
current_time = time.time()
|
||||||
|
for host in hosts:
|
||||||
|
filename = "%s/%s.pem" % (self.certs, host)
|
||||||
|
host_arg = '-d %s' % (host)
|
||||||
|
if not os.path.exists(filename):
|
||||||
|
Functions.log("CERTBOT", "info", "Request new certificate for %s" % (host))
|
||||||
|
request_certs.append(host_arg)
|
||||||
|
else:
|
||||||
|
creation_time = os.path.getctime(filename)
|
||||||
|
if (current_time - creation_time) // (24 * 3600) > 90:
|
||||||
|
Functions.log("CERTBOT", "info", "Request expired certificate for %s" % (host))
|
||||||
|
request_certs.append(host_arg)
|
||||||
|
if (current_time - creation_time) // (24 * 3600) >= 45:
|
||||||
|
Functions.log("CERTBOT", "info", "Renew certificate for %s" % (host))
|
||||||
|
renew_certs.append(host_arg)
|
||||||
|
|
||||||
|
certbot_certonly = ('/usr/bin/certbot certonly '
|
||||||
|
' --standalone'
|
||||||
|
' --preferred-challenges http'
|
||||||
|
' --http-01-port 2080'
|
||||||
|
' --agree-tos'
|
||||||
|
' --issuance-timeout 90'
|
||||||
|
' --no-eff-email'
|
||||||
|
' --non-interactive'
|
||||||
|
' --max-log-backups=0'
|
||||||
|
' %s --email %s' % (' '.join(request_certs), self.email)
|
||||||
|
)
|
||||||
|
|
||||||
|
ret_reload = False
|
||||||
|
if len(request_certs) > 0:
|
||||||
|
Functions.run_bash("CERTBOT", certbot_certonly, return_result=False)
|
||||||
|
ret_reload = True
|
||||||
|
|
||||||
|
if len(renew_certs) > 0:
|
||||||
|
Functions.run_bash("CERTBOT", "/usb/bin/certbot renew", return_result=False)
|
||||||
|
ret_reload = True
|
||||||
|
|
||||||
|
if ret_reload:
|
||||||
|
self.find_live_certificates()
|
||||||
|
|
||||||
|
return ret_reload
|
||||||
|
except Exception as e:
|
||||||
|
Functions.log("CERTBOT", "error", "%s" % (e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
def merge_certificate(self, cert, key, filename):
|
||||||
|
Functions.save(filename, cert + key)
|
||||||
|
|
||||||
|
def find_live_certificates(self):
|
||||||
|
letsencrypt_certs = "/etc/letsencrypt/live/"
|
||||||
|
for item in os.listdir(letsencrypt_certs):
|
||||||
|
path = os.path.join(letsencrypt_certs, item)
|
||||||
|
if os.path.isdir(path):
|
||||||
|
cert = Functions.load(os.path.join(path, "fullchain.pem"))
|
||||||
|
key = Functions.load(os.path.join(path, "privkey.pem"))
|
||||||
|
filename = "%s/%s.pem" % (self.certs, item)
|
||||||
|
self.merge_certificate(cert, key, filename)
|
||||||
76
src/main.py
Normal file
76
src/main.py
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
from functions import Functions, DaemonizeHAProxy, Certbot
|
||||||
|
from processor import ProcessorInterface
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from deepdiff import DeepDiff
|
||||||
|
|
||||||
|
easyhaproxy_config = "/etc/haproxy/easyconfig.yml"
|
||||||
|
haproxy_config = "/etc/haproxy/haproxy.cfg"
|
||||||
|
certs_letsencrypt = "/certs/letsencrypt"
|
||||||
|
certs_haproxy = "/certs/haproxy"
|
||||||
|
|
||||||
|
def start():
|
||||||
|
processor_obj = ProcessorInterface.factory(os.getenv("EASYHAPROXY_DISCOVER"))
|
||||||
|
if processor_obj is None:
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
os.makedirs(certs_letsencrypt, exist_ok=True)
|
||||||
|
os.makedirs(certs_haproxy, exist_ok=True)
|
||||||
|
|
||||||
|
processor_obj.save_config(haproxy_config)
|
||||||
|
processor_obj.save_certs(certs_haproxy)
|
||||||
|
letsencrypt_certs_found = processor_obj.get_letsencrypt_hosts()
|
||||||
|
Functions.log('EASYHAPROXY', 'info', 'Found hosts: %s' % ", ".join(processor_obj.get_hosts())) # Needs to after save_config
|
||||||
|
|
||||||
|
old_haproxy = None
|
||||||
|
haproxy = DaemonizeHAProxy()
|
||||||
|
haproxy.haproxy("start")
|
||||||
|
|
||||||
|
certbot = Certbot(certs_letsencrypt, os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL"))
|
||||||
|
|
||||||
|
while True:
|
||||||
|
time.sleep(10)
|
||||||
|
if old_haproxy is not None:
|
||||||
|
old_haproxy.kill()
|
||||||
|
old_haproxy = None
|
||||||
|
try:
|
||||||
|
old_parsed = processor_obj.get_parsed_object()
|
||||||
|
processor_obj.refresh()
|
||||||
|
if DeepDiff(old_parsed, processor_obj.get_parsed_object()) != {} or not haproxy.is_alive():
|
||||||
|
Functions.log('EASYHAPROXY', 'info', 'New configuration found. Reloading...')
|
||||||
|
processor_obj.save_config(haproxy_config)
|
||||||
|
processor_obj.save_certs(certs_haproxy)
|
||||||
|
letsencrypt_certs_found = processor_obj.get_letsencrypt_hosts()
|
||||||
|
Functions.log('EASYHAPROXY', 'info', 'Found hosts: %s' % ", ".join(processor_obj.get_hosts())) # Needs to after save_config
|
||||||
|
old_haproxy = haproxy
|
||||||
|
haproxy = DaemonizeHAProxy()
|
||||||
|
haproxy.haproxy("reload")
|
||||||
|
old_haproxy.terminate()
|
||||||
|
|
||||||
|
certbot.check_certificates(letsencrypt_certs_found)
|
||||||
|
except Exception as e:
|
||||||
|
Functions.log('EASYHAPROXY', 'error', "Err: %s" % (e))
|
||||||
|
Functions.log('EASYHAPROXY', 'info', 'Heartbeat')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
Functions.run_bash('INIT', '/usr/sbin/haproxy -v')
|
||||||
|
|
||||||
|
Functions.log('INIT', 'info', " _ ")
|
||||||
|
Functions.log('INIT', 'info', " ___ __ _ ____ _ ___| |_ __ _ _ __ _ _ _____ ___ _ ")
|
||||||
|
Functions.log('INIT', 'info', "/ -_) _` (_-< || |___| ' \/ _` | '_ \ '_/ _ \ \ / || |")
|
||||||
|
Functions.log('INIT', 'info', "\___\__,_/__/\_, | |_||_\__,_| .__/_| \___/_\_\\_, |")
|
||||||
|
Functions.log('INIT', 'info', " |__/ |_| |__/ ")
|
||||||
|
|
||||||
|
Functions.log('INIT', 'info', "Release: %s" % (os.getenv("RELEASE_VERSION")))
|
||||||
|
Functions.log('INIT', 'info', 'Environment:')
|
||||||
|
for name, value in os.environ.items():
|
||||||
|
if "HAPROXY" in name:
|
||||||
|
Functions.log('INIT', 'info', "- {0}: {1}".format(name, value))
|
||||||
|
|
||||||
|
start()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
173
src/processor/__init__.py
Normal file
173
src/processor/__init__.py
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
from easymapping import HaproxyConfigGenerator
|
||||||
|
from functions import Functions
|
||||||
|
import yaml
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import docker
|
||||||
|
from kubernetes import client, config
|
||||||
|
from kubernetes.client.rest import ApiException
|
||||||
|
|
||||||
|
class ContainerEnv:
|
||||||
|
@staticmethod
|
||||||
|
def read():
|
||||||
|
env_vars = {
|
||||||
|
"customerrors": True if os.getenv("HAPROXY_CUSTOMERRORS") == "true" else False,
|
||||||
|
"ssl_mode": os.getenv("EASYHAPROXY_SSL_MODE", "default")
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.getenv("HAPROXY_PASSWORD"):
|
||||||
|
env_vars["stats"] = {
|
||||||
|
"username": os.getenv("HAPROXY_USERNAME") if os.getenv("HAPROXY_USERNAME") else "admin",
|
||||||
|
"password": os.getenv("HAPROXY_PASSWORD"),
|
||||||
|
"port": os.getenv("HAPROXY_STATS_PORT") if os.getenv("HAPROXY_STATS_PORT") else "1936",
|
||||||
|
}
|
||||||
|
|
||||||
|
env_vars["lookup_label"] = os.getenv("EASYHAPROXY_LABEL_PREFIX") if os.getenv("EASYHAPROXY_LABEL_PREFIX") else "easyhaproxy"
|
||||||
|
if (os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL")):
|
||||||
|
env_vars["letsencrypt"] = {
|
||||||
|
"email": os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL")
|
||||||
|
}
|
||||||
|
|
||||||
|
return env_vars
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessorInterface:
|
||||||
|
def __init__(self, filename = None):
|
||||||
|
self.filename = filename
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def factory(mode):
|
||||||
|
if mode == "static":
|
||||||
|
return Static("/etc/haproxy/easyconfig.yml")
|
||||||
|
elif mode == "docker":
|
||||||
|
return Docker()
|
||||||
|
elif mode == "swarm":
|
||||||
|
return Swarm()
|
||||||
|
elif mode == "kubernetes":
|
||||||
|
return Kubernetes()
|
||||||
|
else:
|
||||||
|
Functions.log("FACTORY", "error", "Expected mode to be 'static', 'docker', 'swarm' or 'kubernetes'. I got '%s'" % (mode))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
self.letsencrypt_hosts = None
|
||||||
|
self.parsed_object = None
|
||||||
|
self.cfg = None
|
||||||
|
self.hosts = None
|
||||||
|
self.inspect_network()
|
||||||
|
self.parse()
|
||||||
|
|
||||||
|
def inspect_network(self):
|
||||||
|
#Abstract
|
||||||
|
pass
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
self.cfg = HaproxyConfigGenerator(ContainerEnv.read())
|
||||||
|
|
||||||
|
def get_letsencrypt_hosts(self):
|
||||||
|
return self.letsencrypt_hosts
|
||||||
|
|
||||||
|
def get_hosts(self):
|
||||||
|
return self.hosts
|
||||||
|
|
||||||
|
def get_parsed_object(self):
|
||||||
|
return self.parsed_object
|
||||||
|
|
||||||
|
def get_certs(self, key = None):
|
||||||
|
if key is None:
|
||||||
|
return self.cfg.certs
|
||||||
|
else:
|
||||||
|
return None if key not in self.cfg.certs else self.cfg.certs[key]
|
||||||
|
|
||||||
|
def get_haproxy_conf(self):
|
||||||
|
conf = self.cfg.generate(self.parsed_object)
|
||||||
|
self.letsencrypt_hosts = self.cfg.letsencrypt_hosts
|
||||||
|
self.hosts = self.cfg.serving_hosts
|
||||||
|
return conf
|
||||||
|
|
||||||
|
def save_config(self, filename):
|
||||||
|
Functions.save(filename, self.get_haproxy_conf())
|
||||||
|
|
||||||
|
def save_certs(self, path):
|
||||||
|
for cert in self.get_certs():
|
||||||
|
Functions.save("{0}/{1}".format(path, cert), self.get_certs(cert))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Static(ProcessorInterface):
|
||||||
|
def inspect_network(self):
|
||||||
|
self.parsed_object = {}
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
static_content = yaml.load(Functions.load(self.filename), Loader=yaml.FullLoader)
|
||||||
|
self.cfg = HaproxyConfigGenerator(static_content)
|
||||||
|
|
||||||
|
|
||||||
|
class Docker(ProcessorInterface):
|
||||||
|
def __init__(self, filename = None):
|
||||||
|
self.client = docker.from_env()
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def inspect_network(self):
|
||||||
|
self.parsed_object = {}
|
||||||
|
for container in self.client.containers.list():
|
||||||
|
self.parsed_object[container.name] = container.labels
|
||||||
|
|
||||||
|
|
||||||
|
class Swarm(ProcessorInterface):
|
||||||
|
def __init__(self, filename = None):
|
||||||
|
self.client = docker.from_env()
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def inspect_network(self):
|
||||||
|
self.parsed_object = {}
|
||||||
|
for container in self.client.services.list():
|
||||||
|
self.parsed_object[container.attrs["Spec"]["Name"]] = container.attrs["Spec"]["Labels"]
|
||||||
|
|
||||||
|
|
||||||
|
class Kubernetes(ProcessorInterface):
|
||||||
|
def __init__(self, filename = None):
|
||||||
|
config.load_incluster_config()
|
||||||
|
self.api_instance = client.CoreV1Api()
|
||||||
|
self.v1 = client.NetworkingV1Api()
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def inspect_network(self):
|
||||||
|
|
||||||
|
ret = self.v1.list_ingress_for_all_namespaces(watch=False)
|
||||||
|
|
||||||
|
self.parsed_object = {}
|
||||||
|
for i in ret.items:
|
||||||
|
if i.metadata.annotations['kubernetes.io/ingress.class'] != "easyhaproxy-ingress":
|
||||||
|
continue
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
#ingress_name = i.metadata.name
|
||||||
|
data["creation_timestamp"] = i.metadata.creation_timestamp.strftime("%x %X")
|
||||||
|
data["resource_version"] = i.metadata.resource_version
|
||||||
|
data["namespace"] = i.metadata.namespace
|
||||||
|
for rule in i.spec.rules:
|
||||||
|
rule_data = {}
|
||||||
|
port_number = rule.http.paths[0].backend.service.port.number
|
||||||
|
definition = rule.host.replace(".", "-")
|
||||||
|
rule_data["easyhaproxy.%s_%s.host" % (definition, port_number)] = rule.host
|
||||||
|
rule_data["easyhaproxy.%s_%s.port" % (definition, port_number)] = "80"
|
||||||
|
rule_data["easyhaproxy.%s_%s.localport" % (definition, port_number)] = port_number
|
||||||
|
service_name = rule.http.paths[0].backend.service.name
|
||||||
|
try:
|
||||||
|
api_response = self.api_instance.read_namespaced_service(service_name, i.metadata.namespace)
|
||||||
|
cluster_ip = api_response.spec.cluster_ip
|
||||||
|
except ApiException as e:
|
||||||
|
cluster_ip = None
|
||||||
|
# print("Exception when calling CoreV1Api->read_namespaced_service: %s\n" % e)
|
||||||
|
|
||||||
|
if cluster_ip is not None:
|
||||||
|
if cluster_ip not in self.parsed_object.keys():
|
||||||
|
self.parsed_object[cluster_ip] = data
|
||||||
|
self.parsed_object[cluster_ip].update(rule_data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2,4 +2,6 @@ pyyaml
|
||||||
docker
|
docker
|
||||||
jinja2
|
jinja2
|
||||||
pytest
|
pytest
|
||||||
|
docker
|
||||||
kubernetes
|
kubernetes
|
||||||
|
deepdiff
|
||||||
|
|
@ -78,3 +78,4 @@ backend srv_{{ host }}
|
||||||
backend letsencrypt_backend
|
backend letsencrypt_backend
|
||||||
mode http
|
mode http
|
||||||
server certbot 127.0.0.1:2080
|
server certbot 127.0.0.1:2080
|
||||||
|
|
||||||
5
src/tests/fixtures/no-services
vendored
Normal file
5
src/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
src/tests/fixtures/services
vendored
Normal file
6
src/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": {"easyhaproxy.agent.host":"agent.quantum.example.org","easyhaproxy.agent.localport":"9001","easyhaproxy.agent.mode":"tcp","easyhaproxy.agent.port":"31339","com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"},
|
||||||
|
"my-stack_cadvisor": {"easyhaproxy.cadvisor.host":"cadvisor.quantum.example.org","easyhaproxy.cadvisor.localport":"8080","easyhaproxy.cadvisor.port":"31337","com.docker.stack.image":"gcr.io/google-containers/cadvisor:v0.34.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"},
|
||||||
|
"my-stack_node-exporter": {"easyhaproxy.exp.host":"node-exporter.quantum.example.org","easyhaproxy.exp.localport":"9100","easyhaproxy.exp.port":"31337","com.docker.stack.image":"stefanprodan/swarmprom-node-exporter:v0.16.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring","easyhaproxy.exp.letsencrypt":"true"},
|
||||||
|
"my-stack_reverse-proxy": {"com.docker.stack.image":"quay.io/pngmbh/easy-haproxy:tcp-mode","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"},
|
||||||
|
"some-service": {"easyhaproxy.http.port":"80","easyhaproxy.http.host":"www.somehost.com.br","easyhaproxy.http.localport":"80","easyhaproxy.http.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","easyhaproxy.https.port":"443","easyhaproxy.https.host":"www.somehost.com.br","easyhaproxy.https.localport":"80","easyhaproxy.https.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","easyhaproxy.https.sslcert":"U29tZSBQRU0gQ2VydGlmaWNhdGU="}}
|
||||||
6
src/tests/fixtures/services-changed-label
vendored
Normal file
6
src/tests/fixtures/services-changed-label
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": {"haproxy.agent.host":"agent.quantum.example.org","haproxy.agent.localport":"9001","haproxy.agent.mode":"tcp","haproxy.agent.port":"31339","com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"},
|
||||||
|
"my-stack_cadvisor": {"haproxy.cadvisor.host":"cadvisor.quantum.example.org","haproxy.cadvisor.localport":"8080","haproxy.cadvisor.port":"31337","com.docker.stack.image":"gcr.io/google-containers/cadvisor:v0.34.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"},
|
||||||
|
"my-stack_node-exporter": {"haproxy.exp.host":"node-exporter.quantum.example.org","haproxy.exp.localport":"9100","haproxy.exp.port":"31337","com.docker.stack.image":"stefanprodan/swarmprom-node-exporter:v0.16.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring","haproxy.exp.letsencrypt":"yes"},
|
||||||
|
"my-stack_reverse-proxy": {"com.docker.stack.image":"quay.io/pngmbh/easy-haproxy:tcp-mode","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"},
|
||||||
|
"some-service": {"haproxy.http.port":"80","haproxy.http.host":"www.somehost.com.br","haproxy.http.localport":"80","haproxy.http.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","haproxy.https.port":"443","haproxy.https.host":"www.somehost.com.br","haproxy.https.localport":"80","haproxy.https.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","haproxy.https.sslcert":"U29tZSBQRU0gQ2VydGlmaWNhdGU="}}
|
||||||
4
src/tests/fixtures/services-letsencrypt
vendored
Normal file
4
src/tests/fixtures/services-letsencrypt
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{"f5c645a0dfc6": {"com.docker.compose.config-hash":"b95ebc27d0e61caa418cdfa632e05a656da9bbc3ea0d4603651971015f10a1f0","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test.example.org","easyhaproxy.http.letsencrypt":"true","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"80","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}","easyhaproxy.http.redirect_ssl":"true"},
|
||||||
|
"bbd4d1854155": {"com.docker.compose.config-hash":"3dc790bf2bea944359c75a40c45655bd868f1d85beb599d1ca797e8ea2c95ee4","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:0fd95b1512c207048ab3fcc74032354f38143fbb8235ac2a47da903c98a58205","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"haproxy","com.docker.compose.version":"2.8.0"},
|
||||||
|
"b63438410b6a": {"com.docker.compose.config-hash":"b95ebc27d0e61caa418cdfa632e05a656da9bbc3ea0d4603651971015f10a1f0","com.docker.compose.container-number":"2","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test.example.org","easyhaproxy.http.letsencrypt":"true","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"80","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}","easyhaproxy.http.redirect_ssl":"true"},
|
||||||
|
"83d57d592e26": {"com.docker.compose.config-hash":"8c5871144f1e8a3aeca037207c02f011ab2c6e6c311a3773602b63541762dab5","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:c4232396c715f3d568816c666e6d9b4a68ef6c36f6243b4007c4ee1d8335fd65","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"static","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test2.example.org","easyhaproxy.http.localport":"8080","easyhaproxy.http.port":"80","io.buildah.version":"1.21.0"}}
|
||||||
2
src/tests/fixtures/services-multi-containers
vendored
Normal file
2
src/tests/fixtures/services-multi-containers
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
{"test_nginx.2.t5r94mjlced7m3t5orfjbowmm": {"easyhaproxy.http.host":"www.helloworld.com","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","com.docker.stack.image":"stenote/nginx-hostname","com.docker.stack.namespace":"test"},
|
||||||
|
"test_nginx.1.p552hqxkdx88narjrp5kouwb2": {"easyhaproxy.http.host":"www.helloworld.com","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","com.docker.stack.image":"stenote/nginx-hostname","com.docker.stack.namespace":"test"}}
|
||||||
3
src/tests/fixtures/services-multiple-hosts
vendored
Normal file
3
src/tests/fixtures/services-multiple-hosts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{"db79d3a910f4": {"com.docker.compose.config-hash":"5bde40f52451521ad201e70de1291397376a0498a7c955624a609da3b60e7e8e","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:ea39067705590557dd0cd951664a10970ceefcb725a3c1f43690d6d6d4ed5fce","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-multi-containers.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"haproxy","com.docker.compose.version":"2.8.0"},
|
||||||
|
"3e63154954b0": {"com.docker.compose.config-hash":"4e0cbdd8372c6779863799e5021ed8178f74b55bd8e070abcdffaf87eb7baa36","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-multi-containers.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"hello.com\n, www.helloworld.com\n","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}"},
|
||||||
|
"eb294c110eb1": {"com.docker.compose.config-hash":"4e0cbdd8372c6779863799e5021ed8178f74b55bd8e070abcdffaf87eb7baa36","com.docker.compose.container-number":"2","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-multi-containers.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"hello.com\n, www.helloworld.com\n","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}"}}
|
||||||
3
src/tests/fixtures/services-redirect-ssl
vendored
Normal file
3
src/tests/fixtures/services-redirect-ssl
vendored
Normal file
File diff suppressed because one or more lines are too long
2
src/tests/fixtures/services-tcp
vendored
Normal file
2
src/tests/fixtures/services-tcp
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
{"test_agent": {"easyhaproxy.agent.host":"agent.quantum.local","easyhaproxy.agent.localport":"9001","easyhaproxy.agent.mode":"tcp","easyhaproxy.agent.port":"31339","com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"test", "easyhaproxy.agent.health-check":"ssl"},
|
||||||
|
"test_proxy": {"com.docker.stack.image":"byjg/easy-haproxy:local","com.docker.stack.namespace":"test"}}
|
||||||
|
|
@ -2,6 +2,7 @@ import easymapping
|
||||||
import pytest
|
import pytest
|
||||||
import os
|
import os
|
||||||
import yaml
|
import yaml
|
||||||
|
import json
|
||||||
|
|
||||||
CERTS_FOLDER="/tmp/certs"
|
CERTS_FOLDER="/tmp/certs"
|
||||||
CERT_FILE="/tmp/certs/haproxy/www.somehost.com.br.pem"
|
CERT_FILE="/tmp/certs/haproxy/www.somehost.com.br.pem"
|
||||||
|
|
@ -10,7 +11,7 @@ LETSENCRYPT_EMAIL="some@email.com"
|
||||||
def load_fixture(file):
|
def load_fixture(file):
|
||||||
path = os.path.dirname(os.path.realpath(__file__))
|
path = os.path.dirname(os.path.realpath(__file__))
|
||||||
with open(path + "/fixtures/" + file, 'r') as content_file:
|
with open(path + "/fixtures/" + file, 'r') as content_file:
|
||||||
line_list = content_file.readlines()
|
line_list = json.loads("".join(content_file.readlines()))
|
||||||
|
|
||||||
return line_list
|
return line_list
|
||||||
|
|
||||||
|
|
@ -25,7 +26,7 @@ def test_parser_doesnt_crash():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER)
|
cfg = easymapping.HaproxyConfigGenerator(result)
|
||||||
haproxy_config = cfg.generate(line_list)
|
haproxy_config = cfg.generate(line_list)
|
||||||
|
|
||||||
assert len(haproxy_config) > 0
|
assert len(haproxy_config) > 0
|
||||||
|
|
@ -47,10 +48,7 @@ def test_parser_finds_services():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.path.exists(CERT_FILE):
|
cfg = easymapping.HaproxyConfigGenerator(result)
|
||||||
os.remove(CERT_FILE)
|
|
||||||
|
|
||||||
cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER)
|
|
||||||
haproxy_config = cfg.generate(line_list)
|
haproxy_config = cfg.generate(line_list)
|
||||||
|
|
||||||
assert len(haproxy_config) > 0
|
assert len(haproxy_config) > 0
|
||||||
|
|
@ -58,8 +56,7 @@ def test_parser_finds_services():
|
||||||
with open(path + "/expected/services.txt", 'r') as expected_file:
|
with open(path + "/expected/services.txt", 'r') as expected_file:
|
||||||
assert expected_file.read() == haproxy_config
|
assert expected_file.read() == haproxy_config
|
||||||
|
|
||||||
with open(CERT_FILE, 'r') as expected_file:
|
assert {"www.somehost.com.br.pem":"Some PEM Certificate"} == cfg.certs
|
||||||
assert expected_file.read() == "Some PEM Certificate"
|
|
||||||
|
|
||||||
assert ['node-exporter.quantum.example.org'] == cfg.letsencrypt_hosts
|
assert ['node-exporter.quantum.example.org'] == cfg.letsencrypt_hosts
|
||||||
|
|
||||||
|
|
@ -80,7 +77,7 @@ def test_parser_finds_services_changed_label():
|
||||||
if os.path.exists(CERT_FILE):
|
if os.path.exists(CERT_FILE):
|
||||||
os.remove(CERT_FILE)
|
os.remove(CERT_FILE)
|
||||||
|
|
||||||
cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER)
|
cfg = easymapping.HaproxyConfigGenerator(result)
|
||||||
haproxy_config = cfg.generate(line_list)
|
haproxy_config = cfg.generate(line_list)
|
||||||
|
|
||||||
assert len(haproxy_config) > 0
|
assert len(haproxy_config) > 0
|
||||||
|
|
@ -88,8 +85,7 @@ def test_parser_finds_services_changed_label():
|
||||||
with open(path + "/expected/services.txt", 'r') as expected_file:
|
with open(path + "/expected/services.txt", 'r') as expected_file:
|
||||||
assert expected_file.read() == haproxy_config
|
assert expected_file.read() == haproxy_config
|
||||||
|
|
||||||
with open(CERT_FILE, 'r') as expected_file:
|
assert {"www.somehost.com.br.pem":"Some PEM Certificate"} == cfg.certs
|
||||||
assert expected_file.read() == "Some PEM Certificate"
|
|
||||||
|
|
||||||
assert ['node-exporter.quantum.example.org'] == cfg.letsencrypt_hosts
|
assert ['node-exporter.quantum.example.org'] == cfg.letsencrypt_hosts
|
||||||
|
|
||||||
|
|
@ -109,7 +105,7 @@ def test_parser_finds_services_raw():
|
||||||
if os.path.exists(CERT_FILE):
|
if os.path.exists(CERT_FILE):
|
||||||
os.remove(CERT_FILE)
|
os.remove(CERT_FILE)
|
||||||
|
|
||||||
cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER)
|
cfg = easymapping.HaproxyConfigGenerator(result)
|
||||||
|
|
||||||
parsed_object = [
|
parsed_object = [
|
||||||
{
|
{
|
||||||
|
|
@ -217,7 +213,7 @@ def test_parser_static():
|
||||||
with open(path + "/fixtures/static.yml", 'r') as content_file:
|
with open(path + "/fixtures/static.yml", 'r') as content_file:
|
||||||
parsed = yaml.load(content_file.read(), Loader=yaml.FullLoader)
|
parsed = yaml.load(content_file.read(), Loader=yaml.FullLoader)
|
||||||
|
|
||||||
cfg = easymapping.HaproxyConfigGenerator(parsed, CERTS_FOLDER)
|
cfg = easymapping.HaproxyConfigGenerator(parsed)
|
||||||
haproxy_config = cfg.generate()
|
haproxy_config = cfg.generate()
|
||||||
assert len(haproxy_config) > 0
|
assert len(haproxy_config) > 0
|
||||||
|
|
||||||
|
|
@ -295,7 +291,7 @@ def test_parser_tcp():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER)
|
cfg = easymapping.HaproxyConfigGenerator(result)
|
||||||
haproxy_config = cfg.generate(line_list)
|
haproxy_config = cfg.generate(line_list)
|
||||||
# print(haproxy_config)
|
# print(haproxy_config)
|
||||||
|
|
||||||
|
|
@ -315,7 +311,7 @@ def test_parser_multi_containers():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER)
|
cfg = easymapping.HaproxyConfigGenerator(result)
|
||||||
haproxy_config = cfg.generate(line_list)
|
haproxy_config = cfg.generate(line_list)
|
||||||
|
|
||||||
assert len(haproxy_config) > 0
|
assert len(haproxy_config) > 0
|
||||||
|
|
@ -337,7 +333,7 @@ def test_parser_multiple_hosts():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER)
|
cfg = easymapping.HaproxyConfigGenerator(result)
|
||||||
haproxy_config = cfg.generate(line_list)
|
haproxy_config = cfg.generate(line_list)
|
||||||
|
|
||||||
assert len(haproxy_config) > 0
|
assert len(haproxy_config) > 0
|
||||||
|
|
@ -358,7 +354,7 @@ def test_parser_redirect_ssl():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER)
|
cfg = easymapping.HaproxyConfigGenerator(result)
|
||||||
haproxy_config = cfg.generate(line_list)
|
haproxy_config = cfg.generate(line_list)
|
||||||
|
|
||||||
assert len(haproxy_config) > 0
|
assert len(haproxy_config) > 0
|
||||||
|
|
@ -379,7 +375,7 @@ def test_parser_ssl_strict():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER)
|
cfg = easymapping.HaproxyConfigGenerator(result)
|
||||||
haproxy_config = cfg.generate(line_list)
|
haproxy_config = cfg.generate(line_list)
|
||||||
|
|
||||||
assert len(haproxy_config) > 0
|
assert len(haproxy_config) > 0
|
||||||
|
|
@ -396,7 +392,7 @@ def test_parser_ssl_loose():
|
||||||
"ssl_mode": "loose",
|
"ssl_mode": "loose",
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER)
|
cfg = easymapping.HaproxyConfigGenerator(result)
|
||||||
haproxy_config = cfg.generate(line_list)
|
haproxy_config = cfg.generate(line_list)
|
||||||
|
|
||||||
assert len(haproxy_config) > 0
|
assert len(haproxy_config) > 0
|
||||||
|
|
@ -418,7 +414,7 @@ def test_parser_ssl_letsencrypt():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER)
|
cfg = easymapping.HaproxyConfigGenerator(result)
|
||||||
haproxy_config = cfg.generate(line_list)
|
haproxy_config = cfg.generate(line_list)
|
||||||
|
|
||||||
assert len(haproxy_config) > 0
|
assert len(haproxy_config) > 0
|
||||||
|
|
@ -431,3 +427,4 @@ def test_parser_ssl_letsencrypt():
|
||||||
#test_parser_tcp()
|
#test_parser_tcp()
|
||||||
#test_parser_multiple_hosts()
|
#test_parser_multiple_hosts()
|
||||||
#test_parser_ssl_letsencrypt()
|
#test_parser_ssl_letsencrypt()
|
||||||
|
#test_parser_finds_services()
|
||||||
5
tests/fixtures/no-services
vendored
5
tests/fixtures/no-services
vendored
|
|
@ -1,5 +0,0 @@
|
||||||
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
6
tests/fixtures/services
vendored
|
|
@ -1,6 +0,0 @@
|
||||||
portainer-agent_agent={"com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"portainer-agent"}
|
|
||||||
my-stack_agent={"easyhaproxy.agent.host":"agent.quantum.example.org","easyhaproxy.agent.localport":"9001","easyhaproxy.agent.mode":"tcp","easyhaproxy.agent.port":"31339","com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"}
|
|
||||||
my-stack_cadvisor={"easyhaproxy.cadvisor.host":"cadvisor.quantum.example.org","easyhaproxy.cadvisor.localport":"8080","easyhaproxy.cadvisor.port":"31337","com.docker.stack.image":"gcr.io/google-containers/cadvisor:v0.34.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"}
|
|
||||||
my-stack_node-exporter={"easyhaproxy.exp.host":"node-exporter.quantum.example.org","easyhaproxy.exp.localport":"9100","easyhaproxy.exp.port":"31337","com.docker.stack.image":"stefanprodan/swarmprom-node-exporter:v0.16.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring","easyhaproxy.exp.letsencrypt":"true"}
|
|
||||||
my-stack_reverse-proxy={"com.docker.stack.image":"quay.io/pngmbh/easy-haproxy:tcp-mode","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"}
|
|
||||||
some-service={"easyhaproxy.http.port":"80","easyhaproxy.http.host":"www.somehost.com.br","easyhaproxy.http.localport":"80","easyhaproxy.http.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","easyhaproxy.https.port":"443","easyhaproxy.https.host":"www.somehost.com.br","easyhaproxy.https.localport":"80","easyhaproxy.https.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","easyhaproxy.https.sslcert":"U29tZSBQRU0gQ2VydGlmaWNhdGU="}
|
|
||||||
6
tests/fixtures/services-changed-label
vendored
6
tests/fixtures/services-changed-label
vendored
|
|
@ -1,6 +0,0 @@
|
||||||
portainer-agent_agent={"com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"portainer-agent"}
|
|
||||||
my-stack_agent={"haproxy.agent.host":"agent.quantum.example.org","haproxy.agent.localport":"9001","haproxy.agent.mode":"tcp","haproxy.agent.port":"31339","com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"}
|
|
||||||
my-stack_cadvisor={"haproxy.cadvisor.host":"cadvisor.quantum.example.org","haproxy.cadvisor.localport":"8080","haproxy.cadvisor.port":"31337","com.docker.stack.image":"gcr.io/google-containers/cadvisor:v0.34.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"}
|
|
||||||
my-stack_node-exporter={"haproxy.exp.host":"node-exporter.quantum.example.org","haproxy.exp.localport":"9100","haproxy.exp.port":"31337","com.docker.stack.image":"stefanprodan/swarmprom-node-exporter:v0.16.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring","haproxy.exp.letsencrypt":"yes"}
|
|
||||||
my-stack_reverse-proxy={"com.docker.stack.image":"quay.io/pngmbh/easy-haproxy:tcp-mode","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"}
|
|
||||||
some-service={"haproxy.http.port":"80","haproxy.http.host":"www.somehost.com.br","haproxy.http.localport":"80","haproxy.http.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","haproxy.https.port":"443","haproxy.https.host":"www.somehost.com.br","haproxy.https.localport":"80","haproxy.https.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","haproxy.https.sslcert":"U29tZSBQRU0gQ2VydGlmaWNhdGU="}
|
|
||||||
4
tests/fixtures/services-letsencrypt
vendored
4
tests/fixtures/services-letsencrypt
vendored
|
|
@ -1,4 +0,0 @@
|
||||||
f5c645a0dfc6={"com.docker.compose.config-hash":"b95ebc27d0e61caa418cdfa632e05a656da9bbc3ea0d4603651971015f10a1f0","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test.example.org","easyhaproxy.http.letsencrypt":"true","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"80","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}","easyhaproxy.http.redirect_ssl":"true"}
|
|
||||||
bbd4d1854155={"com.docker.compose.config-hash":"3dc790bf2bea944359c75a40c45655bd868f1d85beb599d1ca797e8ea2c95ee4","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:0fd95b1512c207048ab3fcc74032354f38143fbb8235ac2a47da903c98a58205","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"haproxy","com.docker.compose.version":"2.8.0"}
|
|
||||||
b63438410b6a={"com.docker.compose.config-hash":"b95ebc27d0e61caa418cdfa632e05a656da9bbc3ea0d4603651971015f10a1f0","com.docker.compose.container-number":"2","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test.example.org","easyhaproxy.http.letsencrypt":"true","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"80","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}","easyhaproxy.http.redirect_ssl":"true"}
|
|
||||||
83d57d592e26={"com.docker.compose.config-hash":"8c5871144f1e8a3aeca037207c02f011ab2c6e6c311a3773602b63541762dab5","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:c4232396c715f3d568816c666e6d9b4a68ef6c36f6243b4007c4ee1d8335fd65","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"static","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test2.example.org","easyhaproxy.http.localport":"8080","easyhaproxy.http.port":"80","io.buildah.version":"1.21.0"}
|
|
||||||
2
tests/fixtures/services-multi-containers
vendored
2
tests/fixtures/services-multi-containers
vendored
|
|
@ -1,2 +0,0 @@
|
||||||
test_nginx.2.t5r94mjlced7m3t5orfjbowmm={"easyhaproxy.http.host":"www.helloworld.com","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","com.docker.stack.image":"stenote/nginx-hostname","com.docker.stack.namespace":"test"}
|
|
||||||
test_nginx.1.p552hqxkdx88narjrp5kouwb2={"easyhaproxy.http.host":"www.helloworld.com","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","com.docker.stack.image":"stenote/nginx-hostname","com.docker.stack.namespace":"test"}
|
|
||||||
3
tests/fixtures/services-multiple-hosts
vendored
3
tests/fixtures/services-multiple-hosts
vendored
|
|
@ -1,3 +0,0 @@
|
||||||
db79d3a910f4={"com.docker.compose.config-hash":"5bde40f52451521ad201e70de1291397376a0498a7c955624a609da3b60e7e8e","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:ea39067705590557dd0cd951664a10970ceefcb725a3c1f43690d6d6d4ed5fce","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-multi-containers.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"haproxy","com.docker.compose.version":"2.8.0"}
|
|
||||||
3e63154954b0={"com.docker.compose.config-hash":"4e0cbdd8372c6779863799e5021ed8178f74b55bd8e070abcdffaf87eb7baa36","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-multi-containers.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"hello.com\n, www.helloworld.com\n","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}"}
|
|
||||||
eb294c110eb1={"com.docker.compose.config-hash":"4e0cbdd8372c6779863799e5021ed8178f74b55bd8e070abcdffaf87eb7baa36","com.docker.compose.container-number":"2","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-multi-containers.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"hello.com\n, www.helloworld.com\n","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}"}
|
|
||||||
3
tests/fixtures/services-redirect-ssl
vendored
3
tests/fixtures/services-redirect-ssl
vendored
File diff suppressed because one or more lines are too long
2
tests/fixtures/services-tcp
vendored
2
tests/fixtures/services-tcp
vendored
|
|
@ -1,2 +0,0 @@
|
||||||
test_agent={"easyhaproxy.agent.host":"agent.quantum.local","easyhaproxy.agent.localport":"9001","easyhaproxy.agent.mode":"tcp","easyhaproxy.agent.port":"31339","com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"test", "easyhaproxy.agent.health-check":"ssl"}
|
|
||||||
test_proxy={"com.docker.stack.image":"byjg/easy-haproxy:local","com.docker.stack.namespace":"test"}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue