From e0af17a17a4d52d95210dfb6531fd1db290b7139 Mon Sep 17 00:00:00 2001 From: SALLEYRON Julien Date: Fri, 25 Aug 2017 16:10:03 +0200 Subject: [PATCH] Refactor globalConfiguration / WebProvider --- cmd/traefik/configuration.go | 208 ++++++++++++++++++++ cmd/traefik/traefik.go | 78 ++++---- {server => configuration}/configuration.go | 212 ++------------------- integration/log_rotation_test.go | 2 +- {server => provider/web}/web.go | 93 +++++---- server/server.go | 167 ++++++++-------- server/server_test.go | 65 +++---- types/types.go | 3 + 8 files changed, 430 insertions(+), 398 deletions(-) create mode 100644 cmd/traefik/configuration.go rename {server => configuration}/configuration.go (73%) rename {server => provider/web}/web.go (74%) diff --git a/cmd/traefik/configuration.go b/cmd/traefik/configuration.go new file mode 100644 index 000000000..bf668be63 --- /dev/null +++ b/cmd/traefik/configuration.go @@ -0,0 +1,208 @@ +package main + +import ( + "time" + + "github.com/containous/flaeg" + "github.com/containous/traefik/configuration" + "github.com/containous/traefik/middlewares/accesslog" + "github.com/containous/traefik/provider/boltdb" + "github.com/containous/traefik/provider/consul" + "github.com/containous/traefik/provider/docker" + "github.com/containous/traefik/provider/dynamodb" + "github.com/containous/traefik/provider/ecs" + "github.com/containous/traefik/provider/etcd" + "github.com/containous/traefik/provider/file" + "github.com/containous/traefik/provider/kubernetes" + "github.com/containous/traefik/provider/marathon" + "github.com/containous/traefik/provider/mesos" + "github.com/containous/traefik/provider/rancher" + "github.com/containous/traefik/provider/web" + "github.com/containous/traefik/provider/zk" + "github.com/containous/traefik/types" +) + +// TraefikConfiguration holds GlobalConfiguration and other stuff +type TraefikConfiguration struct { + configuration.GlobalConfiguration `mapstructure:",squash"` + ConfigFile string `short:"c" description:"Configuration file to use (TOML)."` +} + +// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values +func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { + //default Docker + var defaultDocker docker.Provider + defaultDocker.Watch = true + defaultDocker.ExposedByDefault = true + defaultDocker.Endpoint = "unix:///var/run/docker.sock" + defaultDocker.SwarmMode = false + + // default File + var defaultFile file.Provider + defaultFile.Watch = true + defaultFile.Filename = "" //needs equivalent to viper.ConfigFileUsed() + + // default Web + var defaultWeb web.Provider + defaultWeb.Address = ":8080" + defaultWeb.Statistics = &types.Statistics{ + RecentErrors: 10, + } + + // default Metrics + defaultWeb.Metrics = &types.Metrics{ + Prometheus: &types.Prometheus{ + Buckets: types.Buckets{0.1, 0.3, 1.2, 5}, + }, + Datadog: &types.Datadog{ + Address: "localhost:8125", + PushInterval: "10s", + }, + StatsD: &types.Statsd{ + Address: "localhost:8125", + PushInterval: "10s", + }, + } + + // default Marathon + var defaultMarathon marathon.Provider + defaultMarathon.Watch = true + defaultMarathon.Endpoint = "http://127.0.0.1:8080" + defaultMarathon.ExposedByDefault = true + defaultMarathon.Constraints = types.Constraints{} + defaultMarathon.DialerTimeout = flaeg.Duration(60 * time.Second) + defaultMarathon.KeepAlive = flaeg.Duration(10 * time.Second) + + // default Consul + var defaultConsul consul.Provider + defaultConsul.Watch = true + defaultConsul.Endpoint = "127.0.0.1:8500" + defaultConsul.Prefix = "traefik" + defaultConsul.Constraints = types.Constraints{} + + // default CatalogProvider + var defaultConsulCatalog consul.CatalogProvider + defaultConsulCatalog.Endpoint = "127.0.0.1:8500" + defaultConsulCatalog.Constraints = types.Constraints{} + defaultConsulCatalog.Prefix = "traefik" + defaultConsulCatalog.FrontEndRule = "Host:{{.ServiceName}}.{{.Domain}}" + + // default Etcd + var defaultEtcd etcd.Provider + defaultEtcd.Watch = true + defaultEtcd.Endpoint = "127.0.0.1:2379" + defaultEtcd.Prefix = "/traefik" + defaultEtcd.Constraints = types.Constraints{} + + //default Zookeeper + var defaultZookeeper zk.Provider + defaultZookeeper.Watch = true + defaultZookeeper.Endpoint = "127.0.0.1:2181" + defaultZookeeper.Prefix = "/traefik" + defaultZookeeper.Constraints = types.Constraints{} + + //default Boltdb + var defaultBoltDb boltdb.Provider + defaultBoltDb.Watch = true + defaultBoltDb.Endpoint = "127.0.0.1:4001" + defaultBoltDb.Prefix = "/traefik" + defaultBoltDb.Constraints = types.Constraints{} + + //default Kubernetes + var defaultKubernetes kubernetes.Provider + defaultKubernetes.Watch = true + defaultKubernetes.Endpoint = "" + defaultKubernetes.LabelSelector = "" + defaultKubernetes.Constraints = types.Constraints{} + + // default Mesos + var defaultMesos mesos.Provider + defaultMesos.Watch = true + defaultMesos.Endpoint = "http://127.0.0.1:5050" + defaultMesos.ExposedByDefault = true + defaultMesos.Constraints = types.Constraints{} + defaultMesos.RefreshSeconds = 30 + defaultMesos.ZkDetectionTimeout = 30 + defaultMesos.StateTimeoutSecond = 30 + + //default ECS + var defaultECS ecs.Provider + defaultECS.Watch = true + defaultECS.ExposedByDefault = true + defaultECS.AutoDiscoverClusters = false + defaultECS.Clusters = ecs.Clusters{"default"} + defaultECS.RefreshSeconds = 15 + defaultECS.Constraints = types.Constraints{} + + //default Rancher + var defaultRancher rancher.Provider + defaultRancher.Watch = true + defaultRancher.ExposedByDefault = true + defaultRancher.RefreshSeconds = 15 + + // default DynamoDB + var defaultDynamoDB dynamodb.Provider + defaultDynamoDB.Constraints = types.Constraints{} + defaultDynamoDB.RefreshSeconds = 15 + defaultDynamoDB.TableName = "traefik" + defaultDynamoDB.Watch = true + + // default AccessLog + defaultAccessLog := types.AccessLog{ + Format: accesslog.CommonFormat, + FilePath: "", + } + + defaultConfiguration := configuration.GlobalConfiguration{ + Docker: &defaultDocker, + File: &defaultFile, + Web: &defaultWeb, + Marathon: &defaultMarathon, + Consul: &defaultConsul, + ConsulCatalog: &defaultConsulCatalog, + Etcd: &defaultEtcd, + Zookeeper: &defaultZookeeper, + Boltdb: &defaultBoltDb, + Kubernetes: &defaultKubernetes, + Mesos: &defaultMesos, + ECS: &defaultECS, + Rancher: &defaultRancher, + DynamoDB: &defaultDynamoDB, + Retry: &configuration.Retry{}, + HealthCheck: &configuration.HealthCheckConfig{}, + AccessLog: &defaultAccessLog, + } + + return &TraefikConfiguration{ + GlobalConfiguration: defaultConfiguration, + } +} + +// NewTraefikConfiguration creates a TraefikConfiguration with default values +func NewTraefikConfiguration() *TraefikConfiguration { + return &TraefikConfiguration{ + GlobalConfiguration: configuration.GlobalConfiguration{ + GraceTimeOut: flaeg.Duration(10 * time.Second), + AccessLogsFile: "", + TraefikLogsFile: "", + LogLevel: "ERROR", + EntryPoints: map[string]*configuration.EntryPoint{}, + Constraints: types.Constraints{}, + DefaultEntryPoints: []string{}, + ProvidersThrottleDuration: flaeg.Duration(2 * time.Second), + MaxIdleConnsPerHost: 200, + IdleTimeout: flaeg.Duration(0), + HealthCheck: &configuration.HealthCheckConfig{ + Interval: flaeg.Duration(configuration.DefaultHealthCheckInterval), + }, + RespondingTimeouts: &configuration.RespondingTimeouts{ + IdleTimeout: flaeg.Duration(configuration.DefaultIdleTimeout), + }, + ForwardingTimeouts: &configuration.ForwardingTimeouts{ + DialTimeout: flaeg.Duration(configuration.DefaultDialTimeout), + }, + CheckNewVersion: true, + }, + ConfigFile: "", + } +} diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index 27f2a122a..2b4f7f8c9 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -18,6 +18,7 @@ import ( "github.com/containous/staert" "github.com/containous/traefik/acme" "github.com/containous/traefik/cluster" + "github.com/containous/traefik/configuration" "github.com/containous/traefik/log" "github.com/containous/traefik/provider/ecs" "github.com/containous/traefik/provider/kubernetes" @@ -35,8 +36,8 @@ func main() { runtime.GOMAXPROCS(runtime.NumCPU()) //traefik config inits - traefikConfiguration := server.NewTraefikConfiguration() - traefikPointersConfiguration := server.NewTraefikDefaultPointersConfiguration() + traefikConfiguration := NewTraefikConfiguration() + traefikPointersConfiguration := NewTraefikDefaultPointersConfiguration() //traefik Command init traefikCmd := &flaeg.Command{ Name: "traefik", @@ -45,7 +46,19 @@ Complete documentation is available at https://traefik.io`, Config: traefikConfiguration, DefaultPointersConfig: traefikPointersConfiguration, Run: func() error { - run(traefikConfiguration) + globalConfiguration := traefikConfiguration.GlobalConfiguration + 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(traefikConfiguration.ConfigFile) != 0 { + log.Infof("Using TOML configuration file %s", traefikConfiguration.ConfigFile) + } + run(&globalConfiguration) return nil }, } @@ -54,7 +67,7 @@ Complete documentation is available at https://traefik.io`, var kv *staert.KvSource var err error - storeconfigCmd := &flaeg.Command{ + storeConfigCmd := &flaeg.Command{ Name: "storeconfig", Description: `Store the static traefik configuration into a Key-value stores. Traefik will not start.`, Config: traefikConfiguration, @@ -74,8 +87,8 @@ Complete documentation is available at https://traefik.io`, } if traefikConfiguration.GlobalConfiguration.ACME != nil && len(traefikConfiguration.GlobalConfiguration.ACME.StorageFile) > 0 { // convert ACME json file to KV store - store := acme.NewLocalStore(traefikConfiguration.GlobalConfiguration.ACME.StorageFile) - object, err := store.Load() + localStore := acme.NewLocalStore(traefikConfiguration.GlobalConfiguration.ACME.StorageFile) + object, err := localStore.Load() if err != nil { return err } @@ -100,7 +113,7 @@ Complete documentation is available at https://traefik.io`, }, } - healthcheckCmd := &flaeg.Command{ + healthCheckCmd := &flaeg.Command{ Name: "healthcheck", Description: `Calls traefik /ping to check health (web provider must be enabled)`, Config: traefikConfiguration, @@ -140,9 +153,9 @@ Complete documentation is available at https://traefik.io`, //init flaeg source f := flaeg.New(traefikCmd, os.Args[1:]) //add custom parsers - f.AddParser(reflect.TypeOf(server.EntryPoints{}), &server.EntryPoints{}) - f.AddParser(reflect.TypeOf(server.DefaultEntryPoints{}), &server.DefaultEntryPoints{}) - f.AddParser(reflect.TypeOf(server.RootCAs{}), &server.RootCAs{}) + f.AddParser(reflect.TypeOf(configuration.EntryPoints{}), &configuration.EntryPoints{}) + f.AddParser(reflect.TypeOf(configuration.DefaultEntryPoints{}), &configuration.DefaultEntryPoints{}) + f.AddParser(reflect.TypeOf(configuration.RootCAs{}), &configuration.RootCAs{}) f.AddParser(reflect.TypeOf(types.Constraints{}), &types.Constraints{}) f.AddParser(reflect.TypeOf(kubernetes.Namespaces{}), &kubernetes.Namespaces{}) f.AddParser(reflect.TypeOf(ecs.Clusters{}), &ecs.Clusters{}) @@ -152,8 +165,8 @@ Complete documentation is available at https://traefik.io`, //add commands f.AddCommand(newVersionCmd()) f.AddCommand(newBugCmd(traefikConfiguration, traefikPointersConfiguration)) - f.AddCommand(storeconfigCmd) - f.AddCommand(healthcheckCmd) + f.AddCommand(storeConfigCmd) + f.AddCommand(healthCheckCmd) usedCmd, err := f.GetCommand() if err != nil { @@ -210,23 +223,13 @@ Complete documentation is available at https://traefik.io`, os.Exit(0) } -func run(traefikConfiguration *server.TraefikConfiguration) { +func run(globalConfiguration *configuration.GlobalConfiguration) { fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags) - // load global configuration - globalConfiguration := traefikConfiguration.GlobalConfiguration - - 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") - } - } + http.DefaultTransport.(*http.Transport).Proxy = http.ProxyFromEnvironment if len(globalConfiguration.EntryPoints) == 0 { - globalConfiguration.EntryPoints = map[string]*server.EntryPoint{"http": {Address: ":80"}} + globalConfiguration.EntryPoints = map[string]*configuration.EntryPoint{"http": {Address: ":80"}} globalConfiguration.DefaultEntryPoints = []string{"http"} } @@ -300,11 +303,8 @@ func run(traefikConfiguration *server.TraefikConfiguration) { }) } - if len(traefikConfiguration.ConfigFile) != 0 { - log.Infof("Using TOML configuration file %s", traefikConfiguration.ConfigFile) - } log.Debugf("Global configuration loaded %s", string(jsonConf)) - svr := server.NewServer(globalConfiguration) + svr := server.NewServer(*globalConfiguration) svr.Start() defer svr.Close() sent, err := daemon.SdNotify(false, "READY=1") @@ -333,34 +333,34 @@ func run(traefikConfiguration *server.TraefikConfiguration) { // CreateKvSource creates KvSource // TLS support is enable for Consul and Etcd backends -func CreateKvSource(traefikConfiguration *server.TraefikConfiguration) (*staert.KvSource, error) { +func CreateKvSource(traefikConfiguration *TraefikConfiguration) (*staert.KvSource, error) { var kv *staert.KvSource - var store store.Store + var kvStore store.Store var err error switch { case traefikConfiguration.Consul != nil: - store, err = traefikConfiguration.Consul.CreateStore() + kvStore, err = traefikConfiguration.Consul.CreateStore() kv = &staert.KvSource{ - Store: store, + Store: kvStore, Prefix: traefikConfiguration.Consul.Prefix, } case traefikConfiguration.Etcd != nil: - store, err = traefikConfiguration.Etcd.CreateStore() + kvStore, err = traefikConfiguration.Etcd.CreateStore() kv = &staert.KvSource{ - Store: store, + Store: kvStore, Prefix: traefikConfiguration.Etcd.Prefix, } case traefikConfiguration.Zookeeper != nil: - store, err = traefikConfiguration.Zookeeper.CreateStore() + kvStore, err = traefikConfiguration.Zookeeper.CreateStore() kv = &staert.KvSource{ - Store: store, + Store: kvStore, Prefix: traefikConfiguration.Zookeeper.Prefix, } case traefikConfiguration.Boltdb != nil: - store, err = traefikConfiguration.Boltdb.CreateStore() + kvStore, err = traefikConfiguration.Boltdb.CreateStore() kv = &staert.KvSource{ - Store: store, + Store: kvStore, Prefix: traefikConfiguration.Boltdb.Prefix, } } diff --git a/server/configuration.go b/configuration/configuration.go similarity index 73% rename from server/configuration.go rename to configuration/configuration.go index c6cbd560a..2f3b6f02b 100644 --- a/server/configuration.go +++ b/configuration/configuration.go @@ -1,4 +1,4 @@ -package server +package configuration import ( "crypto/tls" @@ -11,7 +11,6 @@ import ( "github.com/containous/flaeg" "github.com/containous/traefik/acme" - "github.com/containous/traefik/middlewares/accesslog" "github.com/containous/traefik/provider/boltdb" "github.com/containous/traefik/provider/consul" "github.com/containous/traefik/provider/docker" @@ -24,6 +23,7 @@ import ( "github.com/containous/traefik/provider/marathon" "github.com/containous/traefik/provider/mesos" "github.com/containous/traefik/provider/rancher" + "github.com/containous/traefik/provider/web" "github.com/containous/traefik/provider/zk" "github.com/containous/traefik/types" ) @@ -34,16 +34,11 @@ const ( // DefaultDialTimeout when connecting to a backend server. DefaultDialTimeout = 30 * time.Second + // DefaultIdleTimeout before closing an idle connection. DefaultIdleTimeout = 180 * time.Second ) -// TraefikConfiguration holds GlobalConfiguration and other stuff -type TraefikConfiguration struct { - GlobalConfiguration `mapstructure:",squash"` - ConfigFile string `short:"c" description:"Configuration file to use (TOML)."` -} - // 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 { @@ -70,7 +65,7 @@ type GlobalConfiguration struct { ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers"` Docker *docker.Provider `description:"Enable Docker backend with default settings"` File *file.Provider `description:"Enable File backend with default settings"` - Web *WebProvider `description:"Enable Web backend with default settings"` + Web *web.Provider `description:"Enable Web backend with default settings"` Marathon *marathon.Provider `description:"Enable Marathon backend with default settings"` Consul *consul.Provider `description:"Enable Consul backend with default settings"` ConsulCatalog *consul.CatalogProvider `description:"Enable Consul catalog backend with default settings"` @@ -210,23 +205,23 @@ func (ep *EntryPoints) Set(value string) error { result[name] = matchResult[i] } } - var tls *TLS + var configTLS *TLS if len(result["TLS"]) > 0 { certs := Certificates{} if err := certs.Set(result["TLS"]); err != nil { return err } - tls = &TLS{ + configTLS = &TLS{ Certificates: certs, } } else if len(result["TLSACME"]) > 0 { - tls = &TLS{ + configTLS = &TLS{ Certificates: Certificates{}, } } if len(result["CA"]) > 0 { files := strings.Split(result["CA"], ",") - tls.ClientCAFiles = files + configTLS.ClientCAFiles = files } var redirect *Redirect if len(result["RedirectEntryPoint"]) > 0 || len(result["RedirectRegex"]) > 0 || len(result["RedirectReplacement"]) > 0 { @@ -249,7 +244,7 @@ func (ep *EntryPoints) Set(value string) error { (*ep)[result["Name"]] = &EntryPoint{ Address: result["Address"], - TLS: tls, + TLS: configTLS, Redirect: redirect, Compress: compress, WhitelistSourceRange: whiteListSourceRange, @@ -299,16 +294,16 @@ type TLS struct { ClientCAFiles []string } -// Map of allowed TLS minimum versions -var minVersion = map[string]uint16{ +// MinVersion Map of allowed TLS minimum versions +var MinVersion = map[string]uint16{ `VersionTLS10`: tls.VersionTLS10, `VersionTLS11`: tls.VersionTLS11, `VersionTLS12`: tls.VersionTLS12, } -// Map of TLS CipherSuites from crypto/tls +// CipherSuites Map of TLS CipherSuites from crypto/tls // Available CipherSuites defined at https://golang.org/pkg/crypto/tls/#pkg-constants -var cipherSuites = map[string]uint16{ +var CipherSuites = map[string]uint16{ `TLS_RSA_WITH_RC4_128_SHA`: tls.TLS_RSA_WITH_RC4_128_SHA, `TLS_RSA_WITH_3DES_EDE_CBC_SHA`: tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, `TLS_RSA_WITH_AES_128_CBC_SHA`: tls.TLS_RSA_WITH_AES_128_CBC_SHA, @@ -432,184 +427,3 @@ type ForwardingTimeouts struct { DialTimeout flaeg.Duration `description:"The amount of time to wait until a connection to a backend server can be established. Defaults to 30 seconds. If zero, no timeout exists"` ResponseHeaderTimeout flaeg.Duration `description:"The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists"` } - -// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values -func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { - //default Docker - var defaultDocker docker.Provider - defaultDocker.Watch = true - defaultDocker.ExposedByDefault = true - defaultDocker.Endpoint = "unix:///var/run/docker.sock" - defaultDocker.SwarmMode = false - - // default File - var defaultFile file.Provider - defaultFile.Watch = true - defaultFile.Filename = "" //needs equivalent to viper.ConfigFileUsed() - - // default Web - var defaultWeb WebProvider - defaultWeb.Address = ":8080" - defaultWeb.Statistics = &types.Statistics{ - RecentErrors: 10, - } - - // default Metrics - defaultWeb.Metrics = &types.Metrics{ - Prometheus: &types.Prometheus{ - Buckets: types.Buckets{0.1, 0.3, 1.2, 5}, - }, - Datadog: &types.Datadog{ - Address: "localhost:8125", - PushInterval: "10s", - }, - StatsD: &types.Statsd{ - Address: "localhost:8125", - PushInterval: "10s", - }, - } - - // default Marathon - var defaultMarathon marathon.Provider - defaultMarathon.Watch = true - defaultMarathon.Endpoint = "http://127.0.0.1:8080" - defaultMarathon.ExposedByDefault = true - defaultMarathon.Constraints = types.Constraints{} - defaultMarathon.DialerTimeout = flaeg.Duration(60 * time.Second) - defaultMarathon.KeepAlive = flaeg.Duration(10 * time.Second) - - // default Consul - var defaultConsul consul.Provider - defaultConsul.Watch = true - defaultConsul.Endpoint = "127.0.0.1:8500" - defaultConsul.Prefix = "traefik" - defaultConsul.Constraints = types.Constraints{} - - // default CatalogProvider - var defaultConsulCatalog consul.CatalogProvider - defaultConsulCatalog.Endpoint = "127.0.0.1:8500" - defaultConsulCatalog.Constraints = types.Constraints{} - defaultConsulCatalog.Prefix = "traefik" - defaultConsulCatalog.FrontEndRule = "Host:{{.ServiceName}}.{{.Domain}}" - - // default Etcd - var defaultEtcd etcd.Provider - defaultEtcd.Watch = true - defaultEtcd.Endpoint = "127.0.0.1:2379" - defaultEtcd.Prefix = "/traefik" - defaultEtcd.Constraints = types.Constraints{} - - //default Zookeeper - var defaultZookeeper zk.Provider - defaultZookeeper.Watch = true - defaultZookeeper.Endpoint = "127.0.0.1:2181" - defaultZookeeper.Prefix = "/traefik" - defaultZookeeper.Constraints = types.Constraints{} - - //default Boltdb - var defaultBoltDb boltdb.Provider - defaultBoltDb.Watch = true - defaultBoltDb.Endpoint = "127.0.0.1:4001" - defaultBoltDb.Prefix = "/traefik" - defaultBoltDb.Constraints = types.Constraints{} - - //default Kubernetes - var defaultKubernetes kubernetes.Provider - defaultKubernetes.Watch = true - defaultKubernetes.Endpoint = "" - defaultKubernetes.LabelSelector = "" - defaultKubernetes.Constraints = types.Constraints{} - - // default Mesos - var defaultMesos mesos.Provider - defaultMesos.Watch = true - defaultMesos.Endpoint = "http://127.0.0.1:5050" - defaultMesos.ExposedByDefault = true - defaultMesos.Constraints = types.Constraints{} - defaultMesos.RefreshSeconds = 30 - defaultMesos.ZkDetectionTimeout = 30 - defaultMesos.StateTimeoutSecond = 30 - - //default ECS - var defaultECS ecs.Provider - defaultECS.Watch = true - defaultECS.ExposedByDefault = true - defaultECS.AutoDiscoverClusters = false - defaultECS.Clusters = ecs.Clusters{"default"} - defaultECS.RefreshSeconds = 15 - defaultECS.Constraints = types.Constraints{} - - //default Rancher - var defaultRancher rancher.Provider - defaultRancher.Watch = true - defaultRancher.ExposedByDefault = true - defaultRancher.RefreshSeconds = 15 - - // default DynamoDB - var defaultDynamoDB dynamodb.Provider - defaultDynamoDB.Constraints = types.Constraints{} - defaultDynamoDB.RefreshSeconds = 15 - defaultDynamoDB.TableName = "traefik" - defaultDynamoDB.Watch = true - - // default AccessLog - defaultAccessLog := types.AccessLog{ - Format: accesslog.CommonFormat, - FilePath: "", - } - - defaultConfiguration := GlobalConfiguration{ - Docker: &defaultDocker, - File: &defaultFile, - Web: &defaultWeb, - Marathon: &defaultMarathon, - Consul: &defaultConsul, - ConsulCatalog: &defaultConsulCatalog, - Etcd: &defaultEtcd, - Zookeeper: &defaultZookeeper, - Boltdb: &defaultBoltDb, - Kubernetes: &defaultKubernetes, - Mesos: &defaultMesos, - ECS: &defaultECS, - Rancher: &defaultRancher, - DynamoDB: &defaultDynamoDB, - Retry: &Retry{}, - HealthCheck: &HealthCheckConfig{}, - AccessLog: &defaultAccessLog, - } - - return &TraefikConfiguration{ - GlobalConfiguration: defaultConfiguration, - } -} - -// NewTraefikConfiguration creates a TraefikConfiguration with default values -func NewTraefikConfiguration() *TraefikConfiguration { - return &TraefikConfiguration{ - GlobalConfiguration: GlobalConfiguration{ - GraceTimeOut: flaeg.Duration(10 * time.Second), - AccessLogsFile: "", - TraefikLogsFile: "", - LogLevel: "ERROR", - EntryPoints: map[string]*EntryPoint{}, - Constraints: types.Constraints{}, - DefaultEntryPoints: []string{}, - ProvidersThrottleDuration: flaeg.Duration(2 * time.Second), - MaxIdleConnsPerHost: 200, - IdleTimeout: flaeg.Duration(0), - HealthCheck: &HealthCheckConfig{ - Interval: flaeg.Duration(DefaultHealthCheckInterval), - }, - RespondingTimeouts: &RespondingTimeouts{ - IdleTimeout: flaeg.Duration(DefaultIdleTimeout), - }, - ForwardingTimeouts: &ForwardingTimeouts{ - DialTimeout: flaeg.Duration(DefaultDialTimeout), - }, - CheckNewVersion: true, - }, - ConfigFile: "", - } -} - -type configs map[string]*types.Configuration diff --git a/integration/log_rotation_test.go b/integration/log_rotation_test.go index 8ff02159e..5bf2b89c4 100644 --- a/integration/log_rotation_test.go +++ b/integration/log_rotation_test.go @@ -102,7 +102,7 @@ func (s *LogRotationSuite) TestTraefikLogRotation(c *check.C) { // GreaterOrEqualThan used to ensure test doesn't break // If more log entries are output on startup - c.Assert(lineCount, checker.GreaterOrEqualThan, 6) + c.Assert(lineCount, checker.GreaterOrEqualThan, 5) //Verify traefik.log output as expected lineCount = verifyLogLines(c, traefikTestLogFile, lineCount, false) diff --git a/server/web.go b/provider/web/web.go similarity index 74% rename from server/web.go rename to provider/web/web.go index 036a8acfd..035052d6f 100644 --- a/server/web.go +++ b/provider/web/web.go @@ -1,4 +1,4 @@ -package server +package web import ( "encoding/json" @@ -22,23 +22,20 @@ import ( "github.com/urfave/negroni" ) -var ( - stats = thoas_stats.New() - statsRecorder *middlewares.StatsRecorder -) - -// WebProvider is a provider.Provider implementation that provides the UI. -// FIXME to be handled another way. -type WebProvider struct { - Address string `description:"Web administration port"` - CertFile string `description:"SSL certificate"` - KeyFile string `description:"SSL certificate"` - ReadOnly bool `description:"Enable read only API"` - Statistics *types.Statistics `description:"Enable more detailed statistics"` - Metrics *types.Metrics `description:"Enable a metrics exporter"` - Path string `description:"Root path for dashboard and API"` - server *Server - Auth *types.Auth +// Provider is a provider.Provider implementation that provides the UI +type Provider struct { + Address string `description:"Web administration port"` + CertFile string `description:"SSL certificate"` + KeyFile string `description:"SSL certificate"` + ReadOnly bool `description:"Enable read only API"` + Statistics *types.Statistics `description:"Enable more detailed statistics"` + Metrics *types.Metrics `description:"Enable a metrics exporter"` + Path string `description:"Root path for dashboard and API"` + Auth *types.Auth + Debug bool + CurrentConfigurations *safe.Safe + Stats *thoas_stats.Stats + StatsRecorder *middlewares.StatsRecorder } var ( @@ -57,7 +54,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.Constraints) error { +func (provider *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ types.Constraints) error { systemRouter := mux.NewRouter() @@ -130,8 +127,8 @@ func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessag Handler(http.StripPrefix(provider.Path+"dashboard/", http.FileServer(&assetfs.AssetFS{Asset: autogen.Asset, AssetInfo: autogen.AssetInfo, AssetDir: autogen.AssetDir, Prefix: "static"}))) // expvars - if provider.server.globalConfiguration.Debug { - systemRouter.Methods("GET").Path(provider.Path + "debug/vars").HandlerFunc(expvarHandler) + if provider.Debug { + systemRouter.Methods("GET").Path(provider.Path + "debug/vars").HandlerFunc(expVarHandler) } safe.Go(func() { @@ -173,24 +170,24 @@ type healthResponse struct { *middlewares.Stats } -func (provider *WebProvider) getHealthHandler(response http.ResponseWriter, request *http.Request) { - health := &healthResponse{Data: stats.Data()} - if statsRecorder != nil { - health.Stats = statsRecorder.Data() +func (provider *Provider) getHealthHandler(response http.ResponseWriter, request *http.Request) { + health := &healthResponse{Data: provider.Stats.Data()} + if provider.StatsRecorder != nil { + health.Stats = provider.StatsRecorder.Data() } templatesRenderer.JSON(response, http.StatusOK, health) } -func (provider *WebProvider) getPingHandler(response http.ResponseWriter, request *http.Request) { +func (provider *Provider) getPingHandler(response http.ResponseWriter, request *http.Request) { fmt.Fprint(response, "OK") } -func (provider *WebProvider) getConfigHandler(response http.ResponseWriter, request *http.Request) { - currentConfigurations := provider.server.currentConfigurations.Get().(configs) +func (provider *Provider) getConfigHandler(response http.ResponseWriter, request *http.Request) { + currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations) templatesRenderer.JSON(response, http.StatusOK, currentConfigurations) } -func (provider *WebProvider) getVersionHandler(response http.ResponseWriter, request *http.Request) { +func (provider *Provider) getVersionHandler(response http.ResponseWriter, request *http.Request) { v := struct { Version string Codename string @@ -201,10 +198,10 @@ func (provider *WebProvider) getVersionHandler(response http.ResponseWriter, req templatesRenderer.JSON(response, http.StatusOK, v) } -func (provider *WebProvider) getProviderHandler(response http.ResponseWriter, request *http.Request) { +func (provider *Provider) getProviderHandler(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) providerID := vars["provider"] - currentConfigurations := provider.server.currentConfigurations.Get().(configs) + currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations) if provider, ok := currentConfigurations[providerID]; ok { templatesRenderer.JSON(response, http.StatusOK, provider) } else { @@ -212,10 +209,10 @@ func (provider *WebProvider) getProviderHandler(response http.ResponseWriter, re } } -func (provider *WebProvider) getBackendsHandler(response http.ResponseWriter, request *http.Request) { +func (provider *Provider) getBackendsHandler(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) providerID := vars["provider"] - currentConfigurations := provider.server.currentConfigurations.Get().(configs) + currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations) if provider, ok := currentConfigurations[providerID]; ok { templatesRenderer.JSON(response, http.StatusOK, provider.Backends) } else { @@ -223,11 +220,11 @@ func (provider *WebProvider) getBackendsHandler(response http.ResponseWriter, re } } -func (provider *WebProvider) getBackendHandler(response http.ResponseWriter, request *http.Request) { +func (provider *Provider) getBackendHandler(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) providerID := vars["provider"] backendID := vars["backend"] - currentConfigurations := provider.server.currentConfigurations.Get().(configs) + currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations) if provider, ok := currentConfigurations[providerID]; ok { if backend, ok := provider.Backends[backendID]; ok { templatesRenderer.JSON(response, http.StatusOK, backend) @@ -237,11 +234,11 @@ func (provider *WebProvider) getBackendHandler(response http.ResponseWriter, req http.NotFound(response, request) } -func (provider *WebProvider) getServersHandler(response http.ResponseWriter, request *http.Request) { +func (provider *Provider) getServersHandler(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) providerID := vars["provider"] backendID := vars["backend"] - currentConfigurations := provider.server.currentConfigurations.Get().(configs) + currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations) if provider, ok := currentConfigurations[providerID]; ok { if backend, ok := provider.Backends[backendID]; ok { templatesRenderer.JSON(response, http.StatusOK, backend.Servers) @@ -251,12 +248,12 @@ func (provider *WebProvider) getServersHandler(response http.ResponseWriter, req http.NotFound(response, request) } -func (provider *WebProvider) getServerHandler(response http.ResponseWriter, request *http.Request) { +func (provider *Provider) getServerHandler(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) providerID := vars["provider"] backendID := vars["backend"] serverID := vars["server"] - currentConfigurations := provider.server.currentConfigurations.Get().(configs) + currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations) if provider, ok := currentConfigurations[providerID]; ok { if backend, ok := provider.Backends[backendID]; ok { if server, ok := backend.Servers[serverID]; ok { @@ -268,10 +265,10 @@ func (provider *WebProvider) getServerHandler(response http.ResponseWriter, requ http.NotFound(response, request) } -func (provider *WebProvider) getFrontendsHandler(response http.ResponseWriter, request *http.Request) { +func (provider *Provider) getFrontendsHandler(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) providerID := vars["provider"] - currentConfigurations := provider.server.currentConfigurations.Get().(configs) + currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations) if provider, ok := currentConfigurations[providerID]; ok { templatesRenderer.JSON(response, http.StatusOK, provider.Frontends) } else { @@ -279,11 +276,11 @@ func (provider *WebProvider) getFrontendsHandler(response http.ResponseWriter, r } } -func (provider *WebProvider) getFrontendHandler(response http.ResponseWriter, request *http.Request) { +func (provider *Provider) getFrontendHandler(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) providerID := vars["provider"] frontendID := vars["frontend"] - currentConfigurations := provider.server.currentConfigurations.Get().(configs) + currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations) if provider, ok := currentConfigurations[providerID]; ok { if frontend, ok := provider.Frontends[frontendID]; ok { templatesRenderer.JSON(response, http.StatusOK, frontend) @@ -293,11 +290,11 @@ func (provider *WebProvider) getFrontendHandler(response http.ResponseWriter, re http.NotFound(response, request) } -func (provider *WebProvider) getRoutesHandler(response http.ResponseWriter, request *http.Request) { +func (provider *Provider) getRoutesHandler(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) providerID := vars["provider"] frontendID := vars["frontend"] - currentConfigurations := provider.server.currentConfigurations.Get().(configs) + currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations) if provider, ok := currentConfigurations[providerID]; ok { if frontend, ok := provider.Frontends[frontendID]; ok { templatesRenderer.JSON(response, http.StatusOK, frontend.Routes) @@ -307,13 +304,13 @@ func (provider *WebProvider) getRoutesHandler(response http.ResponseWriter, requ http.NotFound(response, request) } -func (provider *WebProvider) getRouteHandler(response http.ResponseWriter, request *http.Request) { +func (provider *Provider) getRouteHandler(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) providerID := vars["provider"] frontendID := vars["frontend"] routeID := vars["route"] - currentConfigurations := provider.server.currentConfigurations.Get().(configs) + currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations) if provider, ok := currentConfigurations[providerID]; ok { if frontend, ok := provider.Frontends[frontendID]; ok { if route, ok := frontend.Routes[routeID]; ok { @@ -325,7 +322,7 @@ func (provider *WebProvider) getRouteHandler(response http.ResponseWriter, reque http.NotFound(response, request) } -func expvarHandler(w http.ResponseWriter, r *http.Request) { +func expVarHandler(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") fmt.Fprint(w, "{\n") first := true diff --git a/server/server.go b/server/server.go index 191b32526..4c7faa44b 100644 --- a/server/server.go +++ b/server/server.go @@ -20,6 +20,7 @@ import ( "github.com/containous/mux" "github.com/containous/traefik/cluster" + "github.com/containous/traefik/configuration" "github.com/containous/traefik/healthcheck" "github.com/containous/traefik/log" "github.com/containous/traefik/metrics" @@ -29,6 +30,7 @@ import ( "github.com/containous/traefik/safe" "github.com/containous/traefik/types" "github.com/streamrail/concurrent-map" + thoas_stats "github.com/thoas/stats" "github.com/urfave/negroni" "github.com/vulcand/oxy/cbreaker" "github.com/vulcand/oxy/connlimit" @@ -38,7 +40,9 @@ import ( "golang.org/x/net/http2" ) -var oxyLogger = &OxyLogger{} +var ( + oxyLogger = &OxyLogger{} +) // Server is the reverse-proxy/load-balancer engine type Server struct { @@ -49,7 +53,7 @@ type Server struct { stopChan chan bool providers []provider.Provider currentConfigurations safe.Safe - globalConfiguration GlobalConfiguration + globalConfiguration configuration.GlobalConfiguration accessLoggerMiddleware *accesslog.LogHandler routinesPool *safe.Pool leadership *cluster.Leadership @@ -73,7 +77,7 @@ type serverRoute struct { } // NewServer returns an initialized Server. -func NewServer(globalConfiguration GlobalConfiguration) *Server { +func NewServer(globalConfiguration configuration.GlobalConfiguration) *Server { server := new(Server) server.serverEntryPoints = make(map[string]*serverEntryPoint) @@ -83,7 +87,7 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server { server.stopChan = make(chan bool, 1) server.providers = []provider.Provider{} server.configureSignals() - currentConfigurations := make(configs) + currentConfigurations := make(types.Configurations) server.currentConfigurations.Set(currentConfigurations) server.globalConfiguration = globalConfiguration server.routinesPool = safe.NewPool(context.Background()) @@ -118,7 +122,7 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server { // An exception to this is the MaxIdleConns setting as we only provide the option MaxIdleConnsPerHost // in Traefik at this point in time. Setting this value to the default of 100 could lead to confusing // behaviour and backwards compatibility issues. -func createHTTPTransport(globalConfiguration GlobalConfiguration) *http.Transport { +func createHTTPTransport(globalConfiguration configuration.GlobalConfiguration) *http.Transport { dialer := &net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, @@ -152,7 +156,7 @@ func createHTTPTransport(globalConfiguration GlobalConfiguration) *http.Transpor return transport } -func createRootCACertPool(rootCAs RootCAs) *x509.CertPool { +func createRootCACertPool(rootCAs configuration.RootCAs) *x509.CertPool { roots := x509.NewCertPool() for _, cert := range rootCAs { @@ -260,16 +264,20 @@ func (server *Server) startHTTPServers() { } func (server *Server) setupServerEntryPoint(newServerEntryPointName string, newServerEntryPoint *serverEntryPoint) *serverEntryPoint { - serverMiddlewares := []negroni.Handler{middlewares.NegroniRecoverHandler(), stats} + serverMiddlewares := []negroni.Handler{middlewares.NegroniRecoverHandler()} if server.accessLoggerMiddleware != nil { serverMiddlewares = append(serverMiddlewares, server.accessLoggerMiddleware) } if server.metricsRegistry.IsEnabled() { serverMiddlewares = append(serverMiddlewares, middlewares.NewMetricsWrapper(server.metricsRegistry, newServerEntryPointName)) } - if server.globalConfiguration.Web != nil && server.globalConfiguration.Web.Statistics != nil { - statsRecorder = middlewares.NewStatsRecorder(server.globalConfiguration.Web.Statistics.RecentErrors) - serverMiddlewares = append(serverMiddlewares, statsRecorder) + if server.globalConfiguration.Web != nil { + server.globalConfiguration.Web.Stats = thoas_stats.New() + serverMiddlewares = append(serverMiddlewares, server.globalConfiguration.Web.Stats) + if server.globalConfiguration.Web.Statistics != nil { + server.globalConfiguration.Web.StatsRecorder = middlewares.NewStatsRecorder(server.globalConfiguration.Web.Statistics.RecentErrors) + serverMiddlewares = append(serverMiddlewares, server.globalConfiguration.Web.StatsRecorder) + } } if server.globalConfiguration.EntryPoints[newServerEntryPointName].Auth != nil { authMiddleware, err := middlewares.NewAuthenticator(server.globalConfiguration.EntryPoints[newServerEntryPointName].Auth) @@ -288,12 +296,12 @@ func (server *Server) setupServerEntryPoint(newServerEntryPointName string, newS } serverMiddlewares = append(serverMiddlewares, ipWhitelistMiddleware) } - newsrv, err := server.prepareServer(newServerEntryPointName, server.globalConfiguration.EntryPoints[newServerEntryPointName], newServerEntryPoint.httpRouter, serverMiddlewares...) + newSrv, err := server.prepareServer(newServerEntryPointName, server.globalConfiguration.EntryPoints[newServerEntryPointName], newServerEntryPoint.httpRouter, serverMiddlewares...) if err != nil { log.Fatal("Error preparing server: ", err) } serverEntryPoint := server.serverEntryPoints[newServerEntryPointName] - serverEntryPoint.httpServer = newsrv + serverEntryPoint.httpServer = newSrv return serverEntryPoint } @@ -310,7 +318,7 @@ func (server *Server) listenProviders(stop chan bool) { return } server.defaultConfigurationValues(configMsg.Configuration) - currentConfigurations := server.currentConfigurations.Get().(configs) + currentConfigurations := server.currentConfigurations.Get().(types.Configurations) jsonConf, _ := json.Marshal(configMsg.Configuration) log.Debugf("Configuration received from provider %s: %s", configMsg.ProviderName, string(jsonConf)) if configMsg.Configuration == nil || configMsg.Configuration.Backends == nil && configMsg.Configuration.Frontends == nil { @@ -361,10 +369,10 @@ func (server *Server) listenConfigurations(stop chan bool) { if !ok { return } - currentConfigurations := server.currentConfigurations.Get().(configs) + currentConfigurations := server.currentConfigurations.Get().(types.Configurations) // Copy configurations to new map so we don't change current if LoadConfig fails - newConfigurations := make(configs) + newConfigurations := make(types.Configurations) for k, v := range currentConfigurations { newConfigurations[k] = v } @@ -393,9 +401,9 @@ func (server *Server) postLoadConfig() { return } if server.globalConfiguration.ACME.OnHostRule { - currentConfigurations := server.currentConfigurations.Get().(configs) - for _, configuration := range currentConfigurations { - for _, frontend := range configuration.Frontends { + currentConfigurations := server.currentConfigurations.Get().(types.Configurations) + for _, config := range currentConfigurations { + for _, frontend := range config.Frontends { // check if one of the frontend entrypoints is configured with TLS // and is configured with ACME @@ -435,7 +443,8 @@ func (server *Server) configureProviders() { server.providers = append(server.providers, server.globalConfiguration.File) } if server.globalConfiguration.Web != nil { - server.globalConfiguration.Web.server = server + server.globalConfiguration.Web.CurrentConfigurations = &server.currentConfigurations + server.globalConfiguration.Web.Debug = server.globalConfiguration.Debug server.providers = append(server.providers, server.globalConfiguration.Web) } if server.globalConfiguration.Consul != nil { @@ -475,11 +484,11 @@ func (server *Server) configureProviders() { func (server *Server) startProviders() { // start providers - for _, provider := range server.providers { - providerType := reflect.TypeOf(provider) - jsonConf, _ := json.Marshal(provider) + for _, p := range server.providers { + providerType := reflect.TypeOf(p) + jsonConf, _ := json.Marshal(p) log.Infof("Starting provider %v %s", providerType, jsonConf) - currentProvider := provider + currentProvider := p safe.Go(func() { err := currentProvider.Provide(server.configurationChan, server.routinesPool, server.globalConfiguration.Constraints) if err != nil { @@ -489,7 +498,7 @@ func (server *Server) startProviders() { } } -func createClientTLSConfig(tlsOption *TLS) (*tls.Config, error) { +func createClientTLSConfig(tlsOption *configuration.TLS) (*tls.Config, error) { if tlsOption == nil { return nil, errors.New("no TLS provided") } @@ -517,7 +526,7 @@ func createClientTLSConfig(tlsOption *TLS) (*tls.Config, error) { } // creates a TLS config that allows terminating HTTPS for multiple domains using SNI -func (server *Server) createTLSConfig(entryPointName string, tlsOption *TLS, router *middlewares.HandlerSwitcher) (*tls.Config, error) { +func (server *Server) createTLSConfig(entryPointName string, tlsOption *configuration.TLS, router *middlewares.HandlerSwitcher) (*tls.Config, error) { if tlsOption == nil { return nil, nil } @@ -581,7 +590,7 @@ func (server *Server) createTLSConfig(entryPointName string, tlsOption *TLS, rou // in each certificate and populates the config.NameToCertificate map. config.BuildNameToCertificate() //Set the minimum TLS version if set in the config TOML - if minConst, exists := minVersion[server.globalConfiguration.EntryPoints[entryPointName].TLS.MinVersion]; exists { + if minConst, exists := configuration.MinVersion[server.globalConfiguration.EntryPoints[entryPointName].TLS.MinVersion]; exists { config.PreferServerCipherSuites = true config.MinVersion = minConst } @@ -590,7 +599,7 @@ func (server *Server) createTLSConfig(entryPointName string, tlsOption *TLS, rou //if our list of CipherSuites is defined in the entrypoint config, we can re-initilize the suites list as empty config.CipherSuites = make([]uint16, 0) for _, cipher := range server.globalConfiguration.EntryPoints[entryPointName].TLS.CipherSuites { - if cipherConst, exists := cipherSuites[cipher]; exists { + if cipherConst, exists := configuration.CipherSuites[cipher]; exists { config.CipherSuites = append(config.CipherSuites, cipherConst) } else { //CipherSuite listed in the toml does not exist in our listed @@ -602,7 +611,7 @@ func (server *Server) createTLSConfig(entryPointName string, tlsOption *TLS, rou return config, nil } -func (server *Server) startServer(srv *http.Server, globalConfiguration GlobalConfiguration) { +func (server *Server) startServer(srv *http.Server, globalConfiguration configuration.GlobalConfiguration) { log.Infof("Starting server on %s", srv.Addr) var err error if srv.TLSConfig != nil { @@ -615,7 +624,7 @@ func (server *Server) startServer(srv *http.Server, globalConfiguration GlobalCo } } -func (server *Server) prepareServer(entryPointName string, entryPoint *EntryPoint, router *middlewares.HandlerSwitcher, middlewares ...negroni.Handler) (*http.Server, error) { +func (server *Server) prepareServer(entryPointName string, entryPoint *configuration.EntryPoint, router *middlewares.HandlerSwitcher, middlewares ...negroni.Handler) (*http.Server, error) { readTimeout, writeTimeout, idleTimeout := buildServerTimeouts(server.globalConfiguration) log.Infof("Preparing server %s %+v with readTimeout=%s writeTimeout=%s idleTimeout=%s", entryPointName, entryPoint, readTimeout, writeTimeout, idleTimeout) @@ -642,7 +651,7 @@ func (server *Server) prepareServer(entryPointName string, entryPoint *EntryPoin }, nil } -func buildServerTimeouts(globalConfig GlobalConfiguration) (readTimeout, writeTimeout, idleTimeout time.Duration) { +func buildServerTimeouts(globalConfig configuration.GlobalConfiguration) (readTimeout, writeTimeout, idleTimeout time.Duration) { readTimeout = time.Duration(0) writeTimeout = time.Duration(0) if globalConfig.RespondingTimeouts != nil { @@ -658,13 +667,13 @@ func buildServerTimeouts(globalConfig GlobalConfiguration) (readTimeout, writeTi idleTimeout = time.Duration(globalConfig.IdleTimeout) } else { // Default value if neither the deprecated IdleTimeout nor the new RespondingTimeouts.IdleTimout are configured - idleTimeout = time.Duration(DefaultIdleTimeout) + idleTimeout = time.Duration(configuration.DefaultIdleTimeout) } return readTimeout, writeTimeout, idleTimeout } -func (server *Server) buildEntryPoints(globalConfiguration GlobalConfiguration) map[string]*serverEntryPoint { +func (server *Server) buildEntryPoints(globalConfiguration configuration.GlobalConfiguration) map[string]*serverEntryPoint { serverEntryPoints := make(map[string]*serverEntryPoint) for entryPointName := range globalConfiguration.EntryPoints { router := server.buildDefaultHTTPRouter() @@ -677,7 +686,7 @@ func (server *Server) buildEntryPoints(globalConfiguration GlobalConfiguration) // getRoundTripper will either use server.defaultForwardingRoundTripper or create a new one // given a custom TLS configuration is passed and the passTLSCert option is set to true. -func (server *Server) getRoundTripper(globalConfiguration GlobalConfiguration, passTLSCert bool, tls *TLS) (http.RoundTripper, error) { +func (server *Server) getRoundTripper(globalConfiguration configuration.GlobalConfiguration, passTLSCert bool, tls *configuration.TLS) (http.RoundTripper, error) { if passTLSCert { tlsConfig, err := createClientTLSConfig(tls) if err != nil { @@ -695,18 +704,18 @@ func (server *Server) getRoundTripper(globalConfiguration GlobalConfiguration, p // LoadConfig returns a new gorilla.mux Route from the specified global configuration and the dynamic // provider configurations. -func (server *Server) loadConfig(configurations configs, globalConfiguration GlobalConfiguration) (map[string]*serverEntryPoint, error) { +func (server *Server) loadConfig(configurations types.Configurations, globalConfiguration configuration.GlobalConfiguration) (map[string]*serverEntryPoint, error) { serverEntryPoints := server.buildEntryPoints(globalConfiguration) redirectHandlers := make(map[string]negroni.Handler) backends := map[string]http.Handler{} - backendsHealthcheck := map[string]*healthcheck.BackendHealthCheck{} + backendsHealthCheck := map[string]*healthcheck.BackendHealthCheck{} errorHandler := NewRecordingErrorHandler(middlewares.DefaultNetErrorRecorder{}) - for _, configuration := range configurations { - frontendNames := sortedFrontendNamesForConfig(configuration) + for _, config := range configurations { + frontendNames := sortedFrontendNamesForConfig(config) frontend: for _, frontendName := range frontendNames { - frontend := configuration.Frontends[frontendName] + frontend := config.Frontends[frontendName] log.Debugf("Creating frontend %s", frontendName) @@ -736,10 +745,10 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo } entryPoint := globalConfiguration.EntryPoints[entryPointName] - negroni := negroni.New() + n := negroni.New() if entryPoint.Redirect != nil { if redirectHandlers[entryPointName] != nil { - negroni.Use(redirectHandlers[entryPointName]) + n.Use(redirectHandlers[entryPointName]) } else if handler, err := server.loadEntryPointConfig(entryPointName, entryPoint); err != nil { log.Errorf("Error loading entrypoint configuration for frontend %s: %v", frontendName, err) log.Errorf("Skipping frontend %s...", frontendName) @@ -747,10 +756,10 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo } else { if server.accessLoggerMiddleware != nil { saveFrontend := accesslog.NewSaveNegroniFrontend(handler, frontendName) - negroni.Use(saveFrontend) + n.Use(saveFrontend) redirectHandlers[entryPointName] = saveFrontend } else { - negroni.Use(handler) + n.Use(handler) redirectHandlers[entryPointName] = handler } } @@ -788,25 +797,25 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo rr, _ = roundrobin.New(fwd) } - if configuration.Backends[frontend.Backend] == nil { + if config.Backends[frontend.Backend] == nil { log.Errorf("Undefined backend '%s' for frontend %s", frontend.Backend, frontendName) log.Errorf("Skipping frontend %s...", frontendName) continue frontend } - lbMethod, err := types.NewLoadBalancerMethod(configuration.Backends[frontend.Backend].LoadBalancer) + lbMethod, err := types.NewLoadBalancerMethod(config.Backends[frontend.Backend].LoadBalancer) if err != nil { - log.Errorf("Error loading load balancer method '%+v' for frontend %s: %v", configuration.Backends[frontend.Backend].LoadBalancer, frontendName, err) + log.Errorf("Error loading load balancer method '%+v' for frontend %s: %v", config.Backends[frontend.Backend].LoadBalancer, frontendName, err) log.Errorf("Skipping frontend %s...", frontendName) continue frontend } - stickysession := configuration.Backends[frontend.Backend].LoadBalancer.Sticky - cookiename := "_TRAEFIK_BACKEND_" + frontend.Backend + stickySession := config.Backends[frontend.Backend].LoadBalancer.Sticky + cookieName := "_TRAEFIK_BACKEND_" + frontend.Backend var sticky *roundrobin.StickySession - if stickysession { - sticky = roundrobin.NewStickySession(cookiename) + if stickySession { + sticky = roundrobin.NewStickySession(cookieName) } var lb http.Handler @@ -814,25 +823,25 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo case types.Drr: log.Debugf("Creating load-balancer drr") rebalancer, _ := roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger)) - if stickysession { - log.Debugf("Sticky session with cookie %v", cookiename) + if stickySession { + log.Debugf("Sticky session with cookie %v", cookieName) rebalancer, _ = roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger), roundrobin.RebalancerStickySession(sticky)) } lb = rebalancer - if err := configureLBServers(rebalancer, configuration, frontend); err != nil { + if err := configureLBServers(rebalancer, config, frontend); err != nil { log.Errorf("Skipping frontend %s...", frontendName) continue frontend } - hcOpts := parseHealthCheckOptions(rebalancer, frontend.Backend, configuration.Backends[frontend.Backend].HealthCheck, globalConfiguration.HealthCheck) + hcOpts := parseHealthCheckOptions(rebalancer, frontend.Backend, config.Backends[frontend.Backend].HealthCheck, globalConfiguration.HealthCheck) if hcOpts != nil { log.Debugf("Setting up backend health check %s", *hcOpts) - backendsHealthcheck[entryPointName+frontend.Backend] = healthcheck.NewBackendHealthCheck(*hcOpts) + backendsHealthCheck[entryPointName+frontend.Backend] = healthcheck.NewBackendHealthCheck(*hcOpts) } lb = middlewares.NewEmptyBackendHandler(rebalancer, lb) case types.Wrr: log.Debugf("Creating load-balancer wrr") - if stickysession { - log.Debugf("Sticky session with cookie %v", cookiename) + if stickySession { + log.Debugf("Sticky session with cookie %v", cookieName) if server.accessLoggerMiddleware != nil { rr, _ = roundrobin.New(saveFrontend, roundrobin.EnableStickySession(sticky)) } else { @@ -840,26 +849,26 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo } } lb = rr - if err := configureLBServers(rr, configuration, frontend); err != nil { + if err := configureLBServers(rr, config, frontend); err != nil { log.Errorf("Skipping frontend %s...", frontendName) continue frontend } - hcOpts := parseHealthCheckOptions(rr, frontend.Backend, configuration.Backends[frontend.Backend].HealthCheck, globalConfiguration.HealthCheck) + hcOpts := parseHealthCheckOptions(rr, frontend.Backend, config.Backends[frontend.Backend].HealthCheck, globalConfiguration.HealthCheck) if hcOpts != nil { log.Debugf("Setting up backend health check %s", *hcOpts) - backendsHealthcheck[entryPointName+frontend.Backend] = healthcheck.NewBackendHealthCheck(*hcOpts) + backendsHealthCheck[entryPointName+frontend.Backend] = healthcheck.NewBackendHealthCheck(*hcOpts) } lb = middlewares.NewEmptyBackendHandler(rr, lb) } if len(frontend.Errors) > 0 { for _, errorPage := range frontend.Errors { - if configuration.Backends[errorPage.Backend] != nil && configuration.Backends[errorPage.Backend].Servers["error"].URL != "" { - errorPageHandler, err := middlewares.NewErrorPagesHandler(errorPage, configuration.Backends[errorPage.Backend].Servers["error"].URL) + if config.Backends[errorPage.Backend] != nil && config.Backends[errorPage.Backend].Servers["error"].URL != "" { + errorPageHandler, err := middlewares.NewErrorPagesHandler(errorPage, config.Backends[errorPage.Backend].Servers["error"].URL) if err != nil { log.Errorf("Error creating custom error page middleware, %v", err) } else { - negroni.Use(errorPageHandler) + n.Use(errorPageHandler) } } else { log.Errorf("Error Page is configured for Frontend %s, but either Backend %s is not set or Backend URL is missing", frontendName, errorPage.Backend) @@ -867,7 +876,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo } } - maxConns := configuration.Backends[frontend.Backend].MaxConn + maxConns := config.Backends[frontend.Backend].MaxConn if maxConns != nil && maxConns.Amount != 0 { extractFunc, err := utils.NewExtractor(maxConns.ExtractorFunc) if err != nil { @@ -886,17 +895,17 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo if globalConfiguration.Retry != nil { retryListener := middlewares.NewMetricsRetryListener(server.metricsRegistry, frontend.Backend) - lb = registerRetryMiddleware(lb, globalConfiguration, configuration, frontend.Backend, retryListener) + lb = registerRetryMiddleware(lb, globalConfiguration, config, frontend.Backend, retryListener) } if server.metricsRegistry.IsEnabled() { - negroni.Use(middlewares.NewMetricsWrapper(server.metricsRegistry, frontend.Backend)) + n.Use(middlewares.NewMetricsWrapper(server.metricsRegistry, frontend.Backend)) } ipWhitelistMiddleware, err := configureIPWhitelistMiddleware(frontend.WhitelistSourceRange) if err != nil { log.Fatalf("Error creating IP Whitelister: %s", err) } else if ipWhitelistMiddleware != nil { - negroni.Use(ipWhitelistMiddleware) + n.Use(ipWhitelistMiddleware) log.Infof("Configured IP Whitelists: %s", frontend.WhitelistSourceRange) } @@ -914,34 +923,34 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo if err != nil { log.Errorf("Error creating Auth: %s", err) } else { - negroni.Use(authMiddleware) + n.Use(authMiddleware) } } if frontend.Headers.HasCustomHeadersDefined() { headerMiddleware := middlewares.NewHeaderFromStruct(frontend.Headers) log.Debugf("Adding header middleware for frontend %s", frontendName) - negroni.Use(headerMiddleware) + n.Use(headerMiddleware) } if frontend.Headers.HasSecureHeadersDefined() { secureMiddleware := middlewares.NewSecure(frontend.Headers) log.Debugf("Adding secure middleware for frontend %s", frontendName) - negroni.UseFunc(secureMiddleware.HandlerFuncWithNext) + n.UseFunc(secureMiddleware.HandlerFuncWithNext) } - if configuration.Backends[frontend.Backend].CircuitBreaker != nil { - log.Debugf("Creating circuit breaker %s", configuration.Backends[frontend.Backend].CircuitBreaker.Expression) - cbreaker, err := middlewares.NewCircuitBreaker(lb, configuration.Backends[frontend.Backend].CircuitBreaker.Expression, cbreaker.Logger(oxyLogger)) + if config.Backends[frontend.Backend].CircuitBreaker != nil { + log.Debugf("Creating circuit breaker %s", config.Backends[frontend.Backend].CircuitBreaker.Expression) + circuitBreaker, err := middlewares.NewCircuitBreaker(lb, config.Backends[frontend.Backend].CircuitBreaker.Expression, cbreaker.Logger(oxyLogger)) if err != nil { log.Errorf("Error creating circuit breaker: %v", err) log.Errorf("Skipping frontend %s...", frontendName) continue frontend } - negroni.Use(cbreaker) + n.Use(circuitBreaker) } else { - negroni.UseHandler(lb) + n.UseHandler(lb) } - backends[entryPointName+frontend.Backend] = negroni + backends[entryPointName+frontend.Backend] = n } else { log.Debugf("Reusing backend %s", frontend.Backend) } @@ -957,7 +966,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo } } } - healthcheck.GetHealthCheck().SetBackendsConfiguration(server.routinesPool.Ctx(), backendsHealthcheck) + healthcheck.GetHealthCheck().SetBackendsConfiguration(server.routinesPool.Ctx(), backendsHealthCheck) //sort routes for _, serverEntryPoint := range serverEntryPoints { serverEntryPoint.httpRouter.GetHandler().SortRoutes() @@ -1031,7 +1040,7 @@ func (server *Server) wireFrontendBackend(serverRoute *serverRoute, handler http serverRoute.route.Handler(handler) } -func (server *Server) loadEntryPointConfig(entryPointName string, entryPoint *EntryPoint) (negroni.Handler, error) { +func (server *Server) loadEntryPointConfig(entryPointName string, entryPoint *configuration.EntryPoint) (negroni.Handler, error) { regex := entryPoint.Redirect.Regex replacement := entryPoint.Redirect.Replacement if len(entryPoint.Redirect.EntryPoint) > 0 { @@ -1067,7 +1076,7 @@ func (server *Server) buildDefaultHTTPRouter() *mux.Router { return router } -func parseHealthCheckOptions(lb healthcheck.LoadBalancer, backend string, hc *types.HealthCheck, hcConfig *HealthCheckConfig) *healthcheck.Options { +func parseHealthCheckOptions(lb healthcheck.LoadBalancer, backend string, hc *types.HealthCheck, hcConfig *configuration.HealthCheckConfig) *healthcheck.Options { if hc == nil || hc.Path == "" || hcConfig == nil { return nil } @@ -1166,7 +1175,7 @@ func stopMetricsClients() { func registerRetryMiddleware( httpHandler http.Handler, - globalConfig GlobalConfiguration, + globalConfig configuration.GlobalConfiguration, config *types.Configuration, backend string, listener middlewares.RetryListener, diff --git a/server/server_test.go b/server/server_test.go index 22498b762..78c2e2ee4 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -11,6 +11,7 @@ import ( "github.com/containous/flaeg" "github.com/containous/mux" + "github.com/containous/traefik/configuration" "github.com/containous/traefik/healthcheck" "github.com/containous/traefik/metrics" "github.com/containous/traefik/middlewares" @@ -40,15 +41,15 @@ func (lb *testLoadBalancer) Servers() []*url.URL { func TestPrepareServerTimeouts(t *testing.T) { tests := []struct { desc string - globalConfig GlobalConfiguration + globalConfig configuration.GlobalConfiguration wantIdleTimeout time.Duration wantReadTimeout time.Duration wantWriteTimeout time.Duration }{ { desc: "full configuration", - globalConfig: GlobalConfiguration{ - RespondingTimeouts: &RespondingTimeouts{ + globalConfig: configuration.GlobalConfiguration{ + RespondingTimeouts: &configuration.RespondingTimeouts{ IdleTimeout: flaeg.Duration(10 * time.Second), ReadTimeout: flaeg.Duration(12 * time.Second), WriteTimeout: flaeg.Duration(14 * time.Second), @@ -60,14 +61,14 @@ func TestPrepareServerTimeouts(t *testing.T) { }, { desc: "using defaults", - globalConfig: GlobalConfiguration{}, + globalConfig: configuration.GlobalConfiguration{}, wantIdleTimeout: time.Duration(180 * time.Second), wantReadTimeout: time.Duration(0 * time.Second), wantWriteTimeout: time.Duration(0 * time.Second), }, { desc: "deprecated IdleTimeout configured", - globalConfig: GlobalConfiguration{ + globalConfig: configuration.GlobalConfiguration{ IdleTimeout: flaeg.Duration(45 * time.Second), }, wantIdleTimeout: time.Duration(45 * time.Second), @@ -76,9 +77,9 @@ func TestPrepareServerTimeouts(t *testing.T) { }, { desc: "deprecated and new IdleTimeout configured", - globalConfig: GlobalConfiguration{ + globalConfig: configuration.GlobalConfiguration{ IdleTimeout: flaeg.Duration(45 * time.Second), - RespondingTimeouts: &RespondingTimeouts{ + RespondingTimeouts: &configuration.RespondingTimeouts{ IdleTimeout: flaeg.Duration(80 * time.Second), }, }, @@ -95,7 +96,7 @@ func TestPrepareServerTimeouts(t *testing.T) { t.Parallel() entryPointName := "http" - entryPoint := &EntryPoint{Address: "localhost:8080"} + entryPoint := &configuration.EntryPoint{Address: "localhost:8080"} router := middlewares.NewHandlerSwitcher(mux.NewRouter()) srv := NewServer(test.globalConfig) @@ -207,14 +208,14 @@ func TestServerLoadConfigHealthCheckOptions(t *testing.T) { for _, lbMethod := range []string{"Wrr", "Drr"} { for _, healthCheck := range healthChecks { t.Run(fmt.Sprintf("%s/hc=%t", lbMethod, healthCheck != nil), func(t *testing.T) { - globalConfig := GlobalConfiguration{ - EntryPoints: EntryPoints{ - "http": &EntryPoint{}, + globalConfig := configuration.GlobalConfiguration{ + EntryPoints: configuration.EntryPoints{ + "http": &configuration.EntryPoint{}, }, - HealthCheck: &HealthCheckConfig{Interval: flaeg.Duration(5 * time.Second)}, + HealthCheck: &configuration.HealthCheckConfig{Interval: flaeg.Duration(5 * time.Second)}, } - dynamicConfigs := configs{ + dynamicConfigs := types.Configurations{ "config": &types.Configuration{ Frontends: map[string]*types.Frontend{ "frontend": { @@ -320,7 +321,7 @@ func TestServerParseHealthCheckOptions(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - gotOpts := parseHealthCheckOptions(lb, "backend", test.hc, &HealthCheckConfig{Interval: flaeg.Duration(globalInterval)}) + gotOpts := parseHealthCheckOptions(lb, "backend", test.hc, &configuration.HealthCheckConfig{Interval: flaeg.Duration(globalInterval)}) if !reflect.DeepEqual(gotOpts, test.wantOpts) { t.Errorf("got health check options %+v, want %+v", gotOpts, test.wantOpts) } @@ -380,13 +381,13 @@ func TestNewServerWithWhitelistSourceRange(t *testing.T) { } func TestServerLoadConfigEmptyBasicAuth(t *testing.T) { - globalConfig := GlobalConfiguration{ - EntryPoints: EntryPoints{ - "http": &EntryPoint{}, + globalConfig := configuration.GlobalConfiguration{ + EntryPoints: configuration.EntryPoints{ + "http": &configuration.EntryPoint{}, }, } - dynamicConfigs := configs{ + dynamicConfigs := types.Configurations{ "config": &types.Configuration{ Frontends: map[string]*types.Frontend{ "frontend": { @@ -497,14 +498,14 @@ func TestConfigureBackends(t *testing.T) { func TestRegisterRetryMiddleware(t *testing.T) { testCases := []struct { name string - globalConfig GlobalConfiguration + globalConfig configuration.GlobalConfiguration countServers int expectedRetries int }{ { name: "configured retry attempts", - globalConfig: GlobalConfiguration{ - Retry: &Retry{ + globalConfig: configuration.GlobalConfiguration{ + Retry: &configuration.Retry{ Attempts: 3, }, }, @@ -512,8 +513,8 @@ func TestRegisterRetryMiddleware(t *testing.T) { }, { name: "retry attempts defaults to server amount", - globalConfig: GlobalConfiguration{ - Retry: &Retry{}, + globalConfig: configuration.GlobalConfiguration{ + Retry: &configuration.Retry{}, }, expectedRetries: 2, }, @@ -565,19 +566,19 @@ func (okHTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func TestServerEntrypointWhitelistConfig(t *testing.T) { tests := []struct { desc string - entrypoint *EntryPoint + entrypoint *configuration.EntryPoint wantMiddleware bool }{ { desc: "no whitelist middleware if no config on entrypoint", - entrypoint: &EntryPoint{ + entrypoint: &configuration.EntryPoint{ Address: ":8080", }, wantMiddleware: false, }, { desc: "whitelist middleware should be added if configured on entrypoint", - entrypoint: &EntryPoint{ + entrypoint: &configuration.EntryPoint{ Address: ":8080", WhitelistSourceRange: []string{ "127.0.0.1/32", @@ -593,8 +594,8 @@ func TestServerEntrypointWhitelistConfig(t *testing.T) { t.Parallel() srv := Server{ - globalConfiguration: GlobalConfiguration{ - EntryPoints: map[string]*EntryPoint{ + globalConfiguration: configuration.GlobalConfiguration{ + EntryPoints: map[string]*configuration.EntryPoint{ "test": test.entrypoint, }, }, @@ -701,12 +702,12 @@ func TestServerResponseEmptyBackend(t *testing.T) { })) defer testServer.Close() - globalConfig := GlobalConfiguration{ - EntryPoints: EntryPoints{ - "http": &EntryPoint{}, + globalConfig := configuration.GlobalConfiguration{ + EntryPoints: configuration.EntryPoints{ + "http": &configuration.EntryPoint{}, }, } - dynamicConfigs := configs{"config": test.dynamicConfig(testServer.URL)} + dynamicConfigs := types.Configurations{"config": test.dynamicConfig(testServer.URL)} srv := NewServer(globalConfig) entryPoints, err := srv.loadConfig(dynamicConfigs, globalConfig) diff --git a/types/types.go b/types/types.go index b62500806..1c511a0b6 100644 --- a/types/types.go +++ b/types/types.go @@ -156,6 +156,9 @@ func NewLoadBalancerMethod(loadBalancer *LoadBalancer) (LoadBalancerMethod, erro return Wrr, fmt.Errorf("invalid load-balancing method '%s'", method) } +// Configurations is for currentConfigurations Map +type Configurations map[string]*Configuration + // Configuration of a provider. type Configuration struct { Backends map[string]*Backend `json:"backends,omitempty"`