1
0
mirror of https://github.com/containous/traefik.git synced 2025-10-07 15:33:25 +03:00

Compare commits

...

59 Commits

Author SHA1 Message Date
Vincent Demeester
f6c860afc0 Merge pull request #380 from errm/k8s-ref-ports-by-name
Allow k8s ports to be referenced by name as well as number
2016-05-19 15:00:12 +02:00
Ed Robinson
d13b755df2 Allow k8s ports to be referenced by name as well as number 2016-05-19 13:36:19 +01:00
Vincent Demeester
6bacbf6cac Merge pull request #379 from errm/k8s-services-from-same-namespace
The referenced k8s service(s) must be in namespace
2016-05-19 14:08:53 +02:00
Ed Robinson
5923d22379 The referenced k8s service(s) must be in namespace
By design k8s ingress is only designed to ballance services from within
the namespace of the ingress.

This is disscuessed a little in
https://github.com/kubernetes/kubernetes/issues/17088.

For now traefik should only reference the services in the current
namespace. For me this was a confusing change of behaviour
from the reference implimentations, as I have services
with the same name in each namespace.
2016-05-18 17:38:47 +01:00
Vincent Demeester
70494117d1 Merge pull request #373 from AlmogBaku/master
K8s: add an option to configure "PathPrefixStrip" for the ingress-resource using annotation
2016-05-18 17:19:11 +02:00
AlmogBaku
8210743dad change log error of "ruleType" to warning 2016-05-17 16:54:40 +03:00
AlmogBaku
895f3cc109 fixes golint error 2016-05-17 16:22:37 +03:00
AlmogBaku
71f160dddc Following up to the conversation on Slack & GitHub:
- Change the annotation to define the rule type in `traefik.frontend.rule.type`
 - Update tests
 - Add documentation
 - Add example
