mirror of
https://github.com/containous/traefik.git
synced 2025-10-07 15:33:25 +03:00
Compare commits
59 Commits
v1.0.0-bet
...
v1.0.0-bet
Author | SHA1 | Date | |
---|---|---|---|
|
f6c860afc0 | ||
|
d13b755df2 | ||
|
6bacbf6cac | ||
|
5923d22379 | ||
|
70494117d1 | ||
|
8210743dad | ||
|
895f3cc109 | ||
|
71f160dddc | ||
|
92abaa0d47 | ||
|
47710c1385 | ||
|
df3abcbc9a | ||
|
dbb7ad41e5 | ||
|
9773d4e409 | ||
|
993165fa66 | ||
|
c49f5dad05 | ||
|
c0bdedfed3 | ||
|
061107b65f | ||
|
7bf421f847 | ||
|
cb0c1d34a2 | ||
|
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 | ||
|
2f5c9273ee | ||
|
38371234a2 | ||
|
10cb606578 | ||
|
87caf458df | ||
|
4ff4e4e626 | ||
|
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
|
||||
|
@@ -6,14 +6,18 @@ env:
|
||||
- secure: btt4r13t09gQlHb6gYrvGC2yGCMMHfnp1Mz1RQedc4Mpf/FfT8aE6xmK2a2i9CCvskjrP0t/BFaS4yxIURjnFRn+ugQIEa0pLspB9UJArW/vgOSpIWM9/OQ/fg8z5XuMxN6Md4DL1/iLypMNSageA1x0TRdt89+D1N1dALpg5XRCXLFbC84TLi0gjlFuib9ibPKzEhLT+anCRJ6iZMzeupDSoaCVbAtJMoDvXw4+4AcRZ1+k4MybBLyCib5boaEOt4pTT88mz4Kk0YaMwPVJyg9Qv36VqyUcPS09Yd95LuyVQ4+tZt8Y1ccbIzULsK+sLM3hLCzxlmlpN3dQBlZJiiRtQde0mgGAKyC0P0A1XjuDTywcsa5edB+fTk1Dsewz9xZ9V0NmMz8t+UNZnaSsAPga9i86jULbXUUwMVSzVRc+Xgx02liB/8qI1xYC9FM6ilStt7rn7mF0k3KbiWhcptgeXjO6Lah9FjEKd5w4MXsdUSTi/86rQaLo+kj+XdaTrXCTulKHyRyQEUj+8V1w0oVz7pcGjePHd7y5oU9ByifVQy6sytuFBfRZvugM5bKHo+i0pcWvixrZS42DrzwxZJsspANOvqSe5ifVbvOkfUppQdCBIwptxV5N1b49XPKU3W/w34QJ8xGmKp3TFA7WwVCztriFHjPgiRpB3EG99Bg=
|
||||
- REPO: $TRAVIS_REPO_SLUG
|
||||
- VERSION: v1.0.0-beta.$TRAVIS_BUILD_NUMBER
|
||||
matrix:
|
||||
- DOCKER_VERSION=1.9.1
|
||||
- DOCKER_VERSION=1.10.1
|
||||
sudo: required
|
||||
services:
|
||||
- docker
|
||||
install:
|
||||
- sudo service docker stop
|
||||
- sudo curl https://get.docker.com/builds/Linux/x86_64/docker-1.10.1 -o /usr/bin/docker
|
||||
- sudo curl https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION} -o /usr/bin/docker
|
||||
- sudo chmod +x /usr/bin/docker
|
||||
- sudo service docker start
|
||||
- docker version
|
||||
- pip install --user mkdocs
|
||||
- pip install --user pymdown-extensions
|
||||
before_script:
|
||||
|
2
Makefile
2
Makefile
@@ -46,7 +46,7 @@ validate: build ## validate gofmt, golint and go vet
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint
|
||||
|
||||
build: dist
|
||||
docker build -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
|
||||
docker build --build-arg=DOCKER_VERSION=${DOCKER_VERSION} -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
|
||||
|
||||
build-webui:
|
||||
docker build -t traefik-webui -f webui/Dockerfile webui
|
||||
|
18
README.md
18
README.md
@@ -13,7 +13,7 @@
|
||||
|
||||
|
||||
Træfɪk is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||
It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), Rest API, file...) to manage its configuration automatically and dynamically.
|
||||
It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [Kubernetes](http://kubernetes.io/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), Rest API, file...) to manage its configuration automatically and dynamically.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -63,9 +63,11 @@ Run it and forget it!
|
||||
|
||||
## Demo
|
||||
|
||||
Here is a demo of Træfɪk using Docker backend, showing a load-balancing between two servers, hot reloading of configuration, and graceful shutdown.
|
||||
|
||||
[](https://asciinema.org/a/4tcyde7riou5vxulo6my3mtko)
|
||||
Here is a talk (in french) given by [Emile Vauge](https://github.com/emilevauge) at the [Devoxx France 2016](http://www.devoxx.fr) conference.
|
||||
You will learn fundamental Træfɪk features and see some demos with Docker, Mesos/Marathon and Lets'Encrypt.
|
||||
|
||||
[](http://www.youtube.com/watch?v=QvAz9mVx5TI)
|
||||
|
||||
## Web UI
|
||||
|
||||
@@ -110,6 +112,11 @@ You can find the complete documentation [here](https://docs.traefik.io).
|
||||
|
||||
Please refer to [this section](.github/CONTRIBUTING.md).
|
||||
|
||||
## Support
|
||||
|
||||
You can join [](https://traefik.herokuapp.com) to get basic support.
|
||||
If you prefer a commercial support, please contact [containo.us](https://containo.us) by mail: <mailto:support@containo.us>.
|
||||
|
||||
## Træfɪk here and there
|
||||
|
||||
These projects use Træfɪk internally. If your company uses Træfɪk, we would be glad to get your feedback :) Contact us on [](https://traefik.herokuapp.com)
|
||||
@@ -133,8 +140,11 @@ Europe. We provide consulting, development, training and support for the world
|
||||
software products.
|
||||
|
||||
|
||||
|
||||
[](https://aster.is)
|
||||
|
||||
Founded in 2014, Asteris creates next-generation infrastructure software for the modern datacenter. Asteris writes software that makes it easy for companies to implement continuous delivery and realtime data pipelines. We support the HashiCorp stack, along with Kubernetes, Apache Mesos, Spark and Kafka. We're core committers on mantl.io, consul-cli and mesos-consul.
|
||||
.
|
||||
|
||||
## Credits
|
||||
|
||||
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,17 +1,12 @@
|
||||
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
|
||||
|
||||
# Which docker version to test on
|
||||
ENV DOCKER_VERSION 1.10.1
|
||||
|
||||
# enable GO15VENDOREXPERIMENT
|
||||
ENV GO15VENDOREXPERIMENT 1
|
||||
ARG DOCKER_VERSION=1.10.1
|
||||
|
||||
# Download docker
|
||||
RUN set -ex; \
|
||||
|
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"))
|
||||
|
@@ -38,9 +38,10 @@ Run it and forget it!
|
||||
|
||||
## Demo
|
||||
|
||||
Here is a demo of Træfɪk using Docker backend, showing a load-balancing between two servers, hot reloading of configuration, and graceful shutdown.
|
||||
Here is a talk (in french) given by [Emile Vauge](https://github.com/emilevauge) at the [Devoxx France 2016](http://www.devoxx.fr) conference.
|
||||
You will learn fundamental Træfɪk features and see some demos with Docker, Mesos/Marathon and Lets'Encrypt.
|
||||
|
||||
[](https://asciinema.org/a/4tcyde7riou5vxulo6my3mtko)
|
||||
[](https://www.youtube.com/watch?v=QvAz9mVx5TI)
|
||||
|
||||
## Get it
|
||||
|
||||
|
14
docs/toml.md
14
docs/toml.md
@@ -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.
|
||||
#
|
||||
@@ -612,7 +619,7 @@ Labels can be used on containers to override default behaviour:
|
||||
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
|
||||
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
||||
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
||||
* `traefik.domain=traefik.localhost`: override the default domain
|
||||
- `traefik.domain=traefik.localhost`: override the default domain
|
||||
|
||||
|
||||
## Kubernetes Ingress backend
|
||||
@@ -641,8 +648,13 @@ Træfɪk can be configured to use Kubernetes Ingress as a backend configuration:
|
||||
# Optional
|
||||
#
|
||||
# endpoint = "http://localhost:8080"
|
||||
# namespaces = ["default","production"]
|
||||
```
|
||||
|
||||
Annotations can be used on containers to override default behaviour for the whole Ingress resource:
|
||||
|
||||
- `traefik.frontend.rule.type: PathPrefixStrip`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
|
||||
|
||||
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).
|
||||
|
||||
## Consul backend
|
||||
|
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
|
@@ -12,7 +12,7 @@ spec:
|
||||
nodePort: 30283
|
||||
targetPort: 80
|
||||
protocol: TCP
|
||||
name: http
|
||||
name: https
|
||||
selector:
|
||||
app: whoami
|
||||
---
|
||||
@@ -91,3 +91,21 @@ spec:
|
||||
- backend:
|
||||
serviceName: service3
|
||||
servicePort: 80
|
||||
|
||||
---
|
||||
# Another Ingress with PathPrefixStrip
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: whoami-ingress-stripped
|
||||
annotations:
|
||||
traefik.frontend.rule.type: "PathPrefixStrip"
|
||||
spec:
|
||||
rules:
|
||||
- host: foo.localhost
|
||||
http:
|
||||
paths:
|
||||
- path: /prefixWillBeStripped
|
||||
backend:
|
||||
serviceName: service1
|
||||
servicePort: 80
|
||||
|
@@ -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
|
||||
|
53
glide.lock
generated
53
glide.lock
generated
@@ -1,5 +1,5 @@
|
||||
hash: e92948ce12f546d39a02c2e58668f7d12d7b1f3dd56eb046e01b527df756f734
|
||||
updated: 2016-04-26T23:18:15.861898862+02:00
|
||||
hash: da7239dce8bda69f6e10b2f2bfae57dd4fd95b817055dca1379a72af42939b97
|
||||
updated: 2016-05-12T11:48:22.158455011+02:00
|
||||
imports:
|
||||
- name: github.com/alecthomas/template
|
||||
version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
|
||||
@@ -42,11 +42,12 @@ imports:
|
||||
subpackages:
|
||||
- spew
|
||||
- name: github.com/docker/distribution
|
||||
version: ff6f38ccb69afa96214c7ee955359465d1fc767a
|
||||
version: 467fc068d88aa6610691b7f1a677271a3fac4aac
|
||||
subpackages:
|
||||
- reference
|
||||
- digest
|
||||
- name: github.com/docker/docker
|
||||
version: f39987afe8d611407887b3094c03d6ba6a766a67
|
||||
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
|
||||
subpackages:
|
||||
- autogen
|
||||
- api
|
||||
@@ -85,7 +86,7 @@ imports:
|
||||
- utils
|
||||
- volume
|
||||
- name: github.com/docker/engine-api
|
||||
version: 8924d6900370b4c7e7984be5adc61f50a80d7537
|
||||
version: fd7f99d354831e7e809386087e7ec3129fdb1520
|
||||
subpackages:
|
||||
- client
|
||||
- types
|
||||
@@ -96,8 +97,10 @@ imports:
|
||||
- client/transport
|
||||
- client/transport/cancellable
|
||||
- types/network
|
||||
- types/reference
|
||||
- types/registry
|
||||
- types/time
|
||||
- types/versions
|
||||
- types/blkiodev
|
||||
- name: github.com/docker/go-connections
|
||||
version: 5b7154ba2efe13ff86ae8830a9e7cb120b080d6e
|
||||
@@ -108,7 +111,7 @@ imports:
|
||||
- name: github.com/docker/go-units
|
||||
version: 5d2041e26a699eaca682e2ea41c8f891e1060444
|
||||
- name: github.com/docker/libcompose
|
||||
version: e290a513ba909ca3afefd5cd611f3a3fe56f6a3a
|
||||
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
|
||||
- name: github.com/docker/libkv
|
||||
version: 7283ef27ed32fe267388510a91709b307bb9942c
|
||||
subpackages:
|
||||
@@ -142,13 +145,13 @@ imports:
|
||||
- name: github.com/gorilla/mux
|
||||
version: f15e0c49460fd49eebe2bcc8486b05d1bef68d3a
|
||||
- name: github.com/gorilla/websocket
|
||||
version: e2e3d8414d0fbae04004f151979f4e27c6747fe7
|
||||
version: 1f512fc3f05332ba7117626cdfb4e07474e58e60
|
||||
- name: github.com/hashicorp/consul
|
||||
version: de080672fee9e6104572eeea89eccdca135bb918
|
||||
subpackages:
|
||||
- api
|
||||
- name: github.com/hashicorp/hcl
|
||||
version: 27a57f2605e04995c111273c263d51cee60d9bc4
|
||||
version: 9a905a34e6280ce905da1a32344b25e81011197a
|
||||
subpackages:
|
||||
- hcl/ast
|
||||
- hcl/parser
|
||||
@@ -163,7 +166,11 @@ imports:
|
||||
- name: github.com/kr/pretty
|
||||
version: add1dbc86daf0f983cd4a48ceb39deb95c729b67
|
||||
- name: github.com/kr/text
|
||||
version: bb797dc4fb8320488f47bf11de07a733d7233e1f
|
||||
version: 7cafcd837844e784b526369c9bce262804aebc60
|
||||
- name: github.com/libkermit/docker
|
||||
version: 9f5a90f8eb3c49bf56e81621f98b3cd86fe4139f
|
||||
- name: github.com/libkermit/docker-check
|
||||
version: bb75a86b169c6c5d22c0ee98278124036f272d7b
|
||||
- name: github.com/magiconair/properties
|
||||
version: c265cfa48dda6474e208715ca93e987829f572f8
|
||||
- name: github.com/mailgun/log
|
||||
@@ -174,20 +181,22 @@ 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
|
||||
version: 3b8b3c98b207f95fe0cd6c7c311a9ac497ba7c0f
|
||||
- name: github.com/miekg/dns
|
||||
version: a5cc44dc6b2eee8eddfd6581e1c6bb753ff0d176
|
||||
version: 48ab6605c66ac797e07f615101c3e9e10e932b66
|
||||
- name: github.com/mitchellh/mapstructure
|
||||
version: d2dd0262208475919e1a362f675cfc0e7c10e905
|
||||
- name: github.com/moul/http2curl
|
||||
version: 1812aee76a1ce98d604a44200c6a23c689b17a89
|
||||
- name: github.com/opencontainers/runc
|
||||
version: 4ab132458fc3e9dbeea624153e0331952dc4c8d5
|
||||
version: 2441732d6fcc0fb0a542671a4372e0c7bc99c19e
|
||||
subpackages:
|
||||
- libcontainer/user
|
||||
- name: github.com/parnurzeal/gorequest
|
||||
version: 91b42fce877cc6af96c45818665a4c615cc5f4ee
|
||||
version: a39a2f8d0463091df7344dbf586a9986e9f7184f
|
||||
- name: github.com/pmezard/go-difflib
|
||||
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
||||
subpackages:
|
||||
@@ -201,21 +210,21 @@ imports:
|
||||
- name: github.com/spf13/cast
|
||||
version: ee7b3e0353166ab1f3a605294ac8cd2b77953778
|
||||
- name: github.com/spf13/cobra
|
||||
version: 4c05eb1145f16d0e6bb4a3e1b6d769f4713cb41f
|
||||
version: 0f866a6211e33cde2091d9290c08f6afd6c9ebbc
|
||||
subpackages:
|
||||
- cobra
|
||||
- name: github.com/spf13/jwalterweatherman
|
||||
version: 33c24e77fb80341fe7130ee7c594256ff08ccc46
|
||||
- name: github.com/spf13/pflag
|
||||
version: 8f6a28b0916586e7f22fe931ae2fcfc380b1c0e6
|
||||
version: cb88ea77998c3f024757528e3305022ab50b43be
|
||||
- name: github.com/spf13/viper
|
||||
version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325
|
||||
- name: github.com/streamrail/concurrent-map
|
||||
version: 788b276dc7eabf20890ea3fa280956664d58b329
|
||||
version: 1ce4642e5a162df67825d273a86b87e6cc8a076b
|
||||
- name: github.com/stretchr/objx
|
||||
version: cbeaeb16a013161a98496fad62933b1d21786672
|
||||
- name: github.com/stretchr/testify
|
||||
version: bcd9e3389dd03b0b668d11f4d462a6af6c2dfd60
|
||||
version: 6cb3b85ef5a0efef77caef88363ec4d4b5c0976d
|
||||
subpackages:
|
||||
- mock
|
||||
- assert
|
||||
@@ -228,9 +237,7 @@ imports:
|
||||
- name: github.com/unrolled/render
|
||||
version: 26b4e3aac686940fe29521545afad9966ddfc80c
|
||||
- name: github.com/vdemeester/docker-events
|
||||
version: 1ecaca5890ef1ffd266fcbfdbe43073ef105704b
|
||||
- name: github.com/vdemeester/libkermit
|
||||
version: 7e4e689a6fa9281e0fb9b7b9c297e22d5342a5ec
|
||||
version: ce5347b72aafad4e3bebd966f15e4183839d5172
|
||||
- name: github.com/vdemeester/shakers
|
||||
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
|
||||
- name: github.com/vulcand/oxy
|
||||
@@ -251,11 +258,11 @@ imports:
|
||||
- name: github.com/wendal/errors
|
||||
version: f66c77a7882b399795a8987ebf87ef64a427417e
|
||||
- name: github.com/xenolf/lego
|
||||
version: 684400fe76a813e78d87803a62bc04d977c501d2
|
||||
version: 948483535f53c34d144419869ecbed86251a30f6
|
||||
subpackages:
|
||||
- acme
|
||||
- name: golang.org/x/crypto
|
||||
version: 1777f3ba8c1fed80fcaec3317e3aaa4f627764d2
|
||||
version: b76c864ef1dca1d8f271f917c290cddcce3d9e0d
|
||||
subpackages:
|
||||
- ocsp
|
||||
- name: golang.org/x/net
|
||||
@@ -277,7 +284,7 @@ imports:
|
||||
subpackages:
|
||||
- bson
|
||||
- name: gopkg.in/square/go-jose.v1
|
||||
version: 40d457b439244b546f023d056628e5184136899b
|
||||
version: e3f973b66b91445ec816dd7411ad1b6495a5a2fc
|
||||
subpackages:
|
||||
- cipher
|
||||
- json
|
||||
|
18
glide.yaml
18
glide.yaml
@@ -59,7 +59,7 @@ import:
|
||||
subpackages:
|
||||
- bson
|
||||
- package: github.com/docker/docker
|
||||
version: f39987afe8d611407887b3094c03d6ba6a766a67
|
||||
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
|
||||
subpackages:
|
||||
- autogen
|
||||
- api
|
||||
@@ -104,7 +104,7 @@ import:
|
||||
- package: gopkg.in/yaml.v2
|
||||
version: 7ad95dd0798a40da1ccdff6dff35fd177b5edf
|
||||
- package: github.com/opencontainers/runc
|
||||
version: 4ab132458fc3e9dbeea624153e0331952dc4c8d5
|
||||
version: 2441732d6fcc0fb0a542671a4372e0c7bc99c19e
|
||||
subpackages:
|
||||
- libcontainer/user
|
||||
- package: github.com/gorilla/mux
|
||||
@@ -157,16 +157,18 @@ import:
|
||||
subpackages:
|
||||
- mock
|
||||
- package: github.com/xenolf/lego
|
||||
- package: github.com/vdemeester/libkermit
|
||||
version: 7e4e689a6fa9281e0fb9b7b9c297e22d5342a5ec
|
||||
- package: github.com/libkermit/docker-check
|
||||
version: bb75a86b169c6c5d22c0ee98278124036f272d7b
|
||||
- package: github.com/libkermit/docker
|
||||
version: 9f5a90f8eb3c49bf56e81621f98b3cd86fe4139f
|
||||
- package: github.com/docker/libcompose
|
||||
version: e290a513ba909ca3afefd5cd611f3a3fe56f6a3a
|
||||
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
|
||||
- package: github.com/docker/distribution
|
||||
version: ff6f38ccb69afa96214c7ee955359465d1fc767a
|
||||
version: 467fc068d88aa6610691b7f1a677271a3fac4aac
|
||||
subpackages:
|
||||
- reference
|
||||
- package: github.com/docker/engine-api
|
||||
version: 8924d6900370b4c7e7984be5adc61f50a80d7537
|
||||
version: fd7f99d354831e7e809386087e7ec3129fdb1520
|
||||
subpackages:
|
||||
- client
|
||||
- types
|
||||
@@ -183,3 +185,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)
|
||||
}
|
||||
|
@@ -13,8 +13,8 @@ import (
|
||||
"github.com/docker/docker/pkg/namesgenerator"
|
||||
"github.com/go-check/check"
|
||||
|
||||
d "github.com/vdemeester/libkermit/docker"
|
||||
docker "github.com/vdemeester/libkermit/docker/check"
|
||||
d "github.com/libkermit/docker"
|
||||
docker "github.com/libkermit/docker-check"
|
||||
checker "github.com/vdemeester/shakers"
|
||||
)
|
||||
|
||||
|
@@ -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"
|
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/containous/traefik/integration/utils"
|
||||
"github.com/go-check/check"
|
||||
|
||||
compose "github.com/vdemeester/libkermit/compose/check"
|
||||
"github.com/libkermit/docker-check/compose"
|
||||
checker "github.com/vdemeester/shakers"
|
||||
)
|
||||
|
||||
@@ -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,55 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"log"
|
||||
"bufio"
|
||||
"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 +62,136 @@ 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) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return lirw.rw.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
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",
|
||||
|
@@ -7,11 +7,9 @@ import (
|
||||
"fmt"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -51,13 +49,13 @@ 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
|
||||
|
||||
body, err := c.do(c.request(getURL))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: GET %q : %v", getURL, err)
|
||||
return nil, fmt.Errorf("failed to create ingresses request: GET %q : %v", getURL, err)
|
||||
}
|
||||
|
||||
var ingressList IngressList
|
||||
@@ -85,7 +83,7 @@ func (c *clientImpl) GetServices(predicate func(Service) bool) ([]Service, error
|
||||
|
||||
body, err := c.do(c.request(getURL))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: GET %q : %v", getURL, err)
|
||||
return nil, fmt.Errorf("failed to create services request: GET %q : %v", getURL, err)
|
||||
}
|
||||
|
||||
var serviceList ServiceList
|
||||
@@ -133,22 +131,22 @@ func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error,
|
||||
stopIngresses := make(chan bool)
|
||||
chanIngresses, chanIngressesErr, err := c.WatchIngresses(stopIngresses)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch %v", err)
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
||||
}
|
||||
stopServices := make(chan bool)
|
||||
chanServices, chanServicesErr, err := c.WatchServices(stopServices)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch %v", err)
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
||||
}
|
||||
stopPods := make(chan bool)
|
||||
chanPods, chanPodsErr, err := c.WatchPods(stopPods)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch %v", err)
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
||||
}
|
||||
stopReplicationControllers := make(chan bool)
|
||||
chanReplicationControllers, chanReplicationControllersErr, err := c.WatchReplicationControllers(stopReplicationControllers)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch %v", err)
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
||||
}
|
||||
go func() {
|
||||
defer close(watchCh)
|
||||
@@ -225,34 +223,26 @@ func (c *clientImpl) watch(url string, stopCh <-chan bool) (chan interface{}, ch
|
||||
// get version
|
||||
body, err := c.do(c.request(url))
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to create request: GET %q : %v", url, err)
|
||||
return watchCh, errCh, fmt.Errorf("failed to do version request: GET %q : %v", url, err)
|
||||
}
|
||||
|
||||
var generic GenericObject
|
||||
if err := json.Unmarshal(body, &generic); err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to create request: GET %q : %v", url, err)
|
||||
return watchCh, errCh, fmt.Errorf("failed to decode version %v", err)
|
||||
}
|
||||
resourceVersion := generic.ResourceVersion
|
||||
|
||||
url = url + "?watch&resourceVersion=" + resourceVersion
|
||||
// Make request to Kubernetes API
|
||||
request := c.request(url)
|
||||
request.Transport.Dial = func(network, addr string) (net.Conn, error) {
|
||||
conn, err := net.Dial(network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// No timeout for long-polling request
|
||||
conn.SetDeadline(time.Now())
|
||||
return conn, nil
|
||||
}
|
||||
req, err := request.TLSClientConfig(c.tls).MakeRequest()
|
||||
req, err := request.MakeRequest()
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to create request: GET %q : %v", url, err)
|
||||
return watchCh, errCh, fmt.Errorf("failed to make watch request: GET %q : %v", url, err)
|
||||
}
|
||||
request.Client.Transport = request.Transport
|
||||
res, err := request.Client.Do(req)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to make request: GET %q: %v", url, err)
|
||||
return watchCh, errCh, fmt.Errorf("failed to do watch request: GET %q: %v", url, err)
|
||||
}
|
||||
|
||||
shouldStop := safe.New(false)
|
||||
|
@@ -9,6 +9,7 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
@@ -21,8 +22,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 +126,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 +144,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 +155,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 {
|
||||
@@ -153,12 +166,28 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
|
||||
}
|
||||
}
|
||||
if len(pa.Path) > 0 {
|
||||
ruleType := i.Annotations["traefik.frontend.rule.type"]
|
||||
|
||||
switch strings.ToLower(ruleType) {
|
||||
case "pathprefixstrip":
|
||||
ruleType = "PathPrefixStrip"
|
||||
case "pathstrip":
|
||||
ruleType = "PathStrip"
|
||||
case "path":
|
||||
ruleType = "Path"
|
||||
case "pathprefix":
|
||||
ruleType = "PathPrefix"
|
||||
default:
|
||||
log.Warnf("Unknown RuleType `%s`, falling back to `PathPrefix", ruleType)
|
||||
ruleType = "PathPrefix"
|
||||
}
|
||||
|
||||
templateObjects.Frontends[r.Host+pa.Path].Routes[pa.Path] = types.Route{
|
||||
Rule: "PathPrefixStrip:" + pa.Path,
|
||||
Rule: ruleType + ":" + pa.Path,
|
||||
}
|
||||
}
|
||||
services, err := k8sClient.GetServices(func(service k8s.Service) bool {
|
||||
return service.Name == pa.Backend.ServiceName
|
||||
return service.ObjectMeta.Namespace == i.ObjectMeta.Namespace && service.Name == pa.Backend.ServiceName
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("Error retrieving services: %v", err)
|
||||
@@ -170,12 +199,14 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
|
||||
log.Errorf("Error retrieving services %s", pa.Backend.ServiceName)
|
||||
}
|
||||
for _, service := range services {
|
||||
var protocol string
|
||||
protocol := "http"
|
||||
for _, port := range service.Spec.Ports {
|
||||
if port.Port == pa.Backend.ServicePort.IntValue() {
|
||||
protocol = port.Name
|
||||
if equalPorts(port, pa.Backend.ServicePort) {
|
||||
if port.Port == 443 {
|
||||
protocol = "https"
|
||||
}
|
||||
templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{
|
||||
URL: protocol + "://" + service.Spec.ClusterIP + ":" + pa.Backend.ServicePort.String(),
|
||||
URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(port.Port),
|
||||
Weight: 1,
|
||||
}
|
||||
break
|
||||
@@ -188,6 +219,23 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
|
||||
return &templateObjects, nil
|
||||
}
|
||||
|
||||
func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool {
|
||||
if servicePort.Port == ingressPort.IntValue() {
|
||||
return true
|
||||
}
|
||||
if servicePort.Name != "" && servicePort.Name == ingressPort.String() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
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)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/containous/traefik/provider/k8s"
|
||||
"github.com/containous/traefik/types"
|
||||
"reflect"
|
||||
@@ -20,7 +21,7 @@ func TestLoadIngresses(t *testing.T) {
|
||||
Path: "/bar",
|
||||
Backend: k8s.IngressBackend{
|
||||
ServiceName: "service1",
|
||||
ServicePort: k8s.FromInt(801),
|
||||
ServicePort: k8s.FromString("http"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -35,7 +36,7 @@ func TestLoadIngresses(t *testing.T) {
|
||||
{
|
||||
Backend: k8s.IngressBackend{
|
||||
ServiceName: "service3",
|
||||
ServicePort: k8s.FromInt(803),
|
||||
ServicePort: k8s.FromInt(443),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -76,7 +77,6 @@ func TestLoadIngresses(t *testing.T) {
|
||||
ClusterIP: "10.0.0.2",
|
||||
Ports: []k8s.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 802,
|
||||
},
|
||||
},
|
||||
@@ -92,7 +92,7 @@ func TestLoadIngresses(t *testing.T) {
|
||||
Ports: []k8s.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 803,
|
||||
Port: 443,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -129,7 +129,331 @@ func TestLoadIngresses(t *testing.T) {
|
||||
Weight: 1,
|
||||
},
|
||||
"3": {
|
||||
URL: "http://10.0.0.3:803",
|
||||
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 TestRuleType(t *testing.T) {
|
||||
ingresses := []k8s.Ingress{
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Annotations: map[string]string{"traefik.frontend.rule.type": "PathPrefixStrip"}, //camel case
|
||||
},
|
||||
Spec: k8s.IngressSpec{
|
||||
Rules: []k8s.IngressRule{
|
||||
{
|
||||
Host: "foo1",
|
||||
IngressRuleValue: k8s.IngressRuleValue{
|
||||
HTTP: &k8s.HTTPIngressRuleValue{
|
||||
Paths: []k8s.HTTPIngressPath{
|
||||
{
|
||||
Path: "/bar1",
|
||||
Backend: k8s.IngressBackend{
|
||||
ServiceName: "service1",
|
||||
ServicePort: k8s.FromInt(801),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Annotations: map[string]string{"traefik.frontend.rule.type": "path"}, //lower case
|
||||
},
|
||||
Spec: k8s.IngressSpec{
|
||||
Rules: []k8s.IngressRule{
|
||||
{
|
||||
Host: "foo1",
|
||||
IngressRuleValue: k8s.IngressRuleValue{
|
||||
HTTP: &k8s.HTTPIngressRuleValue{
|
||||
Paths: []k8s.HTTPIngressPath{
|
||||
{
|
||||
Path: "/bar2",
|
||||
Backend: k8s.IngressBackend{
|
||||
ServiceName: "service1",
|
||||
ServicePort: k8s.FromInt(801),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Annotations: map[string]string{"traefik.frontend.rule.type": "PathPrefix"}, //path prefix
|
||||
},
|
||||
Spec: k8s.IngressSpec{
|
||||
Rules: []k8s.IngressRule{
|
||||
{
|
||||
Host: "foo2",
|
||||
IngressRuleValue: k8s.IngressRuleValue{
|
||||
HTTP: &k8s.HTTPIngressRuleValue{
|
||||
Paths: []k8s.HTTPIngressPath{
|
||||
{
|
||||
Path: "/bar1",
|
||||
Backend: k8s.IngressBackend{
|
||||
ServiceName: "service1",
|
||||
ServicePort: k8s.FromInt(801),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Annotations: map[string]string{"traefik.frontend.rule.type": "PathStrip"}, //path strip
|
||||
},
|
||||
Spec: k8s.IngressSpec{
|
||||
Rules: []k8s.IngressRule{
|
||||
{
|
||||
Host: "foo2",
|
||||
IngressRuleValue: k8s.IngressRuleValue{
|
||||
HTTP: &k8s.HTTPIngressRuleValue{
|
||||
Paths: []k8s.HTTPIngressPath{
|
||||
{
|
||||
Path: "/bar2",
|
||||
Backend: k8s.IngressBackend{
|
||||
ServiceName: "service1",
|
||||
ServicePort: k8s.FromInt(801),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Annotations: map[string]string{"traefik.frontend.rule.type": "PathXXStrip"}, //wrong rule
|
||||
},
|
||||
Spec: k8s.IngressSpec{
|
||||
Rules: []k8s.IngressRule{
|
||||
{
|
||||
Host: "foo1",
|
||||
IngressRuleValue: k8s.IngressRuleValue{
|
||||
HTTP: &k8s.HTTPIngressRuleValue{
|
||||
Paths: []k8s.HTTPIngressPath{
|
||||
{
|
||||
Path: "/bar3",
|
||||
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}
|
||||
actualConfig, err := provider.loadIngresses(client)
|
||||
actual := actualConfig.Frontends
|
||||
if err != nil {
|
||||
t.Fatalf("error %+v", err)
|
||||
}
|
||||
|
||||
expected := map[string]*types.Frontend{
|
||||
"foo1/bar1": {
|
||||
Backend: "foo1/bar1",
|
||||
Routes: map[string]types.Route{
|
||||
"/bar1": {
|
||||
Rule: "PathPrefixStrip:/bar1",
|
||||
},
|
||||
"foo1": {
|
||||
Rule: "Host:foo1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"foo1/bar2": {
|
||||
Backend: "foo1/bar2",
|
||||
Routes: map[string]types.Route{
|
||||
"/bar2": {
|
||||
Rule: "Path:/bar2",
|
||||
},
|
||||
"foo1": {
|
||||
Rule: "Host:foo1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"foo2/bar1": {
|
||||
Backend: "foo2/bar1",
|
||||
Routes: map[string]types.Route{
|
||||
"/bar1": {
|
||||
Rule: "PathPrefix:/bar1",
|
||||
},
|
||||
"foo2": {
|
||||
Rule: "Host:foo2",
|
||||
},
|
||||
},
|
||||
},
|
||||
"foo2/bar2": {
|
||||
Backend: "foo2/bar2",
|
||||
Routes: map[string]types.Route{
|
||||
"/bar2": {
|
||||
Rule: "PathStrip:/bar2",
|
||||
},
|
||||
"foo2": {
|
||||
Rule: "Host:foo2",
|
||||
},
|
||||
},
|
||||
},
|
||||
"foo1/bar3": {
|
||||
Backend: "foo1/bar3",
|
||||
Routes: map[string]types.Route{
|
||||
"/bar3": {
|
||||
Rule: "PathPrefix:/bar3",
|
||||
},
|
||||
"foo1": {
|
||||
Rule: "Host:foo1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
actualJSON, _ := json.Marshal(actual)
|
||||
expectedJSON, _ := json.Marshal(expected)
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("expected %+v, got %+v", string(expectedJSON), string(actualJSON))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPassHostHeader(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),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
services := []k8s.Service{
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Name: "service1",
|
||||
Namespace: "awesome",
|
||||
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,
|
||||
},
|
||||
},
|
||||
@@ -142,7 +466,316 @@ func TestLoadIngresses(t *testing.T) {
|
||||
Backend: "foo/bar",
|
||||
Routes: map[string]types.Route{
|
||||
"/bar": {
|
||||
Rule: "PathPrefixStrip:/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 TestOnlyReferencesServicesFromOwnNamespace(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{
|
||||
{
|
||||
Backend: k8s.IngressBackend{
|
||||
ServiceName: "service",
|
||||
ServicePort: k8s.FromInt(80),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
services := []k8s.Service{
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Name: "service",
|
||||
UID: "1",
|
||||
Namespace: "awesome",
|
||||
},
|
||||
Spec: k8s.ServiceSpec{
|
||||
ClusterIP: "10.0.0.1",
|
||||
Ports: []k8s.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Name: "service",
|
||||
UID: "2",
|
||||
Namespace: "not-awesome",
|
||||
},
|
||||
Spec: k8s.ServiceSpec{
|
||||
ClusterIP: "10.0.0.2",
|
||||
Ports: []k8s.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
watchChan := make(chan interface{})
|
||||
client := clientMock{
|
||||
ingresses: ingresses,
|
||||
services: services,
|
||||
watchChan: watchChan,
|
||||
}
|
||||
provider := Kubernetes{}
|
||||
actual, err := provider.loadIngresses(client)
|
||||
if err != nil {
|
||||
t.Fatalf("error %+v", err)
|
||||
}
|
||||
|
||||
expected := &types.Configuration{
|
||||
Backends: map[string]*types.Backend{
|
||||
"foo": {
|
||||
Servers: map[string]types.Server{
|
||||
"1": {
|
||||
URL: "http://10.0.0.1:80",
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
LoadBalancer: nil,
|
||||
},
|
||||
},
|
||||
Frontends: map[string]*types.Frontend{
|
||||
"foo": {
|
||||
Backend: "foo",
|
||||
PassHostHeader: true,
|
||||
Routes: map[string]types.Route{
|
||||
"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{
|
||||
Namespace: "awesome",
|
||||
Name: "service1",
|
||||
UID: "1",
|
||||
},
|
||||
Spec: k8s.ServiceSpec{
|
||||
ClusterIP: "10.0.0.1",
|
||||
Ports: []k8s.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 801,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Name: "service1",
|
||||
Namespace: "not-awesome",
|
||||
UID: "1",
|
||||
},
|
||||
Spec: k8s.ServiceSpec{
|
||||
ClusterIP: "10.0.0.1",
|
||||
Ports: []k8s.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 801,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Name: "service2",
|
||||
Namespace: "awesome",
|
||||
UID: "2",
|
||||
},
|
||||
Spec: k8s.ServiceSpec{
|
||||
ClusterIP: "10.0.0.2",
|
||||
Ports: []k8s.ServicePort{
|
||||
{
|
||||
Port: 802,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Name: "service3",
|
||||
Namespace: "awesome",
|
||||
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",
|
||||
@@ -150,7 +783,8 @@ func TestLoadIngresses(t *testing.T) {
|
||||
},
|
||||
},
|
||||
"bar": {
|
||||
Backend: "bar",
|
||||
Backend: "bar",
|
||||
PassHostHeader: true,
|
||||
Routes: map[string]types.Route{
|
||||
"bar": {
|
||||
Rule: "Host:bar",
|
||||
@@ -159,11 +793,270 @@ func TestLoadIngresses(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(actual.Backends, expected.Backends) {
|
||||
t.Fatalf("expected %+v, got %+v", expected.Backends, actual.Backends)
|
||||
actualJSON, _ := json.Marshal(actual)
|
||||
expectedJSON, _ := json.Marshal(expected)
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("expected %+v, got %+v", string(expectedJSON), string(actualJSON))
|
||||
}
|
||||
if !reflect.DeepEqual(actual.Frontends, expected.Frontends) {
|
||||
t.Fatalf("expected %+v, got %+v", expected.Frontends, actual.Frontends)
|
||||
}
|
||||
|
||||
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",
|
||||
Namespace: "awesome",
|
||||
UID: "1",
|
||||
},
|
||||
Spec: k8s.ServiceSpec{
|
||||
ClusterIP: "10.0.0.1",
|
||||
Ports: []k8s.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 801,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Namespace: "somewhat-awesome",
|
||||
Name: "service1",
|
||||
UID: "17",
|
||||
},
|
||||
Spec: k8s.ServiceSpec{
|
||||
ClusterIP: "10.0.0.4",
|
||||
Ports: []k8s.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 801,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Namespace: "awesome",
|
||||
Name: "service2",
|
||||
UID: "2",
|
||||
},
|
||||
Spec: k8s.ServiceSpec{
|
||||
ClusterIP: "10.0.0.2",
|
||||
Ports: []k8s.ServicePort{
|
||||
{
|
||||
Port: 802,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: k8s.ObjectMeta{
|
||||
Namespace: "awesome",
|
||||
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{
|
||||
"17": {
|
||||
URL: "http://10.0.0.4: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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,13 +1067,25 @@ 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
|
||||
}
|
||||
func (c clientMock) GetServices(predicate func(k8s.Service) bool) ([]k8s.Service, error) {
|
||||
return c.services, nil
|
||||
var services []k8s.Service
|
||||
for _, service := range c.services {
|
||||
if predicate(service) {
|
||||
services = append(services, service)
|
||||
}
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
func (c clientMock) WatchAll(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
|
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if ([ "$TRAVIS_BRANCH" = "master" ] || [ ! -z "$TRAVIS_TAG" ]) && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
|
||||
if ([ "$TRAVIS_BRANCH" = "master" ] || [ ! -z "$TRAVIS_TAG" ]) && [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$DOCKER_VERSION" = "1.10.1" ]; then
|
||||
echo "Deploying..."
|
||||
else
|
||||
echo "Skipping deploy"
|
||||
|
@@ -11,4 +11,6 @@ if [ -n "$VERBOSE" ]; then
|
||||
fi
|
||||
|
||||
cd integration
|
||||
echo "Testing against…"
|
||||
docker version
|
||||
CGO_ENABLED=0 go test $TESTFLAGS
|
||||
|
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