forked from Ivasoft/traefik
Compare commits
29 Commits
v1.0.alpha
...
v1.0.alpha
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a08655b06 | ||
|
|
9a9c8e5709 | ||
|
|
c7d34b54aa | ||
|
|
8d860c84c8 | ||
|
|
1dc086730e | ||
|
|
5d79e56d30 | ||
|
|
6e7677de79 | ||
|
|
fab6b8be3c | ||
|
|
f1c1eed437 | ||
|
|
348ab794c9 | ||
|
|
aacedcc4b3 | ||
|
|
786acc961a | ||
|
|
7adffdbd78 | ||
|
|
e3b519cdd8 | ||
|
|
e9c23195a0 | ||
|
|
c6c3af8099 | ||
|
|
07c077cf94 | ||
|
|
4ac18f1989 | ||
|
|
4ecb919787 | ||
|
|
4152bd5e26 | ||
|
|
a8cc26fd91 | ||
|
|
81cb00573f | ||
|
|
c22598c8ff | ||
|
|
bb3b9f61cd | ||
|
|
49cd7f799e | ||
|
|
8b334551d8 | ||
|
|
5ef6d53d00 | ||
|
|
901c9b29bc | ||
|
|
2d79c500df |
25
Makefile
25
Makefile
@@ -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)
|
||||
|
||||
25
README.md
25
README.md
@@ -1,13 +1,13 @@
|
||||

|
||||
___
|
||||
|
||||
[](https://circleci.com/gh/emilevauge/traefik)
|
||||
[](https://github.com/EmileVauge/traefik/blob/master/LICENSE.md)
|
||||
[](https://circleci.com/gh/emilevauge/traefik)
|
||||
[](https://github.com/EmileVauge/traefik/blob/master/LICENSE.md)
|
||||
[](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 [](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 [](https://traefik.herokuapp.com)
|
||||
|
||||
- Project [Mantl](https://mantl.io/) from Cisco
|
||||
|
||||

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

|
||||
> 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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
16
cmd.go
@@ -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()
|
||||
|
||||
140
configuration.go
140
configuration.go
@@ -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
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
BIN
docs/img/mantl-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
281
docs/index.md
281
docs/index.md
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
# Reverse proxy port
|
||||
#
|
||||
# Optional
|
||||
# Default: ":80"
|
||||
#
|
||||
port = ":8000"
|
||||
#
|
||||
# LogLevel
|
||||
logLevel = "DEBUG"
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":8000"
|
||||
@@ -1,6 +1,9 @@
|
||||
port = ":8000"
|
||||
logLevel = "DEBUG"
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":8000"
|
||||
|
||||
[web]
|
||||
|
||||
address = ":8080"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
31
middlewares/rewrite.go
Normal 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
829
mocks/Marathon.go
Normal 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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
307
server.go
@@ -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
|
||||
}
|
||||
|
||||
@@ -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}}"
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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 .}}"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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/
|
||||
|
||||
|
||||
@@ -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"> </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>
|
||||
|
||||
Reference in New Issue
Block a user