From 77a9613c3a78767e8ffca6f23bca90c9f3abafdf Mon Sep 17 00:00:00 2001 From: Russell Clare Date: Fri, 15 Apr 2016 15:00:27 +0100 Subject: [PATCH 01/48] Updating Toml to sure PathPrefix instead of Path --- docs/toml.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/toml.md b/docs/toml.md index c3aa5f4f0..0d71cd995 100644 --- a/docs/toml.md +++ b/docs/toml.md @@ -934,7 +934,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:/test` | +| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` | ## Atomic configuration changes From aeb9cc17328a610221166632731424fc16bc32e4 Mon Sep 17 00:00:00 2001 From: Jonas Falck Date: Thu, 19 May 2016 10:52:17 +0200 Subject: [PATCH 02/48] http protocol should not use TLS I need this in order to run kubectl proxy and then make traefik use http://localhost to get to my cluster when developing --- cmd.go | 5 +++-- provider/k8s/client.go | 10 ++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/cmd.go b/cmd.go index f4240c4f4..573382ea6 100644 --- a/cmd.go +++ b/cmd.go @@ -10,12 +10,13 @@ import ( "strings" "time" + "net/http" + log "github.com/Sirupsen/logrus" "github.com/containous/traefik/middlewares" "github.com/containous/traefik/provider" "github.com/spf13/cobra" "github.com/spf13/viper" - "net/http" ) var traefikCmd = &cobra.Command{ @@ -171,7 +172,7 @@ func init() { traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Prefix, "boltdb.prefix", "/traefik", "Prefix used for KV store") traefikCmd.PersistentFlags().BoolVar(&arguments.kubernetes, "kubernetes", false, "Enable Kubernetes backend") - traefikCmd.PersistentFlags().StringVar(&arguments.Kubernetes.Endpoint, "kubernetes.endpoint", "127.0.0.1:8080", "Kubernetes server endpoint") + traefikCmd.PersistentFlags().StringVar(&arguments.Kubernetes.Endpoint, "kubernetes.endpoint", "http://127.0.0.1:8080", "Kubernetes server endpoint") traefikCmd.PersistentFlags().StringSliceVar(&arguments.Kubernetes.Namespaces, "kubernetes.namespaces", []string{}, "Kubernetes namespaces") _ = viper.BindPFlag("configFile", traefikCmd.PersistentFlags().Lookup("configFile")) diff --git a/provider/k8s/client.go b/provider/k8s/client.go index 3122c9f44..ec089f40a 100644 --- a/provider/k8s/client.go +++ b/provider/k8s/client.go @@ -5,11 +5,12 @@ import ( "crypto/x509" "encoding/json" "fmt" - "github.com/containous/traefik/safe" - "github.com/parnurzeal/gorequest" "net/http" "net/url" "strings" + + "github.com/containous/traefik/safe" + "github.com/parnurzeal/gorequest" ) const ( @@ -201,6 +202,11 @@ func (c *clientImpl) do(request *gorequest.SuperAgent) ([]byte, error) { func (c *clientImpl) request(url string) *gorequest.SuperAgent { // Make request to Kubernetes API request := gorequest.New().Get(url) + + if strings.HasPrefix(url, "http://") { + return request + } + if len(c.token) > 0 { request.Header["Authorization"] = "Bearer " + c.token pool := x509.NewCertPool() From 43acbaa7027961246990a11b604e3581846f9e5d Mon Sep 17 00:00:00 2001 From: Emile Vauge Date: Mon, 23 May 2016 14:00:14 +0200 Subject: [PATCH 03/48] Fix safari error with http2 Signed-off-by: Emile Vauge --- glide.lock | 23 ++++++++++++----------- glide.yaml | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/glide.lock b/glide.lock index 6c78fffae..b52394f42 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: da7239dce8bda69f6e10b2f2bfae57dd4fd95b817055dca1379a72af42939b97 -updated: 2016-05-12T11:48:22.158455011+02:00 +hash: 68bc4f87206f9a486e1455f1dfcad737369c359a803566271432fcb85de3a12c +updated: 2016-05-23T13:57:35.191541555+02:00 imports: - name: github.com/alecthomas/template version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0 @@ -22,7 +22,7 @@ imports: - name: github.com/codegangsta/negroni version: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b - name: github.com/containous/oxy - version: 021f82bd8260ba15f5862a9fe62018437720dff5 + version: 183212964e13e7b8afe01a08b193d04300554a68 subpackages: - cbreaker - forward @@ -103,7 +103,7 @@ imports: - types/versions - types/blkiodev - name: github.com/docker/go-connections - version: 5b7154ba2efe13ff86ae8830a9e7cb120b080d6e + version: c7838b258fbfa3fe88eecfb2a0e08ea0dbd6a646 subpackages: - nat - sockets @@ -184,19 +184,19 @@ imports: - name: github.com/mattn/go-shellwords version: 525bedee691b5a8df547cb5cf9f86b7fb1883e24 - name: github.com/Microsoft/go-winio - version: 3b8b3c98b207f95fe0cd6c7c311a9ac497ba7c0f + version: 4f1a71750d95a5a8a46c40a67ffbed8129c2f138 - name: github.com/miekg/dns version: 48ab6605c66ac797e07f615101c3e9e10e932b66 - name: github.com/mitchellh/mapstructure version: d2dd0262208475919e1a362f675cfc0e7c10e905 - name: github.com/moul/http2curl - version: 1812aee76a1ce98d604a44200c6a23c689b17a89 + version: b1479103caacaa39319f75e7f57fc545287fca0d - name: github.com/opencontainers/runc version: 2441732d6fcc0fb0a542671a4372e0c7bc99c19e subpackages: - libcontainer/user - name: github.com/parnurzeal/gorequest - version: a39a2f8d0463091df7344dbf586a9986e9f7184f + version: 2169dfca686cfcbefc983a98a25e9c22a2815be4 - name: github.com/pmezard/go-difflib version: d8ed2627bdf02c080bf22230dbb337003b7aba2d subpackages: @@ -210,7 +210,7 @@ imports: - name: github.com/spf13/cast version: ee7b3e0353166ab1f3a605294ac8cd2b77953778 - name: github.com/spf13/cobra - version: 0f866a6211e33cde2091d9290c08f6afd6c9ebbc + version: f368244301305f414206f889b1735a54cfc8bde8 subpackages: - cobra - name: github.com/spf13/jwalterweatherman @@ -237,7 +237,7 @@ imports: - name: github.com/unrolled/render version: 26b4e3aac686940fe29521545afad9966ddfc80c - name: github.com/vdemeester/docker-events - version: ce5347b72aafad4e3bebd966f15e4183839d5172 + version: b308d2e8d639d928c882913bcb4f85b3a84c7a07 - name: github.com/vdemeester/shakers version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3 - name: github.com/vulcand/oxy @@ -258,11 +258,11 @@ imports: - name: github.com/wendal/errors version: f66c77a7882b399795a8987ebf87ef64a427417e - name: github.com/xenolf/lego - version: 948483535f53c34d144419869ecbed86251a30f6 + version: b119bc45fbd1cc71348003541aac9d3a7da63654 subpackages: - acme - name: golang.org/x/crypto - version: b76c864ef1dca1d8f271f917c290cddcce3d9e0d + version: 5bcd134fee4dd1475da17714aac19c0aa0142e2f subpackages: - ocsp - name: golang.org/x/net @@ -275,6 +275,7 @@ imports: version: eb2c74142fd19a79b3f237334c7384d5167b1b46 subpackages: - unix + - windows - name: gopkg.in/alecthomas/kingpin.v2 version: 639879d6110b1b0409410c7b737ef0bb18325038 - name: gopkg.in/fsnotify.v1 diff --git a/glide.yaml b/glide.yaml index 4c55bbb92..3ca22fb8c 100644 --- a/glide.yaml +++ b/glide.yaml @@ -7,7 +7,7 @@ import: - package: github.com/mailgun/log version: 44874009257d4d47ba9806f1b7f72a32a015e4d8 - package: github.com/containous/oxy - version: 021f82bd8260ba15f5862a9fe62018437720dff5 + version: 183212964e13e7b8afe01a08b193d04300554a68 subpackages: - cbreaker - forward From 80ab967d391cc5eb32ace1ebe1cb1b4690ada285 Mon Sep 17 00:00:00 2001 From: Emile Vauge Date: Mon, 23 May 2016 14:02:25 +0200 Subject: [PATCH 04/48] Fix benchmarks doc Signed-off-by: Emile Vauge --- docs/benchmarks.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/benchmarks.md b/docs/benchmarks.md index 008f0e378..82d37a855 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -146,7 +146,7 @@ defaultEntryPoints = ["http"] ### whoami: ``` -wrk -t8 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-whoami:80/bench +wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-whoami:80/bench Running 1m test @ http://IP-whoami:80/bench 20 threads and 1000 connections Thread Stats Avg Stdev Max +/- Stdev @@ -184,7 +184,7 @@ Transfer/sec: 4.97MB ### traefik: ``` -wrk -t8 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-traefik:8000/bench +wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-traefik:8000/bench Running 1m test @ http://IP-traefik:8000/bench 20 threads and 1000 connections Thread Stats Avg Stdev Max +/- Stdev From 86053ea54b47103f061972f67177cf3343208dc1 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Mon, 23 May 2016 16:07:35 +0200 Subject: [PATCH 05/48] Update Makefile to fix local builds (#397) Use --build-arg only if DOCKER_VERSION is specified Signed-off-by: Vincent Demeester --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6dc75de35..9e6a5d15d 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]') TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"containous/traefik") INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -v "/var/run/docker.sock:/var/run/docker.sock") +DOCKER_BUILD_ARGS := $(if $(DOCKER_VERSION), "--build-arg=DOCKER_VERSION=$(DOCKER_VERSION)",) DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) -it $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)" print-%: ; @echo $*=$($*) @@ -46,7 +47,7 @@ validate: build ## validate gofmt, golint and go vet $(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint build: dist - docker build --build-arg=DOCKER_VERSION=${DOCKER_VERSION} -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile . + docker build $(DOCKER_BUILD_ARGS) -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile . build-webui: docker build -t traefik-webui -f webui/Dockerfile webui From 86f3891a2bd9bd3735b3adfb66f8160891fe7e3e Mon Sep 17 00:00:00 2001 From: Emile Vauge Date: Thu, 19 May 2016 20:06:33 +0200 Subject: [PATCH 06/48] Add debug flag Signed-off-by: Emile Vauge --- cmd.go | 4 +++- configuration.go | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd.go b/cmd.go index 573382ea6..6580d6a15 100644 --- a/cmd.go +++ b/cmd.go @@ -94,7 +94,8 @@ var arguments = struct { func init() { traefikCmd.AddCommand(versionCmd) - traefikCmd.PersistentFlags().StringP("configFile", "c", "", "Configuration file to use (TOML, JSON, YAML, HCL).") + traefikCmd.PersistentFlags().StringP("configFile", "c", "", "Configuration file to use (TOML).") + traefikCmd.PersistentFlags().BoolVarP(&arguments.Debug, "debug", "d", false, "Enable debug mode") 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") @@ -178,6 +179,7 @@ func init() { _ = viper.BindPFlag("configFile", traefikCmd.PersistentFlags().Lookup("configFile")) _ = viper.BindPFlag("graceTimeOut", traefikCmd.PersistentFlags().Lookup("graceTimeOut")) _ = viper.BindPFlag("logLevel", traefikCmd.PersistentFlags().Lookup("logLevel")) + _ = viper.BindPFlag("debug", traefikCmd.PersistentFlags().Lookup("debug")) // TODO: wait for this issue to be corrected: https://github.com/spf13/viper/issues/105 _ = viper.BindPFlag("providersThrottleDuration", traefikCmd.PersistentFlags().Lookup("providersThrottleDuration")) _ = viper.BindPFlag("maxIdleConnsPerHost", traefikCmd.PersistentFlags().Lookup("maxIdleConnsPerHost")) diff --git a/configuration.go b/configuration.go index 693b74c90..bae48d7cb 100644 --- a/configuration.go +++ b/configuration.go @@ -19,6 +19,7 @@ import ( // It's populated from the traefik configuration file passed as an argument to the binary. type GlobalConfiguration struct { GraceTimeOut int64 + Debug bool AccessLogsFile string TraefikLogsFile string LogLevel string From dc404b365f6b4c03b36be66a2e60f7c25de123f4 Mon Sep 17 00:00:00 2001 From: Emile Vauge Date: Thu, 19 May 2016 20:06:49 +0200 Subject: [PATCH 07/48] Add expvar endpoint Signed-off-by: Emile Vauge --- web.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/web.go b/web.go index 445a83bce..d6093db7e 100644 --- a/web.go +++ b/web.go @@ -2,9 +2,11 @@ package main import ( "encoding/json" + "expvar" "fmt" "io/ioutil" "net/http" + "runtime" log "github.com/Sirupsen/logrus" "github.com/containous/traefik/autogen" @@ -33,6 +35,14 @@ var ( }) ) +func init() { + expvar.Publish("Goroutines", expvar.Func(goroutines)) +} + +func goroutines() interface{} { + return runtime.NumGoroutine() +} + // Provide allows the provider to provide configurations to traefik // using the given configuration channel. func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { @@ -97,6 +107,11 @@ func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessag } } }() + + // expvars + if provider.server.globalConfiguration.Debug { + systemRouter.Methods("GET").Path("/debug/vars").HandlerFunc(expvarHandler) + } return nil } @@ -231,3 +246,17 @@ func (provider *WebProvider) getRouteHandler(response http.ResponseWriter, reque } http.NotFound(response, request) } + +func expvarHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + fmt.Fprintf(w, "{\n") + first := true + expvar.Do(func(kv expvar.KeyValue) { + if !first { + fmt.Fprintf(w, ",\n") + } + first = false + fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) + }) + fmt.Fprintf(w, "\n}\n") +} From 2af1e4b1921d7a00cbfe63133695863029ce2cb4 Mon Sep 17 00:00:00 2001 From: Emile Vauge Date: Thu, 19 May 2016 20:07:57 +0200 Subject: [PATCH 08/48] Fix k8s compose file Signed-off-by: Emile Vauge --- examples/compose-k8s.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/examples/compose-k8s.yaml b/examples/compose-k8s.yaml index e9abe96b4..626ea1d1c 100644 --- a/examples/compose-k8s.yaml +++ b/examples/compose-k8s.yaml @@ -1,8 +1,3 @@ -# etcd: -# image: gcr.io/google_containers/etcd:2.2.1 -# net: host -# command: ['/usr/local/bin/etcd', '--addr=127.0.0.1:4001', '--bind-addr=0.0.0.0:4001', '--data-dir=/var/etcd/data'] - kubelet: image: gcr.io/google_containers/hyperkube-amd64:v1.2.2 privileged: true From 0f23581f644707eca93226136cc933e5fa947d4c Mon Sep 17 00:00:00 2001 From: Emile Vauge Date: Thu, 19 May 2016 20:09:01 +0200 Subject: [PATCH 09/48] Fix k8s memory leak Signed-off-by: Emile Vauge --- provider/k8s/client.go | 56 ++++++++++++++++++++++-------------------- provider/kubernetes.go | 28 +++++++++++---------- server.go | 4 +-- web.go | 10 ++++---- 4 files changed, 52 insertions(+), 46 deletions(-) diff --git a/provider/k8s/client.go b/provider/k8s/client.go index ec089f40a..957d66a9b 100644 --- a/provider/k8s/client.go +++ b/provider/k8s/client.go @@ -5,12 +5,10 @@ import ( "crypto/x509" "encoding/json" "fmt" + "github.com/parnurzeal/gorequest" "net/http" "net/url" "strings" - - "github.com/containous/traefik/safe" - "github.com/parnurzeal/gorequest" ) const ( @@ -126,8 +124,8 @@ func (c *clientImpl) WatchReplicationControllers(stopCh <-chan bool) (chan inter // WatchAll returns events in the cluster func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) { - watchCh := make(chan interface{}) - errCh := make(chan error) + watchCh := make(chan interface{}, 10) + errCh := make(chan error, 10) stopIngresses := make(chan bool) chanIngresses, chanIngressesErr, err := c.WatchIngresses(stopIngresses) @@ -164,7 +162,7 @@ func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, stopServices <- true stopPods <- true stopReplicationControllers <- true - break + return case err := <-chanIngressesErr: errCh <- err case err := <-chanServicesErr: @@ -193,6 +191,7 @@ func (c *clientImpl) do(request *gorequest.SuperAgent) ([]byte, error) { if errs != nil { return nil, fmt.Errorf("failed to create request: GET %q : %v", request.Url, errs) } + defer res.Body.Close() if res.StatusCode != http.StatusOK { return nil, fmt.Errorf("http error %d GET %q: %q", res.StatusCode, request.Url, string(body)) } @@ -202,6 +201,7 @@ func (c *clientImpl) do(request *gorequest.SuperAgent) ([]byte, error) { func (c *clientImpl) request(url string) *gorequest.SuperAgent { // Make request to Kubernetes API request := gorequest.New().Get(url) + request.Transport.DisableKeepAlives = true if strings.HasPrefix(url, "http://") { return request @@ -223,8 +223,8 @@ type GenericObject struct { } func (c *clientImpl) watch(url string, stopCh <-chan bool) (chan interface{}, chan error, error) { - watchCh := make(chan interface{}) - errCh := make(chan error) + watchCh := make(chan interface{}, 10) + errCh := make(chan error, 10) // get version body, err := c.do(c.request(url)) @@ -246,34 +246,38 @@ func (c *clientImpl) watch(url string, stopCh <-chan bool) (chan interface{}, ch return watchCh, errCh, fmt.Errorf("failed to make watch request: GET %q : %v", url, err) } request.Client.Transport = request.Transport + res, err := request.Client.Do(req) if err != nil { return watchCh, errCh, fmt.Errorf("failed to do watch request: GET %q: %v", url, err) } - shouldStop := safe.New(false) - - go func() { - select { - case <-stopCh: - shouldStop.Set(true) - res.Body.Close() - return - } - }() - go func() { + finishCh := make(chan bool) + defer close(finishCh) defer close(watchCh) defer close(errCh) - for { - var eventList interface{} - if err := json.NewDecoder(res.Body).Decode(&eventList); err != nil { - if !shouldStop.Get().(bool) { - errCh <- fmt.Errorf("failed to decode watch event: %v", err) + go func() { + defer res.Body.Close() + for { + var eventList interface{} + if err := json.NewDecoder(res.Body).Decode(&eventList); err != nil { + if !strings.Contains(err.Error(), "net/http: request canceled") { + errCh <- fmt.Errorf("failed to decode watch event: GET %q : %v", url, err) + } + finishCh <- true + return } - return + watchCh <- eventList } - watchCh <- eventList + }() + select { + case <-stopCh: + go func() { + request.Transport.CancelRequest(req) + }() + <-finishCh + return } }() return watchCh, errCh, nil diff --git a/provider/kubernetes.go b/provider/kubernetes.go index 4af905084..0cfb75625 100644 --- a/provider/kubernetes.go +++ b/provider/kubernetes.go @@ -62,19 +62,20 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage backOff := backoff.NewExponentialBackOff() pool.Go(func(stop chan bool) { - stopWatch := make(chan bool) - defer close(stopWatch) operation := func() error { - select { - case <-stop: - return nil - default: - } for { + stopWatch := make(chan bool, 5) + defer close(stopWatch) eventsChan, errEventsChan, err := k8sClient.WatchAll(stopWatch) if err != nil { log.Errorf("Error watching kubernetes events: %v", err) - return err + timer := time.NewTimer(1 * time.Second) + select { + case <-timer.C: + return err + case <-stop: + return nil + } } Watch: for { @@ -82,14 +83,15 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage case <-stop: stopWatch <- true return nil - case err := <-errEventsChan: - if strings.Contains(err.Error(), io.EOF.Error()) { + case err, ok := <-errEventsChan: + stopWatch <- true + if ok && strings.Contains(err.Error(), io.EOF.Error()) { // edge case, kubernetes long-polling disconnection break Watch } return err case event := <-eventsChan: - log.Debugf("Received event from kubenetes %+v", event) + log.Debugf("Received event from kubernetes %+v", event) templateObjects, err := provider.loadIngresses(k8sClient) if err != nil { return err @@ -190,13 +192,13 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur return service.ObjectMeta.Namespace == i.ObjectMeta.Namespace && service.Name == pa.Backend.ServiceName }) if err != nil { - log.Errorf("Error retrieving services: %v", err) + log.Warnf("Error retrieving services: %v", err) continue } if len(services) == 0 { // no backends found, delete frontend... delete(templateObjects.Frontends, r.Host+pa.Path) - log.Errorf("Error retrieving services %s", pa.Backend.ServiceName) + log.Warnf("Error retrieving services %s", pa.Backend.ServiceName) } for _, service := range services { protocol := "http" diff --git a/server.go b/server.go index 39a09c271..c1bb55cd0 100644 --- a/server.go +++ b/server.go @@ -68,8 +68,8 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server { server := new(Server) server.serverEntryPoints = make(map[string]*serverEntryPoint) - server.configurationChan = make(chan types.ConfigMessage, 10) - server.configurationValidatedChan = make(chan types.ConfigMessage, 10) + server.configurationChan = make(chan types.ConfigMessage, 100) + server.configurationValidatedChan = make(chan types.ConfigMessage, 100) server.signals = make(chan os.Signal, 1) server.stopChan = make(chan bool, 1) server.providers = []provider.Provider{} diff --git a/web.go b/web.go index d6093db7e..f32874aa6 100644 --- a/web.go +++ b/web.go @@ -94,6 +94,11 @@ func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessag }) systemRouter.Methods("GET").PathPrefix("/dashboard/").Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: autogen.Asset, AssetDir: autogen.AssetDir, Prefix: "static"}))) + // expvars + if provider.server.globalConfiguration.Debug { + systemRouter.Methods("GET").Path("/debug/vars").HandlerFunc(expvarHandler) + } + go func() { if len(provider.CertFile) > 0 && len(provider.KeyFile) > 0 { err := http.ListenAndServeTLS(provider.Address, provider.CertFile, provider.KeyFile, systemRouter) @@ -107,11 +112,6 @@ func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessag } } }() - - // expvars - if provider.server.globalConfiguration.Debug { - systemRouter.Methods("GET").Path("/debug/vars").HandlerFunc(expvarHandler) - } return nil } From b79535f3691cc72d49820317b8026bbfa8775274 Mon Sep 17 00:00:00 2001 From: Ed Robinson Date: Wed, 25 May 2016 13:16:19 +0100 Subject: [PATCH 10/48] Support ingresses without a host (#406) fixes #370 --- provider/kubernetes.go | 8 ++-- provider/kubernetes_test.go | 87 +++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 3 deletions(-) diff --git a/provider/kubernetes.go b/provider/kubernetes.go index 0cfb75625..7362898b2 100644 --- a/provider/kubernetes.go +++ b/provider/kubernetes.go @@ -162,9 +162,11 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur Routes: make(map[string]types.Route), } } - if _, exists := templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host]; !exists { - templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host] = types.Route{ - Rule: "Host:" + r.Host, + if len(r.Host) > 0 { + if _, exists := templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host]; !exists { + templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host] = types.Route{ + Rule: "Host:" + r.Host, + } } } if len(pa.Path) > 0 { diff --git a/provider/kubernetes_test.go b/provider/kubernetes_test.go index 0b9b98d62..b74b0536d 100644 --- a/provider/kubernetes_test.go +++ b/provider/kubernetes_test.go @@ -1060,6 +1060,93 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) { } } +func TestHostlessIngress(t *testing.T) { + ingresses := []k8s.Ingress{{ + ObjectMeta: k8s.ObjectMeta{ + Namespace: "awesome", + }, + Spec: k8s.IngressSpec{ + Rules: []k8s.IngressRule{ + { + IngressRuleValue: k8s.IngressRuleValue{ + HTTP: &k8s.HTTPIngressRuleValue{ + Paths: []k8s.HTTPIngressPath{ + { + Path: "/bar", + Backend: k8s.IngressBackend{ + ServiceName: "service1", + ServicePort: k8s.FromInt(801), + }, + }, + }, + }, + }, + }, + }, + }, + }} + services := []k8s.Service{ + { + ObjectMeta: k8s.ObjectMeta{ + Name: "service1", + Namespace: "awesome", + UID: "1", + }, + Spec: k8s.ServiceSpec{ + ClusterIP: "10.0.0.1", + Ports: []k8s.ServicePort{ + { + Name: "http", + Port: 801, + }, + }, + }, + }, + } + watchChan := make(chan interface{}) + client := clientMock{ + ingresses: ingresses, + services: services, + watchChan: watchChan, + } + provider := Kubernetes{disablePassHostHeaders: true} + actual, err := provider.loadIngresses(client) + if err != nil { + t.Fatalf("error %+v", err) + } + + expected := &types.Configuration{ + Backends: map[string]*types.Backend{ + "/bar": { + Servers: map[string]types.Server{ + "1": { + URL: "http://10.0.0.1:801", + Weight: 1, + }, + }, + CircuitBreaker: nil, + LoadBalancer: nil, + }, + }, + Frontends: map[string]*types.Frontend{ + "/bar": { + Backend: "/bar", + Routes: map[string]types.Route{ + "/bar": { + Rule: "PathPrefix:/bar", + }, + }, + }, + }, + } + actualJSON, _ := json.Marshal(actual) + expectedJSON, _ := json.Marshal(expected) + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("expected %+v, got %+v", string(expectedJSON), string(actualJSON)) + } +} + type clientMock struct { ingresses []k8s.Ingress services []k8s.Service From e948a013cdf866caa9d0b14366d74fd575a6fffe Mon Sep 17 00:00:00 2001 From: Ed Robinson Date: Fri, 20 May 2016 17:34:57 +0100 Subject: [PATCH 11/48] Build backend config using the K8S endpoint resource. * Potentialy saves a network hop * Ability to configure LB algothim (given some work to expose an anotation etc...) * K8s config Watch is triggered far less often --- provider/k8s/client.go | 55 +++++++++++------------- provider/k8s/endpoints.go | 84 +++++++++++++++++++++++++++++++++++++ provider/kubernetes.go | 38 +++++++++++++++-- provider/kubernetes_test.go | 75 +++++++++++++++++++++++++++++---- 4 files changed, 210 insertions(+), 42 deletions(-) create mode 100644 provider/k8s/endpoints.go diff --git a/provider/k8s/client.go b/provider/k8s/client.go index 957d66a9b..930c9835e 100644 --- a/provider/k8s/client.go +++ b/provider/k8s/client.go @@ -22,6 +22,7 @@ const ( type Client interface { GetIngresses(predicate func(Ingress) bool) ([]Ingress, error) GetServices(predicate func(Service) bool) ([]Service, error) + GetEndpoints(name, namespace string) (Endpoints, error) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) } @@ -104,21 +105,26 @@ func (c *clientImpl) WatchServices(stopCh <-chan bool) (chan interface{}, chan e return c.watch(getURL, stopCh) } -// WatchEvents returns events in the cluster -func (c *clientImpl) WatchEvents(stopCh <-chan bool) (chan interface{}, chan error, error) { - getURL := c.endpointURL + APIEndpoint + "/events" - return c.watch(getURL, stopCh) +// GetEndpoints returns the named Endpoints +// Endpoints have the same name as the coresponding service +func (c *clientImpl) GetEndpoints(name, namespace string) (Endpoints, error) { + getURL := c.endpointURL + APIEndpoint + "/namespaces/" + namespace + "/endpoints/" + name + + body, err := c.do(c.request(getURL)) + if err != nil { + return Endpoints{}, fmt.Errorf("failed to create endpoints request: GET %q : %v", getURL, err) + } + + var endpoints Endpoints + if err := json.Unmarshal(body, &endpoints); err != nil { + return Endpoints{}, fmt.Errorf("failed to decode endpoints resources: %v", err) + } + return endpoints, nil } -// WatchPods returns pods in the cluster -func (c *clientImpl) WatchPods(stopCh <-chan bool) (chan interface{}, chan error, error) { - getURL := c.endpointURL + APIEndpoint + "/pods" - return c.watch(getURL, stopCh) -} - -// WatchReplicationControllers returns ReplicationControllers in the cluster -func (c *clientImpl) WatchReplicationControllers(stopCh <-chan bool) (chan interface{}, chan error, error) { - getURL := c.endpointURL + APIEndpoint + "/replicationcontrollers" +// WatchEndpoints returns endpoints in the cluster +func (c *clientImpl) WatchEndpoints(stopCh <-chan bool) (chan interface{}, chan error, error) { + getURL := c.endpointURL + APIEndpoint + "/endpoints" return c.watch(getURL, stopCh) } @@ -137,13 +143,8 @@ func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, if err != nil { return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err) } - stopPods := make(chan bool) - chanPods, chanPodsErr, err := c.WatchPods(stopPods) - if err != nil { - return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err) - } - stopReplicationControllers := make(chan bool) - chanReplicationControllers, chanReplicationControllersErr, err := c.WatchReplicationControllers(stopReplicationControllers) + stopEndpoints := make(chan bool) + chanEndpoints, chanEndpointsErr, err := c.WatchEndpoints(stopEndpoints) if err != nil { return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err) } @@ -152,32 +153,26 @@ func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, defer close(errCh) defer close(stopIngresses) defer close(stopServices) - defer close(stopPods) - defer close(stopReplicationControllers) + defer close(stopEndpoints) for { select { case <-stopCh: stopIngresses <- true stopServices <- true - stopPods <- true - stopReplicationControllers <- true + stopEndpoints <- true return case err := <-chanIngressesErr: errCh <- err case err := <-chanServicesErr: errCh <- err - case err := <-chanPodsErr: - errCh <- err - case err := <-chanReplicationControllersErr: + case err := <-chanEndpointsErr: errCh <- err case event := <-chanIngresses: watchCh <- event case event := <-chanServices: watchCh <- event - case event := <-chanPods: - watchCh <- event - case event := <-chanReplicationControllers: + case event := <-chanEndpoints: watchCh <- event } } diff --git a/provider/k8s/endpoints.go b/provider/k8s/endpoints.go new file mode 100644 index 000000000..123ffe36c --- /dev/null +++ b/provider/k8s/endpoints.go @@ -0,0 +1,84 @@ +package k8s + +// Endpoints is a collection of endpoints that implement the actual service. Example: +// Name: "mysvc", +// Subsets: [ +// { +// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}], +// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}] +// }, +// { +// Addresses: [{"ip": "10.10.3.3"}], +// Ports: [{"name": "a", "port": 93}, {"name": "b", "port": 76}] +// }, +// ] +type Endpoints struct { + TypeMeta `json:",inline"` + ObjectMeta `json:"metadata,omitempty"` + + // The set of all endpoints is the union of all subsets. + Subsets []EndpointSubset +} + +// EndpointSubset is a group of addresses with a common set of ports. The +// expanded set of endpoints is the Cartesian product of Addresses x Ports. +// For example, given: +// { +// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}], +// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}] +// } +// The resulting set of endpoints can be viewed as: +// a: [ 10.10.1.1:8675, 10.10.2.2:8675 ], +// b: [ 10.10.1.1:309, 10.10.2.2:309 ] +type EndpointSubset struct { + Addresses []EndpointAddress + NotReadyAddresses []EndpointAddress + Ports []EndpointPort +} + +// EndpointAddress is a tuple that describes single IP address. +type EndpointAddress struct { + // The IP of this endpoint. + // IPv6 is also accepted but not fully supported on all platforms. Also, certain + // kubernetes components, like kube-proxy, are not IPv6 ready. + // TODO: This should allow hostname or IP, see #4447. + IP string + // Optional: Hostname of this endpoint + // Meant to be used by DNS servers etc. + Hostname string `json:"hostname,omitempty"` + // Optional: The kubernetes object related to the entry point. + TargetRef *ObjectReference +} + +// EndpointPort is a tuple that describes a single port. +type EndpointPort struct { + // The name of this port (corresponds to ServicePort.Name). Optional + // if only one port is defined. Must be a DNS_LABEL. + Name string + + // The port number. + Port int32 + + // The IP protocol for this port. + Protocol Protocol +} + +// ObjectReference contains enough information to let you inspect or modify the referred object. +type ObjectReference struct { + Kind string `json:"kind,omitempty"` + Namespace string `json:"namespace,omitempty"` + Name string `json:"name,omitempty"` + UID UID `json:"uid,omitempty"` + APIVersion string `json:"apiVersion,omitempty"` + ResourceVersion string `json:"resourceVersion,omitempty"` + + // Optional. If referring to a piece of an object instead of an entire object, this string + // should contain information to identify the sub-object. For example, if the object + // reference is to a container within a pod, this would take on a value like: + // "spec.containers{name}" (where "name" refers to the name of the container that triggered + // the event) or if no container name is specified "spec.containers[2]" (container with + // index 2 in this pod). This syntax is chosen only to have some well-defined way of + // referencing a part of an object. + // TODO: this design is not final and this field is subject to change in the future. + FieldPath string `json:"fieldPath,omitempty"` +} diff --git a/provider/kubernetes.go b/provider/kubernetes.go index 7362898b2..11556f49c 100644 --- a/provider/kubernetes.go +++ b/provider/kubernetes.go @@ -209,9 +209,27 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur if port.Port == 443 { protocol = "https" } - templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{ - URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(port.Port), - Weight: 1, + endpoints, err := k8sClient.GetEndpoints(service.ObjectMeta.Name, service.ObjectMeta.Namespace) + if err != nil { + log.Errorf("Error retrieving endpoints: %v", err) + continue + } + if len(endpoints.Subsets) == 0 { + log.Warnf("Endpoints not found for %s/%s, falling back to Service ClusterIP", service.ObjectMeta.Namespace, service.ObjectMeta.Name) + templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{ + URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(port.Port), + Weight: 1, + } + } else { + for _, subset := range endpoints.Subsets { + for _, address := range subset.Addresses { + url := protocol + "://" + address.IP + ":" + strconv.Itoa(endpointPortNumber(port, subset.Ports)) + templateObjects.Backends[r.Host+pa.Path].Servers[url] = types.Server{ + URL: url, + Weight: 1, + } + } + } } break } @@ -223,6 +241,20 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur return &templateObjects, nil } +func endpointPortNumber(servicePort k8s.ServicePort, endpointPorts []k8s.EndpointPort) int { + if len(endpointPorts) > 0 { + //name is optional if there is only one port + port := endpointPorts[0] + for _, endpointPort := range endpointPorts { + if servicePort.Name == endpointPort.Name { + port = endpointPort + } + } + return int(port.Port) + } + return servicePort.Port +} + func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool { if servicePort.Port == ingressPort.IntValue() { return true diff --git a/provider/kubernetes_test.go b/provider/kubernetes_test.go index b74b0536d..9f6ddfb32 100644 --- a/provider/kubernetes_test.go +++ b/provider/kubernetes_test.go @@ -10,6 +10,9 @@ import ( func TestLoadIngresses(t *testing.T) { ingresses := []k8s.Ingress{{ + ObjectMeta: k8s.ObjectMeta{ + Namespace: "testing", + }, Spec: k8s.IngressSpec{ Rules: []k8s.IngressRule{ { @@ -55,23 +58,25 @@ func TestLoadIngresses(t *testing.T) { services := []k8s.Service{ { ObjectMeta: k8s.ObjectMeta{ - Name: "service1", - UID: "1", + Name: "service1", + UID: "1", + Namespace: "testing", }, Spec: k8s.ServiceSpec{ ClusterIP: "10.0.0.1", Ports: []k8s.ServicePort{ { Name: "http", - Port: 801, + Port: 80, }, }, }, }, { ObjectMeta: k8s.ObjectMeta{ - Name: "service2", - UID: "2", + Name: "service2", + UID: "2", + Namespace: "testing", }, Spec: k8s.ServiceSpec{ ClusterIP: "10.0.0.2", @@ -84,8 +89,9 @@ func TestLoadIngresses(t *testing.T) { }, { ObjectMeta: k8s.ObjectMeta{ - Name: "service3", - UID: "3", + Name: "service3", + UID: "3", + Namespace: "testing", }, Spec: k8s.ServiceSpec{ ClusterIP: "10.0.0.3", @@ -98,10 +104,46 @@ func TestLoadIngresses(t *testing.T) { }, }, } + endpoints := []k8s.Endpoints{ + { + ObjectMeta: k8s.ObjectMeta{ + Name: "service1", + UID: "1", + Namespace: "testing", + }, + Subsets: []k8s.EndpointSubset{ + { + Addresses: []k8s.EndpointAddress{ + { + IP: "10.10.0.1", + }, + }, + Ports: []k8s.EndpointPort{ + { + Port: 8080, + }, + }, + }, + { + Addresses: []k8s.EndpointAddress{ + { + IP: "10.21.0.1", + }, + }, + Ports: []k8s.EndpointPort{ + { + Port: 8080, + }, + }, + }, + }, + }, + } watchChan := make(chan interface{}) client := clientMock{ ingresses: ingresses, services: services, + endpoints: endpoints, watchChan: watchChan, } provider := Kubernetes{} @@ -114,8 +156,12 @@ func TestLoadIngresses(t *testing.T) { Backends: map[string]*types.Backend{ "foo/bar": { Servers: map[string]types.Server{ - "1": { - URL: "http://10.0.0.1:801", + "http://10.10.0.1:8080": { + URL: "http://10.10.0.1:8080", + Weight: 1, + }, + "http://10.21.0.1:8080": { + URL: "http://10.21.0.1:8080", Weight: 1, }, }, @@ -1150,6 +1196,7 @@ func TestHostlessIngress(t *testing.T) { type clientMock struct { ingresses []k8s.Ingress services []k8s.Service + endpoints []k8s.Endpoints watchChan chan interface{} } @@ -1174,6 +1221,16 @@ func (c clientMock) GetServices(predicate func(k8s.Service) bool) ([]k8s.Service } return services, nil } + +func (c clientMock) GetEndpoints(name, namespace string) (k8s.Endpoints, error) { + for _, endpoints := range c.endpoints { + if endpoints.Namespace == namespace && endpoints.Name == name { + return endpoints, nil + } + } + return k8s.Endpoints{}, nil +} + func (c clientMock) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) { return c.watchChan, make(chan error), nil } From 6accb90c4769f9de24a73ddf7f56e3058cc460d6 Mon Sep 17 00:00:00 2001 From: Ed Robinson Date: Thu, 26 May 2016 00:53:51 +0100 Subject: [PATCH 12/48] Simplify Service Lookup Since we already know the name and namespace of the service(s) we want we can just get the correct one back from the API without filtering the results. --- provider/k8s/client.go | 27 +++++++---------- provider/kubernetes.go | 59 ++++++++++++++++--------------------- provider/kubernetes_test.go | 9 +++--- 3 files changed, 41 insertions(+), 54 deletions(-) diff --git a/provider/k8s/client.go b/provider/k8s/client.go index 930c9835e..0b2195615 100644 --- a/provider/k8s/client.go +++ b/provider/k8s/client.go @@ -16,12 +16,13 @@ const ( APIEndpoint = "/api/v1" extentionsEndpoint = "/apis/extensions/v1beta1" defaultIngress = "/ingresses" + namespaces = "/namespaces/" ) // Client is a client for the Kubernetes master. type Client interface { GetIngresses(predicate func(Ingress) bool) ([]Ingress, error) - GetServices(predicate func(Service) bool) ([]Service, error) + GetService(name, namespace string) (Service, error) GetEndpoints(name, namespace string) (Endpoints, error) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) } @@ -77,26 +78,20 @@ func (c *clientImpl) WatchIngresses(stopCh <-chan bool) (chan interface{}, chan return c.watch(getURL, stopCh) } -// GetServices returns all services in the cluster -func (c *clientImpl) GetServices(predicate func(Service) bool) ([]Service, error) { - getURL := c.endpointURL + APIEndpoint + "/services" +// GetService returns the named service from the named namespace +func (c *clientImpl) GetService(name, namespace string) (Service, error) { + getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/services/" + name body, err := c.do(c.request(getURL)) if err != nil { - return nil, fmt.Errorf("failed to create services request: GET %q : %v", getURL, err) + return Service{}, fmt.Errorf("failed to create services request: GET %q : %v", getURL, err) } - var serviceList ServiceList - if err := json.Unmarshal(body, &serviceList); err != nil { - return nil, fmt.Errorf("failed to decode list of services resources: %v", err) + var service Service + if err := json.Unmarshal(body, &service); err != nil { + return Service{}, fmt.Errorf("failed to decode service resource: %v", err) } - services := serviceList.Items[:0] - for _, service := range serviceList.Items { - if predicate(service) { - services = append(services, service) - } - } - return services, nil + return service, nil } // WatchServices returns all services in the cluster @@ -108,7 +103,7 @@ func (c *clientImpl) WatchServices(stopCh <-chan bool) (chan interface{}, chan e // GetEndpoints returns the named Endpoints // Endpoints have the same name as the coresponding service func (c *clientImpl) GetEndpoints(name, namespace string) (Endpoints, error) { - getURL := c.endpointURL + APIEndpoint + "/namespaces/" + namespace + "/endpoints/" + name + getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/endpoints/" + name body, err := c.do(c.request(getURL)) if err != nil { diff --git a/provider/kubernetes.go b/provider/kubernetes.go index 11556f49c..abb527af6 100644 --- a/provider/kubernetes.go +++ b/provider/kubernetes.go @@ -190,49 +190,42 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur Rule: ruleType + ":" + pa.Path, } } - services, err := k8sClient.GetServices(func(service k8s.Service) bool { - return service.ObjectMeta.Namespace == i.ObjectMeta.Namespace && service.Name == pa.Backend.ServiceName - }) + service, err := k8sClient.GetService(pa.Backend.ServiceName, i.ObjectMeta.Namespace) if err != nil { log.Warnf("Error retrieving services: %v", err) - continue - } - if len(services) == 0 { - // no backends found, delete frontend... delete(templateObjects.Frontends, r.Host+pa.Path) log.Warnf("Error retrieving services %s", pa.Backend.ServiceName) + continue } - for _, service := range services { - protocol := "http" - for _, port := range service.Spec.Ports { - if equalPorts(port, pa.Backend.ServicePort) { - if port.Port == 443 { - protocol = "https" + protocol := "http" + for _, port := range service.Spec.Ports { + if equalPorts(port, pa.Backend.ServicePort) { + if port.Port == 443 { + protocol = "https" + } + endpoints, err := k8sClient.GetEndpoints(service.ObjectMeta.Name, service.ObjectMeta.Namespace) + if err != nil { + log.Errorf("Error retrieving endpoints: %v", err) + continue + } + if len(endpoints.Subsets) == 0 { + log.Warnf("Endpoints not found for %s/%s, falling back to Service ClusterIP", service.ObjectMeta.Namespace, service.ObjectMeta.Name) + templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{ + URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(port.Port), + Weight: 1, } - endpoints, err := k8sClient.GetEndpoints(service.ObjectMeta.Name, service.ObjectMeta.Namespace) - if err != nil { - log.Errorf("Error retrieving endpoints: %v", err) - continue - } - if len(endpoints.Subsets) == 0 { - log.Warnf("Endpoints not found for %s/%s, falling back to Service ClusterIP", service.ObjectMeta.Namespace, service.ObjectMeta.Name) - templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{ - URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(port.Port), - Weight: 1, - } - } else { - for _, subset := range endpoints.Subsets { - for _, address := range subset.Addresses { - url := protocol + "://" + address.IP + ":" + strconv.Itoa(endpointPortNumber(port, subset.Ports)) - templateObjects.Backends[r.Host+pa.Path].Servers[url] = types.Server{ - URL: url, - Weight: 1, - } + } else { + for _, subset := range endpoints.Subsets { + for _, address := range subset.Addresses { + url := protocol + "://" + address.IP + ":" + strconv.Itoa(endpointPortNumber(port, subset.Ports)) + templateObjects.Backends[r.Host+pa.Path].Servers[url] = types.Server{ + URL: url, + Weight: 1, } } } - break } + break } } } diff --git a/provider/kubernetes_test.go b/provider/kubernetes_test.go index 9f6ddfb32..39a592f1b 100644 --- a/provider/kubernetes_test.go +++ b/provider/kubernetes_test.go @@ -1212,14 +1212,13 @@ func (c clientMock) GetIngresses(predicate func(k8s.Ingress) bool) ([]k8s.Ingres func (c clientMock) WatchIngresses(predicate func(k8s.Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) { return c.watchChan, make(chan error), nil } -func (c clientMock) GetServices(predicate func(k8s.Service) bool) ([]k8s.Service, error) { - var services []k8s.Service +func (c clientMock) GetService(name, namespace string) (k8s.Service, error) { for _, service := range c.services { - if predicate(service) { - services = append(services, service) + if service.Namespace == namespace && service.Name == name { + return service, nil } } - return services, nil + return k8s.Service{}, nil } func (c clientMock) GetEndpoints(name, namespace string) (k8s.Endpoints, error) { From 2e735f622f6b51e0d8dd95984cdf59e5f832dd9d Mon Sep 17 00:00:00 2001 From: Ed Robinson Date: Thu, 26 May 2016 12:09:36 +0100 Subject: [PATCH 13/48] Adds some more coverage of the endpoint port selection logic. --- provider/kubernetes_test.go | 60 +++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/provider/kubernetes_test.go b/provider/kubernetes_test.go index 39a592f1b..2e31a7df9 100644 --- a/provider/kubernetes_test.go +++ b/provider/kubernetes_test.go @@ -24,7 +24,7 @@ func TestLoadIngresses(t *testing.T) { Path: "/bar", Backend: k8s.IngressBackend{ ServiceName: "service1", - ServicePort: k8s.FromString("http"), + ServicePort: k8s.FromInt(80), }, }, }, @@ -39,7 +39,7 @@ func TestLoadIngresses(t *testing.T) { { Backend: k8s.IngressBackend{ ServiceName: "service3", - ServicePort: k8s.FromInt(443), + ServicePort: k8s.FromString("https"), }, }, { @@ -66,7 +66,6 @@ func TestLoadIngresses(t *testing.T) { ClusterIP: "10.0.0.1", Ports: []k8s.ServicePort{ { - Name: "http", Port: 80, }, }, @@ -98,6 +97,10 @@ func TestLoadIngresses(t *testing.T) { Ports: []k8s.ServicePort{ { Name: "http", + Port: 80, + }, + { + Name: "https", Port: 443, }, }, @@ -138,6 +141,49 @@ func TestLoadIngresses(t *testing.T) { }, }, }, + { + ObjectMeta: k8s.ObjectMeta{ + Name: "service3", + UID: "3", + Namespace: "testing", + }, + Subsets: []k8s.EndpointSubset{ + { + Addresses: []k8s.EndpointAddress{ + { + IP: "10.15.0.1", + }, + }, + Ports: []k8s.EndpointPort{ + { + Name: "http", + Port: 8080, + }, + { + Name: "https", + Port: 8443, + }, + }, + }, + { + Addresses: []k8s.EndpointAddress{ + { + IP: "10.15.0.2", + }, + }, + Ports: []k8s.EndpointPort{ + { + Name: "http", + Port: 9080, + }, + { + Name: "https", + Port: 9443, + }, + }, + }, + }, + }, } watchChan := make(chan interface{}) client := clientMock{ @@ -174,8 +220,12 @@ func TestLoadIngresses(t *testing.T) { URL: "http://10.0.0.2:802", Weight: 1, }, - "3": { - URL: "https://10.0.0.3:443", + "https://10.15.0.1:8443": { + URL: "https://10.15.0.1:8443", + Weight: 1, + }, + "https://10.15.0.2:9443": { + URL: "https://10.15.0.2:9443", Weight: 1, }, }, From 45589d5133144ca79111e041a65fe3dd43f75f3e Mon Sep 17 00:00:00 2001 From: Erin Dachtler Date: Thu, 26 May 2016 11:03:40 -0700 Subject: [PATCH 14/48] Reminder --- provider/docker_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provider/docker_test.go b/provider/docker_test.go index 8cf5d2771..9fd52ce43 100644 --- a/provider/docker_test.go +++ b/provider/docker_test.go @@ -263,7 +263,7 @@ func TestDockerGetPort(t *testing.T) { NetworkSettings: &docker.NetworkSettings{ NetworkSettingsBase: docker.NetworkSettingsBase{ Ports: nat.PortMap{ - "80/tcp": {}, + "80/tcp": {}, // TODO: Change so there is no exposed port }, }, }, From fe0a8f3363d3cae4785b18b9293f5984f8ea35f2 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 3 May 2016 16:52:14 +0200 Subject: [PATCH 15/48] Flaeg integration --- cmd.go | 230 -------------------------------- configuration.go | 266 ++++++++++++++++++------------------- flaeg_test.go | 91 +++++++++++++ provider/boltdb.go | 2 +- provider/consul.go | 2 +- provider/consul_catalog.go | 4 +- provider/docker.go | 14 +- provider/etcd.go | 2 +- provider/file.go | 2 +- provider/kubernetes.go | 11 +- provider/kv.go | 16 +-- provider/marathon.go | 10 +- provider/provider.go | 4 +- provider/zk.go | 2 +- traefik.go | 97 +++++++++++++- web.go | 9 +- 16 files changed, 361 insertions(+), 401 deletions(-) delete mode 100644 cmd.go create mode 100644 flaeg_test.go diff --git a/cmd.go b/cmd.go deleted file mode 100644 index 6580d6a15..000000000 --- a/cmd.go +++ /dev/null @@ -1,230 +0,0 @@ -/* -Copyright -*/ -package main - -import ( - "encoding/json" - fmtlog "log" - "os" - "strings" - "time" - - "net/http" - - log "github.com/Sirupsen/logrus" - "github.com/containous/traefik/middlewares" - "github.com/containous/traefik/provider" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var traefikCmd = &cobra.Command{ - Use: "traefik", - Short: "traefik, a modern reverse proxy", - Long: `traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease. -Complete documentation is available at http://traefik.io`, - Run: func(cmd *cobra.Command, args []string) { - run() - }, -} -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Print version", - Long: `Print version`, - Run: func(cmd *cobra.Command, args []string) { - fmtlog.Println(Version + " built on the " + BuildDate) - os.Exit(0) - }, -} - -var arguments = struct { - GlobalConfiguration - web bool - file bool - docker bool - dockerTLS bool - marathon bool - consul bool - consulTLS bool - consulCatalog bool - zookeeper bool - etcd bool - etcdTLS bool - boltdb bool - kubernetes bool -}{ - GlobalConfiguration{ - EntryPoints: make(EntryPoints), - Docker: &provider.Docker{ - TLS: &provider.DockerTLS{}, - }, - File: &provider.File{}, - Web: &WebProvider{}, - Marathon: &provider.Marathon{}, - Consul: &provider.Consul{ - Kv: provider.Kv{ - TLS: &provider.KvTLS{}, - }, - }, - ConsulCatalog: &provider.ConsulCatalog{}, - Zookeeper: &provider.Zookepper{}, - Etcd: &provider.Etcd{ - Kv: provider.Kv{ - TLS: &provider.KvTLS{}, - }, - }, - Boltdb: &provider.BoltDb{}, - Kubernetes: &provider.Kubernetes{}, - }, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, -} - -func init() { - traefikCmd.AddCommand(versionCmd) - traefikCmd.PersistentFlags().StringP("configFile", "c", "", "Configuration file to use (TOML).") - traefikCmd.PersistentFlags().BoolVarP(&arguments.Debug, "debug", "d", false, "Enable debug mode") - 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.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") - traefikCmd.PersistentFlags().StringVar(&arguments.Web.CertFile, "web.cerFile", "", "SSL certificate") - traefikCmd.PersistentFlags().StringVar(&arguments.Web.KeyFile, "web.keyFile", "", "SSL certificate") - traefikCmd.PersistentFlags().BoolVar(&arguments.Web.ReadOnly, "web.readOnly", false, "Enable read only API") - - traefikCmd.PersistentFlags().BoolVar(&arguments.file, "file", false, "Enable File backend") - traefikCmd.PersistentFlags().BoolVar(&arguments.File.Watch, "file.watch", true, "Watch provider") - traefikCmd.PersistentFlags().StringVar(&arguments.File.Filename, "file.filename", "", "Override default configuration template. For advanced users :)") - - traefikCmd.PersistentFlags().BoolVar(&arguments.docker, "docker", false, "Enable Docker backend") - traefikCmd.PersistentFlags().BoolVar(&arguments.Docker.Watch, "docker.watch", true, "Watch provider") - traefikCmd.PersistentFlags().StringVar(&arguments.Docker.Filename, "docker.filename", "", "Override default configuration template. For advanced users :)") - traefikCmd.PersistentFlags().StringVar(&arguments.Docker.Endpoint, "docker.endpoint", "unix:///var/run/docker.sock", "Docker server endpoint. Can be a tcp or a unix socket endpoint") - traefikCmd.PersistentFlags().StringVar(&arguments.Docker.Domain, "docker.domain", "", "Default domain used") - traefikCmd.PersistentFlags().BoolVar(&arguments.dockerTLS, "docker.tls", false, "Enable Docker TLS support") - traefikCmd.PersistentFlags().StringVar(&arguments.Docker.TLS.CA, "docker.tls.ca", "", "TLS CA") - traefikCmd.PersistentFlags().StringVar(&arguments.Docker.TLS.Cert, "docker.tls.cert", "", "TLS cert") - traefikCmd.PersistentFlags().StringVar(&arguments.Docker.TLS.Key, "docker.tls.key", "", "TLS key") - traefikCmd.PersistentFlags().BoolVar(&arguments.Docker.TLS.InsecureSkipVerify, "docker.tls.insecureSkipVerify", false, "TLS insecure skip verify") - - traefikCmd.PersistentFlags().BoolVar(&arguments.marathon, "marathon", false, "Enable Marathon backend") - traefikCmd.PersistentFlags().BoolVar(&arguments.Marathon.Watch, "marathon.watch", true, "Watch provider") - traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.Filename, "marathon.filename", "", "Override default configuration template. For advanced users :)") - traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.Endpoint, "marathon.endpoint", "http://127.0.0.1:8080", "Marathon server endpoint. You can also specify multiple endpoint for Marathon") - traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.Domain, "marathon.domain", "", "Default domain used") - traefikCmd.PersistentFlags().BoolVar(&arguments.Marathon.ExposedByDefault, "marathon.exposedByDefault", true, "Expose Marathon apps by default") - - traefikCmd.PersistentFlags().BoolVar(&arguments.consul, "consul", false, "Enable Consul backend") - traefikCmd.PersistentFlags().BoolVar(&arguments.Consul.Watch, "consul.watch", true, "Watch provider") - traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Filename, "consul.filename", "", "Override default configuration template. For advanced users :)") - traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Endpoint, "consul.endpoint", "127.0.0.1:8500", "Comma sepparated Consul server endpoints") - traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Prefix, "consul.prefix", "/traefik", "Prefix used for KV store") - traefikCmd.PersistentFlags().BoolVar(&arguments.consulTLS, "consul.tls", false, "Enable Consul TLS support") - traefikCmd.PersistentFlags().StringVar(&arguments.Consul.TLS.CA, "consul.tls.ca", "", "TLS CA") - traefikCmd.PersistentFlags().StringVar(&arguments.Consul.TLS.Cert, "consul.tls.cert", "", "TLS cert") - traefikCmd.PersistentFlags().StringVar(&arguments.Consul.TLS.Key, "consul.tls.key", "", "TLS key") - traefikCmd.PersistentFlags().BoolVar(&arguments.Consul.TLS.InsecureSkipVerify, "consul.tls.insecureSkipVerify", false, "TLS insecure skip verify") - - traefikCmd.PersistentFlags().BoolVar(&arguments.consulCatalog, "consulCatalog", false, "Enable Consul catalog backend") - traefikCmd.PersistentFlags().StringVar(&arguments.ConsulCatalog.Domain, "consulCatalog.domain", "", "Default domain used") - traefikCmd.PersistentFlags().StringVar(&arguments.ConsulCatalog.Endpoint, "consulCatalog.endpoint", "127.0.0.1:8500", "Consul server endpoint") - traefikCmd.PersistentFlags().StringVar(&arguments.ConsulCatalog.Prefix, "consulCatalog.prefix", "traefik", "Consul catalog tag prefix") - - traefikCmd.PersistentFlags().BoolVar(&arguments.zookeeper, "zookeeper", false, "Enable Zookeeper backend") - traefikCmd.PersistentFlags().BoolVar(&arguments.Zookeeper.Watch, "zookeeper.watch", true, "Watch provider") - traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Filename, "zookeeper.filename", "", "Override default configuration template. For advanced users :)") - traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Endpoint, "zookeeper.endpoint", "127.0.0.1:2181", "Comma sepparated Zookeeper server endpoints") - traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Prefix, "zookeeper.prefix", "/traefik", "Prefix used for KV store") - - traefikCmd.PersistentFlags().BoolVar(&arguments.etcd, "etcd", false, "Enable Etcd backend") - traefikCmd.PersistentFlags().BoolVar(&arguments.Etcd.Watch, "etcd.watch", true, "Watch provider") - traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Filename, "etcd.filename", "", "Override default configuration template. For advanced users :)") - traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Endpoint, "etcd.endpoint", "127.0.0.1:4001", "Comma sepparated Etcd server endpoints") - traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Prefix, "etcd.prefix", "/traefik", "Prefix used for KV store") - traefikCmd.PersistentFlags().BoolVar(&arguments.etcdTLS, "etcd.tls", false, "Enable Etcd TLS support") - traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.TLS.CA, "etcd.tls.ca", "", "TLS CA") - traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.TLS.Cert, "etcd.tls.cert", "", "TLS cert") - traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.TLS.Key, "etcd.tls.key", "", "TLS key") - traefikCmd.PersistentFlags().BoolVar(&arguments.Etcd.TLS.InsecureSkipVerify, "etcd.tls.insecureSkipVerify", false, "TLS insecure skip verify") - - traefikCmd.PersistentFlags().BoolVar(&arguments.boltdb, "boltdb", false, "Enable Boltdb backend") - traefikCmd.PersistentFlags().BoolVar(&arguments.Boltdb.Watch, "boltdb.watch", true, "Watch provider") - traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Filename, "boltdb.filename", "", "Override default configuration template. For advanced users :)") - traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Endpoint, "boltdb.endpoint", "127.0.0.1:4001", "Boltdb server endpoint") - traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Prefix, "boltdb.prefix", "/traefik", "Prefix used for KV store") - - traefikCmd.PersistentFlags().BoolVar(&arguments.kubernetes, "kubernetes", false, "Enable Kubernetes backend") - traefikCmd.PersistentFlags().StringVar(&arguments.Kubernetes.Endpoint, "kubernetes.endpoint", "http://127.0.0.1:8080", "Kubernetes server endpoint") - traefikCmd.PersistentFlags().StringSliceVar(&arguments.Kubernetes.Namespaces, "kubernetes.namespaces", []string{}, "Kubernetes namespaces") - - _ = viper.BindPFlag("configFile", traefikCmd.PersistentFlags().Lookup("configFile")) - _ = viper.BindPFlag("graceTimeOut", traefikCmd.PersistentFlags().Lookup("graceTimeOut")) - _ = viper.BindPFlag("logLevel", traefikCmd.PersistentFlags().Lookup("logLevel")) - _ = viper.BindPFlag("debug", traefikCmd.PersistentFlags().Lookup("debug")) - // TODO: wait for this issue to be corrected: https://github.com/spf13/viper/issues/105 - _ = viper.BindPFlag("providersThrottleDuration", traefikCmd.PersistentFlags().Lookup("providersThrottleDuration")) - _ = viper.BindPFlag("maxIdleConnsPerHost", traefikCmd.PersistentFlags().Lookup("maxIdleConnsPerHost")) - viper.SetDefault("providersThrottleDuration", time.Duration(2*time.Second)) - viper.SetDefault("logLevel", "ERROR") - viper.SetDefault("MaxIdleConnsPerHost", 200) -} - -func run() { - fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags) - - // load global configuration - globalConfiguration := LoadConfiguration() - - http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = globalConfiguration.MaxIdleConnsPerHost - loggerMiddleware := middlewares.NewLogger(globalConfiguration.AccessLogsFile) - defer loggerMiddleware.Close() - - // logging - level, err := log.ParseLevel(strings.ToLower(globalConfiguration.LogLevel)) - if err != nil { - log.Fatal("Error getting level", err) - } - log.SetLevel(level) - - if len(globalConfiguration.TraefikLogsFile) > 0 { - fi, err := os.OpenFile(globalConfiguration.TraefikLogsFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) - defer func() { - if err := fi.Close(); err != nil { - log.Error("Error closinf file", err) - } - }() - if err != nil { - log.Fatal("Error opening file", err) - } else { - log.SetOutput(fi) - log.SetFormatter(&log.TextFormatter{DisableColors: true, FullTimestamp: true, DisableSorting: true}) - } - } else { - log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableSorting: true}) - } - jsonConf, _ := json.Marshal(globalConfiguration) - log.Debugf("Global configuration loaded %s", string(jsonConf)) - server := NewServer(*globalConfiguration) - server.Start() - defer server.Close() - log.Info("Shutting down") -} diff --git a/configuration.go b/configuration.go index bae48d7cb..9b39d5db6 100644 --- a/configuration.go +++ b/configuration.go @@ -3,42 +3,44 @@ package main import ( "errors" "fmt" - fmtlog "log" - "regexp" - "strings" - "time" - "github.com/containous/traefik/acme" "github.com/containous/traefik/provider" "github.com/containous/traefik/types" - "github.com/mitchellh/mapstructure" - "github.com/spf13/viper" + "regexp" + "strings" + "time" ) +// TraefikConfiguration holds GlobalConfiguration and other stuff +type TraefikConfiguration struct { + GlobalConfiguration + ConfigFile string `short:"c" description:"Timeout in seconds. Duration to give active requests a chance to finish during hot-reloads"` +} + // 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 { - GraceTimeOut int64 + GraceTimeOut int64 `short:"g" description:"Configuration file to use (TOML)."` Debug bool - AccessLogsFile string - TraefikLogsFile string - LogLevel string - EntryPoints EntryPoints + AccessLogsFile string `description:"Access logs file"` + TraefikLogsFile string `description:"Traefik logs file"` + LogLevel string `short:"l" description:"Log level"` + EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'"` ACME *acme.ACME - DefaultEntryPoints DefaultEntryPoints - ProvidersThrottleDuration time.Duration - MaxIdleConnsPerHost int + DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"` + ProvidersThrottleDuration time.Duration `description:"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."` + MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used"` Retry *Retry - Docker *provider.Docker - File *provider.File - Web *WebProvider - Marathon *provider.Marathon - Consul *provider.Consul - ConsulCatalog *provider.ConsulCatalog - Etcd *provider.Etcd - Zookeeper *provider.Zookepper - Boltdb *provider.BoltDb - Kubernetes *provider.Kubernetes + Docker *provider.Docker `description:"Enable Docker backend"` + File *provider.File `description:"Enable File backend"` + Web *WebProvider `description:"Enable Web backend"` + Marathon *provider.Marathon `description:"Enable Marathon backend"` + Consul *provider.Consul `description:"Enable Consul backend"` + ConsulCatalog *provider.ConsulCatalog `description:"Enable Consul catalog backend"` + Etcd *provider.Etcd `description:"Enable Etcd backend"` + Zookeeper *provider.Zookepper `description:"Enable Zookeeper backend"` + Boltdb *provider.BoltDb `description:"Enable Boltdb backend"` + Kubernetes *provider.Kubernetes `description:"Enable Kubernetes backend"` } // DefaultEntryPoints holds default entry points @@ -47,6 +49,7 @@ 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 { + //TODO : return fmt.Sprintf("%#v", dep) } @@ -64,6 +67,14 @@ func (dep *DefaultEntryPoints) Set(value string) error { return nil } +// Get return the EntryPoints map +func (dep *DefaultEntryPoints) Get() interface{} { return DefaultEntryPoints(*dep) } + +// SetValue sets the EntryPoints map with val +func (dep *DefaultEntryPoints) SetValue(val interface{}) { + *dep = DefaultEntryPoints(val.(DefaultEntryPoints)) +} + // Type is type of the struct func (dep *DefaultEntryPoints) Type() string { return fmt.Sprint("defaultentrypoints²") @@ -75,6 +86,7 @@ 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 { + //TODO : return "" } @@ -122,6 +134,14 @@ func (ep *EntryPoints) Set(value string) error { return nil } +// Get return the EntryPoints map +func (ep *EntryPoints) Get() interface{} { return EntryPoints(*ep) } + +// SetValue sets the EntryPoints map with val +func (ep *EntryPoints) SetValue(val interface{}) { + *ep = EntryPoints(val.(EntryPoints)) +} + // Type is type of the struct func (ep *EntryPoints) Type() string { return fmt.Sprint("entrypoints²") @@ -154,6 +174,7 @@ type Certificates []Certificate // The String method's output will be used in diagnostics. func (certs *Certificates) String() string { if len(*certs) == 0 { + //TODO : return "" } return (*certs)[0].CertFile + "," + (*certs)[0].KeyFile @@ -191,117 +212,96 @@ type Retry struct { MaxMem int64 } -// NewGlobalConfiguration returns a GlobalConfiguration with default values. -func NewGlobalConfiguration() *GlobalConfiguration { - return new(GlobalConfiguration) +// NewTraefikPointersConfiguration creates a TraefikConfiguration with pointers default values +func NewTraefikPointersConfiguration() *TraefikConfiguration { + //default Docker + var defaultDocker provider.Docker + defaultDocker.Watch = true + defaultDocker.Endpoint = "unix:///var/run/docker.sock" + defaultDocker.TLS = &provider.DockerTLS{} + + // default File + var defaultFile provider.File + defaultFile.Watch = true + defaultFile.Filename = "" //needs equivalent to viper.ConfigFileUsed() + + // default Web + var defaultWeb WebProvider + defaultWeb.Address = ":8080" + + // default Marathon + var defaultMarathon provider.Marathon + defaultMarathon.Watch = true + defaultMarathon.Endpoint = "http://127.0.0.1:8080" + defaultMarathon.ExposedByDefault = true + + // default Consul + var defaultConsul provider.Consul + defaultConsul.Watch = true + defaultConsul.Endpoint = "127.0.0.1:8500" + defaultConsul.Prefix = "/traefik" + defaultConsul.TLS = &provider.KvTLS{} + + // default ConsulCatalog + var defaultConsulCatalog provider.ConsulCatalog + defaultConsulCatalog.Endpoint = "127.0.0.1:8500" + + // default Etcd + var defaultEtcd provider.Etcd + defaultEtcd.Watch = true + defaultEtcd.Endpoint = "127.0.0.1:400" + defaultEtcd.Prefix = "/traefik" + defaultEtcd.TLS = &provider.KvTLS{} + + //default Zookeeper + var defaultZookeeper provider.Zookepper + defaultZookeeper.Watch = true + defaultZookeeper.Endpoint = "127.0.0.1:2181" + defaultZookeeper.Prefix = "/traefik" + + //default Boltdb + var defaultBoltDb provider.BoltDb + defaultBoltDb.Watch = true + defaultBoltDb.Endpoint = "127.0.0.1:4001" + defaultBoltDb.Prefix = "/traefik" + + //default Kubernetes + var defaultKubernetes provider.Kubernetes + defaultKubernetes.Watch = true + defaultKubernetes.Endpoint = "127.0.0.1:8080" + + defaultConfiguration := GlobalConfiguration{ + Docker: &defaultDocker, + File: &defaultFile, + Web: &defaultWeb, + Marathon: &defaultMarathon, + Consul: &defaultConsul, + ConsulCatalog: &defaultConsulCatalog, + Etcd: &defaultEtcd, + Zookeeper: &defaultZookeeper, + Boltdb: &defaultBoltDb, + Kubernetes: &defaultKubernetes, + } + return &TraefikConfiguration{ + GlobalConfiguration: defaultConfiguration, + } } -// LoadConfiguration returns a GlobalConfiguration. -func LoadConfiguration() *GlobalConfiguration { - configuration := NewGlobalConfiguration() - viper.SetEnvPrefix("traefik") - viper.SetConfigType("toml") - viper.AutomaticEnv() - if len(viper.GetString("configFile")) > 0 { - viper.SetConfigFile(viper.GetString("configFile")) - } else { - viper.SetConfigName("traefik") // name of config file (without extension) +// NewTraefikConfiguration creates a TraefikConfiguration with default values +func NewTraefikConfiguration() *TraefikConfiguration { + return &TraefikConfiguration{ + GlobalConfiguration: GlobalConfiguration{ + GraceTimeOut: 10, + AccessLogsFile: "log/access.log", + TraefikLogsFile: "log/traefik.log", + LogLevel: "ERROR", + EntryPoints: map[string]*EntryPoint{"http": &EntryPoint{Address: ":80"}}, + DefaultEntryPoints: []string{"http"}, + ProvidersThrottleDuration: time.Duration(2 * time.Second), + MaxIdleConnsPerHost: 200, + }, + ConfigFile: "", } - viper.AddConfigPath("/etc/traefik/") // path to look for the config file in - viper.AddConfigPath("$HOME/.traefik/") // call multiple times to add many search paths - viper.AddConfigPath(".") // optionally look for config in the working directory - if err := viper.ReadInConfig(); err != nil { - if len(viper.ConfigFileUsed()) > 0 { - fmtlog.Printf("Error reading configuration file: %s", err) - } else { - fmtlog.Printf("No configuration file found") - } - } - - 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) - } - if arguments.file { - viper.Set("file", arguments.File) - } - if !arguments.dockerTLS { - arguments.Docker.TLS = nil - } - if arguments.docker { - viper.Set("docker", arguments.Docker) - } - if arguments.marathon { - viper.Set("marathon", arguments.Marathon) - } - if !arguments.consulTLS { - arguments.Consul.TLS = nil - } - if arguments.consul { - viper.Set("consul", arguments.Consul) - } - if arguments.consulCatalog { - viper.Set("consulCatalog", arguments.ConsulCatalog) - } - if arguments.zookeeper { - viper.Set("zookeeper", arguments.Zookeeper) - } - if !arguments.etcdTLS { - arguments.Etcd.TLS = nil - } - if arguments.etcd { - viper.Set("etcd", arguments.Etcd) - } - if arguments.boltdb { - viper.Set("boltdb", arguments.Boltdb) - } - if arguments.kubernetes { - viper.Set("kubernetes", arguments.Kubernetes) - } - if err := unmarshal(&configuration); err != nil { - - 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 -} - -func unmarshal(rawVal interface{}) error { - config := &mapstructure.DecoderConfig{ - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), - Metadata: nil, - Result: rawVal, - WeaklyTypedInput: true, - } - - decoder, err := mapstructure.NewDecoder(config) - if err != nil { - return err - } - - err = decoder.Decode(viper.AllSettings()) - if err != nil { - return err - } - return nil } type configs map[string]*types.Configuration diff --git a/flaeg_test.go b/flaeg_test.go new file mode 100644 index 000000000..bbf36c42d --- /dev/null +++ b/flaeg_test.go @@ -0,0 +1,91 @@ +package main + +import ( + "github.com/cocap10/flaeg" + "reflect" + "testing" + "time" +) + +func TestLoad(t *testing.T) { + var configuration GlobalConfiguration + defaultConfiguration := NewGlobalConfiguration() + args := []string{ + // "-h", + "--docker", + "--file", + "--web", + "--marathon", + "--consul", + "--consulcatalog", + "--etcd", + "--zookeeper", + "--boltdb", + } + if err := flaeg.Load(&configuration, defaultConfiguration, args); err != nil { + t.Fatalf("Error: %s", err) + } + // fmt.Printf("result : \n%+v\n", configuration) + if !reflect.DeepEqual(configuration, *defaultConfiguration) { + t.Fatalf("\nexpected\t: %+v\ngot\t\t\t: %+v", *defaultConfiguration, configuration) + } +} + +func TestLoadWithParsers(t *testing.T) { + var configuration GlobalConfiguration + defaultConfiguration := NewGlobalConfiguration() + args := []string{ + // "-h", + "--docker", + // "--file", + "--web.address=:8888", + "--marathon", + "--consul", + "--consulcatalog", + "--etcd.tls.insecureskipverify", + "--zookeeper", + "--boltdb", + "--accesslogsfile=log2/access.log", + "--entrypoints=Name:http Address::8000 Redirect.EntryPoint:https", + "--entrypoints=Name:https Address::8443 Redirect.EntryPoint:http", + "--defaultentrypoints=https", + "--defaultentrypoints=ssh", + "--providersthrottleduration=4s", + } + parsers := map[reflect.Type]flaeg.Parser{} + var defaultEntryPointsParser DefaultEntryPoints + parsers[reflect.TypeOf(DefaultEntryPoints{})] = &defaultEntryPointsParser + entryPointsParser := EntryPoints{} + parsers[reflect.TypeOf(EntryPoints{})] = &entryPointsParser + + if err := flaeg.LoadWithParsers(&configuration, defaultConfiguration, args, parsers); err != nil { + t.Fatalf("Error: %s", err) + } + // fmt.Printf("result : \n%+v\n", configuration) + + //Check + check := *defaultConfiguration + check.File = nil + check.Web.Address = ":8888" + check.AccessLogsFile = "log2/access.log" + check.Etcd.TLS.InsecureSkipVerify = true + check.EntryPoints = make(map[string]*EntryPoint) + check.EntryPoints["http"] = &EntryPoint{ + Address: ":8000", + Redirect: &Redirect{ + EntryPoint: "https", + }, + } + check.EntryPoints["https"] = &EntryPoint{ + Address: ":8443", + Redirect: &Redirect{ + EntryPoint: "http", + }, + } + check.DefaultEntryPoints = []string{"https", "ssh"} + check.ProvidersThrottleDuration = time.Duration(4 * time.Second) + + if !reflect.DeepEqual(&configuration, &check) { + t.Fatalf("\nexpected\t: %+v\ngot\t\t\t: %+v", check, configuration) + } +} diff --git a/provider/boltdb.go b/provider/boltdb.go index 0f941627e..966a4b8da 100644 --- a/provider/boltdb.go +++ b/provider/boltdb.go @@ -9,7 +9,7 @@ import ( // BoltDb holds configurations of the BoltDb provider. type BoltDb struct { - Kv `mapstructure:",squash"` + Kv `mapstructure:",squash" description:"go through"` } // Provide allows the provider to provide configurations to traefik diff --git a/provider/consul.go b/provider/consul.go index a2df6852b..d6d81f3e7 100644 --- a/provider/consul.go +++ b/provider/consul.go @@ -9,7 +9,7 @@ import ( // Consul holds configurations of the Consul provider. type Consul struct { - Kv `mapstructure:",squash"` + Kv `mapstructure:",squash" description:"go through"` } // Provide allows the provider to provide configurations to traefik diff --git a/provider/consul_catalog.go b/provider/consul_catalog.go index 90606e9d4..c17cfe0a9 100644 --- a/provider/consul_catalog.go +++ b/provider/consul_catalog.go @@ -24,8 +24,8 @@ const ( // ConsulCatalog holds configurations of the Consul catalog provider. type ConsulCatalog struct { BaseProvider `mapstructure:",squash"` - Endpoint string - Domain string + Endpoint string `description:"Consul server endpoint"` + Domain string `description:"Default domain used"` client *api.Client Prefix string } diff --git a/provider/docker.go b/provider/docker.go index 7169f20e1..55b73f8b6 100644 --- a/provider/docker.go +++ b/provider/docker.go @@ -30,17 +30,17 @@ const DockerAPIVersion string = "1.21" // Docker holds configurations of the Docker provider. type Docker struct { BaseProvider `mapstructure:",squash"` - Endpoint string - Domain string - TLS *DockerTLS + Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"` + Domain string `description:"Default domain used"` + TLS *DockerTLS `description:"Enable Docker TLS support"` } // DockerTLS holds TLS specific configurations type DockerTLS struct { - CA string - Cert string - Key string - InsecureSkipVerify bool + CA string `description:"TLS CA"` + Cert string `description:"TLS cert"` + Key string `description:"TLS key"` + InsecureSkipVerify bool `description:"TLS insecure skip verify"` } func (provider *Docker) createClient() (client.APIClient, error) { diff --git a/provider/etcd.go b/provider/etcd.go index 3d0b9e428..3344245e4 100644 --- a/provider/etcd.go +++ b/provider/etcd.go @@ -9,7 +9,7 @@ import ( // Etcd holds configurations of the Etcd provider. type Etcd struct { - Kv `mapstructure:",squash"` + Kv `mapstructure:",squash" description:"go through"` } // Provide allows the provider to provide configurations to traefik diff --git a/provider/file.go b/provider/file.go index 6b943b199..c5597ec19 100644 --- a/provider/file.go +++ b/provider/file.go @@ -14,7 +14,7 @@ import ( // File holds configurations of the File provider. type File struct { - BaseProvider `mapstructure:",squash"` + BaseProvider `mapstructure:",squash" description:"go through"` } // Provide allows the provider to provide configurations to traefik diff --git a/provider/kubernetes.go b/provider/kubernetes.go index abb527af6..bdaf245ce 100644 --- a/provider/kubernetes.go +++ b/provider/kubernetes.go @@ -20,12 +20,15 @@ const ( serviceAccountCACert = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" ) +// Namespaces holds kubernetes namespaces +type Namespaces []string + // Kubernetes holds configurations of the Kubernetes provider. type Kubernetes struct { BaseProvider `mapstructure:",squash"` - Endpoint string - disablePassHostHeaders bool - Namespaces []string + Endpoint string `description:"Kubernetes server endpoint"` + DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"` + Namespaces Namespaces `description:"Kubernetes namespaces"` } func (provider *Kubernetes) createClient() (k8s.Client, error) { @@ -259,7 +262,7 @@ func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool { } func (provider *Kubernetes) getPassHostHeader() bool { - if provider.disablePassHostHeaders { + if provider.DisablePassHostHeaders { return false } return true diff --git a/provider/kv.go b/provider/kv.go index b257fd71e..852799dcb 100644 --- a/provider/kv.go +++ b/provider/kv.go @@ -22,20 +22,20 @@ import ( // Kv holds common configurations of key-value providers. type Kv struct { - BaseProvider `mapstructure:",squash"` - Endpoint string - Prefix string - TLS *KvTLS + BaseProvider `mapstructure:",squash" description:"go through"` + Endpoint string `description:"Comma sepparated server endpoints"` + Prefix string `description:"Prefix used for KV store"` + TLS *KvTLS `description:"Enable TLS support"` storeType store.Backend kvclient store.Store } // KvTLS holds TLS specific configurations type KvTLS struct { - CA string - Cert string - Key string - InsecureSkipVerify bool + CA string `description:"TLS CA"` + Cert string `description:"TLS cert"` + Key string `description:"TLS key"` + InsecureSkipVerify bool `description:"TLS insecure skip verify"` } func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) error { diff --git a/provider/marathon.go b/provider/marathon.go index 63ba9d8c0..91b4a2e8d 100644 --- a/provider/marathon.go +++ b/provider/marathon.go @@ -20,10 +20,10 @@ import ( // Marathon holds configuration of the Marathon provider. type Marathon struct { - BaseProvider `mapstructure:",squash"` - Endpoint string - Domain string - ExposedByDefault bool + BaseProvider `mapstructure:",squash" description:"go through"` + Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"` + Domain string `description:"Default domain used"` + ExposedByDefault bool `description:"Expose Marathon apps by default"` Basic *MarathonBasic TLS *tls.Config marathonClient marathon.Marathon @@ -36,8 +36,8 @@ type MarathonBasic struct { } type lightMarathonClient interface { - Applications(url.Values) (*marathon.Applications, error) AllTasks(v url.Values) (*marathon.Tasks, error) + Applications(url.Values) (*marathon.Applications, error) } // Provide allows the provider to provide configurations to traefik diff --git a/provider/provider.go b/provider/provider.go index 3fa7612ec..eddf5a76c 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -22,8 +22,8 @@ type Provider interface { // BaseProvider should be inherited by providers type BaseProvider struct { - Watch bool - Filename string + Watch bool `description:"Watch provider"` + Filename string `description:"Override default configuration template. For advanced users :)"` } func (p *BaseProvider) getConfiguration(defaultTemplateFile string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) { diff --git a/provider/zk.go b/provider/zk.go index 77b28100f..e8164a75f 100644 --- a/provider/zk.go +++ b/provider/zk.go @@ -9,7 +9,7 @@ import ( // Zookepper holds configurations of the Zookepper provider. type Zookepper struct { - Kv + Kv `description:"go through"` } // Provide allows the provider to provide configurations to traefik diff --git a/traefik.go b/traefik.go index dec978a6f..a1c060914 100644 --- a/traefik.go +++ b/traefik.go @@ -1,16 +1,111 @@ package main import ( + "encoding/json" + log "github.com/Sirupsen/logrus" + "github.com/containous/flaeg" + "github.com/containous/staert" + "github.com/containous/traefik/middlewares" fmtlog "log" + "net/http" "os" + "reflect" "runtime" + "strings" ) func main() { runtime.GOMAXPROCS(runtime.NumCPU()) - if err := traefikCmd.Execute(); err != nil { + + //traefik config inits + traefikConfiguration := NewTraefikConfiguration() + traefikPointersConfiguration := NewTraefikPointersConfiguration() + //traefik Command init + traefikCmd := &flaeg.Command{ + Name: "traefik", + Description: `traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease. +Complete documentation is available at https://traefik.io`, + Config: traefikConfiguration, + DefaultPointersConfig: traefikPointersConfiguration, + Run: func() error { + run(traefikConfiguration) + return nil + }, + } + + //version Command init + versionCmd := &flaeg.Command{ + Name: "version", + Description: `Print version`, + Run: func() error { + fmtlog.Println(Version + " built on the " + BuildDate) + return nil + }, + } + + //staert init + s := staert.NewStaert(traefikCmd) + + //init toml source + toml := staert.NewTomlSource("traefik", []string{traefikConfiguration.ConfigFile, "/etc/traefik/", "$HOME/.traefik/", "."}) + //init flaeg source + f := flaeg.New(traefikCmd, os.Args[1:]) + //add custom parsers + f.AddParser(reflect.TypeOf(EntryPoints{}), &EntryPoints{}) + f.AddParser(reflect.TypeOf(DefaultEntryPoints{}), &DefaultEntryPoints{}) + //Wait for DefaultSliceStringParser + //add version command + f.AddCommand(versionCmd) + + //add sources to staert + s.AddSource(f) + s.AddSource(toml) + s.AddSource(f) + if err := s.Run(); err != nil { fmtlog.Println(err) os.Exit(-1) } + os.Exit(0) } + +func run(traefikConfiguration *TraefikConfiguration) { + fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags) + + // load global configuration + globalConfiguration := traefikConfiguration.GlobalConfiguration + + http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = globalConfiguration.MaxIdleConnsPerHost + loggerMiddleware := middlewares.NewLogger(globalConfiguration.AccessLogsFile) + defer loggerMiddleware.Close() + + // logging + level, err := log.ParseLevel(strings.ToLower(globalConfiguration.LogLevel)) + if err != nil { + log.Fatal("Error getting level", err) + } + log.SetLevel(level) + + if len(globalConfiguration.TraefikLogsFile) > 0 { + fi, err := os.OpenFile(globalConfiguration.TraefikLogsFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + defer func() { + if err := fi.Close(); err != nil { + log.Error("Error closinf file", err) + } + }() + if err != nil { + log.Fatal("Error opening file", err) + } else { + log.SetOutput(fi) + log.SetFormatter(&log.TextFormatter{DisableColors: true, FullTimestamp: true, DisableSorting: true}) + } + } else { + log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableSorting: true}) + } + jsonConf, _ := json.Marshal(globalConfiguration) + log.Debugf("Global configuration loaded %s", string(jsonConf)) + server := NewServer(globalConfiguration) + server.Start() + defer server.Close() + log.Info("Shutting down") +} diff --git a/web.go b/web.go index f32874aa6..bf77685a4 100644 --- a/web.go +++ b/web.go @@ -23,10 +23,11 @@ var metrics = stats.New() // WebProvider is a provider.Provider implementation that provides the UI. // FIXME to be handled another way. type WebProvider struct { - Address string - CertFile, KeyFile string - ReadOnly bool - server *Server + Address string `description:"Web administration port"` + CertFile string `description:"SSL certificate"` + KeyFile string `description:"SSL certificate"` + ReadOnly bool `description:"Enable read only API"` + server *Server } var ( From 414fb1f406951f17226fda57172c574cd55dc4e1 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 May 2016 15:45:48 +0200 Subject: [PATCH 16/48] add kubernetes.Namespaces parser --- provider/kubernetes.go | 24 ++++++++++++++++++++++++ traefik.go | 3 ++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/provider/kubernetes.go b/provider/kubernetes.go index bdaf245ce..3dd3e9e4e 100644 --- a/provider/kubernetes.go +++ b/provider/kubernetes.go @@ -1,6 +1,7 @@ package provider import ( + "fmt" log "github.com/Sirupsen/logrus" "github.com/cenkalti/backoff" "github.com/containous/traefik/provider/k8s" @@ -23,6 +24,29 @@ const ( // Namespaces holds kubernetes namespaces type Namespaces []string +//Set adds strings elem into the the parser +//it splits str on , and ; +func (ns *Namespaces) Set(str string) error { + fargs := func(c rune) bool { + return c == ',' || c == ';' + } + // get function + slice := strings.FieldsFunc(str, fargs) + *ns = append(*ns, slice...) + return nil +} + +//Get []string +func (ns *Namespaces) Get() interface{} { return Namespaces(*ns) } + +//String return slice in a string +func (ns *Namespaces) String() string { return fmt.Sprintf("%v", *ns) } + +//SetValue sets []string into the parser +func (ns *Namespaces) SetValue(val interface{}) { + *ns = Namespaces(val.(Namespaces)) +} + // Kubernetes holds configurations of the Kubernetes provider. type Kubernetes struct { BaseProvider `mapstructure:",squash"` diff --git a/traefik.go b/traefik.go index a1c060914..b0e2cb745 100644 --- a/traefik.go +++ b/traefik.go @@ -6,6 +6,7 @@ import ( "github.com/containous/flaeg" "github.com/containous/staert" "github.com/containous/traefik/middlewares" + "github.com/containous/traefik/provider" fmtlog "log" "net/http" "os" @@ -53,7 +54,7 @@ Complete documentation is available at https://traefik.io`, //add custom parsers f.AddParser(reflect.TypeOf(EntryPoints{}), &EntryPoints{}) f.AddParser(reflect.TypeOf(DefaultEntryPoints{}), &DefaultEntryPoints{}) - //Wait for DefaultSliceStringParser + f.AddParser(reflect.TypeOf(provider.Namespaces{}), &provider.Namespaces{}) //add version command f.AddCommand(versionCmd) From e115e3c4e70b4f5a0aa83a35303450c17508d277 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 19 May 2016 17:12:36 +0200 Subject: [PATCH 17/48] fix default value --- configuration.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configuration.go b/configuration.go index 9b39d5db6..e54e49c90 100644 --- a/configuration.go +++ b/configuration.go @@ -292,8 +292,8 @@ func NewTraefikConfiguration() *TraefikConfiguration { return &TraefikConfiguration{ GlobalConfiguration: GlobalConfiguration{ GraceTimeOut: 10, - AccessLogsFile: "log/access.log", - TraefikLogsFile: "log/traefik.log", + AccessLogsFile: "", + TraefikLogsFile: "", LogLevel: "ERROR", EntryPoints: map[string]*EntryPoint{"http": &EntryPoint{Address: ":80"}}, DefaultEntryPoints: []string{"http"}, From 629be45c4a779ea7553a80abacdeb6a31b5682c1 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 19 May 2016 17:44:33 +0200 Subject: [PATCH 18/48] fix DisablePassHostHeaders --- provider/kubernetes_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/provider/kubernetes_test.go b/provider/kubernetes_test.go index 2e31a7df9..8426b73ad 100644 --- a/provider/kubernetes_test.go +++ b/provider/kubernetes_test.go @@ -416,7 +416,7 @@ func TestRuleType(t *testing.T) { services: services, watchChan: watchChan, } - provider := Kubernetes{disablePassHostHeaders: true} + provider := Kubernetes{DisablePassHostHeaders: true} actualConfig, err := provider.loadIngresses(client) actual := actualConfig.Frontends if err != nil { @@ -538,7 +538,7 @@ func TestGetPassHostHeader(t *testing.T) { services: services, watchChan: watchChan, } - provider := Kubernetes{disablePassHostHeaders: true} + provider := Kubernetes{DisablePassHostHeaders: true} actual, err := provider.loadIngresses(client) if err != nil { t.Fatalf("error %+v", err) From 1e27c2dabe9cbaa63493b86559d43f8ada19aebf Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 24 May 2016 14:23:42 +0200 Subject: [PATCH 19/48] fix TestNoOrInexistentConfigShouldNotFail --- integration/basic_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/integration/basic_test.go b/integration/basic_test.go index 40f5ab997..0e7220279 100644 --- a/integration/basic_test.go +++ b/integration/basic_test.go @@ -15,7 +15,7 @@ import ( // SimpleSuite type SimpleSuite struct{ BaseSuite } -func (s *SimpleSuite) TestNoOrInexistentConfigShouldFail(c *check.C) { +func (s *SimpleSuite) TestNoOrInexistentConfigShouldNotFail(c *check.C) { cmd := exec.Command(traefikBinary) var b bytes.Buffer @@ -26,7 +26,7 @@ func (s *SimpleSuite) TestNoOrInexistentConfigShouldFail(c *check.C) { time.Sleep(500 * time.Millisecond) output := b.Bytes() - c.Assert(string(output), checker.Contains, "No configuration file found") + c.Assert(string(output), checker.Not(checker.Contains), "No configuration file found") cmd.Process.Kill() nonExistentFile := "non/existent/file.toml" @@ -39,7 +39,7 @@ func (s *SimpleSuite) TestNoOrInexistentConfigShouldFail(c *check.C) { time.Sleep(500 * time.Millisecond) output = b.Bytes() - c.Assert(string(output), checker.Contains, fmt.Sprintf("Error reading configuration file: open %s: no such file or directory", nonExistentFile)) + c.Assert(string(output), checker.Not(checker.Contains), fmt.Sprintf("Error reading configuration file: open %s: no such file or directory", nonExistentFile)) cmd.Process.Kill() } @@ -55,7 +55,7 @@ func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) { defer cmd.Process.Kill() output := b.Bytes() - c.Assert(string(output), checker.Contains, "While parsing config: Near line 0 (last key parsed ''): Bare keys cannot contain '{'") + c.Assert(string(output), checker.Contains, "Near line 0 (last key parsed ''): Bare keys cannot contain '{'") } func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) { From 1a0f347023b32dc687b1206ec08e50ce6a6ae8e2 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 24 May 2016 14:58:25 +0200 Subject: [PATCH 20/48] update default value --- configuration.go | 6 +++--- traefik.go | 33 +++++++++++++++++++++++++-------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/configuration.go b/configuration.go index e54e49c90..daa037246 100644 --- a/configuration.go +++ b/configuration.go @@ -77,7 +77,7 @@ func (dep *DefaultEntryPoints) SetValue(val interface{}) { // Type is type of the struct func (dep *DefaultEntryPoints) Type() string { - return fmt.Sprint("defaultentrypoints²") + return fmt.Sprint("defaultentrypoints") } // EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...) @@ -295,8 +295,8 @@ func NewTraefikConfiguration() *TraefikConfiguration { AccessLogsFile: "", TraefikLogsFile: "", LogLevel: "ERROR", - EntryPoints: map[string]*EntryPoint{"http": &EntryPoint{Address: ":80"}}, - DefaultEntryPoints: []string{"http"}, + EntryPoints: map[string]*EntryPoint{}, + DefaultEntryPoints: []string{}, ProvidersThrottleDuration: time.Duration(2 * time.Second), MaxIdleConnsPerHost: 200, }, diff --git a/traefik.go b/traefik.go index b0e2cb745..adb5c4475 100644 --- a/traefik.go +++ b/traefik.go @@ -36,19 +36,16 @@ Complete documentation is available at https://traefik.io`, //version Command init versionCmd := &flaeg.Command{ - Name: "version", - Description: `Print version`, + Name: "version", + Description: `Print version`, + Config: struct{}{}, + DefaultPointersConfig: struct{}{}, Run: func() error { fmtlog.Println(Version + " built on the " + BuildDate) return nil }, } - //staert init - s := staert.NewStaert(traefikCmd) - - //init toml source - toml := staert.NewTomlSource("traefik", []string{traefikConfiguration.ConfigFile, "/etc/traefik/", "$HOME/.traefik/", "."}) //init flaeg source f := flaeg.New(traefikCmd, os.Args[1:]) //add custom parsers @@ -57,11 +54,31 @@ Complete documentation is available at https://traefik.io`, f.AddParser(reflect.TypeOf(provider.Namespaces{}), &provider.Namespaces{}) //add version command f.AddCommand(versionCmd) + if _, err := f.Parse(traefikCmd); err != nil { + fmtlog.Println(err) + os.Exit(-1) + } + + //staert init + s := staert.NewStaert(traefikCmd) + //init toml source + toml := staert.NewTomlSource("traefik", []string{traefikConfiguration.ConfigFile, "/etc/traefik/", "$HOME/.traefik/", "."}) //add sources to staert - s.AddSource(f) s.AddSource(toml) s.AddSource(f) + if _, err := s.GetConfig(); err != nil { + fmtlog.Println(err) + } + if traefikConfiguration.File != nil && len(traefikConfiguration.File.Filename) == 0 { + // no filename, setting to global config file + log.Debugf("ConfigFileUsed %s", toml.ConfigFileUsed()) + traefikConfiguration.File.Filename = toml.ConfigFileUsed() + } + if len(traefikConfiguration.EntryPoints) == 0 { + traefikConfiguration.EntryPoints = map[string]*EntryPoint{"http": &EntryPoint{Address: ":80"}} + traefikConfiguration.DefaultEntryPoints = []string{"http"} + } if err := s.Run(); err != nil { fmtlog.Println(err) os.Exit(-1) From 89e00eb5a452f0a4e2e7f5767a3e0fc0e6f157c8 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 24 May 2016 14:59:18 +0200 Subject: [PATCH 21/48] add staert & fleag --- glide.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/glide.yaml b/glide.yaml index 3ca22fb8c..7d9d76ba8 100644 --- a/glide.yaml +++ b/glide.yaml @@ -187,3 +187,6 @@ import: - package: github.com/parnurzeal/gorequest - package: github.com/mattn/go-shellwords - package: github.com/moul/http2curl +- package: github.com/containous/flaeg +- package: github.com/containous/staert +- package: github.com/ogier/pflag From 0821c7bdd9c72521a756184142cbd098e070227d Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 24 May 2016 15:45:20 +0200 Subject: [PATCH 22/48] Add version in logs --- traefik.go | 1 + 1 file changed, 1 insertion(+) diff --git a/traefik.go b/traefik.go index adb5c4475..636514f2d 100644 --- a/traefik.go +++ b/traefik.go @@ -121,6 +121,7 @@ func run(traefikConfiguration *TraefikConfiguration) { log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableSorting: true}) } jsonConf, _ := json.Marshal(globalConfiguration) + log.Infof("Traefik version %s built on %s", Version, BuildDate) log.Debugf("Global configuration loaded %s", string(jsonConf)) server := NewServer(globalConfiguration) server.Start() From 059da90a96565cdbd506b5b4c2dcbf4ff6806e50 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 24 May 2016 15:45:41 +0200 Subject: [PATCH 23/48] clean glide dependancies --- glide.lock | 172 +++++++++++----------------------------- glide.yaml | 228 +++++++++++++---------------------------------------- 2 files changed, 103 insertions(+), 297 deletions(-) diff --git a/glide.lock b/glide.lock index b52394f42..a88e1030c 100644 --- a/glide.lock +++ b/glide.lock @@ -1,39 +1,40 @@ -hash: 68bc4f87206f9a486e1455f1dfcad737369c359a803566271432fcb85de3a12c -updated: 2016-05-23T13:57:35.191541555+02:00 +hash: 50e873651126f0af9204fd8cc11f10917ce56b76479310ada578f5487162e0c4 +updated: 2016-05-24T15:42:45.810673781+02:00 imports: -- name: github.com/alecthomas/template - version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0 -- name: github.com/alecthomas/units - version: 6b4e7dc5e3143b85ea77909c72caf89416fc2915 - name: github.com/boltdb/bolt - version: 51f99c862475898df9773747d3accd05a7ca33c1 + version: dfb21201d9270c1082d5fb0f07f500311ff72f18 - name: github.com/BurntSushi/toml - version: bbd5bb678321a0d6e58f1099321dfa73391c1b6f + version: f0aeabca5a127c4078abb8c8d64298b147264b55 - name: github.com/BurntSushi/ty version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74 subpackages: - fun - name: github.com/cenkalti/backoff - version: 4dc77674aceaabba2c7e3da25d4c823edfb73f99 + version: c29158af31815ccc31ca29c86c121bc39e00d3d8 - name: github.com/codahale/hdrhistogram - version: 954f16e8b9ef0e5d5189456aa4c1202758e04f17 + version: 9208b142303c12d8899bae836fd524ac9338b4fd - name: github.com/codegangsta/cli version: bf4a526f48af7badd25d2cb02d587e1b01be3b50 - name: github.com/codegangsta/negroni - version: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b + version: ffbc66b612ee3eac2eba29aedce4c3a65e4dd0a1 +- name: github.com/containous/flaeg + version: e390ea90e4ee21ff73fb1630b8c50a843a6d7674 - name: github.com/containous/oxy version: 183212964e13e7b8afe01a08b193d04300554a68 subpackages: - cbreaker - - forward - - memmetrics - - roundrobin - - utils - connlimit + - forward + - roundrobin - stream + - utils +- name: github.com/containous/staert + version: f0da5ea8404f0d3999d72ba8f8736d3aa4b8c8f1 - name: github.com/coreos/etcd - version: 26e52d2bce9e3e11b77b68cc84bf91aebb1ef637 + version: c400d05d0aa73e21e431c16145e558d624098018 subpackages: + - Godeps/_workspace/src/github.com/ugorji/go/codec + - Godeps/_workspace/src/golang.org/x/net/context - client - pkg/pathutil - pkg/types @@ -42,72 +43,37 @@ imports: subpackages: - spew - name: github.com/docker/distribution - version: 467fc068d88aa6610691b7f1a677271a3fac4aac + version: 5bbf65499960b184fe8e0f045397375e1a6722b8 subpackages: - reference - digest - name: github.com/docker/docker version: 9837ec4da53f15f9120d53a6e1517491ba8b0261 subpackages: - - autogen - - api - - cliconfig - - daemon/network - - graph/tags - - image - - opts - - pkg/archive - - pkg/fileutils - - pkg/homedir - - pkg/httputils - - pkg/ioutils - - pkg/jsonmessage - - pkg/mflag - - pkg/nat - - pkg/parsers - - pkg/pools - - pkg/promise - - pkg/random - - pkg/stdcopy - - pkg/stringid - - pkg/symlink - - pkg/system - - pkg/tarsum - - pkg/term - - pkg/timeutils - - pkg/tlsconfig - - pkg/ulimit - - pkg/units - - pkg/urlutil - - pkg/useragent - - pkg/version - - registry - - runconfig - - utils - - volume + - namesgenerator - name: github.com/docker/engine-api version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb subpackages: - client - types - - types/container - - types/filters - - types/strslice - types/events + - types/filters - client/transport - client/transport/cancellable + - types/container - types/network - types/reference - types/registry - types/time - types/versions - types/blkiodev + - types/strslice - name: github.com/docker/go-connections version: c7838b258fbfa3fe88eecfb2a0e08ea0dbd6a646 subpackages: - - nat - sockets - tlsconfig + - nat - name: github.com/docker/go-units version: 5d2041e26a699eaca682e2ea41c8f891e1060444 - name: github.com/docker/libcompose @@ -120,61 +86,39 @@ imports: - store/consul - store/etcd - store/zookeeper -- name: github.com/docker/libtrust - version: 9cbd2a1374f46905c68a4eb3694a130610adc62a - name: github.com/donovanhide/eventsource - version: d8a3071799b98cacd30b6da92f536050ccfe6da4 + version: c3f57f280ec708df24886d9e62f2fd178d69d8e8 - name: github.com/elazarl/go-bindata-assetfs - version: d5cac425555ca5cf00694df246e04f05e6a55150 -- name: github.com/flynn/go-shlex - version: 3f9db97f856818214da2e1057f8ad84803971cff + version: 57eb5e1fc594ad4b0b1dbea7b286d299e0cb43c2 - name: github.com/gambol99/go-marathon version: ade11d1dc2884ee1f387078fc28509559b6235d1 - name: github.com/go-check/check - version: 11d3bc7aa68e238947792f30573146a3231fc0f1 -- name: github.com/golang/glog - version: fca8c8854093a154ff1eb580aae10276ad6b1b5f + version: 4f90aeace3a26ad7021961c297b22c42160c7b25 - name: github.com/google/go-querystring version: 9235644dd9e52eeae6fa48efd539fdc351a0af53 subpackages: - query - name: github.com/gorilla/context - version: 215affda49addc4c8ef7e2534915df2c8c35c6cd -- name: github.com/gorilla/handlers - version: 40694b40f4a928c062f56849989d3e9cd0570e5f + version: a8d44e7d8e4d532b6a27a02dd82abb31cc1b01bd - name: github.com/gorilla/mux - version: f15e0c49460fd49eebe2bcc8486b05d1bef68d3a -- name: github.com/gorilla/websocket - version: 1f512fc3f05332ba7117626cdfb4e07474e58e60 + version: 9c19ed558d5df4da88e2ade9c8940d742aef0e7e - name: github.com/hashicorp/consul - version: de080672fee9e6104572eeea89eccdca135bb918 + version: f6fef66e1bf17be4f3c9855fbec6de802ca6bd7d subpackages: - api -- name: github.com/hashicorp/hcl - version: 9a905a34e6280ce905da1a32344b25e81011197a +- name: github.com/hashicorp/go-cleanhttp + version: 875fb671b3ddc66f8e2f0acc33829c8cb989a38d +- name: github.com/hashicorp/serf + version: e4ec8cc423bbe20d26584b96efbeb9102e16d05f subpackages: - - hcl/ast - - hcl/parser - - hcl/token - - json/parser - - hcl/scanner - - hcl/strconv - - json/scanner - - json/token -- name: github.com/inconshreveable/mousetrap - version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 -- name: github.com/kr/pretty - version: add1dbc86daf0f983cd4a48ceb39deb95c729b67 -- name: github.com/kr/text - version: 7cafcd837844e784b526369c9bce262804aebc60 + - coordinate + - serf - name: github.com/libkermit/docker version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02 - name: github.com/libkermit/docker-check version: bb75a86b169c6c5d22c0ee98278124036f272d7b -- name: github.com/magiconair/properties - version: c265cfa48dda6474e208715ca93e987829f572f8 - name: github.com/mailgun/log - version: 44874009257d4d47ba9806f1b7f72a32a015e4d8 + version: 2f35a4607f1abf71f97f77f99b0de8493ef6f4ef - name: github.com/mailgun/manners version: fada45142db3f93097ca917da107aa3fad0ffcb5 - name: github.com/mailgun/multibuf @@ -187,12 +131,12 @@ imports: version: 4f1a71750d95a5a8a46c40a67ffbed8129c2f138 - name: github.com/miekg/dns version: 48ab6605c66ac797e07f615101c3e9e10e932b66 -- name: github.com/mitchellh/mapstructure - version: d2dd0262208475919e1a362f675cfc0e7c10e905 - name: github.com/moul/http2curl version: b1479103caacaa39319f75e7f57fc545287fca0d +- name: github.com/ogier/pflag + version: 45c278ab3607870051a2ea9040bb85fcb8557481 - name: github.com/opencontainers/runc - version: 2441732d6fcc0fb0a542671a4372e0c7bc99c19e + version: d2d09b9bcd0573c58d7cd94e57bd7555af0c2072 subpackages: - libcontainer/user - name: github.com/parnurzeal/gorequest @@ -202,25 +146,13 @@ imports: subpackages: - difflib - name: github.com/samuel/go-zookeeper - version: fa6674abf3f4580b946a01bf7a1ce4ba8766205b + version: 6eb1b09c6ce23f305f4c81bf748b22fbc6f3f9e9 subpackages: - zk - name: github.com/Sirupsen/logrus - version: 418b41d23a1bf978c06faea5313ba194650ac088 -- name: github.com/spf13/cast - version: ee7b3e0353166ab1f3a605294ac8cd2b77953778 -- name: github.com/spf13/cobra - version: f368244301305f414206f889b1735a54cfc8bde8 - subpackages: - - cobra -- name: github.com/spf13/jwalterweatherman - version: 33c24e77fb80341fe7130ee7c594256ff08ccc46 -- name: github.com/spf13/pflag - version: cb88ea77998c3f024757528e3305022ab50b43be -- name: github.com/spf13/viper - version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325 + version: 6d9ae300aaf85d6acd2e5424081c7fcddb21dab8 - name: github.com/streamrail/concurrent-map - version: 1ce4642e5a162df67825d273a86b87e6cc8a076b + version: 65a174a3a4188c0b7099acbc6cfa0c53628d3287 - name: github.com/stretchr/objx version: cbeaeb16a013161a98496fad62933b1d21786672 - name: github.com/stretchr/testify @@ -229,13 +161,9 @@ imports: - mock - assert - name: github.com/thoas/stats - version: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8 -- name: github.com/ugorji/go - version: ea9cd21fa0bc41ee4bdd50ac7ed8cbc7ea2ed960 - subpackages: - - codec + version: 69e3c072eec2df2df41afe6214f62eb940e4cd80 - name: github.com/unrolled/render - version: 26b4e3aac686940fe29521545afad9966ddfc80c + version: 198ad4d8b8a4612176b804ca10555b222a086b40 - name: github.com/vdemeester/docker-events version: b308d2e8d639d928c882913bcb4f85b3a84c7a07 - name: github.com/vdemeester/shakers @@ -255,8 +183,6 @@ imports: - plugin/rewrite - plugin - router -- name: github.com/wendal/errors - version: f66c77a7882b399795a8987ebf87ef64a427417e - name: github.com/xenolf/lego version: b119bc45fbd1cc71348003541aac9d3a7da63654 subpackages: @@ -266,7 +192,7 @@ imports: subpackages: - ocsp - name: golang.org/x/net - version: d9558e5c97f85372afee28cf2b6059d7d3818919 + version: 6460565bec1e8891e29ff478184c71b9e443ac36 subpackages: - context - publicsuffix @@ -276,12 +202,10 @@ imports: subpackages: - unix - windows -- name: gopkg.in/alecthomas/kingpin.v2 - version: 639879d6110b1b0409410c7b737ef0bb18325038 - name: gopkg.in/fsnotify.v1 - version: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0 + version: 30411dbcefb7a1da7e84f75530ad3abe4011b4f8 - name: gopkg.in/mgo.v2 - version: 22287bab4379e1fbf6002fb4eb769888f3fb224c + version: b6e2fa371e64216a45e61072a96d4e3859f169da subpackages: - bson - name: gopkg.in/square/go-jose.v1 @@ -289,6 +213,4 @@ imports: subpackages: - cipher - json -- name: gopkg.in/yaml.v2 - version: 7ad95dd0798a40da1ccdff6dff35fd177b5edf40 devImports: [] diff --git a/glide.yaml b/glide.yaml index 7d9d76ba8..df8f8dcf5 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,192 +1,76 @@ -package: main +package: github.com/containous/traefik import: -- package: github.com/coreos/etcd - version: 26e52d2bce9e3e11b77b68cc84bf91aebb1ef637 +- package: github.com/BurntSushi/toml +- package: github.com/BurntSushi/ty subpackages: - - client -- package: github.com/mailgun/log - version: 44874009257d4d47ba9806f1b7f72a32a015e4d8 + - fun +- package: github.com/Sirupsen/logrus +- package: github.com/cenkalti/backoff +- package: github.com/codegangsta/negroni +- package: github.com/containous/flaeg - package: github.com/containous/oxy - version: 183212964e13e7b8afe01a08b193d04300554a68 subpackages: - cbreaker + - connlimit - forward - - memmetrics - roundrobin + - stream - utils -- package: github.com/hashicorp/consul - version: de080672fee9e6104572eeea89eccdca135bb918 - subpackages: - - api -- package: github.com/samuel/go-zookeeper - version: fa6674abf3f4580b946a01bf7a1ce4ba8766205b - subpackages: - - zk -- package: github.com/docker/libtrust - version: 9cbd2a1374f46905c68a4eb3694a130610adc62a -- package: github.com/go-check/check - version: 11d3bc7aa68e238947792f30573146a3231fc0f1 -- package: golang.org/x/net - version: d9558e5c97f85372afee28cf2b6059d7d3818919 - subpackages: - - context -- package: github.com/gorilla/handlers - version: 40694b40f4a928c062f56849989d3e9cd0570e5f -- package: github.com/docker/libkv - version: 7283ef27ed32fe267388510a91709b307bb9942c -- package: github.com/alecthomas/template - version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0 -- package: github.com/vdemeester/shakers - version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3 -- package: github.com/alecthomas/units - version: 6b4e7dc5e3143b85ea77909c72caf89416fc2915 -- package: github.com/gambol99/go-marathon - version: ade11d1dc2884ee1f387078fc28509559b6235d1 -- package: github.com/vulcand/predicate - version: cb0bff91a7ab7cf7571e661ff883fc997bc554a3 -- package: github.com/thoas/stats - version: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8 -- package: github.com/Sirupsen/logrus - version: 418b41d23a1bf978c06faea5313ba194650ac088 -- package: github.com/unrolled/render - version: 26b4e3aac686940fe29521545afad9966ddfc80c -- package: github.com/flynn/go-shlex - version: 3f9db97f856818214da2e1057f8ad84803971cff -- package: github.com/boltdb/bolt - version: 51f99c862475898df9773747d3accd05a7ca33c1 -- package: gopkg.in/mgo.v2 - version: 22287bab4379e1fbf6002fb4eb769888f3fb224c - subpackages: - - bson -- package: github.com/docker/docker - version: 9837ec4da53f15f9120d53a6e1517491ba8b0261 - subpackages: - - autogen - - api - - cliconfig - - daemon/network - - graph/tags - - image - - opts - - pkg/archive - - pkg/fileutils - - pkg/homedir - - pkg/httputils - - pkg/ioutils - - pkg/jsonmessage - - pkg/mflag - - pkg/nat - - pkg/parsers - - pkg/pools - - pkg/promise - - pkg/random - - pkg/stdcopy - - pkg/stringid - - pkg/symlink - - pkg/system - - pkg/tarsum - - pkg/term - - pkg/timeutils - - pkg/tlsconfig - - pkg/ulimit - - pkg/units - - pkg/urlutil - - pkg/useragent - - pkg/version - - registry - - runconfig - - utils - - volume -- package: github.com/mailgun/timetools - version: fd192d755b00c968d312d23f521eb0cdc6f66bd0 -- package: github.com/codegangsta/negroni - version: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b -- package: gopkg.in/yaml.v2 - version: 7ad95dd0798a40da1ccdff6dff35fd177b5edf -- package: github.com/opencontainers/runc - version: 2441732d6fcc0fb0a542671a4372e0c7bc99c19e - subpackages: - - libcontainer/user -- package: github.com/gorilla/mux - version: f15e0c49460fd49eebe2bcc8486b05d1bef68d3a -- package: github.com/BurntSushi/ty - version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74 -- package: github.com/elazarl/go-bindata-assetfs - version: d5cac425555ca5cf00694df246e04f05e6a55150 -- package: github.com/BurntSushi/toml - version: bbd5bb678321a0d6e58f1099321dfa73391c1b6f -- package: gopkg.in/alecthomas/kingpin.v2 - version: 639879d6110b1b0409410c7b737ef0bb18325038 -- package: github.com/cenkalti/backoff - version: 4dc77674aceaabba2c7e3da25d4c823edfb73f99 -- package: gopkg.in/fsnotify.v1 - version: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0 -- package: github.com/mailgun/manners - version: fada45142db3f93097ca917da107aa3fad0ffcb5 -- package: github.com/gorilla/context - version: 215affda49addc4c8ef7e2534915df2c8c35c6cd -- package: github.com/codahale/hdrhistogram - version: 954f16e8b9ef0e5d5189456aa4c1202758e04f17 -- package: github.com/gorilla/websocket -- package: github.com/donovanhide/eventsource - version: d8a3071799b98cacd30b6da92f536050ccfe6da4 -- package: github.com/golang/glog - version: fca8c8854093a154ff1eb580aae10276ad6b1b5f -- package: github.com/spf13/cast - version: ee7b3e0353166ab1f3a605294ac8cd2b77953778 -- package: github.com/mitchellh/mapstructure -- package: github.com/spf13/jwalterweatherman -- package: github.com/spf13/pflag -- package: github.com/wendal/errors -- package: github.com/hashicorp/hcl -- package: github.com/kr/pretty -- package: github.com/magiconair/properties -- package: github.com/kr/text -- package: github.com/spf13/viper - version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325 -- package: github.com/spf13/cobra - subpackages: - - cobra -- package: github.com/google/go-querystring - subpackages: - - query -- package: github.com/vulcand/vulcand - subpackages: - - plugin/rewrite -- package: github.com/stretchr/testify - subpackages: - - mock -- package: github.com/xenolf/lego -- package: github.com/libkermit/docker-check - version: bb75a86b169c6c5d22c0ee98278124036f272d7b -- package: github.com/libkermit/docker - version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02 -- package: github.com/docker/libcompose - version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873 -- package: github.com/docker/distribution - version: 467fc068d88aa6610691b7f1a677271a3fac4aac - subpackages: - - reference +- package: github.com/containous/staert - package: github.com/docker/engine-api version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb subpackages: - client - types - - types/container + - types/events - types/filters - - types/strslice -- package: github.com/vdemeester/docker-events - package: github.com/docker/go-connections subpackages: - - nat - sockets - tlsconfig -- package: github.com/docker/go-units -- package: github.com/mailgun/multibuf -- package: github.com/streamrail/concurrent-map +- package: github.com/docker/libkv + subpackages: + - store + - store/boltdb + - store/consul + - store/etcd + - store/zookeeper +- package: github.com/elazarl/go-bindata-assetfs +- package: github.com/gambol99/go-marathon + version: ade11d1dc2884ee1f387078fc28509559b6235d1 +- package: github.com/gorilla/mux +- package: github.com/hashicorp/consul + subpackages: + - api +- package: github.com/mailgun/manners - package: github.com/parnurzeal/gorequest +- package: github.com/streamrail/concurrent-map +- package: github.com/stretchr/testify + subpackages: + - mock +- package: github.com/thoas/stats +- package: github.com/unrolled/render +- package: github.com/vdemeester/docker-events +- package: github.com/vulcand/vulcand + subpackages: + - plugin/rewrite +- package: github.com/xenolf/lego + subpackages: + - acme +- package: golang.org/x/net + subpackages: + - context +- package: gopkg.in/fsnotify.v1 +- package: github.com/libkermit/docker-check + version: bb75a86b169c6c5d22c0ee98278124036f272d7b +- package: github.com/libkermit/docker + version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02 +- package: github.com/docker/docker + version: 9837ec4da53f15f9120d53a6e1517491ba8b0261 + subpackages: + - namesgenerator +- package: github.com/go-check/check +- package: github.com/docker/libcompose + version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873 - package: github.com/mattn/go-shellwords -- package: github.com/moul/http2curl -- package: github.com/containous/flaeg -- package: github.com/containous/staert -- package: github.com/ogier/pflag +- package: github.com/vdemeester/shakers From ab138e7df12f4c632bc587e2e192f8d5f9d0bbe9 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 24 May 2016 17:29:56 +0200 Subject: [PATCH 24/48] update to new version go-bindata-assetfs --- web.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web.go b/web.go index bf77685a4..f402edfec 100644 --- a/web.go +++ b/web.go @@ -93,7 +93,7 @@ func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessag systemRouter.Methods("GET").Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) { http.Redirect(response, request, "/dashboard/", 302) }) - systemRouter.Methods("GET").PathPrefix("/dashboard/").Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: autogen.Asset, AssetDir: autogen.AssetDir, Prefix: "static"}))) + systemRouter.Methods("GET").PathPrefix("/dashboard/").Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: autogen.Asset, AssetInfo: autogen.AssetInfo, AssetDir: autogen.AssetDir, Prefix: "static"}))) // expvars if provider.server.globalConfiguration.Debug { From 6752b495369763aee445f22e63c786e447456dbd Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 24 May 2016 17:31:50 +0200 Subject: [PATCH 25/48] rm useless StrucTag --- flaeg_test.go | 91 -------------------------------------- provider/boltdb.go | 2 +- provider/consul.go | 2 +- provider/consul_catalog.go | 10 ++--- provider/docker.go | 8 ++-- provider/etcd.go | 2 +- provider/file.go | 2 +- provider/kubernetes.go | 2 +- provider/kv.go | 12 ++--- traefik.go | 2 +- 10 files changed, 21 insertions(+), 112 deletions(-) delete mode 100644 flaeg_test.go diff --git a/flaeg_test.go b/flaeg_test.go deleted file mode 100644 index bbf36c42d..000000000 --- a/flaeg_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package main - -import ( - "github.com/cocap10/flaeg" - "reflect" - "testing" - "time" -) - -func TestLoad(t *testing.T) { - var configuration GlobalConfiguration - defaultConfiguration := NewGlobalConfiguration() - args := []string{ - // "-h", - "--docker", - "--file", - "--web", - "--marathon", - "--consul", - "--consulcatalog", - "--etcd", - "--zookeeper", - "--boltdb", - } - if err := flaeg.Load(&configuration, defaultConfiguration, args); err != nil { - t.Fatalf("Error: %s", err) - } - // fmt.Printf("result : \n%+v\n", configuration) - if !reflect.DeepEqual(configuration, *defaultConfiguration) { - t.Fatalf("\nexpected\t: %+v\ngot\t\t\t: %+v", *defaultConfiguration, configuration) - } -} - -func TestLoadWithParsers(t *testing.T) { - var configuration GlobalConfiguration - defaultConfiguration := NewGlobalConfiguration() - args := []string{ - // "-h", - "--docker", - // "--file", - "--web.address=:8888", - "--marathon", - "--consul", - "--consulcatalog", - "--etcd.tls.insecureskipverify", - "--zookeeper", - "--boltdb", - "--accesslogsfile=log2/access.log", - "--entrypoints=Name:http Address::8000 Redirect.EntryPoint:https", - "--entrypoints=Name:https Address::8443 Redirect.EntryPoint:http", - "--defaultentrypoints=https", - "--defaultentrypoints=ssh", - "--providersthrottleduration=4s", - } - parsers := map[reflect.Type]flaeg.Parser{} - var defaultEntryPointsParser DefaultEntryPoints - parsers[reflect.TypeOf(DefaultEntryPoints{})] = &defaultEntryPointsParser - entryPointsParser := EntryPoints{} - parsers[reflect.TypeOf(EntryPoints{})] = &entryPointsParser - - if err := flaeg.LoadWithParsers(&configuration, defaultConfiguration, args, parsers); err != nil { - t.Fatalf("Error: %s", err) - } - // fmt.Printf("result : \n%+v\n", configuration) - - //Check - check := *defaultConfiguration - check.File = nil - check.Web.Address = ":8888" - check.AccessLogsFile = "log2/access.log" - check.Etcd.TLS.InsecureSkipVerify = true - check.EntryPoints = make(map[string]*EntryPoint) - check.EntryPoints["http"] = &EntryPoint{ - Address: ":8000", - Redirect: &Redirect{ - EntryPoint: "https", - }, - } - check.EntryPoints["https"] = &EntryPoint{ - Address: ":8443", - Redirect: &Redirect{ - EntryPoint: "http", - }, - } - check.DefaultEntryPoints = []string{"https", "ssh"} - check.ProvidersThrottleDuration = time.Duration(4 * time.Second) - - if !reflect.DeepEqual(&configuration, &check) { - t.Fatalf("\nexpected\t: %+v\ngot\t\t\t: %+v", check, configuration) - } -} diff --git a/provider/boltdb.go b/provider/boltdb.go index 966a4b8da..4c2a33844 100644 --- a/provider/boltdb.go +++ b/provider/boltdb.go @@ -9,7 +9,7 @@ import ( // BoltDb holds configurations of the BoltDb provider. type BoltDb struct { - Kv `mapstructure:",squash" description:"go through"` + Kv } // Provide allows the provider to provide configurations to traefik diff --git a/provider/consul.go b/provider/consul.go index d6d81f3e7..d94dc7e03 100644 --- a/provider/consul.go +++ b/provider/consul.go @@ -9,7 +9,7 @@ import ( // Consul holds configurations of the Consul provider. type Consul struct { - Kv `mapstructure:",squash" description:"go through"` + Kv } // Provide allows the provider to provide configurations to traefik diff --git a/provider/consul_catalog.go b/provider/consul_catalog.go index c17cfe0a9..2aecc4622 100644 --- a/provider/consul_catalog.go +++ b/provider/consul_catalog.go @@ -23,11 +23,11 @@ const ( // ConsulCatalog holds configurations of the Consul catalog provider. type ConsulCatalog struct { - BaseProvider `mapstructure:",squash"` - Endpoint string `description:"Consul server endpoint"` - Domain string `description:"Default domain used"` - client *api.Client - Prefix string + BaseProvider + Endpoint string `description:"Consul server endpoint"` + Domain string `description:"Default domain used"` + client *api.Client + Prefix string } type serviceUpdate struct { diff --git a/provider/docker.go b/provider/docker.go index 55b73f8b6..75f5c4ef8 100644 --- a/provider/docker.go +++ b/provider/docker.go @@ -29,10 +29,10 @@ const DockerAPIVersion string = "1.21" // Docker holds configurations of the Docker provider. type Docker struct { - BaseProvider `mapstructure:",squash"` - Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"` - Domain string `description:"Default domain used"` - TLS *DockerTLS `description:"Enable Docker TLS support"` + BaseProvider + Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"` + Domain string `description:"Default domain used"` + TLS *DockerTLS `description:"Enable Docker TLS support"` } // DockerTLS holds TLS specific configurations diff --git a/provider/etcd.go b/provider/etcd.go index 3344245e4..a7fd7ae6a 100644 --- a/provider/etcd.go +++ b/provider/etcd.go @@ -9,7 +9,7 @@ import ( // Etcd holds configurations of the Etcd provider. type Etcd struct { - Kv `mapstructure:",squash" description:"go through"` + Kv } // Provide allows the provider to provide configurations to traefik diff --git a/provider/file.go b/provider/file.go index c5597ec19..1b463593a 100644 --- a/provider/file.go +++ b/provider/file.go @@ -14,7 +14,7 @@ import ( // File holds configurations of the File provider. type File struct { - BaseProvider `mapstructure:",squash" description:"go through"` + BaseProvider } // Provide allows the provider to provide configurations to traefik diff --git a/provider/kubernetes.go b/provider/kubernetes.go index 3dd3e9e4e..cab4217ea 100644 --- a/provider/kubernetes.go +++ b/provider/kubernetes.go @@ -49,7 +49,7 @@ func (ns *Namespaces) SetValue(val interface{}) { // Kubernetes holds configurations of the Kubernetes provider. type Kubernetes struct { - BaseProvider `mapstructure:",squash"` + BaseProvider Endpoint string `description:"Kubernetes server endpoint"` DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"` Namespaces Namespaces `description:"Kubernetes namespaces"` diff --git a/provider/kv.go b/provider/kv.go index 852799dcb..713416781 100644 --- a/provider/kv.go +++ b/provider/kv.go @@ -22,12 +22,12 @@ import ( // Kv holds common configurations of key-value providers. type Kv struct { - BaseProvider `mapstructure:",squash" description:"go through"` - Endpoint string `description:"Comma sepparated server endpoints"` - Prefix string `description:"Prefix used for KV store"` - TLS *KvTLS `description:"Enable TLS support"` - storeType store.Backend - kvclient store.Store + BaseProvider + Endpoint string `description:"Comma sepparated server endpoints"` + Prefix string `description:"Prefix used for KV store"` + TLS *KvTLS `description:"Enable TLS support"` + storeType store.Backend + kvclient store.Store } // KvTLS holds TLS specific configurations diff --git a/traefik.go b/traefik.go index 636514f2d..fbc6a4f7f 100644 --- a/traefik.go +++ b/traefik.go @@ -76,7 +76,7 @@ Complete documentation is available at https://traefik.io`, traefikConfiguration.File.Filename = toml.ConfigFileUsed() } if len(traefikConfiguration.EntryPoints) == 0 { - traefikConfiguration.EntryPoints = map[string]*EntryPoint{"http": &EntryPoint{Address: ":80"}} + traefikConfiguration.EntryPoints = map[string]*EntryPoint{"http": {Address: ":80"}} traefikConfiguration.DefaultEntryPoints = []string{"http"} } if err := s.Run(); err != nil { From f64c2bc065c0e306c40748ec9aceefeb3e55a7af Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 25 May 2016 17:06:34 +0200 Subject: [PATCH 26/48] add flag on ACME add flag on Retry set Retry.MaxMem to 2 by default rm useless import rm useless structtag add custom parser on []acme.Domain type add commants + refactor --- acme/acme.go | 45 +++++++++++++++++++++++++++++++++++++++------ configuration.go | 39 +++++++++++++++++++++------------------ provider/zk.go | 2 +- traefik.go | 5 ++++- 4 files changed, 65 insertions(+), 26 deletions(-) diff --git a/acme/acme.go b/acme/acme.go index 53c1bd123..081e4d8e4 100644 --- a/acme/acme.go +++ b/acme/acme.go @@ -16,6 +16,7 @@ import ( fmtlog "log" "os" "reflect" + "strings" "sync" "time" ) @@ -161,15 +162,47 @@ func (dc *DomainsCertificate) needRenew() bool { // ACME allows to connect to lets encrypt and retrieve certs type ACME struct { - Email string - Domains []Domain - StorageFile string - OnDemand bool - CAServer string - EntryPoint string + Email string `description:"Email address used for registration"` + Domains []Domain `description:"SANs (alternative domains) to each main domain"` + StorageFile string `description:"File used for certificates storage."` + OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."` + CAServer string `description:"CA server to use."` + EntryPoint string `description:"Entrypoint to proxy acme challenge to."` storageLock sync.RWMutex } +//Domains parse []Domain +type Domains []Domain + +//Set []Domain +func (ds *Domains) Set(str string) error { + fargs := func(c rune) bool { + return c == ',' || c == ';' + } + // get function + slice := strings.FieldsFunc(str, fargs) + if len(slice) >= 2 { + return fmt.Errorf("Parse error ACME.Domain. Imposible to parse %s", str) + } + d := Domain{ + Main: slice[0], + SANs: slice[1:], + } + *ds = append(*ds, d) + return nil +} + +//Get []Domain +func (ds *Domains) Get() interface{} { return []Domain(*ds) } + +//String returns []Domain in string +func (ds *Domains) String() string { return fmt.Sprintf("%+v", *ds) } + +//SetValue sets []Domain into the parser +func (ds *Domains) SetValue(val interface{}) { + *ds = Domains(val.([]Domain)) +} + // Domain holds a domain name with SANs type Domain struct { Main string diff --git a/configuration.go b/configuration.go index daa037246..201a61fe8 100644 --- a/configuration.go +++ b/configuration.go @@ -20,17 +20,17 @@ type TraefikConfiguration struct { // 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 { - GraceTimeOut int64 `short:"g" description:"Configuration file to use (TOML)."` - Debug bool - AccessLogsFile string `description:"Access logs file"` - TraefikLogsFile string `description:"Traefik logs file"` - LogLevel string `short:"l" description:"Log level"` - EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'"` - ACME *acme.ACME - DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"` - ProvidersThrottleDuration time.Duration `description:"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."` - MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used"` - Retry *Retry + GraceTimeOut int64 `short:"g" description:"Configuration file to use (TOML)."` + Debug bool + AccessLogsFile string `description:"Access logs file"` + TraefikLogsFile string `description:"Traefik logs file"` + LogLevel string `short:"l" description:"Log level"` + EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'"` + ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"` + DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"` + ProvidersThrottleDuration time.Duration `description:"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."` + MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used"` + Retry *Retry `description:"Enable retry sending request if network error"` Docker *provider.Docker `description:"Enable Docker backend"` File *provider.File `description:"Enable File backend"` Web *WebProvider `description:"Enable Web backend"` @@ -49,7 +49,7 @@ 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 { - //TODO : + //TODO : The string returned should be formatted in such way that the func Set below could parse it. return fmt.Sprintf("%#v", dep) } @@ -86,8 +86,10 @@ 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 { - //TODO : - return "" + //TODO : The string returned should be formatted in such way that the func Set below could parse it. + //Like this --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' + //But the Set func parses entrypoint one by one only + return fmt.Sprintf("%+v", *ep) } // Set is the method to set the flag value, part of the flag.Value interface. @@ -208,12 +210,12 @@ type Certificate struct { // Retry contains request retry config type Retry struct { - Attempts int - MaxMem int64 + Attempts int `description:"Number of attempts"` + MaxMem int64 `description:"Maximum request body to be stored in memory in Mo"` } -// NewTraefikPointersConfiguration creates a TraefikConfiguration with pointers default values -func NewTraefikPointersConfiguration() *TraefikConfiguration { +// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values +func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { //default Docker var defaultDocker provider.Docker defaultDocker.Watch = true @@ -281,6 +283,7 @@ func NewTraefikPointersConfiguration() *TraefikConfiguration { Zookeeper: &defaultZookeeper, Boltdb: &defaultBoltDb, Kubernetes: &defaultKubernetes, + Retry: &Retry{MaxMem: 2}, } return &TraefikConfiguration{ GlobalConfiguration: defaultConfiguration, diff --git a/provider/zk.go b/provider/zk.go index e8164a75f..77b28100f 100644 --- a/provider/zk.go +++ b/provider/zk.go @@ -9,7 +9,7 @@ import ( // Zookepper holds configurations of the Zookepper provider. type Zookepper struct { - Kv `description:"go through"` + Kv } // Provide allows the provider to provide configurations to traefik diff --git a/traefik.go b/traefik.go index fbc6a4f7f..898cac622 100644 --- a/traefik.go +++ b/traefik.go @@ -5,6 +5,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/containous/flaeg" "github.com/containous/staert" + "github.com/containous/traefik/acme" "github.com/containous/traefik/middlewares" "github.com/containous/traefik/provider" fmtlog "log" @@ -20,7 +21,7 @@ func main() { //traefik config inits traefikConfiguration := NewTraefikConfiguration() - traefikPointersConfiguration := NewTraefikPointersConfiguration() + traefikPointersConfiguration := NewTraefikDefaultPointersConfiguration() //traefik Command init traefikCmd := &flaeg.Command{ Name: "traefik", @@ -52,6 +53,8 @@ Complete documentation is available at https://traefik.io`, f.AddParser(reflect.TypeOf(EntryPoints{}), &EntryPoints{}) f.AddParser(reflect.TypeOf(DefaultEntryPoints{}), &DefaultEntryPoints{}) f.AddParser(reflect.TypeOf(provider.Namespaces{}), &provider.Namespaces{}) + f.AddParser(reflect.TypeOf([]acme.Domain{}), &acme.Domains{}) + //add version command f.AddCommand(versionCmd) if _, err := f.Parse(traefikCmd); err != nil { From 7f6b2b80f8850cbb4cefec9b8b2a004873ab1d31 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 26 May 2016 11:09:20 +0200 Subject: [PATCH 27/48] rm useless TestNoOrInexistentConfigShouldNotFail Signed-off-by: Martin --- integration/basic_test.go | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/integration/basic_test.go b/integration/basic_test.go index 0e7220279..7e5549174 100644 --- a/integration/basic_test.go +++ b/integration/basic_test.go @@ -5,7 +5,6 @@ import ( "os/exec" "time" - "fmt" "github.com/go-check/check" "bytes" @@ -15,34 +14,6 @@ import ( // SimpleSuite type SimpleSuite struct{ BaseSuite } -func (s *SimpleSuite) TestNoOrInexistentConfigShouldNotFail(c *check.C) { - cmd := exec.Command(traefikBinary) - - var b bytes.Buffer - cmd.Stdout = &b - cmd.Stderr = &b - - cmd.Start() - time.Sleep(500 * time.Millisecond) - output := b.Bytes() - - c.Assert(string(output), checker.Not(checker.Contains), "No configuration file found") - cmd.Process.Kill() - - nonExistentFile := "non/existent/file.toml" - cmd = exec.Command(traefikBinary, "--configFile="+nonExistentFile) - - cmd.Stdout = &b - cmd.Stderr = &b - - cmd.Start() - time.Sleep(500 * time.Millisecond) - output = b.Bytes() - - c.Assert(string(output), checker.Not(checker.Contains), fmt.Sprintf("Error reading configuration file: open %s: no such file or directory", nonExistentFile)) - cmd.Process.Kill() -} - func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) { cmd := exec.Command(traefikBinary, "--configFile=fixtures/invalid_configuration.toml") From cc2735f7332eaf627e978eedd4267f91722608e9 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 26 May 2016 15:55:50 +0200 Subject: [PATCH 28/48] add Debug StructTag Signed-off-by: Martin --- configuration.go | 2 +- provider/kubernetes_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configuration.go b/configuration.go index 201a61fe8..35a7234de 100644 --- a/configuration.go +++ b/configuration.go @@ -21,7 +21,7 @@ type TraefikConfiguration struct { // It's populated from the traefik configuration file passed as an argument to the binary. type GlobalConfiguration struct { GraceTimeOut int64 `short:"g" description:"Configuration file to use (TOML)."` - Debug bool + Debug bool `short:"d" description:"Enable debug mode"` AccessLogsFile string `description:"Access logs file"` TraefikLogsFile string `description:"Traefik logs file"` LogLevel string `short:"l" description:"Log level"` diff --git a/provider/kubernetes_test.go b/provider/kubernetes_test.go index 8426b73ad..25e48b8d7 100644 --- a/provider/kubernetes_test.go +++ b/provider/kubernetes_test.go @@ -1205,7 +1205,7 @@ func TestHostlessIngress(t *testing.T) { services: services, watchChan: watchChan, } - provider := Kubernetes{disablePassHostHeaders: true} + provider := Kubernetes{DisablePassHostHeaders: true} actual, err := provider.loadIngresses(client) if err != nil { t.Fatalf("error %+v", err) From c5084fd02523d7025417dfacecda5360e0c66eb6 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 27 May 2016 10:04:56 +0200 Subject: [PATCH 29/48] update staert + glide pin version --- glide.lock | 8 ++++---- glide.yaml | 2 ++ traefik.go | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/glide.lock b/glide.lock index a88e1030c..cd8969fca 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 50e873651126f0af9204fd8cc11f10917ce56b76479310ada578f5487162e0c4 -updated: 2016-05-24T15:42:45.810673781+02:00 +hash: 660249b4d5cbcfd0cb0a2d9d39ce798ec8df7d3277c53ed4043fe2f61b29eeb9 +updated: 2016-05-27T09:59:17.855890752+02:00 imports: - name: github.com/boltdb/bolt version: dfb21201d9270c1082d5fb0f07f500311ff72f18 @@ -18,7 +18,7 @@ imports: - name: github.com/codegangsta/negroni version: ffbc66b612ee3eac2eba29aedce4c3a65e4dd0a1 - name: github.com/containous/flaeg - version: e390ea90e4ee21ff73fb1630b8c50a843a6d7674 + version: c425b9d758df1864ca838dbd433f1cf8f5097d51 - name: github.com/containous/oxy version: 183212964e13e7b8afe01a08b193d04300554a68 subpackages: @@ -29,7 +29,7 @@ imports: - stream - utils - name: github.com/containous/staert - version: f0da5ea8404f0d3999d72ba8f8736d3aa4b8c8f1 + version: ff272631ecfc9c22490b651fea0f08d364d46518 - name: github.com/coreos/etcd version: c400d05d0aa73e21e431c16145e558d624098018 subpackages: diff --git a/glide.yaml b/glide.yaml index df8f8dcf5..d64ed3c4c 100644 --- a/glide.yaml +++ b/glide.yaml @@ -8,6 +8,7 @@ import: - package: github.com/cenkalti/backoff - package: github.com/codegangsta/negroni - package: github.com/containous/flaeg + version: c425b9d758df1864ca838dbd433f1cf8f5097d51 - package: github.com/containous/oxy subpackages: - cbreaker @@ -17,6 +18,7 @@ import: - stream - utils - package: github.com/containous/staert + version: ff272631ecfc9c22490b651fea0f08d364d46518 - package: github.com/docker/engine-api version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb subpackages: diff --git a/traefik.go b/traefik.go index 898cac622..a313c6466 100644 --- a/traefik.go +++ b/traefik.go @@ -70,7 +70,7 @@ Complete documentation is available at https://traefik.io`, //add sources to staert s.AddSource(toml) s.AddSource(f) - if _, err := s.GetConfig(); err != nil { + if _, err := s.LoadConfig(); err != nil { fmtlog.Println(err) } if traefikConfiguration.File != nil && len(traefikConfiguration.File.Filename) == 0 { From 4776fa136112e714747391b70a296dff2e913831 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 27 May 2016 11:13:34 +0200 Subject: [PATCH 30/48] add parsers tests Signed-off-by: Martin --- acme/acme.go | 9 ++++--- acme/acme_test.go | 61 +++++++++++++++++++++++++++++++++++++++++++++++ configuration.go | 7 +----- 3 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 acme/acme_test.go diff --git a/acme/acme.go b/acme/acme.go index 081e4d8e4..99e340ae4 100644 --- a/acme/acme.go +++ b/acme/acme.go @@ -163,7 +163,7 @@ func (dc *DomainsCertificate) needRenew() bool { // ACME allows to connect to lets encrypt and retrieve certs type ACME struct { Email string `description:"Email address used for registration"` - Domains []Domain `description:"SANs (alternative domains) to each main domain"` + Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"` StorageFile string `description:"File used for certificates storage."` OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."` CAServer string `description:"CA server to use."` @@ -181,12 +181,15 @@ func (ds *Domains) Set(str string) error { } // get function slice := strings.FieldsFunc(str, fargs) - if len(slice) >= 2 { + if len(slice) < 1 { return fmt.Errorf("Parse error ACME.Domain. Imposible to parse %s", str) } d := Domain{ Main: slice[0], - SANs: slice[1:], + SANs: []string{}, + } + if len(slice) > 1 { + d.SANs = slice[1:] } *ds = append(*ds, d) return nil diff --git a/acme/acme_test.go b/acme/acme_test.go new file mode 100644 index 000000000..302c5866e --- /dev/null +++ b/acme/acme_test.go @@ -0,0 +1,61 @@ +package acme + +import ( + "reflect" + "testing" +) + +func TestDomainsSet(t *testing.T) { + checkMap := map[string]Domains{ + "": {}, + "foo.com": {Domain{Main: "foo.com", SANs: []string{}}}, + "foo.com,bar.net": {Domain{Main: "foo.com", SANs: []string{"bar.net"}}}, + "foo.com,bar1.net,bar2.net,bar3.net": {Domain{Main: "foo.com", SANs: []string{"bar1.net", "bar2.net", "bar3.net"}}}, + } + for in, check := range checkMap { + ds := Domains{} + ds.Set(in) + if !reflect.DeepEqual(check, ds) { + t.Errorf("Expected %+v\nGo %+v", check, ds) + } + } +} + +func TestDomainsSetAppend(t *testing.T) { + inSlice := []string{ + "", + "foo1.com", + "foo2.com,bar.net", + "foo3.com,bar1.net,bar2.net,bar3.net", + } + checkSlice := []Domains{ + {}, + { + Domain{ + Main: "foo1.com", + SANs: []string{}}}, + { + Domain{ + Main: "foo1.com", + SANs: []string{}}, + Domain{ + Main: "foo2.com", + SANs: []string{"bar.net"}}}, + { + Domain{ + Main: "foo1.com", + SANs: []string{}}, + Domain{ + Main: "foo2.com", + SANs: []string{"bar.net"}}, + Domain{Main: "foo3.com", + SANs: []string{"bar1.net", "bar2.net", "bar3.net"}}}, + } + ds := Domains{} + for i, in := range inSlice { + ds.Set(in) + if !reflect.DeepEqual(checkSlice[i], ds) { + t.Errorf("Expected %s %+v\nGo %+v", in, checkSlice[i], ds) + } + } +} diff --git a/configuration.go b/configuration.go index 35a7234de..72703e177 100644 --- a/configuration.go +++ b/configuration.go @@ -49,8 +49,7 @@ 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 { - //TODO : The string returned should be formatted in such way that the func Set below could parse it. - return fmt.Sprintf("%#v", dep) + return strings.Join(*dep, ",") } // Set is the method to set the flag value, part of the flag.Value interface. @@ -86,9 +85,6 @@ 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 { - //TODO : The string returned should be formatted in such way that the func Set below could parse it. - //Like this --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' - //But the Set func parses entrypoint one by one only return fmt.Sprintf("%+v", *ep) } @@ -176,7 +172,6 @@ type Certificates []Certificate // The String method's output will be used in diagnostics. func (certs *Certificates) String() string { if len(*certs) == 0 { - //TODO : return "" } return (*certs)[0].CertFile + "," + (*certs)[0].KeyFile From 53ae64e578d73b740e9d85aacd94d2267971264b Mon Sep 17 00:00:00 2001 From: Erin Dachtler Date: Sat, 28 May 2016 15:16:57 -0700 Subject: [PATCH 31/48] Filter containers with no exposed ports unless they have a traefik.port label --- provider/docker.go | 6 +++--- provider/docker_test.go | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/provider/docker.go b/provider/docker.go index 7169f20e1..3c7fe8f38 100644 --- a/provider/docker.go +++ b/provider/docker.go @@ -196,11 +196,11 @@ func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.Conta } func containerFilter(container dockertypes.ContainerJSON) bool { - if len(container.NetworkSettings.Ports) == 0 { - log.Debugf("Filtering container without port %s", container.Name) + _, err := strconv.Atoi(container.Config.Labels["traefik.port"]) + if len(container.NetworkSettings.Ports) == 0 && err != nil { + log.Debugf("Filtering container without port and no traefik.port label %s", container.Name) return false } - _, err := strconv.Atoi(container.Config.Labels["traefik.port"]) if len(container.NetworkSettings.Ports) > 1 && err != nil { log.Debugf("Filtering container with more than 1 port and no traefik.port label %s", container.Name) return false diff --git a/provider/docker_test.go b/provider/docker_test.go index 9fd52ce43..417a120d4 100644 --- a/provider/docker_test.go +++ b/provider/docker_test.go @@ -250,6 +250,20 @@ func TestDockerGetPort(t *testing.T) { // }, // expected: "80", // }, + { + container: docker.ContainerJSON{ + ContainerJSONBase: &docker.ContainerJSONBase{ + Name: "test", + }, + Config: &container.Config{ + Labels: map[string]string{ + "traefik.port": "8080", + }, + }, + NetworkSettings: &docker.NetworkSettings{}, + }, + expected: "8080", + }, { container: docker.ContainerJSON{ ContainerJSONBase: &docker.ContainerJSONBase{ @@ -263,7 +277,7 @@ func TestDockerGetPort(t *testing.T) { NetworkSettings: &docker.NetworkSettings{ NetworkSettingsBase: docker.NetworkSettingsBase{ Ports: nat.PortMap{ - "80/tcp": {}, // TODO: Change so there is no exposed port + "80/tcp": {}, }, }, }, From d8ad30f38ab843b35098f0ef9f0b42909f7e09eb Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 30 May 2016 11:37:12 +0200 Subject: [PATCH 32/48] log info about TOML configuration file using +glide update flaeg & staert +fix README.md +fix configFile flag description Signed-off-by: Martin --- README.md | 2 +- configuration.go | 2 +- glide.lock | 8 ++++---- glide.yaml | 4 ++-- traefik.go | 29 ++++++++++++++++++++--------- 5 files changed, 28 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 684f164fc..e16bb35a9 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ You can access to a simple HTML frontend of Træfik. - The simple way: grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml): ```shell -./traefik -c traefik.toml +./traefik --configFile=traefik.toml ``` - Use the tiny Docker image: diff --git a/configuration.go b/configuration.go index 72703e177..9949d3cf3 100644 --- a/configuration.go +++ b/configuration.go @@ -14,7 +14,7 @@ import ( // TraefikConfiguration holds GlobalConfiguration and other stuff type TraefikConfiguration struct { GlobalConfiguration - ConfigFile string `short:"c" description:"Timeout in seconds. Duration to give active requests a chance to finish during hot-reloads"` + ConfigFile string `short:"c" description:"Configuration file to use (TOML)."` } // GlobalConfiguration holds global configuration (with providers, etc.). diff --git a/glide.lock b/glide.lock index cd8969fca..cdc1a522d 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 660249b4d5cbcfd0cb0a2d9d39ce798ec8df7d3277c53ed4043fe2f61b29eeb9 -updated: 2016-05-27T09:59:17.855890752+02:00 +hash: 1d42530971835a5a1e593e8efc54f28f2714cb8d1dfcb333e3b9ba92ef1917cc +updated: 2016-05-30T16:53:17.058435869+02:00 imports: - name: github.com/boltdb/bolt version: dfb21201d9270c1082d5fb0f07f500311ff72f18 @@ -18,7 +18,7 @@ imports: - name: github.com/codegangsta/negroni version: ffbc66b612ee3eac2eba29aedce4c3a65e4dd0a1 - name: github.com/containous/flaeg - version: c425b9d758df1864ca838dbd433f1cf8f5097d51 + version: a208db24c0e580be76efed167736fe3204c7c954 - name: github.com/containous/oxy version: 183212964e13e7b8afe01a08b193d04300554a68 subpackages: @@ -29,7 +29,7 @@ imports: - stream - utils - name: github.com/containous/staert - version: ff272631ecfc9c22490b651fea0f08d364d46518 + version: 9f815ef829b66de3519fea6c0b8d4708eb9472d4 - name: github.com/coreos/etcd version: c400d05d0aa73e21e431c16145e558d624098018 subpackages: diff --git a/glide.yaml b/glide.yaml index d64ed3c4c..38d0ae099 100644 --- a/glide.yaml +++ b/glide.yaml @@ -8,7 +8,7 @@ import: - package: github.com/cenkalti/backoff - package: github.com/codegangsta/negroni - package: github.com/containous/flaeg - version: c425b9d758df1864ca838dbd433f1cf8f5097d51 + version: a208db24c0e580be76efed167736fe3204c7c954 - package: github.com/containous/oxy subpackages: - cbreaker @@ -18,7 +18,7 @@ import: - stream - utils - package: github.com/containous/staert - version: ff272631ecfc9c22490b651fea0f08d364d46518 + version: 9f815ef829b66de3519fea6c0b8d4708eb9472d4 - package: github.com/docker/engine-api version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb subpackages: diff --git a/traefik.go b/traefik.go index a313c6466..5fef4bc08 100644 --- a/traefik.go +++ b/traefik.go @@ -73,15 +73,9 @@ Complete documentation is available at https://traefik.io`, if _, err := s.LoadConfig(); err != nil { fmtlog.Println(err) } - if traefikConfiguration.File != nil && len(traefikConfiguration.File.Filename) == 0 { - // no filename, setting to global config file - log.Debugf("ConfigFileUsed %s", toml.ConfigFileUsed()) - traefikConfiguration.File.Filename = toml.ConfigFileUsed() - } - if len(traefikConfiguration.EntryPoints) == 0 { - traefikConfiguration.EntryPoints = map[string]*EntryPoint{"http": {Address: ":80"}} - traefikConfiguration.DefaultEntryPoints = []string{"http"} - } + + traefikConfiguration.ConfigFile = toml.ConfigFileUsed() + if err := s.Run(); err != nil { fmtlog.Println(err) os.Exit(-1) @@ -107,6 +101,23 @@ func run(traefikConfiguration *TraefikConfiguration) { } log.SetLevel(level) + if len(traefikConfiguration.ConfigFile) != 0 { + log.Infof("Using TOML configuration file %s", traefikConfiguration.ConfigFile) + } + if traefikConfiguration.File != nil && len(traefikConfiguration.File.Filename) == 0 { + // no filename, setting to global config file + if len(traefikConfiguration.ConfigFile) != 0 { + traefikConfiguration.File.Filename = traefikConfiguration.ConfigFile + } else { + log.Errorln("Error using file configuration backend, no filename defined") + } + } + + if len(traefikConfiguration.EntryPoints) == 0 { + traefikConfiguration.EntryPoints = map[string]*EntryPoint{"http": {Address: ":80"}} + traefikConfiguration.DefaultEntryPoints = []string{"http"} + } + if len(globalConfiguration.TraefikLogsFile) > 0 { fi, err := os.OpenFile(globalConfiguration.TraefikLogsFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) defer func() { From db6c85d3d705730576ac813446a94fb3a5de0374 Mon Sep 17 00:00:00 2001 From: Emile Vauge Date: Fri, 27 May 2016 14:12:29 +0200 Subject: [PATCH 33/48] Prepare release candidate Signed-off-by: Emile Vauge --- .travis.yml | 4 +--- script/deploy.sh | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 69353d4c6..3e91bd945 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,9 @@ branches: - except: - - /^v\d\.\d\.\d.*$/ env: global: - secure: btt4r13t09gQlHb6gYrvGC2yGCMMHfnp1Mz1RQedc4Mpf/FfT8aE6xmK2a2i9CCvskjrP0t/BFaS4yxIURjnFRn+ugQIEa0pLspB9UJArW/vgOSpIWM9/OQ/fg8z5XuMxN6Md4DL1/iLypMNSageA1x0TRdt89+D1N1dALpg5XRCXLFbC84TLi0gjlFuib9ibPKzEhLT+anCRJ6iZMzeupDSoaCVbAtJMoDvXw4+4AcRZ1+k4MybBLyCib5boaEOt4pTT88mz4Kk0YaMwPVJyg9Qv36VqyUcPS09Yd95LuyVQ4+tZt8Y1ccbIzULsK+sLM3hLCzxlmlpN3dQBlZJiiRtQde0mgGAKyC0P0A1XjuDTywcsa5edB+fTk1Dsewz9xZ9V0NmMz8t+UNZnaSsAPga9i86jULbXUUwMVSzVRc+Xgx02liB/8qI1xYC9FM6ilStt7rn7mF0k3KbiWhcptgeXjO6Lah9FjEKd5w4MXsdUSTi/86rQaLo+kj+XdaTrXCTulKHyRyQEUj+8V1w0oVz7pcGjePHd7y5oU9ByifVQy6sytuFBfRZvugM5bKHo+i0pcWvixrZS42DrzwxZJsspANOvqSe5ifVbvOkfUppQdCBIwptxV5N1b49XPKU3W/w34QJ8xGmKp3TFA7WwVCztriFHjPgiRpB3EG99Bg= - REPO: $TRAVIS_REPO_SLUG - - VERSION: v1.0.0-beta.$TRAVIS_BUILD_NUMBER + - VERSION: $TRAVIS_TAG matrix: - DOCKER_VERSION=1.9.1 - DOCKER_VERSION=1.10.1 diff --git a/script/deploy.sh b/script/deploy.sh index 97f0f2abd..32415679a 100755 --- a/script/deploy.sh +++ b/script/deploy.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -if ([ "$TRAVIS_BRANCH" = "master" ] || [ ! -z "$TRAVIS_TAG" ]) && [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$DOCKER_VERSION" = "1.10.1" ]; then +if [ -z "$TRAVIS_TAG" ] && [ "$DOCKER_VERSION" = "1.10.1" ]; then echo "Deploying..." else echo "Skipping deploy" From 0e3c2ef10f8500203eb2196606aed2143eee0eb3 Mon Sep 17 00:00:00 2001 From: Emile Vauge Date: Mon, 30 May 2016 17:57:57 +0200 Subject: [PATCH 34/48] Fix log config file Signed-off-by: Emile Vauge --- traefik.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/traefik.go b/traefik.go index 5fef4bc08..b05b558eb 100644 --- a/traefik.go +++ b/traefik.go @@ -101,9 +101,6 @@ func run(traefikConfiguration *TraefikConfiguration) { } log.SetLevel(level) - if len(traefikConfiguration.ConfigFile) != 0 { - log.Infof("Using TOML configuration file %s", traefikConfiguration.ConfigFile) - } if traefikConfiguration.File != nil && len(traefikConfiguration.File.Filename) == 0 { // no filename, setting to global config file if len(traefikConfiguration.ConfigFile) != 0 { @@ -136,6 +133,9 @@ func run(traefikConfiguration *TraefikConfiguration) { } jsonConf, _ := json.Marshal(globalConfiguration) log.Infof("Traefik version %s built on %s", Version, BuildDate) + if len(traefikConfiguration.ConfigFile) != 0 { + log.Infof("Using TOML configuration file %s", traefikConfiguration.ConfigFile) + } log.Debugf("Global configuration loaded %s", string(jsonConf)) server := NewServer(globalConfiguration) server.Start() From 01a4002169ffd844066c60fda4738b5c0654f48c Mon Sep 17 00:00:00 2001 From: Emile Vauge Date: Mon, 30 May 2016 23:12:26 +0200 Subject: [PATCH 35/48] Fix travis tag check Signed-off-by: Emile Vauge --- script/deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/deploy.sh b/script/deploy.sh index 32415679a..bf2d1bb7f 100755 --- a/script/deploy.sh +++ b/script/deploy.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -if [ -z "$TRAVIS_TAG" ] && [ "$DOCKER_VERSION" = "1.10.1" ]; then +if [ -n "$TRAVIS_TAG" ] && [ "$DOCKER_VERSION" = "1.10.1" ]; then echo "Deploying..." else echo "Skipping deploy" From a6c360eedaccc5e7a3779a8f31e54a9f8f5c64a5 Mon Sep 17 00:00:00 2001 From: Emile Vauge Date: Mon, 30 May 2016 23:59:27 +0200 Subject: [PATCH 36/48] Fix travis hangs on docker version Signed-off-by: Emile Vauge --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 3e91bd945..74fcb4bcc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ install: - sudo curl https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION} -o /usr/bin/docker - sudo chmod +x /usr/bin/docker - sudo service docker start +- sleep 5 - docker version - pip install --user mkdocs - pip install --user pymdown-extensions From 46c2184de4214cd10ce81ce2f6f3ad7755f463cb Mon Sep 17 00:00:00 2001 From: Samuel BERTHE Date: Mon, 30 May 2016 11:10:51 +0200 Subject: [PATCH 37/48] doc(tooling): Doc about selecting some tests with argument '-check.f' of gochecker library --- .github/CONTRIBUTING.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index ecdac0319..8772fa828 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -77,6 +77,23 @@ ok github.com/containous/traefik 0.005s coverage: 4.1% of statements Test success ``` +For development purpose, you can specifiy which tests to run by using: +``` +# Run every tests in the MyTest suite +TESTFLAGS="-check.f MyTestSuite" make test-integration + +# Run the test "MyTest" in the MyTest suite +TESTFLAGS="-check.f MyTestSuite.MyTest" make test-integration + +# Run every tests starting with "My", in the MyTest suite +TESTFLAGS="-check.f MyTestSuite.My" make test-integration + +# Run every tests ending with "Test", in the MyTest suite +TESTFLAGS="-check.f MyTestSuite.*Test" make test-integration +``` + +More: https://labix.org/gocheck + ### Documentation The [documentation site](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/) From df7e1cf078e221356e2a353417c2c191bcca8431 Mon Sep 17 00:00:00 2001 From: Erin Dachtler Date: Tue, 31 May 2016 22:11:17 -0700 Subject: [PATCH 38/48] Squashed commit of the following: commit 468cdf5c74b8df80fe6cc093feda84d124d47460 Author: Erin Dachtler Date: Mon May 30 17:21:50 2016 -0700 Documentation update commit bcbe622141fc333579177e056b49d418997c511d Author: Erin Dachtler Date: Sat May 28 15:32:34 2016 -0700 Whoops, forgot to fmt commit 1ad5f1052541372722adc372069da094b422c793 Author: Erin Dachtler Date: Sat May 28 14:56:04 2016 -0700 Added getIPAddress helper for docker template, and tests --- docs/toml.md | 6 ++-- provider/docker.go | 17 +++++++++ provider/docker_test.go | 76 +++++++++++++++++++++++++++++++++++++++++ templates/docker.tmpl | 2 +- 4 files changed, 97 insertions(+), 4 deletions(-) diff --git a/docs/toml.md b/docs/toml.md index 0d71cd995..c024ac2af 100644 --- a/docs/toml.md +++ b/docs/toml.md @@ -539,7 +539,8 @@ Labels can be used on containers to override default behaviour: - `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`). - `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend. - `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. -* `traefik.domain=traefik.localhost`: override the default domain +- `traefik.domain=traefik.localhost`: override the default domain +- `traefik.docker.network`: Set the docker network to use for connections to this container ## Marathon backend @@ -644,7 +645,7 @@ Træfɪk can be configured to use Kubernetes Ingress as a backend configuration: # and KUBERNETES_SERVICE_PORT_HTTPS as endpoint # Secure token will be found in /var/run/secrets/kubernetes.io/serviceaccount/token # and SSL CA cert in /var/run/secrets/kubernetes.io/serviceaccount/ca.crt -# +# # Optional # # endpoint = "http://localhost:8080" @@ -973,4 +974,3 @@ Once the `/traefik/alias` key is updated, the new `/traefik_configurations/2` co | `/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. - diff --git a/provider/docker.go b/provider/docker.go index 453c33476..bec410170 100644 --- a/provider/docker.go +++ b/provider/docker.go @@ -160,6 +160,7 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.ContainerJSON) *types.Configuration { var DockerFuncMap = template.FuncMap{ "getBackend": provider.getBackend, + "getIPAddress": provider.getIPAddress, "getPort": provider.getPort, "getWeight": provider.getWeight, "getDomain": provider.getDomain, @@ -244,6 +245,22 @@ func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string { return normalize(container.Name) } +func (provider *Docker) getIPAddress(container dockertypes.ContainerJSON) string { + if label, err := getLabel(container, "traefik.docker.network"); err == nil && label != "" { + networks := container.NetworkSettings.Networks + if networks != nil { + network := networks[label] + if network != nil { + return network.IPAddress + } + } + } + for _, network := range container.NetworkSettings.Networks { + return network.IPAddress + } + return "" +} + func (provider *Docker) getPort(container dockertypes.ContainerJSON) string { if label, err := getLabel(container, "traefik.port"); err == nil { return label diff --git a/provider/docker_test.go b/provider/docker_test.go index 417a120d4..3605d280a 100644 --- a/provider/docker_test.go +++ b/provider/docker_test.go @@ -203,6 +203,82 @@ func TestDockerGetBackend(t *testing.T) { } } +func TestDockerGetIPAddress(t *testing.T) { // TODO + provider := &Docker{} + + containers := []struct { + container docker.ContainerJSON + expected string + }{ + { + container: docker.ContainerJSON{ + ContainerJSONBase: &docker.ContainerJSONBase{ + Name: "bar", + }, + Config: &container.Config{}, + NetworkSettings: &docker.NetworkSettings{ + Networks: map[string]*network.EndpointSettings{ + "testnet": { + IPAddress: "10.11.12.13", + }, + }, + }, + }, + expected: "10.11.12.13", + }, + { + container: docker.ContainerJSON{ + ContainerJSONBase: &docker.ContainerJSONBase{ + Name: "bar", + }, + Config: &container.Config{ + Labels: map[string]string{ + "traefik.docker.network": "testnet", + }, + }, + NetworkSettings: &docker.NetworkSettings{ + Networks: map[string]*network.EndpointSettings{ + "nottestnet": { + IPAddress: "10.11.12.13", + }, + }, + }, + }, + expected: "10.11.12.13", + }, + { + container: docker.ContainerJSON{ + ContainerJSONBase: &docker.ContainerJSONBase{ + Name: "bar", + }, + Config: &container.Config{ + Labels: map[string]string{ + "traefik.docker.network": "testnet2", + }, + }, + NetworkSettings: &docker.NetworkSettings{ + Networks: map[string]*network.EndpointSettings{ + "testnet1": { + IPAddress: "10.11.12.13", + }, + "testnet2": { + IPAddress: "10.11.12.14", + }, + }, + }, + }, + expected: "10.11.12.14", + }, + } + + for _, e := range containers { + actual := provider.getIPAddress(e.container) + if actual != e.expected { + t.Fatalf("expected %q, got %q", e.expected, actual) + } + } +} + func TestDockerGetPort(t *testing.T) { provider := &Docker{} diff --git a/templates/docker.tmpl b/templates/docker.tmpl index f43bef8b4..08e1f54b5 100644 --- a/templates/docker.tmpl +++ b/templates/docker.tmpl @@ -1,6 +1,6 @@ [backends]{{range .Containers}} [backends.backend-{{getBackend .}}.servers.server-{{.Name | replace "/" "" | replace "." "-"}}] - url = "{{getProtocol .}}://{{range $i := .NetworkSettings.Networks}}{{if $i}}{{.IPAddress}}{{end}}{{end}}:{{getPort .}}" + url = "{{getProtocol .}}://{{getIPAddress .}}:{{getPort .}}" weight = {{getWeight .}} {{end}} From ac087921d8afd89dc4f6eebdeeabb98272c72f82 Mon Sep 17 00:00:00 2001 From: Samuel BERTHE Date: Mon, 30 May 2016 15:05:58 +0200 Subject: [PATCH 39/48] feat(constraints): Implementation of constraint filtering (cmd + toml + matching functions), implementation proposal with consul --- configuration.go | 2 + glide.lock | 5 +- glide.yaml | 1 + provider/boltdb.go | 4 +- provider/consul.go | 4 +- provider/consul_catalog.go | 43 ++++++++++--- provider/docker.go | 3 +- provider/etcd.go | 4 +- provider/file.go | 2 +- provider/kubernetes.go | 3 +- provider/kv.go | 2 +- provider/marathon.go | 3 +- provider/provider.go | 30 +++++++++- provider/zk.go | 4 +- server.go | 2 +- types/types.go | 120 +++++++++++++++++++++++++++++++++++++ web.go | 2 +- 17 files changed, 209 insertions(+), 25 deletions(-) diff --git a/configuration.go b/configuration.go index 9949d3cf3..ee996b693 100644 --- a/configuration.go +++ b/configuration.go @@ -26,6 +26,7 @@ type GlobalConfiguration struct { TraefikLogsFile string `description:"Traefik logs file"` LogLevel string `short:"l" description:"Log level"` EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'"` + Constraints []*types.Constraint `description:"Filter services by constraint, matching with service tags."` ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"` DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"` ProvidersThrottleDuration time.Duration `description:"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."` @@ -294,6 +295,7 @@ func NewTraefikConfiguration() *TraefikConfiguration { TraefikLogsFile: "", LogLevel: "ERROR", EntryPoints: map[string]*EntryPoint{}, + Constraints: []*Constraint, DefaultEntryPoints: []string{}, ProvidersThrottleDuration: time.Duration(2 * time.Second), MaxIdleConnsPerHost: 200, diff --git a/glide.lock b/glide.lock index cdc1a522d..1f95df545 100644 --- a/glide.lock +++ b/glide.lock @@ -213,4 +213,7 @@ imports: subpackages: - cipher - json -devImports: [] +- name: gopkg.in/yaml.v2 + version: 7ad95dd0798a40da1ccdff6dff35fd177b5edf40 +- name: github.com/ryanuber/go-glob + version: 572520ed46dbddaed19ea3d9541bdd0494163693 diff --git a/glide.yaml b/glide.yaml index 38d0ae099..4636007ae 100644 --- a/glide.yaml +++ b/glide.yaml @@ -76,3 +76,4 @@ import: version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873 - package: github.com/mattn/go-shellwords - package: github.com/vdemeester/shakers +- package: github.com/ryanuber/go-glob diff --git a/provider/boltdb.go b/provider/boltdb.go index 4c2a33844..50b1fa36a 100644 --- a/provider/boltdb.go +++ b/provider/boltdb.go @@ -14,8 +14,8 @@ type BoltDb struct { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { +func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error { provider.storeType = store.BOLTDB boltdb.Register() - return provider.provide(configurationChan, pool) + return provider.provide(configurationChan, pool, constraints) } diff --git a/provider/consul.go b/provider/consul.go index d94dc7e03..f74490371 100644 --- a/provider/consul.go +++ b/provider/consul.go @@ -14,8 +14,8 @@ type Consul struct { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { +func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error { provider.storeType = store.CONSUL consul.Register() - return provider.provide(configurationChan, pool) + return provider.provide(configurationChan, pool, constraints) } diff --git a/provider/consul_catalog.go b/provider/consul_catalog.go index 2aecc4622..ec82ecb39 100644 --- a/provider/consul_catalog.go +++ b/provider/consul_catalog.go @@ -90,13 +90,25 @@ func (provider *ConsulCatalog) healthyNodes(service string) (catalogUpdate, erro set := map[string]bool{} tags := []string{} + nodes := []*api.ServiceEntry{} for _, node := range data { - for _, tag := range node.Service.Tags { - if _, ok := set[tag]; ok == false { - set[tag] = true - tags = append(tags, tag) + constraintTags := provider.getContraintTags(node.Service.Tags) + if ok, failingConstraint, err := provider.MatchConstraints(constraintTags); err != nil { + return catalogUpdate{}, err + } else if ok == true { + nodes = append(nodes, node) + // merge tags of every nodes in a single slice + // only if node match constraint + for _, tag := range node.Service.Tags { + if _, ok := set[tag]; ok == false { + set[tag] = true + tags = append(tags, tag) + } } + } else if ok == false && failingConstraint != nil { + log.Debugf("Service %v pruned by '%v' constraint", service, failingConstraint.String()) } + } return catalogUpdate{ @@ -104,7 +116,7 @@ func (provider *ConsulCatalog) healthyNodes(service string) (catalogUpdate, erro ServiceName: service, Attributes: tags, }, - Nodes: data, + Nodes: nodes, }, nil } @@ -157,6 +169,19 @@ func (provider *ConsulCatalog) getAttribute(name string, tags []string, defaultV return defaultValue } +func (provider *ConsulCatalog) getContraintTags(tags []string) []string { + var list []string + + for _, tag := range tags { + if strings.Index(strings.ToLower(tag), DefaultConsulCatalogTagPrefix+".tags=") == 0 { + splitedTags := strings.Split(tag[len(DefaultConsulCatalogTagPrefix+".tags="):], ",") + list = append(list, splitedTags...) + } + } + + return list +} + func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration { var FuncMap = template.FuncMap{ "getBackend": provider.getBackend, @@ -212,7 +237,10 @@ func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpd if err != nil { return nil, err } - nodes = append(nodes, healthy) + // healthy.Nodes can be empty if constraints do not match, without throwing error + if healthy.Service != nil && len(healthy.Nodes) > 0 { + nodes = append(nodes, healthy) + } } } return nodes, nil @@ -248,7 +276,7 @@ func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessag // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { +func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error { config := api.DefaultConfig() config.Address = provider.Endpoint client, err := api.NewClient(config) @@ -256,6 +284,7 @@ func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMess return err } provider.client = client + provider.Constraints = append(provider.Constraints, constraints...) pool.Go(func(stop chan bool) { notify := func(err error, time time.Duration) { diff --git a/provider/docker.go b/provider/docker.go index 453c33476..2565d1a84 100644 --- a/provider/docker.go +++ b/provider/docker.go @@ -79,7 +79,8 @@ func (provider *Docker) createClient() (client.APIClient, error) { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { +func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error { + provider.Constraints = append(provider.Constraints, constraints...) // TODO register this routine in pool, and watch for stop channel safe.Go(func() { operation := func() error { diff --git a/provider/etcd.go b/provider/etcd.go index a7fd7ae6a..35f493d03 100644 --- a/provider/etcd.go +++ b/provider/etcd.go @@ -14,8 +14,8 @@ type Etcd struct { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { +func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error { provider.storeType = store.ETCD etcd.Register() - return provider.provide(configurationChan, pool) + return provider.provide(configurationChan, pool, constraints) } diff --git a/provider/file.go b/provider/file.go index 1b463593a..b1038df77 100644 --- a/provider/file.go +++ b/provider/file.go @@ -19,7 +19,7 @@ type File struct { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { +func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []*types.Constraint) error { watcher, err := fsnotify.NewWatcher() if err != nil { log.Error("Error creating file watcher", err) diff --git a/provider/kubernetes.go b/provider/kubernetes.go index cab4217ea..0681ff24b 100644 --- a/provider/kubernetes.go +++ b/provider/kubernetes.go @@ -81,12 +81,13 @@ func (provider *Kubernetes) createClient() (k8s.Client, error) { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { +func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error { k8sClient, err := provider.createClient() if err != nil { return err } backOff := backoff.NewExponentialBackOff() + provider.Constraints = append(provider.Constraints, constraints...) pool.Go(func(stop chan bool) { operation := func() error { diff --git a/provider/kv.go b/provider/kv.go index 713416781..3791dd3b4 100644 --- a/provider/kv.go +++ b/provider/kv.go @@ -73,7 +73,7 @@ func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix return nil } -func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { +func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error { storeConfig := &store.Config{ ConnectionTimeout: 30 * time.Second, Bucket: "traefik", diff --git a/provider/marathon.go b/provider/marathon.go index 91b4a2e8d..cf5e96622 100644 --- a/provider/marathon.go +++ b/provider/marathon.go @@ -42,7 +42,8 @@ type lightMarathonClient interface { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { +func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error { + provider.Constraints = append(provider.Constraints, constraints...) operation := func() error { config := marathon.NewDefaultConfig() config.URL = provider.Endpoint diff --git a/provider/provider.go b/provider/provider.go index eddf5a76c..5355ae3d8 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -5,25 +5,46 @@ import ( "io/ioutil" "strings" "text/template" + "unicode" "github.com/BurntSushi/toml" "github.com/containous/traefik/autogen" "github.com/containous/traefik/safe" "github.com/containous/traefik/types" - "unicode" ) // Provider defines methods of a provider. type Provider interface { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. - Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error + Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error } // BaseProvider should be inherited by providers type BaseProvider struct { Watch bool `description:"Watch provider"` Filename string `description:"Override default configuration template. For advanced users :)"` + Constraints []*types.Constraint `description:"Filter services by constraint, matching with Traefik tags."` +} + +// MatchConstraints must match with EVERY single contraint +// returns first constraint that do not match or nil +// returns errors for future use (regex) +func (p *BaseProvider) MatchConstraints(tags []string) (bool, *types.Constraint, error) { + // if there is no tags and no contraints, filtering is disabled + if len(tags) == 0 && len(p.Constraints) == 0 { + return true, nil, nil + } + + for _, constraint := range p.Constraints { + if ok := constraint.MatchConstraintWithAtLeastOneTag(tags); xor(ok == true, constraint.MustMatch == true) { + return false, constraint, nil + } + } + + // If no constraint or every constraints matching + return true, nil, nil +>>>>>>> e844462... feat(constraints): Implementation of constraints (cmd + toml + matching functions), implementation proposal with consul } func (p *BaseProvider) getConfiguration(defaultTemplateFile string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) { @@ -77,3 +98,8 @@ func normalize(name string) string { // get function return strings.Join(strings.FieldsFunc(name, fargs), "-") } + +// golang does not support ^ operator +func xor(cond1 bool, cond2 bool) bool { + return cond1 != cond2 +} diff --git a/provider/zk.go b/provider/zk.go index 77b28100f..7d5562ee5 100644 --- a/provider/zk.go +++ b/provider/zk.go @@ -14,8 +14,8 @@ type Zookepper struct { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { +func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error { provider.storeType = store.ZK zookeeper.Register() - return provider.provide(configurationChan, pool) + return provider.provide(configurationChan, pool, constraints) } diff --git a/server.go b/server.go index c1bb55cd0..14694efbe 100644 --- a/server.go +++ b/server.go @@ -248,7 +248,7 @@ func (server *Server) startProviders() { log.Infof("Starting provider %v %s", reflect.TypeOf(provider), jsonConf) currentProvider := provider safe.Go(func() { - err := currentProvider.Provide(server.configurationChan, &server.routinesPool) + err := currentProvider.Provide(server.configurationChan, &server.routinesPool, server.globalConfiguration.Constraints) if err != nil { log.Errorf("Error starting provider %s", err) } diff --git a/types/types.go b/types/types.go index eeedcb73b..5eaa1e51e 100644 --- a/types/types.go +++ b/types/types.go @@ -2,6 +2,10 @@ package types import ( "errors" + "fmt" + "github.com/mitchellh/mapstructure" + "github.com/ryanuber/go-glob" + "reflect" "strings" ) @@ -93,3 +97,119 @@ type ConfigMessage struct { ProviderName string Configuration *Configuration } + +// Constraint hold a parsed constraint expresssion +type Constraint struct { + Key string + // MustMatch is true if operator is "==" or false if operator is "!=" + MustMatch bool + Regex string +} + +func NewConstraint(exp string) (*Constraint, error) { + sep := "" + constraint := &Constraint{} + + if strings.Contains(exp, "==") { + sep = "==" + constraint.MustMatch = true + } else if strings.Contains(exp, "!=") { + sep = "!=" + constraint.MustMatch = false + } else { + return nil, errors.New("Constraint expression missing valid operator: '==' or '!='") + } + + kv := strings.SplitN(exp, sep, 2) + if len(kv) == 2 { + // At the moment, it only supports tags + if kv[0] != "tag" { + return nil, errors.New("Constraint must be tag-based. Syntax: tag==us-*") + } + + constraint.Key = kv[0] + constraint.Regex = kv[1] + return constraint, nil + } + + return nil, errors.New("Incorrect constraint expression: " + exp) +} + +func (c *Constraint) String() string { + if c.MustMatch { + return c.Key + "==" + c.Regex + } + return c.Key + "!=" + c.Regex +} + +func (c *Constraint) MatchConstraintWithAtLeastOneTag(tags []string) bool { + for _, tag := range tags { + if glob.Glob(c.Regex, tag) { + return true + } + } + return false +} + +// StringToConstraintHookFunc returns a DecodeHookFunc that converts strings to Constraint. +// This hook is triggered during the configuration file unmarshal-ing +func StringToConstraintHookFunc() mapstructure.DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(&Constraint{}) { + return data, nil + } + + if constraint, err := NewConstraint(data.(string)); err != nil { + return data, err + } else { + return constraint, nil + } + } +} + +type Constraints struct { + value *[]*Constraint + changed bool +} + +// Command line +func (cs *Constraints) Set(value string) error { + exps := strings.Split(value, ",") + if len(exps) == 0 { + return errors.New("Bad Constraint format: " + value) + } + for _, exp := range exps { + if constraint, err := NewConstraint(exp); err != nil { + return err + } else { + *cs.value = append(*cs.value, constraint) + } + } + return nil +} + +func (c *Constraints) Type() string { + return "constraints" +} + +func (c *Constraints) String() string { + return fmt.Sprintln("%v", *c.value) +} + +// NewConstraintSliceValue make an alias of []*Constraint to Constraints for the command line +// Viper does not supprt SliceVar value types +// Constraints.Set called by viper will fill the []*Constraint slice +func NewConstraintSliceValue(p *[]*Constraint) *Constraints { + cs := new(Constraints) + cs.value = p + if p == nil { + *cs.value = []*Constraint{} + } + return cs +} diff --git a/web.go b/web.go index f402edfec..7cafdcc0d 100644 --- a/web.go +++ b/web.go @@ -46,7 +46,7 @@ func goroutines() interface{} { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { +func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []*types.Constraint) error { systemRouter := mux.NewRouter() // health route From cd2100ed84f40b824d44e0435ebbb2d25c78558b Mon Sep 17 00:00:00 2001 From: Samuel BERTHE Date: Fri, 20 May 2016 16:43:56 +0200 Subject: [PATCH 40/48] doc(constraints): Added in ConsulCatalog backend + new 'Constraint' section --- docs/toml.md | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/docs/toml.md b/docs/toml.md index 0d71cd995..7355cf6ba 100644 --- a/docs/toml.md +++ b/docs/toml.md @@ -195,6 +195,51 @@ entryPoint = "https" main = "local4.com" ``` +## Constraints + +In a micro-service architecture, with a central service discovery, setting constraints limits Træfɪk scope to a smaller number of routes. + +Træfɪk filters services according to service attributes/tags set in your configuration backends. + +Supported backends: + +- Consul Catalog + +Supported filters: + +- ```tag``` + +``` +# Constraints definition + +# +# Optional +# + +# Simple matching constraint +# constraints = ["tag==api"] + +# Simple mismatching constraint +# constraints = ["tag!=api"] + +# Globbing +# constraints = ["tag==us-*"] + +# Backend-specific constraint +# [consulCatalog] +# endpoint = 127.0.0.1:8500 +# constraints = ["tag==api"] + +# Multiple constraints +# - "tag==" must match with at least one tag +# - "tag!=" must match with none of tags +# constraints = ["tag!=us-*", "tag!=asia-*"] +# [consulCatalog] +# endpoint = 127.0.0.1:8500 +# constraints = ["tag==api", "tag!=v*-beta"] +``` + + # Configuration backends ## File backend @@ -741,6 +786,13 @@ domain = "consul.localhost" # Optional # prefix = "traefik" + +# Constraint on Consul catalog tags +# +# Optional +# +constraints = ["tag==api", "tag==he*ld"] +# Matching with containers having this tag: "traefik.tags=api,helloworld" ``` This backend will create routes matching on hostname based on the service name From f46accc74d09f43651bbc8b2ad1058720e35fd7c Mon Sep 17 00:00:00 2001 From: Samuel BERTHE Date: Fri, 20 May 2016 17:17:38 +0200 Subject: [PATCH 41/48] test(constraint): unit tests + integration tests + make validate --- integration/constraint_test.go | 211 ++++++++++++++++++ integration/integration_test.go | 1 + integration/resources/compose/constraints.yml | 17 ++ provider/consul_catalog.go | 31 ++- provider/provider.go | 18 +- provider/provider_test.go | 99 ++++++++ types/types.go | 25 ++- 7 files changed, 362 insertions(+), 40 deletions(-) create mode 100644 integration/constraint_test.go create mode 100644 integration/resources/compose/constraints.yml diff --git a/integration/constraint_test.go b/integration/constraint_test.go new file mode 100644 index 000000000..50e194d2e --- /dev/null +++ b/integration/constraint_test.go @@ -0,0 +1,211 @@ +package main + +import ( + //"io/ioutil" + //"fmt" + "net/http" + "os/exec" + "time" + + "github.com/go-check/check" + "github.com/hashicorp/consul/api" + + checker "github.com/vdemeester/shakers" +) + +// Constraint test suite +type ConstraintSuite struct { + BaseSuite + consulIP string + consulClient *api.Client +} + +func (s *ConstraintSuite) SetUpSuite(c *check.C) { + + s.createComposeProject(c, "constraints") + s.composeProject.Start(c) + + consul := s.composeProject.Container(c, "consul") + + s.consulIP = consul.NetworkSettings.IPAddress + config := api.DefaultConfig() + config.Address = s.consulIP + ":8500" + consulClient, err := api.NewClient(config) + if err != nil { + c.Fatalf("Error creating consul client") + } + s.consulClient = consulClient + + // Wait for consul to elect itself leader + time.Sleep(2000 * time.Millisecond) +} + +func (s *ConstraintSuite) registerService(name string, address string, port int, tags []string) error { + catalog := s.consulClient.Catalog() + _, err := catalog.Register( + &api.CatalogRegistration{ + Node: address, + Address: address, + Service: &api.AgentService{ + ID: name, + Service: name, + Address: address, + Port: port, + Tags: tags, + }, + }, + &api.WriteOptions{}, + ) + return err +} + +func (s *ConstraintSuite) deregisterService(name string, address string) error { + catalog := s.consulClient.Catalog() + _, err := catalog.Deregister( + &api.CatalogDeregistration{ + Node: address, + Address: address, + ServiceID: name, + }, + &api.WriteOptions{}, + ) + return err +} + +func (s *ConstraintSuite) TestMatchConstraintGlobal(c *check.C) { + cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--constraints=tag==api") + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + nginx := s.composeProject.Container(c, "nginx") + + err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api"}) + c.Assert(err, checker.IsNil, check.Commentf("Error registering service")) + defer s.deregisterService("test", nginx.NetworkSettings.IPAddress) + + time.Sleep(5000 * time.Millisecond) + client := &http.Client{} + req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil) + c.Assert(err, checker.IsNil) + req.Host = "test.consul.localhost" + resp, err := client.Do(req) + + c.Assert(err, checker.IsNil) + c.Assert(resp.StatusCode, checker.Equals, 200) +} + +func (s *ConstraintSuite) TestDoesNotMatchConstraintGlobal(c *check.C) { + cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--constraints=tag==api") + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + nginx := s.composeProject.Container(c, "nginx") + + err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{}) + c.Assert(err, checker.IsNil, check.Commentf("Error registering service")) + defer s.deregisterService("test", nginx.NetworkSettings.IPAddress) + + time.Sleep(5000 * time.Millisecond) + client := &http.Client{} + req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil) + c.Assert(err, checker.IsNil) + req.Host = "test.consul.localhost" + resp, err := client.Do(req) + + c.Assert(err, checker.IsNil) + c.Assert(resp.StatusCode, checker.Equals, 404) +} + +func (s *ConstraintSuite) TestMatchConstraintProvider(c *check.C) { + cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api") + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + nginx := s.composeProject.Container(c, "nginx") + + err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api"}) + c.Assert(err, checker.IsNil, check.Commentf("Error registering service")) + defer s.deregisterService("test", nginx.NetworkSettings.IPAddress) + + time.Sleep(5000 * time.Millisecond) + client := &http.Client{} + req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil) + c.Assert(err, checker.IsNil) + req.Host = "test.consul.localhost" + resp, err := client.Do(req) + + c.Assert(err, checker.IsNil) + c.Assert(resp.StatusCode, checker.Equals, 200) +} + +func (s *ConstraintSuite) TestDoesNotMatchConstraintProvider(c *check.C) { + cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api") + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + nginx := s.composeProject.Container(c, "nginx") + + err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{}) + c.Assert(err, checker.IsNil, check.Commentf("Error registering service")) + defer s.deregisterService("test", nginx.NetworkSettings.IPAddress) + + time.Sleep(5000 * time.Millisecond) + client := &http.Client{} + req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil) + c.Assert(err, checker.IsNil) + req.Host = "test.consul.localhost" + resp, err := client.Do(req) + + c.Assert(err, checker.IsNil) + c.Assert(resp.StatusCode, checker.Equals, 404) +} + +func (s *ConstraintSuite) TestMatchMultipleConstraint(c *check.C) { + cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api", "--constraints=tag!=us-*") + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + nginx := s.composeProject.Container(c, "nginx") + + err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api", "traefik.tags=eu-1"}) + c.Assert(err, checker.IsNil, check.Commentf("Error registering service")) + defer s.deregisterService("test", nginx.NetworkSettings.IPAddress) + + time.Sleep(5000 * time.Millisecond) + client := &http.Client{} + req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil) + c.Assert(err, checker.IsNil) + req.Host = "test.consul.localhost" + resp, err := client.Do(req) + + c.Assert(err, checker.IsNil) + c.Assert(resp.StatusCode, checker.Equals, 200) +} + +func (s *ConstraintSuite) TestDoesNotMatchMultipleConstraint(c *check.C) { + cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api", "--constraints=tag!=us-*") + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + nginx := s.composeProject.Container(c, "nginx") + + err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api", "traefik.tags=us-1"}) + c.Assert(err, checker.IsNil, check.Commentf("Error registering service")) + defer s.deregisterService("test", nginx.NetworkSettings.IPAddress) + + time.Sleep(5000 * time.Millisecond) + client := &http.Client{} + req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil) + c.Assert(err, checker.IsNil) + req.Host = "test.consul.localhost" + resp, err := client.Do(req) + + c.Assert(err, checker.IsNil) + c.Assert(resp.StatusCode, checker.Equals, 404) +} diff --git a/integration/integration_test.go b/integration/integration_test.go index c31832a19..9d7bff2bb 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -31,6 +31,7 @@ func init() { check.Suite(&ConsulCatalogSuite{}) check.Suite(&EtcdSuite{}) check.Suite(&MarathonSuite{}) + check.Suite(&ConstraintSuite{}) } var traefikBinary = "../dist/traefik" diff --git a/integration/resources/compose/constraints.yml b/integration/resources/compose/constraints.yml new file mode 100644 index 000000000..9a2688904 --- /dev/null +++ b/integration/resources/compose/constraints.yml @@ -0,0 +1,17 @@ +consul: + image: progrium/consul + command: -server -bootstrap -log-level debug -ui-dir /ui + ports: + - "8400:8400" + - "8500:8500" + - "8600:53/udp" + expose: + - "8300" + - "8301" + - "8301/udp" + - "8302" + - "8302/udp" +nginx: + image: nginx + ports: + - "8881:80" diff --git a/provider/consul_catalog.go b/provider/consul_catalog.go index ec82ecb39..0b0d2294b 100644 --- a/provider/consul_catalog.go +++ b/provider/consul_catalog.go @@ -7,6 +7,7 @@ import ( "text/template" "time" + "github.com/BurntSushi/ty/fun" log "github.com/Sirupsen/logrus" "github.com/cenkalti/backoff" "github.com/containous/traefik/safe" @@ -88,28 +89,22 @@ func (provider *ConsulCatalog) healthyNodes(service string) (catalogUpdate, erro return catalogUpdate{}, err } - set := map[string]bool{} - tags := []string{} - nodes := []*api.ServiceEntry{} - for _, node := range data { + nodes := fun.Filter(func(node *api.ServiceEntry) bool { constraintTags := provider.getContraintTags(node.Service.Tags) - if ok, failingConstraint, err := provider.MatchConstraints(constraintTags); err != nil { - return catalogUpdate{}, err - } else if ok == true { - nodes = append(nodes, node) - // merge tags of every nodes in a single slice - // only if node match constraint - for _, tag := range node.Service.Tags { - if _, ok := set[tag]; ok == false { - set[tag] = true - tags = append(tags, tag) - } - } - } else if ok == false && failingConstraint != nil { + ok, failingConstraint := provider.MatchConstraints(constraintTags) + if ok == false && failingConstraint != nil { log.Debugf("Service %v pruned by '%v' constraint", service, failingConstraint.String()) } + return ok + }, data).([]*api.ServiceEntry) - } + //Merge tags of nodes matching constraints, in a single slice. + tags := fun.Foldl(func(node *api.ServiceEntry, set []string) []string { + return fun.Keys(fun.Union( + fun.Set(set), + fun.Set(node.Service.Tags), + ).(map[string]bool)).([]string) + }, []string{}, nodes).([]string) return catalogUpdate{ Service: &serviceUpdate{ diff --git a/provider/provider.go b/provider/provider.go index 5355ae3d8..43d662401 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -29,22 +29,21 @@ type BaseProvider struct { // MatchConstraints must match with EVERY single contraint // returns first constraint that do not match or nil -// returns errors for future use (regex) -func (p *BaseProvider) MatchConstraints(tags []string) (bool, *types.Constraint, error) { +func (p *BaseProvider) MatchConstraints(tags []string) (bool, *types.Constraint) { // if there is no tags and no contraints, filtering is disabled if len(tags) == 0 && len(p.Constraints) == 0 { - return true, nil, nil + return true, nil } for _, constraint := range p.Constraints { - if ok := constraint.MatchConstraintWithAtLeastOneTag(tags); xor(ok == true, constraint.MustMatch == true) { - return false, constraint, nil + // xor: if ok and constraint.MustMatch are equal, then no tag is currently matching with the constraint + if ok := constraint.MatchConstraintWithAtLeastOneTag(tags); ok != constraint.MustMatch { + return false, constraint } } // If no constraint or every constraints matching - return true, nil, nil ->>>>>>> e844462... feat(constraints): Implementation of constraints (cmd + toml + matching functions), implementation proposal with consul + return true, nil } func (p *BaseProvider) getConfiguration(defaultTemplateFile string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) { @@ -98,8 +97,3 @@ func normalize(name string) string { // get function return strings.Join(strings.FieldsFunc(name, fargs), "-") } - -// golang does not support ^ operator -func xor(cond1 bool, cond2 bool) bool { - return cond1 != cond2 -} diff --git a/provider/provider_test.go b/provider/provider_test.go index b76f5e6bd..824d3ced0 100644 --- a/provider/provider_test.go +++ b/provider/provider_test.go @@ -6,6 +6,8 @@ import ( "strings" "testing" "text/template" + + "github.com/containous/traefik/types" ) type myProvider struct { @@ -206,3 +208,100 @@ func TestGetConfigurationReturnsCorrectMaxConnConfiguration(t *testing.T) { t.Fatalf("Configuration did not parse MaxConn.ExtractorFunc properly") } } + +func TestMatchingConstraints(t *testing.T) { + cases := []struct { + constraints []*types.Constraint + tags []string + expected bool + }{ + // simple test: must match + { + constraints: []*types.Constraint{ + { + Key: "tag", + MustMatch: true, + Regex: "us-east-1", + }, + }, + tags: []string{ + "us-east-1", + }, + expected: true, + }, + // simple test: must match but does not match + { + constraints: []*types.Constraint{ + { + Key: "tag", + MustMatch: true, + Regex: "us-east-1", + }, + }, + tags: []string{ + "us-east-2", + }, + expected: false, + }, + // simple test: must not match + { + constraints: []*types.Constraint{ + { + Key: "tag", + MustMatch: false, + Regex: "us-east-1", + }, + }, + tags: []string{ + "us-east-1", + }, + expected: false, + }, + // complex test: globbing + { + constraints: []*types.Constraint{ + { + Key: "tag", + MustMatch: true, + Regex: "us-east-*", + }, + }, + tags: []string{ + "us-east-1", + }, + expected: true, + }, + // complex test: multiple constraints + { + constraints: []*types.Constraint{ + { + Key: "tag", + MustMatch: true, + Regex: "us-east-*", + }, + { + Key: "tag", + MustMatch: false, + Regex: "api", + }, + }, + tags: []string{ + "api", + "us-east-1", + }, + expected: false, + }, + } + + for i, c := range cases { + provider := myProvider{ + BaseProvider{ + Constraints: c.constraints, + }, + } + actual, _ := provider.MatchConstraints(c.tags) + if actual != c.expected { + t.Fatalf("test #%v: expected %q, got %q, for %q", i, c.expected, actual, c.constraints) + } + } +} diff --git a/types/types.go b/types/types.go index 5eaa1e51e..2ce5ceb97 100644 --- a/types/types.go +++ b/types/types.go @@ -106,6 +106,7 @@ type Constraint struct { Regex string } +// NewConstraint receive a string and return a *Constraint, after checking syntax and parsing the constraint expression func NewConstraint(exp string) (*Constraint, error) { sep := "" constraint := &Constraint{} @@ -142,6 +143,7 @@ func (c *Constraint) String() string { return c.Key + "!=" + c.Regex } +// MatchConstraintWithAtLeastOneTag tests a constraint for one single service func (c *Constraint) MatchConstraintWithAtLeastOneTag(tags []string) bool { for _, tag := range tags { if glob.Glob(c.Regex, tag) { @@ -165,41 +167,44 @@ func StringToConstraintHookFunc() mapstructure.DecodeHookFunc { return data, nil } - if constraint, err := NewConstraint(data.(string)); err != nil { + constraint, err := NewConstraint(data.(string)) + if err != nil { return data, err - } else { - return constraint, nil } + return constraint, nil } } +// Constraints own a pointer on globalConfiguration.Constraints and supports a Set() method (not possible on a slice) +// interface: type Constraints struct { value *[]*Constraint changed bool } -// Command line +// Set receive a cli argument and add it to globalConfiguration func (cs *Constraints) Set(value string) error { exps := strings.Split(value, ",") if len(exps) == 0 { return errors.New("Bad Constraint format: " + value) } for _, exp := range exps { - if constraint, err := NewConstraint(exp); err != nil { + constraint, err := NewConstraint(exp) + if err != nil { return err - } else { - *cs.value = append(*cs.value, constraint) } + *cs.value = append(*cs.value, constraint) } return nil } -func (c *Constraints) Type() string { +// Type exports the Constraints type as a string +func (cs *Constraints) Type() string { return "constraints" } -func (c *Constraints) String() string { - return fmt.Sprintln("%v", *c.value) +func (cs *Constraints) String() string { + return fmt.Sprintln("%v", *cs.value) } // NewConstraintSliceValue make an alias of []*Constraint to Constraints for the command line From 1de5434e1ad5a2a8f04e04ab8a7b464894d276ec Mon Sep 17 00:00:00 2001 From: Samuel BERTHE Date: Tue, 31 May 2016 09:54:42 +0200 Subject: [PATCH 42/48] refacto(constraints): Migration to Flaeg cli library --- configuration.go | 46 +++++++++++++++++++++- glide.lock | 3 +- integration/constraint_test.go | 2 - provider/boltdb.go | 2 +- provider/consul.go | 2 +- provider/consul_catalog.go | 2 +- provider/docker.go | 2 +- provider/etcd.go | 2 +- provider/file.go | 2 +- provider/kubernetes.go | 2 +- provider/kv.go | 2 +- provider/marathon.go | 2 +- provider/provider.go | 10 ++--- provider/provider_test.go | 12 +++--- provider/zk.go | 2 +- server.go | 2 +- traefik.go | 3 ++ types/types.go | 72 +--------------------------------- web.go | 2 +- 19 files changed, 73 insertions(+), 99 deletions(-) diff --git a/configuration.go b/configuration.go index ee996b693..2df24ca82 100644 --- a/configuration.go +++ b/configuration.go @@ -26,7 +26,7 @@ type GlobalConfiguration struct { TraefikLogsFile string `description:"Traefik logs file"` LogLevel string `short:"l" description:"Log level"` EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'"` - Constraints []*types.Constraint `description:"Filter services by constraint, matching with service tags."` + Constraints Constraints `description:"Filter services by constraint, matching with service tags."` ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"` DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"` ProvidersThrottleDuration time.Duration `description:"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."` @@ -146,6 +146,41 @@ func (ep *EntryPoints) Type() string { return fmt.Sprint("entrypoints²") } +// Constraints holds a Constraint parser +type Constraints []types.Constraint + +//Set []*Constraint +func (cs *Constraints) Set(str string) error { + exps := strings.Split(str, ",") + if len(exps) == 0 { + return errors.New("Bad Constraint format: " + str) + } + for _, exp := range exps { + constraint, err := types.NewConstraint(exp) + if err != nil { + return err + } + *cs = append(*cs, *constraint) + } + return nil +} + +//Get []*Constraint +func (cs *Constraints) Get() interface{} { return []types.Constraint(*cs) } + +//String returns []*Constraint in string +func (cs *Constraints) String() string { return fmt.Sprintf("%+v", *cs) } + +//SetValue sets []*Constraint into the parser +func (cs *Constraints) SetValue(val interface{}) { + *cs = Constraints(val.([]types.Constraint)) +} + +// Type exports the Constraints type as a string +func (cs *Constraints) Type() string { + return fmt.Sprint("constraint²") +} + // EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...) type EntryPoint struct { Network string @@ -232,6 +267,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { defaultMarathon.Watch = true defaultMarathon.Endpoint = "http://127.0.0.1:8080" defaultMarathon.ExposedByDefault = true + defaultMarathon.Constraints = []types.Constraint{} // default Consul var defaultConsul provider.Consul @@ -239,10 +275,12 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { defaultConsul.Endpoint = "127.0.0.1:8500" defaultConsul.Prefix = "/traefik" defaultConsul.TLS = &provider.KvTLS{} + defaultConsul.Constraints = []types.Constraint{} // default ConsulCatalog var defaultConsulCatalog provider.ConsulCatalog defaultConsulCatalog.Endpoint = "127.0.0.1:8500" + defaultConsulCatalog.Constraints = []types.Constraint{} // default Etcd var defaultEtcd provider.Etcd @@ -250,23 +288,27 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { defaultEtcd.Endpoint = "127.0.0.1:400" defaultEtcd.Prefix = "/traefik" defaultEtcd.TLS = &provider.KvTLS{} + defaultEtcd.Constraints = []types.Constraint{} //default Zookeeper var defaultZookeeper provider.Zookepper defaultZookeeper.Watch = true defaultZookeeper.Endpoint = "127.0.0.1:2181" defaultZookeeper.Prefix = "/traefik" + defaultZookeeper.Constraints = []types.Constraint{} //default Boltdb var defaultBoltDb provider.BoltDb defaultBoltDb.Watch = true defaultBoltDb.Endpoint = "127.0.0.1:4001" defaultBoltDb.Prefix = "/traefik" + defaultBoltDb.Constraints = []types.Constraint{} //default Kubernetes var defaultKubernetes provider.Kubernetes defaultKubernetes.Watch = true defaultKubernetes.Endpoint = "127.0.0.1:8080" + defaultKubernetes.Constraints = []types.Constraint{} defaultConfiguration := GlobalConfiguration{ Docker: &defaultDocker, @@ -295,7 +337,7 @@ func NewTraefikConfiguration() *TraefikConfiguration { TraefikLogsFile: "", LogLevel: "ERROR", EntryPoints: map[string]*EntryPoint{}, - Constraints: []*Constraint, + Constraints: []types.Constraint{}, DefaultEntryPoints: []string{}, ProvidersThrottleDuration: time.Duration(2 * time.Second), MaxIdleConnsPerHost: 200, diff --git a/glide.lock b/glide.lock index 1f95df545..69e24ab89 100644 --- a/glide.lock +++ b/glide.lock @@ -213,7 +213,6 @@ imports: subpackages: - cipher - json -- name: gopkg.in/yaml.v2 - version: 7ad95dd0798a40da1ccdff6dff35fd177b5edf40 - name: github.com/ryanuber/go-glob version: 572520ed46dbddaed19ea3d9541bdd0494163693 +devImports: [] diff --git a/integration/constraint_test.go b/integration/constraint_test.go index 50e194d2e..66ad8bbc0 100644 --- a/integration/constraint_test.go +++ b/integration/constraint_test.go @@ -1,8 +1,6 @@ package main import ( - //"io/ioutil" - //"fmt" "net/http" "os/exec" "time" diff --git a/provider/boltdb.go b/provider/boltdb.go index 50b1fa36a..574956ace 100644 --- a/provider/boltdb.go +++ b/provider/boltdb.go @@ -14,7 +14,7 @@ type BoltDb struct { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error { +func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error { provider.storeType = store.BOLTDB boltdb.Register() return provider.provide(configurationChan, pool, constraints) diff --git a/provider/consul.go b/provider/consul.go index f74490371..f936e79f3 100644 --- a/provider/consul.go +++ b/provider/consul.go @@ -14,7 +14,7 @@ type Consul struct { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error { +func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error { provider.storeType = store.CONSUL consul.Register() return provider.provide(configurationChan, pool, constraints) diff --git a/provider/consul_catalog.go b/provider/consul_catalog.go index 0b0d2294b..cce6db185 100644 --- a/provider/consul_catalog.go +++ b/provider/consul_catalog.go @@ -271,7 +271,7 @@ func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessag // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error { +func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error { config := api.DefaultConfig() config.Address = provider.Endpoint client, err := api.NewClient(config) diff --git a/provider/docker.go b/provider/docker.go index 2565d1a84..f04a82c03 100644 --- a/provider/docker.go +++ b/provider/docker.go @@ -79,7 +79,7 @@ func (provider *Docker) createClient() (client.APIClient, error) { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error { +func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error { provider.Constraints = append(provider.Constraints, constraints...) // TODO register this routine in pool, and watch for stop channel safe.Go(func() { diff --git a/provider/etcd.go b/provider/etcd.go index 35f493d03..934e0f245 100644 --- a/provider/etcd.go +++ b/provider/etcd.go @@ -14,7 +14,7 @@ type Etcd struct { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error { +func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error { provider.storeType = store.ETCD etcd.Register() return provider.provide(configurationChan, pool, constraints) diff --git a/provider/file.go b/provider/file.go index b1038df77..07bcbd02f 100644 --- a/provider/file.go +++ b/provider/file.go @@ -19,7 +19,7 @@ type File struct { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []*types.Constraint) error { +func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []types.Constraint) error { watcher, err := fsnotify.NewWatcher() if err != nil { log.Error("Error creating file watcher", err) diff --git a/provider/kubernetes.go b/provider/kubernetes.go index 0681ff24b..e46b165d4 100644 --- a/provider/kubernetes.go +++ b/provider/kubernetes.go @@ -81,7 +81,7 @@ func (provider *Kubernetes) createClient() (k8s.Client, error) { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error { +func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error { k8sClient, err := provider.createClient() if err != nil { return err diff --git a/provider/kv.go b/provider/kv.go index 3791dd3b4..42181718a 100644 --- a/provider/kv.go +++ b/provider/kv.go @@ -73,7 +73,7 @@ func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix return nil } -func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error { +func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error { storeConfig := &store.Config{ ConnectionTimeout: 30 * time.Second, Bucket: "traefik", diff --git a/provider/marathon.go b/provider/marathon.go index cf5e96622..efdaaf238 100644 --- a/provider/marathon.go +++ b/provider/marathon.go @@ -42,7 +42,7 @@ type lightMarathonClient interface { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error { +func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error { provider.Constraints = append(provider.Constraints, constraints...) operation := func() error { config := marathon.NewDefaultConfig() diff --git a/provider/provider.go b/provider/provider.go index 43d662401..d983ae7ca 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -17,14 +17,14 @@ import ( type Provider interface { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. - Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error + Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error } // BaseProvider should be inherited by providers type BaseProvider struct { - Watch bool `description:"Watch provider"` - Filename string `description:"Override default configuration template. For advanced users :)"` - Constraints []*types.Constraint `description:"Filter services by constraint, matching with Traefik tags."` + Watch bool `description:"Watch provider"` + Filename string `description:"Override default configuration template. For advanced users :)"` + Constraints []types.Constraint `description:"Filter services by constraint, matching with Traefik tags."` } // MatchConstraints must match with EVERY single contraint @@ -38,7 +38,7 @@ func (p *BaseProvider) MatchConstraints(tags []string) (bool, *types.Constraint) for _, constraint := range p.Constraints { // xor: if ok and constraint.MustMatch are equal, then no tag is currently matching with the constraint if ok := constraint.MatchConstraintWithAtLeastOneTag(tags); ok != constraint.MustMatch { - return false, constraint + return false, &constraint } } diff --git a/provider/provider_test.go b/provider/provider_test.go index 824d3ced0..7b7e487d9 100644 --- a/provider/provider_test.go +++ b/provider/provider_test.go @@ -211,13 +211,13 @@ func TestGetConfigurationReturnsCorrectMaxConnConfiguration(t *testing.T) { func TestMatchingConstraints(t *testing.T) { cases := []struct { - constraints []*types.Constraint + constraints []types.Constraint tags []string expected bool }{ // simple test: must match { - constraints: []*types.Constraint{ + constraints: []types.Constraint{ { Key: "tag", MustMatch: true, @@ -231,7 +231,7 @@ func TestMatchingConstraints(t *testing.T) { }, // simple test: must match but does not match { - constraints: []*types.Constraint{ + constraints: []types.Constraint{ { Key: "tag", MustMatch: true, @@ -245,7 +245,7 @@ func TestMatchingConstraints(t *testing.T) { }, // simple test: must not match { - constraints: []*types.Constraint{ + constraints: []types.Constraint{ { Key: "tag", MustMatch: false, @@ -259,7 +259,7 @@ func TestMatchingConstraints(t *testing.T) { }, // complex test: globbing { - constraints: []*types.Constraint{ + constraints: []types.Constraint{ { Key: "tag", MustMatch: true, @@ -273,7 +273,7 @@ func TestMatchingConstraints(t *testing.T) { }, // complex test: multiple constraints { - constraints: []*types.Constraint{ + constraints: []types.Constraint{ { Key: "tag", MustMatch: true, diff --git a/provider/zk.go b/provider/zk.go index 7d5562ee5..06eb65000 100644 --- a/provider/zk.go +++ b/provider/zk.go @@ -14,7 +14,7 @@ type Zookepper struct { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []*types.Constraint) error { +func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error { provider.storeType = store.ZK zookeeper.Register() return provider.provide(configurationChan, pool, constraints) diff --git a/server.go b/server.go index 14694efbe..6d8605b66 100644 --- a/server.go +++ b/server.go @@ -248,7 +248,7 @@ func (server *Server) startProviders() { log.Infof("Starting provider %v %s", reflect.TypeOf(provider), jsonConf) currentProvider := provider safe.Go(func() { - err := currentProvider.Provide(server.configurationChan, &server.routinesPool, server.globalConfiguration.Constraints) + err := currentProvider.Provide(server.configurationChan, &server.routinesPool, server.globalConfiguration.Constraints.Get().([]types.Constraint)) if err != nil { log.Errorf("Error starting provider %s", err) } diff --git a/traefik.go b/traefik.go index b05b558eb..c440fd39c 100644 --- a/traefik.go +++ b/traefik.go @@ -8,6 +8,7 @@ import ( "github.com/containous/traefik/acme" "github.com/containous/traefik/middlewares" "github.com/containous/traefik/provider" + "github.com/containous/traefik/types" fmtlog "log" "net/http" "os" @@ -52,6 +53,8 @@ Complete documentation is available at https://traefik.io`, //add custom parsers f.AddParser(reflect.TypeOf(EntryPoints{}), &EntryPoints{}) f.AddParser(reflect.TypeOf(DefaultEntryPoints{}), &DefaultEntryPoints{}) + f.AddParser(reflect.TypeOf([]types.Constraint{}), &Constraints{}) + f.AddParser(reflect.TypeOf(Constraints{}), &Constraints{}) f.AddParser(reflect.TypeOf(provider.Namespaces{}), &provider.Namespaces{}) f.AddParser(reflect.TypeOf([]acme.Domain{}), &acme.Domains{}) diff --git a/types/types.go b/types/types.go index 2ce5ceb97..557a31b20 100644 --- a/types/types.go +++ b/types/types.go @@ -2,10 +2,7 @@ package types import ( "errors" - "fmt" - "github.com/mitchellh/mapstructure" "github.com/ryanuber/go-glob" - "reflect" "strings" ) @@ -103,7 +100,8 @@ type Constraint struct { Key string // MustMatch is true if operator is "==" or false if operator is "!=" MustMatch bool - Regex string + // TODO: support regex + Regex string } // NewConstraint receive a string and return a *Constraint, after checking syntax and parsing the constraint expression @@ -152,69 +150,3 @@ func (c *Constraint) MatchConstraintWithAtLeastOneTag(tags []string) bool { } return false } - -// StringToConstraintHookFunc returns a DecodeHookFunc that converts strings to Constraint. -// This hook is triggered during the configuration file unmarshal-ing -func StringToConstraintHookFunc() mapstructure.DecodeHookFunc { - return func( - f reflect.Type, - t reflect.Type, - data interface{}) (interface{}, error) { - if f.Kind() != reflect.String { - return data, nil - } - if t != reflect.TypeOf(&Constraint{}) { - return data, nil - } - - constraint, err := NewConstraint(data.(string)) - if err != nil { - return data, err - } - return constraint, nil - } -} - -// Constraints own a pointer on globalConfiguration.Constraints and supports a Set() method (not possible on a slice) -// interface: -type Constraints struct { - value *[]*Constraint - changed bool -} - -// Set receive a cli argument and add it to globalConfiguration -func (cs *Constraints) Set(value string) error { - exps := strings.Split(value, ",") - if len(exps) == 0 { - return errors.New("Bad Constraint format: " + value) - } - for _, exp := range exps { - constraint, err := NewConstraint(exp) - if err != nil { - return err - } - *cs.value = append(*cs.value, constraint) - } - return nil -} - -// Type exports the Constraints type as a string -func (cs *Constraints) Type() string { - return "constraints" -} - -func (cs *Constraints) String() string { - return fmt.Sprintln("%v", *cs.value) -} - -// NewConstraintSliceValue make an alias of []*Constraint to Constraints for the command line -// Viper does not supprt SliceVar value types -// Constraints.Set called by viper will fill the []*Constraint slice -func NewConstraintSliceValue(p *[]*Constraint) *Constraints { - cs := new(Constraints) - cs.value = p - if p == nil { - *cs.value = []*Constraint{} - } - return cs -} diff --git a/web.go b/web.go index 7cafdcc0d..690c2ecfc 100644 --- a/web.go +++ b/web.go @@ -46,7 +46,7 @@ func goroutines() interface{} { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. -func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []*types.Constraint) error { +func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []types.Constraint) error { systemRouter := mux.NewRouter() // health route From d297a220ce3289f9aeb8ae9dd44b519590079c5e Mon Sep 17 00:00:00 2001 From: Samuel BERTHE Date: Wed, 1 Jun 2016 10:29:55 +0200 Subject: [PATCH 43/48] fix(constraints): Syntax --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index 6d8605b66..14694efbe 100644 --- a/server.go +++ b/server.go @@ -248,7 +248,7 @@ func (server *Server) startProviders() { log.Infof("Starting provider %v %s", reflect.TypeOf(provider), jsonConf) currentProvider := provider safe.Go(func() { - err := currentProvider.Provide(server.configurationChan, &server.routinesPool, server.globalConfiguration.Constraints.Get().([]types.Constraint)) + err := currentProvider.Provide(server.configurationChan, &server.routinesPool, server.globalConfiguration.Constraints) if err != nil { log.Errorf("Error starting provider %s", err) } From 423268f485f22f0ebcb29eae21cdfeeda35e516b Mon Sep 17 00:00:00 2001 From: Emile Vauge Date: Thu, 2 Jun 2016 11:37:51 +0200 Subject: [PATCH 44/48] Fix default configuration Signed-off-by: Emile Vauge --- examples/k8s.rc.yaml | 2 +- traefik.go | 29 ++++++++++++++--------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/examples/k8s.rc.yaml b/examples/k8s.rc.yaml index d7232b37d..43d3029d5 100644 --- a/examples/k8s.rc.yaml +++ b/examples/k8s.rc.yaml @@ -16,7 +16,7 @@ spec: spec: terminationGracePeriodSeconds: 60 containers: - - image: containous/traefik + - image: traefik name: traefik-ingress-lb imagePullPolicy: Always ports: diff --git a/traefik.go b/traefik.go index c440fd39c..68d1af1b3 100644 --- a/traefik.go +++ b/traefik.go @@ -97,27 +97,26 @@ func run(traefikConfiguration *TraefikConfiguration) { loggerMiddleware := middlewares.NewLogger(globalConfiguration.AccessLogsFile) defer loggerMiddleware.Close() + if globalConfiguration.File != nil && len(globalConfiguration.File.Filename) == 0 { + // no filename, setting to global config file + if len(traefikConfiguration.ConfigFile) != 0 { + globalConfiguration.File.Filename = traefikConfiguration.ConfigFile + } else { + log.Errorln("Error using file configuration backend, no filename defined") + } + } + + if len(globalConfiguration.EntryPoints) == 0 { + globalConfiguration.EntryPoints = map[string]*EntryPoint{"http": {Address: ":80"}} + globalConfiguration.DefaultEntryPoints = []string{"http"} + } + // logging level, err := log.ParseLevel(strings.ToLower(globalConfiguration.LogLevel)) if err != nil { log.Fatal("Error getting level", err) } log.SetLevel(level) - - if traefikConfiguration.File != nil && len(traefikConfiguration.File.Filename) == 0 { - // no filename, setting to global config file - if len(traefikConfiguration.ConfigFile) != 0 { - traefikConfiguration.File.Filename = traefikConfiguration.ConfigFile - } else { - log.Errorln("Error using file configuration backend, no filename defined") - } - } - - if len(traefikConfiguration.EntryPoints) == 0 { - traefikConfiguration.EntryPoints = map[string]*EntryPoint{"http": {Address: ":80"}} - traefikConfiguration.DefaultEntryPoints = []string{"http"} - } - if len(globalConfiguration.TraefikLogsFile) > 0 { fi, err := os.OpenFile(globalConfiguration.TraefikLogsFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) defer func() { From 3f08bb4cdf0bcd8828145a296591d0f841fcf7b4 Mon Sep 17 00:00:00 2001 From: Emile Vauge Date: Thu, 2 Jun 2016 15:17:04 +0200 Subject: [PATCH 45/48] Fix panic on help, Better version Signed-off-by: Emile Vauge --- configuration.go | 39 ++---------------------------------- glide.lock | 42 +++++++++++++++++++-------------------- glide.yaml | 4 ++-- integration/basic_test.go | 31 +++++++++++++++++++++++++++++ provider/provider.go | 6 +++--- traefik.go | 39 +++++++++++++++++++++++++++++++++--- types/types.go | 36 +++++++++++++++++++++++++++++++++ version.go | 4 ++-- 8 files changed, 133 insertions(+), 68 deletions(-) diff --git a/configuration.go b/configuration.go index 2df24ca82..aa91a16f9 100644 --- a/configuration.go +++ b/configuration.go @@ -26,7 +26,7 @@ type GlobalConfiguration struct { TraefikLogsFile string `description:"Traefik logs file"` LogLevel string `short:"l" description:"Log level"` EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'"` - Constraints Constraints `description:"Filter services by constraint, matching with service tags."` + Constraints types.Constraints `description:"Filter services by constraint, matching with service tags."` ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"` DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"` ProvidersThrottleDuration time.Duration `description:"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."` @@ -143,42 +143,7 @@ func (ep *EntryPoints) SetValue(val interface{}) { // Type is type of the struct func (ep *EntryPoints) Type() string { - return fmt.Sprint("entrypoints²") -} - -// Constraints holds a Constraint parser -type Constraints []types.Constraint - -//Set []*Constraint -func (cs *Constraints) Set(str string) error { - exps := strings.Split(str, ",") - if len(exps) == 0 { - return errors.New("Bad Constraint format: " + str) - } - for _, exp := range exps { - constraint, err := types.NewConstraint(exp) - if err != nil { - return err - } - *cs = append(*cs, *constraint) - } - return nil -} - -//Get []*Constraint -func (cs *Constraints) Get() interface{} { return []types.Constraint(*cs) } - -//String returns []*Constraint in string -func (cs *Constraints) String() string { return fmt.Sprintf("%+v", *cs) } - -//SetValue sets []*Constraint into the parser -func (cs *Constraints) SetValue(val interface{}) { - *cs = Constraints(val.([]types.Constraint)) -} - -// Type exports the Constraints type as a string -func (cs *Constraints) Type() string { - return fmt.Sprint("constraint²") + return fmt.Sprint("entrypoints") } // EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...) diff --git a/glide.lock b/glide.lock index 69e24ab89..05374f993 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 1d42530971835a5a1e593e8efc54f28f2714cb8d1dfcb333e3b9ba92ef1917cc -updated: 2016-05-30T16:53:17.058435869+02:00 +hash: dc59755b72e71945a21135c5a37e4a5c11ae511ac7404d1440166ea0aed736c4 +updated: 2016-06-02T15:11:52.77657652+02:00 imports: - name: github.com/boltdb/bolt version: dfb21201d9270c1082d5fb0f07f500311ff72f18 @@ -10,15 +10,15 @@ imports: subpackages: - fun - name: github.com/cenkalti/backoff - version: c29158af31815ccc31ca29c86c121bc39e00d3d8 + version: a6030178a585d5972d4d33ce61f4a1fa40eaaed0 - name: github.com/codahale/hdrhistogram version: 9208b142303c12d8899bae836fd524ac9338b4fd - name: github.com/codegangsta/cli version: bf4a526f48af7badd25d2cb02d587e1b01be3b50 - name: github.com/codegangsta/negroni - version: ffbc66b612ee3eac2eba29aedce4c3a65e4dd0a1 + version: fb7b7c045dfb05dc81a5c3688c568550b5bd6e36 - name: github.com/containous/flaeg - version: a208db24c0e580be76efed167736fe3204c7c954 + version: b98687da5c323650f4513fda6b6203fcbdec9313 - name: github.com/containous/oxy version: 183212964e13e7b8afe01a08b193d04300554a68 subpackages: @@ -29,7 +29,7 @@ imports: - stream - utils - name: github.com/containous/staert - version: 9f815ef829b66de3519fea6c0b8d4708eb9472d4 + version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602 - name: github.com/coreos/etcd version: c400d05d0aa73e21e431c16145e558d624098018 subpackages: @@ -43,7 +43,7 @@ imports: subpackages: - spew - name: github.com/docker/distribution - version: 5bbf65499960b184fe8e0f045397375e1a6722b8 + version: bb330cd684eb4afab9cc4f2453d7c8918099d7ee subpackages: - reference - digest @@ -75,7 +75,7 @@ imports: - tlsconfig - nat - name: github.com/docker/go-units - version: 5d2041e26a699eaca682e2ea41c8f891e1060444 + version: 09dda9d4b0d748c57c14048906d3d094a58ec0c9 - name: github.com/docker/libcompose version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873 - name: github.com/docker/libkv @@ -87,7 +87,7 @@ imports: - store/etcd - store/zookeeper - name: github.com/donovanhide/eventsource - version: c3f57f280ec708df24886d9e62f2fd178d69d8e8 + version: fd1de70867126402be23c306e1ce32828455d85b - name: github.com/elazarl/go-bindata-assetfs version: 57eb5e1fc594ad4b0b1dbea7b286d299e0cb43c2 - name: github.com/gambol99/go-marathon @@ -99,11 +99,11 @@ imports: subpackages: - query - name: github.com/gorilla/context - version: a8d44e7d8e4d532b6a27a02dd82abb31cc1b01bd + version: aed02d124ae4a0e94fea4541c8effd05bf0c8296 - name: github.com/gorilla/mux - version: 9c19ed558d5df4da88e2ade9c8940d742aef0e7e + version: bd09be08ed4377796d312df0a45314e11b8f5dc1 - name: github.com/hashicorp/consul - version: f6fef66e1bf17be4f3c9855fbec6de802ca6bd7d + version: ebf7ea1d759184c02a5bb5263a7c52d29838ffc3 subpackages: - api - name: github.com/hashicorp/go-cleanhttp @@ -136,27 +136,29 @@ imports: - name: github.com/ogier/pflag version: 45c278ab3607870051a2ea9040bb85fcb8557481 - name: github.com/opencontainers/runc - version: d2d09b9bcd0573c58d7cd94e57bd7555af0c2072 + version: 6c485e6902bb9dd77b8234042b8f00e20ef87a18 subpackages: - libcontainer/user - name: github.com/parnurzeal/gorequest - version: 2169dfca686cfcbefc983a98a25e9c22a2815be4 + version: f17fef20c518e688f4edb3eb2af148462ecab3ef - name: github.com/pmezard/go-difflib version: d8ed2627bdf02c080bf22230dbb337003b7aba2d subpackages: - difflib +- name: github.com/ryanuber/go-glob + version: 572520ed46dbddaed19ea3d9541bdd0494163693 - name: github.com/samuel/go-zookeeper - version: 6eb1b09c6ce23f305f4c81bf748b22fbc6f3f9e9 + version: 4b20de542e40ed2b89d65ae195fc20a330919b92 subpackages: - zk - name: github.com/Sirupsen/logrus - version: 6d9ae300aaf85d6acd2e5424081c7fcddb21dab8 + version: f3cfb454f4c209e6668c95216c4744b8fddb2356 - name: github.com/streamrail/concurrent-map version: 65a174a3a4188c0b7099acbc6cfa0c53628d3287 - name: github.com/stretchr/objx version: cbeaeb16a013161a98496fad62933b1d21786672 - name: github.com/stretchr/testify - version: 6cb3b85ef5a0efef77caef88363ec4d4b5c0976d + version: 8d64eb7173c7753d6419fd4a9caf057398611364 subpackages: - mock - assert @@ -165,7 +167,7 @@ imports: - name: github.com/unrolled/render version: 198ad4d8b8a4612176b804ca10555b222a086b40 - name: github.com/vdemeester/docker-events - version: b308d2e8d639d928c882913bcb4f85b3a84c7a07 + version: 20e6d2db238723e68197a9e3c6c34c99a9893a9c - name: github.com/vdemeester/shakers version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3 - name: github.com/vulcand/oxy @@ -184,7 +186,7 @@ imports: - plugin - router - name: github.com/xenolf/lego - version: b119bc45fbd1cc71348003541aac9d3a7da63654 + version: 30a7a8e8821de3532192d1240a45e53c6204f603 subpackages: - acme - name: golang.org/x/crypto @@ -213,6 +215,4 @@ imports: subpackages: - cipher - json -- name: github.com/ryanuber/go-glob - version: 572520ed46dbddaed19ea3d9541bdd0494163693 devImports: [] diff --git a/glide.yaml b/glide.yaml index 4636007ae..9a1e4385c 100644 --- a/glide.yaml +++ b/glide.yaml @@ -8,7 +8,7 @@ import: - package: github.com/cenkalti/backoff - package: github.com/codegangsta/negroni - package: github.com/containous/flaeg - version: a208db24c0e580be76efed167736fe3204c7c954 + version: b98687da5c323650f4513fda6b6203fcbdec9313 - package: github.com/containous/oxy subpackages: - cbreaker @@ -18,7 +18,7 @@ import: - stream - utils - package: github.com/containous/staert - version: 9f815ef829b66de3519fea6c0b8d4708eb9472d4 + version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602 - package: github.com/docker/engine-api version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb subpackages: diff --git a/integration/basic_test.go b/integration/basic_test.go index 7e5549174..1ef8521cc 100644 --- a/integration/basic_test.go +++ b/integration/basic_test.go @@ -57,3 +57,34 @@ func (s *SimpleSuite) TestWithWebConfig(c *check.C) { c.Assert(err, checker.IsNil) c.Assert(resp.StatusCode, checker.Equals, 200) } + +func (s *SimpleSuite) TestDefaultEntryPoints(c *check.C) { + cmd := exec.Command(traefikBinary, "--debug") + + var b bytes.Buffer + cmd.Stdout = &b + cmd.Stderr = &b + + cmd.Start() + time.Sleep(500 * time.Millisecond) + defer cmd.Process.Kill() + output := b.Bytes() + + c.Assert(string(output), checker.Contains, "\\\"DefaultEntryPoints\\\":[\\\"http\\\"]") +} + +func (s *SimpleSuite) TestPrintHelp(c *check.C) { + cmd := exec.Command(traefikBinary, "--help") + + var b bytes.Buffer + cmd.Stdout = &b + cmd.Stderr = &b + + cmd.Start() + time.Sleep(500 * time.Millisecond) + defer cmd.Process.Kill() + output := b.Bytes() + + c.Assert(string(output), checker.Not(checker.Contains), "panic:") + c.Assert(string(output), checker.Contains, "Usage:") +} diff --git a/provider/provider.go b/provider/provider.go index d983ae7ca..fafc39c7e 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -22,9 +22,9 @@ type Provider interface { // BaseProvider should be inherited by providers type BaseProvider struct { - Watch bool `description:"Watch provider"` - Filename string `description:"Override default configuration template. For advanced users :)"` - Constraints []types.Constraint `description:"Filter services by constraint, matching with Traefik tags."` + Watch bool `description:"Watch provider"` + Filename string `description:"Override default configuration template. For advanced users :)"` + Constraints types.Constraints `description:"Filter services by constraint, matching with Traefik tags."` } // MatchConstraints must match with EVERY single contraint diff --git a/traefik.go b/traefik.go index 68d1af1b3..a8be2edd1 100644 --- a/traefik.go +++ b/traefik.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "fmt" log "github.com/Sirupsen/logrus" "github.com/containous/flaeg" "github.com/containous/staert" @@ -15,8 +16,14 @@ import ( "reflect" "runtime" "strings" + "text/template" ) +var versionTemplate = `Version: {{.Version}} +Go version: {{.GoVersion}} +Built: {{.BuildTime}} +OS/Arch: {{.Os}}/{{.Arch}}` + func main() { runtime.GOMAXPROCS(runtime.NumCPU()) @@ -43,8 +50,31 @@ Complete documentation is available at https://traefik.io`, Config: struct{}{}, DefaultPointersConfig: struct{}{}, Run: func() error { - fmtlog.Println(Version + " built on the " + BuildDate) + tmpl, err := template.New("").Parse(versionTemplate) + if err != nil { + return err + } + + v := struct { + Version string + GoVersion string + BuildTime string + Os string + Arch string + }{ + Version: Version, + GoVersion: runtime.Version(), + BuildTime: BuildDate, + Os: runtime.GOOS, + Arch: runtime.GOARCH, + } + + if err := tmpl.Execute(os.Stdout, v); err != nil { + return err + } + fmt.Printf("\n") return nil + }, } @@ -53,8 +83,7 @@ Complete documentation is available at https://traefik.io`, //add custom parsers f.AddParser(reflect.TypeOf(EntryPoints{}), &EntryPoints{}) f.AddParser(reflect.TypeOf(DefaultEntryPoints{}), &DefaultEntryPoints{}) - f.AddParser(reflect.TypeOf([]types.Constraint{}), &Constraints{}) - f.AddParser(reflect.TypeOf(Constraints{}), &Constraints{}) + f.AddParser(reflect.TypeOf(types.Constraints{}), &types.Constraints{}) f.AddParser(reflect.TypeOf(provider.Namespaces{}), &provider.Namespaces{}) f.AddParser(reflect.TypeOf([]acme.Domain{}), &acme.Domains{}) @@ -111,6 +140,10 @@ func run(traefikConfiguration *TraefikConfiguration) { globalConfiguration.DefaultEntryPoints = []string{"http"} } + if globalConfiguration.Debug { + globalConfiguration.LogLevel = "DEBUG" + } + // logging level, err := log.ParseLevel(strings.ToLower(globalConfiguration.LogLevel)) if err != nil { diff --git a/types/types.go b/types/types.go index 557a31b20..dcac6bd46 100644 --- a/types/types.go +++ b/types/types.go @@ -2,6 +2,7 @@ package types import ( "errors" + "fmt" "github.com/ryanuber/go-glob" "strings" ) @@ -150,3 +151,38 @@ func (c *Constraint) MatchConstraintWithAtLeastOneTag(tags []string) bool { } return false } + +//Set []*Constraint +func (cs *Constraints) Set(str string) error { + exps := strings.Split(str, ",") + if len(exps) == 0 { + return errors.New("Bad Constraint format: " + str) + } + for _, exp := range exps { + constraint, err := NewConstraint(exp) + if err != nil { + return err + } + *cs = append(*cs, *constraint) + } + return nil +} + +// Constraints holds a Constraint parser +type Constraints []Constraint + +//Get []*Constraint +func (cs *Constraints) Get() interface{} { return []Constraint(*cs) } + +//String returns []*Constraint in string +func (cs *Constraints) String() string { return fmt.Sprintf("%+v", *cs) } + +//SetValue sets []*Constraint into the parser +func (cs *Constraints) SetValue(val interface{}) { + *cs = Constraints(val.(Constraints)) +} + +// Type exports the Constraints type as a string +func (cs *Constraints) Type() string { + return fmt.Sprint("constraint") +} diff --git a/version.go b/version.go index 15a6ee9dd..10f7cdb3c 100644 --- a/version.go +++ b/version.go @@ -2,7 +2,7 @@ package main var ( // Version holds the current version of traefik. - Version = "" + Version = "dev" // BuildDate holds the build date of traefik. - BuildDate = "" + BuildDate = "I don't remember exactly" ) From 3c3b179c290e0ef4f5dacfee81652144c4b50a04 Mon Sep 17 00:00:00 2001 From: Emile Vauge Date: Thu, 2 Jun 2016 15:29:36 +0200 Subject: [PATCH 46/48] Deploy PR Docker image Signed-off-by: Emile Vauge --- .travis.yml | 1 + Makefile | 3 +++ script/deploy-pr.sh | 25 +++++++++++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100755 script/deploy-pr.sh diff --git a/.travis.yml b/.travis.yml index 74fcb4bcc..adcc5e2c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,3 +29,4 @@ script: - make image after_success: - make deploy +- make deploy-pr diff --git a/Makefile b/Makefile index 9e6a5d15d..8ca374210 100644 --- a/Makefile +++ b/Makefile @@ -85,5 +85,8 @@ fmt: deploy: ./script/deploy.sh +deploy-pr: + ./script/deploy-pr.sh + help: ## this help @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) diff --git a/script/deploy-pr.sh b/script/deploy-pr.sh new file mode 100755 index 000000000..7fd79407c --- /dev/null +++ b/script/deploy-pr.sh @@ -0,0 +1,25 @@ +#!/bin/bash +set -e + +if ([ "$TRAVIS_BRANCH" = "master" ] && [ -z "$TRAVIS_TAG" ]) && [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$DOCKER_VERSION" = "1.10.1" ]; then + echo "Deploying PR..." +else + echo "Skipping deploy PR" + exit 0 +fi + +COMMENT=$(git log -1 --pretty=%B) +PR=$(echo $COMMENT | grep -oP "Merge pull request #\K(([0-9]*))(?=.*)") + +if [ -z "$PR" ]; then + echo "Unable to get PR number: $PR from: $COMMENT" + exit 0 +fi + +# create docker image containous/traefik +echo "Updating docker containous/traefik image..." +docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS +docker tag containous/traefik containous/traefik:pr-${PR} +docker push containous/traefik:pr-${PR} + +echo "Deployed" From 72f88e5c0fa7f03afb77ea097e94e0705dfa3dde Mon Sep 17 00:00:00 2001 From: Emile Vauge Date: Tue, 31 May 2016 23:23:23 +0200 Subject: [PATCH 47/48] Add marathon directory subdomain Signed-off-by: Emile Vauge --- examples/whoami-group.json | 40 ++++++++++++++++++++++++++++++++++++++ provider/docker.go | 7 ++++++- provider/marathon.go | 10 +++++++++- provider/provider.go | 5 ----- 4 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 examples/whoami-group.json diff --git a/examples/whoami-group.json b/examples/whoami-group.json new file mode 100644 index 000000000..9c21a8dae --- /dev/null +++ b/examples/whoami-group.json @@ -0,0 +1,40 @@ +{ + "id": "/foo", + "groups": [ + { + "id": "/foo/bar", + "apps": [ + { + "id": "whoami", + "cpus": 0.1, + "mem": 64.0, + "instances": 3, + "container": { + "type": "DOCKER", + "docker": { + "image": "emilevauge/whoami", + "network": "BRIDGE", + "portMappings": [ + { + "containerPort": 80, + "hostPort": 0, + "protocol": "tcp" + } + ] + } + }, + "healthChecks": [ + { + "protocol": "HTTP", + "portIndex": 0, + "path": "/", + "gracePeriodSeconds": 5, + "intervalSeconds": 20, + "maxConsecutiveFailures": 3 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/provider/docker.go b/provider/docker.go index 542f244ab..964fc8ea8 100644 --- a/provider/docker.go +++ b/provider/docker.go @@ -236,7 +236,7 @@ func (provider *Docker) getFrontendRule(container dockertypes.ContainerJSON) str if label, err := getLabel(container, "traefik.frontend.rule"); err == nil { return label } - return "Host:" + getEscapedName(container.Name) + "." + provider.Domain + return "Host:" + provider.getEscapedName(container.Name) + "." + provider.Domain } func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string { @@ -349,3 +349,8 @@ func listContainers(dockerClient client.APIClient) ([]dockertypes.ContainerJSON, } return containersInspected, nil } + +// Escape beginning slash "/", convert all others to dash "-" +func (provider *Docker) getEscapedName(name string) string { + return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1) +} diff --git a/provider/marathon.go b/provider/marathon.go index efdaaf238..7612b3b26 100644 --- a/provider/marathon.go +++ b/provider/marathon.go @@ -3,6 +3,7 @@ package provider import ( "errors" "net/url" + "sort" "strconv" "strings" "text/template" @@ -341,7 +342,7 @@ func (provider *Marathon) getFrontendRule(application marathon.Application) stri if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil { return label } - return "Host:" + getEscapedName(application.ID) + "." + provider.Domain + return "Host:" + provider.getEscapedName(application.ID) + "." + provider.Domain } func (provider *Marathon) getBackend(task marathon.Task, applications []marathon.Application) string { @@ -359,3 +360,10 @@ func (provider *Marathon) getFrontendBackend(application marathon.Application) s } return replace("/", "-", application.ID) } + +func (provider *Marathon) getEscapedName(name string) string { + splitedName := strings.Split(strings.TrimPrefix(name, "/"), "/") + sort.Sort(sort.Reverse(sort.StringSlice(splitedName))) + reverseName := strings.Join(splitedName, ".") + return reverseName +} diff --git a/provider/provider.go b/provider/provider.go index fafc39c7e..5f52988e0 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -85,11 +85,6 @@ func replace(s1 string, s2 string, s3 string) string { return strings.Replace(s3, s1, s2, -1) } -// Escape beginning slash "/", convert all others to dash "-" -func getEscapedName(name string) string { - return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1) -} - func normalize(name string) string { fargs := func(c rune) bool { return !unicode.IsLetter(c) && !unicode.IsNumber(c) From 92ca220890d167091fafc5f0d55b4e250fa0c092 Mon Sep 17 00:00:00 2001 From: Emile Vauge Date: Wed, 1 Jun 2016 16:47:39 +0200 Subject: [PATCH 48/48] Add groupsAsSubDomains option Signed-off-by: Emile Vauge --- docs/toml.md | 11 ++++++++++- provider/docker.go | 4 ++-- provider/marathon.go | 30 +++++++++++++++++------------- traefik.sample.toml | 11 ++++++++++- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/docs/toml.md b/docs/toml.md index 9689fafa6..254280691 100644 --- a/docs/toml.md +++ b/docs/toml.md @@ -636,7 +636,16 @@ domain = "marathon.localhost" # Optional # Default: false # -# ExposedByDefault = true +# exposedByDefault = true + +# Convert Marathon groups to subdomains +# Default behavior: /foo/bar/myapp => foo-bar-myapp.{defaultDomain} +# with groupsAsSubDomains enabled: /foo/bar/myapp => myapp.bar.foo.{defaultDomain} +# +# Optional +# Default: false +# +# groupsAsSubDomains = true # Enable Marathon basic authentication # diff --git a/provider/docker.go b/provider/docker.go index 964fc8ea8..657eabfda 100644 --- a/provider/docker.go +++ b/provider/docker.go @@ -236,7 +236,7 @@ func (provider *Docker) getFrontendRule(container dockertypes.ContainerJSON) str if label, err := getLabel(container, "traefik.frontend.rule"); err == nil { return label } - return "Host:" + provider.getEscapedName(container.Name) + "." + provider.Domain + return "Host:" + provider.getSubDomain(container.Name) + "." + provider.Domain } func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string { @@ -351,6 +351,6 @@ func listContainers(dockerClient client.APIClient) ([]dockertypes.ContainerJSON, } // Escape beginning slash "/", convert all others to dash "-" -func (provider *Docker) getEscapedName(name string) string { +func (provider *Docker) getSubDomain(name string) string { return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1) } diff --git a/provider/marathon.go b/provider/marathon.go index 7612b3b26..a97e70ecb 100644 --- a/provider/marathon.go +++ b/provider/marathon.go @@ -21,13 +21,14 @@ import ( // Marathon holds configuration of the Marathon provider. type Marathon struct { - BaseProvider `mapstructure:",squash" description:"go through"` - Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"` - Domain string `description:"Default domain used"` - ExposedByDefault bool `description:"Expose Marathon apps by default"` - Basic *MarathonBasic - TLS *tls.Config - marathonClient marathon.Marathon + BaseProvider + Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"` + Domain string `description:"Default domain used"` + ExposedByDefault bool `description:"Expose Marathon apps by default"` + GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"` + Basic *MarathonBasic + TLS *tls.Config + marathonClient marathon.Marathon } // MarathonBasic holds basic authentication specific configurations @@ -342,7 +343,7 @@ func (provider *Marathon) getFrontendRule(application marathon.Application) stri if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil { return label } - return "Host:" + provider.getEscapedName(application.ID) + "." + provider.Domain + return "Host:" + provider.getSubDomain(application.ID) + "." + provider.Domain } func (provider *Marathon) getBackend(task marathon.Task, applications []marathon.Application) string { @@ -361,9 +362,12 @@ func (provider *Marathon) getFrontendBackend(application marathon.Application) s return replace("/", "-", application.ID) } -func (provider *Marathon) getEscapedName(name string) string { - splitedName := strings.Split(strings.TrimPrefix(name, "/"), "/") - sort.Sort(sort.Reverse(sort.StringSlice(splitedName))) - reverseName := strings.Join(splitedName, ".") - return reverseName +func (provider *Marathon) getSubDomain(name string) string { + if provider.GroupsAsSubDomains { + splitedName := strings.Split(strings.TrimPrefix(name, "/"), "/") + sort.Sort(sort.Reverse(sort.StringSlice(splitedName))) + reverseName := strings.Join(splitedName, ".") + return reverseName + } + return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1) } diff --git a/traefik.sample.toml b/traefik.sample.toml index 51f2442e4..3b900c112 100644 --- a/traefik.sample.toml +++ b/traefik.sample.toml @@ -306,7 +306,16 @@ # Optional # Default: false # -# ExposedByDefault = true +# exposedByDefault = true + +# Convert Marathon groups to subdomains +# Default behavior: /foo/bar/myapp => foo-bar-myapp.{defaultDomain} +# with groupsAsSubDomains enabled: /foo/bar/myapp => myapp.bar.foo.{defaultDomain} +# +# Optional +# Default: false +# +# groupsAsSubDomains = true # Enable Marathon basic authentication #