From a0b15a0efdc8be79c41ce9bee38d8571835da2a1 Mon Sep 17 00:00:00 2001 From: emile Date: Wed, 13 Jan 2016 22:45:49 +0100 Subject: [PATCH 1/4] Main file refactoring, extract a Server object --- adapters.go | 2 +- glide.yaml | 4 + server.go | 369 ++++++++++++++++++++++++++++++++++++++++++++++++++++ traefik.go | 325 +-------------------------------------------- web.go | 69 +++++----- 5 files changed, 416 insertions(+), 353 deletions(-) create mode 100644 server.go diff --git a/adapters.go b/adapters.go index bebe4e7d1..dcd7e5f84 100644 --- a/adapters.go +++ b/adapters.go @@ -35,7 +35,7 @@ func notFoundHandler(w http.ResponseWriter, r *http.Request) { } // LoadDefaultConfig returns a default gorrilla.mux router from the specified configuration. -func LoadDefaultConfig(globalConfiguration *GlobalConfiguration) *mux.Router { +func LoadDefaultConfig(globalConfiguration GlobalConfiguration) *mux.Router { router := mux.NewRouter() router.NotFoundHandler = http.HandlerFunc(notFoundHandler) return router diff --git a/glide.yaml b/glide.yaml index fd31cc94f..65400739f 100644 --- a/glide.yaml +++ b/glide.yaml @@ -146,4 +146,8 @@ import: ref: d8a3071799b98cacd30b6da92f536050ccfe6da4 - package: github.com/golang/glog ref: fca8c8854093a154ff1eb580aae10276ad6b1b5f + - package: github.com/spf13/cast + version: ee7b3e0353166ab1f3a605294ac8cd2b77953778 + - package: github.com/spf13/viper + version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325 diff --git a/server.go b/server.go new file mode 100644 index 000000000..d273d3d43 --- /dev/null +++ b/server.go @@ -0,0 +1,369 @@ +/* +Copyright +*/ +package main + +import ( + "crypto/tls" + "errors" + log "github.com/Sirupsen/logrus" + "github.com/codegangsta/negroni" + "github.com/emilevauge/traefik/middlewares" + "github.com/emilevauge/traefik/provider" + "github.com/emilevauge/traefik/types" + "github.com/gorilla/mux" + "github.com/mailgun/manners" + "github.com/mailgun/oxy/cbreaker" + "github.com/mailgun/oxy/forward" + "github.com/mailgun/oxy/roundrobin" + "net/http" + "net/url" + "os" + "os/signal" + "reflect" + "sync" + "syscall" + "time" +) + +// Server is the reverse-proxy/load-balancer engine +type Server struct { + srv *manners.GracefulServer + configurationRouter *mux.Router + configurationChan chan types.ConfigMessage + configurationChanValidated chan types.ConfigMessage + sigs chan os.Signal + stopChan chan bool + providers []provider.Provider + serverLock sync.Mutex + currentConfigurations configs + globalConfiguration GlobalConfiguration + loggerMiddleware *middlewares.Logger +} + +// NewServer returns an initialized Server. +func NewServer(globalConfiguration GlobalConfiguration) *Server { + server := new(Server) + + server.configurationChan = make(chan types.ConfigMessage, 10) + server.configurationChanValidated = make(chan types.ConfigMessage, 10) + server.sigs = make(chan os.Signal, 1) + server.stopChan = make(chan bool) + server.providers = []provider.Provider{} + signal.Notify(server.sigs, syscall.SIGINT, syscall.SIGTERM) + server.currentConfigurations = make(configs) + server.globalConfiguration = globalConfiguration + server.loggerMiddleware = middlewares.NewLogger(globalConfiguration.AccessLogsFile) + + return server +} + +// Start starts the server and blocks until server is shutted down. +func (server *Server) Start() { + server.configurationRouter = LoadDefaultConfig(server.globalConfiguration) + go server.listenProviders() + go server.enableRouter() + server.configureProviders() + server.startProviders() + go server.listenSignals() + + var er error + server.serverLock.Lock() + server.srv, er = server.prepareServer(server.configurationRouter, server.globalConfiguration, nil, server.loggerMiddleware, metrics) + if er != nil { + log.Fatal("Error preparing server: ", er) + } + go server.startServer(server.srv, server.globalConfiguration) + //TODO change that! + time.Sleep(100 * time.Millisecond) + server.serverLock.Unlock() + + <-server.stopChan +} + +// Stop stops the server +func (server *Server) Stop() { + server.srv.Close() + server.stopChan <- true +} + +// Close destroys the server +func (server *Server) Close() { + defer close(server.configurationChan) + defer close(server.configurationChanValidated) + defer close(server.sigs) + defer close(server.stopChan) + defer server.loggerMiddleware.Close() +} + +func (server *Server) listenProviders() { + lastReceivedConfiguration := time.Unix(0, 0) + lastConfigs := make(map[string]*types.ConfigMessage) + for { + configMsg := <-server.configurationChan + log.Infof("Configuration receveived from provider %s: %#v", configMsg.ProviderName, configMsg.Configuration) + lastConfigs[configMsg.ProviderName] = &configMsg + if time.Now().After(lastReceivedConfiguration.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) { + log.Infof("Last %s config received more than %s, OK", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration) + // last config received more than n s ago + server.configurationChanValidated <- configMsg + } else { + log.Infof("Last %s config received less than %s, waiting...", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration) + go func() { + <-time.After(server.globalConfiguration.ProvidersThrottleDuration) + if time.Now().After(lastReceivedConfiguration.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) { + log.Infof("Waited for %s config, OK", configMsg.ProviderName) + server.configurationChanValidated <- *lastConfigs[configMsg.ProviderName] + } + }() + } + lastReceivedConfiguration = time.Now() + } +} + +func (server *Server) enableRouter() { + for { + configMsg := <-server.configurationChanValidated + if configMsg.Configuration == nil { + log.Info("Skipping empty Configuration") + } else if reflect.DeepEqual(server.currentConfigurations[configMsg.ProviderName], configMsg.Configuration) { + log.Info("Skipping same configuration") + } else { + // Copy configurations to new map so we don't change current if LoadConfig fails + newConfigurations := make(configs) + for k, v := range server.currentConfigurations { + newConfigurations[k] = v + } + newConfigurations[configMsg.ProviderName] = configMsg.Configuration + + newConfigurationRouter, err := server.loadConfig(newConfigurations, server.globalConfiguration) + if err == nil { + server.serverLock.Lock() + server.currentConfigurations = newConfigurations + server.configurationRouter = newConfigurationRouter + oldServer := server.srv + newsrv, err := server.prepareServer(server.configurationRouter, server.globalConfiguration, oldServer, server.loggerMiddleware, metrics) + if err != nil { + log.Fatal("Error preparing server: ", err) + } + go server.startServer(newsrv, server.globalConfiguration) + server.srv = newsrv + time.Sleep(1 * time.Second) + if oldServer != nil { + log.Info("Stopping old server") + oldServer.Close() + } + server.serverLock.Unlock() + } else { + log.Error("Error loading new configuration, aborted ", err) + } + } + } +} + +func (server *Server) configureProviders() { + // configure providers + if server.globalConfiguration.Docker != nil { + server.providers = append(server.providers, server.globalConfiguration.Docker) + } + if server.globalConfiguration.Marathon != nil { + server.providers = append(server.providers, server.globalConfiguration.Marathon) + } + if server.globalConfiguration.File != nil { + if len(server.globalConfiguration.File.Filename) == 0 { + // no filename, setting to global config file + server.globalConfiguration.File.Filename = *globalConfigFile + } + server.providers = append(server.providers, server.globalConfiguration.File) + } + if server.globalConfiguration.Web != nil { + server.globalConfiguration.Web.server = server + server.providers = append(server.providers, server.globalConfiguration.Web) + } + if server.globalConfiguration.Consul != nil { + server.providers = append(server.providers, server.globalConfiguration.Consul) + } + if server.globalConfiguration.Etcd != nil { + server.providers = append(server.providers, server.globalConfiguration.Etcd) + } + if server.globalConfiguration.Zookeeper != nil { + server.providers = append(server.providers, server.globalConfiguration.Zookeeper) + } + if server.globalConfiguration.Boltdb != nil { + server.providers = append(server.providers, server.globalConfiguration.Boltdb) + } +} + +func (server *Server) startProviders() { + // start providers + for _, provider := range server.providers { + log.Infof("Starting provider %v %+v", reflect.TypeOf(provider), provider) + currentProvider := provider + go func() { + err := currentProvider.Provide(server.configurationChan) + if err != nil { + log.Errorf("Error starting provider %s", err) + } + }() + } +} + +func (server *Server) listenSignals() { + sig := <-server.sigs + log.Infof("I have to go... %+v", sig) + log.Info("Stopping server") + server.Stop() +} + +// creates a TLS config that allows terminating HTTPS for multiple domains using SNI +func (server *Server) createTLSConfig(certs []Certificate) (*tls.Config, error) { + if len(certs) == 0 { + return nil, nil + } + + config := &tls.Config{} + if config.NextProtos == nil { + config.NextProtos = []string{"http/1.1"} + } + + var err error + config.Certificates = make([]tls.Certificate, len(certs)) + for i, v := range certs { + config.Certificates[i], err = tls.LoadX509KeyPair(v.CertFile, v.KeyFile) + if err != nil { + return nil, err + } + } + // BuildNameToCertificate parses the CommonName and SubjectAlternateName fields + // in each certificate and populates the config.NameToCertificate map. + config.BuildNameToCertificate() + return config, nil +} + +func (server *Server) startServer(srv *manners.GracefulServer, globalConfiguration GlobalConfiguration) { + log.Info("Starting server") + if srv.TLSConfig != nil { + err := srv.ListenAndServeTLSWithConfig(srv.TLSConfig) + if err != nil { + log.Fatal("Error creating server: ", err) + } + } else { + err := srv.ListenAndServe() + if err != nil { + log.Fatal("Error creating server: ", err) + } + } + log.Info("Server stopped") +} + +func (server *Server) prepareServer(router *mux.Router, globalConfiguration GlobalConfiguration, oldServer *manners.GracefulServer, middlewares ...negroni.Handler) (*manners.GracefulServer, error) { + log.Info("Preparing server") + // middlewares + var negroni = negroni.New() + for _, middleware := range middlewares { + negroni.Use(middleware) + } + negroni.UseHandler(router) + tlsConfig, err := server.createTLSConfig(globalConfiguration.Certificates) + if err != nil { + log.Fatalf("Error creating TLS config %s", err) + return nil, err + } + + if oldServer == nil { + return manners.NewWithServer( + &http.Server{ + Addr: globalConfiguration.Port, + Handler: negroni, + TLSConfig: tlsConfig, + }), nil + } + gracefulServer, err := oldServer.HijackListener(&http.Server{ + Addr: globalConfiguration.Port, + Handler: negroni, + TLSConfig: tlsConfig, + }, tlsConfig) + if err != nil { + log.Fatalf("Error hijacking server %s", err) + return nil, err + } + return gracefulServer, nil +} + +// LoadConfig returns a new gorilla.mux Route from the specified global configuration and the dynamic +// provider configurations. +func (server *Server) loadConfig(configurations configs, globalConfiguration GlobalConfiguration) (*mux.Router, error) { + router := mux.NewRouter() + router.NotFoundHandler = http.HandlerFunc(notFoundHandler) + backends := map[string]http.Handler{} + for _, configuration := range configurations { + for frontendName, frontend := range configuration.Frontends { + log.Debugf("Creating frontend %s", frontendName) + fwd, _ := forward.New(forward.Logger(oxyLogger), forward.PassHostHeader(frontend.PassHostHeader)) + newRoute := router.NewRoute().Name(frontendName) + for routeName, route := range frontend.Routes { + log.Debugf("Creating route %s %s:%s", routeName, route.Rule, route.Value) + newRouteReflect, err := invoke(newRoute, route.Rule, route.Value) + if err != nil { + return nil, err + } + newRoute = newRouteReflect[0].Interface().(*mux.Route) + } + if backends[frontend.Backend] == nil { + log.Debugf("Creating backend %s", frontend.Backend) + var lb http.Handler + rr, _ := roundrobin.New(fwd) + if configuration.Backends[frontend.Backend] == nil { + return nil, errors.New("Backend not found: " + frontend.Backend) + } + lbMethod, err := types.NewLoadBalancerMethod(configuration.Backends[frontend.Backend].LoadBalancer) + if err != nil { + configuration.Backends[frontend.Backend].LoadBalancer = &types.LoadBalancer{Method: "wrr"} + } + switch lbMethod { + case types.Drr: + log.Infof("Creating load-balancer drr") + rebalancer, _ := roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger)) + lb = rebalancer + for serverName, server := range configuration.Backends[frontend.Backend].Servers { + url, err := url.Parse(server.URL) + if err != nil { + return nil, err + } + log.Infof("Creating server %s %s", serverName, url.String()) + rebalancer.UpsertServer(url, roundrobin.Weight(server.Weight)) + } + case types.Wrr: + log.Infof("Creating load-balancer wrr") + lb = middlewares.NewWebsocketUpgrader(rr) + for serverName, server := range configuration.Backends[frontend.Backend].Servers { + url, err := url.Parse(server.URL) + if err != nil { + return nil, err + } + log.Infof("Creating server %s %s", serverName, url.String()) + rr.UpsertServer(url, roundrobin.Weight(server.Weight)) + } + } + var negroni = negroni.New() + if configuration.Backends[frontend.Backend].CircuitBreaker != nil { + log.Infof("Creating circuit breaker %s", configuration.Backends[frontend.Backend].CircuitBreaker.Expression) + negroni.Use(middlewares.NewCircuitBreaker(lb, configuration.Backends[frontend.Backend].CircuitBreaker.Expression, cbreaker.Logger(oxyLogger))) + } else { + negroni.UseHandler(lb) + } + backends[frontend.Backend] = negroni + } else { + log.Infof("Reusing backend %s", frontend.Backend) + } + // stream.New(backends[frontend.Backend], stream.Retry("IsNetworkError() && Attempts() <= " + strconv.Itoa(globalConfiguration.Replay)), stream.Logger(oxyLogger)) + + newRoute.Handler(backends[frontend.Backend]) + err := newRoute.GetError() + if err != nil { + log.Errorf("Error building route: %s", err) + } + } + } + return router, nil +} diff --git a/traefik.go b/traefik.go index c84d99ba4..562fd3a62 100644 --- a/traefik.go +++ b/traefik.go @@ -1,40 +1,24 @@ package main import ( - "crypto/tls" "errors" fmtlog "log" - "net/http" - "net/url" "os" - "os/signal" "reflect" "runtime" "strings" - "syscall" - "time" log "github.com/Sirupsen/logrus" - "github.com/codegangsta/negroni" "github.com/emilevauge/traefik/middlewares" - "github.com/emilevauge/traefik/provider" - "github.com/emilevauge/traefik/types" - "github.com/gorilla/mux" - "github.com/mailgun/manners" - "github.com/mailgun/oxy/cbreaker" - "github.com/mailgun/oxy/forward" - "github.com/mailgun/oxy/roundrobin" "github.com/thoas/stats" "gopkg.in/alecthomas/kingpin.v2" - "sync" ) var ( - globalConfigFile = kingpin.Arg("conf", "Main configration file.").Default("traefik.toml").String() - version = kingpin.Flag("version", "Get Version.").Short('v').Bool() - currentConfigurations = make(configs) - metrics = stats.New() - oxyLogger = &OxyLogger{} + globalConfigFile = kingpin.Arg("conf", "Main configration file.").Default("traefik.toml").String() + version = kingpin.Flag("version", "Get Version.").Short('v').Bool() + metrics = stats.New() + oxyLogger = &OxyLogger{} ) func main() { @@ -42,19 +26,6 @@ func main() { kingpin.Version(Version + " built on the " + BuildDate) kingpin.Parse() fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags) - var srv *manners.GracefulServer - var configurationRouter *mux.Router - var configurationChan = make(chan types.ConfigMessage, 10) - defer close(configurationChan) - var configurationChanValidated = make(chan types.ConfigMessage, 10) - defer close(configurationChanValidated) - var sigs = make(chan os.Signal, 1) - defer close(sigs) - var stopChan = make(chan bool) - defer close(stopChan) - var providers = []provider.Provider{} - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) - var serverLock sync.Mutex // load global configuration globalConfiguration := LoadFileConfig(*globalConfigFile) @@ -82,294 +53,12 @@ func main() { log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableSorting: true}) } log.Debugf("Global configuration loaded %+v", globalConfiguration) - configurationRouter = LoadDefaultConfig(globalConfiguration) - - // listen new configurations from providers - go func() { - lastReceivedConfiguration := time.Unix(0, 0) - lastConfigs := make(map[string]*types.ConfigMessage) - for { - configMsg := <-configurationChan - log.Infof("Configuration receveived from provider %s: %#v", configMsg.ProviderName, configMsg.Configuration) - lastConfigs[configMsg.ProviderName] = &configMsg - if time.Now().After(lastReceivedConfiguration.Add(time.Duration(globalConfiguration.ProvidersThrottleDuration))) { - log.Infof("Last %s config received more than %s, OK", configMsg.ProviderName, globalConfiguration.ProvidersThrottleDuration) - // last config received more than n s ago - configurationChanValidated <- configMsg - } else { - log.Infof("Last %s config received less than %s, waiting...", configMsg.ProviderName, globalConfiguration.ProvidersThrottleDuration) - go func() { - <-time.After(globalConfiguration.ProvidersThrottleDuration) - if time.Now().After(lastReceivedConfiguration.Add(time.Duration(globalConfiguration.ProvidersThrottleDuration))) { - log.Infof("Waited for %s config, OK", configMsg.ProviderName) - configurationChanValidated <- *lastConfigs[configMsg.ProviderName] - } - }() - } - lastReceivedConfiguration = time.Now() - } - }() - go func() { - for { - configMsg := <-configurationChanValidated - if configMsg.Configuration == nil { - log.Info("Skipping empty Configuration") - } else if reflect.DeepEqual(currentConfigurations[configMsg.ProviderName], configMsg.Configuration) { - log.Info("Skipping same configuration") - } else { - // Copy configurations to new map so we don't change current if LoadConfig fails - newConfigurations := make(configs) - for k, v := range currentConfigurations { - newConfigurations[k] = v - } - newConfigurations[configMsg.ProviderName] = configMsg.Configuration - - newConfigurationRouter, err := LoadConfig(newConfigurations, globalConfiguration) - if err == nil { - serverLock.Lock() - currentConfigurations = newConfigurations - configurationRouter = newConfigurationRouter - oldServer := srv - newsrv, err := prepareServer(configurationRouter, globalConfiguration, oldServer, loggerMiddleware, metrics) - if err != nil { - log.Fatal("Error preparing server: ", err) - } - go startServer(newsrv, globalConfiguration) - srv = newsrv - time.Sleep(1 * time.Second) - if oldServer != nil { - log.Info("Stopping old server") - oldServer.Close() - } - serverLock.Unlock() - } else { - log.Error("Error loading new configuration, aborted ", err) - } - } - } - }() - - // configure providers - if globalConfiguration.Docker != nil { - providers = append(providers, globalConfiguration.Docker) - } - if globalConfiguration.Marathon != nil { - providers = append(providers, globalConfiguration.Marathon) - } - if globalConfiguration.File != nil { - if len(globalConfiguration.File.Filename) == 0 { - // no filename, setting to global config file - globalConfiguration.File.Filename = *globalConfigFile - } - providers = append(providers, globalConfiguration.File) - } - if globalConfiguration.Web != nil { - providers = append(providers, globalConfiguration.Web) - } - if globalConfiguration.Consul != nil { - providers = append(providers, globalConfiguration.Consul) - } - if globalConfiguration.Etcd != nil { - providers = append(providers, globalConfiguration.Etcd) - } - if globalConfiguration.Zookeeper != nil { - providers = append(providers, globalConfiguration.Zookeeper) - } - if globalConfiguration.Boltdb != nil { - providers = append(providers, globalConfiguration.Boltdb) - } - - // start providers - for _, provider := range providers { - log.Infof("Starting provider %v %+v", reflect.TypeOf(provider), provider) - currentProvider := provider - go func() { - err := currentProvider.Provide(configurationChan) - if err != nil { - log.Errorf("Error starting provider %s", err) - } - }() - } - - go func() { - sig := <-sigs - log.Infof("I have to go... %+v", sig) - log.Info("Stopping server") - srv.Close() - stopChan <- true - }() - - //negroni.Use(middlewares.NewCircuitBreaker(oxyLogger)) - //negroni.Use(middlewares.NewRoutes(configurationRouter)) - - var er error - serverLock.Lock() - srv, er = prepareServer(configurationRouter, globalConfiguration, nil, loggerMiddleware, metrics) - if er != nil { - log.Fatal("Error preparing server: ", er) - } - go startServer(srv, globalConfiguration) - //TODO change that! - time.Sleep(100 * time.Millisecond) - serverLock.Unlock() - - <-stopChan + server := NewServer(*globalConfiguration) + server.Start() + server.Close() log.Info("Shutting down") } -// creates a TLS config that allows terminating HTTPS for multiple domains using SNI -func createTLSConfig(certs []Certificate) (*tls.Config, error) { - if len(certs) == 0 { - return nil, nil - } - - config := &tls.Config{} - if config.NextProtos == nil { - config.NextProtos = []string{"http/1.1"} - } - - var err error - config.Certificates = make([]tls.Certificate, len(certs)) - for i, v := range certs { - config.Certificates[i], err = tls.LoadX509KeyPair(v.CertFile, v.KeyFile) - if err != nil { - return nil, err - } - } - // BuildNameToCertificate parses the CommonName and SubjectAlternateName fields - // in each certificate and populates the config.NameToCertificate map. - config.BuildNameToCertificate() - return config, nil -} - -func startServer(srv *manners.GracefulServer, globalConfiguration *GlobalConfiguration) { - log.Info("Starting server") - if srv.TLSConfig != nil { - err := srv.ListenAndServeTLSWithConfig(srv.TLSConfig) - if err != nil { - log.Fatal("Error creating server: ", err) - } - } else { - err := srv.ListenAndServe() - if err != nil { - log.Fatal("Error creating server: ", err) - } - } - log.Info("Server stopped") -} - -func prepareServer(router *mux.Router, globalConfiguration *GlobalConfiguration, oldServer *manners.GracefulServer, middlewares ...negroni.Handler) (*manners.GracefulServer, error) { - log.Info("Preparing server") - // middlewares - var negroni = negroni.New() - for _, middleware := range middlewares { - negroni.Use(middleware) - } - negroni.UseHandler(router) - tlsConfig, err := createTLSConfig(globalConfiguration.Certificates) - if err != nil { - log.Fatalf("Error creating TLS config %s", err) - return nil, err - } - - if oldServer == nil { - return manners.NewWithServer( - &http.Server{ - Addr: globalConfiguration.Port, - Handler: negroni, - TLSConfig: tlsConfig, - }), nil - } - server, err := oldServer.HijackListener(&http.Server{ - Addr: globalConfiguration.Port, - Handler: negroni, - TLSConfig: tlsConfig, - }, tlsConfig) - if err != nil { - log.Fatalf("Error hijacking server %s", err) - return nil, err - } - return server, nil -} - -// LoadConfig returns a new gorrilla.mux Route from the specified global configuration and the dynamic -// provider configurations. -func LoadConfig(configurations configs, globalConfiguration *GlobalConfiguration) (*mux.Router, error) { - router := mux.NewRouter() - router.NotFoundHandler = http.HandlerFunc(notFoundHandler) - backends := map[string]http.Handler{} - for _, configuration := range configurations { - for frontendName, frontend := range configuration.Frontends { - log.Debugf("Creating frontend %s", frontendName) - fwd, _ := forward.New(forward.Logger(oxyLogger), forward.PassHostHeader(frontend.PassHostHeader)) - newRoute := router.NewRoute().Name(frontendName) - for routeName, route := range frontend.Routes { - log.Debugf("Creating route %s %s:%s", routeName, route.Rule, route.Value) - newRouteReflect, err := invoke(newRoute, route.Rule, route.Value) - if err != nil { - return nil, err - } - newRoute = newRouteReflect[0].Interface().(*mux.Route) - } - if backends[frontend.Backend] == nil { - log.Debugf("Creating backend %s", frontend.Backend) - var lb http.Handler - rr, _ := roundrobin.New(fwd) - if configuration.Backends[frontend.Backend] == nil { - return nil, errors.New("Backend not found: " + frontend.Backend) - } - lbMethod, err := types.NewLoadBalancerMethod(configuration.Backends[frontend.Backend].LoadBalancer) - if err != nil { - configuration.Backends[frontend.Backend].LoadBalancer = &types.LoadBalancer{Method: "wrr"} - } - switch lbMethod { - case types.Drr: - log.Infof("Creating load-balancer drr") - rebalancer, _ := roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger)) - lb = rebalancer - for serverName, server := range configuration.Backends[frontend.Backend].Servers { - url, err := url.Parse(server.URL) - if err != nil { - return nil, err - } - log.Infof("Creating server %s %s", serverName, url.String()) - rebalancer.UpsertServer(url, roundrobin.Weight(server.Weight)) - } - case types.Wrr: - log.Infof("Creating load-balancer wrr") - lb = middlewares.NewWebsocketUpgrader(rr) - for serverName, server := range configuration.Backends[frontend.Backend].Servers { - url, err := url.Parse(server.URL) - if err != nil { - return nil, err - } - log.Infof("Creating server %s %s", serverName, url.String()) - rr.UpsertServer(url, roundrobin.Weight(server.Weight)) - } - } - var negroni = negroni.New() - if configuration.Backends[frontend.Backend].CircuitBreaker != nil { - log.Infof("Creating circuit breaker %s", configuration.Backends[frontend.Backend].CircuitBreaker.Expression) - negroni.Use(middlewares.NewCircuitBreaker(lb, configuration.Backends[frontend.Backend].CircuitBreaker.Expression, cbreaker.Logger(oxyLogger))) - } else { - negroni.UseHandler(lb) - } - backends[frontend.Backend] = negroni - } else { - log.Infof("Reusing backend %s", frontend.Backend) - } - // stream.New(backends[frontend.Backend], stream.Retry("IsNetworkError() && Attempts() <= " + strconv.Itoa(globalConfiguration.Replay)), stream.Logger(oxyLogger)) - - newRoute.Handler(backends[frontend.Backend]) - err := newRoute.GetError() - if err != nil { - log.Errorf("Error building route: %s", err) - } - } - } - return router, nil -} - // Invoke calls the specified method with the specified arguments on the specified interface. // It uses the go(lang) reflect package. func invoke(any interface{}, name string, args ...interface{}) ([]reflect.Value, error) { diff --git a/web.go b/web.go index cf46392ab..3c4eb91a1 100644 --- a/web.go +++ b/web.go @@ -20,6 +20,7 @@ type WebProvider struct { Address string CertFile, KeyFile string ReadOnly bool + server *Server } var ( @@ -34,12 +35,12 @@ func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessag systemRouter := mux.NewRouter() // health route - systemRouter.Methods("GET").Path("/health").HandlerFunc(getHealthHandler) + systemRouter.Methods("GET").Path("/health").HandlerFunc(provider.getHealthHandler) // API routes - systemRouter.Methods("GET").Path("/api").HandlerFunc(getConfigHandler) - systemRouter.Methods("GET").Path("/api/providers").HandlerFunc(getConfigHandler) - systemRouter.Methods("GET").Path("/api/providers/{provider}").HandlerFunc(getProviderHandler) + systemRouter.Methods("GET").Path("/api").HandlerFunc(provider.getConfigHandler) + systemRouter.Methods("GET").Path("/api/providers").HandlerFunc(provider.getConfigHandler) + systemRouter.Methods("GET").Path("/api/providers/{provider}").HandlerFunc(provider.getProviderHandler) systemRouter.Methods("PUT").Path("/api/providers/{provider}").HandlerFunc(func(response http.ResponseWriter, request *http.Request) { if provider.ReadOnly { response.WriteHeader(http.StatusForbidden) @@ -58,20 +59,20 @@ func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessag err := json.Unmarshal(body, configuration) if err == nil { configurationChan <- types.ConfigMessage{"web", configuration} - getConfigHandler(response, request) + provider.getConfigHandler(response, request) } else { log.Errorf("Error parsing configuration %+v", err) http.Error(response, fmt.Sprintf("%+v", err), http.StatusBadRequest) } }) - systemRouter.Methods("GET").Path("/api/providers/{provider}/backends").HandlerFunc(getBackendsHandler) - systemRouter.Methods("GET").Path("/api/providers/{provider}/backends/{backend}").HandlerFunc(getBackendHandler) - systemRouter.Methods("GET").Path("/api/providers/{provider}/backends/{backend}/servers").HandlerFunc(getServersHandler) - systemRouter.Methods("GET").Path("/api/providers/{provider}/backends/{backend}/servers/{server}").HandlerFunc(getServerHandler) - systemRouter.Methods("GET").Path("/api/providers/{provider}/frontends").HandlerFunc(getFrontendsHandler) - systemRouter.Methods("GET").Path("/api/providers/{provider}/frontends/{frontend}").HandlerFunc(getFrontendHandler) - systemRouter.Methods("GET").Path("/api/providers/{provider}/frontends/{frontend}/routes").HandlerFunc(getRoutesHandler) - systemRouter.Methods("GET").Path("/api/providers/{provider}/frontends/{frontend}/routes/{route}").HandlerFunc(getRouteHandler) + systemRouter.Methods("GET").Path("/api/providers/{provider}/backends").HandlerFunc(provider.getBackendsHandler) + systemRouter.Methods("GET").Path("/api/providers/{provider}/backends/{backend}").HandlerFunc(provider.getBackendHandler) + systemRouter.Methods("GET").Path("/api/providers/{provider}/backends/{backend}/servers").HandlerFunc(provider.getServersHandler) + systemRouter.Methods("GET").Path("/api/providers/{provider}/backends/{backend}/servers/{server}").HandlerFunc(provider.getServerHandler) + systemRouter.Methods("GET").Path("/api/providers/{provider}/frontends").HandlerFunc(provider.getFrontendsHandler) + systemRouter.Methods("GET").Path("/api/providers/{provider}/frontends/{frontend}").HandlerFunc(provider.getFrontendHandler) + systemRouter.Methods("GET").Path("/api/providers/{provider}/frontends/{frontend}/routes").HandlerFunc(provider.getRoutesHandler) + systemRouter.Methods("GET").Path("/api/providers/{provider}/frontends/{frontend}/routes/{route}").HandlerFunc(provider.getRouteHandler) // Expose dashboard systemRouter.Methods("GET").Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) { @@ -95,39 +96,39 @@ func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessag return nil } -func getHealthHandler(response http.ResponseWriter, request *http.Request) { +func (provider *WebProvider) getHealthHandler(response http.ResponseWriter, request *http.Request) { templatesRenderer.JSON(response, http.StatusOK, metrics.Data()) } -func getConfigHandler(response http.ResponseWriter, request *http.Request) { - templatesRenderer.JSON(response, http.StatusOK, currentConfigurations) +func (provider *WebProvider) getConfigHandler(response http.ResponseWriter, request *http.Request) { + templatesRenderer.JSON(response, http.StatusOK, provider.server.currentConfigurations) } -func getProviderHandler(response http.ResponseWriter, request *http.Request) { +func (provider *WebProvider) getProviderHandler(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) providerID := vars["provider"] - if provider, ok := currentConfigurations[providerID]; ok { + if provider, ok := provider.server.currentConfigurations[providerID]; ok { templatesRenderer.JSON(response, http.StatusOK, provider) } else { http.NotFound(response, request) } } -func getBackendsHandler(response http.ResponseWriter, request *http.Request) { +func (provider *WebProvider) getBackendsHandler(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) providerID := vars["provider"] - if provider, ok := currentConfigurations[providerID]; ok { + if provider, ok := provider.server.currentConfigurations[providerID]; ok { templatesRenderer.JSON(response, http.StatusOK, provider.Backends) } else { http.NotFound(response, request) } } -func getBackendHandler(response http.ResponseWriter, request *http.Request) { +func (provider *WebProvider) getBackendHandler(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) providerID := vars["provider"] backendID := vars["backend"] - if provider, ok := currentConfigurations[providerID]; ok { + if provider, ok := provider.server.currentConfigurations[providerID]; ok { if backend, ok := provider.Backends[backendID]; ok { templatesRenderer.JSON(response, http.StatusOK, backend) return @@ -136,11 +137,11 @@ func getBackendHandler(response http.ResponseWriter, request *http.Request) { http.NotFound(response, request) } -func getServersHandler(response http.ResponseWriter, request *http.Request) { +func (provider *WebProvider) getServersHandler(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) providerID := vars["provider"] backendID := vars["backend"] - if provider, ok := currentConfigurations[providerID]; ok { + if provider, ok := provider.server.currentConfigurations[providerID]; ok { if backend, ok := provider.Backends[backendID]; ok { templatesRenderer.JSON(response, http.StatusOK, backend.Servers) return @@ -149,12 +150,12 @@ func getServersHandler(response http.ResponseWriter, request *http.Request) { http.NotFound(response, request) } -func getServerHandler(response http.ResponseWriter, request *http.Request) { +func (provider *WebProvider) getServerHandler(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) providerID := vars["provider"] backendID := vars["backend"] serverID := vars["server"] - if provider, ok := currentConfigurations[providerID]; ok { + if provider, ok := provider.server.currentConfigurations[providerID]; ok { if backend, ok := provider.Backends[backendID]; ok { if server, ok := backend.Servers[serverID]; ok { templatesRenderer.JSON(response, http.StatusOK, server) @@ -165,21 +166,21 @@ func getServerHandler(response http.ResponseWriter, request *http.Request) { http.NotFound(response, request) } -func getFrontendsHandler(response http.ResponseWriter, request *http.Request) { +func (provider *WebProvider) getFrontendsHandler(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) providerID := vars["provider"] - if provider, ok := currentConfigurations[providerID]; ok { + if provider, ok := provider.server.currentConfigurations[providerID]; ok { templatesRenderer.JSON(response, http.StatusOK, provider.Frontends) } else { http.NotFound(response, request) } } -func getFrontendHandler(response http.ResponseWriter, request *http.Request) { +func (provider *WebProvider) getFrontendHandler(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) providerID := vars["provider"] frontendID := vars["frontend"] - if provider, ok := currentConfigurations[providerID]; ok { + if provider, ok := provider.server.currentConfigurations[providerID]; ok { if frontend, ok := provider.Frontends[frontendID]; ok { templatesRenderer.JSON(response, http.StatusOK, frontend) return @@ -188,11 +189,11 @@ func getFrontendHandler(response http.ResponseWriter, request *http.Request) { http.NotFound(response, request) } -func getRoutesHandler(response http.ResponseWriter, request *http.Request) { +func (provider *WebProvider) getRoutesHandler(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) providerID := vars["provider"] frontendID := vars["frontend"] - if provider, ok := currentConfigurations[providerID]; ok { + if provider, ok := provider.server.currentConfigurations[providerID]; ok { if frontend, ok := provider.Frontends[frontendID]; ok { templatesRenderer.JSON(response, http.StatusOK, frontend.Routes) return @@ -201,12 +202,12 @@ func getRoutesHandler(response http.ResponseWriter, request *http.Request) { http.NotFound(response, request) } -func getRouteHandler(response http.ResponseWriter, request *http.Request) { +func (provider *WebProvider) getRouteHandler(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) providerID := vars["provider"] frontendID := vars["frontend"] routeID := vars["route"] - if provider, ok := currentConfigurations[providerID]; ok { + if provider, ok := provider.server.currentConfigurations[providerID]; ok { if frontend, ok := provider.Frontends[frontendID]; ok { if route, ok := frontend.Routes[routeID]; ok { templatesRenderer.JSON(response, http.StatusOK, route) From 35070f7c1cad4adb97491d071cefc4535a1ed168 Mon Sep 17 00:00:00 2001 From: emile Date: Wed, 13 Jan 2016 22:46:44 +0100 Subject: [PATCH 2/4] Use of Viper and cobra --- cmd.go | 180 ++++++++++++++++++ configuration.go | 113 ++++++++++- glide.yaml | 10 +- integration/basic_test.go | 18 +- integration/consul_test.go | 4 +- integration/docker_test.go | 16 +- integration/file_test.go | 8 +- integration/fixtures/consul/simple.toml | 1 + integration/fixtures/docker/simple.toml | 1 + .../fixtures/file/56-simple-panic.toml | 1 + integration/fixtures/file/simple.toml | 1 + integration/fixtures/https/https_sni.toml | 2 +- integration/fixtures/marathon/simple.toml | 1 + integration/fixtures/simple_default.toml | 2 +- integration/fixtures/simple_web.toml | 1 + integration/https_test.go | 10 +- integration/marathon_test.go | 4 +- provider/boltdb.go | 4 +- provider/consul.go | 4 +- provider/docker.go | 8 +- provider/etcd.go | 4 +- provider/file.go | 2 +- provider/kv.go | 16 +- provider/marathon.go | 2 +- provider/provider.go | 5 +- provider/provider_test.go | 10 +- provider/zk.go | 2 +- server.go | 31 +-- traefik.go | 65 +------ traefik.sample.toml | 5 + utils.go | 23 +++ web.go | 3 + 32 files changed, 414 insertions(+), 143 deletions(-) create mode 100644 cmd.go create mode 100644 utils.go diff --git a/cmd.go b/cmd.go new file mode 100644 index 000000000..81c804d27 --- /dev/null +++ b/cmd.go @@ -0,0 +1,180 @@ +/* +Copyright +*/ +package main + +import ( + fmtlog "log" + "os" + "strings" + "time" + + log "github.com/Sirupsen/logrus" + "github.com/emilevauge/traefik/middlewares" + "github.com/emilevauge/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 + zookeeper bool + etcd bool + boltdb bool +}{ + GlobalConfiguration{ + Docker: &provider.Docker{ + TLS: &provider.DockerTLS{}, + }, + File: &provider.File{}, + Web: &WebProvider{}, + Marathon: &provider.Marathon{}, + Consul: &provider.Consul{}, + Zookeeper: &provider.Zookepper{}, + Etcd: &provider.Etcd{}, + Boltdb: &provider.BoltDb{}, + }, + false, + false, + false, + false, + false, + false, + false, + false, + false, +} + +func init() { + traefikCmd.AddCommand(versionCmd) + traefikCmd.PersistentFlags().StringP("configFile", "c", "", "Configuration file to use (TOML, JSON, YAML, HCL).") + traefikCmd.PersistentFlags().StringP("port", "p", ":80", "Reverse proxy port") + traefikCmd.PersistentFlags().StringP("graceTimeOut", "g", "10", "Timeout in seconds. Duration to give active requests a chance to finish during hot-reloads") + traefikCmd.PersistentFlags().String("accessLogsFile", "log/access.log", "Access logs file") + traefikCmd.PersistentFlags().String("traefikLogsFile", "log/traefik.log", "Traefik logs file") + traefikCmd.PersistentFlags().Var(&arguments.Certificates, "certificates", "SSL certificates and keys. You may add several certificate/key pairs to terminate HTTPS for multiple domain names using TLS SNI") + 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().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().StringVar(&arguments.Marathon.NetworkInterface, "marathon.networkInterface", "eth0", "Network interface used to call Marathon web services. Needed in case of multiple network interfaces") + + 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", "Consul server endpoint") + traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Prefix, "consul.prefix", "/traefik", "Prefix used for KV store") + + 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", "Zookeeper server endpoint") + 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", "Etcd server endpoint") + traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Prefix, "etcd.prefix", "/traefik", "Prefix used for KV store") + + 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") + + viper.BindPFlag("configFile", traefikCmd.PersistentFlags().Lookup("configFile")) + viper.BindPFlag("port", traefikCmd.PersistentFlags().Lookup("port")) + viper.BindPFlag("graceTimeOut", traefikCmd.PersistentFlags().Lookup("graceTimeOut")) + // viper.BindPFlag("certificates", TraefikCmd.PersistentFlags().Lookup("certificates")) + viper.BindPFlag("logLevel", traefikCmd.PersistentFlags().Lookup("logLevel")) + // TODO: wait for this issue to be corrected: https://github.com/spf13/viper/issues/105 + viper.BindPFlag("providersThrottleDuration", traefikCmd.PersistentFlags().Lookup("providersThrottleDuration")) + viper.SetDefault("certificates", &Certificates{}) + viper.SetDefault("providersThrottleDuration", time.Duration(2*time.Second)) +} + +func run() { + fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags) + + // load global configuration + globalConfiguration := LoadConfiguration() + + 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 fi.Close() + 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}) + } + log.Debugf("Global configuration loaded %+v", globalConfiguration) + server := NewServer(*globalConfiguration) + server.Start() + defer server.Close() + log.Info("Shutting down") +} diff --git a/configuration.go b/configuration.go index 92dadf383..14b74ae87 100644 --- a/configuration.go +++ b/configuration.go @@ -4,9 +4,13 @@ import ( fmtlog "log" "time" - "github.com/BurntSushi/toml" + "fmt" "github.com/emilevauge/traefik/provider" "github.com/emilevauge/traefik/types" + "github.com/mitchellh/mapstructure" + "github.com/spf13/viper" + "github.com/wendal/errors" + "strings" ) // GlobalConfiguration holds global configuration (with providers, etc.). @@ -16,7 +20,7 @@ type GlobalConfiguration struct { GraceTimeOut int64 AccessLogsFile string TraefikLogsFile string - Certificates []Certificate + Certificates Certificates LogLevel string ProvidersThrottleDuration time.Duration Docker *provider.Docker @@ -29,6 +33,38 @@ type GlobalConfiguration struct { Boltdb *provider.BoltDb } +// Certificates defines traefik certificates type +type Certificates []Certificate + +// 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 (certs *Certificates) String() string { + if len(*certs) == 0 { + return "" + } + return (*certs)[0].CertFile + "," + (*certs)[0].KeyFile +} + +// Set is the method to set the flag value, part of the flag.Value interface. +// Set's argument is a string to be parsed to set the flag. +// It's a comma-separated list, so we split it. +func (certs *Certificates) Set(value string) error { + files := strings.Split(value, ",") + if len(files) != 2 { + return errors.New("Bad certificates format: " + value) + } + *certs = append(*certs, Certificate{ + CertFile: files[0], + KeyFile: files[1], + }) + return nil +} + +// Type is type of the struct +func (certs *Certificates) Type() string { + return fmt.Sprint("certificates") +} + // Certificate holds a SSL cert/key pair type Certificate struct { CertFile string @@ -47,13 +83,80 @@ func NewGlobalConfiguration() *GlobalConfiguration { return globalConfiguration } -// LoadFileConfig returns a GlobalConfiguration from reading the specified file (a toml file). -func LoadFileConfig(file string) *GlobalConfiguration { +// LoadConfiguration returns a GlobalConfiguration. +func LoadConfiguration() *GlobalConfiguration { configuration := NewGlobalConfiguration() - if _, err := toml.DecodeFile(file, configuration); err != nil { + 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) + } + 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 + err := viper.ReadInConfig() // Find and read the config file + if err != nil { // Handle errors reading the config file fmtlog.Fatalf("Error reading file: %s", err) } + if len(arguments.Certificates) > 0 { + viper.Set("certificates", arguments.Certificates) + } + 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.consul { + viper.Set("consul", arguments.Consul) + } + if arguments.zookeeper { + viper.Set("zookeeper", arguments.Zookeeper) + } + if arguments.etcd { + viper.Set("etcd", arguments.Etcd) + } + if arguments.boltdb { + viper.Set("boltdb", arguments.Boltdb) + } + err = unmarshal(&configuration) + if err != nil { + fmtlog.Fatalf("Error reading file: %s", err) + } + 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/glide.yaml b/glide.yaml index 65400739f..8143a1975 100644 --- a/glide.yaml +++ b/glide.yaml @@ -145,9 +145,13 @@ import: - package: github.com/donovanhide/eventsource ref: d8a3071799b98cacd30b6da92f536050ccfe6da4 - package: github.com/golang/glog - ref: fca8c8854093a154ff1eb580aae10276ad6b1b5f + ref: fca8c8854093a154ff1eb580aae10276ad6b1b5f - package: github.com/spf13/cast - version: ee7b3e0353166ab1f3a605294ac8cd2b77953778 + ref: ee7b3e0353166ab1f3a605294ac8cd2b77953778 - package: github.com/spf13/viper - version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325 + ref: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325 + - package: github.com/spf13/cobra + subpackages: + - /cobra + diff --git a/integration/basic_test.go b/integration/basic_test.go index cb47be0a0..d9a9178a8 100644 --- a/integration/basic_test.go +++ b/integration/basic_test.go @@ -1,11 +1,11 @@ package main import ( - "fmt" "net/http" "os/exec" "time" + "fmt" checker "github.com/vdemeester/shakers" check "gopkg.in/check.v1" ) @@ -18,10 +18,10 @@ func (s *SimpleSuite) TestNoOrInexistentConfigShouldFail(c *check.C) { output, err := cmd.CombinedOutput() c.Assert(err, checker.NotNil) - c.Assert(string(output), checker.Contains, "Error reading file: open traefik.toml: no such file or directory") + c.Assert(string(output), checker.Contains, "Error reading file: open : no such file or directory") nonExistentFile := "non/existent/file.toml" - cmd = exec.Command(traefikBinary, nonExistentFile) + cmd = exec.Command(traefikBinary, "--configFile="+nonExistentFile) output, err = cmd.CombinedOutput() c.Assert(err, checker.NotNil) @@ -29,30 +29,30 @@ func (s *SimpleSuite) TestNoOrInexistentConfigShouldFail(c *check.C) { } func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) { - cmd := exec.Command(traefikBinary, "fixtures/invalid_configuration.toml") + cmd := exec.Command(traefikBinary, "--configFile=fixtures/invalid_configuration.toml") output, err := cmd.CombinedOutput() c.Assert(err, checker.NotNil) - c.Assert(string(output), checker.Contains, "Error reading file: Near line 1") + c.Assert(string(output), checker.Contains, "Error reading file: While parsing config: Near line 1") } func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) { - cmd := exec.Command(traefikBinary, "fixtures/simple_default.toml") + cmd := exec.Command(traefikBinary, "--configFile=fixtures/simple_default.toml") err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() time.Sleep(500 * time.Millisecond) // TODO validate : run on 80 - resp, err := http.Get("http://127.0.0.1/") + resp, err := http.Get("http://127.0.0.1:8000/") - // Expected a 404 as we did not comfigure anything + // Expected a 404 as we did not configure anything c.Assert(err, checker.IsNil) c.Assert(resp.StatusCode, checker.Equals, 404) } func (s *SimpleSuite) TestWithWebConfig(c *check.C) { - cmd := exec.Command(traefikBinary, "fixtures/simple_web.toml") + cmd := exec.Command(traefikBinary, "--configFile=fixtures/simple_web.toml") err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() diff --git a/integration/consul_test.go b/integration/consul_test.go index 634196ae8..07af1aa7f 100644 --- a/integration/consul_test.go +++ b/integration/consul_test.go @@ -10,14 +10,14 @@ import ( ) func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) { - cmd := exec.Command(traefikBinary, "fixtures/consul/simple.toml") + cmd := exec.Command(traefikBinary, "--configFile=fixtures/consul/simple.toml") err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() time.Sleep(500 * time.Millisecond) // TODO validate : run on 80 - resp, err := http.Get("http://127.0.0.1/") + resp, err := http.Get("http://127.0.0.1:8000/") // Expected a 404 as we did not comfigure anything c.Assert(err, checker.IsNil) diff --git a/integration/docker_test.go b/integration/docker_test.go index 0f11049bf..884e7dd4b 100644 --- a/integration/docker_test.go +++ b/integration/docker_test.go @@ -139,14 +139,14 @@ func (s *DockerSuite) TestSimpleConfiguration(c *check.C) { file := s.adaptFileForHost(c, "fixtures/docker/simple.toml") defer os.Remove(file) - cmd := exec.Command(traefikBinary, file) + cmd := exec.Command(traefikBinary, "--configFile="+file) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() time.Sleep(500 * time.Millisecond) // TODO validate : run on 80 - resp, err := http.Get("http://127.0.0.1/") + resp, err := http.Get("http://127.0.0.1:8000/") c.Assert(err, checker.IsNil) // Expected a 404 as we did not comfigure anything @@ -159,7 +159,7 @@ func (s *DockerSuite) TestDefaultDockerContainers(c *check.C) { name := s.startContainer(c, "swarm:1.0.0", "manage", "token://blablabla") // Start traefik - cmd := exec.Command(traefikBinary, file) + cmd := exec.Command(traefikBinary, "--configFile="+file) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -168,7 +168,7 @@ func (s *DockerSuite) TestDefaultDockerContainers(c *check.C) { time.Sleep(1500 * time.Millisecond) client := &http.Client{} - req, err := http.NewRequest("GET", "http://127.0.0.1/version", nil) + req, err := http.NewRequest("GET", "http://127.0.0.1:8000/version", nil) c.Assert(err, checker.IsNil) req.Host = fmt.Sprintf("%s.docker.localhost", name) resp, err := client.Do(req) @@ -196,7 +196,7 @@ func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) { s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla") // Start traefik - cmd := exec.Command(traefikBinary, file) + cmd := exec.Command(traefikBinary, "--configFile="+file) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -205,7 +205,7 @@ func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) { time.Sleep(1500 * time.Millisecond) client := &http.Client{} - req, err := http.NewRequest("GET", "http://127.0.0.1/version", nil) + req, err := http.NewRequest("GET", "http://127.0.0.1:8000/version", nil) c.Assert(err, checker.IsNil) req.Host = fmt.Sprintf("my.super.host") resp, err := client.Do(req) @@ -232,7 +232,7 @@ func (s *DockerSuite) TestDockerContainersWithOneMissingLabels(c *check.C) { s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla") // Start traefik - cmd := exec.Command(traefikBinary, file) + cmd := exec.Command(traefikBinary, "--configFile="+file) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -241,7 +241,7 @@ func (s *DockerSuite) TestDockerContainersWithOneMissingLabels(c *check.C) { time.Sleep(1500 * time.Millisecond) client := &http.Client{} - req, err := http.NewRequest("GET", "http://127.0.0.1/version", nil) + req, err := http.NewRequest("GET", "http://127.0.0.1:8000/version", nil) c.Assert(err, checker.IsNil) req.Host = fmt.Sprintf("my.super.host") resp, err := client.Do(req) diff --git a/integration/file_test.go b/integration/file_test.go index 7fbed2390..8d32dae63 100644 --- a/integration/file_test.go +++ b/integration/file_test.go @@ -10,13 +10,13 @@ import ( ) func (s *FileSuite) TestSimpleConfiguration(c *check.C) { - cmd := exec.Command(traefikBinary, "fixtures/file/simple.toml") + cmd := exec.Command(traefikBinary, "--configFile=fixtures/file/simple.toml") err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() time.Sleep(1000 * time.Millisecond) - resp, err := http.Get("http://127.0.0.1/") + resp, err := http.Get("http://127.0.0.1:8000/") // Expected a 404 as we did not configure anything c.Assert(err, checker.IsNil) @@ -25,13 +25,13 @@ func (s *FileSuite) TestSimpleConfiguration(c *check.C) { // #56 regression test, make sure it does not fail func (s *FileSuite) TestSimpleConfigurationNoPanic(c *check.C) { - cmd := exec.Command(traefikBinary, "fixtures/file/56-simple-panic.toml") + cmd := exec.Command(traefikBinary, "--configFile=fixtures/file/56-simple-panic.toml") err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() time.Sleep(1000 * time.Millisecond) - resp, err := http.Get("http://127.0.0.1/") + resp, err := http.Get("http://127.0.0.1:8000/") // Expected a 404 as we did not configure anything c.Assert(err, checker.IsNil) diff --git a/integration/fixtures/consul/simple.toml b/integration/fixtures/consul/simple.toml index 02707a085..34947f7e8 100644 --- a/integration/fixtures/consul/simple.toml +++ b/integration/fixtures/consul/simple.toml @@ -4,6 +4,7 @@ # Default: ":80" # # port = ":80" +port = ":8000" # # LogLevel logLevel = "DEBUG" diff --git a/integration/fixtures/docker/simple.toml b/integration/fixtures/docker/simple.toml index 8968895c0..963cfd5a8 100644 --- a/integration/fixtures/docker/simple.toml +++ b/integration/fixtures/docker/simple.toml @@ -4,6 +4,7 @@ # Default: ":80" # # port = ":80" +port = ":8000" # # LogLevel logLevel = "DEBUG" diff --git a/integration/fixtures/file/56-simple-panic.toml b/integration/fixtures/file/56-simple-panic.toml index 100b58a93..f8c792e65 100644 --- a/integration/fixtures/file/56-simple-panic.toml +++ b/integration/fixtures/file/56-simple-panic.toml @@ -4,6 +4,7 @@ # Default: ":80" # # port = ":80" +port = ":8000" # # LogLevel logLevel = "DEBUG" diff --git a/integration/fixtures/file/simple.toml b/integration/fixtures/file/simple.toml index 6d799d24b..3db190f40 100644 --- a/integration/fixtures/file/simple.toml +++ b/integration/fixtures/file/simple.toml @@ -4,6 +4,7 @@ # Default: ":80" # # port = ":80" +port = ":8000" # # LogLevel logLevel = "DEBUG" diff --git a/integration/fixtures/https/https_sni.toml b/integration/fixtures/https/https_sni.toml index 6af20c15f..aebf22657 100644 --- a/integration/fixtures/https/https_sni.toml +++ b/integration/fixtures/https/https_sni.toml @@ -1,4 +1,4 @@ -port = ":443" +port = ":4443" logLevel = "DEBUG" [[certificates]] diff --git a/integration/fixtures/marathon/simple.toml b/integration/fixtures/marathon/simple.toml index 1e97650a6..c6cfbb05a 100644 --- a/integration/fixtures/marathon/simple.toml +++ b/integration/fixtures/marathon/simple.toml @@ -4,6 +4,7 @@ # Default: ":80" # # port = ":80" +port = ":8000" # # LogLevel logLevel = "DEBUG" diff --git a/integration/fixtures/simple_default.toml b/integration/fixtures/simple_default.toml index ac7dee9f1..3f5fe41df 100644 --- a/integration/fixtures/simple_default.toml +++ b/integration/fixtures/simple_default.toml @@ -3,7 +3,7 @@ # Optional # Default: ":80" # -# port = ":80" +port = ":8000" # # LogLevel logLevel = "DEBUG" diff --git a/integration/fixtures/simple_web.toml b/integration/fixtures/simple_web.toml index e88dd3049..49452f6e5 100644 --- a/integration/fixtures/simple_web.toml +++ b/integration/fixtures/simple_web.toml @@ -1,3 +1,4 @@ +port = ":8000" logLevel = "DEBUG" [web] diff --git a/integration/https_test.go b/integration/https_test.go index 9a81609e0..41b89e243 100644 --- a/integration/https_test.go +++ b/integration/https_test.go @@ -19,7 +19,7 @@ type HTTPSSuite struct{ BaseSuite } // "snitest.com", which happens to match the CN of 'snitest.com.crt'. The test // verifies that traefik presents the correct certificate. func (s *HTTPSSuite) TestWithSNIConfigHandshake(c *check.C) { - cmd := exec.Command(traefikBinary, "fixtures/https/https_sni.toml") + cmd := exec.Command(traefikBinary, "--configFile=fixtures/https/https_sni.toml") err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -30,7 +30,7 @@ func (s *HTTPSSuite) TestWithSNIConfigHandshake(c *check.C) { InsecureSkipVerify: true, ServerName: "snitest.com", } - conn, err := tls.Dial("tcp", "127.0.0.1:443", tlsConfig) + conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig) c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server")) defer conn.Close() @@ -46,7 +46,7 @@ func (s *HTTPSSuite) TestWithSNIConfigHandshake(c *check.C) { // SNI hostnames of "snitest.org" and "snitest.com". The test verifies // that traefik routes the requests to the expected backends. func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) { - cmd := exec.Command(traefikBinary, "fixtures/https/https_sni.toml") + cmd := exec.Command(traefikBinary, "--configFile=fixtures/https/https_sni.toml") err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -72,7 +72,7 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) { } client := &http.Client{Transport: tr1} - req, _ := http.NewRequest("GET", "https://127.0.0.1/", nil) + req, _ := http.NewRequest("GET", "https://127.0.0.1:4443/", nil) req.Host = "snitest.com" req.Header.Set("Host", "snitest.com") req.Header.Set("Accept", "*/*") @@ -82,7 +82,7 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) { c.Assert(resp.StatusCode, checker.Equals, 204) client = &http.Client{Transport: tr2} - req, _ = http.NewRequest("GET", "https://127.0.0.1/", nil) + req, _ = http.NewRequest("GET", "https://127.0.0.1:4443/", nil) req.Host = "snitest.org" req.Header.Set("Host", "snitest.org") req.Header.Set("Accept", "*/*") diff --git a/integration/marathon_test.go b/integration/marathon_test.go index 0f10f7252..40a42ffd6 100644 --- a/integration/marathon_test.go +++ b/integration/marathon_test.go @@ -10,14 +10,14 @@ import ( ) func (s *MarathonSuite) TestSimpleConfiguration(c *check.C) { - cmd := exec.Command(traefikBinary, "fixtures/marathon/simple.toml") + cmd := exec.Command(traefikBinary, "--configFile=fixtures/marathon/simple.toml") err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() time.Sleep(500 * time.Millisecond) // TODO validate : run on 80 - resp, err := http.Get("http://127.0.0.1/") + resp, err := http.Get("http://127.0.0.1:8000/") // Expected a 404 as we did not configure anything c.Assert(err, checker.IsNil) diff --git a/provider/boltdb.go b/provider/boltdb.go index 9304f8a8f..fec8c3654 100644 --- a/provider/boltdb.go +++ b/provider/boltdb.go @@ -8,13 +8,13 @@ import ( // BoltDb holds configurations of the BoltDb provider. type BoltDb struct { - Kv + Kv `mapstructure:",squash"` } // Provide allows the provider to provide configurations to traefik // using the given configuration channel. func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage) error { - provider.StoreType = store.BOLTDB + provider.storeType = store.BOLTDB boltdb.Register() return provider.provide(configurationChan) } diff --git a/provider/consul.go b/provider/consul.go index fa0be91cf..49d3031ac 100644 --- a/provider/consul.go +++ b/provider/consul.go @@ -8,13 +8,13 @@ import ( // Consul holds configurations of the Consul provider. type Consul struct { - Kv + Kv `mapstructure:",squash"` } // Provide allows the provider to provide configurations to traefik // using the given configuration channel. func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage) error { - provider.StoreType = store.CONSUL + provider.storeType = store.CONSUL consul.Register() return provider.provide(configurationChan) } diff --git a/provider/docker.go b/provider/docker.go index 2d8ea65a8..e4e6a1f5b 100644 --- a/provider/docker.go +++ b/provider/docker.go @@ -17,10 +17,10 @@ import ( // Docker holds configurations of the Docker provider. type Docker struct { - baseProvider - Endpoint string - Domain string - TLS *DockerTLS + BaseProvider `mapstructure:",squash"` + Endpoint string + Domain string + TLS *DockerTLS } // DockerTLS holds TLS specific configurations diff --git a/provider/etcd.go b/provider/etcd.go index d51ecc380..21455b155 100644 --- a/provider/etcd.go +++ b/provider/etcd.go @@ -8,13 +8,13 @@ import ( // Etcd holds configurations of the Etcd provider. type Etcd struct { - Kv + Kv `mapstructure:",squash"` } // Provide allows the provider to provide configurations to traefik // using the given configuration channel. func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage) error { - provider.StoreType = store.ETCD + provider.storeType = store.ETCD etcd.Register() return provider.provide(configurationChan) } diff --git a/provider/file.go b/provider/file.go index c5ef28c00..0b3bb0cdd 100644 --- a/provider/file.go +++ b/provider/file.go @@ -13,7 +13,7 @@ import ( // File holds configurations of the File provider. type File struct { - baseProvider + BaseProvider `mapstructure:",squash"` } // Provide allows the provider to provide configurations to traefik diff --git a/provider/kv.go b/provider/kv.go index d610e666d..4fc5cb143 100644 --- a/provider/kv.go +++ b/provider/kv.go @@ -15,16 +15,16 @@ import ( // Kv holds common configurations of key-value providers. type Kv struct { - baseProvider - Endpoint string - Prefix string - StoreType store.Backend - kvclient store.Store + BaseProvider `mapstructure:",squash"` + Endpoint string + Prefix string + storeType store.Backend + kvclient store.Store } func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage) error { kv, err := libkv.NewStore( - provider.StoreType, + provider.storeType, []string{provider.Endpoint}, &store.Config{ ConnectionTimeout: 30 * time.Second, @@ -50,7 +50,7 @@ func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage) error configuration := provider.loadConfig() if configuration != nil { configurationChan <- types.ConfigMessage{ - ProviderName: string(provider.StoreType), + ProviderName: string(provider.storeType), Configuration: configuration, } } @@ -60,7 +60,7 @@ func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage) error } configuration := provider.loadConfig() configurationChan <- types.ConfigMessage{ - ProviderName: string(provider.StoreType), + ProviderName: string(provider.storeType), Configuration: configuration, } return nil diff --git a/provider/marathon.go b/provider/marathon.go index a2c8f5532..c2fc96f25 100644 --- a/provider/marathon.go +++ b/provider/marathon.go @@ -14,7 +14,7 @@ import ( // Marathon holds configuration of the Marathon provider. type Marathon struct { - baseProvider + BaseProvider `mapstructure:",squash"` Endpoint string Domain string NetworkInterface string diff --git a/provider/provider.go b/provider/provider.go index d2ceb5ebc..78087da9e 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -18,12 +18,13 @@ type Provider interface { Provide(configurationChan chan<- types.ConfigMessage) error } -type baseProvider struct { +// BaseProvider should be inherited by providers +type BaseProvider struct { Watch bool Filename string } -func (p *baseProvider) getConfiguration(defaultTemplateFile string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) { +func (p *BaseProvider) getConfiguration(defaultTemplateFile string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) { var ( buf []byte err error diff --git a/provider/provider_test.go b/provider/provider_test.go index d9578fee2..490a82a7b 100644 --- a/provider/provider_test.go +++ b/provider/provider_test.go @@ -9,7 +9,7 @@ import ( ) type myProvider struct { - baseProvider + BaseProvider } func (p *myProvider) Foo() string { @@ -49,7 +49,7 @@ func TestConfigurationErrors(t *testing.T) { }{ { provider: &myProvider{ - baseProvider{ + BaseProvider{ Filename: "/non/existent/template.tmpl", }, }, @@ -62,7 +62,7 @@ func TestConfigurationErrors(t *testing.T) { }, { provider: &myProvider{ - baseProvider{ + BaseProvider{ Filename: templateErrorFile.Name(), }, }, @@ -70,7 +70,7 @@ func TestConfigurationErrors(t *testing.T) { }, { provider: &myProvider{ - baseProvider{ + BaseProvider{ Filename: templateInvalidTOMLFile.Name(), }, }, @@ -125,7 +125,7 @@ func TestGetConfiguration(t *testing.T) { } provider := &myProvider{ - baseProvider{ + BaseProvider{ Filename: templateFile.Name(), }, } diff --git a/provider/zk.go b/provider/zk.go index 2f379aa7a..1dfc0fa7b 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) error { - provider.StoreType = store.ZK + provider.storeType = store.ZK zookeeper.Register() return provider.provide(configurationChan) } diff --git a/server.go b/server.go index d273d3d43..5b70fa1fa 100644 --- a/server.go +++ b/server.go @@ -16,6 +16,7 @@ import ( "github.com/mailgun/oxy/cbreaker" "github.com/mailgun/oxy/forward" "github.com/mailgun/oxy/roundrobin" + "github.com/spf13/viper" "net/http" "net/url" "os" @@ -26,6 +27,8 @@ import ( "time" ) +var oxyLogger = &OxyLogger{} + // Server is the reverse-proxy/load-balancer engine type Server struct { srv *manners.GracefulServer @@ -89,11 +92,11 @@ func (server *Server) Stop() { // Close destroys the server func (server *Server) Close() { - defer close(server.configurationChan) - defer close(server.configurationChanValidated) - defer close(server.sigs) - defer close(server.stopChan) - defer server.loggerMiddleware.Close() + close(server.configurationChan) + close(server.configurationChanValidated) + close(server.sigs) + close(server.stopChan) + server.loggerMiddleware.Close() } func (server *Server) listenProviders() { @@ -101,18 +104,18 @@ func (server *Server) listenProviders() { lastConfigs := make(map[string]*types.ConfigMessage) for { configMsg := <-server.configurationChan - log.Infof("Configuration receveived from provider %s: %#v", configMsg.ProviderName, configMsg.Configuration) + log.Debugf("Configuration receveived from provider %s: %#v", configMsg.ProviderName, configMsg.Configuration) lastConfigs[configMsg.ProviderName] = &configMsg if time.Now().After(lastReceivedConfiguration.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) { - log.Infof("Last %s config received more than %s, OK", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration) + log.Debugf("Last %s config received more than %s, OK", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration) // last config received more than n s ago server.configurationChanValidated <- configMsg } else { - log.Infof("Last %s config received less than %s, waiting...", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration) + log.Debugf("Last %s config received less than %s, waiting...", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration) go func() { <-time.After(server.globalConfiguration.ProvidersThrottleDuration) if time.Now().After(lastReceivedConfiguration.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) { - log.Infof("Waited for %s config, OK", configMsg.ProviderName) + log.Debugf("Waited for %s config, OK", configMsg.ProviderName) server.configurationChanValidated <- *lastConfigs[configMsg.ProviderName] } }() @@ -172,7 +175,7 @@ func (server *Server) configureProviders() { if server.globalConfiguration.File != nil { if len(server.globalConfiguration.File.Filename) == 0 { // no filename, setting to global config file - server.globalConfiguration.File.Filename = *globalConfigFile + server.globalConfiguration.File.Filename = viper.GetString("configFile") } server.providers = append(server.providers, server.globalConfiguration.File) } @@ -298,11 +301,11 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo backends := map[string]http.Handler{} for _, configuration := range configurations { for frontendName, frontend := range configuration.Frontends { - log.Debugf("Creating frontend %s", frontendName) + log.Infof("Creating frontend %s", frontendName) fwd, _ := forward.New(forward.Logger(oxyLogger), forward.PassHostHeader(frontend.PassHostHeader)) newRoute := router.NewRoute().Name(frontendName) for routeName, route := range frontend.Routes { - log.Debugf("Creating route %s %s:%s", routeName, route.Rule, route.Value) + log.Infof("Creating route %s %s:%s", routeName, route.Rule, route.Value) newRouteReflect, err := invoke(newRoute, route.Rule, route.Value) if err != nil { return nil, err @@ -310,7 +313,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo newRoute = newRouteReflect[0].Interface().(*mux.Route) } if backends[frontend.Backend] == nil { - log.Debugf("Creating backend %s", frontend.Backend) + log.Infof("Creating backend %s", frontend.Backend) var lb http.Handler rr, _ := roundrobin.New(fwd) if configuration.Backends[frontend.Backend] == nil { @@ -341,7 +344,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo if err != nil { return nil, err } - log.Infof("Creating server %s %s", serverName, url.String()) + log.Infof("Creating server %s at %s with weight %d", serverName, url.String(), server.Weight) rr.UpsertServer(url, roundrobin.Weight(server.Weight)) } } diff --git a/traefik.go b/traefik.go index 562fd3a62..ea5ef7d62 100644 --- a/traefik.go +++ b/traefik.go @@ -1,74 +1,17 @@ package main import ( - "errors" fmtlog "log" "os" - "reflect" "runtime" - "strings" - - log "github.com/Sirupsen/logrus" - "github.com/emilevauge/traefik/middlewares" - "github.com/thoas/stats" - "gopkg.in/alecthomas/kingpin.v2" -) - -var ( - globalConfigFile = kingpin.Arg("conf", "Main configration file.").Default("traefik.toml").String() - version = kingpin.Flag("version", "Get Version.").Short('v').Bool() - metrics = stats.New() - oxyLogger = &OxyLogger{} ) func main() { runtime.GOMAXPROCS(runtime.NumCPU()) - kingpin.Version(Version + " built on the " + BuildDate) - kingpin.Parse() - fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags) - // load global configuration - globalConfiguration := LoadFileConfig(*globalConfigFile) - - 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) + if err := traefikCmd.Execute(); err != nil { + fmtlog.Println(err) + os.Exit(-1) } - 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 fi.Close() - 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}) - } - log.Debugf("Global configuration loaded %+v", globalConfiguration) - server := NewServer(*globalConfiguration) - server.Start() - server.Close() - log.Info("Shutting down") -} - -// Invoke calls the specified method with the specified arguments on the specified interface. -// It uses the go(lang) reflect package. -func invoke(any interface{}, name string, args ...interface{}) ([]reflect.Value, error) { - inputs := make([]reflect.Value, len(args)) - for i := range args { - inputs[i] = reflect.ValueOf(args[i]) - } - method := reflect.ValueOf(any).MethodByName(name) - if method.IsValid() { - return method.Call(inputs), nil - } - return nil, errors.New("Method not found: " + name) + os.Exit(0) } diff --git a/traefik.sample.toml b/traefik.sample.toml index 020bc4acf..bf5a7b7fc 100644 --- a/traefik.sample.toml +++ b/traefik.sample.toml @@ -78,6 +78,11 @@ # # CertFile = "traefik.crt" # KeyFile = "traefik.key" +# +# Set REST API to read-only mode +# +# Optional +# ReadOnly = false ################################################################ diff --git a/utils.go b/utils.go new file mode 100644 index 000000000..bed6ae7f8 --- /dev/null +++ b/utils.go @@ -0,0 +1,23 @@ +/* +Copyright +*/ +package main + +import ( + "errors" + "reflect" +) + +// Invoke calls the specified method with the specified arguments on the specified interface. +// It uses the go(lang) reflect package. +func invoke(any interface{}, name string, args ...interface{}) ([]reflect.Value, error) { + inputs := make([]reflect.Value, len(args)) + for i := range args { + inputs[i] = reflect.ValueOf(args[i]) + } + method := reflect.ValueOf(any).MethodByName(name) + if method.IsValid() { + return method.Call(inputs), nil + } + return nil, errors.New("Method not found: " + name) +} diff --git a/web.go b/web.go index 3c4eb91a1..6d1f7d3fe 100644 --- a/web.go +++ b/web.go @@ -11,9 +11,12 @@ import ( "github.com/emilevauge/traefik/autogen" "github.com/emilevauge/traefik/types" "github.com/gorilla/mux" + "github.com/thoas/stats" "github.com/unrolled/render" ) +var metrics = stats.New() + // WebProvider is a provider.Provider implementation that provides the UI. // FIXME to be handled another way. type WebProvider struct { From 8adadaa5d443f42025d326cb5b09e6e2e9f172ce Mon Sep 17 00:00:00 2001 From: emile Date: Fri, 22 Jan 2016 12:36:24 +0100 Subject: [PATCH 3/4] Add launch configuration documentation --- cmd.go | 2 +- configuration.go | 10 ++--- docs/index.md | 99 +++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 104 insertions(+), 7 deletions(-) diff --git a/cmd.go b/cmd.go index 81c804d27..5bfeb4851 100644 --- a/cmd.go +++ b/cmd.go @@ -77,7 +77,7 @@ func init() { traefikCmd.PersistentFlags().StringP("graceTimeOut", "g", "10", "Timeout in seconds. Duration to give active requests a chance to finish during hot-reloads") traefikCmd.PersistentFlags().String("accessLogsFile", "log/access.log", "Access logs file") traefikCmd.PersistentFlags().String("traefikLogsFile", "log/traefik.log", "Traefik logs file") - traefikCmd.PersistentFlags().Var(&arguments.Certificates, "certificates", "SSL certificates and keys. You may add several certificate/key pairs to terminate HTTPS for multiple domain names using TLS SNI") + traefikCmd.PersistentFlags().Var(&arguments.Certificates, "certificates", "SSL certificates and keys pair, ie 'tests/traefik.crt,tests/traefik.key'. You may add several certificate/key pairs to terminate HTTPS for multiple domain names using TLS SNI") traefikCmd.PersistentFlags().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.") diff --git a/configuration.go b/configuration.go index 14b74ae87..3bca90541 100644 --- a/configuration.go +++ b/configuration.go @@ -94,11 +94,11 @@ func LoadConfiguration() *GlobalConfiguration { } else { viper.SetConfigName("traefik") // name of config file (without extension) } - 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 - err := viper.ReadInConfig() // Find and read the config file - if err != nil { // Handle errors reading the config file + 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 + err := viper.ReadInConfig() // Find and read the config file + if err != nil { // Handle errors reading the config file fmtlog.Fatalf("Error reading file: %s", err) } if len(arguments.Certificates) > 0 { diff --git a/docs/index.md b/docs/index.md index 3473bd308..fab43ea2b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,6 +5,7 @@ ___ # Documentation - [Basics](#basics) +- [Launch configuration](#launch) - [Global configuration](#global) - [File backend](#file) - [API backend](#api) @@ -38,7 +39,8 @@ Frontends can be defined using the following rules: A frontend is a set of rules that forwards the incoming http traffic to a backend. - You can optionally enable `passHostHeader` to forward client `Host` header to the backend. + You can optionally enable `passHostHeader` to +- []forward client `Host` header to the backend. ### HTTP Backends @@ -59,6 +61,101 @@ For example: - `LatencyAtQuantileMS(50.0) > 50` - `ResponseCodeRatio(500, 600, 0, 600) > 0.5` + +## Launch configuration + +Træfɪk can be configured using a TOML file configuration, arguments, or both. +By default, Træfɪk will try to find a `traefik.toml` in the following places: +- `/etc/traefik/` +- `$HOME/.traefik/` +- `.` the working directory + +You can override this by setting a `configFile` argument: + +```bash +$ traefik --configFile=foo/bar/myconfigfile.toml +``` + +Træfɪk uses the following precedence order. Each item takes precedence over the item below it: + +- arguments +- configuration file +- default + +It means that arguments overrides configuration file. +Each argument is described in the help section: +```bash +$ traefik --help +traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease. +Complete documentation is available at http://traefik.io + +Usage: + traefik [flags] + traefik [command] + +Available Commands: + version Print version + help Help about any command + +Flags: + --accessLogsFile="log/access.log": Access logs file + --boltdb=false: Enable Boltdb backend + --boltdb.endpoint="127.0.0.1:4001": Boltdb server endpoint + --boltdb.filename="": Override default configuration template. For advanced users :) + --boltdb.prefix="/traefik": Prefix used for KV store + --boltdb.watch=true: Watch provider + --certificates=: SSL certificates and keys. You may add several certificate/key pairs to terminate HTTPS for multiple domain names using TLS SNI + -c, --configFile="": Configuration file to use (TOML, JSON, YAML, HCL). + --consul=false: Enable Consul backend + --consul.endpoint="127.0.0.1:8500": Consul server endpoint + --consul.filename="": Override default configuration template. For advanced users :) + --consul.prefix="/traefik": Prefix used for KV store + --consul.watch=true: Watch provider + --docker=false: Enable Docker backend + --docker.domain="": Default domain used + --docker.endpoint="unix:///var/run/docker.sock": Docker server endpoint. Can be a tcp or a unix socket endpoint + --docker.filename="": Override default configuration template. For advanced users :) + --docker.tls=false: Enable Docker TLS support + --docker.tls.ca="": TLS CA + --docker.tls.cert="": TLS cert + --docker.tls.insecureSkipVerify=false: TLS insecure skip verify + --docker.tls.key="": TLS key + --docker.watch=true: Watch provider + --etcd=false: Enable Etcd backend + --etcd.endpoint="127.0.0.1:4001": Etcd server endpoint + --etcd.filename="": Override default configuration template. For advanced users :) + --etcd.prefix="/traefik": Prefix used for KV store + --etcd.watch=true: Watch provider + --file=false: Enable File backend + --file.filename="": Override default configuration template. For advanced users :) + --file.watch=true: Watch provider + -g, --graceTimeOut="10": Timeout in seconds. Duration to give active requests a chance to finish during hot-reloads + -h, --help=false: help for traefik + -l, --logLevel="ERROR": Log level + --marathon=false: Enable Marathon backend + --marathon.domain="": Default domain used + --marathon.endpoint="http://127.0.0.1:8080": Marathon server endpoint. You can also specify multiple endpoint for Marathon + --marathon.filename="": Override default configuration template. For advanced users :) + --marathon.networkInterface="eth0": Network interface used to call Marathon web services. Needed in case of multiple network interfaces + --marathon.watch=true: Watch provider + -p, --port=":80": Reverse proxy port + --providersThrottleDuration=2s: Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time. + --traefikLogsFile="log/traefik.log": Traefik logs file + --web=false: Enable Web backend + --web.address=":8080": Web administration port + --web.cerFile="": SSL certificate + --web.keyFile="": SSL certificate + --web.readOnly=false: Enable read only API + --zookeeper=false: Enable Zookeeper backend + --zookeeper.endpoint="127.0.0.1:2181": Zookeeper server endpoint + --zookeeper.filename="": Override default configuration template. For advanced users :) + --zookeeper.prefix="/traefik": Prefix used for KV store + --zookeeper.watch=true: Watch provider + + +Use "traefik help [command]" for more information about a command. +``` + ## Global configuration ```toml From f1b62b45f4956fd0bf06e200e77dcfda4c2842f4 Mon Sep 17 00:00:00 2001 From: emile Date: Sat, 23 Jan 2016 17:41:56 +0100 Subject: [PATCH 4/4] Fixes following review --- configuration.go | 12 +++++------- glide.yaml | 9 ++++++++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/configuration.go b/configuration.go index 3bca90541..dfb1c68f3 100644 --- a/configuration.go +++ b/configuration.go @@ -1,16 +1,16 @@ package main import ( + "errors" + "fmt" fmtlog "log" + "strings" "time" - "fmt" "github.com/emilevauge/traefik/provider" "github.com/emilevauge/traefik/types" "github.com/mitchellh/mapstructure" "github.com/spf13/viper" - "github.com/wendal/errors" - "strings" ) // GlobalConfiguration holds global configuration (with providers, etc.). @@ -97,8 +97,7 @@ func LoadConfiguration() *GlobalConfiguration { 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 - err := viper.ReadInConfig() // Find and read the config file - if err != nil { // Handle errors reading the config file + if err := viper.ReadInConfig(); err != nil { fmtlog.Fatalf("Error reading file: %s", err) } if len(arguments.Certificates) > 0 { @@ -131,8 +130,7 @@ func LoadConfiguration() *GlobalConfiguration { if arguments.boltdb { viper.Set("boltdb", arguments.Boltdb) } - err = unmarshal(&configuration) - if err != nil { + if err := unmarshal(&configuration); err != nil { fmtlog.Fatalf("Error reading file: %s", err) } diff --git a/glide.yaml b/glide.yaml index 8143a1975..376b55c35 100644 --- a/glide.yaml +++ b/glide.yaml @@ -148,10 +148,17 @@ import: ref: fca8c8854093a154ff1eb580aae10276ad6b1b5f - package: github.com/spf13/cast ref: 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 ref: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325 - package: github.com/spf13/cobra subpackages: - /cobra -