Compare commits

...

29 Commits

Author SHA1 Message Date
Vincent Demeester
3a08655b06 Merge pull request #188 from emilevauge/add-marathon-tls-client-config
Add Marathon TLS client config
2016-02-18 14:23:38 +01:00
emile
9a9c8e5709 Add Marathon TLS client config
Signed-off-by: emile <emile@vauge.com>
2016-02-18 12:42:59 +00:00
Vincent Demeester
c7d34b54aa Merge pull request #205 from emilevauge/bump-go-1.6
Bump go 1.6
2016-02-18 11:07:18 +01:00
emile
8d860c84c8 Add HTTP2 support
Signed-off-by: emile <emile@vauge.com>
2016-02-18 10:31:56 +01:00
emile
1dc086730e Bump golang 1.6 2016-02-18 10:30:41 +01:00
Emile Vauge
5d79e56d30 Merge pull request #193 from dontrebootme/fix192
fix mantl url typo
2016-02-17 23:05:26 +01:00
Vincent Demeester
6e7677de79 Merge pull request #203 from goguardian/alias
Implement `/traefik/alias` for KV stores.
2016-02-17 09:56:43 +01:00
Patrick O'Connor
fab6b8be3c fix mantl url typo 2016-02-16 12:30:33 -08:00
Advait Shinde
f1c1eed437 Fix typo in documentation 2016-02-16 11:55:42 -05:00
Advait Shinde
348ab794c9 Add documentation for /traefik/alias. 2016-02-15 18:46:03 -05:00
Advait Shinde
aacedcc4b3 Implement /traefik/alias for KV stores. 2016-02-15 18:14:57 -05:00
Emile Vauge
786acc961a Merge pull request #200 from vdemeester/some-fixes
Do some build / test-integration fixes
2016-02-13 16:33:42 +01:00
Vincent Demeester
7adffdbd78 Run npm in quiet mode O:)
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-02-13 16:14:26 +01:00
Vincent Demeester
e3b519cdd8 Do some build / test-integration fixes
- target generate-webui depends on build-webui
- generate-webui will run only if the folder static does not exists
- create compose project before starting it >_<'', otherwise it does
  nothing :'D

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-02-13 16:04:08 +01:00
Vincent Demeester
e9c23195a0 Merge pull request #195 from emilevauge/fix-regression-marathon-backends
Fix regression on marathon backend
2016-02-12 15:31:59 +01:00
emile
c6c3af8099 Fix regression on marathon backend
Signed-off-by: emile <emile@vauge.com>
2016-02-12 15:03:28 +01:00
Emile Vauge
07c077cf94 Merge pull request #189 from vdemeester/fix-make-all
Fix make all
2016-02-10 14:10:26 +01:00
Vincent Demeester
4ac18f1989 Fix make all
Add build-ui as dependent target for all. Otherwise it does not build >_<

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-02-10 13:55:47 +01:00
Vincent Demeester
4ecb919787 Merge pull request #178 from emilevauge/add-multiple-entrypoints-support
Add multiple entry points support
2016-02-10 13:54:30 +01:00
emile
4152bd5e26 Update doc with entrypoints 2016-02-10 12:18:50 +01:00
emile
a8cc26fd91 Add entrypoints to providers 2016-02-10 12:14:16 +01:00
emile
81cb00573f Fix tests to accept entrypoints 2016-02-10 12:14:16 +01:00
emile
c22598c8ff Add multiple entry points support, add entry point redirection 2016-02-10 12:14:16 +01:00
Vincent Demeester
bb3b9f61cd Merge pull request #187 from emilevauge/add-max-idle-conns-per-host
Add MaxIdleConnsPerHost. Fixes too many open files error.
2016-02-09 22:49:03 +01:00
emile
49cd7f799e Add MaxIdleConnsPerHost. Fixes too many open files error. 2016-02-09 22:29:01 +01:00
Vincent Demeester
8b334551d8 Merge pull request #185 from ReadmeCritic/master
Update README URLs based on HTTP redirects
2016-02-08 19:15:41 +01:00
ReadmeCritic
5ef6d53d00 Update README URLs based on HTTP redirects 2016-02-08 07:43:43 -08:00
Vincent Demeester
901c9b29bc Merge pull request #171 from tayzlor/fix-170-marathon-getbackend
Fix #170 - getBackend for marathon plugin
2016-02-06 14:06:42 +01:00
Graham Taylor
2d79c500df Fix #170 - lookup backend for marathon plugin so we can specify traefik.backend via labels on the container 2016-02-05 18:01:47 +00:00
40 changed files with 1757 additions and 387 deletions

View File

@@ -6,6 +6,9 @@ TRAEFIK_ENVS := \
-e TESTFLAGS \
-e CIRCLECI
SRCS = $(shell git ls-files '*.go' | grep -v '^external/')
BIND_DIR := "dist"
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/emilevauge/traefik/$(BIND_DIR)"
@@ -21,13 +24,13 @@ print-%: ; @echo $*=$($*)
default: binary
all: build
all: generate-webui build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh
binary: build-webui generate-webui build
binary: generate-webui build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate binary
crossbinary: build-webui generate-webui build
crossbinary: generate-webui build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate crossbinary
test: build
@@ -74,7 +77,15 @@ run-dev:
go build
./traefik
generate-webui:
mkdir -p static
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui gulp
echo 'For more informations show `webui/readme.md`' > $$PWD/static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md
generate-webui: build-webui
if [ ! -d "static" ]; then \
mkdir -p static; \
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui gulp; \
echo 'For more informations show `webui/readme.md`' > $$PWD/static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md; \
fi
lint:
$(foreach file,$(SRCS),golint $(file) || exit;)
fmt:
gofmt -s -l -w $(SRCS)

View File