2016-05-17 13:50:06 +03:00
AlmogBaku
92abaa0d47 fixes wrong actual on unit-test 2016-05-16 01:24:23 +03:00
AlmogBaku
47710c1385 fixes wrong names on tests - make it pass 2016-05-16 01:06:30 +03:00
AlmogBaku
df3abcbc9a gofmt to tests.. 2016-05-15 20:01:26 +03:00
AlmogBaku
dbb7ad41e5 Add tests for k8s PathPrefixStrip annotation 2016-05-15 12:16:27 +03:00
AlmogBaku
9773d4e409 K8s: add an option to configure "PathPrefixStrip" for the ingress-resource using annotation 2016-05-15 12:00:20 +03:00
Emile Vauge
993165fa66 Added Devoxx Video & support (#371)
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-13 17:32:39 +02:00
Emile Vauge
c49f5dad05 Merge pull request #368 from vdemeester/update-docker-deps
Update docker dependencies to fix issues
2016-05-13 08:21:09 +02:00
Vincent Demeester
c0bdedfed3 Add travis matrix for 1.9.1 and 1.10.1
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-05-12 17:18:58 +02:00
Vincent Demeester
061107b65f Update docker dependencies to fix issues
Update engine-api, libcompose and related dependencies (including
libkermit), to fix issues with docker v1.9.1 and lower.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-05-12 17:10:42 +02:00
Vincent Demeester
7bf421f847 Merge pull request #369 from containous/fix-hijack-logger
fix hijack logger middleware
2016-05-12 17:06:15 +02:00
Emile Vauge
cb0c1d34a2 fix hijack logger middleware
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-12 16:32:12 +02:00
Vincent Demeester
749b381f26 Merge pull request #355 from containous/change-PathPrefixStrip-k8s
Replace PathPrefixStrip by PathPrefix in k8s
2016-05-12 13:56:43 +02:00
Emile Vauge
d89279d708 Replace PathPrefixStrip by PathPrefix in k8s
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-12 12:44:26 +02:00
Marcus Stong
be209ed30c #216: sets passHostHeader to true by default (#351) 2016-05-10 13:43:24 +02:00
Emile Vauge
4a4ba2791d Merge pull request #357 from s7anley/generate-fix
Create static folder on first generate
2016-05-09 23:47:23 +02:00
Ján Koščo
c61d9776e7 Create static folder on first generate 2016-05-09 23:13:22 +02:00
Vincent Demeester
b5716abd3e Merge pull request #347 from keis/consul-catalog-replace-space-in-tags
Replace whitespace in tags in consul_catalog
2016-05-07 17:26:17 +02:00
David Keijser
b9bb78d04b Normalise tags in backend name of consul_catalog
Another fun thing consul lets you do is use spaces in your tags. This
means when including tags in backend name it's possible to generate
invalid names.
2016-05-07 16:30:38 +02:00
Vincent Demeester
8a39ee65cd Merge pull request #341 from errm/k8s-namespacing
Adds option to namespace k8s ingresses
2016-05-07 13:30:35 +02:00
Ed Robinson
301a463aeb Adds option to namespace k8s ingresses
If the flag kubernetes.namespaces is set...
Then we only select ingresses from that/those namespace(s)

This allows multiple instances of traefik to
independently load balance for each namespace.
This could be for logical or security reasons.

Addresses #336
2016-05-06 11:36:53 +01:00
Vincent Demeester
d1b0bece47 Merge pull request #356 from errm/documentation-documentation
Adds some documentation about adding documentation
2016-05-05 23:33:35 +02:00
Ed Robinson
63fd7d1d63 Adds some documentation about adding documentation 2016-05-05 21:45:57 +01:00
Vincent Demeester
f4fb2518a1 Merge pull request #352 from containous/remove-alpine-from-build
Go back to standard golang image
2016-05-05 19:45:12 +02:00
Emile Vauge
ee486de947 Go back to standard golang image
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-05 18:31:55 +02:00
Vincent Demeester
c1a12a58eb Merge pull request #340 from containous/fix-etcd-backend
Fix etcd backend with prefix /
2016-05-03 16:16:10 +02:00
Emile Vauge
c3aadab615 Add Consul integration tests
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-03 15:41:09 +02:00
Emile Vauge
26774d2317 Add Etcd integration tests
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-03 15:41:09 +02:00
Emile Vauge
61def880db Fix etcd backend with prefix /
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-03 15:41:09 +02:00
Vincent Demeester
11a6331185 Merge pull request #349 from stongo/acme-bundle
#345: bundle intermediate certificates
2016-05-03 11:49:35 +02:00
Marcus Stong
378509cef4 #345: bundle intermediate certificates
fixes warnings and gives generated certs an A rating on ssl labs
2016-05-02 20:01:10 -04:00
Vincent Demeester
4a1fa03b2d Merge pull request #318 from stongo/master
#304: enhance acme documentation
2016-05-02 18:21:48 +02:00
Marcus Stong
52bff85dda Merge remote-tracking branch 'upstream/master' 2016-05-02 11:44:07 -04:00
Vincent Demeester
e5b0b34604 Merge pull request #325 from CiscoCloud/master
Add frontend, backend, and timing to access log
2016-04-28 23:19:27 +02:00
David Tootill
0a0063fa27 Tweak comments to satisfy golint 2016-04-28 04:00:38 -07:00
David Tootill
bf1f6f663a Minor refactor as requested in PR comments 2016-04-28 10:53:02 +00:00
David Tootill
8bac454792 Merge branch 'master' of https://github.com/CiscoCloud/traefik 2016-04-28 10:20:24 +00:00
Vincent Demeester
7eaf09b3da Merge pull request #310 from samber/TRAEFIK-294--consul--use-service-addess-or-node-if-nil
feat(consul-provider): If service ip is nil then use node ip
2016-04-27 22:34:06 +02:00
Poney baker
378a261e64 feat(consul-provider): If Service.Address is nil then use Node.Address
test(consul-catalog): Test on backend-name and backend-address
2016-04-27 21:09:42 +02:00
David Tootill
53c99f7469 Add moul/http2curl to glide.yaml 2016-04-27 10:24:00 -07:00
David Tootill
f93e618f67 Merge remote-tracking branch 'refs/remotes/containous/master'
# Conflicts:
#	glide.lock
#	glide.yaml
2016-04-27 09:41:51 -07:00
David Tootill
64b78461f6 Remove some debug logs (requested in review) 2016-04-27 09:25:13 -07:00
Vincent Demeester
2f5c9273ee Merge pull request #334 from containous/fix-k8s-watch-ssl
Fix Kubernetes watch SSL & empty schema
2016-04-27 10:20:26 +02:00
Emile Vauge
38371234a2 Add logo credits
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-27 10:05:04 +02:00
Emile Vauge
10cb606578 Add Kubernetes URL
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-27 10:05:04 +02:00
Emile Vauge
87caf458df Fix Kubernetes schema
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-27 10:05:04 +02:00
Emile Vauge
4ff4e4e626 Fix Kubernetes watch SSL
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-27 10:05:04 +02:00
David Tootill
2a76a717e6 Add access log integration test 2016-04-20 11:54:57 -07:00
David Tootill
c8c0d208be Update glide files for mattn/shellwords 2016-04-20 01:36:51 +00:00
David Tootill
04dd41ac3b Minor corrections 2016-04-20 01:25:22 +00:00
David Tootill
10815eca8e Initial update - manage access log 2016-04-19 16:45:59 -07:00
Marcus Stong
a7b4463f86 #304: enhance acme documentation 2016-04-18 12:31:45 -04:00
56 changed files with 2515 additions and 204 deletions

View File

@@ -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
View File

@@ -10,4 +10,6 @@ traefik.toml
vendor/
static/
.vscode/
site/
site/
*.log
*.exe

View File

@@ -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:

View File

@@ -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

View File

@@ -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.
[![asciicast](https://asciinema.org/a/4tcyde7riou5vxulo6my3mtko.png)](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.
[![Traefik Devoxx France](http://img.youtube.com/vi/QvAz9mVx5TI/0.jpg)](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 [![Join the chat at https://traefik.herokuapp.com](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](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 [![Join the chat at https://traefik.herokuapp.com](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://traefik.herokuapp.com)
@@ -133,8 +140,11 @@ Europe. We provide consulting, development, training and support for the world
software products.
[![Asteris](docs/img/asteris.logo.png)](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 ![logo](docs/img/traefik.icon.png)

View File

@@ -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)

View File

@@ -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
View File

@@ -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"))

View File

@@ -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.
[![asciicast](https://asciinema.org/a/4tcyde7riou5vxulo6my3mtko.png)](https://asciinema.org/a/4tcyde7riou5vxulo6my3mtko)
[![Traefik Devoxx France](https://img.youtube.com/vi/QvAz9mVx5TI/0.jpg)](https://www.youtube.com/watch?v=QvAz9mVx5TI)
## Get it

View File

@@ -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
View File

@@ -0,0 +1,2 @@
exampleHandler
exampleHandler.exe

View 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
View 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

View 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

View 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"

View 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"

View 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
View 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

View File

@@ -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

View File

@@ -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
View File

@@ -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

View File

@@ -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

View 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
}

View File

@@ -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)
}

View File

@@ -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"
)

View File

@@ -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)
}

View 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"

View File

@@ -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"

View File

@@ -1,11 +1,11 @@
defaultEntryPoints = ["http"]
logLevel = "DEBUG"
[entryPoints]
[entryPoints.http]
address = ":8000"
logLevel = "DEBUG"
[docker]
# It's dynamagic !

View File

@@ -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"

View File

@@ -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()

View File

@@ -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

View File

@@ -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
View 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
}
}

View File

@@ -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
View 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) {
}

View 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)
}

View File

@@ -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{}

View File

@@ -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,
},

View File

@@ -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 {

View File

@@ -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",

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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": {

View File

@@ -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 {

View File

@@ -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",
},
}

View File

@@ -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

View File

@@ -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"

View File

@@ -11,4 +11,6 @@ if [ -n "$VERBOSE" ]; then
fi
cd integration
echo "Testing against…"
docker version
CGO_ENABLED=0 go test $TESTFLAGS

View File

@@ -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
}

View File

@@ -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}}

View File

@@ -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}}"

View File

@@ -39,7 +39,7 @@
{{$entryPoints := SplitGet . "/entrypoints"}}
[frontends."{{$frontend}}"]
backend = "{{Get "" . "/backend"}}"
passHostHeader = {{Get "false" . "/passHostHeader"}}
passHostHeader = {{Get "true" . "/passHostHeader"}}
entryPoints = [{{range $entryPoints}}
"{{.}}",
{{end}}]

View File

@@ -343,6 +343,7 @@
# Optional
#
# endpoint = "http://localhost:8080"
# namespaces = ["default"]
################################################################
# Consul KV configuration backend