diff --git a/configuration.go b/configuration.go index cf0c43a71..2b00d8e8d 100644 --- a/configuration.go +++ b/configuration.go @@ -47,4 +47,4 @@ type Route struct { type Configuration struct { Backends map[string]Backend Routes map[string]Route -} \ No newline at end of file +} diff --git a/docker.go b/docker.go index 04ace100f..9f8f12325 100644 --- a/docker.go +++ b/docker.go @@ -1,13 +1,14 @@ package main + import ( - "github.com/fsouza/go-dockerclient" - "github.com/leekchan/gtf" "bytes" "github.com/BurntSushi/toml" - "text/template" - "strings" "github.com/BurntSushi/ty/fun" + "github.com/fsouza/go-dockerclient" + "github.com/leekchan/gtf" "strconv" + "strings" + "text/template" ) type DockerProvider struct { @@ -30,7 +31,7 @@ func NewDockerProvider() *DockerProvider { var DockerFuncMap = template.FuncMap{ "getBackend": func(container docker.Container) string { for key, value := range container.Config.Labels { - if (key == "traefik.backend") { + if key == "traefik.backend" { return value } } @@ -38,7 +39,7 @@ var DockerFuncMap = template.FuncMap{ }, "getPort": func(container docker.Container) string { for key, value := range container.Config.Labels { - if (key == "traefik.port") { + if key == "traefik.port" { return value } } @@ -49,7 +50,7 @@ var DockerFuncMap = template.FuncMap{ }, "getWeight": func(container docker.Container) string { for key, value := range container.Config.Labels { - if (key == "traefik.weight") { + if key == "traefik.weight" { return value } } @@ -61,29 +62,29 @@ var DockerFuncMap = template.FuncMap{ "getHost": getHost, } -func (provider *DockerProvider) Provide(configurationChan chan <- *Configuration) { +func (provider *DockerProvider) Provide(configurationChan chan<- *Configuration) { if client, err := docker.NewClient(provider.Endpoint); err != nil { log.Fatalf("Failed to create a client for docker, error: %s", err) } else { provider.dockerClient = client _, err := provider.dockerClient.Info() - if (err != nil){ + if err != nil { log.Fatalf("Docker connection error %+v", err) } log.Debug("Docker connection established") dockerEvents := make(chan *docker.APIEvents) - if (provider.Watch) { + if provider.Watch { provider.dockerClient.AddEventListener(dockerEvents) go func() { for { event := <-dockerEvents - if (event == nil){ + if event == nil { log.Fatalf("Docker connection error %+v", err) } - if(event.Status == "start" || event.Status == "die"){ + if event.Status == "start" || event.Status == "die" { log.Debug("Docker event receveived %+v", event) configuration := provider.loadDockerConfig() - if (configuration != nil) { + if configuration != nil { configurationChan <- configuration } } @@ -110,16 +111,16 @@ func (provider *DockerProvider) loadDockerConfig() *Configuration { // filter containers filteredContainers := fun.Filter(func(container docker.Container) bool { - if (len(container.NetworkSettings.Ports) == 0) { + if len(container.NetworkSettings.Ports) == 0 { log.Debug("Filtering container without port %s", container.Name) return false } _, err := strconv.Atoi(container.Config.Labels["traefik.port"]) - if (len(container.NetworkSettings.Ports) > 1 && err != nil) { + if len(container.NetworkSettings.Ports) > 1 && err != nil { log.Debug("Filtering container with more than 1 port and no traefik.port label %s", container.Name) return false } - if (container.Config.Labels["traefik.enable"] == "false") { + if container.Config.Labels["traefik.enable"] == "false" { log.Debug("Filtering disabled container %s", container.Name) return false } @@ -141,13 +142,13 @@ func (provider *DockerProvider) loadDockerConfig() *Configuration { } gtf.Inject(DockerFuncMap) tmpl := template.New(provider.Filename).Funcs(DockerFuncMap) - if(len(provider.Filename) > 0){ + if len(provider.Filename) > 0 { _, err := tmpl.ParseFiles(provider.Filename) if err != nil { log.Error("Error reading file", err) return nil } - }else{ + } else { buf, err := Asset("providerTemplates/docker.tmpl") if err != nil { log.Error("Error reading file", err) @@ -175,9 +176,9 @@ func (provider *DockerProvider) loadDockerConfig() *Configuration { func getHost(container docker.Container) string { for key, value := range container.Config.Labels { - if (key == "traefik.host") { + if key == "traefik.host" { return value } } return strings.Replace(strings.Replace(container.Name, "/", "", -1), ".", "-", -1) -} \ No newline at end of file +} diff --git a/file.go b/file.go index 3fcaba7c0..876a95b92 100644 --- a/file.go +++ b/file.go @@ -1,15 +1,15 @@ package main import ( - "gopkg.in/fsnotify.v1" "github.com/BurntSushi/toml" + "gopkg.in/fsnotify.v1" "os" "path/filepath" "strings" ) type FileProvider struct { - Watch bool + Watch bool Filename string } @@ -21,7 +21,7 @@ func NewFileProvider() *FileProvider { return fileProvider } -func (provider *FileProvider) Provide(configurationChan chan<- *Configuration){ +func (provider *FileProvider) Provide(configurationChan chan<- *Configuration) { watcher, err := fsnotify.NewWatcher() if err != nil { log.Error("Error creating file watcher", err) @@ -42,10 +42,10 @@ func (provider *FileProvider) Provide(configurationChan chan<- *Configuration){ for { select { case event := <-watcher.Events: - if(strings.Contains(event.Name,file.Name())){ + if strings.Contains(event.Name, file.Name()) { log.Debug("File event:", event) configuration := provider.LoadFileConfig(file.Name()) - if(configuration != nil) { + if configuration != nil { configurationChan <- configuration } } @@ -55,7 +55,7 @@ func (provider *FileProvider) Provide(configurationChan chan<- *Configuration){ } }() - if(provider.Watch){ + if provider.Watch { err = watcher.Add(filepath.Dir(file.Name())) } @@ -64,13 +64,11 @@ func (provider *FileProvider) Provide(configurationChan chan<- *Configuration){ return } - configuration := provider.LoadFileConfig(file.Name()) configurationChan <- configuration <-done } - func (provider *FileProvider) LoadFileConfig(filename string) *Configuration { configuration := new(Configuration) if _, err := toml.DecodeFile(filename, configuration); err != nil { @@ -78,4 +76,4 @@ func (provider *FileProvider) LoadFileConfig(filename string) *Configuration { return nil } return configuration -} \ No newline at end of file +} diff --git a/generate.go b/generate.go index d9839c60a..4757a70bd 100644 --- a/generate.go +++ b/generate.go @@ -6,4 +6,4 @@ Copyright //go:generate rm -vf gen.go //go:generate go-bindata -o gen.go static/... templates/... providerTemplates/... -package main \ No newline at end of file +package main diff --git a/marathon.go b/marathon.go index 4715a8621..cba8831d2 100644 --- a/marathon.go +++ b/marathon.go @@ -1,13 +1,14 @@ package main + import ( - "github.com/gambol99/go-marathon" - "github.com/leekchan/gtf" "bytes" "github.com/BurntSushi/toml" - "text/template" - "strings" - "strconv" "github.com/BurntSushi/ty/fun" + "github.com/gambol99/go-marathon" + "github.com/leekchan/gtf" + "strconv" + "strings" + "text/template" ) type MarathonProvider struct { @@ -38,7 +39,7 @@ var MarathonFuncMap = template.FuncMap{ }, "getHost": func(application marathon.Application) string { for key, value := range application.Labels { - if (key == "traefik.host") { + if key == "traefik.host" { return value } } @@ -46,7 +47,7 @@ var MarathonFuncMap = template.FuncMap{ }, "getWeight": func(application marathon.Application) string { for key, value := range application.Labels { - if (key == "traefik.weight") { + if key == "traefik.weight" { return value } } @@ -57,7 +58,7 @@ var MarathonFuncMap = template.FuncMap{ }, } -func (provider *MarathonProvider) Provide(configurationChan chan <- *Configuration) { +func (provider *MarathonProvider) Provide(configurationChan chan<- *Configuration) { config := marathon.NewDefaultConfig() config.URL = provider.Endpoint config.EventsInterface = provider.NetworkInterface @@ -67,7 +68,7 @@ func (provider *MarathonProvider) Provide(configurationChan chan <- *Configurati } else { provider.marathonClient = client update := make(marathon.EventsChannel, 5) - if (provider.Watch) { + if provider.Watch { if err := client.AddEventsListener(update, marathon.EVENTS_APPLICATIONS); err != nil { log.Error("Failed to register for subscriptions, %s", err) } else { @@ -76,7 +77,7 @@ func (provider *MarathonProvider) Provide(configurationChan chan <- *Configurati event := <-update log.Debug("Marathon event receveived", event) configuration := provider.loadMarathonConfig() - if (configuration != nil) { + if configuration != nil { configurationChan <- configuration } } @@ -93,30 +94,30 @@ func (provider *MarathonProvider) loadMarathonConfig() *Configuration { configuration := new(Configuration) applications, err := provider.marathonClient.Applications(nil) - if (err != nil) { + if err != nil { log.Error("Failed to create a client for marathon, error: %s", err) return nil } tasks, err := provider.marathonClient.AllTasks() - if (err != nil) { + if err != nil { log.Error("Failed to create a client for marathon, error: %s", err) return nil } //filter tasks filteredTasks := fun.Filter(func(task marathon.Task) bool { - if (len(task.Ports) == 0) { + if len(task.Ports) == 0 { log.Debug("Filtering marathon task without port", task.AppID) return false } application := getApplication(task, applications.Apps) _, err := strconv.Atoi(application.Labels["traefik.port"]) - if (len(application.Ports) > 1 && err != nil) { + if len(application.Ports) > 1 && err != nil { log.Debug("Filtering marathon task with more than 1 port and no traefik.port label", task.AppID) return false } - if (application.Labels["traefik.enable"] == "false") { + if application.Labels["traefik.enable"] == "false" { log.Debug("Filtering disabled marathon task", task.AppID) return false } @@ -126,12 +127,12 @@ func (provider *MarathonProvider) loadMarathonConfig() *Configuration { //filter apps filteredApps := fun.Filter(func(app marathon.Application) bool { //get ports from app tasks - if (!fun.Exists(func(task marathon.Task) bool { - if (task.AppID == app.ID) { + if !fun.Exists(func(task marathon.Task) bool { + if task.AppID == app.ID { return true } return false - }, filteredTasks)) { + }, filteredTasks) { return false } return true @@ -149,13 +150,13 @@ func (provider *MarathonProvider) loadMarathonConfig() *Configuration { gtf.Inject(MarathonFuncMap) tmpl := template.New(provider.Filename).Funcs(DockerFuncMap) - if(len(provider.Filename) > 0){ + if len(provider.Filename) > 0 { _, err := tmpl.ParseFiles(provider.Filename) if err != nil { log.Error("Error reading file", err) return nil } - }else{ + } else { buf, err := Asset("providerTemplates/marathon.tmpl") if err != nil { log.Error("Error reading file", err) @@ -185,9 +186,9 @@ func (provider *MarathonProvider) loadMarathonConfig() *Configuration { func getApplication(task marathon.Task, apps []marathon.Application) *marathon.Application { for _, application := range apps { - if (application.ID == task.AppID) { + if application.ID == task.AppID { return &application } } return nil -} \ No newline at end of file +} diff --git a/middlewares/logger.go b/middlewares/logger.go new file mode 100644 index 000000000..33c8777bf --- /dev/null +++ b/middlewares/logger.go @@ -0,0 +1,37 @@ +/* +Copyright +*/ +package middlewares + +import ( + "log" + "net/http" + "os" + "github.com/gorilla/handlers" +) + +// Logger is a middleware handler that logs the request as it goes in and the response as it goes out. +type Logger struct { + file *os.File +} + +// NewLogger returns a new Logger instance +func NewLogger(file string) *Logger { + if (len(file) > 0 ) { + fi, err := os.OpenFile(file, os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666) + if err != nil { + log.Fatal("Error opening file", err) + } + return &Logger{fi} + }else { + return &Logger{nil} + } +} + +func (l *Logger) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { + if(l.file == nil){ + next(rw, r) + }else{ + handlers.CombinedLoggingHandler(l.file, next).ServeHTTP(rw, r) + } +} \ No newline at end of file diff --git a/traefik.go b/traefik.go index e2ee2b5a2..f2abeb083 100644 --- a/traefik.go +++ b/traefik.go @@ -1,10 +1,17 @@ package main import ( + "github.com/BurntSushi/toml" + "github.com/codegangsta/negroni" "github.com/gorilla/mux" "github.com/mailgun/oxy/forward" "github.com/mailgun/oxy/roundrobin" + "github.com/op/go-logging" + "github.com/thoas/stats" "github.com/tylerb/graceful" + "github.com/unrolled/render" + "gopkg.in/alecthomas/kingpin.v2" + "./middlewares" "net/http" "net/url" "os" @@ -12,23 +19,16 @@ import ( "reflect" "syscall" "time" - "github.com/op/go-logging" - "github.com/BurntSushi/toml" - "github.com/gorilla/handlers" - "github.com/unrolled/render" - "gopkg.in/alecthomas/kingpin.v2" - "github.com/thoas/stats" ) - var ( - globalConfigFile = kingpin.Arg("conf", "Main configration file.").Default("traefik.toml").String() + globalConfigFile = kingpin.Arg("conf", "Main configration file.").Default("traefik.toml").String() currentConfiguration = new(Configuration) - metrics = stats.New() - log = logging.MustGetLogger("traefik") - templatesRenderer = render.New(render.Options{ - Directory: "templates", - Asset: Asset, + metrics = stats.New() + log = logging.MustGetLogger("traefik") + templatesRenderer = render.New(render.Options{ + Directory: "templates", + Asset: Asset, AssetNames: AssetNames, }) ) @@ -53,11 +53,11 @@ func main() { log.Fatal("Error getting level", err) } - if (len(gloablConfiguration.TraefikLogsFile) > 0 ) { - fi, err := os.OpenFile(gloablConfiguration.TraefikLogsFile, os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666) + if len(gloablConfiguration.TraefikLogsFile) > 0 { + fi, err := os.OpenFile(gloablConfiguration.TraefikLogsFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { log.Fatal("Error opening file", err) - }else { + } else { logBackend := logging.NewLogBackend(fi, "", 0) logBackendFormatter := logging.NewBackendFormatter(logBackend, logging.GlogFormatter) logBackendLeveled := logging.AddModuleLevel(logBackend) @@ -65,7 +65,7 @@ func main() { backends = append(backends, logBackendFormatter) } } - if (gloablConfiguration.TraefikLogsStdout) { + if gloablConfiguration.TraefikLogsStdout { logBackend := logging.NewLogBackend(os.Stdout, "", 0) logBackendFormatter := logging.NewBackendFormatter(logBackend, format) logBackendLeveled := logging.AddModuleLevel(logBackend) @@ -74,7 +74,6 @@ func main() { } logging.SetBackend(backends...) - configurationRouter = LoadDefaultConfig(gloablConfiguration) // listen new configurations from providers @@ -84,32 +83,32 @@ func main() { log.Info("Configuration receveived %+v", configuration) if configuration == nil { log.Info("Skipping empty configuration") - } else if (reflect.DeepEqual(currentConfiguration, configuration)) { + } else if reflect.DeepEqual(currentConfiguration, configuration) { log.Info("Skipping same configuration") } else { currentConfiguration = configuration configurationRouter = LoadConfig(configuration, gloablConfiguration) - srv.Stop(10 * time.Second) + srv.Stop(time.Duration(gloablConfiguration.GraceTimeOut) * time.Second) time.Sleep(3 * time.Second) } } }() // configure providers - if (gloablConfiguration.Docker != nil) { + if gloablConfiguration.Docker != nil { providers = append(providers, gloablConfiguration.Docker) } - if (gloablConfiguration.Marathon != nil) { + if gloablConfiguration.Marathon != nil { providers = append(providers, gloablConfiguration.Marathon) } - if (gloablConfiguration.File != nil) { - if (len(gloablConfiguration.File.Filename) == 0) { + if gloablConfiguration.File != nil { + if len(gloablConfiguration.File.Filename) == 0 { // no filename, setting to global config file gloablConfiguration.File.Filename = *globalConfigFile } providers = append(providers, gloablConfiguration.File) } - if (gloablConfiguration.Web != nil) { + if gloablConfiguration.Web != nil { providers = append(providers, gloablConfiguration.Web) } @@ -134,18 +133,25 @@ func main() { if goAway { break } + + // middlewares + var negroni = negroni.New() + negroni.Use(metrics) + negroni.Use(middlewares.NewLogger(gloablConfiguration.AccessLogsFile)) + negroni.UseHandler(configurationRouter) + srv = &graceful.Server{ - Timeout: time.Duration(gloablConfiguration.GraceTimeOut) * time.Second, + Timeout: time.Duration(gloablConfiguration.GraceTimeOut) * time.Second, NoSignalHandling: true, Server: &http.Server{ Addr: gloablConfiguration.Port, - Handler: metrics.Handler(configurationRouter), + Handler: negroni, }, } go func() { - if (len(gloablConfiguration.CertFile) > 0 && len(gloablConfiguration.KeyFile) > 0) { + if len(gloablConfiguration.CertFile) > 0 && len(gloablConfiguration.KeyFile) > 0 { srv.ListenAndServeTLS(gloablConfiguration.CertFile, gloablConfiguration.KeyFile) } else { srv.ListenAndServe() @@ -163,29 +169,13 @@ func notFoundHandler(w http.ResponseWriter, r *http.Request) { func LoadDefaultConfig(gloablConfiguration *GlobalConfiguration) *mux.Router { router := mux.NewRouter() - if (len(gloablConfiguration.AccessLogsFile) > 0 ) { - fi, err := os.OpenFile(gloablConfiguration.AccessLogsFile, os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666) - if err != nil { - log.Fatal("Error opening file", err) - } - router.NotFoundHandler = handlers.CombinedLoggingHandler(fi, http.HandlerFunc(notFoundHandler)) - }else { - router.NotFoundHandler = http.HandlerFunc(notFoundHandler) - } + router.NotFoundHandler = http.HandlerFunc(notFoundHandler) return router } func LoadConfig(configuration *Configuration, gloablConfiguration *GlobalConfiguration) *mux.Router { router := mux.NewRouter() - if (len(gloablConfiguration.AccessLogsFile) > 0 ) { - fi, err := os.OpenFile(gloablConfiguration.AccessLogsFile, os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666) - if err != nil { - log.Fatal("Error opening file", err) - } - router.NotFoundHandler = handlers.CombinedLoggingHandler(fi, http.HandlerFunc(notFoundHandler)) - }else { - router.NotFoundHandler = http.HandlerFunc(notFoundHandler) - } + router.NotFoundHandler = http.HandlerFunc(notFoundHandler) backends := map[string]http.Handler{} for routeName, route := range configuration.Routes { log.Debug("Creating route %s", routeName) @@ -196,7 +186,7 @@ func LoadConfig(configuration *Configuration, gloablConfiguration *GlobalConfigu newRouteReflect := Invoke(newRoute, rule.Category, rule.Value) newRoute = newRouteReflect[0].Interface().(*mux.Route) } - if (backends[route.Backend] ==nil) { + if backends[route.Backend] == nil { log.Debug("Creating backend %s", route.Backend) lb, _ := roundrobin.New(fwd) rb, _ := roundrobin.NewRebalancer(lb) @@ -205,19 +195,11 @@ func LoadConfig(configuration *Configuration, gloablConfiguration *GlobalConfigu url, _ := url.Parse(server.Url) rb.UpsertServer(url, roundrobin.Weight(server.Weight)) } - backends[route.Backend]=lb - }else { + backends[route.Backend] = lb + } else { log.Debug("Reusing backend %s", route.Backend) } - if (len(gloablConfiguration.AccessLogsFile) > 0 ) { - fi, err := os.OpenFile(gloablConfiguration.AccessLogsFile, os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666) - if err != nil { - log.Fatal("Error opening file ", err) - } - newRoute.Handler(handlers.CombinedLoggingHandler(fi, backends[route.Backend])) - }else { - newRoute.Handler(backends[route.Backend]) - } + newRoute.Handler(backends[route.Backend]) err := newRoute.GetError() if err != nil { log.Error("Error building route ", err) @@ -241,4 +223,4 @@ func LoadFileConfig(file string) *GlobalConfiguration { } log.Debug("Global configuration loaded %+v", configuration) return configuration -} \ No newline at end of file +} diff --git a/web.go b/web.go index cd434d760..16b486de2 100644 --- a/web.go +++ b/web.go @@ -1,12 +1,12 @@ package main import ( - "github.com/gorilla/mux" - "net/http" - "fmt" - "io/ioutil" "encoding/json" + "fmt" "github.com/elazarl/go-bindata-assetfs" + "github.com/gorilla/mux" + "io/ioutil" + "net/http" ) type WebProvider struct { @@ -17,7 +17,7 @@ type Page struct { Configuration Configuration } -func (provider *WebProvider) Provide(configurationChan chan <- *Configuration) { +func (provider *WebProvider) Provide(configurationChan chan<- *Configuration) { systemRouter := mux.NewRouter() systemRouter.Methods("GET").PathPrefix("/web/").Handler(http.HandlerFunc(GetHtmlConfigHandler)) systemRouter.Methods("GET").PathPrefix("/metrics/").Handler(http.HandlerFunc(GetStatsHandler)) @@ -27,10 +27,10 @@ func (provider *WebProvider) Provide(configurationChan chan <- *Configuration) { configuration := new(Configuration) b, _ := ioutil.ReadAll(r.Body) err := json.Unmarshal(b, configuration) - if (err == nil) { + if err == nil { configurationChan <- configuration GetConfigHandler(rw, r) - }else { + } else { log.Error("Error parsing configuration %+v\n", err) http.Error(rw, fmt.Sprintf("%+v", err), http.StatusBadRequest) } @@ -45,7 +45,7 @@ func GetConfigHandler(rw http.ResponseWriter, r *http.Request) { } func GetHtmlConfigHandler(response http.ResponseWriter, request *http.Request) { - templatesRenderer.HTML(response, http.StatusOK, "configuration", Page{Configuration:*currentConfiguration}) + templatesRenderer.HTML(response, http.StatusOK, "configuration", Page{Configuration: *currentConfiguration}) } func GetStatsHandler(rw http.ResponseWriter, r *http.Request) {