@@ -1,13 +1,13 @@
![Træfɪk](http://traefik.github.io/traefik.logo.svg "Træfɪk")
___
[![Circle CI](https://circleci.com/gh/emilevauge/traefik.svg?style=shield&circle-token=:circle-token)](https://circleci.com/gh/emilevauge/traefik)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/EmileVauge/traefik/blob/master/LICENSE.md)
[![Circle CI](https://circleci.com/gh/emilevauge/traefik/tree/master.png?circle-token)](https://circleci.com/gh/emilevauge/traefik)
[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://github.com/EmileVauge/traefik/blob/master/LICENSE.md)
[![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)
Træfɪk is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
It supports several backends ([Docker :whale:](https://www.docker.com/), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [Consul](https://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 :whale:](https://www.docker.com/), [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.
## Features
@@ -22,11 +22,12 @@ It supports several backends ([Docker :whale:](https://www.docker.com/), [Mesos/
- Circuit breakers on backends
- Round Robin, rebalancer load-balancers
- Rest Metrics
- Tiny docker image included
- Tiny docker image included [![Image Layers](https://badge.imagelayers.io/emilevauge/traefik:latest.svg)](https://imagelayers.io/?images=emilevauge/traefik:latest 'Image Layers')
- SSL backends support
- SSL frontend support
- Clean AngularJS Web UI
- Websocket support
- HTTP/2 support
## Demo
@@ -43,7 +44,7 @@ You can access to a simple HTML frontend of Træfik.
## Plumbing
- [Oxy](https://github.com/mailgun/oxy/): an awsome proxy library made by Mailgun guys
- [Oxy](https://github.com/vulcand/oxy): an awsome proxy library made by Mailgun guys
- [Gorilla mux](https://github.com/gorilla/mux): famous request router
- [Negroni](https://github.com/codegangsta/negroni): web middlewares made simple
- [Manners](https://github.com/mailgun/manners): graceful shutdown of http.Handler servers
@@ -76,6 +77,20 @@ You can find the complete documentation [here](docs/index.md).
Refer to the [benchmarks section](docs/index.md#benchmarks) in the documentation.
## 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)
- Project [Mantl](https://mantl.io/) from Cisco
![Web UI Providers](docs/img/mantl-logo.png)
> Mantl is a modern platform for rapidly deploying globally distributed services. A container orchestrator, docker, a network stack, something to pool your logs, something to monitor health, a sprinkle of service discovery and some automation.
- Project [Apollo](http://capgemini.github.io/devops/apollo/) from Cap Gemini
![Web UI Providers](docs/img/apollo-logo.png)
> Apollo is an open source project to aid with building and deploying IAAS and PAAS services. It is particularly geared towards managing containerized applications across multiple hosts, and big data type workloads. Apollo leverages other open source components to provide basic mechanisms for deployment, maintenance, and scaling of infrastructure and applications.
## Contributing
### Building

View File

@@ -7,7 +7,6 @@ import (
"net/http"
log "github.com/Sirupsen/logrus"
"github.com/gorilla/mux"
)
// OxyLogger implements oxy Logger interface with logrus.
@@ -33,10 +32,3 @@ func notFoundHandler(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
//templatesRenderer.HTML(w, http.StatusNotFound, "notFound", nil)
}
// LoadDefaultConfig returns a default gorrilla.mux router from the specified configuration.
func LoadDefaultConfig(globalConfiguration GlobalConfiguration) *mux.Router {
router := mux.NewRouter()
router.NotFoundHandler = http.HandlerFunc(notFoundHandler)
return router
}

View File

@@ -1,4 +1,4 @@
FROM golang:1.5.3
FROM golang:1.6
RUN go get github.com/Masterminds/glide
RUN go get github.com/mitchellh/gox

16
cmd.go
View File

@@ -4,6 +4,7 @@ Copyright
package main
import (
"encoding/json"
fmtlog "log"
"os"
"strings"
@@ -14,6 +15,7 @@ import (
"github.com/emilevauge/traefik/provider"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"net/http"
)
var traefikCmd = &cobra.Command{
@@ -48,6 +50,7 @@ var arguments = struct {
boltdb bool
}{
GlobalConfiguration{
EntryPoints: make(EntryPoints),
Docker: &provider.Docker{
TLS: &provider.DockerTLS{},
},
@@ -77,9 +80,11 @@ func init() {
traefikCmd.PersistentFlags().StringP("graceTimeOut", "g", "10", "Timeout in seconds. Duration to give active requests a chance to finish during hot-reloads")
traefikCmd.PersistentFlags().String("accessLogsFile", "log/access.log", "Access logs file")
traefikCmd.PersistentFlags().String("traefikLogsFile", "log/traefik.log", "Traefik logs file")
traefikCmd.PersistentFlags().Var(&arguments.Certificates, "certificates", "SSL certificates and keys pair, ie 'tests/traefik.crt,tests/traefik.key'. You may add several certificate/key pairs to terminate HTTPS for multiple domain names using TLS SNI")
traefikCmd.PersistentFlags().Var(&arguments.EntryPoints, "entryPoints", "Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'")
traefikCmd.PersistentFlags().Var(&arguments.DefaultEntryPoints, "defaultEntryPoints", "Entrypoints to be used by frontends that do not specify any entrypoint")
traefikCmd.PersistentFlags().StringP("logLevel", "l", "ERROR", "Log level")
traefikCmd.PersistentFlags().DurationVar(&arguments.ProvidersThrottleDuration, "providersThrottleDuration", time.Duration(2*time.Second), "Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time.")
traefikCmd.PersistentFlags().Int("maxIdleConnsPerHost", 0, "If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used")
traefikCmd.PersistentFlags().BoolVar(&arguments.web, "web", false, "Enable Web backend")
traefikCmd.PersistentFlags().StringVar(&arguments.Web.Address, "web.address", ":8080", "Web administration port")
@@ -136,12 +141,13 @@ func init() {
viper.BindPFlag("configFile", traefikCmd.PersistentFlags().Lookup("configFile"))
viper.BindPFlag("port", traefikCmd.PersistentFlags().Lookup("port"))
viper.BindPFlag("graceTimeOut", traefikCmd.PersistentFlags().Lookup("graceTimeOut"))
// viper.BindPFlag("certificates", TraefikCmd.PersistentFlags().Lookup("certificates"))
//viper.BindPFlag("defaultEntryPoints", traefikCmd.PersistentFlags().Lookup("defaultEntryPoints"))
viper.BindPFlag("logLevel", traefikCmd.PersistentFlags().Lookup("logLevel"))
// TODO: wait for this issue to be corrected: https://github.com/spf13/viper/issues/105
viper.BindPFlag("providersThrottleDuration", traefikCmd.PersistentFlags().Lookup("providersThrottleDuration"))
viper.SetDefault("certificates", &Certificates{})
viper.BindPFlag("maxIdleConnsPerHost", traefikCmd.PersistentFlags().Lookup("maxIdleConnsPerHost"))
viper.SetDefault("providersThrottleDuration", time.Duration(2*time.Second))
viper.SetDefault("logLevel", "ERROR")
}
func run() {
@@ -150,6 +156,7 @@ func run() {
// load global configuration
globalConfiguration := LoadConfiguration()
http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = globalConfiguration.MaxIdleConnsPerHost
loggerMiddleware := middlewares.NewLogger(globalConfiguration.AccessLogsFile)
defer loggerMiddleware.Close()
@@ -172,7 +179,8 @@ func run() {
} else {
log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableSorting: true})
}
log.Debugf("Global configuration loaded %+v", globalConfiguration)
jsonConf, _ := json.Marshal(globalConfiguration)
log.Debugf("Global configuration loaded %s", string(jsonConf))
server := NewServer(*globalConfiguration)
server.Start()
defer server.Close()

View File

@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
fmtlog "log"
"regexp"
"strings"
"time"
@@ -16,13 +17,14 @@ import (
// GlobalConfiguration holds global configuration (with providers, etc.).
// It's populated from the traefik configuration file passed as an argument to the binary.
type GlobalConfiguration struct {
Port string
GraceTimeOut int64
AccessLogsFile string
TraefikLogsFile string
Certificates Certificates
LogLevel string
EntryPoints EntryPoints
DefaultEntryPoints DefaultEntryPoints
ProvidersThrottleDuration time.Duration
MaxIdleConnsPerHost int
Docker *provider.Docker
File *provider.File
Web *WebProvider
@@ -33,6 +35,110 @@ type GlobalConfiguration struct {
Boltdb *provider.BoltDb
}
// DefaultEntryPoints holds default entry points
type DefaultEntryPoints []string
// String is the method to format the flag's value, part of the flag.Value interface.
// The String method's output will be used in diagnostics.
func (dep *DefaultEntryPoints) String() string {
return fmt.Sprintf("%#v", dep)
}
// Set is the method to set the flag value, part of the flag.Value interface.
// Set's argument is a string to be parsed to set the flag.
// It's a comma-separated list, so we split it.
func (dep *DefaultEntryPoints) Set(value string) error {
entrypoints := strings.Split(value, ",")
if len(entrypoints) == 0 {
return errors.New("Bad DefaultEntryPoints format: " + value)
}
for _, entrypoint := range entrypoints {
*dep = append(*dep, entrypoint)
}
return nil
}
// Type is type of the struct
func (dep *DefaultEntryPoints) Type() string {
return fmt.Sprint("defaultentrypoints²")
}
// EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...)
type EntryPoints map[string]*EntryPoint
// String is the method to format the flag's value, part of the flag.Value interface.
// The String method's output will be used in diagnostics.
func (ep *EntryPoints) String() string {
return ""
}
// Set is the method to set the flag value, part of the flag.Value interface.
// Set's argument is a string to be parsed to set the flag.
// It's a comma-separated list, so we split it.
func (ep *EntryPoints) Set(value string) error {
regex := regexp.MustCompile("(?:Name:(?P<Name>\\S*))\\s*(?:Address:(?P<Address>\\S*))?\\s*(?:TLS:(?P<TLS>\\S*))?\\s*(?:Redirect.EntryPoint:(?P<RedirectEntryPoint>\\S*))?\\s*(?:Redirect.Regex:(?P<RedirectRegex>\\S*))?\\s*(?:Redirect.Replacement:(?P<RedirectReplacement>\\S*))?")
match := regex.FindAllStringSubmatch(value, -1)
if match == nil {
return errors.New("Bad EntryPoints format: " + value)
}
matchResult := match[0]
result := make(map[string]string)
for i, name := range regex.SubexpNames() {
if i != 0 {
result[name] = matchResult[i]
}
}
var tls *TLS
if len(result["TLS"]) > 0 {
certs := Certificates{}
certs.Set(result["TLS"])
tls = &TLS{
Certificates: certs,
}
}
var redirect *Redirect
if len(result["RedirectEntryPoint"]) > 0 || len(result["RedirectRegex"]) > 0 || len(result["RedirectReplacement"]) > 0 {
redirect = &Redirect{
EntryPoint: result["RedirectEntryPoint"],
Regex: result["RedirectRegex"],
Replacement: result["RedirectReplacement"],
}
}
(*ep)[result["Name"]] = &EntryPoint{
Address: result["Address"],
TLS: tls,
Redirect: redirect,
}
return nil
}
// Type is type of the struct
func (ep *EntryPoints) Type() string {
return fmt.Sprint("entrypoints²")
}
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
type EntryPoint struct {
Network string
Address string
TLS *TLS
Redirect *Redirect
}
// Redirect configures a redirection of an entry point to another, or to an URL
type Redirect struct {
EntryPoint string
Regex string
Replacement string
}
// TLS configures TLS for an entry point
type TLS struct {
Certificates Certificates
}
// Certificates defines traefik certificates type
type Certificates []Certificate
@@ -73,14 +179,7 @@ type Certificate struct {
// NewGlobalConfiguration returns a GlobalConfiguration with default values.
func NewGlobalConfiguration() *GlobalConfiguration {
globalConfiguration := new(GlobalConfiguration)
// default values
globalConfiguration.Port = ":80"
globalConfiguration.GraceTimeOut = 10
globalConfiguration.LogLevel = "ERROR"
globalConfiguration.ProvidersThrottleDuration = time.Duration(2 * time.Second)
return globalConfiguration
return new(GlobalConfiguration)
}
// LoadConfiguration returns a GlobalConfiguration.
@@ -100,8 +199,12 @@ func LoadConfiguration() *GlobalConfiguration {
if err := viper.ReadInConfig(); err != nil {
fmtlog.Fatalf("Error reading file: %s", err)
}
if len(arguments.Certificates) > 0 {
viper.Set("certificates", arguments.Certificates)
if len(arguments.EntryPoints) > 0 {
viper.Set("entryPoints", arguments.EntryPoints)
}
if len(arguments.DefaultEntryPoints) > 0 {
viper.Set("defaultEntryPoints", arguments.DefaultEntryPoints)
}
if arguments.web {
viper.Set("web", arguments.Web)
@@ -134,6 +237,19 @@ func LoadConfiguration() *GlobalConfiguration {
fmtlog.Fatalf("Error reading file: %s", err)
}
if len(configuration.EntryPoints) == 0 {
configuration.EntryPoints = make(map[string]*EntryPoint)
configuration.EntryPoints["http"] = &EntryPoint{
Address: ":80",
}
configuration.DefaultEntryPoints = []string{"http"}
}
if configuration.File != nil && len(configuration.File.Filename) == 0 {
// no filename, setting to global config file
configuration.File.Filename = viper.ConfigFileUsed()
}
return configuration
}

BIN
docs/img/apollo-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
docs/img/mantl-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -15,6 +15,7 @@ ___
- [Etcd backend](#etcd)
- [Zookeeper backend](#zk)
- [Boltdb backend](#boltdb)
- [Atomic configuration changes](#atomicconfig)
- [Benchmarks](#benchmarks)
@@ -39,8 +40,7 @@ Frontends can be defined using the following rules:
A frontend is a set of rules that forwards the incoming http traffic to a backend.
You can optionally enable `passHostHeader` to
- []forward client `Host` header to the backend.
You can optionally enable `passHostHeader` to forward client `Host` header to the backend.
### HTTP Backends
@@ -95,65 +95,64 @@ Usage:
Available Commands:
version Print version
help Help about any command
Flags:
--accessLogsFile="log/access.log": Access logs file
--boltdb=false: Enable Boltdb backend
--boltdb.endpoint="127.0.0.1:4001": Boltdb server endpoint
--boltdb.filename="": Override default configuration template. For advanced users :)
--boltdb.prefix="/traefik": Prefix used for KV store
--boltdb.watch=true: Watch provider
--certificates=: SSL certificates and keys. You may add several certificate/key pairs to terminate HTTPS for multiple domain names using TLS SNI
-c, --configFile="": Configuration file to use (TOML, JSON, YAML, HCL).
--consul=false: Enable Consul backend
--consul.endpoint="127.0.0.1:8500": Consul server endpoint
--consul.filename="": Override default configuration template. For advanced users :)
--consul.prefix="/traefik": Prefix used for KV store
--consul.watch=true: Watch provider
--docker=false: Enable Docker backend
--docker.domain="": Default domain used
--docker.endpoint="unix:///var/run/docker.sock": Docker server endpoint. Can be a tcp or a unix socket endpoint
--docker.filename="": Override default configuration template. For advanced users :)
--docker.tls=false: Enable Docker TLS support
--docker.tls.ca="": TLS CA
--docker.tls.cert="": TLS cert
--docker.tls.insecureSkipVerify=false: TLS insecure skip verify
--docker.tls.key="": TLS key
--docker.watch=true: Watch provider
--etcd=false: Enable Etcd backend
--etcd.endpoint="127.0.0.1:4001": Etcd server endpoint
--etcd.filename="": Override default configuration template. For advanced users :)
--etcd.prefix="/traefik": Prefix used for KV store
--etcd.watch=true: Watch provider
--file=false: Enable File backend
--file.filename="": Override default configuration template. For advanced users :)
--file.watch=true: Watch provider
-g, --graceTimeOut="10": Timeout in seconds. Duration to give active requests a chance to finish during hot-reloads
-h, --help=false: help for traefik
-l, --logLevel="ERROR": Log level
--marathon=false: Enable Marathon backend
--marathon.domain="": Default domain used
--marathon.endpoint="http://127.0.0.1:8080": Marathon server endpoint. You can also specify multiple endpoint for Marathon
--marathon.filename="": Override default configuration template. For advanced users :)
--marathon.networkInterface="eth0": Network interface used to call Marathon web services. Needed in case of multiple network interfaces
--marathon.watch=true: Watch provider
-p, --port=":80": Reverse proxy port
--providersThrottleDuration=2s: Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time.
--traefikLogsFile="log/traefik.log": Traefik logs file
--web=false: Enable Web backend
--web.address=":8080": Web administration port
--web.cerFile="": SSL certificate
--web.keyFile="": SSL certificate
--web.readOnly=false: Enable read only API
--zookeeper=false: Enable Zookeeper backend
--zookeeper.endpoint="127.0.0.1:2181": Zookeeper server endpoint
--zookeeper.filename="": Override default configuration template. For advanced users :)
--zookeeper.prefix="/traefik": Prefix used for KV store
--zookeeper.watch=true: Watch provider
--accessLogsFile string Access logs file (default "log/access.log")
--boltdb Enable Boltdb backend
--boltdb.endpoint string Boltdb server endpoint (default "127.0.0.1:4001")
--boltdb.filename string Override default configuration template. For advanced users :)
--boltdb.prefix string Prefix used for KV store (default "/traefik")
--boltdb.watch Watch provider (default true)
-c, --configFile string Configuration file to use (TOML, JSON, YAML, HCL).
--consul Enable Consul backend
--consul.endpoint string Consul server endpoint (default "127.0.0.1:8500")
--consul.filename string Override default configuration template. For advanced users :)
--consul.prefix string Prefix used for KV store (default "/traefik")
--consul.watch Watch provider (default true)
--defaultEntryPoints value Entrypoints to be used by frontends that do not specify any entrypoint (default &main.DefaultEntryPoints(nil))
--docker Enable Docker backend
--docker.domain string Default domain used
--docker.endpoint string Docker server endpoint. Can be a tcp or a unix socket endpoint (default "unix:///var/run/docker.sock")
--docker.filename string Override default configuration template. For advanced users :)
--docker.tls Enable Docker TLS support
--docker.tls.ca string TLS CA
--docker.tls.cert string TLS cert
--docker.tls.insecureSkipVerify TLS insecure skip verify
--docker.tls.key string TLS key
--docker.watch Watch provider (default true)
--entryPoints value Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'
--etcd Enable Etcd backend
--etcd.endpoint string Etcd server endpoint (default "127.0.0.1:4001")
--etcd.filename string Override default configuration template. For advanced users :)
--etcd.prefix string Prefix used for KV store (default "/traefik")
--etcd.watch Watch provider (default true)
--file Enable File backend
--file.filename string Override default configuration template. For advanced users :)
--file.watch Watch provider (default true)
-g, --graceTimeOut string Timeout in seconds. Duration to give active requests a chance to finish during hot-reloads (default "10")
-l, --logLevel string Log level (default "ERROR")
--marathon Enable Marathon backend
--marathon.domain string Default domain used
--marathon.endpoint string Marathon server endpoint. You can also specify multiple endpoint for Marathon (default "http://127.0.0.1:8080")
--marathon.filename string Override default configuration template. For advanced users :)
--marathon.networkInterface string Network interface used to call Marathon web services. Needed in case of multiple network interfaces (default "eth0")
--marathon.watch Watch provider (default true)
--maxIdleConnsPerHost int If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used
-p, --port string Reverse proxy port (default ":80")
--providersThrottleDuration duration Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time. (default 2s)
--traefikLogsFile string Traefik logs file (default "log/traefik.log")
--web Enable Web backend
--web.address string Web administration port (default ":8080")
--web.cerFile string SSL certificate
--web.keyFile string SSL certificate
--web.readOnly Enable read only API
--zookeeper Enable Zookeeper backend
--zookeeper.endpoint string Zookeeper server endpoint (default "127.0.0.1:2181")
--zookeeper.filename string Override default configuration template. For advanced users :)
--zookeeper.prefix string Prefix used for KV store (default "/traefik")
--zookeeper.watch Watch provider (default true)
Use "traefik help [command]" for more information about a command.
Use "traefik [command] --help" for more information about a command.
```
## <a id="global"></a> Global configuration
@@ -164,12 +163,45 @@ Use "traefik help [command]" for more information about a command.
# Global configuration
################################################################
# Reverse proxy port
# Entrypoints definition
#
# Optional
# Default: ":80"
# Default:
# [entryPoints]
# [entryPoints.http]
# address = ":80"
#
# port = ":80"
# To redirect an http entrypoint to an https entrypoint (with SNI support):
# [entryPoints]
# [entryPoints.http]
# address = ":80"
# [entryPoints.http.redirect]
# entryPoint = "https"
# [entryPoints.https]
# address = ":443"
# [entryPoints.https.tls]
# [[entryPoints.https.tls.certificates]]
# CertFile = "integration/fixtures/https/snitest.com.cert"
# KeyFile = "integration/fixtures/https/snitest.com.key"
# [[entryPoints.https.tls.certificates]]
# CertFile = "integration/fixtures/https/snitest.org.cert"
# KeyFile = "integration/fixtures/https/snitest.org.key"
#
# To redirect an entrypoint rewriting the URL:
# [entryPoints]
# [entryPoints.http]
# address = ":80"
# [entryPoints.http.redirect]
# regex = "^http://localhost/(.*)"
# replacement = "http://mydomain/$1"
# Entrypoints to be used by frontends that do not specify any entrypoint.
# Each frontend can specify its own entrypoints.
#
# Optional
# Default: ["http"]
#
# defaultEntryPoints = ["http", "https"]
# Timeout in seconds.
# Duration to give active requests a chance to finish during hot-reloads
@@ -199,15 +231,6 @@ Use "traefik help [command]" for more information about a command.
#
# logLevel = "ERROR"
# SSL certificates and keys
# You may add several certificate/key pairs to terminate HTTPS for multiple domain names using TLS SNI
#
# Optional
#
# [[certificates]]
# CertFile = "traefik.crt"
# KeyFile = "traefik.key"
# Backends throttle duration: minimum duration between 2 events from providers
# before applying a new configuration. It avoids unnecessary reloads if multiples events
# are sent in a short amount of time.
@@ -217,6 +240,14 @@ Use "traefik help [command]" for more information about a command.
#
# ProvidersThrottleDuration = "5s"
# If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used.
# If you encounter 'too many open files' errors, you can either change this value, or change `ulimit` value.
#
# Optional
# Default: http.DefaultMaxIdleConnsPerHost
#
# MaxIdleConnsPerHost = 200
```
@@ -228,7 +259,21 @@ Like any other reverse proxy, Træfɪk can be configured with a file. You have t
```toml
# traefik.toml
port = ":80"
defaultEntryPoints = ["http", "https"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[[entryPoints.https.tls.certificates]]
CertFile = "integration/fixtures/https/snitest.com.cert"
KeyFile = "integration/fixtures/https/snitest.com.key"
[[entryPoints.https.tls.certificates]]
CertFile = "integration/fixtures/https/snitest.org.cert"
KeyFile = "integration/fixtures/https/snitest.org.key"
graceTimeOut = 10
logLevel = "DEBUG"
@@ -264,7 +309,13 @@ logLevel = "DEBUG"
[frontends.frontend2]
backend = "backend1"
passHostHeader = true
[frontends.frontend2.routes.test_2]
entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1]
rule = "Host"
value = "{subdomain:[a-z]+}.localhost"
[frontends.frontend3]
entrypoints = ["http", "https"] # overrides defaultEntryPoints
backend = "backend2"
rule = "Path"
value = "/test"
```
@@ -273,7 +324,20 @@ logLevel = "DEBUG"
```toml
# traefik.toml
port = ":80"
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[[entryPoints.https.tls.certificates]]
CertFile = "integration/fixtures/https/snitest.com.cert"
KeyFile = "integration/fixtures/https/snitest.com.key"
[[entryPoints.https.tls.certificates]]
CertFile = "integration/fixtures/https/snitest.org.cert"
KeyFile = "integration/fixtures/https/snitest.org.key"
graceTimeOut = 10
logLevel = "DEBUG"
@@ -312,10 +376,15 @@ filename = "rules.toml"
[frontends.frontend2]
backend = "backend1"
passHostHeader = true
[frontends.frontend2.routes.test_2]
entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1]
rule = "Host"
value = "{subdomain:[a-z]+}.localhost"
[frontends.frontend3]
entrypoints = ["http", "https"] # overrides defaultEntryPoints
backend = "backend2"
rule = "Path"
value = "/test"
```
If you want Træfɪk to watch file changes automatically, just add:
@@ -528,6 +597,7 @@ Labels can be used on containers to override default behaviour:
- `traefik.frontend.rule=Host`: override the default frontend rule (Default: Host). See [frontends](#frontends).
- `traefik.frontend.value=test.example.com`: override the default frontend value (Default: `{containerName}.{domain}`) See [frontends](#frontends). Must be associated with label traefik.frontend.rule.
- `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
@@ -555,11 +625,11 @@ Træfɪk can be configured to use Marathon as a backend configuration:
#
endpoint = "http://127.0.0.1:8080"
# Network interface used to call Marathon web services
# Network interface used to call Marathon web services. Needed in case of multiple network interfaces.
# Optional
# Default: "eth0"
#
# networkInterface = "eth0"
networkInterface = "eth0"
# Enable watch Marathon changes
#
@@ -579,6 +649,21 @@ domain = "marathon.localhost"
# Optional
#
# filename = "marathon.tmpl"
# Enable Marathon basic authentication
#
# Optional
#
# [marathon.basic]
# httpBasicAuthUser = "foo"
# httpBasicPassword = "bar"
# TLS client configuration. https://golang.org/pkg/crypto/tls/#Config
#
# Optional
#
# [marathon.TLS]
# InsecureSkipVerify = true
```
Labels can be used on containers to override default behaviour:
@@ -592,6 +677,7 @@ Labels can be used on containers to override default behaviour:
- `traefik.frontend.rule=Host`: override the default frontend rule (Default: Host). See [frontends](#frontends).
- `traefik.frontend.value=test.example.com`: override the default frontend value (Default: `{appName}.{domain}`) See [frontends](#frontends). Must be associated with label traefik.frontend.rule.
- `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
## <a id="consul"></a> Consul backend
@@ -670,6 +756,7 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
|----------------------------------------------------|------------|
| `/traefik/frontends/frontend2/backend` | `backend1` |
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
| `/traefik/frontends/frontend2/entrypoints` |`http,https`|
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path` |
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
@@ -750,6 +837,7 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
|----------------------------------------------------|------------|
| `/traefik/frontends/frontend2/backend` | `backend1` |
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
| `/traefik/frontends/frontend2/entrypoints` |`http,https`|
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path` |
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
@@ -829,6 +917,7 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
|----------------------------------------------------|------------|
| `/traefik/frontends/frontend2/backend` | `backend1` |
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
| `/traefik/frontends/frontend2/entrypoints` |`http,https`|
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path` |
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
@@ -873,6 +962,44 @@ Træfɪk can be configured to use BoltDB as a backend configuration:
# filename = "boltdb.tmpl"
```
## <a id="atomicconfig"></a> Atomic configuration changes
The [Etcd](https://github.com/coreos/etcd/issues/860) and [Consul](https://github.com/hashicorp/consul/issues/886) backends do not support updating multiple keys atomically. As a result, it may be possible for Træfɪk to read an intermediate configuration state despite judicious use of the `--providersThrottleDuration` flag. To solve this problem, Træfɪk supports a special key called `/traefik/alias`. If set, Træfɪk use the value as an alternative key prefix.
Given the key structure below, Træfɪk will use the `http://172.17.0.2:80` as its only backend (frontend keys have been omitted for brevity).
| Key | Value |
|-------------------------------------------------------------------------|-----------------------------|
| `/traefik/alias` | `/traefik_configurations/1` |
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
When an atomic configuration change is required, you may write a new configuration at an alternative prefix. Here, although the `/traefik_configurations/2/...` keys have been set, the old configuration is still active because the `/traefik/alias` key still points to `/traefik_configurations/1`:
| Key | Value |
|-------------------------------------------------------------------------|-----------------------------|
| `/traefik/alias` | `/traefik_configurations/1` |
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
Once the `/traefik/alias` key is updated, the new `/traefik_configurations/2` configuration becomes active atomically. Here, we have a 50% balance between the `http://172.17.0.3:80` and the `http://172.17.0.4:80` hosts while no traffic is sent to the `172.17.0.2:80` host:
| Key | Value |
|-------------------------------------------------------------------------|-----------------------------|
| `/traefik/alias` | `/traefik_configurations/2` |
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.3:80` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.4:80` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik` prefix. Further, if the `/traefik/alias` key is set, all other sibling keys with the `/traefik` prefix are ignored.
## <a id="benchmarks"></a> Benchmarks

View File

@@ -43,7 +43,7 @@ import:
- package: github.com/alecthomas/units
ref: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
- package: github.com/gambol99/go-marathon
ref: 8ce3f764250b2de3f2c627d12ca7dd21bd5e7f93
ref: ade11d1dc2884ee1f387078fc28509559b6235d1
- package: github.com/mailgun/predicate
ref: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
- package: github.com/thoas/stats
@@ -145,7 +145,7 @@ import:
- package: github.com/donovanhide/eventsource
ref: d8a3071799b98cacd30b6da92f536050ccfe6da4
- package: github.com/golang/glog
ref: fca8c8854093a154ff1eb580aae10276ad6b1b5f
ref: fca8c8854093a154ff1eb580aae10276ad6b1b5f
- package: github.com/spf13/cast
ref: ee7b3e0353166ab1f3a605294ac8cd2b77953778
- package: github.com/mitchellh/mapstructure
@@ -161,4 +161,7 @@ import:
- package: github.com/spf13/cobra
subpackages:
- /cobra
- package: github.com/google/go-querystring/query
- package: github.com/vulcand/vulcand/plugin/rewrite
- package: github.com/stretchr/testify/mock

View File

@@ -46,9 +46,10 @@ func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) {
// 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)
// Expected no response as we did not configure anything
c.Assert(resp, checker.IsNil)
c.Assert(err, checker.NotNil)
c.Assert(err.Error(), checker.Contains, fmt.Sprintf("getsockopt: connection refused"))
}
func (s *SimpleSuite) TestWithWebConfig(c *check.C) {

View File

@@ -5,6 +5,7 @@ import (
"os/exec"
"time"
"fmt"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
@@ -19,7 +20,8 @@ func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
// TODO validate : run on 80
resp, err := http.Get("http://127.0.0.1:8000/")
// Expected a 404 as we did not comfigure anything
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 404)
// Expected no response as we did not configure anything
c.Assert(resp, checker.IsNil)
c.Assert(err, checker.NotNil)
c.Assert(err.Error(), checker.Contains, fmt.Sprintf("getsockopt: connection refused"))
}

View File

@@ -1,12 +1,9 @@
# Reverse proxy port
#
# Optional
# Default: ":80"
#
# port = ":80"
port = ":8000"
#
# LogLevel
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"
logLevel = "DEBUG"
[consul]

View File

@@ -1,12 +1,9 @@
# Reverse proxy port
#
# Optional
# Default: ":80"
#
# port = ":80"
port = ":8000"
#
# LogLevel
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"
logLevel = "DEBUG"
[docker]

View File

@@ -1,12 +1,9 @@
# Reverse proxy port
#
# Optional
# Default: ":80"
#
# port = ":80"
port = ":8000"
#
# LogLevel
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"
logLevel = "DEBUG"
[file]

View File

@@ -1,12 +1,9 @@
# Reverse proxy port
#
# Optional
# Default: ":80"
#
# port = ":80"
port = ":8000"
#
# LogLevel
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"
logLevel = "DEBUG"
[file]

View File

@@ -1,13 +1,17 @@
port = ":4443"
logLevel = "DEBUG"
[[certificates]]
CertFile = "fixtures/https/snitest.com.cert"
KeyFile = "fixtures/https/snitest.com.key"
defaultEntryPoints = ["https"]
[[certificates]]
CertFile = "fixtures/https/snitest.org.cert"
KeyFile = "fixtures/https/snitest.org.key"
[entryPoints]
[entryPoints.https]
address = ":4443"
[entryPoints.https.tls]
[[entryPoints.https.tls.certificates]]
CertFile = "fixtures/https/snitest.com.cert"
KeyFile = "fixtures/https/snitest.com.key"
[[entryPoints.https.tls.certificates]]
CertFile = "fixtures/https/snitest.org.cert"
KeyFile = "fixtures/https/snitest.org.key"
[file]

View File

@@ -1,12 +1,9 @@
# Reverse proxy port
#
# Optional
# Default: ":80"
#
# port = ":80"
port = ":8000"
#
# LogLevel
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"
logLevel = "DEBUG"
[marathon]

View File

@@ -1,9 +1,5 @@
# Reverse proxy port
#
# Optional
# Default: ":80"
#
port = ":8000"
#
# LogLevel
logLevel = "DEBUG"
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"

View File

@@ -1,6 +1,9 @@
port = ":8000"
logLevel = "DEBUG"
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"
[web]
address = ":8080"

View File

@@ -87,6 +87,9 @@ func (s *BaseSuite) createComposeProject(c *check.C, name string) {
c.Assert(err, checker.IsNil)
s.composeProject = composeProject
err = composeProject.Create()
c.Assert(err, checker.IsNil)
s.started = make(chan bool)
s.stopped = make(chan bool)
s.deleted = make(chan bool)
@@ -96,7 +99,8 @@ func (s *BaseSuite) createComposeProject(c *check.C, name string) {
composeProject.AddListener(s.listenChan)
composeProject.Start()
err = composeProject.Start()
c.Assert(err, checker.IsNil)
// Wait for compose to start
<-s.started

View File

@@ -1,6 +1,7 @@
package main
import (
"fmt"
"net/http"
"os/exec"
"time"
@@ -19,7 +20,8 @@ func (s *MarathonSuite) TestSimpleConfiguration(c *check.C) {
// 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)
// Expected no response as we did not configure anything
c.Assert(resp, checker.IsNil)
c.Assert(err, checker.NotNil)
c.Assert(err.Error(), checker.Contains, fmt.Sprintf("getsockopt: connection refused"))
}

31
middlewares/rewrite.go Normal file
View File

@@ -0,0 +1,31 @@
package middlewares
import (
log "github.com/Sirupsen/logrus"
"github.com/vulcand/vulcand/plugin/rewrite"
"net/http"
)
// Rewrite is a middleware that allows redirections
type Rewrite struct {
rewriter *rewrite.Rewrite
}
// NewRewrite creates a Rewrite middleware
func NewRewrite(regex, replacement string, redirect bool) (*Rewrite, error) {
rewriter, err := rewrite.NewRewrite(regex, replacement, false, redirect)
if err != nil {
return nil, err
}
return &Rewrite{rewriter: rewriter}, nil
}
//
func (rewrite *Rewrite) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
handler, err := rewrite.rewriter.NewHandler(next)
if err != nil {
log.Error("Error in rewrite middleware ", err)
return
}
handler.ServeHTTP(rw, r)
}

829
mocks/Marathon.go Normal file
View File

@@ -0,0 +1,829 @@
package mocks
import "github.com/gambol99/go-marathon"
import "github.com/stretchr/testify/mock"
import "net/url"
import "time"
// Marathon is a mock of marathon.Marathon
type Marathon struct {
mock.Mock
}
// ListApplications provides a mock function with given fields: _a0
func (_m *Marathon) ListApplications(_a0 url.Values) ([]string, error) {
ret := _m.Called(_a0)
var r0 []string
if rf, ok := ret.Get(0).(func(url.Values) []string); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(url.Values) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ApplicationVersions provides a mock function with given fields: name
func (_m *Marathon) ApplicationVersions(name string) (*marathon.ApplicationVersions, error) {
ret := _m.Called(name)
var r0 *marathon.ApplicationVersions
if rf, ok := ret.Get(0).(func(string) *marathon.ApplicationVersions); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.ApplicationVersions)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// HasApplicationVersion provides a mock function with given fields: name, version
func (_m *Marathon) HasApplicationVersion(name string, version string) (bool, error) {
ret := _m.Called(name, version)
var r0 bool
if rf, ok := ret.Get(0).(func(string, string) bool); ok {
r0 = rf(name, version)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string) error); ok {
r1 = rf(name, version)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SetApplicationVersion provides a mock function with given fields: name, version
func (_m *Marathon) SetApplicationVersion(name string, version *marathon.ApplicationVersion) (*marathon.DeploymentID, error) {
ret := _m.Called(name, version)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string, *marathon.ApplicationVersion) *marathon.DeploymentID); ok {
r0 = rf(name, version)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, *marathon.ApplicationVersion) error); ok {
r1 = rf(name, version)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ApplicationOK provides a mock function with given fields: name
func (_m *Marathon) ApplicationOK(name string) (bool, error) {
ret := _m.Called(name)
var r0 bool
if rf, ok := ret.Get(0).(func(string) bool); ok {
r0 = rf(name)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateApplication provides a mock function with given fields: application
func (_m *Marathon) CreateApplication(application *marathon.Application) (*marathon.Application, error) {
ret := _m.Called(application)
var r0 *marathon.Application
if rf, ok := ret.Get(0).(func(*marathon.Application) *marathon.Application); ok {
r0 = rf(application)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Application)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*marathon.Application) error); ok {
r1 = rf(application)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// DeleteApplication provides a mock function with given fields: name
func (_m *Marathon) DeleteApplication(name string) (*marathon.DeploymentID, error) {
ret := _m.Called(name)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string) *marathon.DeploymentID); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateApplication provides a mock function with given fields: application
func (_m *Marathon) UpdateApplication(application *marathon.Application) (*marathon.DeploymentID, error) {
ret := _m.Called(application)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(*marathon.Application) *marathon.DeploymentID); ok {
r0 = rf(application)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*marathon.Application) error); ok {
r1 = rf(application)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ApplicationDeployments provides a mock function with given fields: name
func (_m *Marathon) ApplicationDeployments(name string) ([]*marathon.DeploymentID, error) {
ret := _m.Called(name)
var r0 []*marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string) []*marathon.DeploymentID); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ScaleApplicationInstances provides a mock function with given fields: name, instances, force
func (_m *Marathon) ScaleApplicationInstances(name string, instances int, force bool) (*marathon.DeploymentID, error) {
ret := _m.Called(name, instances, force)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string, int, bool) *marathon.DeploymentID); ok {
r0 = rf(name, instances, force)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, int, bool) error); ok {
r1 = rf(name, instances, force)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RestartApplication provides a mock function with given fields: name, force
func (_m *Marathon) RestartApplication(name string, force bool) (*marathon.DeploymentID, error) {
ret := _m.Called(name, force)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string, bool) *marathon.DeploymentID); ok {
r0 = rf(name, force)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, bool) error); ok {
r1 = rf(name, force)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Applications provides a mock function with given fields: _a0
func (_m *Marathon) Applications(_a0 url.Values) (*marathon.Applications, error) {
ret := _m.Called(_a0)
var r0 *marathon.Applications
if rf, ok := ret.Get(0).(func(url.Values) *marathon.Applications); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Applications)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(url.Values) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Application provides a mock function with given fields: name
func (_m *Marathon) Application(name string) (*marathon.Application, error) {
ret := _m.Called(name)
var r0 *marathon.Application
if rf, ok := ret.Get(0).(func(string) *marathon.Application); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Application)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// WaitOnApplication provides a mock function with given fields: name, timeout
func (_m *Marathon) WaitOnApplication(name string, timeout time.Duration) error {
ret := _m.Called(name, timeout)
var r0 error
if rf, ok := ret.Get(0).(func(string, time.Duration) error); ok {
r0 = rf(name, timeout)
} else {
r0 = ret.Error(0)
}
return r0
}
// Tasks provides a mock function with given fields: application
func (_m *Marathon) Tasks(application string) (*marathon.Tasks, error) {
ret := _m.Called(application)
var r0 *marathon.Tasks
if rf, ok := ret.Get(0).(func(string) *marathon.Tasks); ok {
r0 = rf(application)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Tasks)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(application)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AllTasks provides a mock function with given fields: opts
func (_m *Marathon) AllTasks(opts *marathon.AllTasksOpts) (*marathon.Tasks, error) {
ret := _m.Called(opts)
var r0 *marathon.Tasks
if rf, ok := ret.Get(0).(func(*marathon.AllTasksOpts) *marathon.Tasks); ok {
r0 = rf(opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Tasks)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*marathon.AllTasksOpts) error); ok {
r1 = rf(opts)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// TaskEndpoints provides a mock function with given fields: name, port, healthCheck
func (_m *Marathon) TaskEndpoints(name string, port int, healthCheck bool) ([]string, error) {
ret := _m.Called(name, port, healthCheck)
var r0 []string
if rf, ok := ret.Get(0).(func(string, int, bool) []string); ok {
r0 = rf(name, port, healthCheck)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, int, bool) error); ok {
r1 = rf(name, port, healthCheck)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// KillApplicationTasks provides a mock function with given fields: applicationID, opts
func (_m *Marathon) KillApplicationTasks(applicationID string, opts *marathon.KillApplicationTasksOpts) (*marathon.Tasks, error) {
ret := _m.Called(applicationID, opts)
var r0 *marathon.Tasks
if rf, ok := ret.Get(0).(func(string, *marathon.KillApplicationTasksOpts) *marathon.Tasks); ok {
r0 = rf(applicationID, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Tasks)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, *marathon.KillApplicationTasksOpts) error); ok {
r1 = rf(applicationID, opts)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// KillTask provides a mock function with given fields: taskID, opts
func (_m *Marathon) KillTask(taskID string, opts *marathon.KillTaskOpts) (*marathon.Task, error) {
ret := _m.Called(taskID, opts)
var r0 *marathon.Task
if rf, ok := ret.Get(0).(func(string, *marathon.KillTaskOpts) *marathon.Task); ok {
r0 = rf(taskID, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Task)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, *marathon.KillTaskOpts) error); ok {
r1 = rf(taskID, opts)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// KillTasks provides a mock function with given fields: taskIDs, opts
func (_m *Marathon) KillTasks(taskIDs []string, opts *marathon.KillTaskOpts) error {
ret := _m.Called(taskIDs, opts)
var r0 error
if rf, ok := ret.Get(0).(func([]string, *marathon.KillTaskOpts) error); ok {
r0 = rf(taskIDs, opts)
} else {
r0 = ret.Error(0)
}
return r0
}
// Groups provides a mock function with given fields:
func (_m *Marathon) Groups() (*marathon.Groups, error) {
ret := _m.Called()
var r0 *marathon.Groups
if rf, ok := ret.Get(0).(func() *marathon.Groups); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Groups)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Group provides a mock function with given fields: name
func (_m *Marathon) Group(name string) (*marathon.Group, error) {
ret := _m.Called(name)
var r0 *marathon.Group
if rf, ok := ret.Get(0).(func(string) *marathon.Group); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Group)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateGroup provides a mock function with given fields: group
func (_m *Marathon) CreateGroup(group *marathon.Group) error {
ret := _m.Called(group)
var r0 error
if rf, ok := ret.Get(0).(func(*marathon.Group) error); ok {
r0 = rf(group)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteGroup provides a mock function with given fields: name
func (_m *Marathon) DeleteGroup(name string) (*marathon.DeploymentID, error) {
ret := _m.Called(name)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string) *marathon.DeploymentID); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateGroup provides a mock function with given fields: id, group
func (_m *Marathon) UpdateGroup(id string, group *marathon.Group) (*marathon.DeploymentID, error) {
ret := _m.Called(id, group)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string, *marathon.Group) *marathon.DeploymentID); ok {
r0 = rf(id, group)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, *marathon.Group) error); ok {
r1 = rf(id, group)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// HasGroup provides a mock function with given fields: name
func (_m *Marathon) HasGroup(name string) (bool, error) {
ret := _m.Called(name)
var r0 bool
if rf, ok := ret.Get(0).(func(string) bool); ok {
r0 = rf(name)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// WaitOnGroup provides a mock function with given fields: name, timeout
func (_m *Marathon) WaitOnGroup(name string, timeout time.Duration) error {
ret := _m.Called(name, timeout)
var r0 error
if rf, ok := ret.Get(0).(func(string, time.Duration) error); ok {
r0 = rf(name, timeout)
} else {
r0 = ret.Error(0)
}
return r0
}
// Deployments provides a mock function with given fields:
func (_m *Marathon) Deployments() ([]*marathon.Deployment, error) {
ret := _m.Called()
var r0 []*marathon.Deployment
if rf, ok := ret.Get(0).(func() []*marathon.Deployment); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*marathon.Deployment)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// DeleteDeployment provides a mock function with given fields: id, force
func (_m *Marathon) DeleteDeployment(id string, force bool) (*marathon.DeploymentID, error) {
ret := _m.Called(id, force)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string, bool) *marathon.DeploymentID); ok {
r0 = rf(id, force)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, bool) error); ok {
r1 = rf(id, force)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// HasDeployment provides a mock function with given fields: id
func (_m *Marathon) HasDeployment(id string) (bool, error) {
ret := _m.Called(id)
var r0 bool
if rf, ok := ret.Get(0).(func(string) bool); ok {
r0 = rf(id)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// WaitOnDeployment provides a mock function with given fields: id, timeout
func (_m *Marathon) WaitOnDeployment(id string, timeout time.Duration) error {
ret := _m.Called(id, timeout)
var r0 error
if rf, ok := ret.Get(0).(func(string, time.Duration) error); ok {
r0 = rf(id, timeout)
} else {
r0 = ret.Error(0)
}
return r0
}
// Subscriptions provides a mock function with given fields:
func (_m *Marathon) Subscriptions() (*marathon.Subscriptions, error) {
ret := _m.Called()
var r0 *marathon.Subscriptions
if rf, ok := ret.Get(0).(func() *marathon.Subscriptions); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Subscriptions)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AddEventsListener provides a mock function with given fields: channel, filter
func (_m *Marathon) AddEventsListener(channel marathon.EventsChannel, filter int) error {
ret := _m.Called(channel, filter)
var r0 error
if rf, ok := ret.Get(0).(func(marathon.EventsChannel, int) error); ok {
r0 = rf(channel, filter)
} else {
r0 = ret.Error(0)
}
return r0
}
// RemoveEventsListener provides a mock function with given fields: channel
func (_m *Marathon) RemoveEventsListener(channel marathon.EventsChannel) {
_m.Called(channel)
}
// Unsubscribe provides a mock function with given fields: _a0
func (_m *Marathon) Unsubscribe(_a0 string) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetMarathonURL provides a mock function with given fields:
func (_m *Marathon) GetMarathonURL() string {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// Ping provides a mock function with given fields:
func (_m *Marathon) Ping() (bool, error) {
ret := _m.Called()
var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Info provides a mock function with given fields:
func (_m *Marathon) Info() (*marathon.Info, error) {
ret := _m.Called()
var r0 *marathon.Info
if rf, ok := ret.Get(0).(func() *marathon.Info); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Info)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Leader provides a mock function with given fields:
func (_m *Marathon) Leader() (string, error) {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AbdicateLeader provides a mock function with given fields:
func (_m *Marathon) AbdicateLeader() (string, error) {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@@ -107,6 +107,7 @@ func (provider *Docker) loadDockerConfig(containersInspected []docker.Container)
"getDomain": provider.getDomain,
"getProtocol": provider.getProtocol,
"getPassHostHeader": provider.getPassHostHeader,
"getEntryPoints": provider.getEntryPoints,
"getFrontendValue": provider.getFrontendValue,
"getFrontendRule": provider.getFrontendRule,
"replace": replace,
@@ -234,6 +235,13 @@ func (provider *Docker) getPassHostHeader(container docker.Container) string {
return "false"
}
func (provider *Docker) getEntryPoints(container docker.Container) []string {
if entryPoints, err := getLabel(container, "traefik.frontend.entryPoints"); err == nil {
return strings.Split(entryPoints, ",")
}
return []string{}
}
func getLabel(container docker.Container, label string) (string, error) {
for key, value := range container.Config.Labels {
if key == label {

View File

@@ -682,7 +682,8 @@ func TestDockerLoadDockerConfig(t *testing.T) {
},
expectedFrontends: map[string]*types.Frontend{
`"frontend-Host-test-docker-localhost"`: {
Backend: "backend-test",
Backend: "backend-test",
EntryPoints: []string{},
Routes: map[string]types.Route{
`"route-frontend-Host-test-docker-localhost"`: {
Rule: "Host",
@@ -709,7 +710,8 @@ func TestDockerLoadDockerConfig(t *testing.T) {
Name: "test1",
Config: &docker.Config{
Labels: map[string]string{
"traefik.backend": "foobar",
"traefik.backend": "foobar",
"traefik.frontend.entryPoints": "http,https",
},
},
NetworkSettings: &docker.NetworkSettings{
@@ -736,7 +738,8 @@ func TestDockerLoadDockerConfig(t *testing.T) {
},
expectedFrontends: map[string]*types.Frontend{
`"frontend-Host-test1-docker-localhost"`: {
Backend: "backend-foobar",
Backend: "backend-foobar",
EntryPoints: []string{"http", "https"},
Routes: map[string]types.Route{
`"route-frontend-Host-test1-docker-localhost"`: {
Rule: "Host",
@@ -745,7 +748,8 @@ func TestDockerLoadDockerConfig(t *testing.T) {
},
},
`"frontend-Host-test2-docker-localhost"`: {
Backend: "backend-foobar",
Backend: "backend-foobar",
EntryPoints: []string{},
Routes: map[string]types.Route{
`"route-frontend-Host-test2-docker-localhost"`: {
Rule: "Host",

View File

@@ -70,12 +70,14 @@ func (provider *Kv) loadConfig() *types.Configuration {
templateObjects := struct {
Prefix string
}{
provider.Prefix,
// Allow `/traefik/alias` to superesede `provider.Prefix`
strings.TrimSuffix(provider.get(provider.Prefix, provider.Prefix+"/alias"), "/"),
}
var KvFuncMap = template.FuncMap{
"List": provider.list,
"Get": provider.get,
"Last": provider.last,
"List": provider.list,
"Get": provider.get,
"SplitGet": provider.splitGet,
"Last": provider.last,
}
configuration, err := provider.getConfiguration("templates/kv.tmpl", KvFuncMap, templateObjects)
@@ -89,7 +91,7 @@ func (provider *Kv) list(keys ...string) []string {
joinedKeys := strings.Join(keys, "")
keysPairs, err := provider.kvclient.List(joinedKeys)
if err != nil {
log.Error("Error getting keys: ", joinedKeys, err)
log.Errorf("Error getting keys %s %s ", joinedKeys, err)
return nil
}
directoryKeys := make(map[string]string)
@@ -100,18 +102,32 @@ func (provider *Kv) list(keys ...string) []string {
return fun.Values(directoryKeys).([]string)
}
func (provider *Kv) get(keys ...string) string {
func (provider *Kv) get(defaultValue string, keys ...string) string {
joinedKeys := strings.Join(keys, "")
keyPair, err := provider.kvclient.Get(joinedKeys)
if err != nil {
log.Error("Error getting key: ", joinedKeys, err)
return ""
log.Warnf("Error getting key %s %s, setting default %s", joinedKeys, err, defaultValue)
return defaultValue
} else if keyPair == nil {
return ""
log.Warnf("Error getting key %s, setting default %s", joinedKeys, defaultValue)
return defaultValue
}
return string(keyPair.Value)
}
func (provider *Kv) splitGet(keys ...string) []string {
joinedKeys := strings.Join(keys, "")
keyPair, err := provider.kvclient.Get(joinedKeys)
if err != nil {
log.Warnf("Error getting key %s %s, setting default empty", joinedKeys, err)
return []string{}
} else if keyPair == nil {
log.Warnf("Error getting key %s, setting default %empty", joinedKeys)
return []string{}
}
return strings.Split(string(keyPair.Value), ",")
}
func (provider *Kv) last(key string) string {
splittedKey := strings.Split(key, "/")
return splittedKey[len(splittedKey)-1]

View File

@@ -176,7 +176,7 @@ func TestKvGet(t *testing.T) {
}
for _, c := range cases {
actual := c.provider.get(c.keys...)
actual := c.provider.get("", c.keys...)
if actual != c.expected {
t.Fatalf("expected %v, got %v for %v and %v", c.expected, actual, c.keys, c.provider)
}
@@ -188,7 +188,7 @@ func TestKvGet(t *testing.T) {
Error: true,
},
}
actual := provider.get("anything")
actual := provider.get("", "anything")
if actual != "" {
t.Fatalf("Should have return nil, got %v", actual)
}

View File

@@ -4,12 +4,15 @@ import (
"errors"
"net/url"
"strconv"
"strings"
"text/template"
"crypto/tls"
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/emilevauge/traefik/types"
"github.com/gambol99/go-marathon"
"net/http"
)
// Marathon holds configuration of the Marathon provider.
@@ -19,7 +22,8 @@ type Marathon struct {
Domain string
NetworkInterface string
Basic *MarathonBasic
marathonClient lightMarathonClient
TLS *tls.Config
marathonClient marathon.Marathon
}
// MarathonBasic holds basic authentication specific configurations
@@ -43,6 +47,11 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage)
config.HTTPBasicAuthUser = provider.Basic.HTTPBasicAuthUser
config.HTTPBasicPassword = provider.Basic.HTTPBasicPassword
}
config.HTTPClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: provider.TLS,
},
}
client, err := marathon.NewClient(config)
if err != nil {
log.Errorf("Failed to create a client for marathon, error: %s", err)
@@ -80,14 +89,17 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage)
func (provider *Marathon) loadMarathonConfig() *types.Configuration {
var MarathonFuncMap = template.FuncMap{
"getPort": provider.getPort,
"getWeight": provider.getWeight,
"getDomain": provider.getDomain,
"getProtocol": provider.getProtocol,
"getPassHostHeader": provider.getPassHostHeader,
"getFrontendValue": provider.getFrontendValue,
"getFrontendRule": provider.getFrontendRule,
"replace": replace,
"getBackend": provider.getBackend,
"getPort": provider.getPort,
"getWeight": provider.getWeight,
"getDomain": provider.getDomain,
"getProtocol": provider.getProtocol,
"getPassHostHeader": provider.getPassHostHeader,
"getEntryPoints": provider.getEntryPoints,
"getFrontendValue": provider.getFrontendValue,
"getFrontendRule": provider.getFrontendRule,
"getFrontendBackend": provider.getFrontendBackend,
"replace": replace,
}
applications, err := provider.marathonClient.Applications(nil)
@@ -96,7 +108,7 @@ func (provider *Marathon) loadMarathonConfig() *types.Configuration {
return nil
}
tasks, err := provider.marathonClient.AllTasks((url.Values{"status": []string{"running"}}))
tasks, err := provider.marathonClient.AllTasks(&marathon.AllTasksOpts{Status: "running"})
if err != nil {
log.Errorf("Failed to create a client for marathon, error: %s", err)
return nil
@@ -186,7 +198,7 @@ func taskFilter(task marathon.Task, applications *marathon.Applications) bool {
//filter healthchecks
if application.HasHealthChecks() {
if task.HasHealthCheckResults() {
for _, healthcheck := range task.HealthCheckResult {
for _, healthcheck := range task.HealthCheckResults {
// found one bad healthcheck, return false
if !healthcheck.Alive {
log.Debugf("Filtering marathon task %s with bad healthcheck", task.AppID)
@@ -285,6 +297,13 @@ func (provider *Marathon) getPassHostHeader(application marathon.Application) st
return "false"
}
func (provider *Marathon) getEntryPoints(application marathon.Application) []string {
if entryPoints, err := provider.getLabel(application, "traefik.frontend.entryPoints"); err == nil {
return strings.Split(entryPoints, ",")
}
return []string{}
}
// getFrontendValue returns the frontend value for the specified application, using
// it's label. It returns a default one if the label is not present.
func (provider *Marathon) getFrontendValue(application marathon.Application) string {
@@ -302,3 +321,19 @@ func (provider *Marathon) getFrontendRule(application marathon.Application) stri
}
return "Host"
}
func (provider *Marathon) getBackend(task marathon.Task, applications []marathon.Application) string {
application, errApp := getApplication(task, applications)
if errApp != nil {
log.Errorf("Unable to get marathon application from task %s", task.AppID)
return ""
}
return provider.getFrontendBackend(application)
}
func (provider *Marathon) getFrontendBackend(application marathon.Application) string {
if label, err := provider.getLabel(application, "traefik.backend"); err == nil {
return label
}
return replace("/", "-", application.ID)
}

View File

@@ -1,34 +1,32 @@
package provider
import (
"errors"
"net/url"
"reflect"
"testing"
"errors"
"github.com/emilevauge/traefik/mocks"
"github.com/emilevauge/traefik/types"
"github.com/gambol99/go-marathon"
"github.com/stretchr/testify/mock"
)
type fakeClient struct {
applicationsError bool
applications *marathon.Applications
tasksError bool
tasks *marathon.Tasks
mocks.Marathon
}
func (c *fakeClient) Applications(url.Values) (*marathon.Applications, error) {
if c.applicationsError {
return nil, errors.New("error")
func newFakeClient(applicationsError bool, applications *marathon.Applications, tasksError bool, tasks *marathon.Tasks) *fakeClient {
// create an instance of our test object
fakeClient := new(fakeClient)
if applicationsError {
fakeClient.On("Applications", mock.AnythingOfType("url.Values")).Return(nil, errors.New("error"))
}
return c.applications, nil
}
func (c *fakeClient) AllTasks(v url.Values) (*marathon.Tasks, error) {
if c.tasksError {
return nil, errors.New("error")
fakeClient.On("Applications", mock.AnythingOfType("url.Values")).Return(applications, nil)
if tasksError {
fakeClient.On("AllTasks", mock.AnythingOfType("*marathon.AllTasksOpts")).Return(nil, errors.New("error"))
}
return c.tasks, nil
fakeClient.On("AllTasks", mock.AnythingOfType("*marathon.AllTasksOpts")).Return(tasks, nil)
return fakeClient
}
func TestMarathonLoadConfig(t *testing.T) {
@@ -84,7 +82,8 @@ func TestMarathonLoadConfig(t *testing.T) {
},
expectedFrontends: map[string]*types.Frontend{
`frontend-test`: {
Backend: "backend-test",
Backend: "backend-test",
EntryPoints: []string{},
Routes: map[string]types.Route{
`route-host-test`: {
Rule: "Host",
@@ -109,14 +108,10 @@ func TestMarathonLoadConfig(t *testing.T) {
}
for _, c := range cases {
fakeClient := newFakeClient(c.applicationsError, c.applications, c.tasksError, c.tasks)
provider := &Marathon{
Domain: "docker.localhost",
marathonClient: &fakeClient{
applicationsError: c.applicationsError,
applications: c.applications,
tasksError: c.tasksError,
tasks: c.tasks,
},
Domain: "docker.localhost",
marathonClient: fakeClient,
}
actualConfig := provider.loadMarathonConfig()
if c.expectedNil {
@@ -314,7 +309,7 @@ func TestMarathonTaskFilter(t *testing.T) {
task: marathon.Task{
AppID: "foo",
Ports: []int{80},
HealthCheckResult: []*marathon.HealthCheckResult{
HealthCheckResults: []*marathon.HealthCheckResult{
{
Alive: false,
},
@@ -337,7 +332,7 @@ func TestMarathonTaskFilter(t *testing.T) {
task: marathon.Task{
AppID: "foo",
Ports: []int{80},
HealthCheckResult: []*marathon.HealthCheckResult{
HealthCheckResults: []*marathon.HealthCheckResult{
{
Alive: true,
},
@@ -378,7 +373,7 @@ func TestMarathonTaskFilter(t *testing.T) {
task: marathon.Task{
AppID: "foo",
Ports: []int{80},
HealthCheckResult: []*marathon.HealthCheckResult{
HealthCheckResults: []*marathon.HealthCheckResult{
{
Alive: true,
},
@@ -735,6 +730,36 @@ func TestMarathonGetPassHostHeader(t *testing.T) {
}
}
func TestMarathonGetEntryPoints(t *testing.T) {
provider := &Marathon{}
applications := []struct {
application marathon.Application
expected []string
}{
{
application: marathon.Application{},
expected: []string{},
},
{
application: marathon.Application{
Labels: map[string]string{
"traefik.frontend.entryPoints": "http,https",
},
},
expected: []string{"http", "https"},
},
}
for _, a := range applications {
actual := provider.getEntryPoints(a.application)
if !reflect.DeepEqual(actual, a.expected) {
t.Fatalf("expected %#v, got %#v", a.expected, actual)
}
}
}
func TestMarathonGetFrontendValue(t *testing.T) {
provider := &Marathon{
Domain: "docker.localhost",
@@ -800,3 +825,29 @@ func TestMarathonGetFrontendRule(t *testing.T) {
}
}
}
func TestMarathonGetBackend(t *testing.T) {
provider := &Marathon{}
applications := []struct {
application marathon.Application
expected string
}{
{
application: marathon.Application{
ID: "foo",
Labels: map[string]string{
"traefik.backend": "bar",
},
},
expected: "bar",
},
}
for _, a := range applications {
actual := provider.getFrontendBackend(a.application)
if actual != a.expected {
t.Fatalf("expected %q, got %q", a.expected, actual)
}
}
}

307
server.go
View File

@@ -5,6 +5,7 @@ package main
import (
"crypto/tls"
"encoding/json"
"errors"
log "github.com/Sirupsen/logrus"
"github.com/codegangsta/negroni"
@@ -16,12 +17,12 @@ import (
"github.com/mailgun/oxy/cbreaker"
"github.com/mailgun/oxy/forward"
"github.com/mailgun/oxy/roundrobin"
"github.com/spf13/viper"
"net/http"
"net/url"
"os"
"os/signal"
"reflect"
"regexp"
"sync"
"syscall"
"time"
@@ -31,11 +32,10 @@ var oxyLogger = &OxyLogger{}
// Server is the reverse-proxy/load-balancer engine
type Server struct {
srv *manners.GracefulServer
configurationRouter *mux.Router
serverEntryPoints map[string]serverEntryPoint
configurationChan chan types.ConfigMessage
configurationChanValidated chan types.ConfigMessage
sigs chan os.Signal
configurationValidatedChan chan types.ConfigMessage
signals chan os.Signal
stopChan chan bool
providers []provider.Provider
serverLock sync.Mutex
@@ -44,16 +44,22 @@ type Server struct {
loggerMiddleware *middlewares.Logger
}
type serverEntryPoint struct {
httpServer *manners.GracefulServer
httpRouter *mux.Router
}
// NewServer returns an initialized Server.
func NewServer(globalConfiguration GlobalConfiguration) *Server {
server := new(Server)
server.serverEntryPoints = make(map[string]serverEntryPoint)
server.configurationChan = make(chan types.ConfigMessage, 10)
server.configurationChanValidated = make(chan types.ConfigMessage, 10)
server.sigs = make(chan os.Signal, 1)
server.configurationValidatedChan = make(chan types.ConfigMessage, 10)
server.signals = make(chan os.Signal, 1)
server.stopChan = make(chan bool)
server.providers = []provider.Provider{}
signal.Notify(server.sigs, syscall.SIGINT, syscall.SIGTERM)
signal.Notify(server.signals, syscall.SIGINT, syscall.SIGTERM)
server.currentConfigurations = make(configs)
server.globalConfiguration = globalConfiguration
server.loggerMiddleware = middlewares.NewLogger(globalConfiguration.AccessLogsFile)
@@ -63,38 +69,27 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
// Start starts the server and blocks until server is shutted down.
func (server *Server) Start() {
server.configurationRouter = LoadDefaultConfig(server.globalConfiguration)
go server.listenProviders()
go server.enableRouter()
go server.listenConfigurations()
server.configureProviders()
server.startProviders()
go server.listenSignals()
var er error
server.serverLock.Lock()
server.srv, er = server.prepareServer(server.configurationRouter, server.globalConfiguration, nil, server.loggerMiddleware, metrics)
if er != nil {
log.Fatal("Error preparing server: ", er)
}
go server.startServer(server.srv, server.globalConfiguration)
//TODO change that!
time.Sleep(100 * time.Millisecond)
server.serverLock.Unlock()
<-server.stopChan
}
// Stop stops the server
func (server *Server) Stop() {
server.srv.Close()
for _, serverEntryPoint := range server.serverEntryPoints {
serverEntryPoint.httpServer.Close()
}
server.stopChan <- true
}
// Close destroys the server
func (server *Server) Close() {
close(server.configurationChan)
close(server.configurationChanValidated)
close(server.sigs)
close(server.configurationValidatedChan)
close(server.signals)
close(server.stopChan)
server.loggerMiddleware.Close()
}
@@ -104,19 +99,20 @@ func (server *Server) listenProviders() {
lastConfigs := make(map[string]*types.ConfigMessage)
for {
configMsg := <-server.configurationChan
log.Debugf("Configuration receveived from provider %s: %#v", configMsg.ProviderName, configMsg.Configuration)
jsonConf, _ := json.Marshal(configMsg.Configuration)
log.Debugf("Configuration receveived from provider %s: %s", configMsg.ProviderName, string(jsonConf))
lastConfigs[configMsg.ProviderName] = &configMsg
if time.Now().After(lastReceivedConfiguration.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) {
log.Debugf("Last %s config received more than %s, OK", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
// last config received more than n s ago
server.configurationChanValidated <- configMsg
server.configurationValidatedChan <- configMsg
} else {
log.Debugf("Last %s config received less than %s, waiting...", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
go func() {
<-time.After(server.globalConfiguration.ProvidersThrottleDuration)
if time.Now().After(lastReceivedConfiguration.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) {
log.Debugf("Waited for %s config, OK", configMsg.ProviderName)
server.configurationChanValidated <- *lastConfigs[configMsg.ProviderName]
server.configurationValidatedChan <- *lastConfigs[configMsg.ProviderName]
}
}()
}
@@ -124,9 +120,9 @@ func (server *Server) listenProviders() {
}
}
func (server *Server) enableRouter() {
func (server *Server) listenConfigurations() {
for {
configMsg := <-server.configurationChanValidated
configMsg := <-server.configurationValidatedChan
if configMsg.Configuration == nil {
log.Info("Skipping empty Configuration")
} else if reflect.DeepEqual(server.currentConfigurations[configMsg.ProviderName], configMsg.Configuration) {
@@ -139,22 +135,26 @@ func (server *Server) enableRouter() {
}
newConfigurations[configMsg.ProviderName] = configMsg.Configuration
newConfigurationRouter, err := server.loadConfig(newConfigurations, server.globalConfiguration)
newServerEntryPoints, err := server.loadConfig(newConfigurations, server.globalConfiguration)
if err == nil {
server.serverLock.Lock()
server.currentConfigurations = newConfigurations
server.configurationRouter = newConfigurationRouter
oldServer := server.srv
newsrv, err := server.prepareServer(server.configurationRouter, server.globalConfiguration, oldServer, server.loggerMiddleware, metrics)
if err != nil {
log.Fatal("Error preparing server: ", err)
}
go server.startServer(newsrv, server.globalConfiguration)
server.srv = newsrv
time.Sleep(1 * time.Second)
if oldServer != nil {
log.Info("Stopping old server")
oldServer.Close()
for newServerEntryPointName, newServerEntryPoint := range newServerEntryPoints {
currentServerEntryPoint := server.serverEntryPoints[newServerEntryPointName]
server.currentConfigurations = newConfigurations
currentServerEntryPoint.httpRouter = newServerEntryPoint.httpRouter
oldServer := currentServerEntryPoint.httpServer
newsrv, err := server.prepareServer(currentServerEntryPoint.httpRouter, server.globalConfiguration.EntryPoints[newServerEntryPointName], oldServer, server.loggerMiddleware, metrics)
if err != nil {
log.Fatal("Error preparing server: ", err)
}
go server.startServer(newsrv, server.globalConfiguration)
currentServerEntryPoint.httpServer = newsrv
server.serverEntryPoints[newServerEntryPointName] = currentServerEntryPoint
time.Sleep(1 * time.Second)
if oldServer != nil {
log.Info("Stopping old server")
oldServer.Close()
}
}
server.serverLock.Unlock()
} else {
@@ -173,10 +173,6 @@ func (server *Server) configureProviders() {
server.providers = append(server.providers, server.globalConfiguration.Marathon)
}
if server.globalConfiguration.File != nil {
if len(server.globalConfiguration.File.Filename) == 0 {
// no filename, setting to global config file
server.globalConfiguration.File.Filename = viper.GetString("configFile")
}
server.providers = append(server.providers, server.globalConfiguration.File)
}
if server.globalConfiguration.Web != nil {
@@ -200,7 +196,8 @@ func (server *Server) configureProviders() {
func (server *Server) startProviders() {
// start providers
for _, provider := range server.providers {
log.Infof("Starting provider %v %+v", reflect.TypeOf(provider), provider)
jsonConf, _ := json.Marshal(provider)
log.Infof("Starting provider %v %s", reflect.TypeOf(provider), jsonConf)
currentProvider := provider
go func() {
err := currentProvider.Provide(server.configurationChan)
@@ -212,15 +209,18 @@ func (server *Server) startProviders() {
}
func (server *Server) listenSignals() {
sig := <-server.sigs
sig := <-server.signals
log.Infof("I have to go... %+v", sig)
log.Info("Stopping server")
server.Stop()
}
// creates a TLS config that allows terminating HTTPS for multiple domains using SNI
func (server *Server) createTLSConfig(certs []Certificate) (*tls.Config, error) {
if len(certs) == 0 {
func (server *Server) createTLSConfig(tlsOption *TLS) (*tls.Config, error) {
if tlsOption == nil {
return nil, nil
}
if len(tlsOption.Certificates) == 0 {
return nil, nil
}
@@ -230,8 +230,8 @@ func (server *Server) createTLSConfig(certs []Certificate) (*tls.Config, error)
}
var err error
config.Certificates = make([]tls.Certificate, len(certs))
for i, v := range certs {
config.Certificates = make([]tls.Certificate, len(tlsOption.Certificates))
for i, v := range tlsOption.Certificates {
config.Certificates[i], err = tls.LoadX509KeyPair(v.CertFile, v.KeyFile)
if err != nil {
return nil, err
@@ -244,7 +244,7 @@ func (server *Server) createTLSConfig(certs []Certificate) (*tls.Config, error)
}
func (server *Server) startServer(srv *manners.GracefulServer, globalConfiguration GlobalConfiguration) {
log.Info("Starting server")
log.Info("Starting server on ", srv.Addr)
if srv.TLSConfig != nil {
err := srv.ListenAndServeTLSWithConfig(srv.TLSConfig)
if err != nil {
@@ -259,7 +259,7 @@ func (server *Server) startServer(srv *manners.GracefulServer, globalConfigurati
log.Info("Server stopped")
}
func (server *Server) prepareServer(router *mux.Router, globalConfiguration GlobalConfiguration, oldServer *manners.GracefulServer, middlewares ...negroni.Handler) (*manners.GracefulServer, error) {
func (server *Server) prepareServer(router *mux.Router, entryPoint *EntryPoint, oldServer *manners.GracefulServer, middlewares ...negroni.Handler) (*manners.GracefulServer, error) {
log.Info("Preparing server")
// middlewares
var negroni = negroni.New()
@@ -267,7 +267,7 @@ func (server *Server) prepareServer(router *mux.Router, globalConfiguration Glob
negroni.Use(middleware)
}
negroni.UseHandler(router)
tlsConfig, err := server.createTLSConfig(globalConfiguration.Certificates)
tlsConfig, err := server.createTLSConfig(entryPoint.TLS)
if err != nil {
log.Fatalf("Error creating TLS config %s", err)
return nil, err
@@ -276,13 +276,13 @@ func (server *Server) prepareServer(router *mux.Router, globalConfiguration Glob
if oldServer == nil {
return manners.NewWithServer(
&http.Server{
Addr: globalConfiguration.Port,
Addr: entryPoint.Address,
Handler: negroni,
TLSConfig: tlsConfig,
}), nil
}
gracefulServer, err := oldServer.HijackListener(&http.Server{
Addr: globalConfiguration.Port,
Addr: entryPoint.Address,
Handler: negroni,
TLSConfig: tlsConfig,
}, tlsConfig)
@@ -293,80 +293,147 @@ func (server *Server) prepareServer(router *mux.Router, globalConfiguration Glob
return gracefulServer, nil
}
func (server *Server) buildEntryPoints(globalConfiguration GlobalConfiguration) map[string]serverEntryPoint {
serverEntryPoints := make(map[string]serverEntryPoint)
for entryPointName := range globalConfiguration.EntryPoints {
router := server.buildDefaultHTTPRouter()
serverEntryPoints[entryPointName] = serverEntryPoint{
httpRouter: router,
}
}
return serverEntryPoints
}
// LoadConfig returns a new gorilla.mux Route from the specified global configuration and the dynamic
// provider configurations.
func (server *Server) loadConfig(configurations configs, globalConfiguration GlobalConfiguration) (*mux.Router, error) {
router := mux.NewRouter()
router.NotFoundHandler = http.HandlerFunc(notFoundHandler)
func (server *Server) loadConfig(configurations configs, globalConfiguration GlobalConfiguration) (map[string]serverEntryPoint, error) {
serverEntryPoints := server.buildEntryPoints(globalConfiguration)
redirectHandlers := make(map[string]http.Handler)
backends := map[string]http.Handler{}
for _, configuration := range configurations {
for frontendName, frontend := range configuration.Frontends {
log.Infof("Creating frontend %s", frontendName)
log.Debugf("Creating frontend %s", frontendName)
fwd, _ := forward.New(forward.Logger(oxyLogger), forward.PassHostHeader(frontend.PassHostHeader))
newRoute := router.NewRoute().Name(frontendName)
for routeName, route := range frontend.Routes {
log.Infof("Creating route %s %s:%s", routeName, route.Rule, route.Value)
newRouteReflect, err := invoke(newRoute, route.Rule, route.Value)
if err != nil {
return nil, err
}
newRoute = newRouteReflect[0].Interface().(*mux.Route)
// default endpoints if not defined in frontends
if len(frontend.EntryPoints) == 0 {
frontend.EntryPoints = globalConfiguration.DefaultEntryPoints
}
if backends[frontend.Backend] == nil {
log.Infof("Creating backend %s", frontend.Backend)
var lb http.Handler
rr, _ := roundrobin.New(fwd)
if configuration.Backends[frontend.Backend] == nil {
return nil, errors.New("Backend not found: " + frontend.Backend)
for _, entryPointName := range frontend.EntryPoints {
log.Debugf("Wiring frontend %s to entryPoint %s", frontendName, entryPointName)
if _, ok := serverEntryPoints[entryPointName]; !ok {
return nil, errors.New("Undefined entrypoint: " + entryPointName)
}
lbMethod, err := types.NewLoadBalancerMethod(configuration.Backends[frontend.Backend].LoadBalancer)
if err != nil {
configuration.Backends[frontend.Backend].LoadBalancer = &types.LoadBalancer{Method: "wrr"}
}
switch lbMethod {
case types.Drr:
log.Infof("Creating load-balancer drr")
rebalancer, _ := roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger))
lb = rebalancer
for serverName, server := range configuration.Backends[frontend.Backend].Servers {
url, err := url.Parse(server.URL)
if err != nil {
return nil, err
}
log.Infof("Creating server %s %s", serverName, url.String())
rebalancer.UpsertServer(url, roundrobin.Weight(server.Weight))
}
case types.Wrr:
log.Infof("Creating load-balancer wrr")
lb = middlewares.NewWebsocketUpgrader(rr)
for serverName, server := range configuration.Backends[frontend.Backend].Servers {
url, err := url.Parse(server.URL)
if err != nil {
return nil, err
}
log.Infof("Creating server %s at %s with weight %d", serverName, url.String(), server.Weight)
rr.UpsertServer(url, roundrobin.Weight(server.Weight))
newRoute := serverEntryPoints[entryPointName].httpRouter.NewRoute().Name(frontendName)
for routeName, route := range frontend.Routes {
log.Debugf("Creating route %s %s:%s", routeName, route.Rule, route.Value)
newRouteReflect, err := invoke(newRoute, route.Rule, route.Value)
if err != nil {
return nil, err
}
newRoute = newRouteReflect[0].Interface().(*mux.Route)
}
var negroni = negroni.New()
if configuration.Backends[frontend.Backend].CircuitBreaker != nil {
log.Infof("Creating circuit breaker %s", configuration.Backends[frontend.Backend].CircuitBreaker.Expression)
negroni.Use(middlewares.NewCircuitBreaker(lb, configuration.Backends[frontend.Backend].CircuitBreaker.Expression, cbreaker.Logger(oxyLogger)))
entryPoint := globalConfiguration.EntryPoints[entryPointName]
if entryPoint.Redirect != nil {
if redirectHandlers[entryPointName] != nil {
newRoute.Handler(redirectHandlers[entryPointName])
} else if handler, err := server.loadEntryPointConfig(entryPointName, entryPoint); err != nil {
return nil, err
} else {
newRoute.Handler(handler)
redirectHandlers[entryPointName] = handler
}
} else {
negroni.UseHandler(lb)
if backends[frontend.Backend] == nil {
log.Debugf("Creating backend %s", frontend.Backend)
var lb http.Handler
rr, _ := roundrobin.New(fwd)
if configuration.Backends[frontend.Backend] == nil {
return nil, errors.New("Undefined backend: " + frontend.Backend)
}
lbMethod, err := types.NewLoadBalancerMethod(configuration.Backends[frontend.Backend].LoadBalancer)
if err != nil {
configuration.Backends[frontend.Backend].LoadBalancer = &types.LoadBalancer{Method: "wrr"}
}
switch lbMethod {
case types.Drr:
log.Debugf("Creating load-balancer drr")
rebalancer, _ := roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger))
lb = rebalancer
for serverName, server := range configuration.Backends[frontend.Backend].Servers {
url, err := url.Parse(server.URL)
if err != nil {
return nil, err
}
log.Debugf("Creating server %s at %s with weight %d", serverName, url.String(), server.Weight)
rebalancer.UpsertServer(url, roundrobin.Weight(server.Weight))
}
case types.Wrr:
log.Debugf("Creating load-balancer wrr")
lb = middlewares.NewWebsocketUpgrader(rr)
for serverName, server := range configuration.Backends[frontend.Backend].Servers {
url, err := url.Parse(server.URL)
if err != nil {
return nil, err
}
log.Debugf("Creating server %s at %s with weight %d", serverName, url.String(), server.Weight)
rr.UpsertServer(url, roundrobin.Weight(server.Weight))
}
}
var negroni = negroni.New()
if configuration.Backends[frontend.Backend].CircuitBreaker != nil {
log.Debugf("Creating circuit breaker %s", configuration.Backends[frontend.Backend].CircuitBreaker.Expression)
negroni.Use(middlewares.NewCircuitBreaker(lb, configuration.Backends[frontend.Backend].CircuitBreaker.Expression, cbreaker.Logger(oxyLogger)))
} else {
negroni.UseHandler(lb)
}
backends[frontend.Backend] = negroni
} else {
log.Debugf("Reusing backend %s", frontend.Backend)
}
newRoute.Handler(backends[frontend.Backend])
}
err := newRoute.GetError()
if err != nil {
log.Errorf("Error building route: %s", err)
}
backends[frontend.Backend] = negroni
} else {
log.Infof("Reusing backend %s", frontend.Backend)
}
// stream.New(backends[frontend.Backend], stream.Retry("IsNetworkError() && Attempts() <= " + strconv.Itoa(globalConfiguration.Replay)), stream.Logger(oxyLogger))
newRoute.Handler(backends[frontend.Backend])
err := newRoute.GetError()
if err != nil {
log.Errorf("Error building route: %s", err)
}
}
}
return router, nil
return serverEntryPoints, nil
}
func (server *Server) loadEntryPointConfig(entryPointName string, entryPoint *EntryPoint) (http.Handler, error) {
regex := entryPoint.Redirect.Regex
replacement := entryPoint.Redirect.Replacement
if len(entryPoint.Redirect.EntryPoint) > 0 {
regex = "^(?:https?:\\/\\/)?([\\da-z\\.-]+)(?::\\d+)(.*)$"
if server.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint] == nil {
return nil, errors.New("Unkown entrypoint " + entryPoint.Redirect.EntryPoint)
}
protocol := "http"
if server.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint].TLS != nil {
protocol = "https"
}
r, _ := regexp.Compile("(:\\d+)")
match := r.FindStringSubmatch(server.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint].Address)
if len(match) == 0 {
return nil, errors.New("Bad Address format: " + server.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint].Address)
}
replacement = protocol + "://$1" + match[0] + "$2"
}
rewrite, err := middlewares.NewRewrite(regex, replacement, true)
if err != nil {
return nil, err
}
log.Debugf("Creating entryPoint redirect %s -> %s : %s -> %s", entryPointName, entryPoint.Redirect.EntryPoint, regex, replacement)
negroni := negroni.New()
negroni.Use(rewrite)
return negroni, nil
}
func (server *Server) buildDefaultHTTPRouter() *mux.Router {
router := mux.NewRouter()
router.NotFoundHandler = http.HandlerFunc(notFoundHandler)
return router
}

View File

@@ -8,6 +8,9 @@
[frontends."frontend-{{$frontend}}"]{{$container := index $containers 0}}
backend = "backend-{{getBackend $container}}"
passHostHeader = {{getPassHostHeader $container}}
entryPoints = [{{range getEntryPoints $container}}
"{{.}}",
{{end}}]
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
rule = "{{getFrontendRule $container}}"
value = "{{getFrontendValue $container}}"

View File

@@ -5,13 +5,13 @@
{{$backend := .}}
{{$servers := List $backend "/servers/" }}
{{$circuitBreaker := Get . "/circuitbreaker/" "expression"}}
{{$circuitBreaker := Get "" . "/circuitbreaker/" "expression"}}
{{with $circuitBreaker}}
[backends.{{Last $backend}}.circuitBreaker]
expression = "{{$circuitBreaker}}"
{{end}}
{{$loadBalancer := Get . "/loadbalancer/" "method"}}
{{$loadBalancer := Get "" . "/loadbalancer/" "method"}}
{{with $loadBalancer}}
[backends.{{Last $backend}}.loadBalancer]
method = "{{$loadBalancer}}"
@@ -19,20 +19,24 @@
{{range $servers}}
[backends.{{Last $backend}}.servers.{{Last .}}]
url = "{{Get . "/url"}}"
weight = {{Get . "/weight"}}
url = "{{Get "" . "/url"}}"
weight = {{Get "" . "/weight"}}
{{end}}
{{end}}
[frontends]{{range $frontends}}
{{$frontend := Last .}}
{{$entryPoints := SplitGet . "/entrypoints"}}
[frontends.{{$frontend}}]
backend = "{{Get . "/backend"}}"
passHostHeader = {{Get . "/passHostHeader"}}
backend = "{{Get "" . "/backend"}}"
passHostHeader = {{Get "false" . "/passHostHeader"}}
entryPoints = [{{range $entryPoints}}
"{{.}}",
{{end}}]
{{$routes := List . "/routes/"}}
{{range $routes}}
[frontends.{{$frontend}}.routes.{{Last .}}]
rule = "{{Get . "/rule"}}"
value = "{{Get . "/value"}}"
rule = "{{Get "" . "/rule"}}"
value = "{{Get "" . "/value"}}"
{{end}}
{{end}}

View File

@@ -1,14 +1,17 @@
{{$apps := .Applications}}
[backends]{{range .Tasks}}
[backends.backend{{.AppID | replace "/" "-"}}.servers.server-{{.ID | replace "." "-"}}]
[backends.backend{{getBackend . $apps}}.servers.server-{{.ID | replace "." "-"}}]
url = "{{getProtocol . $apps}}://{{.Host}}:{{getPort . $apps}}"
weight = {{getWeight . $apps}}
{{end}}
[frontends]{{range .Applications}}
[frontends.frontend{{.ID | replace "/" "-"}}]
backend = "backend{{.ID | replace "/" "-"}}"
backend = "backend{{getFrontendBackend .}}"
passHostHeader = {{getPassHostHeader .}}
entryPoints = [{{range getEntryPoints .}}
"{{.}}",
{{end}}]
[frontends.frontend{{.ID | replace "/" "-"}}.routes.route-host{{.ID | replace "/" "-"}}]
rule = "{{getFrontendRule .}}"
value = "{{getFrontendValue .}}"

View File

@@ -31,6 +31,7 @@ slave:
- /usr/bin/docker:/usr/bin/docker:ro
- /usr/lib/x86_64-linux-gnu/libapparmor.so.1:/usr/lib/x86_64-linux-gnu/libapparmor.so.1:ro
- /var/run/docker.sock:/var/run/docker.sock
- /lib/x86_64-linux-gnu/libsystemd-journal.so.0:/lib/x86_64-linux-gnu/libsystemd-journal.so.0
marathon:
image: mesosphere/marathon:v0.13.0

View File

@@ -16,10 +16,12 @@ curl -i -H "Accept: application/json" -X PUT -d "2" ht
# frontend 1
curl -i -H "Accept: application/json" -X PUT -d "backend2" http://localhost:8500/v1/kv/traefik/frontends/frontend1/backend
curl -i -H "Accept: application/json" -X PUT -d "http" http://localhost:8500/v1/kv/traefik/frontends/frontend1/entrypoints
curl -i -H "Accept: application/json" -X PUT -d "Host" http://localhost:8500/v1/kv/traefik/frontends/frontend1/routes/test_1/rule
curl -i -H "Accept: application/json" -X PUT -d "test.localhost" http://localhost:8500/v1/kv/traefik/frontends/frontend1/routes/test_1/value
# frontend 2
curl -i -H "Accept: application/json" -X PUT -d "backend1" http://localhost:8500/v1/kv/traefik/frontends/frontend2/backend
curl -i -H "Accept: application/json" -X PUT -d "http,https" http://localhost:8500/v1/kv/traefik/frontends/frontend2/entrypoints
curl -i -H "Accept: application/json" -X PUT -d "Path" http://localhost:8500/v1/kv/traefik/frontends/frontend2/routes/test_2/rule
curl -i -H "Accept: application/json" -X PUT -d "/test" http://localhost:8500/v1/kv/traefik/frontends/frontend2/routes/test_2/value

View File

@@ -2,12 +2,45 @@
# Global configuration
################################################################
# Reverse proxy port
# Entrypoints definition
#
# Optional
# Default: ":80"
# Default:
# [entryPoints]
# [entryPoints.http]
# address = ":80"
#
# port = ":80"
# To redirect an http entrypoint to an https entrypoint (with SNI support):
# [entryPoints]
# [entryPoints.http]
# address = ":80"
# [entryPoints.http.redirect]
# entryPoint = "https"
# [entryPoints.https]
# address = ":443"
# [entryPoints.https.tls]
# [[entryPoints.https.tls.certificates]]
# CertFile = "integration/fixtures/https/snitest.com.cert"
# KeyFile = "integration/fixtures/https/snitest.com.key"
# [[entryPoints.https.tls.certificates]]
# CertFile = "integration/fixtures/https/snitest.org.cert"
# KeyFile = "integration/fixtures/https/snitest.org.key"
#
# To redirect an entrypoint rewriting the URL:
# [entryPoints]
# [entryPoints.http]
# address = ":80"
# [entryPoints.http.redirect]
# regex = "^http://localhost/(.*)"
# replacement = "http://mydomain/$1"
# Entrypoints to be used by frontends that do not specify any entrypoint.
# Each frontend can specify its own entrypoints.
#
# Optional
# Default: ["http"]
#
# defaultEntryPoints = ["http", "https"]
# Timeout in seconds.
# Duration to give active requests a chance to finish during hot-reloads
@@ -37,15 +70,6 @@
#
# logLevel = "ERROR"
# SSL certificates and keys
# You may add several certificate/key pairs to terminate HTTPS for multiple domain names using TLS SNI
#
# Optional
#
# [[certificates]]
# CertFile = "traefik.crt"
# KeyFile = "traefik.key"
# Backends throttle duration: minimum duration between 2 events from providers
# before applying a new configuration. It avoids unnecessary reloads if multiples events
# are sent in a short amount of time.
@@ -55,6 +79,14 @@
#
# ProvidersThrottleDuration = "5s"
# If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used.
# If you encounter 'too many open files' errors, you can either change this value, or change `ulimit` value.
#
# Optional
# Default: http.DefaultMaxIdleConnsPerHost
#
# MaxIdleConnsPerHost = 200
################################################################
# Web configuration backend
@@ -207,6 +239,13 @@
# httpBasicAuthUser = "foo"
# httpBasicPassword = "bar"
# TLS client configuration. https://golang.org/pkg/crypto/tls/#Config
#
# Optional
#
# [marathon.TLS]
# InsecureSkipVerify = true
################################################################
# Consul KV configuration backend
@@ -382,6 +421,12 @@
# [frontends.frontend2]
# backend = "backend1"
# passHostHeader = true
# [frontends.frontend2.routes.test_2]
# entrypoints = ["https"] # overrides defaultEntryPoints
# [frontends.frontend2.routes.test_1]
# rule = "Host"
# value = "{subdomain:[a-z]+}.localhost"
# [frontends.frontend3]
# entrypoints = ["http", "https"] # overrides defaultEntryPoints
# backend = "backend2"
# rule = "Path"
# value = "/test"

View File

@@ -36,6 +36,7 @@ type Route struct {
// Frontend holds frontend configuration.
type Frontend struct {
EntryPoints []string `json:"entryPoints,omitempty"`
Backend string `json:"backend,omitempty"`
Routes map[string]Route `json:"routes,omitempty"`
PassHostHeader bool `json:"passHostHeader,omitempty"`

View File

@@ -11,8 +11,8 @@ COPY bower.json $WEBUI_DIR/
WORKDIR $WEBUI_DIR
RUN npm set progress=false
RUN npm install
RUN bower install --allow-root
RUN npm install --quiet
RUN bower install --allow-root --quiet
COPY . $WEBUI_DIR/

View File

@@ -17,6 +17,7 @@
</table>
</div>
<div data-bg-show="frontendCtrl.frontend.backend" class="panel-footer">
<span data-ng-repeat="entryPoint in frontendCtrl.frontend.entryPoints"><span class="label label-primary">{{entryPoint}}</span><span data-ng-hide="$last">&nbsp;</span></span>
<span class="label label-warning" role="button" data-toggle="collapse" href="#{{frontendCtrl.frontend.backend}}" aria-expanded="false">{{frontendCtrl.frontend.backend}}</span>
<span data-ng-show="frontendCtrl.frontend.passHostHeader" class="label label-warning">Pass Host Header</span>
</div>