Split Web into API/Dashboard, ping, metric and Rest Provider
This commit is contained in:
parent
384488ac02
commit
27d1b46835
24 changed files with 1252 additions and 377 deletions
23
api/dashboard.go
Normal file
23
api/dashboard.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/containous/mux"
|
||||||
|
"github.com/containous/traefik/autogen"
|
||||||
|
"github.com/elazarl/go-bindata-assetfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DashboardHandler expose dashboard routes
|
||||||
|
type DashboardHandler struct{}
|
||||||
|
|
||||||
|
// AddRoutes add dashboard routes on a router
|
||||||
|
func (g DashboardHandler) AddRoutes(router *mux.Router) {
|
||||||
|
// Expose dashboard
|
||||||
|
router.Methods("GET").Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||||
|
http.Redirect(response, request, "/dashboard/", 302)
|
||||||
|
})
|
||||||
|
router.Methods("GET").PathPrefix("/dashboard/").
|
||||||
|
Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: autogen.Asset, AssetInfo: autogen.AssetInfo, AssetDir: autogen.AssetDir, Prefix: "static"})))
|
||||||
|
|
||||||
|
}
|
39
api/debug.go
Normal file
39
api/debug.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"expvar"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/containous/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
expvar.Publish("Goroutines", expvar.Func(goroutines))
|
||||||
|
}
|
||||||
|
|
||||||
|
func goroutines() interface{} {
|
||||||
|
return runtime.NumGoroutine()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DebugHandler expose debug routes
|
||||||
|
type DebugHandler struct{}
|
||||||
|
|
||||||
|
// AddRoutes add debug routes on a router
|
||||||
|
func (g DebugHandler) AddRoutes(router *mux.Router) {
|
||||||
|
router.Methods("GET").Path("/debug/vars").
|
||||||
|
HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
fmt.Fprint(w, "{\n")
|
||||||
|
first := true
|
||||||
|
expvar.Do(func(kv expvar.KeyValue) {
|
||||||
|
if !first {
|
||||||
|
fmt.Fprint(w, ",\n")
|
||||||
|
}
|
||||||
|
first = false
|
||||||
|
fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
|
||||||
|
})
|
||||||
|
fmt.Fprint(w, "\n}\n")
|
||||||
|
})
|
||||||
|
}
|
250
api/handler.go
Normal file
250
api/handler.go
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/containous/mux"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
"github.com/containous/traefik/middlewares"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
|
"github.com/containous/traefik/version"
|
||||||
|
thoas_stats "github.com/thoas/stats"
|
||||||
|
"github.com/unrolled/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler expose api routes
|
||||||
|
type Handler struct {
|
||||||
|
EntryPoint string `description:"EntryPoint" export:"true"`
|
||||||
|
Dashboard bool `description:"Activate dashboard" export:"true"`
|
||||||
|
Debug bool `export:"true"`
|
||||||
|
CurrentConfigurations *safe.Safe
|
||||||
|
Statistics *types.Statistics `description:"Enable more detailed statistics" export:"true"`
|
||||||
|
Stats *thoas_stats.Stats
|
||||||
|
StatsRecorder *middlewares.StatsRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
templatesRenderer = render.New(render.Options{
|
||||||
|
Directory: "nowhere",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddRoutes add api routes on a router
|
||||||
|
func (p Handler) AddRoutes(router *mux.Router) {
|
||||||
|
if p.Debug {
|
||||||
|
DebugHandler{}.AddRoutes(router)
|
||||||
|
}
|
||||||
|
|
||||||
|
router.Methods("GET").Path("/api").HandlerFunc(p.getConfigHandler)
|
||||||
|
router.Methods("GET").Path("/api/providers").HandlerFunc(p.getConfigHandler)
|
||||||
|
router.Methods("GET").Path("/api/providers/{provider}").HandlerFunc(p.getProviderHandler)
|
||||||
|
router.Methods("GET").Path("/api/providers/{provider}/backends").HandlerFunc(p.getBackendsHandler)
|
||||||
|
router.Methods("GET").Path("/api/providers/{provider}/backends/{backend}").HandlerFunc(p.getBackendHandler)
|
||||||
|
router.Methods("GET").Path("/api/providers/{provider}/backends/{backend}/servers").HandlerFunc(p.getServersHandler)
|
||||||
|
router.Methods("GET").Path("/api/providers/{provider}/backends/{backend}/servers/{server}").HandlerFunc(p.getServerHandler)
|
||||||
|
router.Methods("GET").Path("/api/providers/{provider}/frontends").HandlerFunc(p.getFrontendsHandler)
|
||||||
|
router.Methods("GET").Path("/api/providers/{provider}/frontends/{frontend}").HandlerFunc(p.getFrontendHandler)
|
||||||
|
router.Methods("GET").Path("/api/providers/{provider}/frontends/{frontend}/routes").HandlerFunc(p.getRoutesHandler)
|
||||||
|
router.Methods("GET").Path("/api/providers/{provider}/frontends/{frontend}/routes/{route}").HandlerFunc(p.getRouteHandler)
|
||||||
|
|
||||||
|
// health route
|
||||||
|
router.Methods("GET").Path("/health").HandlerFunc(p.getHealthHandler)
|
||||||
|
|
||||||
|
version.Handler{}.AddRoutes(router)
|
||||||
|
|
||||||
|
if p.Dashboard {
|
||||||
|
DashboardHandler{}.AddRoutes(router)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProviderIDFromVars(vars map[string]string) string {
|
||||||
|
providerID := vars["provider"]
|
||||||
|
// TODO: Deprecated
|
||||||
|
if providerID == "rest" {
|
||||||
|
providerID = "web"
|
||||||
|
}
|
||||||
|
return providerID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Handler) getConfigHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||||
|
err := templatesRenderer.JSON(response, http.StatusOK, currentConfigurations)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Handler) getProviderHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
providerID := getProviderIDFromVars(mux.Vars(request))
|
||||||
|
|
||||||
|
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||||
|
if provider, ok := currentConfigurations[providerID]; ok {
|
||||||
|
err := templatesRenderer.JSON(response, http.StatusOK, provider)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
http.NotFound(response, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Handler) getBackendsHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
providerID := getProviderIDFromVars(mux.Vars(request))
|
||||||
|
|
||||||
|
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||||
|
if provider, ok := currentConfigurations[providerID]; ok {
|
||||||
|
err := templatesRenderer.JSON(response, http.StatusOK, provider.Backends)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
http.NotFound(response, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Handler) getBackendHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
vars := mux.Vars(request)
|
||||||
|
providerID := getProviderIDFromVars(vars)
|
||||||
|
backendID := vars["backend"]
|
||||||
|
|
||||||
|
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||||
|
if provider, ok := currentConfigurations[providerID]; ok {
|
||||||
|
if backend, ok := provider.Backends[backendID]; ok {
|
||||||
|
err := templatesRenderer.JSON(response, http.StatusOK, backend)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.NotFound(response, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Handler) getServersHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
vars := mux.Vars(request)
|
||||||
|
providerID := getProviderIDFromVars(vars)
|
||||||
|
backendID := vars["backend"]
|
||||||
|
|
||||||
|
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||||
|
if provider, ok := currentConfigurations[providerID]; ok {
|
||||||
|
if backend, ok := provider.Backends[backendID]; ok {
|
||||||
|
err := templatesRenderer.JSON(response, http.StatusOK, backend.Servers)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.NotFound(response, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Handler) getServerHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
vars := mux.Vars(request)
|
||||||
|
providerID := getProviderIDFromVars(vars)
|
||||||
|
backendID := vars["backend"]
|
||||||
|
serverID := vars["server"]
|
||||||
|
|
||||||
|
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||||
|
if provider, ok := currentConfigurations[providerID]; ok {
|
||||||
|
if backend, ok := provider.Backends[backendID]; ok {
|
||||||
|
if server, ok := backend.Servers[serverID]; ok {
|
||||||
|
err := templatesRenderer.JSON(response, http.StatusOK, server)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.NotFound(response, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Handler) getFrontendsHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
providerID := getProviderIDFromVars(mux.Vars(request))
|
||||||
|
|
||||||
|
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||||
|
if provider, ok := currentConfigurations[providerID]; ok {
|
||||||
|
err := templatesRenderer.JSON(response, http.StatusOK, provider.Frontends)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
http.NotFound(response, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Handler) getFrontendHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
vars := mux.Vars(request)
|
||||||
|
providerID := getProviderIDFromVars(vars)
|
||||||
|
frontendID := vars["frontend"]
|
||||||
|
|
||||||
|
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||||
|
if provider, ok := currentConfigurations[providerID]; ok {
|
||||||
|
if frontend, ok := provider.Frontends[frontendID]; ok {
|
||||||
|
err := templatesRenderer.JSON(response, http.StatusOK, frontend)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.NotFound(response, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Handler) getRoutesHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
vars := mux.Vars(request)
|
||||||
|
providerID := getProviderIDFromVars(vars)
|
||||||
|
frontendID := vars["frontend"]
|
||||||
|
|
||||||
|
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||||
|
if provider, ok := currentConfigurations[providerID]; ok {
|
||||||
|
if frontend, ok := provider.Frontends[frontendID]; ok {
|
||||||
|
err := templatesRenderer.JSON(response, http.StatusOK, frontend.Routes)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.NotFound(response, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Handler) getRouteHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
vars := mux.Vars(request)
|
||||||
|
providerID := getProviderIDFromVars(vars)
|
||||||
|
frontendID := vars["frontend"]
|
||||||
|
routeID := vars["route"]
|
||||||
|
|
||||||
|
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||||
|
if provider, ok := currentConfigurations[providerID]; ok {
|
||||||
|
if frontend, ok := provider.Frontends[frontendID]; ok {
|
||||||
|
if route, ok := frontend.Routes[routeID]; ok {
|
||||||
|
err := templatesRenderer.JSON(response, http.StatusOK, route)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.NotFound(response, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
// healthResponse combines data returned by thoas/stats with statistics (if
|
||||||
|
// they are enabled).
|
||||||
|
type healthResponse struct {
|
||||||
|
*thoas_stats.Data
|
||||||
|
*middlewares.Stats
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Handler) getHealthHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
health := &healthResponse{Data: p.Stats.Data()}
|
||||||
|
if p.StatsRecorder != nil {
|
||||||
|
health.Stats = p.StatsRecorder.Data()
|
||||||
|
}
|
||||||
|
err := templatesRenderer.JSON(response, http.StatusOK, health)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"github.com/containous/flaeg"
|
"github.com/containous/flaeg"
|
||||||
"github.com/containous/traefik/acme"
|
"github.com/containous/traefik/acme"
|
||||||
"github.com/containous/traefik/configuration"
|
"github.com/containous/traefik/configuration"
|
||||||
"github.com/containous/traefik/middlewares"
|
|
||||||
"github.com/containous/traefik/provider"
|
"github.com/containous/traefik/provider"
|
||||||
"github.com/containous/traefik/provider/boltdb"
|
"github.com/containous/traefik/provider/boltdb"
|
||||||
"github.com/containous/traefik/provider/consul"
|
"github.com/containous/traefik/provider/consul"
|
||||||
|
@ -23,12 +22,9 @@ import (
|
||||||
"github.com/containous/traefik/provider/marathon"
|
"github.com/containous/traefik/provider/marathon"
|
||||||
"github.com/containous/traefik/provider/mesos"
|
"github.com/containous/traefik/provider/mesos"
|
||||||
"github.com/containous/traefik/provider/rancher"
|
"github.com/containous/traefik/provider/rancher"
|
||||||
"github.com/containous/traefik/provider/web"
|
|
||||||
"github.com/containous/traefik/provider/zk"
|
"github.com/containous/traefik/provider/zk"
|
||||||
"github.com/containous/traefik/safe"
|
|
||||||
traefikTls "github.com/containous/traefik/tls"
|
traefikTls "github.com/containous/traefik/tls"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
thoas_stats "github.com/thoas/stats"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDo_globalConfiguration(t *testing.T) {
|
func TestDo_globalConfiguration(t *testing.T) {
|
||||||
|
@ -247,7 +243,7 @@ func TestDo_globalConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
Directory: "file Directory",
|
Directory: "file Directory",
|
||||||
}
|
}
|
||||||
config.Web = &web.Provider{
|
config.Web = &configuration.WebCompatibility{
|
||||||
Address: "web Address",
|
Address: "web Address",
|
||||||
CertFile: "web CertFile",
|
CertFile: "web CertFile",
|
||||||
KeyFile: "web KeyFile",
|
KeyFile: "web KeyFile",
|
||||||
|
@ -290,15 +286,6 @@ func TestDo_globalConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Debug: true,
|
Debug: true,
|
||||||
CurrentConfigurations: &safe.Safe{},
|
|
||||||
Stats: &thoas_stats.Stats{
|
|
||||||
Uptime: time.Now(),
|
|
||||||
Pid: 666,
|
|
||||||
ResponseCounts: map[string]int{"foo": 1, "fii": 2, "fuu": 3},
|
|
||||||
TotalResponseCounts: map[string]int{"foo": 1, "fii": 2, "fuu": 3},
|
|
||||||
TotalResponseTime: time.Now(),
|
|
||||||
},
|
|
||||||
StatsRecorder: &middlewares.StatsRecorder{},
|
|
||||||
}
|
}
|
||||||
config.Marathon = &marathon.Provider{
|
config.Marathon = &marathon.Provider{
|
||||||
BaseProvider: provider.BaseProvider{
|
BaseProvider: provider.BaseProvider{
|
||||||
|
|
|
@ -4,8 +4,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containous/flaeg"
|
"github.com/containous/flaeg"
|
||||||
|
"github.com/containous/traefik/api"
|
||||||
"github.com/containous/traefik/configuration"
|
"github.com/containous/traefik/configuration"
|
||||||
"github.com/containous/traefik/middlewares/accesslog"
|
"github.com/containous/traefik/middlewares/accesslog"
|
||||||
|
"github.com/containous/traefik/ping"
|
||||||
"github.com/containous/traefik/provider/boltdb"
|
"github.com/containous/traefik/provider/boltdb"
|
||||||
"github.com/containous/traefik/provider/consul"
|
"github.com/containous/traefik/provider/consul"
|
||||||
"github.com/containous/traefik/provider/docker"
|
"github.com/containous/traefik/provider/docker"
|
||||||
|
@ -18,7 +20,7 @@ import (
|
||||||
"github.com/containous/traefik/provider/marathon"
|
"github.com/containous/traefik/provider/marathon"
|
||||||
"github.com/containous/traefik/provider/mesos"
|
"github.com/containous/traefik/provider/mesos"
|
||||||
"github.com/containous/traefik/provider/rancher"
|
"github.com/containous/traefik/provider/rancher"
|
||||||
"github.com/containous/traefik/provider/web"
|
"github.com/containous/traefik/provider/rest"
|
||||||
"github.com/containous/traefik/provider/zk"
|
"github.com/containous/traefik/provider/zk"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
)
|
)
|
||||||
|
@ -43,14 +45,18 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||||
defaultFile.Watch = true
|
defaultFile.Watch = true
|
||||||
defaultFile.Filename = "" //needs equivalent to viper.ConfigFileUsed()
|
defaultFile.Filename = "" //needs equivalent to viper.ConfigFileUsed()
|
||||||
|
|
||||||
// default Web
|
// default Rest
|
||||||
var defaultWeb web.Provider
|
var defaultRest rest.Provider
|
||||||
|
defaultRest.EntryPoint = configuration.DefaultInternalEntryPointName
|
||||||
|
|
||||||
|
// TODO: Deprecated - Web provider, use REST provider instead
|
||||||
|
var defaultWeb configuration.WebCompatibility
|
||||||
defaultWeb.Address = ":8080"
|
defaultWeb.Address = ":8080"
|
||||||
defaultWeb.Statistics = &types.Statistics{
|
defaultWeb.Statistics = &types.Statistics{
|
||||||
RecentErrors: 10,
|
RecentErrors: 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
// default Metrics
|
// TODO: Deprecated - default Metrics
|
||||||
defaultWeb.Metrics = &types.Metrics{
|
defaultWeb.Metrics = &types.Metrics{
|
||||||
Prometheus: &types.Prometheus{
|
Prometheus: &types.Prometheus{
|
||||||
Buckets: types.Buckets{0.1, 0.3, 1.2, 5},
|
Buckets: types.Buckets{0.1, 0.3, 1.2, 5},
|
||||||
|
@ -157,6 +163,11 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||||
var defaultEureka eureka.Provider
|
var defaultEureka eureka.Provider
|
||||||
defaultEureka.Delay = "30s"
|
defaultEureka.Delay = "30s"
|
||||||
|
|
||||||
|
// default Ping
|
||||||
|
var defaultPing = ping.Handler{
|
||||||
|
EntryPoint: "traefik",
|
||||||
|
}
|
||||||
|
|
||||||
// default TraefikLog
|
// default TraefikLog
|
||||||
defaultTraefikLog := types.TraefikLog{
|
defaultTraefikLog := types.TraefikLog{
|
||||||
Format: "common",
|
Format: "common",
|
||||||
|
@ -189,10 +200,40 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||||
GraceTimeOut: flaeg.Duration(configuration.DefaultGraceTimeout),
|
GraceTimeOut: flaeg.Duration(configuration.DefaultGraceTimeout),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// default ApiConfiguration
|
||||||
|
defaultAPI := api.Handler{
|
||||||
|
EntryPoint: "traefik",
|
||||||
|
Dashboard: true,
|
||||||
|
}
|
||||||
|
defaultAPI.Statistics = &types.Statistics{
|
||||||
|
RecentErrors: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
// default Metrics
|
||||||
|
defaultMetrics := types.Metrics{
|
||||||
|
Prometheus: &types.Prometheus{
|
||||||
|
Buckets: types.Buckets{0.1, 0.3, 1.2, 5},
|
||||||
|
EntryPoint: "traefik",
|
||||||
|
},
|
||||||
|
Datadog: &types.Datadog{
|
||||||
|
Address: "localhost:8125",
|
||||||
|
PushInterval: "10s",
|
||||||
|
},
|
||||||
|
StatsD: &types.Statsd{
|
||||||
|
Address: "localhost:8125",
|
||||||
|
PushInterval: "10s",
|
||||||
|
},
|
||||||
|
InfluxDB: &types.InfluxDB{
|
||||||
|
Address: "localhost:8089",
|
||||||
|
PushInterval: "10s",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
defaultConfiguration := configuration.GlobalConfiguration{
|
defaultConfiguration := configuration.GlobalConfiguration{
|
||||||
Docker: &defaultDocker,
|
Docker: &defaultDocker,
|
||||||
File: &defaultFile,
|
File: &defaultFile,
|
||||||
Web: &defaultWeb,
|
Web: &defaultWeb,
|
||||||
|
Rest: &defaultRest,
|
||||||
Marathon: &defaultMarathon,
|
Marathon: &defaultMarathon,
|
||||||
Consul: &defaultConsul,
|
Consul: &defaultConsul,
|
||||||
ConsulCatalog: &defaultConsulCatalog,
|
ConsulCatalog: &defaultConsulCatalog,
|
||||||
|
@ -212,6 +253,9 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||||
TraefikLog: &defaultTraefikLog,
|
TraefikLog: &defaultTraefikLog,
|
||||||
AccessLog: &defaultAccessLog,
|
AccessLog: &defaultAccessLog,
|
||||||
LifeCycle: &defaultLifeycle,
|
LifeCycle: &defaultLifeycle,
|
||||||
|
Ping: &defaultPing,
|
||||||
|
API: &defaultAPI,
|
||||||
|
Metrics: &defaultMetrics,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &TraefikConfiguration{
|
return &TraefikConfiguration{
|
||||||
|
|
|
@ -102,19 +102,25 @@ Complete documentation is available at https://traefik.io`,
|
||||||
|
|
||||||
healthCheckCmd := &flaeg.Command{
|
healthCheckCmd := &flaeg.Command{
|
||||||
Name: "healthcheck",
|
Name: "healthcheck",
|
||||||
Description: `Calls traefik /ping to check health (web provider must be enabled)`,
|
Description: `Calls traefik /ping to check health (ping must be enabled)`,
|
||||||
Config: traefikConfiguration,
|
Config: traefikConfiguration,
|
||||||
DefaultPointersConfig: traefikPointersConfiguration,
|
DefaultPointersConfig: traefikPointersConfiguration,
|
||||||
Run: func() error {
|
Run: func() error {
|
||||||
traefikConfiguration.GlobalConfiguration.SetEffectiveConfiguration(traefikConfiguration.ConfigFile)
|
traefikConfiguration.GlobalConfiguration.SetEffectiveConfiguration(traefikConfiguration.ConfigFile)
|
||||||
|
|
||||||
if traefikConfiguration.Web == nil {
|
if traefikConfiguration.Ping == nil {
|
||||||
fmt.Println("Please enable the web provider to use healtcheck.")
|
fmt.Println("Please enable `ping` to use healtcheck.")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pingEntryPoint, ok := traefikConfiguration.EntryPoints[traefikConfiguration.Ping.EntryPoint]
|
||||||
|
if !ok {
|
||||||
|
pingEntryPoint = &configuration.EntryPoint{Address: ":8080"}
|
||||||
|
}
|
||||||
|
|
||||||
client := &http.Client{Timeout: 5 * time.Second}
|
client := &http.Client{Timeout: 5 * time.Second}
|
||||||
protocol := "http"
|
protocol := "http"
|
||||||
if len(traefikConfiguration.Web.CertFile) > 0 {
|
if pingEntryPoint.TLS != nil {
|
||||||
protocol = "https"
|
protocol = "https"
|
||||||
tr := &http.Transport{
|
tr := &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
@ -122,9 +128,9 @@ Complete documentation is available at https://traefik.io`,
|
||||||
client.Transport = tr
|
client.Transport = tr
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := client.Head(protocol + "://" + traefikConfiguration.Web.Address + traefikConfiguration.Web.Path + "ping")
|
resp, errPing := client.Head(protocol + "://" + pingEntryPoint.Address + traefikConfiguration.Web.Path + "ping")
|
||||||
if err != nil {
|
if errPing != nil {
|
||||||
fmt.Printf("Error calling healthcheck: %s\n", err)
|
fmt.Printf("Error calling healthcheck: %s\n", errPing)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
|
|
@ -7,7 +7,9 @@ import (
|
||||||
|
|
||||||
"github.com/containous/flaeg"
|
"github.com/containous/flaeg"
|
||||||
"github.com/containous/traefik/acme"
|
"github.com/containous/traefik/acme"
|
||||||
|
"github.com/containous/traefik/api"
|
||||||
"github.com/containous/traefik/log"
|
"github.com/containous/traefik/log"
|
||||||
|
"github.com/containous/traefik/ping"
|
||||||
"github.com/containous/traefik/provider/boltdb"
|
"github.com/containous/traefik/provider/boltdb"
|
||||||
"github.com/containous/traefik/provider/consul"
|
"github.com/containous/traefik/provider/consul"
|
||||||
"github.com/containous/traefik/provider/docker"
|
"github.com/containous/traefik/provider/docker"
|
||||||
|
@ -20,13 +22,16 @@ import (
|
||||||
"github.com/containous/traefik/provider/marathon"
|
"github.com/containous/traefik/provider/marathon"
|
||||||
"github.com/containous/traefik/provider/mesos"
|
"github.com/containous/traefik/provider/mesos"
|
||||||
"github.com/containous/traefik/provider/rancher"
|
"github.com/containous/traefik/provider/rancher"
|
||||||
"github.com/containous/traefik/provider/web"
|
"github.com/containous/traefik/provider/rest"
|
||||||
"github.com/containous/traefik/provider/zk"
|
"github.com/containous/traefik/provider/zk"
|
||||||
"github.com/containous/traefik/tls"
|
"github.com/containous/traefik/tls"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// DefaultInternalEntryPointName the name of the default internal entry point
|
||||||
|
DefaultInternalEntryPointName = "traefik"
|
||||||
|
|
||||||
// DefaultHealthCheckInterval is the default health check interval.
|
// DefaultHealthCheckInterval is the default health check interval.
|
||||||
DefaultHealthCheckInterval = 30 * time.Second
|
DefaultHealthCheckInterval = 30 * time.Second
|
||||||
|
|
||||||
|
@ -67,9 +72,9 @@ type GlobalConfiguration struct {
|
||||||
HealthCheck *HealthCheckConfig `description:"Health check parameters" export:"true"`
|
HealthCheck *HealthCheckConfig `description:"Health check parameters" export:"true"`
|
||||||
RespondingTimeouts *RespondingTimeouts `description:"Timeouts for incoming requests to the Traefik instance" export:"true"`
|
RespondingTimeouts *RespondingTimeouts `description:"Timeouts for incoming requests to the Traefik instance" export:"true"`
|
||||||
ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers" export:"true"`
|
ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers" export:"true"`
|
||||||
|
Web *WebCompatibility `description:"(Deprecated) Enable Web backend with default settings" export:"true"` // Deprecated
|
||||||
Docker *docker.Provider `description:"Enable Docker backend with default settings" export:"true"`
|
Docker *docker.Provider `description:"Enable Docker backend with default settings" export:"true"`
|
||||||
File *file.Provider `description:"Enable File backend with default settings" export:"true"`
|
File *file.Provider `description:"Enable File backend with default settings" export:"true"`
|
||||||
Web *web.Provider `description:"Enable Web backend with default settings" export:"true"`
|
|
||||||
Marathon *marathon.Provider `description:"Enable Marathon backend with default settings" export:"true"`
|
Marathon *marathon.Provider `description:"Enable Marathon backend with default settings" export:"true"`
|
||||||
Consul *consul.Provider `description:"Enable Consul backend with default settings" export:"true"`
|
Consul *consul.Provider `description:"Enable Consul backend with default settings" export:"true"`
|
||||||
ConsulCatalog *consul.CatalogProvider `description:"Enable Consul catalog backend with default settings" export:"true"`
|
ConsulCatalog *consul.CatalogProvider `description:"Enable Consul catalog backend with default settings" export:"true"`
|
||||||
|
@ -82,6 +87,70 @@ type GlobalConfiguration struct {
|
||||||
ECS *ecs.Provider `description:"Enable ECS backend with default settings" export:"true"`
|
ECS *ecs.Provider `description:"Enable ECS backend with default settings" export:"true"`
|
||||||
Rancher *rancher.Provider `description:"Enable Rancher backend with default settings" export:"true"`
|
Rancher *rancher.Provider `description:"Enable Rancher backend with default settings" export:"true"`
|
||||||
DynamoDB *dynamodb.Provider `description:"Enable DynamoDB backend with default settings" export:"true"`
|
DynamoDB *dynamodb.Provider `description:"Enable DynamoDB backend with default settings" export:"true"`
|
||||||
|
Rest *rest.Provider `description:"Enable Rest backend with default settings" export:"true"`
|
||||||
|
API *api.Handler `description:"Enable api/dashboard" export:"true"`
|
||||||
|
Metrics *types.Metrics `description:"Enable a metrics exporter" export:"true"`
|
||||||
|
Ping *ping.Handler `description:"Enable ping" export:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebCompatibility is a configuration to handle compatibility with deprecated web provider options
|
||||||
|
type WebCompatibility struct {
|
||||||
|
Address string `description:"Web administration port" export:"true"`
|
||||||
|
CertFile string `description:"SSL certificate" export:"true"`
|
||||||
|
KeyFile string `description:"SSL certificate" export:"true"`
|
||||||
|
ReadOnly bool `description:"Enable read only API" export:"true"`
|
||||||
|
Statistics *types.Statistics `description:"Enable more detailed statistics" export:"true"`
|
||||||
|
Metrics *types.Metrics `description:"Enable a metrics exporter" export:"true"`
|
||||||
|
Path string `description:"Root path for dashboard and API" export:"true"`
|
||||||
|
Auth *types.Auth `export:"true"`
|
||||||
|
Debug bool `export:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GlobalConfiguration) handleWebDeprecation() {
|
||||||
|
if gc.Web != nil {
|
||||||
|
log.Warn("web provider configuration is deprecated, you should use these options : api, rest provider, ping and metrics")
|
||||||
|
|
||||||
|
if gc.API != nil || gc.Metrics != nil || gc.Ping != nil || gc.Rest != nil {
|
||||||
|
log.Warn("web option is ignored if you use it with one of these options : api, rest provider, ping or metrics")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gc.EntryPoints[DefaultInternalEntryPointName] = &EntryPoint{
|
||||||
|
Address: gc.Web.Address,
|
||||||
|
Auth: gc.Web.Auth,
|
||||||
|
}
|
||||||
|
if gc.Web.CertFile != "" {
|
||||||
|
gc.EntryPoints[DefaultInternalEntryPointName].TLS = &tls.TLS{
|
||||||
|
Certificates: []tls.Certificate{
|
||||||
|
{
|
||||||
|
CertFile: tls.FileOrContent(gc.Web.CertFile),
|
||||||
|
KeyFile: tls.FileOrContent(gc.Web.KeyFile),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if gc.API == nil {
|
||||||
|
gc.API = &api.Handler{
|
||||||
|
EntryPoint: DefaultInternalEntryPointName,
|
||||||
|
Statistics: gc.Web.Statistics,
|
||||||
|
Dashboard: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if gc.Ping == nil {
|
||||||
|
gc.Ping = &ping.Handler{
|
||||||
|
EntryPoint: DefaultInternalEntryPointName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if gc.Metrics == nil {
|
||||||
|
gc.Metrics = gc.Web.Metrics
|
||||||
|
}
|
||||||
|
|
||||||
|
if !gc.Debug {
|
||||||
|
gc.Debug = gc.Web.Debug
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEffectiveConfiguration adds missing configuration parameters derived from existing ones.
|
// SetEffectiveConfiguration adds missing configuration parameters derived from existing ones.
|
||||||
|
@ -95,6 +164,17 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
|
||||||
gc.DefaultEntryPoints = []string{"http"}
|
gc.DefaultEntryPoints = []string{"http"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gc.handleWebDeprecation()
|
||||||
|
|
||||||
|
if (gc.API != nil && gc.API.EntryPoint == DefaultInternalEntryPointName) ||
|
||||||
|
(gc.Ping != nil && gc.Ping.EntryPoint == DefaultInternalEntryPointName) ||
|
||||||
|
(gc.Metrics != nil && gc.Metrics.Prometheus != nil && gc.Metrics.Prometheus.EntryPoint == DefaultInternalEntryPointName) ||
|
||||||
|
(gc.Rest != nil && gc.Rest.EntryPoint == DefaultInternalEntryPointName) {
|
||||||
|
if _, ok := gc.EntryPoints[DefaultInternalEntryPointName]; !ok {
|
||||||
|
gc.EntryPoints[DefaultInternalEntryPointName] = &EntryPoint{Address: ":8080"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ForwardedHeaders must be remove in the next breaking version
|
// ForwardedHeaders must be remove in the next breaking version
|
||||||
for entryPointName := range gc.EntryPoints {
|
for entryPointName := range gc.EntryPoints {
|
||||||
entryPoint := gc.EntryPoints[entryPointName]
|
entryPoint := gc.EntryPoints[entryPointName]
|
||||||
|
@ -136,6 +216,14 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if gc.API != nil {
|
||||||
|
gc.API.Debug = gc.Debug
|
||||||
|
}
|
||||||
|
|
||||||
|
if gc.Debug {
|
||||||
|
gc.LogLevel = "DEBUG"
|
||||||
|
}
|
||||||
|
|
||||||
if gc.Web != nil && (gc.Web.Path == "" || !strings.HasSuffix(gc.Web.Path, "/")) {
|
if gc.Web != nil && (gc.Web.Path == "" || !strings.HasSuffix(gc.Web.Path, "/")) {
|
||||||
gc.Web.Path += "/"
|
gc.Web.Path += "/"
|
||||||
}
|
}
|
||||||
|
|
203
docs/configuration/api.md
Normal file
203
docs/configuration/api.md
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
# API Definition
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# API definition
|
||||||
|
[api]
|
||||||
|
# Name of the related entry point
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "traefik"
|
||||||
|
#
|
||||||
|
entryPoint = "traefik"
|
||||||
|
|
||||||
|
# Enabled Dashboard
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
dashboard = true
|
||||||
|
|
||||||
|
# Enabled debug mode
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
debug = true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Web UI
|
||||||
|
|
||||||
|
![Web UI Providers](/img/web.frontend.png)
|
||||||
|
|
||||||
|
![Web UI Health](/img/traefik-health.png)
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
| Path | Method | Description |
|
||||||
|
|-----------------------------------------------------------------|------------------|-------------------------------------------|
|
||||||
|
| `/` | `GET` | Provides a simple HTML frontend of Træfik |
|
||||||
|
| `/health` | `GET` | json health metrics |
|
||||||
|
| `/api` | `GET` | Configuration for all providers |
|
||||||
|
| `/api/providers` | `GET` | Providers |
|
||||||
|
| `/api/providers/{provider}` | `GET`, `PUT` | Get or update provider |
|
||||||
|
| `/api/providers/{provider}/backends` | `GET` | List backends |
|
||||||
|
| `/api/providers/{provider}/backends/{backend}` | `GET` | Get backend |
|
||||||
|
| `/api/providers/{provider}/backends/{backend}/servers` | `GET` | List servers in backend |
|
||||||
|
| `/api/providers/{provider}/backends/{backend}/servers/{server}` | `GET` | Get a server in a backend |
|
||||||
|
| `/api/providers/{provider}/frontends` | `GET` | List frontends |
|
||||||
|
| `/api/providers/{provider}/frontends/{frontend}` | `GET` | Get a frontend |
|
||||||
|
| `/api/providers/{provider}/frontends/{frontend}/routes` | `GET` | List routes in a frontend |
|
||||||
|
| `/api/providers/{provider}/frontends/{frontend}/routes/{route}` | `GET` | Get a route in a frontend |
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
For compatibility reason, when you activate the rest provider, you can use `web` or `rest` as `provider` value.
|
||||||
|
But be careful, in the configuration for all providers the key is still `web`.
|
||||||
|
|
||||||
|
### Provider configurations
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -s "http://localhost:8080/api" | jq .
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"file": {
|
||||||
|
"frontends": {
|
||||||
|
"frontend2": {
|
||||||
|
"routes": {
|
||||||
|
"test_2": {
|
||||||
|
"rule": "Path:/test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"backend": "backend1"
|
||||||
|
},
|
||||||
|
"frontend1": {
|
||||||
|
"routes": {
|
||||||
|
"test_1": {
|
||||||
|
"rule": "Host:test.localhost"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"backend": "backend2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"backends": {
|
||||||
|
"backend2": {
|
||||||
|
"loadBalancer": {
|
||||||
|
"method": "drr"
|
||||||
|
},
|
||||||
|
"servers": {
|
||||||
|
"server2": {
|
||||||
|
"weight": 2,
|
||||||
|
"URL": "http://172.17.0.5:80"
|
||||||
|
},
|
||||||
|
"server1": {
|
||||||
|
"weight": 1,
|
||||||
|
"url": "http://172.17.0.4:80"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"backend1": {
|
||||||
|
"loadBalancer": {
|
||||||
|
"method": "wrr"
|
||||||
|
},
|
||||||
|
"circuitBreaker": {
|
||||||
|
"expression": "NetworkErrorRatio() > 0.5"
|
||||||
|
},
|
||||||
|
"servers": {
|
||||||
|
"server2": {
|
||||||
|
"weight": 1,
|
||||||
|
"url": "http://172.17.0.3:80"
|
||||||
|
},
|
||||||
|
"server1": {
|
||||||
|
"weight": 10,
|
||||||
|
"url": "http://172.17.0.2:80"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Health
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -s "http://localhost:8080/health" | jq .
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
// Træfik PID
|
||||||
|
"pid": 2458,
|
||||||
|
// Træfik server uptime (formated time)
|
||||||
|
"uptime": "39m6.885931127s",
|
||||||
|
// Træfik server uptime in seconds
|
||||||
|
"uptime_sec": 2346.885931127,
|
||||||
|
// current server date
|
||||||
|
"time": "2015-10-07 18:32:24.362238909 +0200 CEST",
|
||||||
|
// current server date in seconds
|
||||||
|
"unixtime": 1444235544,
|
||||||
|
// count HTTP response status code in realtime
|
||||||
|
"status_code_count": {
|
||||||
|
"502": 1
|
||||||
|
},
|
||||||
|
// count HTTP response status code since Træfik started
|
||||||
|
"total_status_code_count": {
|
||||||
|
"200": 7,
|
||||||
|
"404": 21,
|
||||||
|
"502": 13
|
||||||
|
},
|
||||||
|
// count HTTP response
|
||||||
|
"count": 1,
|
||||||
|
// count HTTP response
|
||||||
|
"total_count": 41,
|
||||||
|
// sum of all response time (formated time)
|
||||||
|
"total_response_time": "35.456865605s",
|
||||||
|
// sum of all response time in seconds
|
||||||
|
"total_response_time_sec": 35.456865605,
|
||||||
|
// average response time (formated time)
|
||||||
|
"average_response_time": "864.8016ms",
|
||||||
|
// average response time in seconds
|
||||||
|
"average_response_time_sec": 0.8648016000000001,
|
||||||
|
|
||||||
|
// request statistics [requires --web.statistics to be set]
|
||||||
|
// ten most recent requests with 4xx and 5xx status codes
|
||||||
|
"recent_errors": [
|
||||||
|
{
|
||||||
|
// status code
|
||||||
|
"status_code": 500,
|
||||||
|
// description of status code
|
||||||
|
"status": "Internal Server Error",
|
||||||
|
// request HTTP method
|
||||||
|
"method": "GET",
|
||||||
|
// request hostname
|
||||||
|
"host": "localhost",
|
||||||
|
// request path
|
||||||
|
"path": "/path",
|
||||||
|
// RFC 3339 formatted date/time
|
||||||
|
"time": "2016-10-21T16:59:15.418495872-07:00"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Metrics
|
||||||
|
|
||||||
|
You can enable Traefik to export internal metrics to different monitoring systems.
|
||||||
|
```toml
|
||||||
|
[api]
|
||||||
|
# ...
|
||||||
|
|
||||||
|
# Enable more detailed statistics.
|
||||||
|
[api.statistics]
|
||||||
|
|
||||||
|
# Number of recent errors logged.
|
||||||
|
#
|
||||||
|
# Default: 10
|
||||||
|
#
|
||||||
|
recentErrors = 10
|
||||||
|
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
| Path | Method | Description |
|
||||||
|
|------------|---------------|-------------------------|
|
||||||
|
| `/metrics` | `GET` | Export internal metrics |
|
91
docs/configuration/backends/rest.md
Normal file
91
docs/configuration/backends/rest.md
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
# Rest Backend
|
||||||
|
|
||||||
|
Træfik can be configured:
|
||||||
|
|
||||||
|
- using a RESTful api.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Enable rest backend.
|
||||||
|
[rest]
|
||||||
|
# Name of the related entry point
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "traefik"
|
||||||
|
#
|
||||||
|
entryPoint = "traefik"
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
| Path | Method | Description |
|
||||||
|
|------------------------------|--------|-----------------|
|
||||||
|
| `/api/providers/web` | `PUT` | update provider |
|
||||||
|
| `/api/providers/rest` | `PUT` | update provider |
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
For compatibility reason, when you activate the rest provider, you can use `web` or `rest` as `provider` value.
|
||||||
|
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -XPUT @file "http://localhost:8080/api"
|
||||||
|
```
|
||||||
|
with `@file`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"frontends": {
|
||||||
|
"frontend2": {
|
||||||
|
"routes": {
|
||||||
|
"test_2": {
|
||||||
|
"rule": "Path:/test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"backend": "backend1"
|
||||||
|
},
|
||||||
|
"frontend1": {
|
||||||
|
"routes": {
|
||||||
|
"test_1": {
|
||||||
|
"rule": "Host:test.localhost"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"backend": "backend2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"backends": {
|
||||||
|
"backend2": {
|
||||||
|
"loadBalancer": {
|
||||||
|
"method": "drr"
|
||||||
|
},
|
||||||
|
"servers": {
|
||||||
|
"server2": {
|
||||||
|
"weight": 2,
|
||||||
|
"URL": "http://172.17.0.5:80"
|
||||||
|
},
|
||||||
|
"server1": {
|
||||||
|
"weight": 1,
|
||||||
|
"url": "http://172.17.0.4:80"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"backend1": {
|
||||||
|
"loadBalancer": {
|
||||||
|
"method": "wrr"
|
||||||
|
},
|
||||||
|
"circuitBreaker": {
|
||||||
|
"expression": "NetworkErrorRatio() > 0.5"
|
||||||
|
},
|
||||||
|
"servers": {
|
||||||
|
"server2": {
|
||||||
|
"weight": 1,
|
||||||
|
"url": "http://172.17.0.3:80"
|
||||||
|
},
|
||||||
|
"server1": {
|
||||||
|
"weight": 10,
|
||||||
|
"url": "http://172.17.0.2:80"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
|
@ -1,5 +1,8 @@
|
||||||
# Web Backend
|
# Web Backend
|
||||||
|
|
||||||
|
!!! danger "DEPRECATED"
|
||||||
|
The web provider is deprecated, please use the [api](/configuration/api.md), the [ping](/configuration/ping.md), the [metrics](/configuration/metrics) and the [rest](/configuration/backends/rest.md) provider.
|
||||||
|
|
||||||
Træfik can be configured:
|
Træfik can be configured:
|
||||||
|
|
||||||
- using a RESTful api.
|
- using a RESTful api.
|
||||||
|
|
119
docs/configuration/metrics.md
Normal file
119
docs/configuration/metrics.md
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
# Metrics Definition
|
||||||
|
|
||||||
|
## Prometheus
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Metrics definition
|
||||||
|
[metrics]
|
||||||
|
#...
|
||||||
|
|
||||||
|
# To enable Traefik to export internal metrics to Prometheus
|
||||||
|
[metrics.prometheus]
|
||||||
|
|
||||||
|
# Buckets for latency metrics
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: [0.1, 0.3, 1.2, 5]
|
||||||
|
#
|
||||||
|
buckets = [0.1,0.3,1.2,5.0]
|
||||||
|
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## DataDog
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Metrics definition
|
||||||
|
[metrics]
|
||||||
|
#...
|
||||||
|
|
||||||
|
# DataDog metrics exporter type
|
||||||
|
[metrics.datadog]
|
||||||
|
|
||||||
|
# DataDog's address.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
# Default: "localhost:8125"
|
||||||
|
#
|
||||||
|
address = "localhost:8125"
|
||||||
|
|
||||||
|
# DataDog push interval
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "10s"
|
||||||
|
#
|
||||||
|
pushInterval = "10s"
|
||||||
|
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## StatsD
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Metrics definition
|
||||||
|
[metrics]
|
||||||
|
#...
|
||||||
|
|
||||||
|
# StatsD metrics exporter type
|
||||||
|
[metrics.statsd]
|
||||||
|
|
||||||
|
# StatD's address.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
# Default: "localhost:8125"
|
||||||
|
#
|
||||||
|
address = "localhost:8125"
|
||||||
|
|
||||||
|
# StatD push interval
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "10s"
|
||||||
|
#
|
||||||
|
pushInterval = "10s"
|
||||||
|
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
### InfluxDB
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[web]
|
||||||
|
# ...
|
||||||
|
|
||||||
|
# InfluxDB metrics exporter type
|
||||||
|
[web.metrics.influxdb]
|
||||||
|
|
||||||
|
# InfluxDB's address.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
# Default: "localhost:8089"
|
||||||
|
#
|
||||||
|
address = "localhost:8089"
|
||||||
|
|
||||||
|
# InfluxDB push interval
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "10s"
|
||||||
|
#
|
||||||
|
pushinterval = "10s"
|
||||||
|
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Statistics
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Metrics definition
|
||||||
|
[metrics]
|
||||||
|
# ...
|
||||||
|
|
||||||
|
# Enable more detailed statistics.
|
||||||
|
[metrics.statistics]
|
||||||
|
|
||||||
|
# Number of recent errors logged.
|
||||||
|
#
|
||||||
|
# Default: 10
|
||||||
|
#
|
||||||
|
recentErrors = 10
|
||||||
|
|
||||||
|
# ...
|
||||||
|
```
|
42
docs/configuration/ping.md
Normal file
42
docs/configuration/ping.md
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
# Ping Definition
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Ping definition
|
||||||
|
[ping]
|
||||||
|
# Name of the related entry point
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "traefik"
|
||||||
|
#
|
||||||
|
entryPoint = "traefik"
|
||||||
|
```
|
||||||
|
|
||||||
|
| Path | Method | Description |
|
||||||
|
|---------|---------------|----------------------------------------------------------------------------------------------------|
|
||||||
|
| `/ping` | `GET`, `HEAD` | A simple endpoint to check for Træfik process liveness. Return a code `200` with the content: `OK` |
|
||||||
|
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
Even if you have authentication configured on entry point, the `/ping` path of the api is excluded from authentication.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -sv "http://localhost:8080/ping"
|
||||||
|
```
|
||||||
|
```shell
|
||||||
|
* Trying ::1...
|
||||||
|
* Connected to localhost (::1) port 8080 (#0)
|
||||||
|
> GET /ping HTTP/1.1
|
||||||
|
> Host: localhost:8080
|
||||||
|
> User-Agent: curl/7.43.0
|
||||||
|
> Accept: */*
|
||||||
|
>
|
||||||
|
< HTTP/1.1 200 OK
|
||||||
|
< Date: Thu, 25 Aug 2016 01:35:36 GMT
|
||||||
|
< Content-Length: 2
|
||||||
|
< Content-Type: text/plain; charset=utf-8
|
||||||
|
<
|
||||||
|
* Connection #0 to host localhost left intact
|
||||||
|
OK
|
||||||
|
```
|
|
@ -162,3 +162,104 @@ func (s *SimpleSuite) TestRequestAcceptGraceTimeout(c *check.C) {
|
||||||
c.Fatal("Traefik did not terminate in time")
|
c.Fatal("Traefik did not terminate in time")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SimpleSuite) TestApiOnSameEntryPoint(c *check.C) {
|
||||||
|
s.createComposeProject(c, "base")
|
||||||
|
s.composeProject.Start(c)
|
||||||
|
|
||||||
|
cmd, output := s.traefikCmd("--defaultEntryPoints=http", "--entryPoints=Name:http Address::8000", "--api.entryPoint=http", "--debug", "--docker")
|
||||||
|
defer output(c)
|
||||||
|
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
// TODO validate : run on 80
|
||||||
|
// Expected a 404 as we did not configure anything
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8000/test", 1*time.Second, try.StatusCodeIs(http.StatusNotFound))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8000/api", 1*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8000/api/providers", 1*time.Second, try.BodyContains("PathPrefix"))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SimpleSuite) TestNoAuthOnPing(c *check.C) {
|
||||||
|
s.createComposeProject(c, "base")
|
||||||
|
s.composeProject.Start(c)
|
||||||
|
|
||||||
|
cmd, output := s.traefikCmd(withConfigFile("./fixtures/simple_auth.toml"))
|
||||||
|
defer output(c)
|
||||||
|
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8001/api", 1*time.Second, try.StatusCodeIs(http.StatusUnauthorized))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8001/ping", 1*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SimpleSuite) TestWebCompatibilityWithoutPath(c *check.C) {
|
||||||
|
|
||||||
|
s.createComposeProject(c, "base")
|
||||||
|
s.composeProject.Start(c)
|
||||||
|
|
||||||
|
cmd, output := s.traefikCmd("--defaultEntryPoints=http", "--entryPoints=Name:http Address::8000", "--web", "--debug", "--docker")
|
||||||
|
defer output(c)
|
||||||
|
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
// TODO validate : run on 80
|
||||||
|
// Expected a 404 as we did not configure anything
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8000/test", 1*time.Second, try.StatusCodeIs(http.StatusNotFound))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8080/api", 1*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("PathPrefix"))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SimpleSuite) TestWebCompatibilityWithPath(c *check.C) {
|
||||||
|
|
||||||
|
s.createComposeProject(c, "base")
|
||||||
|
s.composeProject.Start(c)
|
||||||
|
|
||||||
|
cmd, output := s.traefikCmd("--defaultEntryPoints=http", "--entryPoints=Name:http Address::8000", "--web.path=/test", "--debug", "--docker")
|
||||||
|
defer output(c)
|
||||||
|
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
// TODO validate : run on 80
|
||||||
|
// Expected a 404 as we did not configure anything
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8000/notfound", 1*time.Second, try.StatusCodeIs(http.StatusNotFound))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8080/test/api", 1*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8080/test/ping", 1*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8080/test/api/providers", 1*time.Second, try.BodyContains("PathPrefix"))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
|
16
integration/fixtures/simple_auth.toml
Normal file
16
integration/fixtures/simple_auth.toml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
logLevel = "DEBUG"
|
||||||
|
defaultEntryPoints = ["http"]
|
||||||
|
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":8000"
|
||||||
|
|
||||||
|
[entryPoints.traefik]
|
||||||
|
address = ":8001"
|
||||||
|
[entryPoints.traefik.auth.basic]
|
||||||
|
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||||
|
|
||||||
|
|
||||||
|
[api]
|
||||||
|
|
||||||
|
[ping]
|
5
integration/resources/compose/base.yml
Normal file
5
integration/resources/compose/base.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
whoami1:
|
||||||
|
image: emilevauge/whoami
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.frontend.rule=PathPrefix:/whoami
|
|
@ -1,9 +1,11 @@
|
||||||
package metrics
|
package metrics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/containous/mux"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/go-kit/kit/metrics/prometheus"
|
"github.com/go-kit/kit/metrics/prometheus"
|
||||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -14,6 +16,14 @@ const (
|
||||||
retriesTotalName = metricNamePrefix + "backend_retries_total"
|
retriesTotalName = metricNamePrefix + "backend_retries_total"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// PrometheusHandler expose Prometheus routes
|
||||||
|
type PrometheusHandler struct{}
|
||||||
|
|
||||||
|
// AddRoutes add Prometheus routes on a router
|
||||||
|
func (h PrometheusHandler) AddRoutes(router *mux.Router) {
|
||||||
|
router.Methods("GET").Path("/metrics").Handler(promhttp.Handler())
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterPrometheus registers all Prometheus metrics.
|
// RegisterPrometheus registers all Prometheus metrics.
|
||||||
// It must be called only once and failing to register the metrics will lead to a panic.
|
// It must be called only once and failing to register the metrics will lead to a panic.
|
||||||
func RegisterPrometheus(config *types.Prometheus) Registry {
|
func RegisterPrometheus(config *types.Prometheus) Registry {
|
||||||
|
|
|
@ -84,7 +84,11 @@ pages:
|
||||||
- 'Backend: Marathon': 'configuration/backends/marathon.md'
|
- 'Backend: Marathon': 'configuration/backends/marathon.md'
|
||||||
- 'Backend: Mesos': 'configuration/backends/mesos.md'
|
- 'Backend: Mesos': 'configuration/backends/mesos.md'
|
||||||
- 'Backend: Rancher': 'configuration/backends/rancher.md'
|
- 'Backend: Rancher': 'configuration/backends/rancher.md'
|
||||||
|
- 'Backend: Rest': 'configuration/backends/rest.md'
|
||||||
- 'Backend: Zookeeper': 'configuration/backends/zookeeper.md'
|
- 'Backend: Zookeeper': 'configuration/backends/zookeeper.md'
|
||||||
|
- 'API / Dashboard': 'configuration/api.md'
|
||||||
|
- 'Ping': 'configuration/ping.md'
|
||||||
|
- 'Metrics': 'configuration/metrics.md'
|
||||||
- User Guides:
|
- User Guides:
|
||||||
- 'Configuration Examples': 'user-guide/examples.md'
|
- 'Configuration Examples': 'user-guide/examples.md'
|
||||||
- 'Swarm Mode Cluster': 'user-guide/swarm-mode.md'
|
- 'Swarm Mode Cluster': 'user-guide/swarm-mode.md'
|
||||||
|
|
21
ping/ping.go
Normal file
21
ping/ping.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package ping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/containous/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Handler expose ping routes
|
||||||
|
type Handler struct {
|
||||||
|
EntryPoint string `description:"Ping entryPoint" export:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRoutes add ping routes on a router
|
||||||
|
func (g Handler) AddRoutes(router *mux.Router) {
|
||||||
|
router.Methods("GET", "HEAD").Path("/ping").
|
||||||
|
HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||||
|
fmt.Fprint(response, "OK")
|
||||||
|
})
|
||||||
|
}
|
65
provider/rest/rest.go
Normal file
65
provider/rest/rest.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/containous/mux"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
|
"github.com/unrolled/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Provider is a provider.Provider implementation that provides a Rest API
|
||||||
|
type Provider struct {
|
||||||
|
configurationChan chan<- types.ConfigMessage
|
||||||
|
EntryPoint string `description:"EntryPoint" export:"true"`
|
||||||
|
CurrentConfigurations *safe.Safe
|
||||||
|
}
|
||||||
|
|
||||||
|
var templatesRenderer = render.New(render.Options{Directory: "nowhere"})
|
||||||
|
|
||||||
|
// AddRoutes add rest provider routes on a router
|
||||||
|
func (p *Provider) AddRoutes(systemRouter *mux.Router) {
|
||||||
|
systemRouter.Methods("PUT").Path("/api/providers/{provider}").HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||||
|
vars := mux.Vars(request)
|
||||||
|
// TODO: Deprecated configuration - Need to be removed in the future
|
||||||
|
if vars["provider"] != "web" && vars["provider"] != "rest" {
|
||||||
|
response.WriteHeader(http.StatusBadRequest)
|
||||||
|
fmt.Fprint(response, "Only 'rest' provider can be updated through the REST API")
|
||||||
|
return
|
||||||
|
} else if vars["provider"] == "web" {
|
||||||
|
log.Warn("The provider web is deprecated. Please use /rest instead")
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration := new(types.Configuration)
|
||||||
|
body, _ := ioutil.ReadAll(request.Body)
|
||||||
|
err := json.Unmarshal(body, configuration)
|
||||||
|
if err == nil {
|
||||||
|
// TODO: Deprecated configuration - Change to `rest` in the future
|
||||||
|
p.configurationChan <- types.ConfigMessage{ProviderName: "web", Configuration: configuration}
|
||||||
|
p.getConfigHandler(response, request)
|
||||||
|
} else {
|
||||||
|
log.Errorf("Error parsing configuration %+v", err)
|
||||||
|
http.Error(response, fmt.Sprintf("%+v", err), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide allows the provider to provide configurations to traefik
|
||||||
|
// using the given configuration channel.
|
||||||
|
func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ types.Constraints) error {
|
||||||
|
p.configurationChan = configurationChan
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) getConfigHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||||
|
err := templatesRenderer.JSON(response, http.StatusOK, currentConfigurations)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,330 +0,0 @@
|
||||||
package web
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"expvar"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/containous/mux"
|
|
||||||
"github.com/containous/traefik/autogen"
|
|
||||||
"github.com/containous/traefik/log"
|
|
||||||
"github.com/containous/traefik/middlewares"
|
|
||||||
mauth "github.com/containous/traefik/middlewares/auth"
|
|
||||||
"github.com/containous/traefik/safe"
|
|
||||||
"github.com/containous/traefik/types"
|
|
||||||
"github.com/containous/traefik/version"
|
|
||||||
"github.com/elazarl/go-bindata-assetfs"
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
||||||
thoas_stats "github.com/thoas/stats"
|
|
||||||
"github.com/unrolled/render"
|
|
||||||
"github.com/urfave/negroni"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Provider is a provider.Provider implementation that provides the UI
|
|
||||||
type Provider struct {
|
|
||||||
Address string `description:"Web administration port" export:"true"`
|
|
||||||
CertFile string `description:"SSL certificate" export:"true"`
|
|
||||||
KeyFile string `description:"SSL certificate" export:"true"`
|
|
||||||
ReadOnly bool `description:"Enable read only API" export:"true"`
|
|
||||||
Statistics *types.Statistics `description:"Enable more detailed statistics" export:"true"`
|
|
||||||
Metrics *types.Metrics `description:"Enable a metrics exporter" export:"true"`
|
|
||||||
Path string `description:"Root path for dashboard and API"`
|
|
||||||
Auth *types.Auth `export:"true"`
|
|
||||||
Debug bool `export:"true"`
|
|
||||||
CurrentConfigurations *safe.Safe
|
|
||||||
Stats *thoas_stats.Stats
|
|
||||||
StatsRecorder *middlewares.StatsRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
templatesRenderer = render.New(render.Options{
|
|
||||||
Directory: "nowhere",
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
expvar.Publish("Goroutines", expvar.Func(goroutines))
|
|
||||||
}
|
|
||||||
|
|
||||||
func goroutines() interface{} {
|
|
||||||
return runtime.NumGoroutine()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
|
||||||
// using the given configuration channel.
|
|
||||||
func (provider *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ types.Constraints) error {
|
|
||||||
systemRouter := mux.NewRouter()
|
|
||||||
|
|
||||||
if provider.Path != "/" {
|
|
||||||
systemRouter.Methods("GET").Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
|
||||||
http.Redirect(response, request, provider.Path, 302)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prometheus route
|
|
||||||
if provider.Metrics != nil && provider.Metrics.Prometheus != nil {
|
|
||||||
systemRouter.Methods("GET").Path(provider.Path + "metrics").Handler(promhttp.Handler())
|
|
||||||
}
|
|
||||||
|
|
||||||
// health route
|
|
||||||
systemRouter.Methods("GET").Path(provider.Path + "health").HandlerFunc(provider.getHealthHandler)
|
|
||||||
|
|
||||||
// ping route
|
|
||||||
systemRouter.Methods("GET", "HEAD").Path(provider.Path + "ping").HandlerFunc(provider.getPingHandler)
|
|
||||||
// API routes
|
|
||||||
systemRouter.Methods("GET").Path(provider.Path + "api").HandlerFunc(provider.getConfigHandler)
|
|
||||||
systemRouter.Methods("GET").Path(provider.Path + "api/version").HandlerFunc(provider.getVersionHandler)
|
|
||||||
systemRouter.Methods("GET").Path(provider.Path + "api/providers").HandlerFunc(provider.getConfigHandler)
|
|
||||||
systemRouter.Methods("GET").Path(provider.Path + "api/providers/{provider}").HandlerFunc(provider.getProviderHandler)
|
|
||||||
systemRouter.Methods("PUT").Path(provider.Path + "api/providers/{provider}").HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
|
||||||
if provider.ReadOnly {
|
|
||||||
response.WriteHeader(http.StatusForbidden)
|
|
||||||
fmt.Fprint(response, "REST API is in read-only mode")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
vars := mux.Vars(request)
|
|
||||||
if vars["provider"] != "web" {
|
|
||||||
response.WriteHeader(http.StatusBadRequest)
|
|
||||||
fmt.Fprint(response, "Only 'web' provider can be updated through the REST API")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
configuration := new(types.Configuration)
|
|
||||||
body, _ := ioutil.ReadAll(request.Body)
|
|
||||||
err := json.Unmarshal(body, configuration)
|
|
||||||
if err == nil {
|
|
||||||
configurationChan <- types.ConfigMessage{ProviderName: "web", Configuration: configuration}
|
|
||||||
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(provider.Path + "api/providers/{provider}/backends").HandlerFunc(provider.getBackendsHandler)
|
|
||||||
systemRouter.Methods("GET").Path(provider.Path + "api/providers/{provider}/backends/{backend}").HandlerFunc(provider.getBackendHandler)
|
|
||||||
systemRouter.Methods("GET").Path(provider.Path + "api/providers/{provider}/backends/{backend}/servers").HandlerFunc(provider.getServersHandler)
|
|
||||||
systemRouter.Methods("GET").Path(provider.Path + "api/providers/{provider}/backends/{backend}/servers/{server}").HandlerFunc(provider.getServerHandler)
|
|
||||||
systemRouter.Methods("GET").Path(provider.Path + "api/providers/{provider}/frontends").HandlerFunc(provider.getFrontendsHandler)
|
|
||||||
systemRouter.Methods("GET").Path(provider.Path + "api/providers/{provider}/frontends/{frontend}").HandlerFunc(provider.getFrontendHandler)
|
|
||||||
systemRouter.Methods("GET").Path(provider.Path + "api/providers/{provider}/frontends/{frontend}/routes").HandlerFunc(provider.getRoutesHandler)
|
|
||||||
systemRouter.Methods("GET").Path(provider.Path + "api/providers/{provider}/frontends/{frontend}/routes/{route}").HandlerFunc(provider.getRouteHandler)
|
|
||||||
|
|
||||||
// Expose dashboard
|
|
||||||
systemRouter.Methods("GET").Path(provider.Path).HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
|
||||||
http.Redirect(response, request, provider.Path+"dashboard/", 302)
|
|
||||||
})
|
|
||||||
systemRouter.Methods("GET").PathPrefix(provider.Path + "dashboard/").
|
|
||||||
Handler(http.StripPrefix(provider.Path+"dashboard/", http.FileServer(&assetfs.AssetFS{Asset: autogen.Asset, AssetInfo: autogen.AssetInfo, AssetDir: autogen.AssetDir, Prefix: "static"})))
|
|
||||||
|
|
||||||
// expvars
|
|
||||||
if provider.Debug {
|
|
||||||
systemRouter.Methods("GET").Path(provider.Path + "debug/vars").HandlerFunc(expVarHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
safe.Go(func() {
|
|
||||||
var err error
|
|
||||||
var negroniInstance = negroni.New()
|
|
||||||
if provider.Auth != nil {
|
|
||||||
authMiddleware, err := mauth.NewAuthenticator(provider.Auth)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error creating Auth: ", err)
|
|
||||||
}
|
|
||||||
authMiddlewareWrapper := negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
|
||||||
if r.URL.Path == "/ping" {
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
} else {
|
|
||||||
authMiddleware.ServeHTTP(w, r, next)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
negroniInstance.Use(authMiddlewareWrapper)
|
|
||||||
}
|
|
||||||
negroniInstance.UseHandler(systemRouter)
|
|
||||||
|
|
||||||
if len(provider.CertFile) > 0 && len(provider.KeyFile) > 0 {
|
|
||||||
err = http.ListenAndServeTLS(provider.Address, provider.CertFile, provider.KeyFile, negroniInstance)
|
|
||||||
} else {
|
|
||||||
err = http.ListenAndServe(provider.Address, negroniInstance)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != http.ErrServerClosed {
|
|
||||||
log.Fatal("Error creating server: ", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// healthResponse combines data returned by thoas/stats with statistics (if
|
|
||||||
// they are enabled).
|
|
||||||
type healthResponse struct {
|
|
||||||
*thoas_stats.Data
|
|
||||||
*middlewares.Stats
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) getHealthHandler(response http.ResponseWriter, request *http.Request) {
|
|
||||||
health := &healthResponse{Data: provider.Stats.Data()}
|
|
||||||
if provider.StatsRecorder != nil {
|
|
||||||
health.Stats = provider.StatsRecorder.Data()
|
|
||||||
}
|
|
||||||
templatesRenderer.JSON(response, http.StatusOK, health)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) getPingHandler(response http.ResponseWriter, request *http.Request) {
|
|
||||||
fmt.Fprint(response, "OK")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) getConfigHandler(response http.ResponseWriter, request *http.Request) {
|
|
||||||
currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations)
|
|
||||||
templatesRenderer.JSON(response, http.StatusOK, currentConfigurations)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) getVersionHandler(response http.ResponseWriter, request *http.Request) {
|
|
||||||
v := struct {
|
|
||||||
Version string
|
|
||||||
Codename string
|
|
||||||
}{
|
|
||||||
Version: version.Version,
|
|
||||||
Codename: version.Codename,
|
|
||||||
}
|
|
||||||
templatesRenderer.JSON(response, http.StatusOK, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) getProviderHandler(response http.ResponseWriter, request *http.Request) {
|
|
||||||
vars := mux.Vars(request)
|
|
||||||
providerID := vars["provider"]
|
|
||||||
currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations)
|
|
||||||
if provider, ok := currentConfigurations[providerID]; ok {
|
|
||||||
templatesRenderer.JSON(response, http.StatusOK, provider)
|
|
||||||
} else {
|
|
||||||
http.NotFound(response, request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) getBackendsHandler(response http.ResponseWriter, request *http.Request) {
|
|
||||||
vars := mux.Vars(request)
|
|
||||||
providerID := vars["provider"]
|
|
||||||
currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations)
|
|
||||||
if provider, ok := currentConfigurations[providerID]; ok {
|
|
||||||
templatesRenderer.JSON(response, http.StatusOK, provider.Backends)
|
|
||||||
} else {
|
|
||||||
http.NotFound(response, request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) getBackendHandler(response http.ResponseWriter, request *http.Request) {
|
|
||||||
vars := mux.Vars(request)
|
|
||||||
providerID := vars["provider"]
|
|
||||||
backendID := vars["backend"]
|
|
||||||
currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations)
|
|
||||||
if provider, ok := currentConfigurations[providerID]; ok {
|
|
||||||
if backend, ok := provider.Backends[backendID]; ok {
|
|
||||||
templatesRenderer.JSON(response, http.StatusOK, backend)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
http.NotFound(response, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) getServersHandler(response http.ResponseWriter, request *http.Request) {
|
|
||||||
vars := mux.Vars(request)
|
|
||||||
providerID := vars["provider"]
|
|
||||||
backendID := vars["backend"]
|
|
||||||
currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations)
|
|
||||||
if provider, ok := currentConfigurations[providerID]; ok {
|
|
||||||
if backend, ok := provider.Backends[backendID]; ok {
|
|
||||||
templatesRenderer.JSON(response, http.StatusOK, backend.Servers)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
http.NotFound(response, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) getServerHandler(response http.ResponseWriter, request *http.Request) {
|
|
||||||
vars := mux.Vars(request)
|
|
||||||
providerID := vars["provider"]
|
|
||||||
backendID := vars["backend"]
|
|
||||||
serverID := vars["server"]
|
|
||||||
currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations)
|
|
||||||
if provider, ok := currentConfigurations[providerID]; ok {
|
|
||||||
if backend, ok := provider.Backends[backendID]; ok {
|
|
||||||
if server, ok := backend.Servers[serverID]; ok {
|
|
||||||
templatesRenderer.JSON(response, http.StatusOK, server)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
http.NotFound(response, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) getFrontendsHandler(response http.ResponseWriter, request *http.Request) {
|
|
||||||
vars := mux.Vars(request)
|
|
||||||
providerID := vars["provider"]
|
|
||||||
currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations)
|
|
||||||
if provider, ok := currentConfigurations[providerID]; ok {
|
|
||||||
templatesRenderer.JSON(response, http.StatusOK, provider.Frontends)
|
|
||||||
} else {
|
|
||||||
http.NotFound(response, request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) getFrontendHandler(response http.ResponseWriter, request *http.Request) {
|
|
||||||
vars := mux.Vars(request)
|
|
||||||
providerID := vars["provider"]
|
|
||||||
frontendID := vars["frontend"]
|
|
||||||
currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations)
|
|
||||||
if provider, ok := currentConfigurations[providerID]; ok {
|
|
||||||
if frontend, ok := provider.Frontends[frontendID]; ok {
|
|
||||||
templatesRenderer.JSON(response, http.StatusOK, frontend)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
http.NotFound(response, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) getRoutesHandler(response http.ResponseWriter, request *http.Request) {
|
|
||||||
vars := mux.Vars(request)
|
|
||||||
providerID := vars["provider"]
|
|
||||||
frontendID := vars["frontend"]
|
|
||||||
currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations)
|
|
||||||
if provider, ok := currentConfigurations[providerID]; ok {
|
|
||||||
if frontend, ok := provider.Frontends[frontendID]; ok {
|
|
||||||
templatesRenderer.JSON(response, http.StatusOK, frontend.Routes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
http.NotFound(response, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) getRouteHandler(response http.ResponseWriter, request *http.Request) {
|
|
||||||
|
|
||||||
vars := mux.Vars(request)
|
|
||||||
providerID := vars["provider"]
|
|
||||||
frontendID := vars["frontend"]
|
|
||||||
routeID := vars["route"]
|
|
||||||
currentConfigurations := provider.CurrentConfigurations.Get().(types.Configurations)
|
|
||||||
if provider, ok := currentConfigurations[providerID]; ok {
|
|
||||||
if frontend, ok := provider.Frontends[frontendID]; ok {
|
|
||||||
if route, ok := frontend.Routes[routeID]; ok {
|
|
||||||
templatesRenderer.JSON(response, http.StatusOK, route)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
http.NotFound(response, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func expVarHandler(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
||||||
fmt.Fprint(w, "{\n")
|
|
||||||
first := true
|
|
||||||
expvar.Do(func(kv expvar.KeyValue) {
|
|
||||||
if !first {
|
|
||||||
fmt.Fprint(w, ",\n")
|
|
||||||
}
|
|
||||||
first = false
|
|
||||||
fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
|
|
||||||
})
|
|
||||||
fmt.Fprint(w, "\n}\n")
|
|
||||||
}
|
|
|
@ -103,14 +103,18 @@ func NewServer(globalConfiguration configuration.GlobalConfiguration) *Server {
|
||||||
currentConfigurations := make(types.Configurations)
|
currentConfigurations := make(types.Configurations)
|
||||||
server.currentConfigurations.Set(currentConfigurations)
|
server.currentConfigurations.Set(currentConfigurations)
|
||||||
server.globalConfiguration = globalConfiguration
|
server.globalConfiguration = globalConfiguration
|
||||||
|
if server.globalConfiguration.API != nil {
|
||||||
|
server.globalConfiguration.API.CurrentConfigurations = &server.currentConfigurations
|
||||||
|
}
|
||||||
|
|
||||||
server.routinesPool = safe.NewPool(context.Background())
|
server.routinesPool = safe.NewPool(context.Background())
|
||||||
server.defaultForwardingRoundTripper = createHTTPTransport(globalConfiguration)
|
server.defaultForwardingRoundTripper = createHTTPTransport(globalConfiguration)
|
||||||
server.lastReceivedConfiguration = safe.New(time.Unix(0, 0))
|
server.lastReceivedConfiguration = safe.New(time.Unix(0, 0))
|
||||||
server.lastConfigs = cmap.New()
|
server.lastConfigs = cmap.New()
|
||||||
|
|
||||||
server.metricsRegistry = metrics.NewVoidRegistry()
|
server.metricsRegistry = metrics.NewVoidRegistry()
|
||||||
if globalConfiguration.Web != nil && globalConfiguration.Web.Metrics != nil {
|
if globalConfiguration.Metrics != nil {
|
||||||
server.registerMetricClients(globalConfiguration.Web.Metrics)
|
server.registerMetricClients(globalConfiguration.Metrics)
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalConfiguration.Cluster != nil {
|
if globalConfiguration.Cluster != nil {
|
||||||
|
@ -280,19 +284,21 @@ func (server *Server) startHTTPServers() {
|
||||||
|
|
||||||
func (server *Server) setupServerEntryPoint(newServerEntryPointName string, newServerEntryPoint *serverEntryPoint) *serverEntryPoint {
|
func (server *Server) setupServerEntryPoint(newServerEntryPointName string, newServerEntryPoint *serverEntryPoint) *serverEntryPoint {
|
||||||
serverMiddlewares := []negroni.Handler{middlewares.NegroniRecoverHandler()}
|
serverMiddlewares := []negroni.Handler{middlewares.NegroniRecoverHandler()}
|
||||||
|
serverInternalMiddlewares := []negroni.Handler{middlewares.NegroniRecoverHandler()}
|
||||||
if server.accessLoggerMiddleware != nil {
|
if server.accessLoggerMiddleware != nil {
|
||||||
serverMiddlewares = append(serverMiddlewares, server.accessLoggerMiddleware)
|
serverMiddlewares = append(serverMiddlewares, server.accessLoggerMiddleware)
|
||||||
}
|
}
|
||||||
if server.metricsRegistry.IsEnabled() {
|
if server.metricsRegistry.IsEnabled() {
|
||||||
serverMiddlewares = append(serverMiddlewares, middlewares.NewMetricsWrapper(server.metricsRegistry, newServerEntryPointName))
|
serverMiddlewares = append(serverMiddlewares, middlewares.NewMetricsWrapper(server.metricsRegistry, newServerEntryPointName))
|
||||||
}
|
}
|
||||||
if server.globalConfiguration.Web != nil {
|
if server.globalConfiguration.API != nil {
|
||||||
server.globalConfiguration.Web.Stats = thoas_stats.New()
|
server.globalConfiguration.API.Stats = thoas_stats.New()
|
||||||
serverMiddlewares = append(serverMiddlewares, server.globalConfiguration.Web.Stats)
|
serverMiddlewares = append(serverMiddlewares, server.globalConfiguration.API.Stats)
|
||||||
if server.globalConfiguration.Web.Statistics != nil {
|
if server.globalConfiguration.API.Statistics != nil {
|
||||||
server.globalConfiguration.Web.StatsRecorder = middlewares.NewStatsRecorder(server.globalConfiguration.Web.Statistics.RecentErrors)
|
server.globalConfiguration.API.StatsRecorder = middlewares.NewStatsRecorder(server.globalConfiguration.API.Statistics.RecentErrors)
|
||||||
serverMiddlewares = append(serverMiddlewares, server.globalConfiguration.Web.StatsRecorder)
|
serverMiddlewares = append(serverMiddlewares, server.globalConfiguration.API.StatsRecorder)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if server.globalConfiguration.EntryPoints[newServerEntryPointName].Auth != nil {
|
if server.globalConfiguration.EntryPoints[newServerEntryPointName].Auth != nil {
|
||||||
authMiddleware, err := mauth.NewAuthenticator(server.globalConfiguration.EntryPoints[newServerEntryPointName].Auth)
|
authMiddleware, err := mauth.NewAuthenticator(server.globalConfiguration.EntryPoints[newServerEntryPointName].Auth)
|
||||||
|
@ -300,6 +306,7 @@ func (server *Server) setupServerEntryPoint(newServerEntryPointName string, newS
|
||||||
log.Fatal("Error starting server: ", err)
|
log.Fatal("Error starting server: ", err)
|
||||||
}
|
}
|
||||||
serverMiddlewares = append(serverMiddlewares, authMiddleware)
|
serverMiddlewares = append(serverMiddlewares, authMiddleware)
|
||||||
|
serverInternalMiddlewares = append(serverInternalMiddlewares, authMiddleware)
|
||||||
}
|
}
|
||||||
if server.globalConfiguration.EntryPoints[newServerEntryPointName].Compress {
|
if server.globalConfiguration.EntryPoints[newServerEntryPointName].Compress {
|
||||||
serverMiddlewares = append(serverMiddlewares, &middlewares.Compress{})
|
serverMiddlewares = append(serverMiddlewares, &middlewares.Compress{})
|
||||||
|
@ -310,8 +317,9 @@ func (server *Server) setupServerEntryPoint(newServerEntryPointName string, newS
|
||||||
log.Fatal("Error starting server: ", err)
|
log.Fatal("Error starting server: ", err)
|
||||||
}
|
}
|
||||||
serverMiddlewares = append(serverMiddlewares, ipWhitelistMiddleware)
|
serverMiddlewares = append(serverMiddlewares, ipWhitelistMiddleware)
|
||||||
|
serverInternalMiddlewares = append(serverInternalMiddlewares, ipWhitelistMiddleware)
|
||||||
}
|
}
|
||||||
newSrv, listener, err := server.prepareServer(newServerEntryPointName, server.globalConfiguration.EntryPoints[newServerEntryPointName], newServerEntryPoint.httpRouter, serverMiddlewares...)
|
newSrv, listener, err := server.prepareServer(newServerEntryPointName, server.globalConfiguration.EntryPoints[newServerEntryPointName], newServerEntryPoint.httpRouter, serverMiddlewares, serverInternalMiddlewares)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error preparing server: ", err)
|
log.Fatal("Error preparing server: ", err)
|
||||||
}
|
}
|
||||||
|
@ -500,10 +508,9 @@ func (server *Server) configureProviders() {
|
||||||
if server.globalConfiguration.File != nil {
|
if server.globalConfiguration.File != nil {
|
||||||
server.providers = append(server.providers, server.globalConfiguration.File)
|
server.providers = append(server.providers, server.globalConfiguration.File)
|
||||||
}
|
}
|
||||||
if server.globalConfiguration.Web != nil {
|
if server.globalConfiguration.Rest != nil {
|
||||||
server.globalConfiguration.Web.CurrentConfigurations = &server.currentConfigurations
|
server.providers = append(server.providers, server.globalConfiguration.Rest)
|
||||||
server.globalConfiguration.Web.Debug = server.globalConfiguration.Debug
|
server.globalConfiguration.Rest.CurrentConfigurations = &server.currentConfigurations
|
||||||
server.providers = append(server.providers, server.globalConfiguration.Web)
|
|
||||||
}
|
}
|
||||||
if server.globalConfiguration.Consul != nil {
|
if server.globalConfiguration.Consul != nil {
|
||||||
server.providers = append(server.providers, server.globalConfiguration.Consul)
|
server.providers = append(server.providers, server.globalConfiguration.Consul)
|
||||||
|
@ -689,7 +696,27 @@ func (server *Server) startServer(serverEntryPoint *serverEntryPoint, globalConf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) prepareServer(entryPointName string, entryPoint *configuration.EntryPoint, router *middlewares.HandlerSwitcher, middlewares ...negroni.Handler) (*http.Server, net.Listener, error) {
|
func (server *Server) addInternalRoutes(entryPointName string, router *mux.Router) {
|
||||||
|
if server.globalConfiguration.Metrics != nil && server.globalConfiguration.Metrics.Prometheus != nil && server.globalConfiguration.Metrics.Prometheus.EntryPoint == entryPointName {
|
||||||
|
metrics.PrometheusHandler{}.AddRoutes(router)
|
||||||
|
}
|
||||||
|
|
||||||
|
if server.globalConfiguration.Rest != nil && server.globalConfiguration.Rest.EntryPoint == entryPointName {
|
||||||
|
server.globalConfiguration.Rest.AddRoutes(router)
|
||||||
|
}
|
||||||
|
|
||||||
|
if server.globalConfiguration.API != nil && server.globalConfiguration.API.EntryPoint == entryPointName {
|
||||||
|
server.globalConfiguration.API.AddRoutes(router)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *Server) addInternalPublicRoutes(entryPointName string, router *mux.Router) {
|
||||||
|
if server.globalConfiguration.Ping != nil && server.globalConfiguration.Ping.EntryPoint != "" && server.globalConfiguration.Ping.EntryPoint == entryPointName {
|
||||||
|
server.globalConfiguration.Ping.AddRoutes(router)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *Server) prepareServer(entryPointName string, entryPoint *configuration.EntryPoint, router *middlewares.HandlerSwitcher, middlewares []negroni.Handler, internalMiddlewares []negroni.Handler) (*http.Server, net.Listener, error) {
|
||||||
readTimeout, writeTimeout, idleTimeout := buildServerTimeouts(server.globalConfiguration)
|
readTimeout, writeTimeout, idleTimeout := buildServerTimeouts(server.globalConfiguration)
|
||||||
log.Infof("Preparing server %s %+v with readTimeout=%s writeTimeout=%s idleTimeout=%s", entryPointName, entryPoint, readTimeout, writeTimeout, idleTimeout)
|
log.Infof("Preparing server %s %+v with readTimeout=%s writeTimeout=%s idleTimeout=%s", entryPointName, entryPoint, readTimeout, writeTimeout, idleTimeout)
|
||||||
|
|
||||||
|
@ -700,6 +727,14 @@ func (server *Server) prepareServer(entryPointName string, entryPoint *configura
|
||||||
}
|
}
|
||||||
n.UseHandler(router)
|
n.UseHandler(router)
|
||||||
|
|
||||||
|
path := "/"
|
||||||
|
if server.globalConfiguration.Web != nil && server.globalConfiguration.Web.Path != "" {
|
||||||
|
path = server.globalConfiguration.Web.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
internalMuxRouter := server.buildInternalRouter(entryPointName, path, internalMiddlewares)
|
||||||
|
internalMuxRouter.NotFoundHandler = n
|
||||||
|
|
||||||
tlsConfig, err := server.createTLSConfig(entryPointName, entryPoint.TLS, router)
|
tlsConfig, err := server.createTLSConfig(entryPointName, entryPoint.TLS, router)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error creating TLS config: %s", err)
|
log.Errorf("Error creating TLS config: %s", err)
|
||||||
|
@ -715,7 +750,7 @@ func (server *Server) prepareServer(entryPointName string, entryPoint *configura
|
||||||
if entryPoint.ProxyProtocol != nil {
|
if entryPoint.ProxyProtocol != nil {
|
||||||
IPs, err := whitelist.NewIP(entryPoint.ProxyProtocol.TrustedIPs, entryPoint.ProxyProtocol.Insecure)
|
IPs, err := whitelist.NewIP(entryPoint.ProxyProtocol.TrustedIPs, entryPoint.ProxyProtocol.Insecure)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("Error creating whitelist: %s", err)
|
return nil, nil, fmt.Errorf("error creating whitelist: %s", err)
|
||||||
}
|
}
|
||||||
log.Infof("Enabling ProxyProtocol for trusted IPs %v", entryPoint.ProxyProtocol.TrustedIPs)
|
log.Infof("Enabling ProxyProtocol for trusted IPs %v", entryPoint.ProxyProtocol.TrustedIPs)
|
||||||
listener = &proxyproto.Listener{
|
listener = &proxyproto.Listener{
|
||||||
|
@ -723,7 +758,7 @@ func (server *Server) prepareServer(entryPointName string, entryPoint *configura
|
||||||
SourceCheck: func(addr net.Addr) (bool, error) {
|
SourceCheck: func(addr net.Addr) (bool, error) {
|
||||||
ip, ok := addr.(*net.TCPAddr)
|
ip, ok := addr.(*net.TCPAddr)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, fmt.Errorf("Type error %v", addr)
|
return false, fmt.Errorf("type error %v", addr)
|
||||||
}
|
}
|
||||||
return IPs.ContainsIP(ip.IP)
|
return IPs.ContainsIP(ip.IP)
|
||||||
},
|
},
|
||||||
|
@ -732,7 +767,7 @@ func (server *Server) prepareServer(entryPointName string, entryPoint *configura
|
||||||
|
|
||||||
return &http.Server{
|
return &http.Server{
|
||||||
Addr: entryPoint.Address,
|
Addr: entryPoint.Address,
|
||||||
Handler: n,
|
Handler: internalMuxRouter,
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
ReadTimeout: readTimeout,
|
ReadTimeout: readTimeout,
|
||||||
WriteTimeout: writeTimeout,
|
WriteTimeout: writeTimeout,
|
||||||
|
@ -742,6 +777,31 @@ func (server *Server) prepareServer(entryPointName string, entryPoint *configura
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (server *Server) buildInternalRouter(entryPointName, path string, internalMiddlewares []negroni.Handler) *mux.Router {
|
||||||
|
internalMuxRouter := mux.NewRouter()
|
||||||
|
internalMuxRouter.StrictSlash(true)
|
||||||
|
internalMuxRouter.SkipClean(true)
|
||||||
|
|
||||||
|
internalMuxSubrouter := internalMuxRouter.PathPrefix(path).Subrouter()
|
||||||
|
internalMuxSubrouter.StrictSlash(true)
|
||||||
|
internalMuxSubrouter.SkipClean(true)
|
||||||
|
|
||||||
|
server.addInternalRoutes(entryPointName, internalMuxSubrouter)
|
||||||
|
internalMuxRouter.Walk(wrapRoute(internalMiddlewares))
|
||||||
|
|
||||||
|
server.addInternalPublicRoutes(entryPointName, internalMuxSubrouter)
|
||||||
|
return internalMuxRouter
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapRoute with middlewares
|
||||||
|
func wrapRoute(middlewares []negroni.Handler) func(*mux.Route, *mux.Router, []*mux.Route) error {
|
||||||
|
return func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
|
||||||
|
middles := append(middlewares, negroni.Wrap(route.GetHandler()))
|
||||||
|
route.Handler(negroni.New(middles...))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func buildServerTimeouts(globalConfig configuration.GlobalConfiguration) (readTimeout, writeTimeout, idleTimeout time.Duration) {
|
func buildServerTimeouts(globalConfig configuration.GlobalConfiguration) (readTimeout, writeTimeout, idleTimeout time.Duration) {
|
||||||
readTimeout = time.Duration(0)
|
readTimeout = time.Duration(0)
|
||||||
writeTimeout = time.Duration(0)
|
writeTimeout = time.Duration(0)
|
||||||
|
|
|
@ -142,7 +142,7 @@ func TestPrepareServerTimeouts(t *testing.T) {
|
||||||
router := middlewares.NewHandlerSwitcher(mux.NewRouter())
|
router := middlewares.NewHandlerSwitcher(mux.NewRouter())
|
||||||
|
|
||||||
srv := NewServer(test.globalConfig)
|
srv := NewServer(test.globalConfig)
|
||||||
httpServer, _, err := srv.prepareServer(entryPointName, entryPoint, router)
|
httpServer, _, err := srv.prepareServer(entryPointName, entryPoint, router, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error when preparing srv: %s", err)
|
t.Fatalf("Unexpected error when preparing srv: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -597,7 +597,7 @@ func TestServerEntryPointWhitelistConfig(t *testing.T) {
|
||||||
|
|
||||||
srv.serverEntryPoints = srv.buildEntryPoints(srv.globalConfiguration)
|
srv.serverEntryPoints = srv.buildEntryPoints(srv.globalConfiguration)
|
||||||
srvEntryPoint := srv.setupServerEntryPoint("test", srv.serverEntryPoints["test"])
|
srvEntryPoint := srv.setupServerEntryPoint("test", srv.serverEntryPoints["test"])
|
||||||
handler := srvEntryPoint.httpServer.Handler.(*negroni.Negroni)
|
handler := srvEntryPoint.httpServer.Handler.(*mux.Router).NotFoundHandler.(*negroni.Negroni)
|
||||||
found := false
|
found := false
|
||||||
for _, handler := range handler.Handlers() {
|
for _, handler := range handler.Handlers() {
|
||||||
if reflect.TypeOf(handler) == reflect.TypeOf((*middlewares.IPWhiteLister)(nil)) {
|
if reflect.TypeOf(handler) == reflect.TypeOf((*middlewares.IPWhiteLister)(nil)) {
|
||||||
|
|
|
@ -375,7 +375,8 @@ type Metrics struct {
|
||||||
|
|
||||||
// Prometheus can contain specific configuration used by the Prometheus Metrics exporter
|
// Prometheus can contain specific configuration used by the Prometheus Metrics exporter
|
||||||
type Prometheus struct {
|
type Prometheus struct {
|
||||||
Buckets Buckets `description:"Buckets for latency metrics" export:"true"`
|
Buckets Buckets `description:"Buckets for latency metrics" export:"true"`
|
||||||
|
EntryPoint string `description:"EntryPoint" export:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Datadog contains address and metrics pushing interval configuration
|
// Datadog contains address and metrics pushing interval configuration
|
||||||
|
|
|
@ -2,11 +2,14 @@ package version
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/containous/mux"
|
||||||
"github.com/containous/traefik/log"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/google/go-github/github"
|
"github.com/google/go-github/github"
|
||||||
goversion "github.com/hashicorp/go-version"
|
goversion "github.com/hashicorp/go-version"
|
||||||
|
"github.com/unrolled/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -18,6 +21,30 @@ var (
|
||||||
BuildDate = "I don't remember exactly"
|
BuildDate = "I don't remember exactly"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Handler expose version routes
|
||||||
|
type Handler struct{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
templatesRenderer = render.New(render.Options{
|
||||||
|
Directory: "nowhere",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddRoutes add version routes on a router
|
||||||
|
func (v Handler) AddRoutes(router *mux.Router) {
|
||||||
|
router.Methods("GET").Path("/api/version").
|
||||||
|
HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||||
|
v := struct {
|
||||||
|
Version string
|
||||||
|
Codename string
|
||||||
|
}{
|
||||||
|
Version: Version,
|
||||||
|
Codename: Codename,
|
||||||
|
}
|
||||||
|
templatesRenderer.JSON(response, http.StatusOK, v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// CheckNewVersion checks if a new version is available
|
// CheckNewVersion checks if a new version is available
|
||||||
func CheckNewVersion() {
|
func CheckNewVersion() {
|
||||||
if Version == "dev" {
|
if Version == "dev" {
|
||||||
|
|
Loading…
Reference in a new issue