forked from Ivasoft/traefik
Compare commits
35 Commits
v1.0.0-bet
...
v1.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
749b381f26 | ||
|
|
d89279d708 | ||
|
|
be209ed30c | ||
|
|
4a4ba2791d | ||
|
|
c61d9776e7 | ||
|
|
b5716abd3e | ||
|
|
b9bb78d04b | ||
|
|
8a39ee65cd | ||
|
|
301a463aeb | ||
|
|
d1b0bece47 | ||
|
|
63fd7d1d63 | ||
|
|
f4fb2518a1 | ||
|
|
ee486de947 | ||
|
|
c1a12a58eb | ||
|
|
c3aadab615 | ||
|
|
26774d2317 | ||
|
|
61def880db | ||
|
|
11a6331185 | ||
|
|
378509cef4 | ||
|
|
4a1fa03b2d | ||
|
|
52bff85dda | ||
|
|
e5b0b34604 | ||
|
|
0a0063fa27 | ||
|
|
bf1f6f663a | ||
|
|
8bac454792 | ||
|
|
7eaf09b3da | ||
|
|
378a261e64 | ||
|
|
53c99f7469 | ||
|
|
f93e618f67 | ||
|
|
64b78461f6 | ||
|
|
2a76a717e6 | ||
|
|
c8c0d208be | ||
|
|
04dd41ac3b | ||
|
|
10815eca8e | ||
|
|
a7b4463f86 |
31
.github/CONTRIBUTING.md
vendored
31
.github/CONTRIBUTING.md
vendored
@@ -76,3 +76,34 @@ ok github.com/containous/traefik 0.005s coverage: 4.1% of statements
|
||||
|
||||
Test success
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
The [documentation site](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/)
|
||||
|
||||
First make sure you have python and pip installed
|
||||
|
||||
```
|
||||
$ python --version
|
||||
Python 2.7.2
|
||||
$ pip --version
|
||||
pip 1.5.2
|
||||
```
|
||||
|
||||
Then install mkdocs with pip
|
||||
|
||||
```
|
||||
$ pip install mkdocs
|
||||
```
|
||||
|
||||
To test documentaion localy run `mkdocs serve` in the root directory, this should start a server localy to preview your changes.
|
||||
|
||||
```
|
||||
$ mkdocs serve
|
||||
INFO - Building documentation...
|
||||
WARNING - Config value: 'theme'. Warning: The theme 'united' will be removed in an upcoming MkDocs release. See http://www.mkdocs.org/about/release-notes/ for more details
|
||||
INFO - Cleaning site directory
|
||||
[I 160505 22:31:24 server:281] Serving on http://127.0.0.1:8000
|
||||
[I 160505 22:31:24 handlers:59] Start watching changes
|
||||
[I 160505 22:31:24 handlers:61] Start detecting changes
|
||||
```
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -10,4 +10,6 @@ traefik.toml
|
||||
vendor/
|
||||
static/
|
||||
.vscode/
|
||||
site/
|
||||
site/
|
||||
*.log
|
||||
*.exe
|
||||
|
||||
@@ -140,4 +140,4 @@ Founded in 2014, Asteris creates next-generation infrastructure software for the
|
||||
|
||||
## Credits
|
||||
|
||||
Thanks you [Peka](http://peka.byethost11.com/photoblog/) for your awesome work on the logo 
|
||||
Kudos to [Peka](http://peka.byethost11.com/photoblog/) for his awesome work on the logo 
|
||||
@@ -406,7 +406,7 @@ func (a *ACME) saveAccount(Account *Account) error {
|
||||
|
||||
func (a *ACME) getDomainsCertificates(client *acme.Client, domains []string) (*Certificate, error) {
|
||||
log.Debugf("Loading ACME certificates %s...", domains)
|
||||
bundle := false
|
||||
bundle := true
|
||||
certificate, failures := client.ObtainCertificate(domains, bundle, nil)
|
||||
if len(failures) > 0 {
|
||||
log.Error(failures)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
FROM golang:1.6.1-alpine
|
||||
FROM golang:1.6.2
|
||||
|
||||
RUN apk update && apk add git bash gcc musl-dev \
|
||||
&& go get github.com/Masterminds/glide \
|
||||
&& go get github.com/mitchellh/gox \
|
||||
RUN go get github.com/Masterminds/glide \
|
||||
&& go get github.com/jteeuwen/go-bindata/... \
|
||||
&& go get github.com/golang/lint/golint \
|
||||
&& go get github.com/kisielk/errcheck
|
||||
|
||||
1
cmd.go
1
cmd.go
@@ -172,6 +172,7 @@ func init() {
|
||||
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.kubernetes, "kubernetes", false, "Enable Kubernetes backend")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Kubernetes.Endpoint, "kubernetes.endpoint", "127.0.0.1:8080", "Kubernetes server endpoint")
|
||||
traefikCmd.PersistentFlags().StringSliceVar(&arguments.Kubernetes.Namespaces, "kubernetes.namespaces", []string{}, "Kubernetes namespaces")
|
||||
|
||||
_ = viper.BindPFlag("configFile", traefikCmd.PersistentFlags().Lookup("configFile"))
|
||||
_ = viper.BindPFlag("graceTimeOut", traefikCmd.PersistentFlags().Lookup("graceTimeOut"))
|
||||
|
||||
@@ -122,6 +122,12 @@
|
||||
## ACME (Let's Encrypt) configuration
|
||||
|
||||
```toml
|
||||
# Sample entrypoint configuration when using ACME
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
|
||||
# Enable ACME (Let's Encrypt): automatic SSL
|
||||
#
|
||||
# Optional
|
||||
@@ -166,6 +172,7 @@ entryPoint = "https"
|
||||
|
||||
# Domains list
|
||||
# You can provide SANs (alternative domains) to each main domain
|
||||
# All domains must have A/AAAA records pointing to Traefik
|
||||
# WARNING, Take note that Let's Encrypt have rate limiting: https://community.letsencrypt.org/t/quick-start-guide/1631
|
||||
# Each domain & SANs will lead to a certificate request.
|
||||
#
|
||||
@@ -641,6 +648,7 @@ Træfɪk can be configured to use Kubernetes Ingress as a backend configuration:
|
||||
# Optional
|
||||
#
|
||||
# endpoint = "http://localhost:8080"
|
||||
# namespaces = ["default","production"]
|
||||
```
|
||||
|
||||
You can find here an example [ingress](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s.ingress.yaml) and [replication controller](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s.rc.yaml).
|
||||
|
||||
2
examples/accessLog/.gitignore
vendored
Normal file
2
examples/accessLog/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
exampleHandler
|
||||
exampleHandler.exe
|
||||
46
examples/accessLog/exampleHandler.go
Normal file
46
examples/accessLog/exampleHandler.go
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
Simple program to start a web server on a specified port
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
name string
|
||||
port int
|
||||
help *bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&name, "n", "", "Name of handler for messages")
|
||||
flag.IntVar(&port, "p", 0, "Port number to listen")
|
||||
help = flag.Bool("h", false, "Displays help message")
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Printf("Usage: example -n name -p port \n")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "%s: Received query %s!\n", name, r.URL.Path[1:])
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if *help || len(name) == 0 || port <= 0 {
|
||||
usage()
|
||||
}
|
||||
http.HandleFunc("/", handler)
|
||||
fmt.Printf("%s: Listening on :%d...\n", name, port)
|
||||
if er := http.ListenAndServe(fmt.Sprintf(":%d", port), nil); er != nil {
|
||||
fmt.Printf("%s: Error from ListenAndServe: %s", name, er.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("%s: How'd we get past listen and serve???\n", name)
|
||||
}
|
||||
122
examples/accessLog/runAb.sh
Executable file
122
examples/accessLog/runAb.sh
Executable file
@@ -0,0 +1,122 @@
|
||||
#!/bin/bash
|
||||
usage()
|
||||
{
|
||||
echo 'runAb.sh - Run Apache Benchmark to test access log'
|
||||
echo ' Usage: runAb.sh [--conn nnn] [--log xxx] [--num nnn] [--time nnn] [--wait nn]'
|
||||
echo ' -c|--conn - number of simultaneous connections (default 100)'
|
||||
echo ' -l|--log - name of logfile (default benchmark.log)'
|
||||
echo ' -n|--num - number of requests (default 50000); ignored when -t specified'
|
||||
echo ' -t|--time - time in seconds for benchmark (default no limit)'
|
||||
echo ' -w|--wait - number of seconds to wait for Traefik to initialize (default 15)'
|
||||
echo ' '
|
||||
exit
|
||||
}
|
||||
|
||||
# Parse options
|
||||
|
||||
conn=100
|
||||
num=50000
|
||||
wait=15
|
||||
time=0
|
||||
logfile=""
|
||||
while [[ $1 =~ ^- ]]
|
||||
do
|
||||
case $1 in
|
||||
-c|--conn)
|
||||
conn=$2
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
;;
|
||||
-l|--log|--logfile)
|
||||
logfile=$2
|
||||
shift
|
||||
;;
|
||||
-n|--num)
|
||||
num=$2
|
||||
shift
|
||||
;;
|
||||
-t|--time)
|
||||
time=$2
|
||||
shift
|
||||
;;
|
||||
-w|--wait)
|
||||
wait=$2
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo Unknown option "$1"
|
||||
usage
|
||||
esac
|
||||
shift
|
||||
done
|
||||
if [ -z "$logfile" ] ; then
|
||||
logfile="benchmark.log"
|
||||
fi
|
||||
|
||||
# Change to accessLog examples directory
|
||||
|
||||
[ -d examples/accessLog ] && cd examples/accessLog
|
||||
if [ ! -r exampleHandler.go ] ; then
|
||||
echo Please run this script either from the traefik repo root or from the examples/accessLog directory
|
||||
exit
|
||||
fi
|
||||
|
||||
# Kill traefik and any running example processes
|
||||
|
||||
sudo pkill -f traefik
|
||||
pkill -f exampleHandler
|
||||
[ ! -d log ] && mkdir log
|
||||
|
||||
# Start new example processes
|
||||
|
||||
go build exampleHandler.go
|
||||
[ $? -ne 0 ] && exit $?
|
||||
./exampleHandler -n Handler1 -p 8081 &
|
||||
[ $? -ne 0 ] && exit $?
|
||||
./exampleHandler -n Handler2 -p 8082 &
|
||||
[ $? -ne 0 ] && exit $?
|
||||
./exampleHandler -n Handler3 -p 8083 &
|
||||
[ $? -ne 0 ] && exit $?
|
||||
|
||||
# Wait a couple of seconds for handlers to initialize and start Traefik
|
||||
|
||||
cd ../..
|
||||
sleep 2s
|
||||
echo Starting Traefik...
|
||||
sudo ./traefik -c examples/accessLog/traefik.ab.toml &
|
||||
[ $? -ne 0 ] && exit $?
|
||||
|
||||
# Wait for Traefik to initialize and run ab
|
||||
|
||||
echo Waiting $wait seconds before starting ab benchmark
|
||||
sleep ${wait}s
|
||||
echo
|
||||
stime=`date '+%s'`
|
||||
if [ $time -eq 0 ] ; then
|
||||
echo Benchmark starting `date` with $conn connections until $num requests processed | tee $logfile
|
||||
echo | tee -a $logfile
|
||||
echo ab -k -c $conn -n $num http://127.0.0.1/test | tee -a $logfile
|
||||
echo | tee -a $logfile
|
||||
ab -k -c $conn -n $num http://127.0.0.1/test 2>&1 | tee -a $logfile
|
||||
else
|
||||
if [ $num -ne 50000 ] ; then
|
||||
echo Request count ignored when --time specified
|
||||
fi
|
||||
echo Benchmark starting `date` with $conn connections for $time seconds | tee $logfile
|
||||
echo | tee -a $logfile
|
||||
echo ab -k -c $conn -t $time -n 100000000 http://127.0.0.1/test | tee -a $logfile
|
||||
echo | tee -a $logfile
|
||||
ab -k -c $conn -t $time -n 100000000 http://127.0.0.1/test 2>&1 | tee -a $logfile
|
||||
fi
|
||||
|
||||
etime=`date '+%s'`
|
||||
let "dt=$etime - $stime"
|
||||
let "ds=$dt % 60"
|
||||
let "dm=($dt / 60) % 60"
|
||||
let "dh=$dt / 3600"
|
||||
echo | tee -a $logfile
|
||||
printf "Benchmark ended `date` after %d:%02d:%02d\n" $dh $dm $ds | tee -a $logfile
|
||||
echo Results available in $logfile
|
||||
|
||||
40
examples/accessLog/runExample.sh
Executable file
40
examples/accessLog/runExample.sh
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
# Script to run a three-server example. This script runs the three servers and restarts Traefik
|
||||
# Once it is running, use the command:
|
||||
#
|
||||
# curl http://127.0.0.1:80/test{1,2,2}
|
||||
#
|
||||
# to send requests to send test requests to the servers. You should see a response like:
|
||||
#
|
||||
# Handler1: received query test1!
|
||||
# Handler2: received query test2!
|
||||
# Handler3: received query test2!
|
||||
#
|
||||
# and can then inspect log/access.log to see frontend, backend, and timing
|
||||
|
||||
# Kill traefik and any running example processes
|
||||
sudo pkill -f traefik
|
||||
pkill -f exampleHandler
|
||||
[ ! -d log ] && mkdir log
|
||||
|
||||
# Start new example processes
|
||||
cd examples/accessLog
|
||||
go build exampleHandler.go
|
||||
[ $? -ne 0 ] && exit $?
|
||||
./exampleHandler -n Handler1 -p 8081 &
|
||||
[ $? -ne 0 ] && exit $?
|
||||
./exampleHandler -n Handler2 -p 8082 &
|
||||
[ $? -ne 0 ] && exit $?
|
||||
./exampleHandler -n Handler3 -p 8083 &
|
||||
[ $? -ne 0 ] && exit $?
|
||||
|
||||
# Wait a couple of seconds for handlers to initialize and start Traefik
|
||||
cd ../..
|
||||
sleep 2s
|
||||
echo Starting Traefik...
|
||||
sudo ./traefik -c examples/accessLog/traefik.example.toml &
|
||||
[ $? -ne 0 ] && exit $?
|
||||
|
||||
echo Sample handlers and traefik started successfully!
|
||||
echo 'Use command curl http://127.0.0.1:80/test{1,2,2} to drive test'
|
||||
echo Then inspect log/access.log to verify it contains frontend, backend, and timing
|
||||
37
examples/accessLog/traefik.ab.toml
Normal file
37
examples/accessLog/traefik.ab.toml
Normal file
@@ -0,0 +1,37 @@
|
||||
################################################################
|
||||
# Global configuration
|
||||
################################################################
|
||||
traefikLogsFile = "log/traefik.log"
|
||||
accessLogsFile = "log/access.log"
|
||||
logLevel = "DEBUG"
|
||||
|
||||
################################################################
|
||||
# Web configuration backend
|
||||
################################################################
|
||||
[web]
|
||||
address = ":7888"
|
||||
|
||||
################################################################
|
||||
# File configuration backend
|
||||
################################################################
|
||||
[file]
|
||||
|
||||
################################################################
|
||||
# rules
|
||||
################################################################
|
||||
[backends]
|
||||
[backends.backend]
|
||||
[backends.backend.LoadBalancer]
|
||||
method = "drr"
|
||||
[backends.backend.servers.server1]
|
||||
url = "http://127.0.0.1:8081"
|
||||
[backends.backend.servers.server2]
|
||||
url = "http://127.0.0.1:8082"
|
||||
[backends.backend.servers.server3]
|
||||
url = "http://127.0.0.1:8083"
|
||||
[frontends]
|
||||
[frontends.frontend]
|
||||
backend = "backend"
|
||||
passHostHeader = true
|
||||
[frontends.frontend.routes.test]
|
||||
rule = "Path: /test"
|
||||
42
examples/accessLog/traefik.example.toml
Normal file
42
examples/accessLog/traefik.example.toml
Normal file
@@ -0,0 +1,42 @@
|
||||
################################################################
|
||||
# Global configuration
|
||||
################################################################
|
||||
traefikLogsFile = "log/traefik.log"
|
||||
accessLogsFile = "log/access.log"
|
||||
logLevel = "DEBUG"
|
||||
|
||||
################################################################
|
||||
# Web configuration backend
|
||||
################################################################
|
||||
[web]
|
||||
address = ":7888"
|
||||
|
||||
################################################################
|
||||
# File configuration backend
|
||||
################################################################
|
||||
[file]
|
||||
|
||||
################################################################
|
||||
# rules
|
||||
################################################################
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://127.0.0.1:8081"
|
||||
[backends.backend2]
|
||||
[backends.backend2.LoadBalancer]
|
||||
method = "drr"
|
||||
[backends.backend2.servers.server1]
|
||||
url = "http://127.0.0.1:8082"
|
||||
[backends.backend2.servers.server2]
|
||||
url = "http://127.0.0.1:8083"
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Path: /test1"
|
||||
[frontends.frontend2]
|
||||
backend = "backend2"
|
||||
passHostHeader = true
|
||||
[frontends.frontend2.routes.test_2]
|
||||
rule = "Path: /test2"
|
||||
4
examples/compose-etcd.yml
Normal file
4
examples/compose-etcd.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
etcd:
|
||||
image: gcr.io/google_containers/etcd:2.2.1
|
||||
net: host
|
||||
command: ['/usr/local/bin/etcd', '--addr=127.0.0.1:4001', '--bind-addr=0.0.0.0:4001', '--data-dir=/var/etcd/data']
|
||||
25
examples/etcd-config.sh
Executable file
25
examples/etcd-config.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
|
||||
# backend 1
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="NetworkErrorRatio() > 0.5" http://localhost:4001/v2/keys/traefik/backends/backend1/circuitbreaker/expression
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.2:80" http://localhost:4001/v2/keys/traefik/backends/backend1/servers/server1/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="10" http://localhost:4001/v2/keys/traefik/backends/backend1/servers/server1/weight
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.3:80" http://localhost:4001/v2/keys/traefik/backends/backend1/servers/server2/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="1" http://localhost:4001/v2/keys/traefik/backends/backend1/servers/server2/weight
|
||||
|
||||
# backend 2
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="drr" http://localhost:4001/v2/keys/traefik/backends/backend2/loadbalancer/method
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.4:80" http://localhost:4001/v2/keys/traefik/backends/backend2/servers/server1/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="1" http://localhost:4001/v2/keys/traefik/backends/backend2/servers/server1/weight
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.5:80" http://localhost:4001/v2/keys/traefik/backends/backend2/servers/server2/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="2" http://localhost:4001/v2/keys/traefik/backends/backend2/servers/server2/weight
|
||||
|
||||
# frontend 1
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="backend2" http://localhost:4001/v2/keys/traefik/frontends/frontend1/backend
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http" http://localhost:4001/v2/keys/traefik/frontends/frontend1/entrypoints
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="Host:test.localhost" http://localhost:4001/v2/keys/traefik/frontends/frontend1/routes/test_1/rule
|
||||
|
||||
# frontend 2
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="backend1" http://localhost:4001/v2/keys/traefik/frontends/frontend2/backend
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http" http://localhost:4001/v2/keys/traefik/frontends/frontend2/entrypoints
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="Path:/test" http://localhost:4001/v2/keys/traefik/frontends/frontend2/routes/test_2/rule
|
||||
@@ -3,6 +3,7 @@ Copyright
|
||||
*/
|
||||
|
||||
//go:generate rm -vf autogen/gen.go
|
||||
//go:generate mkdir -p static
|
||||
//go:generate go-bindata -pkg autogen -o autogen/gen.go ./static/... ./templates/...
|
||||
|
||||
//go:generate mkdir -p vendor/github.com/docker/docker/autogen/dockerversion
|
||||
|
||||
32
glide.lock
generated
32
glide.lock
generated
@@ -1,5 +1,5 @@
|
||||
hash: e92948ce12f546d39a02c2e58668f7d12d7b1f3dd56eb046e01b527df756f734
|
||||
updated: 2016-04-26T23:18:15.861898862+02:00
|
||||
hash: a9f41b9fe89ac3028da27ac9cbe31db9a79ae89082f42507d4d0c58290517ee2
|
||||
updated: 2016-04-27T17:14:45.61228359Z
|
||||
imports:
|
||||
- name: github.com/alecthomas/template
|
||||
version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
|
||||
@@ -100,7 +100,7 @@ imports:
|
||||
- types/time
|
||||
- types/blkiodev
|
||||
- name: github.com/docker/go-connections
|
||||
version: 5b7154ba2efe13ff86ae8830a9e7cb120b080d6e
|
||||
version: f549a9393d05688dff0992ef3efd8bbe6c628aeb
|
||||
subpackages:
|
||||
- nat
|
||||
- sockets
|
||||
@@ -121,6 +121,12 @@ imports:
|
||||
version: 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
||||
- name: github.com/donovanhide/eventsource
|
||||
version: d8a3071799b98cacd30b6da92f536050ccfe6da4
|
||||
- name: github.com/eapache/go-resiliency
|
||||
version: b86b1ec0dd4209a588dc1285cdd471e73525c0b3
|
||||
subpackages:
|
||||
- breaker
|
||||
- name: github.com/eapache/queue
|
||||
version: ded5959c0d4e360646dc9e9908cff48666781367
|
||||
- name: github.com/elazarl/go-bindata-assetfs
|
||||
version: d5cac425555ca5cf00694df246e04f05e6a55150
|
||||
- name: github.com/flynn/go-shlex
|
||||
@@ -131,6 +137,8 @@ imports:
|
||||
version: 11d3bc7aa68e238947792f30573146a3231fc0f1
|
||||
- name: github.com/golang/glog
|
||||
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
|
||||
- name: github.com/golang/snappy
|
||||
version: ec642410cd033af63620b66a91ccbd3c69c2c59a
|
||||
- name: github.com/google/go-querystring
|
||||
version: 9235644dd9e52eeae6fa48efd539fdc351a0af53
|
||||
subpackages:
|
||||
@@ -148,7 +156,7 @@ imports:
|
||||
subpackages:
|
||||
- api
|
||||
- name: github.com/hashicorp/hcl
|
||||
version: 27a57f2605e04995c111273c263d51cee60d9bc4
|
||||
version: 2604f3bda7e8960c1be1063709e7d7f0765048d0
|
||||
subpackages:
|
||||
- hcl/ast
|
||||
- hcl/parser
|
||||
@@ -160,6 +168,8 @@ imports:
|
||||
- json/token
|
||||
- name: github.com/inconshreveable/mousetrap
|
||||
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
||||
- name: github.com/klauspost/crc32
|
||||
version: 19b0b332c9e4516a6370a0456e6182c3b5036720
|
||||
- name: github.com/kr/pretty
|
||||
version: add1dbc86daf0f983cd4a48ceb39deb95c729b67
|
||||
- name: github.com/kr/text
|
||||
@@ -174,10 +184,12 @@ imports:
|
||||
version: 565402cd71fbd9c12aa7e295324ea357e970a61e
|
||||
- name: github.com/mailgun/timetools
|
||||
version: fd192d755b00c968d312d23f521eb0cdc6f66bd0
|
||||
- name: github.com/mattn/go-shellwords
|
||||
version: 525bedee691b5a8df547cb5cf9f86b7fb1883e24
|
||||
- name: github.com/Microsoft/go-winio
|
||||
version: 862b6557927a5c5c81e411c12aa6de7e566cbb7a
|
||||
- name: github.com/miekg/dns
|
||||
version: a5cc44dc6b2eee8eddfd6581e1c6bb753ff0d176
|
||||
version: dd83d5cbcfd986f334b2747feeb907e281318fdf
|
||||
- name: github.com/mitchellh/mapstructure
|
||||
version: d2dd0262208475919e1a362f675cfc0e7c10e905
|
||||
- name: github.com/moul/http2curl
|
||||
@@ -196,6 +208,8 @@ imports:
|
||||
version: fa6674abf3f4580b946a01bf7a1ce4ba8766205b
|
||||
subpackages:
|
||||
- zk
|
||||
- name: github.com/Shopify/sarama
|
||||
version: 92a286e4dde1688175cff3d2ec9b49a02838b447
|
||||
- name: github.com/Sirupsen/logrus
|
||||
version: 418b41d23a1bf978c06faea5313ba194650ac088
|
||||
- name: github.com/spf13/cast
|
||||
@@ -207,7 +221,7 @@ imports:
|
||||
- name: github.com/spf13/jwalterweatherman
|
||||
version: 33c24e77fb80341fe7130ee7c594256ff08ccc46
|
||||
- name: github.com/spf13/pflag
|
||||
version: 8f6a28b0916586e7f22fe931ae2fcfc380b1c0e6
|
||||
version: 1f296710f879815ad9e6d39d947c828c3e4b4c3d
|
||||
- name: github.com/spf13/viper
|
||||
version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325
|
||||
- name: github.com/streamrail/concurrent-map
|
||||
@@ -228,7 +242,7 @@ imports:
|
||||
- name: github.com/unrolled/render
|
||||
version: 26b4e3aac686940fe29521545afad9966ddfc80c
|
||||
- name: github.com/vdemeester/docker-events
|
||||
version: 1ecaca5890ef1ffd266fcbfdbe43073ef105704b
|
||||
version: 6ea3f28df37f29a47498bc8b32b36ad8491dbd37
|
||||
- name: github.com/vdemeester/libkermit
|
||||
version: 7e4e689a6fa9281e0fb9b7b9c297e22d5342a5ec
|
||||
- name: github.com/vdemeester/shakers
|
||||
@@ -251,11 +265,11 @@ imports:
|
||||
- name: github.com/wendal/errors
|
||||
version: f66c77a7882b399795a8987ebf87ef64a427417e
|
||||
- name: github.com/xenolf/lego
|
||||
version: 684400fe76a813e78d87803a62bc04d977c501d2
|
||||
version: 23e88185c255e95a106835d80e76e5a3a66d7c54
|
||||
subpackages:
|
||||
- acme
|
||||
- name: golang.org/x/crypto
|
||||
version: 1777f3ba8c1fed80fcaec3317e3aaa4f627764d2
|
||||
version: d68c3ecb62c850b645dc072a8d78006286bf81ca
|
||||
subpackages:
|
||||
- ocsp
|
||||
- name: golang.org/x/net
|
||||
|
||||
@@ -183,3 +183,5 @@ import:
|
||||
- package: github.com/mailgun/multibuf
|
||||
- package: github.com/streamrail/concurrent-map
|
||||
- package: github.com/parnurzeal/gorequest
|
||||
- package: github.com/mattn/go-shellwords
|
||||
- package: github.com/moul/http2curl
|
||||
|
||||
106
integration/access_log_test.go
Normal file
106
integration/access_log_test.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-check/check"
|
||||
shellwords "github.com/mattn/go-shellwords"
|
||||
|
||||
checker "github.com/vdemeester/shakers"
|
||||
)
|
||||
|
||||
// AccessLogSuite
|
||||
type AccessLogSuite struct{ BaseSuite }
|
||||
|
||||
func (s *AccessLogSuite) TestAccessLog(c *check.C) {
|
||||
// Ensure working directory is clean
|
||||
os.Remove("access.log")
|
||||
os.Remove("traefik.log")
|
||||
|
||||
// Start Traefik
|
||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/access_log_config.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
defer os.Remove("access.log")
|
||||
defer os.Remove("traefik.log")
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
// Verify Traefik started OK
|
||||
traefikLog, err := ioutil.ReadFile("traefik.log")
|
||||
c.Assert(err, checker.IsNil)
|
||||
if len(traefikLog) > 0 {
|
||||
fmt.Printf("%s\n", string(traefikLog))
|
||||
c.Assert(len(traefikLog), checker.Equals, 0)
|
||||
}
|
||||
|
||||
// Start test servers
|
||||
ts1 := startAccessLogServer(8081)
|
||||
defer ts1.Close()
|
||||
ts2 := startAccessLogServer(8082)
|
||||
defer ts2.Close()
|
||||
ts3 := startAccessLogServer(8083)
|
||||
defer ts3.Close()
|
||||
|
||||
// Make some requests
|
||||
_, err = http.Get("http://127.0.0.1:8000/test1")
|
||||
c.Assert(err, checker.IsNil)
|
||||
_, err = http.Get("http://127.0.0.1:8000/test2")
|
||||
c.Assert(err, checker.IsNil)
|
||||
_, err = http.Get("http://127.0.0.1:8000/test2")
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Verify access.log output as expected
|
||||
accessLog, err := ioutil.ReadFile("access.log")
|
||||
c.Assert(err, checker.IsNil)
|
||||
lines := strings.Split(string(accessLog), "\n")
|
||||
count := 0
|
||||
for i, line := range lines {
|
||||
if len(line) > 0 {
|
||||
count++
|
||||
tokens, err := shellwords.Parse(line)
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(len(tokens), checker.Equals, 13)
|
||||
c.Assert(tokens[6], checker.Equals, "200")
|
||||
c.Assert(tokens[9], checker.Equals, fmt.Sprintf("%d", i+1))
|
||||
c.Assert(strings.HasPrefix(tokens[10], "frontend"), checker.True)
|
||||
c.Assert(strings.HasPrefix(tokens[11], "http://127.0.0.1:808"), checker.True)
|
||||
c.Assert(regexp.MustCompile("^\\d+\\.\\d+.*s$").MatchString(tokens[12]), checker.True)
|
||||
}
|
||||
}
|
||||
c.Assert(count, checker.Equals, 3)
|
||||
|
||||
// Verify no other Traefik problems
|
||||
traefikLog, err = ioutil.ReadFile("traefik.log")
|
||||
c.Assert(err, checker.IsNil)
|
||||
if len(traefikLog) > 0 {
|
||||
fmt.Printf("%s\n", string(traefikLog))
|
||||
c.Assert(len(traefikLog), checker.Equals, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func startAccessLogServer(port int) (ts *httptest.Server) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Received query %s!\n", r.URL.Path[1:])
|
||||
})
|
||||
if listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
ts = &httptest.Server{
|
||||
Listener: listener,
|
||||
Config: &http.Server{Handler: handler},
|
||||
}
|
||||
ts.Start()
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -5,29 +5,186 @@ import (
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/docker/libkv"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/consul"
|
||||
"github.com/go-check/check"
|
||||
|
||||
"errors"
|
||||
"github.com/containous/traefik/integration/utils"
|
||||
checker "github.com/vdemeester/shakers"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Consul test suites (using libcompose)
|
||||
type ConsulSuite struct{ BaseSuite }
|
||||
type ConsulSuite struct {
|
||||
BaseSuite
|
||||
kv store.Store
|
||||
}
|
||||
|
||||
func (s *ConsulSuite) SetUpSuite(c *check.C) {
|
||||
s.createComposeProject(c, "consul")
|
||||
s.composeProject.Start(c)
|
||||
|
||||
consul.Register()
|
||||
kv, err := libkv.NewStore(
|
||||
store.CONSUL,
|
||||
[]string{s.composeProject.Container(c, "consul").NetworkSettings.IPAddress + ":8500"},
|
||||
&store.Config{
|
||||
ConnectionTimeout: 10 * time.Second,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
c.Fatal("Cannot create store consul")
|
||||
}
|
||||
s.kv = kv
|
||||
|
||||
// wait for consul
|
||||
err = utils.Try(60*time.Second, func() error {
|
||||
_, err := kv.Exists("test")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/consul/simple.toml")
|
||||
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
|
||||
file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost})
|
||||
defer os.Remove(file)
|
||||
cmd := exec.Command(traefikBinary, "--configFile="+file)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
// TODO validate : run on 80
|
||||
resp, err := http.Get("http://127.0.0.1:8000/")
|
||||
|
||||
// Expected a 404 as we did not configure anything
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
||||
|
||||
func (s *ConsulSuite) TestNominalConfiguration(c *check.C) {
|
||||
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
|
||||
file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost})
|
||||
defer os.Remove(file)
|
||||
cmd := exec.Command(traefikBinary, "--configFile="+file)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
whoami1 := s.composeProject.Container(c, "whoami1")
|
||||
whoami2 := s.composeProject.Container(c, "whoami2")
|
||||
whoami3 := s.composeProject.Container(c, "whoami3")
|
||||
whoami4 := s.composeProject.Container(c, "whoami4")
|
||||
|
||||
backend1 := map[string]string{
|
||||
"traefik/backends/backend1/circuitbreaker/expression": "NetworkErrorRatio() > 0.5",
|
||||
"traefik/backends/backend1/servers/server1/url": "http://" + whoami1.NetworkSettings.IPAddress + ":80",
|
||||
"traefik/backends/backend1/servers/server1/weight": "10",
|
||||
"traefik/backends/backend1/servers/server2/url": "http://" + whoami2.NetworkSettings.IPAddress + ":80",
|
||||
"traefik/backends/backend1/servers/server2/weight": "1",
|
||||
}
|
||||
backend2 := map[string]string{
|
||||
"traefik/backends/backend2/loadbalancer/method": "drr",
|
||||
"traefik/backends/backend2/servers/server1/url": "http://" + whoami3.NetworkSettings.IPAddress + ":80",
|
||||
"traefik/backends/backend2/servers/server1/weight": "1",
|
||||
"traefik/backends/backend2/servers/server2/url": "http://" + whoami4.NetworkSettings.IPAddress + ":80",
|
||||
"traefik/backends/backend2/servers/server2/weight": "2",
|
||||
}
|
||||
frontend1 := map[string]string{
|
||||
"traefik/frontends/frontend1/backend": "backend2",
|
||||
"traefik/frontends/frontend1/entrypoints": "http",
|
||||
"traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost",
|
||||
}
|
||||
frontend2 := map[string]string{
|
||||
"traefik/frontends/frontend2/backend": "backend1",
|
||||
"traefik/frontends/frontend2/entrypoints": "http",
|
||||
"traefik/frontends/frontend2/routes/test_2/rule": "Path:/test",
|
||||
}
|
||||
for key, value := range backend1 {
|
||||
err := s.kv.Put(key, []byte(value), nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
for key, value := range backend2 {
|
||||
err := s.kv.Put(key, []byte(value), nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
for key, value := range frontend1 {
|
||||
err := s.kv.Put(key, []byte(value), nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
for key, value := range frontend2 {
|
||||
err := s.kv.Put(key, []byte(value), nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
// wait for consul
|
||||
err = utils.Try(60*time.Second, func() error {
|
||||
_, err := s.kv.Exists("traefik/frontends/frontend2/routes/test_2/rule")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// wait for traefik
|
||||
err = utils.TryRequest("http://127.0.0.1:8081/api/providers", 60*time.Second, func(res *http.Response) error {
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.Contains(string(body), "Path:/test") {
|
||||
return errors.New("Incorrect traefik config")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.localhost"
|
||||
response, err := client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(response.StatusCode, checker.Equals, 200)
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, checker.IsNil)
|
||||
if !strings.Contains(string(body), whoami3.NetworkSettings.IPAddress) &&
|
||||
!strings.Contains(string(body), whoami4.NetworkSettings.IPAddress) {
|
||||
c.Fail()
|
||||
}
|
||||
|
||||
req, err = http.NewRequest("GET", "http://127.0.0.1:8000/test", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
response, err = client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(response.StatusCode, checker.Equals, 200)
|
||||
|
||||
body, err = ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, checker.IsNil)
|
||||
if !strings.Contains(string(body), whoami1.NetworkSettings.IPAddress) &&
|
||||
!strings.Contains(string(body), whoami2.NetworkSettings.IPAddress) {
|
||||
c.Fail()
|
||||
}
|
||||
|
||||
req, err = http.NewRequest("GET", "http://127.0.0.1:8000/test2", nil)
|
||||
resp, err := client.Do(req)
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
|
||||
req, err = http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
req.Host = "test2.localhost"
|
||||
resp, err = client.Do(req)
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
||||
|
||||
@@ -8,17 +8,58 @@ import (
|
||||
"github.com/go-check/check"
|
||||
|
||||
checker "github.com/vdemeester/shakers"
|
||||
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/containous/traefik/integration/utils"
|
||||
"github.com/docker/libkv"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/etcd"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Etcd test suites (using libcompose)
|
||||
type EtcdSuite struct{ BaseSuite }
|
||||
type EtcdSuite struct {
|
||||
BaseSuite
|
||||
kv store.Store
|
||||
}
|
||||
|
||||
func (s *EtcdSuite) SetUpSuite(c *check.C) {
|
||||
s.createComposeProject(c, "etcd")
|
||||
s.composeProject.Start(c)
|
||||
|
||||
etcd.Register()
|
||||
url := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress + ":4001"
|
||||
kv, err := libkv.NewStore(
|
||||
store.ETCD,
|
||||
[]string{url},
|
||||
&store.Config{
|
||||
ConnectionTimeout: 10 * time.Second,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
c.Fatal("Cannot create store etcd")
|
||||
}
|
||||
s.kv = kv
|
||||
|
||||
// wait for etcd
|
||||
err = utils.Try(60*time.Second, func() error {
|
||||
_, err := kv.Exists("test")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Etcd connection error to %s: %v", url, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/etcd/simple.toml")
|
||||
etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress
|
||||
file := s.adaptFile(c, "fixtures/etcd/simple.toml", struct{ EtcdHost string }{etcdHost})
|
||||
defer os.Remove(file)
|
||||
cmd := exec.Command(traefikBinary, "--configFile="+file)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
@@ -31,3 +72,123 @@ func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) {
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
||||
|
||||
func (s *EtcdSuite) TestNominalConfiguration(c *check.C) {
|
||||
etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress
|
||||
file := s.adaptFile(c, "fixtures/etcd/simple.toml", struct{ EtcdHost string }{etcdHost})
|
||||
defer os.Remove(file)
|
||||
cmd := exec.Command(traefikBinary, "--configFile="+file)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
whoami1 := s.composeProject.Container(c, "whoami1")
|
||||
whoami2 := s.composeProject.Container(c, "whoami2")
|
||||
whoami3 := s.composeProject.Container(c, "whoami3")
|
||||
whoami4 := s.composeProject.Container(c, "whoami4")
|
||||
|
||||
backend1 := map[string]string{
|
||||
"/traefik/backends/backend1/circuitbreaker/expression": "NetworkErrorRatio() > 0.5",
|
||||
"/traefik/backends/backend1/servers/server1/url": "http://" + whoami1.NetworkSettings.IPAddress + ":80",
|
||||
"/traefik/backends/backend1/servers/server1/weight": "10",
|
||||
"/traefik/backends/backend1/servers/server2/url": "http://" + whoami2.NetworkSettings.IPAddress + ":80",
|
||||
"/traefik/backends/backend1/servers/server2/weight": "1",
|
||||
}
|
||||
backend2 := map[string]string{
|
||||
"/traefik/backends/backend2/loadbalancer/method": "drr",
|
||||
"/traefik/backends/backend2/servers/server1/url": "http://" + whoami3.NetworkSettings.IPAddress + ":80",
|
||||
"/traefik/backends/backend2/servers/server1/weight": "1",
|
||||
"/traefik/backends/backend2/servers/server2/url": "http://" + whoami4.NetworkSettings.IPAddress + ":80",
|
||||
"/traefik/backends/backend2/servers/server2/weight": "2",
|
||||
}
|
||||
frontend1 := map[string]string{
|
||||
"/traefik/frontends/frontend1/backend": "backend2",
|
||||
"/traefik/frontends/frontend1/entrypoints": "http",
|
||||
"/traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost",
|
||||
}
|
||||
frontend2 := map[string]string{
|
||||
"/traefik/frontends/frontend2/backend": "backend1",
|
||||
"/traefik/frontends/frontend2/entrypoints": "http",
|
||||
"/traefik/frontends/frontend2/routes/test_2/rule": "Path:/test",
|
||||
}
|
||||
for key, value := range backend1 {
|
||||
err := s.kv.Put(key, []byte(value), nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
for key, value := range backend2 {
|
||||
err := s.kv.Put(key, []byte(value), nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
for key, value := range frontend1 {
|
||||
err := s.kv.Put(key, []byte(value), nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
for key, value := range frontend2 {
|
||||
err := s.kv.Put(key, []byte(value), nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
// wait for etcd
|
||||
err = utils.Try(60*time.Second, func() error {
|
||||
_, err := s.kv.Exists("/traefik/frontends/frontend2/routes/test_2/rule")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// wait for traefik
|
||||
err = utils.TryRequest("http://127.0.0.1:8081/api/providers", 60*time.Second, func(res *http.Response) error {
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.Contains(string(body), "Path:/test") {
|
||||
return errors.New("Incorrect traefik config")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.localhost"
|
||||
response, err := client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(response.StatusCode, checker.Equals, 200)
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, checker.IsNil)
|
||||
if !strings.Contains(string(body), whoami3.NetworkSettings.IPAddress) &&
|
||||
!strings.Contains(string(body), whoami4.NetworkSettings.IPAddress) {
|
||||
c.Fail()
|
||||
}
|
||||
|
||||
req, err = http.NewRequest("GET", "http://127.0.0.1:8000/test", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
response, err = client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(response.StatusCode, checker.Equals, 200)
|
||||
|
||||
body, err = ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, checker.IsNil)
|
||||
if !strings.Contains(string(body), whoami1.NetworkSettings.IPAddress) &&
|
||||
!strings.Contains(string(body), whoami2.NetworkSettings.IPAddress) {
|
||||
c.Fail()
|
||||
}
|
||||
|
||||
req, err = http.NewRequest("GET", "http://127.0.0.1:8000/test2", nil)
|
||||
req.Host = "test2.localhost"
|
||||
resp, err := client.Do(req)
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
|
||||
req, err = http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
resp, err = client.Do(req)
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
||||
|
||||
46
integration/fixtures/access_log_config.toml
Normal file
46
integration/fixtures/access_log_config.toml
Normal file
@@ -0,0 +1,46 @@
|
||||
################################################################
|
||||
# Global configuration
|
||||
################################################################
|
||||
traefikLogsFile = "traefik.log"
|
||||
accessLogsFile = "access.log"
|
||||
logLevel = "ERROR"
|
||||
defaultEntryPoints = ["http"]
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":8000"
|
||||
|
||||
################################################################
|
||||
# Web configuration backend
|
||||
################################################################
|
||||
[web]
|
||||
address = ":7888"
|
||||
|
||||
################################################################
|
||||
# File configuration backend
|
||||
################################################################
|
||||
[file]
|
||||
|
||||
################################################################
|
||||
# rules
|
||||
################################################################
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://127.0.0.1:8081"
|
||||
[backends.backend2]
|
||||
[backends.backend2.LoadBalancer]
|
||||
method = "drr"
|
||||
[backends.backend2.servers.server1]
|
||||
url = "http://127.0.0.1:8082"
|
||||
[backends.backend2.servers.server2]
|
||||
url = "http://127.0.0.1:8083"
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Path: /test1"
|
||||
[frontends.frontend2]
|
||||
backend = "backend2"
|
||||
passHostHeader = true
|
||||
[frontends.frontend2.routes.test_2]
|
||||
rule = "Path: /test2"
|
||||
@@ -1,9 +1,16 @@
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":8000"
|
||||
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[consul]
|
||||
endpoint = "{{.ConsulHost}}:8500"
|
||||
watch = true
|
||||
prefix = "traefik"
|
||||
|
||||
[web]
|
||||
address = ":8081"
|
||||
@@ -1,11 +1,11 @@
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":8000"
|
||||
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[docker]
|
||||
|
||||
# It's dynamagic !
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":8000"
|
||||
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[etcd]
|
||||
endpoint = "127.0.0.1:4003,127.0.0.1:4002,127.0.0.1:4001"
|
||||
endpoint = "{{.EtcdHost}}:4001"
|
||||
prefix = "/traefik"
|
||||
watch = true
|
||||
|
||||
[web]
|
||||
address = ":8081"
|
||||
@@ -23,6 +23,7 @@ func Test(t *testing.T) {
|
||||
|
||||
func init() {
|
||||
check.Suite(&SimpleSuite{})
|
||||
check.Suite(&AccessLogSuite{})
|
||||
check.Suite(&HTTPSSuite{})
|
||||
check.Suite(&FileSuite{})
|
||||
check.Suite(&DockerSuite{})
|
||||
@@ -63,7 +64,11 @@ func (s *BaseSuite) adaptFileForHost(c *check.C, path string) string {
|
||||
// Default docker socket
|
||||
dockerHost = "unix:///var/run/docker.sock"
|
||||
}
|
||||
tempObjects := struct{ DockerHost string }{dockerHost}
|
||||
return s.adaptFile(c, path, tempObjects)
|
||||
}
|
||||
|
||||
func (s *BaseSuite) adaptFile(c *check.C, path string, tempObjects interface{}) string {
|
||||
// Load file
|
||||
tmpl, err := template.ParseFiles(path)
|
||||
c.Assert(err, checker.IsNil)
|
||||
@@ -73,7 +78,7 @@ func (s *BaseSuite) adaptFileForHost(c *check.C, path string) string {
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer tmpFile.Close()
|
||||
|
||||
err = tmpl.ExecuteTemplate(tmpFile, prefix, struct{ DockerHost string }{dockerHost})
|
||||
err = tmpl.ExecuteTemplate(tmpFile, prefix, tempObjects)
|
||||
c.Assert(err, checker.IsNil)
|
||||
err = tmpFile.Sync()
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
consul:
|
||||
image: progrium/consul
|
||||
command: -server -bootstrap -advertise 12.0.0.254 -log-level debug -ui-dir /ui
|
||||
command: -server -bootstrap -log-level debug -ui-dir /ui
|
||||
ports:
|
||||
- "8400:8400"
|
||||
- "8500:8500"
|
||||
@@ -10,4 +10,16 @@ consul:
|
||||
- "8301"
|
||||
- "8301/udp"
|
||||
- "8302"
|
||||
- "8302/udp"
|
||||
- "8302/udp"
|
||||
|
||||
whoami1:
|
||||
image: emilevauge/whoami
|
||||
|
||||
whoami2:
|
||||
image: emilevauge/whoami
|
||||
|
||||
whoami3:
|
||||
image: emilevauge/whoami
|
||||
|
||||
whoami4:
|
||||
image: emilevauge/whoami
|
||||
@@ -1,30 +1,14 @@
|
||||
etcd1:
|
||||
image: quay.io/coreos/etcd:v2.2.0
|
||||
net: "host"
|
||||
command: >
|
||||
--name etcd1
|
||||
--listen-peer-urls http://localhost:7001
|
||||
--listen-client-urls http://localhost:4001
|
||||
--initial-advertise-peer-urls http://localhost:7001
|
||||
--advertise-client-urls http://localhost:4001
|
||||
--initial-cluster etcd1=http://localhost:7001,etcd2=http://localhost:7002,etcd3=http://localhost:7003
|
||||
etcd2:
|
||||
image: quay.io/coreos/etcd:v2.2.0
|
||||
net: "host"
|
||||
command: >
|
||||
--name etcd2
|
||||
--listen-peer-urls http://localhost:7002
|
||||
--listen-client-urls http://localhost:4002
|
||||
--initial-advertise-peer-urls http://localhost:7002
|
||||
--advertise-client-urls http://localhost:4002
|
||||
--initial-cluster etcd1=http://localhost:7001,etcd2=http://localhost:7002,etcd3=http://localhost:7003
|
||||
etcd3:
|
||||
image: quay.io/coreos/etcd:v2.2.0
|
||||
net: "host"
|
||||
command: >
|
||||
--name etcd3
|
||||
--listen-peer-urls http://localhost:7003
|
||||
--listen-client-urls http://localhost:4003
|
||||
--initial-advertise-peer-urls http://localhost:7003
|
||||
--advertise-client-urls http://localhost:4003
|
||||
--initial-cluster etcd1=http://localhost:7001,etcd2=http://localhost:7002,etcd3=http://localhost:7003
|
||||
etcd:
|
||||
image: containous/docker-etcd
|
||||
|
||||
whoami1:
|
||||
image: emilevauge/whoami
|
||||
|
||||
whoami2:
|
||||
image: emilevauge/whoami
|
||||
|
||||
whoami3:
|
||||
image: emilevauge/whoami
|
||||
|
||||
whoami4:
|
||||
image: emilevauge/whoami
|
||||
50
integration/utils/try.go
Normal file
50
integration/utils/try.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/cenkalti/backoff"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TryRequest try operation timeout, and retry backoff
|
||||
func TryRequest(url string, timeout time.Duration, condition Condition) error {
|
||||
exponentialBackOff := backoff.NewExponentialBackOff()
|
||||
exponentialBackOff.MaxElapsedTime = timeout
|
||||
var res *http.Response
|
||||
err := backoff.Retry(func() error {
|
||||
var err error
|
||||
res, err = http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return condition(res)
|
||||
}, exponentialBackOff)
|
||||
return err
|
||||
}
|
||||
|
||||
// Try try operation timeout, and retry backoff
|
||||
func Try(timeout time.Duration, operation func() error) error {
|
||||
exponentialBackOff := backoff.NewExponentialBackOff()
|
||||
exponentialBackOff.MaxElapsedTime = timeout
|
||||
err := backoff.Retry(operation, exponentialBackOff)
|
||||
return err
|
||||
}
|
||||
|
||||
// Condition is a retry condition function.
|
||||
// It receives a response, and returns an error
|
||||
// if the response failed the condition.
|
||||
type Condition func(*http.Response) error
|
||||
|
||||
// ErrorIfStatusCodeIsNot returns a retry condition function.
|
||||
// The condition returns an error
|
||||
// if the given response's status code is not the given HTTP status code.
|
||||
func ErrorIfStatusCodeIsNot(status int) Condition {
|
||||
return func(res *http.Response) error {
|
||||
if res.StatusCode != status {
|
||||
return errors.New("Bad status. Got: " + res.Status + ", expected:" + strconv.Itoa(status))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,54 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"log"
|
||||
"fmt"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/streamrail/concurrent-map"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gorilla/handlers"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Logger is a middleware handler that logs the request as it goes in and the response as it goes out.
|
||||
const (
|
||||
loggerReqidHeader = "X-Traefik-Reqid"
|
||||
)
|
||||
|
||||
/*
|
||||
Logger writes each request and its response to the access log.
|
||||
It gets some information from the logInfoResponseWriter set up by previous middleware.
|
||||
*/
|
||||
type Logger struct {
|
||||
file *os.File
|
||||
}
|
||||
|
||||
// Logging handler to log frontend name, backend name, and elapsed time
|
||||
type frontendBackendLoggingHandler struct {
|
||||
reqid string
|
||||
writer io.Writer
|
||||
handlerFunc http.HandlerFunc
|
||||
}
|
||||
|
||||
var (
|
||||
reqidCounter uint64 // Request ID
|
||||
infoRwMap = cmap.New() // Map of reqid to response writer
|
||||
backend2FrontendMap *map[string]string
|
||||
)
|
||||
|
||||
// logInfoResponseWriter is a wrapper of type http.ResponseWriter
|
||||
// that tracks frontend and backend names and request status and size
|
||||
type logInfoResponseWriter struct {
|
||||
rw http.ResponseWriter
|
||||
backend string
|
||||
frontend string
|
||||
status int
|
||||
size int
|
||||
}
|
||||
|
||||
// NewLogger returns a new Logger instance.
|
||||
func NewLogger(file string) *Logger {
|
||||
if len(file) > 0 {
|
||||
@@ -25,17 +61,132 @@ func NewLogger(file string) *Logger {
|
||||
return &Logger{nil}
|
||||
}
|
||||
|
||||
// SetBackend2FrontendMap is called by server.go to set up frontend translation
|
||||
func SetBackend2FrontendMap(newMap *map[string]string) {
|
||||
backend2FrontendMap = newMap
|
||||
}
|
||||
|
||||
func (l *Logger) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
if l.file == nil {
|
||||
next(rw, r)
|
||||
} else {
|
||||
handlers.CombinedLoggingHandler(l.file, next).ServeHTTP(rw, r)
|
||||
reqid := strconv.FormatUint(atomic.AddUint64(&reqidCounter, 1), 10)
|
||||
r.Header[loggerReqidHeader] = []string{reqid}
|
||||
defer deleteReqid(r, reqid)
|
||||
frontendBackendLoggingHandler{reqid, l.file, next}.ServeHTTP(rw, r)
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the logger (i.e. the file).
|
||||
// Delete a reqid from the map and the request's headers
|
||||
func deleteReqid(r *http.Request, reqid string) {
|
||||
infoRwMap.Remove(reqid)
|
||||
delete(r.Header, loggerReqidHeader)
|
||||
}
|
||||
|
||||
// Save the backend name for the Logger
|
||||
func saveBackendNameForLogger(r *http.Request, backendName string) {
|
||||
if reqidHdr := r.Header[loggerReqidHeader]; len(reqidHdr) == 1 {
|
||||
reqid := reqidHdr[0]
|
||||
if infoRw, ok := infoRwMap.Get(reqid); ok {
|
||||
infoRw.(*logInfoResponseWriter).SetBackend(backendName)
|
||||
infoRw.(*logInfoResponseWriter).SetFrontend((*backend2FrontendMap)[backendName])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the Logger (i.e. the file).
|
||||
func (l *Logger) Close() {
|
||||
if l.file != nil {
|
||||
l.file.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Logging handler to log frontend name, backend name, and elapsed time
|
||||
func (fblh frontendBackendLoggingHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
startTime := time.Now()
|
||||
infoRw := &logInfoResponseWriter{rw: rw}
|
||||
infoRwMap.Set(fblh.reqid, infoRw)
|
||||
fblh.handlerFunc(infoRw, req)
|
||||
|
||||
username := "-"
|
||||
url := *req.URL
|
||||
if url.User != nil {
|
||||
if name := url.User.Username(); name != "" {
|
||||
username = name
|
||||
}
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||
if err != nil {
|
||||
host = req.RemoteAddr
|
||||
}
|
||||
|
||||
ts := startTime.Format("02/Jan/2006:15:04:05 -0700")
|
||||
method := req.Method
|
||||
uri := url.RequestURI()
|
||||
if qmIndex := strings.Index(uri, "?"); qmIndex > 0 {
|
||||
uri = uri[0:qmIndex]
|
||||
}
|
||||
proto := req.Proto
|
||||
referer := req.Referer()
|
||||
agent := req.UserAgent()
|
||||
|
||||
frontend := strings.TrimPrefix(infoRw.GetFrontend(), "frontend-")
|
||||
backend := infoRw.GetBackend()
|
||||
status := infoRw.GetStatus()
|
||||
size := infoRw.GetSize()
|
||||
|
||||
elapsed := time.Now().UTC().Sub(startTime.UTC())
|
||||
fmt.Fprintf(fblh.writer, `%s - %s [%s] "%s %s %s" %d %d "%s" "%s" %s "%s" "%s" %s%s`,
|
||||
host, username, ts, method, uri, proto, status, size, referer, agent, fblh.reqid, frontend, backend, elapsed, "\n")
|
||||
|
||||
}
|
||||
|
||||
func (lirw *logInfoResponseWriter) Header() http.Header {
|
||||
return lirw.rw.Header()
|
||||
}
|
||||
|
||||
func (lirw *logInfoResponseWriter) Write(b []byte) (int, error) {
|
||||
if lirw.status == 0 {
|
||||
lirw.status = http.StatusOK
|
||||
}
|
||||
size, err := lirw.rw.Write(b)
|
||||
lirw.size += size
|
||||
return size, err
|
||||
}
|
||||
|
||||
func (lirw *logInfoResponseWriter) WriteHeader(s int) {
|
||||
lirw.rw.WriteHeader(s)
|
||||
lirw.status = s
|
||||
}
|
||||
|
||||
func (lirw *logInfoResponseWriter) Flush() {
|
||||
f, ok := lirw.rw.(http.Flusher)
|
||||
if ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func (lirw *logInfoResponseWriter) GetStatus() int {
|
||||
return lirw.status
|
||||
}
|
||||
|
||||
func (lirw *logInfoResponseWriter) GetSize() int {
|
||||
return lirw.size
|
||||
}
|
||||
|
||||
func (lirw *logInfoResponseWriter) GetBackend() string {
|
||||
return lirw.backend
|
||||
}
|
||||
|
||||
func (lirw *logInfoResponseWriter) GetFrontend() string {
|
||||
return lirw.frontend
|
||||
}
|
||||
|
||||
func (lirw *logInfoResponseWriter) SetBackend(backend string) {
|
||||
lirw.backend = backend
|
||||
}
|
||||
|
||||
func (lirw *logInfoResponseWriter) SetFrontend(frontend string) {
|
||||
lirw.frontend = frontend
|
||||
}
|
||||
|
||||
116
middlewares/logger_test.go
Normal file
116
middlewares/logger_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
shellwords "github.com/mattn/go-shellwords"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type logtestResponseWriter struct{}
|
||||
|
||||
var (
|
||||
logger *Logger
|
||||
logfileName = "traefikTestLogger.log"
|
||||
logfilePath string
|
||||
helloWorld = "Hello, World"
|
||||
testBackendName = "http://127.0.0.1/testBackend"
|
||||
testFrontendName = "testFrontend"
|
||||
testStatus = 123
|
||||
testHostname = "TestHost"
|
||||
testUsername = "TestUser"
|
||||
testPath = "http://testpath"
|
||||
testPort = 8181
|
||||
testProto = "HTTP/0.0"
|
||||
testMethod = "POST"
|
||||
testReferer = "testReferer"
|
||||
testUserAgent = "testUserAgent"
|
||||
testBackend2FrontendMap = map[string]string{
|
||||
testBackendName: testFrontendName,
|
||||
}
|
||||
printedLogdata bool
|
||||
)
|
||||
|
||||
func TestLogger(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
logfilePath = filepath.Join(os.Getenv("TEMP"), logfileName)
|
||||
} else {
|
||||
logfilePath = filepath.Join("/tmp", logfileName)
|
||||
}
|
||||
|
||||
logger = NewLogger(logfilePath)
|
||||
defer cleanup()
|
||||
SetBackend2FrontendMap(&testBackend2FrontendMap)
|
||||
|
||||
r := &http.Request{
|
||||
Header: map[string][]string{
|
||||
"User-Agent": {testUserAgent},
|
||||
"Referer": {testReferer},
|
||||
},
|
||||
Proto: testProto,
|
||||
Host: testHostname,
|
||||
Method: testMethod,
|
||||
RemoteAddr: fmt.Sprintf("%s:%d", testHostname, testPort),
|
||||
URL: &url.URL{
|
||||
User: url.UserPassword(testUsername, ""),
|
||||
Path: testPath,
|
||||
},
|
||||
}
|
||||
|
||||
logger.ServeHTTP(&logtestResponseWriter{}, r, LogWriterTestHandlerFunc)
|
||||
|
||||
if logdata, err := ioutil.ReadFile(logfilePath); err != nil {
|
||||
fmt.Printf("%s\n%s\n", string(logdata), err.Error())
|
||||
assert.Nil(t, err)
|
||||
} else if tokens, err := shellwords.Parse(string(logdata)); err != nil {
|
||||
fmt.Printf("%s\n", err.Error())
|
||||
assert.Nil(t, err)
|
||||
} else if assert.Equal(t, 14, len(tokens), printLogdata(logdata)) {
|
||||
assert.Equal(t, testHostname, tokens[0], printLogdata(logdata))
|
||||
assert.Equal(t, testUsername, tokens[2], printLogdata(logdata))
|
||||
assert.Equal(t, fmt.Sprintf("%s %s %s", testMethod, testPath, testProto), tokens[5], printLogdata(logdata))
|
||||
assert.Equal(t, fmt.Sprintf("%d", testStatus), tokens[6], printLogdata(logdata))
|
||||
assert.Equal(t, fmt.Sprintf("%d", len(helloWorld)), tokens[7], printLogdata(logdata))
|
||||
assert.Equal(t, testReferer, tokens[8], printLogdata(logdata))
|
||||
assert.Equal(t, testUserAgent, tokens[9], printLogdata(logdata))
|
||||
assert.Equal(t, "1", tokens[10], printLogdata(logdata))
|
||||
assert.Equal(t, testFrontendName, tokens[11], printLogdata(logdata))
|
||||
assert.Equal(t, testBackendName, tokens[12], printLogdata(logdata))
|
||||
}
|
||||
}
|
||||
|
||||
func cleanup() {
|
||||
logger.Close()
|
||||
os.Remove(logfilePath)
|
||||
}
|
||||
|
||||
func printLogdata(logdata []byte) string {
|
||||
return fmt.Sprintf(
|
||||
"\nExpected: %s\n"+
|
||||
"Actual: %s",
|
||||
"TestHost - TestUser [13/Apr/2016:07:14:19 -0700] \"POST http://testpath HTTP/0.0\" 123 12 \"testReferer\" \"testUserAgent\" 1 \"testFrontend\" \"http://127.0.0.1/testBackend\" 1ms",
|
||||
string(logdata))
|
||||
}
|
||||
|
||||
func LogWriterTestHandlerFunc(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Write([]byte(helloWorld))
|
||||
rw.WriteHeader(testStatus)
|
||||
saveBackendNameForLogger(r, testBackendName)
|
||||
}
|
||||
|
||||
func (lrw *logtestResponseWriter) Header() http.Header {
|
||||
return map[string][]string{}
|
||||
}
|
||||
|
||||
func (lrw *logtestResponseWriter) Write(b []byte) (int, error) {
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (lrw *logtestResponseWriter) WriteHeader(s int) {
|
||||
}
|
||||
20
middlewares/saveBackend.go
Normal file
20
middlewares/saveBackend.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// SaveBackend sends the backend name to the logger.
|
||||
type SaveBackend struct {
|
||||
next http.Handler
|
||||
}
|
||||
|
||||
// NewSaveBackend creates a SaveBackend
|
||||
func NewSaveBackend(next http.Handler) *SaveBackend {
|
||||
return &SaveBackend{next}
|
||||
}
|
||||
|
||||
func (sb *SaveBackend) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
saveBackendNameForLogger(r, (*r.URL).String())
|
||||
sb.next.ServeHTTP(rw, r)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
@@ -123,6 +124,28 @@ func (provider *ConsulCatalog) getFrontendRule(service serviceUpdate) string {
|
||||
return "Host:" + service.ServiceName + "." + provider.Domain
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) getBackendAddress(node *api.ServiceEntry) string {
|
||||
if node.Service.Address != "" {
|
||||
return node.Service.Address
|
||||
}
|
||||
return node.Node.Address
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) getBackendName(node *api.ServiceEntry, index int) string {
|
||||
serviceName := node.Service.Service + "--" + node.Service.Address + "--" + strconv.Itoa(node.Service.Port)
|
||||
|
||||
for _, tag := range node.Service.Tags {
|
||||
serviceName += "--" + normalize(tag)
|
||||
}
|
||||
|
||||
serviceName = strings.Replace(serviceName, ".", "-", -1)
|
||||
serviceName = strings.Replace(serviceName, "=", "-", -1)
|
||||
|
||||
// unique int at the end
|
||||
serviceName += "--" + strconv.Itoa(index)
|
||||
return serviceName
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) getAttribute(name string, tags []string, defaultValue string) string {
|
||||
for _, tag := range tags {
|
||||
if strings.Index(strings.ToLower(tag), DefaultConsulCatalogTagPrefix+".") == 0 {
|
||||
@@ -136,11 +159,12 @@ func (provider *ConsulCatalog) getAttribute(name string, tags []string, defaultV
|
||||
|
||||
func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration {
|
||||
var FuncMap = template.FuncMap{
|
||||
"getBackend": provider.getBackend,
|
||||
"getFrontendRule": provider.getFrontendRule,
|
||||
"getAttribute": provider.getAttribute,
|
||||
"getEntryPoints": provider.getEntryPoints,
|
||||
"replace": replace,
|
||||
"getBackend": provider.getBackend,
|
||||
"getFrontendRule": provider.getFrontendRule,
|
||||
"getBackendName": provider.getBackendName,
|
||||
"getBackendAddress": provider.getBackendAddress,
|
||||
"getAttribute": provider.getAttribute,
|
||||
"getEntryPoints": provider.getEntryPoints,
|
||||
}
|
||||
|
||||
allNodes := []*api.ServiceEntry{}
|
||||
|
||||
@@ -82,6 +82,99 @@ func TestConsulCatalogGetAttribute(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsulCatalogGetBackendAddress(t *testing.T) {
|
||||
provider := &ConsulCatalog{
|
||||
Domain: "localhost",
|
||||
}
|
||||
|
||||
services := []struct {
|
||||
node *api.ServiceEntry
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
node: &api.ServiceEntry{
|
||||
Node: &api.Node{
|
||||
Address: "10.1.0.1",
|
||||
},
|
||||
Service: &api.AgentService{
|
||||
Address: "10.2.0.1",
|
||||
},
|
||||
},
|
||||
expected: "10.2.0.1",
|
||||
},
|
||||
{
|
||||
node: &api.ServiceEntry{
|
||||
Node: &api.Node{
|
||||
Address: "10.1.0.1",
|
||||
},
|
||||
Service: &api.AgentService{
|
||||
Address: "",
|
||||
},
|
||||
},
|
||||
expected: "10.1.0.1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range services {
|
||||
actual := provider.getBackendAddress(e.node)
|
||||
if actual != e.expected {
|
||||
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsulCatalogGetBackendName(t *testing.T) {
|
||||
provider := &ConsulCatalog{
|
||||
Domain: "localhost",
|
||||
}
|
||||
|
||||
services := []struct {
|
||||
node *api.ServiceEntry
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
node: &api.ServiceEntry{
|
||||
Service: &api.AgentService{
|
||||
Service: "api",
|
||||
Address: "10.0.0.1",
|
||||
Port: 80,
|
||||
Tags: []string{},
|
||||
},
|
||||
},
|
||||
expected: "api--10-0-0-1--80--0",
|
||||
},
|
||||
{
|
||||
node: &api.ServiceEntry{
|
||||
Service: &api.AgentService{
|
||||
Service: "api",
|
||||
Address: "10.0.0.1",
|
||||
Port: 80,
|
||||
Tags: []string{"traefik.weight=42", "traefik.enable=true"},
|
||||
},
|
||||
},
|
||||
expected: "api--10-0-0-1--80--traefik-weight-42--traefik-enable-true--1",
|
||||
},
|
||||
{
|
||||
node: &api.ServiceEntry{
|
||||
Service: &api.AgentService{
|
||||
Service: "api",
|
||||
Address: "10.0.0.1",
|
||||
Port: 80,
|
||||
Tags: []string{"a funny looking tag"},
|
||||
},
|
||||
},
|
||||
expected: "api--10-0-0-1--80--a-funny-looking-tag--2",
|
||||
},
|
||||
}
|
||||
|
||||
for i, e := range services {
|
||||
actual := provider.getBackendName(e.node, i)
|
||||
if actual != e.expected {
|
||||
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsulCatalogBuildConfig(t *testing.T) {
|
||||
provider := &ConsulCatalog{
|
||||
Domain: "localhost",
|
||||
@@ -143,7 +236,8 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
"frontend-test": {
|
||||
Backend: "backend-test",
|
||||
Backend: "backend-test",
|
||||
PassHostHeader: true,
|
||||
Routes: map[string]types.Route{
|
||||
"route-host-test": {
|
||||
Rule: "Host:test.localhost",
|
||||
@@ -154,7 +248,7 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
|
||||
expectedBackends: map[string]*types.Backend{
|
||||
"backend-test": {
|
||||
Servers: map[string]types.Server{
|
||||
"test--127-0-0-1--80": {
|
||||
"test--127-0-0-1--80--traefik-backend-weight-42--random-foo-bar--traefik-backend-passHostHeader-true--traefik-protocol-https--0": {
|
||||
URL: "https://127.0.0.1:80",
|
||||
Weight: 42,
|
||||
},
|
||||
|
||||
@@ -279,7 +279,7 @@ func (provider *Docker) getPassHostHeader(container dockertypes.ContainerJSON) s
|
||||
if passHostHeader, err := getLabel(container, "traefik.frontend.passHostHeader"); err == nil {
|
||||
return passHostHeader
|
||||
}
|
||||
return "false"
|
||||
return "true"
|
||||
}
|
||||
|
||||
func (provider *Docker) getEntryPoints(container dockertypes.ContainerJSON) []string {
|
||||
|
||||
@@ -401,7 +401,6 @@ func TestDockerGetProtocol(t *testing.T) {
|
||||
|
||||
func TestDockerGetPassHostHeader(t *testing.T) {
|
||||
provider := &Docker{}
|
||||
|
||||
containers := []struct {
|
||||
container docker.ContainerJSON
|
||||
expected string
|
||||
@@ -413,7 +412,7 @@ func TestDockerGetPassHostHeader(t *testing.T) {
|
||||
},
|
||||
Config: &container.Config{},
|
||||
},
|
||||
expected: "false",
|
||||
expected: "true",
|
||||
},
|
||||
{
|
||||
container: docker.ContainerJSON{
|
||||
@@ -422,11 +421,11 @@ func TestDockerGetPassHostHeader(t *testing.T) {
|
||||
},
|
||||
Config: &container.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.frontend.passHostHeader": "true",
|
||||
"traefik.frontend.passHostHeader": "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "true",
|
||||
expected: "false",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -744,8 +743,9 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
"frontend-Host-test-docker-localhost": {
|
||||
Backend: "backend-test",
|
||||
EntryPoints: []string{},
|
||||
Backend: "backend-test",
|
||||
PassHostHeader: true,
|
||||
EntryPoints: []string{},
|
||||
Routes: map[string]types.Route{
|
||||
"route-frontend-Host-test-docker-localhost": {
|
||||
Rule: "Host:test.docker.localhost",
|
||||
@@ -816,8 +816,9 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
"frontend-Host-test1-docker-localhost": {
|
||||
Backend: "backend-foobar",
|
||||
EntryPoints: []string{"http", "https"},
|
||||
Backend: "backend-foobar",
|
||||
PassHostHeader: true,
|
||||
EntryPoints: []string{"http", "https"},
|
||||
Routes: map[string]types.Route{
|
||||
"route-frontend-Host-test1-docker-localhost": {
|
||||
Rule: "Host:test1.docker.localhost",
|
||||
@@ -825,8 +826,9 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
"frontend-Host-test2-docker-localhost": {
|
||||
Backend: "backend-foobar",
|
||||
EntryPoints: []string{},
|
||||
Backend: "backend-foobar",
|
||||
PassHostHeader: true,
|
||||
EntryPoints: []string{},
|
||||
Routes: map[string]types.Route{
|
||||
"route-frontend-Host-test2-docker-localhost": {
|
||||
Rule: "Host:test2.docker.localhost",
|
||||
|
||||
@@ -49,7 +49,7 @@ func NewClient(baseURL string, caCert []byte, token string) (Client, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetIngresses returns all services in the cluster
|
||||
// GetIngresses returns all ingresses in the cluster
|
||||
func (c *clientImpl) GetIngresses(predicate func(Ingress) bool) ([]Ingress, error) {
|
||||
getURL := c.endpointURL + extentionsEndpoint + defaultIngress
|
||||
|
||||
|
||||
@@ -21,8 +21,10 @@ const (
|
||||
|
||||
// Kubernetes holds configurations of the Kubernetes provider.
|
||||
type Kubernetes struct {
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
Endpoint string
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
Endpoint string
|
||||
disablePassHostHeaders bool
|
||||
Namespaces []string
|
||||
}
|
||||
|
||||
func (provider *Kubernetes) createClient() (k8s.Client, error) {
|
||||
@@ -123,7 +125,15 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
|
||||
|
||||
func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configuration, error) {
|
||||
ingresses, err := k8sClient.GetIngresses(func(ingress k8s.Ingress) bool {
|
||||
return true
|
||||
if len(provider.Namespaces) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, n := range provider.Namespaces {
|
||||
if ingress.ObjectMeta.Namespace == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("Error retrieving ingresses: %+v", err)
|
||||
@@ -133,6 +143,7 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
|
||||
map[string]*types.Backend{},
|
||||
map[string]*types.Frontend{},
|
||||
}
|
||||
PassHostHeader := provider.getPassHostHeader()
|
||||
for _, i := range ingresses {
|
||||
for _, r := range i.Spec.Rules {
|
||||
for _, pa := range r.HTTP.Paths {
|
||||
@@ -143,8 +154,9 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
|
||||
}
|
||||
if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists {
|
||||
templateObjects.Frontends[r.Host+pa.Path] = &types.Frontend{
|
||||
Backend: r.Host + pa.Path,
|
||||
Routes: make(map[string]types.Route),
|
||||
Backend: r.Host + pa.Path,
|
||||
PassHostHeader: PassHostHeader,
|
||||
Routes: make(map[string]types.Route),
|
||||
}
|
||||
}
|
||||
if _, exists := templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host]; !exists {
|
||||
@@ -154,7 +166,7 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
|
||||
}
|
||||
if len(pa.Path) > 0 {
|
||||
templateObjects.Frontends[r.Host+pa.Path].Routes[pa.Path] = types.Route{
|
||||
Rule: "PathPrefixStrip:" + pa.Path,
|
||||
Rule: "PathPrefix:" + pa.Path,
|
||||
}
|
||||
}
|
||||
services, err := k8sClient.GetServices(func(service k8s.Service) bool {
|
||||
@@ -190,6 +202,13 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
|
||||
return &templateObjects, nil
|
||||
}
|
||||
|
||||
func (provider *Kubernetes) getPassHostHeader() bool {
|
||||
if provider.disablePassHostHeaders {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *Kubernetes) loadConfig(templateObjects types.Configuration) *types.Configuration {
|
||||
var FuncMap = template.FuncMap{}
|
||||
configuration, err := provider.getConfiguration("templates/kubernetes.tmpl", FuncMap, templateObjects)
|
||||
|
||||
@@ -139,10 +139,11 @@ func TestLoadIngresses(t *testing.T) {
|
||||
},
|
||||
Frontends: map[string]*types.Frontend{
|
||||
"foo/bar": {
|
||||
Backend: "foo/bar",
|
||||
Backend: "foo/bar",
|
||||
PassHostHeader: true,
|
||||
Routes: map[string]types.Route{
|
||||
"/bar": {
|
||||
Rule: "PathPrefixStrip:/bar",
|
||||
Rule: "PathPrefix:/bar",
|
||||
},
|
||||
"foo": {
|
||||
Rule: "Host:foo",
|
||||
@@ -150,7 +151,8 @@ func TestLoadIngresses(t *testing.T) {
|
||||
},
|
||||
},
|
||||
"bar": {
|
||||
Backend: "bar",
|
||||
Backend: "bar",
|
||||
PassHostHeader: true,
|
||||
Routes: map[string]types.Route{
|
||||
"bar": {
|
||||
Rule: "Host:bar",
|
||||
@@ -167,6 +169,526 @@ func TestLoadIngresses(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPassHostHeader(t *testing.T) {
|
||||
ingresses := []k8s.Ingress{{
|
||||
Spec: k8s.IngressSpec{
|
||||
Rules: []k8s.IngressRule{
|
||||
{
|
||||
Host: "foo",
|
||||
IngressRuleValue: k8s.IngressRuleValue{
|
||||
HTTP: &k8s.HTTPIngressRuleValue{
|
||||
Paths: []k8s.HTTPIngressPath{
|
||||
{
|
||||
Path: "/bar",
|
||||
Backend: k8s.IngressBackend{
|
||||
ServiceName: "service1",
|
||||
ServicePort: k8s.FromInt(801),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
services := []k8s.Service{
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Name: "service1",
|
||||
UID: "1",
|
||||
},
|
||||
Spec: k8s.ServiceSpec{
|
||||
ClusterIP: "10.0.0.1",
|
||||
Ports: []k8s.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 801,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
watchChan := make(chan interface{})
|
||||
client := clientMock{
|
||||
ingresses: ingresses,
|
||||
services: services,
|
||||
watchChan: watchChan,
|
||||
}
|
||||
provider := Kubernetes{disablePassHostHeaders: true}
|
||||
actual, err := provider.loadIngresses(client)
|
||||
if err != nil {
|
||||
t.Fatalf("error %+v", err)
|
||||
}
|
||||
|
||||
expected := &types.Configuration{
|
||||
Backends: map[string]*types.Backend{
|
||||
"foo/bar": {
|
||||
Servers: map[string]types.Server{
|
||||
"1": {
|
||||
URL: "http://10.0.0.1:801",
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
LoadBalancer: nil,
|
||||
},
|
||||
},
|
||||
Frontends: map[string]*types.Frontend{
|
||||
"foo/bar": {
|
||||
Backend: "foo/bar",
|
||||
Routes: map[string]types.Route{
|
||||
"/bar": {
|
||||
Rule: "PathPrefix:/bar",
|
||||
},
|
||||
"foo": {
|
||||
Rule: "Host:foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
actualJSON, _ := json.Marshal(actual)
|
||||
expectedJSON, _ := json.Marshal(expected)
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("expected %+v, got %+v", string(expectedJSON), string(actualJSON))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadNamespacedIngresses(t *testing.T) {
|
||||
ingresses := []k8s.Ingress{
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Namespace: "awesome",
|
||||
},
|
||||
Spec: k8s.IngressSpec{
|
||||
Rules: []k8s.IngressRule{
|
||||
{
|
||||
Host: "foo",
|
||||
IngressRuleValue: k8s.IngressRuleValue{
|
||||
HTTP: &k8s.HTTPIngressRuleValue{
|
||||
Paths: []k8s.HTTPIngressPath{
|
||||
{
|
||||
Path: "/bar",
|
||||
Backend: k8s.IngressBackend{
|
||||
ServiceName: "service1",
|
||||
ServicePort: k8s.FromInt(801),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Host: "bar",
|
||||
IngressRuleValue: k8s.IngressRuleValue{
|
||||
HTTP: &k8s.HTTPIngressRuleValue{
|
||||
Paths: []k8s.HTTPIngressPath{
|
||||
{
|
||||
Backend: k8s.IngressBackend{
|
||||
ServiceName: "service3",
|
||||
ServicePort: k8s.FromInt(443),
|
||||
},
|
||||
},
|
||||
{
|
||||
Backend: k8s.IngressBackend{
|
||||
ServiceName: "service2",
|
||||
ServicePort: k8s.FromInt(802),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Namespace: "not-awesome",
|
||||
},
|
||||
Spec: k8s.IngressSpec{
|
||||
Rules: []k8s.IngressRule{
|
||||
{
|
||||
Host: "baz",
|
||||
IngressRuleValue: k8s.IngressRuleValue{
|
||||
HTTP: &k8s.HTTPIngressRuleValue{
|
||||
Paths: []k8s.HTTPIngressPath{
|
||||
{
|
||||
Path: "/baz",
|
||||
Backend: k8s.IngressBackend{
|
||||
ServiceName: "service1",
|
||||
ServicePort: k8s.FromInt(801),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
services := []k8s.Service{
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Name: "service1",
|
||||
UID: "1",
|
||||
},
|
||||
Spec: k8s.ServiceSpec{
|
||||
ClusterIP: "10.0.0.1",
|
||||
Ports: []k8s.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 801,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Name: "service2",
|
||||
UID: "2",
|
||||
},
|
||||
Spec: k8s.ServiceSpec{
|
||||
ClusterIP: "10.0.0.2",
|
||||
Ports: []k8s.ServicePort{
|
||||
{
|
||||
Port: 802,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Name: "service3",
|
||||
UID: "3",
|
||||
},
|
||||
Spec: k8s.ServiceSpec{
|
||||
ClusterIP: "10.0.0.3",
|
||||
Ports: []k8s.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 443,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
watchChan := make(chan interface{})
|
||||
client := clientMock{
|
||||
ingresses: ingresses,
|
||||
services: services,
|
||||
watchChan: watchChan,
|
||||
}
|
||||
provider := Kubernetes{
|
||||
Namespaces: []string{"awesome"},
|
||||
}
|
||||
actual, err := provider.loadIngresses(client)
|
||||
if err != nil {
|
||||
t.Fatalf("error %+v", err)
|
||||
}
|
||||
|
||||
expected := &types.Configuration{
|
||||
Backends: map[string]*types.Backend{
|
||||
"foo/bar": {
|
||||
Servers: map[string]types.Server{
|
||||
"1": {
|
||||
URL: "http://10.0.0.1:801",
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
LoadBalancer: nil,
|
||||
},
|
||||
"bar": {
|
||||
Servers: map[string]types.Server{
|
||||
"2": {
|
||||
URL: "http://10.0.0.2:802",
|
||||
Weight: 1,
|
||||
},
|
||||
"3": {
|
||||
URL: "https://10.0.0.3:443",
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
LoadBalancer: nil,
|
||||
},
|
||||
},
|
||||
Frontends: map[string]*types.Frontend{
|
||||
"foo/bar": {
|
||||
Backend: "foo/bar",
|
||||
PassHostHeader: true,
|
||||
Routes: map[string]types.Route{
|
||||
"/bar": {
|
||||
Rule: "PathPrefix:/bar",
|
||||
},
|
||||
"foo": {
|
||||
Rule: "Host:foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
"bar": {
|
||||
Backend: "bar",
|
||||
PassHostHeader: true,
|
||||
Routes: map[string]types.Route{
|
||||
"bar": {
|
||||
Rule: "Host:bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
actualJSON, _ := json.Marshal(actual)
|
||||
expectedJSON, _ := json.Marshal(expected)
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("expected %+v, got %+v", string(expectedJSON), string(actualJSON))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadMultipleNamespacedIngresses(t *testing.T) {
|
||||
ingresses := []k8s.Ingress{
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Namespace: "awesome",
|
||||
},
|
||||
Spec: k8s.IngressSpec{
|
||||
Rules: []k8s.IngressRule{
|
||||
{
|
||||
Host: "foo",
|
||||
IngressRuleValue: k8s.IngressRuleValue{
|
||||
HTTP: &k8s.HTTPIngressRuleValue{
|
||||
Paths: []k8s.HTTPIngressPath{
|
||||
{
|
||||
Path: "/bar",
|
||||
Backend: k8s.IngressBackend{
|
||||
ServiceName: "service1",
|
||||
ServicePort: k8s.FromInt(801),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Host: "bar",
|
||||
IngressRuleValue: k8s.IngressRuleValue{
|
||||
HTTP: &k8s.HTTPIngressRuleValue{
|
||||
Paths: []k8s.HTTPIngressPath{
|
||||
{
|
||||
Backend: k8s.IngressBackend{
|
||||
ServiceName: "service3",
|
||||
ServicePort: k8s.FromInt(443),
|
||||
},
|
||||
},
|
||||
{
|
||||
Backend: k8s.IngressBackend{
|
||||
ServiceName: "service2",
|
||||
ServicePort: k8s.FromInt(802),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Namespace: "somewhat-awesome",
|
||||
},
|
||||
Spec: k8s.IngressSpec{
|
||||
Rules: []k8s.IngressRule{
|
||||
{
|
||||
Host: "awesome",
|
||||
IngressRuleValue: k8s.IngressRuleValue{
|
||||
HTTP: &k8s.HTTPIngressRuleValue{
|
||||
Paths: []k8s.HTTPIngressPath{
|
||||
{
|
||||
Path: "/quix",
|
||||
Backend: k8s.IngressBackend{
|
||||
ServiceName: "service1",
|
||||
ServicePort: k8s.FromInt(801),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Namespace: "not-awesome",
|
||||
},
|
||||
Spec: k8s.IngressSpec{
|
||||
Rules: []k8s.IngressRule{
|
||||
{
|
||||
Host: "baz",
|
||||
IngressRuleValue: k8s.IngressRuleValue{
|
||||
HTTP: &k8s.HTTPIngressRuleValue{
|
||||
Paths: []k8s.HTTPIngressPath{
|
||||
{
|
||||
Path: "/baz",
|
||||
Backend: k8s.IngressBackend{
|
||||
ServiceName: "service1",
|
||||
ServicePort: k8s.FromInt(801),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
services := []k8s.Service{
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Name: "service1",
|
||||
UID: "1",
|
||||
},
|
||||
Spec: k8s.ServiceSpec{
|
||||
ClusterIP: "10.0.0.1",
|
||||
Ports: []k8s.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 801,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Name: "service2",
|
||||
UID: "2",
|
||||
},
|
||||
Spec: k8s.ServiceSpec{
|
||||
ClusterIP: "10.0.0.2",
|
||||
Ports: []k8s.ServicePort{
|
||||
{
|
||||
Port: 802,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Name: "service3",
|
||||
UID: "3",
|
||||
},
|
||||
Spec: k8s.ServiceSpec{
|
||||
ClusterIP: "10.0.0.3",
|
||||
Ports: []k8s.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 443,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
watchChan := make(chan interface{})
|
||||
client := clientMock{
|
||||
ingresses: ingresses,
|
||||
services: services,
|
||||
watchChan: watchChan,
|
||||
}
|
||||
provider := Kubernetes{
|
||||
Namespaces: []string{"awesome", "somewhat-awesome"},
|
||||
}
|
||||
actual, err := provider.loadIngresses(client)
|
||||
if err != nil {
|
||||
t.Fatalf("error %+v", err)
|
||||
}
|
||||
|
||||
expected := &types.Configuration{
|
||||
Backends: map[string]*types.Backend{
|
||||
"foo/bar": {
|
||||
Servers: map[string]types.Server{
|
||||
"1": {
|
||||
URL: "http://10.0.0.1:801",
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
LoadBalancer: nil,
|
||||
},
|
||||
"bar": {
|
||||
Servers: map[string]types.Server{
|
||||
"2": {
|
||||
URL: "http://10.0.0.2:802",
|
||||
Weight: 1,
|
||||
},
|
||||
"3": {
|
||||
URL: "https://10.0.0.3:443",
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
LoadBalancer: nil,
|
||||
},
|
||||
"awesome/quix": {
|
||||
Servers: map[string]types.Server{
|
||||
"1": {
|
||||
URL: "http://10.0.0.1:801",
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
LoadBalancer: nil,
|
||||
},
|
||||
},
|
||||
Frontends: map[string]*types.Frontend{
|
||||
"foo/bar": {
|
||||
Backend: "foo/bar",
|
||||
PassHostHeader: true,
|
||||
Routes: map[string]types.Route{
|
||||
"/bar": {
|
||||
Rule: "PathPrefix:/bar",
|
||||
},
|
||||
"foo": {
|
||||
Rule: "Host:foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
"bar": {
|
||||
Backend: "bar",
|
||||
PassHostHeader: true,
|
||||
Routes: map[string]types.Route{
|
||||
"bar": {
|
||||
Rule: "Host:bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
"awesome/quix": {
|
||||
Backend: "awesome/quix",
|
||||
PassHostHeader: true,
|
||||
Routes: map[string]types.Route{
|
||||
"/quix": {
|
||||
Rule: "PathPrefix:/quix",
|
||||
},
|
||||
"awesome": {
|
||||
Rule: "Host:awesome",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
actualJSON, _ := json.Marshal(actual)
|
||||
expectedJSON, _ := json.Marshal(expected)
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("expected %+v, got %+v", string(expectedJSON), string(actualJSON))
|
||||
}
|
||||
}
|
||||
|
||||
type clientMock struct {
|
||||
ingresses []k8s.Ingress
|
||||
services []k8s.Service
|
||||
@@ -174,7 +696,13 @@ type clientMock struct {
|
||||
}
|
||||
|
||||
func (c clientMock) GetIngresses(predicate func(k8s.Ingress) bool) ([]k8s.Ingress, error) {
|
||||
return c.ingresses, nil
|
||||
var ingresses []k8s.Ingress
|
||||
for _, ingress := range c.ingresses {
|
||||
if predicate(ingress) {
|
||||
ingresses = append(ingresses, ingress)
|
||||
}
|
||||
}
|
||||
return ingresses, nil
|
||||
}
|
||||
func (c clientMock) WatchIngresses(predicate func(k8s.Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
return c.watchChan, make(chan error), nil
|
||||
|
||||
@@ -38,12 +38,11 @@ type KvTLS struct {
|
||||
InsecureSkipVerify bool
|
||||
}
|
||||
|
||||
func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) {
|
||||
func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) error {
|
||||
operation := func() error {
|
||||
events, err := provider.kvclient.WatchTree(provider.Prefix, make(chan struct{}) /* stop chan */)
|
||||
events, err := provider.kvclient.WatchTree(provider.Prefix, make(chan struct{}))
|
||||
if err != nil {
|
||||
log.Errorf("Failed to WatchTree %s", err)
|
||||
return err
|
||||
return fmt.Errorf("Failed to KV WatchTree: %v", err)
|
||||
}
|
||||
for {
|
||||
select {
|
||||
@@ -65,12 +64,13 @@ func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix
|
||||
}
|
||||
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("KV connection error %+v, retrying in %s", err, time)
|
||||
log.Errorf("KV connection error: %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), notify)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot connect to KV server %+v", err)
|
||||
return fmt.Errorf("Cannot connect to KV server: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
@@ -112,15 +112,18 @@ func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *
|
||||
storeConfig,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||
}
|
||||
if _, err := kv.List(""); err != nil {
|
||||
return err
|
||||
if _, err := kv.Exists("qmslkjdfmqlskdjfmqlksjazçueznbvbwzlkajzebvkwjdcqmlsfj"); err != nil {
|
||||
return fmt.Errorf("Failed to test KV store connection: %v", err)
|
||||
}
|
||||
provider.kvclient = kv
|
||||
if provider.Watch {
|
||||
pool.Go(func(stop chan bool) {
|
||||
provider.watchKv(configurationChan, provider.Prefix, stop)
|
||||
err := provider.watchKv(configurationChan, provider.Prefix, stop)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot watch KV store: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
configuration := provider.loadConfig()
|
||||
@@ -131,11 +134,11 @@ func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("KV connection error %+v, retrying in %s", err, time)
|
||||
log.Errorf("KV connection error: %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), notify)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot connect to KV server %+v", err)
|
||||
return fmt.Errorf("Cannot connect to KV server: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -170,7 +173,7 @@ func (provider *Kv) list(keys ...string) []string {
|
||||
}
|
||||
directoryKeys := make(map[string]string)
|
||||
for _, key := range keysPairs {
|
||||
directory := strings.Split(strings.TrimPrefix(key.Key, strings.TrimPrefix(joinedKeys, "/")), "/")[0]
|
||||
directory := strings.Split(strings.TrimPrefix(key.Key, joinedKeys), "/")[0]
|
||||
directoryKeys[directory] = joinedKeys + directory
|
||||
}
|
||||
return fun.Values(directoryKeys).([]string)
|
||||
@@ -178,7 +181,7 @@ func (provider *Kv) list(keys ...string) []string {
|
||||
|
||||
func (provider *Kv) get(defaultValue string, keys ...string) string {
|
||||
joinedKeys := strings.Join(keys, "")
|
||||
keyPair, err := provider.kvclient.Get(joinedKeys)
|
||||
keyPair, err := provider.kvclient.Get(strings.TrimPrefix(joinedKeys, "/"))
|
||||
if err != nil {
|
||||
log.Warnf("Error getting key %s %s, setting default %s", joinedKeys, err, defaultValue)
|
||||
return defaultValue
|
||||
|
||||
@@ -430,7 +430,7 @@ func TestKVLoadConfig(t *testing.T) {
|
||||
Frontends: map[string]*types.Frontend{
|
||||
"frontend.with.dot": {
|
||||
Backend: "backend.with.dot.too",
|
||||
PassHostHeader: false,
|
||||
PassHostHeader: true,
|
||||
EntryPoints: []string{},
|
||||
Routes: map[string]types.Route{
|
||||
"route.with.dot": {
|
||||
|
||||
@@ -316,7 +316,7 @@ func (provider *Marathon) getPassHostHeader(application marathon.Application) st
|
||||
if passHostHeader, err := provider.getLabel(application, "traefik.frontend.passHostHeader"); err == nil {
|
||||
return passHostHeader
|
||||
}
|
||||
return "false"
|
||||
return "true"
|
||||
}
|
||||
|
||||
func (provider *Marathon) getEntryPoints(application marathon.Application) []string {
|
||||
|
||||
@@ -82,8 +82,9 @@ func TestMarathonLoadConfig(t *testing.T) {
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
`frontend-test`: {
|
||||
Backend: "backend-test",
|
||||
EntryPoints: []string{},
|
||||
Backend: "backend-test",
|
||||
PassHostHeader: true,
|
||||
EntryPoints: []string{},
|
||||
Routes: map[string]types.Route{
|
||||
`route-host-test`: {
|
||||
Rule: "Host:test.docker.localhost",
|
||||
@@ -780,15 +781,15 @@ func TestMarathonGetPassHostHeader(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
application: marathon.Application{},
|
||||
expected: "false",
|
||||
expected: "true",
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
Labels: map[string]string{
|
||||
"traefik.frontend.passHostHeader": "true",
|
||||
"traefik.frontend.passHostHeader": "false",
|
||||
},
|
||||
},
|
||||
expected: "true",
|
||||
expected: "false",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@ fi
|
||||
if [ -z "$1" ]; then
|
||||
# Remove windows platform because of
|
||||
# https://github.com/mailgun/log/issues/10
|
||||
OS_PLATFORM_ARG=(-os="darwin linux")
|
||||
OS_PLATFORM_ARG=(linux)
|
||||
else
|
||||
OS_PLATFORM_ARG=($1)
|
||||
fi
|
||||
|
||||
if [ -z "$2" ]; then
|
||||
OS_ARCH_ARG=(-arch="386 amd64 arm")
|
||||
OS_ARCH_ARG=(386 amd64 arm)
|
||||
else
|
||||
OS_ARCH_ARG=($2)
|
||||
fi
|
||||
@@ -32,5 +32,9 @@ fi
|
||||
rm -f dist/traefik_*
|
||||
|
||||
# Build binaries
|
||||
GOGC=off gox -ldflags "-X main.Version=$VERSION -X main.BuildDate=$DATE" "${OS_PLATFORM_ARG[@]}" "${OS_ARCH_ARG[@]}" \
|
||||
-output="dist/traefik_{{.OS}}-{{.Arch}}"
|
||||
for OS in ${OS_PLATFORM_ARG[@]}; do
|
||||
for ARCH in ${OS_ARCH_ARG[@]}; do
|
||||
echo "Building binary for $OS/$ARCH..."
|
||||
GOARCH=$ARCH GOOS=$OS CGO_ENABLED=0 go build -ldflags "-s -w -X main.Version=$VERSION -X main.BuildDate=$DATE" -o "dist/traefik_$OS-$ARCH" .
|
||||
done
|
||||
done
|
||||
11
server.go
11
server.go
@@ -180,9 +180,9 @@ func (server *Server) listenConfigurations(stop chan bool) {
|
||||
}
|
||||
currentConfigurations := server.currentConfigurations.Get().(configs)
|
||||
if configMsg.Configuration == nil {
|
||||
log.Info("Skipping empty Configuration")
|
||||
log.Infof("Skipping empty Configuration for provider %s", configMsg.ProviderName)
|
||||
} else if reflect.DeepEqual(currentConfigurations[configMsg.ProviderName], configMsg.Configuration) {
|
||||
log.Info("Skipping same configuration")
|
||||
log.Infof("Skipping same configuration for provider %s", configMsg.ProviderName)
|
||||
} else {
|
||||
// Copy configurations to new map so we don't change current if LoadConfig fails
|
||||
newConfigurations := make(configs)
|
||||
@@ -372,6 +372,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
redirectHandlers := make(map[string]http.Handler)
|
||||
|
||||
backends := map[string]http.Handler{}
|
||||
backend2FrontendMap := map[string]string{}
|
||||
for _, configuration := range configurations {
|
||||
frontendNames := sortedFrontendNamesForConfig(configuration)
|
||||
for _, frontendName := range frontendNames {
|
||||
@@ -379,6 +380,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
|
||||
log.Debugf("Creating frontend %s", frontendName)
|
||||
fwd, _ := forward.New(forward.Logger(oxyLogger), forward.PassHostHeader(frontend.PassHostHeader))
|
||||
saveBackend := middlewares.NewSaveBackend(fwd)
|
||||
// default endpoints if not defined in frontends
|
||||
if len(frontend.EntryPoints) == 0 {
|
||||
frontend.EntryPoints = globalConfiguration.DefaultEntryPoints
|
||||
@@ -414,7 +416,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
if backends[frontend.Backend] == nil {
|
||||
log.Debugf("Creating backend %s", frontend.Backend)
|
||||
var lb http.Handler
|
||||
rr, _ := roundrobin.New(fwd)
|
||||
rr, _ := roundrobin.New(saveBackend)
|
||||
if configuration.Backends[frontend.Backend] == nil {
|
||||
return nil, errors.New("Undefined backend: " + frontend.Backend)
|
||||
}
|
||||
@@ -432,6 +434,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
backend2FrontendMap[url.String()] = frontendName
|
||||
log.Debugf("Creating server %s at %s with weight %d", serverName, url.String(), server.Weight)
|
||||
if err := rebalancer.UpsertServer(url, roundrobin.Weight(server.Weight)); err != nil {
|
||||
return nil, err
|
||||
@@ -445,6 +448,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
backend2FrontendMap[url.String()] = frontendName
|
||||
log.Debugf("Creating server %s at %s with weight %d", serverName, url.String(), server.Weight)
|
||||
if err := rr.UpsertServer(url, roundrobin.Weight(server.Weight)); err != nil {
|
||||
return nil, err
|
||||
@@ -506,6 +510,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
}
|
||||
}
|
||||
}
|
||||
middlewares.SetBackend2FrontendMap(&backend2FrontendMap)
|
||||
return serverEntryPoints, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
[backends]
|
||||
{{range .Nodes}}
|
||||
{{if ne (getAttribute "enable" .Service.Tags "true") "false"}}
|
||||
[backends.backend-{{getBackend .}}.servers.{{.Service.Service | replace "." "-"}}--{{.Service.Address | replace "." "-"}}--{{.Service.Port}}]
|
||||
url = "{{getAttribute "protocol" .Service.Tags "http"}}://{{.Service.Address}}:{{.Service.Port}}"
|
||||
{{$weight := getAttribute "backend.weight" .Service.Tags ""}}
|
||||
{{range $index, $node := .Nodes}}
|
||||
{{if ne (getAttribute "enable" $node.Service.Tags "true") "false"}}
|
||||
[backends.backend-{{getBackend $node}}.servers.{{getBackendName $node $index}}]
|
||||
url = "{{getAttribute "protocol" $node.Service.Tags "http"}}://{{getBackendAddress $node}}:{{$node.Service.Port}}"
|
||||
{{$weight := getAttribute "backend.weight" $node.Service.Tags ""}}
|
||||
{{with $weight}}
|
||||
weight = {{$weight}}
|
||||
{{end}}
|
||||
@@ -25,10 +25,11 @@
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
[frontends]{{range .Services}}
|
||||
[frontends]
|
||||
{{range .Services}}
|
||||
[frontends.frontend-{{.ServiceName}}]
|
||||
backend = "backend-{{.ServiceName}}"
|
||||
passHostHeader = {{getAttribute "frontend.passHostHeader" .Attributes "false"}}
|
||||
passHostHeader = {{getAttribute "frontend.passHostHeader" .Attributes "true"}}
|
||||
{{$entryPoints := getAttribute "frontend.entrypoints" .Attributes ""}}
|
||||
{{with $entryPoints}}
|
||||
entrypoints = [{{range getEntryPoints $entryPoints}}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
[frontends]{{range $frontendName, $frontend := .Frontends}}
|
||||
[frontends."{{$frontendName}}"]
|
||||
backend = "{{$frontend.Backend}}"
|
||||
passHostHeader = {{$frontend.PassHostHeader}}
|
||||
{{range $routeName, $route := $frontend.Routes}}
|
||||
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
|
||||
rule = "{{$route.Rule}}"
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
{{$entryPoints := SplitGet . "/entrypoints"}}
|
||||
[frontends."{{$frontend}}"]
|
||||
backend = "{{Get "" . "/backend"}}"
|
||||
passHostHeader = {{Get "false" . "/passHostHeader"}}
|
||||
passHostHeader = {{Get "true" . "/passHostHeader"}}
|
||||
entryPoints = [{{range $entryPoints}}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
|
||||
@@ -343,6 +343,7 @@
|
||||
# Optional
|
||||
#
|
||||
# endpoint = "http://localhost:8080"
|
||||
# namespaces = ["default"]
|
||||
|
||||
################################################################
|
||||
# Consul KV configuration backend
|
||||
|
||||
Reference in New Issue
Block a user