Improve API endpoints
This commit is contained in:
parent
c8bf8e896a
commit
74c5ec70a9
20 changed files with 2266 additions and 1204 deletions
|
@ -111,6 +111,8 @@ All the following endpoints must be accessed with a `GET` HTTP request.
|
||||||
| `/api/tcp/routers/{name}` | Returns the information of the TCP router specified by `name`. |
|
| `/api/tcp/routers/{name}` | Returns the information of the TCP router specified by `name`. |
|
||||||
| `/api/tcp/services` | Lists all the TCP services information. |
|
| `/api/tcp/services` | Lists all the TCP services information. |
|
||||||
| `/api/tcp/services/{name}` | Returns the information of the TCP service specified by `name`. |
|
| `/api/tcp/services/{name}` | Returns the information of the TCP service specified by `name`. |
|
||||||
|
| `/api/entrypoints` | Lists all the entry points information. |
|
||||||
|
| `/api/entrypoints/{name}` | Returns the information of the entry point specified by `name`. |
|
||||||
| `/api/version` | Returns information about Traefik version. |
|
| `/api/version` | Returns information about Traefik version. |
|
||||||
| `/debug/vars` | See the [expvar](https://golang.org/pkg/expvar/) Go documentation. |
|
| `/debug/vars` | See the [expvar](https://golang.org/pkg/expvar/) Go documentation. |
|
||||||
| `/debug/pprof/` | See the [pprof Index](https://golang.org/pkg/net/http/pprof/#Index) Go documentation. |
|
| `/debug/pprof/` | See the [pprof Index](https://golang.org/pkg/net/http/pprof/#Index) Go documentation. |
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -38,37 +37,6 @@ type RunTimeRepresentation struct {
|
||||||
TCPServices map[string]*dynamic.TCPServiceInfo `json:"tcpServices,omitempty"`
|
TCPServices map[string]*dynamic.TCPServiceInfo `json:"tcpServices,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type routerRepresentation struct {
|
|
||||||
*dynamic.RouterInfo
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Provider string `json:"provider,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type serviceRepresentation struct {
|
|
||||||
*dynamic.ServiceInfo
|
|
||||||
ServerStatus map[string]string `json:"serverStatus,omitempty"`
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Provider string `json:"provider,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type middlewareRepresentation struct {
|
|
||||||
*dynamic.MiddlewareInfo
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Provider string `json:"provider,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type tcpRouterRepresentation struct {
|
|
||||||
*dynamic.TCPRouterInfo
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Provider string `json:"provider,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type tcpServiceRepresentation struct {
|
|
||||||
*dynamic.TCPServiceInfo
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Provider string `json:"provider,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type pageInfo struct {
|
type pageInfo struct {
|
||||||
startIndex int
|
startIndex int
|
||||||
endIndex int
|
endIndex int
|
||||||
|
@ -81,6 +49,7 @@ type Handler struct {
|
||||||
debug bool
|
debug bool
|
||||||
// runtimeConfiguration is the data set used to create all the data representations exposed by the API.
|
// runtimeConfiguration is the data set used to create all the data representations exposed by the API.
|
||||||
runtimeConfiguration *dynamic.RuntimeConfiguration
|
runtimeConfiguration *dynamic.RuntimeConfiguration
|
||||||
|
staticConfig static.Configuration
|
||||||
statistics *types.Statistics
|
statistics *types.Statistics
|
||||||
// stats *thoasstats.Stats // FIXME stats
|
// stats *thoasstats.Stats // FIXME stats
|
||||||
// StatsRecorder *middlewares.StatsRecorder // FIXME stats
|
// StatsRecorder *middlewares.StatsRecorder // FIXME stats
|
||||||
|
@ -100,6 +69,7 @@ func New(staticConfig static.Configuration, runtimeConfig *dynamic.RuntimeConfig
|
||||||
statistics: staticConfig.API.Statistics,
|
statistics: staticConfig.API.Statistics,
|
||||||
dashboardAssets: staticConfig.API.DashboardAssets,
|
dashboardAssets: staticConfig.API.DashboardAssets,
|
||||||
runtimeConfiguration: rConfig,
|
runtimeConfiguration: rConfig,
|
||||||
|
staticConfig: staticConfig,
|
||||||
debug: staticConfig.API.Debug,
|
debug: staticConfig.API.Debug,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,6 +82,12 @@ func (h Handler) Append(router *mux.Router) {
|
||||||
|
|
||||||
router.Methods(http.MethodGet).Path("/api/rawdata").HandlerFunc(h.getRuntimeConfiguration)
|
router.Methods(http.MethodGet).Path("/api/rawdata").HandlerFunc(h.getRuntimeConfiguration)
|
||||||
|
|
||||||
|
// Experimental endpoint
|
||||||
|
router.Methods(http.MethodGet).Path("/api/overview").HandlerFunc(h.getOverview)
|
||||||
|
|
||||||
|
router.Methods(http.MethodGet).Path("/api/entrypoints").HandlerFunc(h.getEntryPoints)
|
||||||
|
router.Methods(http.MethodGet).Path("/api/entrypoints/{entryPointID}").HandlerFunc(h.getEntryPoint)
|
||||||
|
|
||||||
router.Methods(http.MethodGet).Path("/api/http/routers").HandlerFunc(h.getRouters)
|
router.Methods(http.MethodGet).Path("/api/http/routers").HandlerFunc(h.getRouters)
|
||||||
router.Methods(http.MethodGet).Path("/api/http/routers/{routerID}").HandlerFunc(h.getRouter)
|
router.Methods(http.MethodGet).Path("/api/http/routers/{routerID}").HandlerFunc(h.getRouter)
|
||||||
router.Methods(http.MethodGet).Path("/api/http/services").HandlerFunc(h.getServices)
|
router.Methods(http.MethodGet).Path("/api/http/services").HandlerFunc(h.getServices)
|
||||||
|
@ -135,283 +111,6 @@ func (h Handler) Append(router *mux.Router) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Handler) getRouters(rw http.ResponseWriter, request *http.Request) {
|
|
||||||
results := make([]routerRepresentation, 0, len(h.runtimeConfiguration.Routers))
|
|
||||||
|
|
||||||
for name, rt := range h.runtimeConfiguration.Routers {
|
|
||||||
results = append(results, routerRepresentation{
|
|
||||||
RouterInfo: rt,
|
|
||||||
Name: name,
|
|
||||||
Provider: getProviderName(name),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(results, func(i, j int) bool {
|
|
||||||
return results[i].Name < results[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
pageInfo, err := pagination(request, len(results))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
|
||||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
|
||||||
|
|
||||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
|
||||||
if err != nil {
|
|
||||||
log.FromContext(request.Context()).Error(err)
|
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Handler) getRouter(rw http.ResponseWriter, request *http.Request) {
|
|
||||||
routerID := mux.Vars(request)["routerID"]
|
|
||||||
|
|
||||||
router, ok := h.runtimeConfiguration.Routers[routerID]
|
|
||||||
if !ok {
|
|
||||||
http.NotFound(rw, request)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result := routerRepresentation{
|
|
||||||
RouterInfo: router,
|
|
||||||
Name: routerID,
|
|
||||||
Provider: getProviderName(routerID),
|
|
||||||
}
|
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
err := json.NewEncoder(rw).Encode(result)
|
|
||||||
if err != nil {
|
|
||||||
log.FromContext(request.Context()).Error(err)
|
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Handler) getServices(rw http.ResponseWriter, request *http.Request) {
|
|
||||||
results := make([]serviceRepresentation, 0, len(h.runtimeConfiguration.Services))
|
|
||||||
|
|
||||||
for name, si := range h.runtimeConfiguration.Services {
|
|
||||||
results = append(results, serviceRepresentation{
|
|
||||||
ServiceInfo: si,
|
|
||||||
Name: name,
|
|
||||||
Provider: getProviderName(name),
|
|
||||||
ServerStatus: si.GetAllStatus(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(results, func(i, j int) bool {
|
|
||||||
return results[i].Name < results[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
pageInfo, err := pagination(request, len(results))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
|
||||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
|
||||||
|
|
||||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
|
||||||
if err != nil {
|
|
||||||
log.FromContext(request.Context()).Error(err)
|
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Handler) getService(rw http.ResponseWriter, request *http.Request) {
|
|
||||||
serviceID := mux.Vars(request)["serviceID"]
|
|
||||||
|
|
||||||
service, ok := h.runtimeConfiguration.Services[serviceID]
|
|
||||||
if !ok {
|
|
||||||
http.NotFound(rw, request)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result := serviceRepresentation{
|
|
||||||
ServiceInfo: service,
|
|
||||||
Name: serviceID,
|
|
||||||
Provider: getProviderName(serviceID),
|
|
||||||
ServerStatus: service.GetAllStatus(),
|
|
||||||
}
|
|
||||||
|
|
||||||
rw.Header().Add("Content-Type", "application/json")
|
|
||||||
|
|
||||||
err := json.NewEncoder(rw).Encode(result)
|
|
||||||
if err != nil {
|
|
||||||
log.FromContext(request.Context()).Error(err)
|
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Handler) getMiddlewares(rw http.ResponseWriter, request *http.Request) {
|
|
||||||
results := make([]middlewareRepresentation, 0, len(h.runtimeConfiguration.Middlewares))
|
|
||||||
|
|
||||||
for name, mi := range h.runtimeConfiguration.Middlewares {
|
|
||||||
results = append(results, middlewareRepresentation{
|
|
||||||
MiddlewareInfo: mi,
|
|
||||||
Name: name,
|
|
||||||
Provider: getProviderName(name),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(results, func(i, j int) bool {
|
|
||||||
return results[i].Name < results[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
pageInfo, err := pagination(request, len(results))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
|
||||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
|
||||||
|
|
||||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
|
||||||
if err != nil {
|
|
||||||
log.FromContext(request.Context()).Error(err)
|
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Handler) getMiddleware(rw http.ResponseWriter, request *http.Request) {
|
|
||||||
middlewareID := mux.Vars(request)["middlewareID"]
|
|
||||||
|
|
||||||
middleware, ok := h.runtimeConfiguration.Middlewares[middlewareID]
|
|
||||||
if !ok {
|
|
||||||
http.NotFound(rw, request)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result := middlewareRepresentation{
|
|
||||||
MiddlewareInfo: middleware,
|
|
||||||
Name: middlewareID,
|
|
||||||
Provider: getProviderName(middlewareID),
|
|
||||||
}
|
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
err := json.NewEncoder(rw).Encode(result)
|
|
||||||
if err != nil {
|
|
||||||
log.FromContext(request.Context()).Error(err)
|
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Handler) getTCPRouters(rw http.ResponseWriter, request *http.Request) {
|
|
||||||
results := make([]tcpRouterRepresentation, 0, len(h.runtimeConfiguration.TCPRouters))
|
|
||||||
|
|
||||||
for name, rt := range h.runtimeConfiguration.TCPRouters {
|
|
||||||
results = append(results, tcpRouterRepresentation{
|
|
||||||
TCPRouterInfo: rt,
|
|
||||||
Name: name,
|
|
||||||
Provider: getProviderName(name),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(results, func(i, j int) bool {
|
|
||||||
return results[i].Name < results[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
pageInfo, err := pagination(request, len(results))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
|
||||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
|
||||||
|
|
||||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
|
||||||
if err != nil {
|
|
||||||
log.FromContext(request.Context()).Error(err)
|
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Handler) getTCPRouter(rw http.ResponseWriter, request *http.Request) {
|
|
||||||
routerID := mux.Vars(request)["routerID"]
|
|
||||||
|
|
||||||
router, ok := h.runtimeConfiguration.TCPRouters[routerID]
|
|
||||||
if !ok {
|
|
||||||
http.NotFound(rw, request)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result := tcpRouterRepresentation{
|
|
||||||
TCPRouterInfo: router,
|
|
||||||
Name: routerID,
|
|
||||||
Provider: getProviderName(routerID),
|
|
||||||
}
|
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
err := json.NewEncoder(rw).Encode(result)
|
|
||||||
if err != nil {
|
|
||||||
log.FromContext(request.Context()).Error(err)
|
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Handler) getTCPServices(rw http.ResponseWriter, request *http.Request) {
|
|
||||||
results := make([]tcpServiceRepresentation, 0, len(h.runtimeConfiguration.TCPServices))
|
|
||||||
|
|
||||||
for name, si := range h.runtimeConfiguration.TCPServices {
|
|
||||||
results = append(results, tcpServiceRepresentation{
|
|
||||||
TCPServiceInfo: si,
|
|
||||||
Name: name,
|
|
||||||
Provider: getProviderName(name),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(results, func(i, j int) bool {
|
|
||||||
return results[i].Name < results[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
pageInfo, err := pagination(request, len(results))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
|
||||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
|
||||||
|
|
||||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
|
||||||
if err != nil {
|
|
||||||
log.FromContext(request.Context()).Error(err)
|
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Handler) getTCPService(rw http.ResponseWriter, request *http.Request) {
|
|
||||||
serviceID := mux.Vars(request)["serviceID"]
|
|
||||||
|
|
||||||
service, ok := h.runtimeConfiguration.TCPServices[serviceID]
|
|
||||||
if !ok {
|
|
||||||
http.NotFound(rw, request)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result := tcpServiceRepresentation{
|
|
||||||
TCPServiceInfo: service,
|
|
||||||
Name: serviceID,
|
|
||||||
Provider: getProviderName(serviceID),
|
|
||||||
}
|
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
err := json.NewEncoder(rw).Encode(result)
|
|
||||||
if err != nil {
|
|
||||||
log.FromContext(request.Context()).Error(err)
|
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Handler) getRuntimeConfiguration(rw http.ResponseWriter, request *http.Request) {
|
func (h Handler) getRuntimeConfiguration(rw http.ResponseWriter, request *http.Request) {
|
||||||
siRepr := make(map[string]*serviceInfoRepresentation, len(h.runtimeConfiguration.Services))
|
siRepr := make(map[string]*serviceInfoRepresentation, len(h.runtimeConfiguration.Services))
|
||||||
for k, v := range h.runtimeConfiguration.Services {
|
for k, v := range h.runtimeConfiguration.Services {
|
||||||
|
|
70
pkg/api/handler_entrypoint.go
Normal file
70
pkg/api/handler_entrypoint.go
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/containous/mux"
|
||||||
|
"github.com/containous/traefik/pkg/config/static"
|
||||||
|
"github.com/containous/traefik/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type entryPointRepresentation struct {
|
||||||
|
*static.EntryPoint
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) getEntryPoints(rw http.ResponseWriter, request *http.Request) {
|
||||||
|
results := make([]entryPointRepresentation, 0, len(h.staticConfig.EntryPoints))
|
||||||
|
|
||||||
|
for name, ep := range h.staticConfig.EntryPoints {
|
||||||
|
results = append(results, entryPointRepresentation{
|
||||||
|
EntryPoint: ep,
|
||||||
|
Name: name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(results, func(i, j int) bool {
|
||||||
|
return results[i].Name < results[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
|
pageInfo, err := pagination(request, len(results))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||||
|
|
||||||
|
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(request.Context()).Error(err)
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) getEntryPoint(rw http.ResponseWriter, request *http.Request) {
|
||||||
|
entryPointID := mux.Vars(request)["entryPointID"]
|
||||||
|
|
||||||
|
ep, ok := h.staticConfig.EntryPoints[entryPointID]
|
||||||
|
if !ok {
|
||||||
|
http.NotFound(rw, request)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result := entryPointRepresentation{
|
||||||
|
EntryPoint: ep,
|
||||||
|
Name: entryPointID,
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
err := json.NewEncoder(rw).Encode(result)
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(request.Context()).Error(err)
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
253
pkg/api/handler_entrypoint_test.go
Normal file
253
pkg/api/handler_entrypoint_test.go
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containous/mux"
|
||||||
|
"github.com/containous/traefik/pkg/config/dynamic"
|
||||||
|
"github.com/containous/traefik/pkg/config/static"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandler_EntryPoints(t *testing.T) {
|
||||||
|
type expected struct {
|
||||||
|
statusCode int
|
||||||
|
nextPage string
|
||||||
|
jsonFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
path string
|
||||||
|
conf static.Configuration
|
||||||
|
expected expected
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "all entry points, but no config",
|
||||||
|
path: "/api/entrypoints",
|
||||||
|
conf: static.Configuration{API: &static.API{}, Global: &static.Global{}},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "1",
|
||||||
|
jsonFile: "testdata/entrypoints-empty.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all entry points",
|
||||||
|
path: "/api/entrypoints",
|
||||||
|
conf: static.Configuration{
|
||||||
|
Global: &static.Global{},
|
||||||
|
API: &static.API{},
|
||||||
|
EntryPoints: map[string]*static.EntryPoint{
|
||||||
|
"web": {
|
||||||
|
Address: ":80",
|
||||||
|
Transport: &static.EntryPointsTransport{
|
||||||
|
LifeCycle: &static.LifeCycle{
|
||||||
|
RequestAcceptGraceTimeout: 1,
|
||||||
|
GraceTimeOut: 2,
|
||||||
|
},
|
||||||
|
RespondingTimeouts: &static.RespondingTimeouts{
|
||||||
|
ReadTimeout: 3,
|
||||||
|
WriteTimeout: 4,
|
||||||
|
IdleTimeout: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ProxyProtocol: &static.ProxyProtocol{
|
||||||
|
Insecure: true,
|
||||||
|
TrustedIPs: []string{"192.168.1.1", "192.168.1.2"},
|
||||||
|
},
|
||||||
|
ForwardedHeaders: &static.ForwardedHeaders{
|
||||||
|
Insecure: true,
|
||||||
|
TrustedIPs: []string{"192.168.1.3", "192.168.1.4"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"web-secure": {
|
||||||
|
Address: ":443",
|
||||||
|
Transport: &static.EntryPointsTransport{
|
||||||
|
LifeCycle: &static.LifeCycle{
|
||||||
|
RequestAcceptGraceTimeout: 10,
|
||||||
|
GraceTimeOut: 20,
|
||||||
|
},
|
||||||
|
RespondingTimeouts: &static.RespondingTimeouts{
|
||||||
|
ReadTimeout: 30,
|
||||||
|
WriteTimeout: 40,
|
||||||
|
IdleTimeout: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ProxyProtocol: &static.ProxyProtocol{
|
||||||
|
Insecure: true,
|
||||||
|
TrustedIPs: []string{"192.168.1.10", "192.168.1.20"},
|
||||||
|
},
|
||||||
|
ForwardedHeaders: &static.ForwardedHeaders{
|
||||||
|
Insecure: true,
|
||||||
|
TrustedIPs: []string{"192.168.1.30", "192.168.1.40"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "1",
|
||||||
|
jsonFile: "testdata/entrypoints.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all entry points, pagination, 1 res per page, want page 2",
|
||||||
|
path: "/api/entrypoints?page=2&per_page=1",
|
||||||
|
conf: static.Configuration{
|
||||||
|
Global: &static.Global{},
|
||||||
|
API: &static.API{},
|
||||||
|
EntryPoints: map[string]*static.EntryPoint{
|
||||||
|
"web1": {Address: ":81"},
|
||||||
|
"web2": {Address: ":82"},
|
||||||
|
"web3": {Address: ":83"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "3",
|
||||||
|
jsonFile: "testdata/entrypoints-page2.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all entry points, pagination, 19 results overall, 7 res per page, want page 3",
|
||||||
|
path: "/api/entrypoints?page=3&per_page=7",
|
||||||
|
conf: static.Configuration{
|
||||||
|
Global: &static.Global{},
|
||||||
|
API: &static.API{},
|
||||||
|
EntryPoints: generateEntryPoints(19),
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "1",
|
||||||
|
jsonFile: "testdata/entrypoints-many-lastpage.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all entry points, pagination, 5 results overall, 10 res per page, want page 2",
|
||||||
|
path: "/api/entrypoints?page=2&per_page=10",
|
||||||
|
conf: static.Configuration{
|
||||||
|
Global: &static.Global{},
|
||||||
|
API: &static.API{},
|
||||||
|
EntryPoints: generateEntryPoints(5),
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all entry points, pagination, 10 results overall, 10 res per page, want page 2",
|
||||||
|
path: "/api/entrypoints?page=2&per_page=10",
|
||||||
|
conf: static.Configuration{
|
||||||
|
Global: &static.Global{},
|
||||||
|
API: &static.API{},
|
||||||
|
EntryPoints: generateEntryPoints(10),
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one entry point by id",
|
||||||
|
path: "/api/entrypoints/bar",
|
||||||
|
conf: static.Configuration{
|
||||||
|
Global: &static.Global{},
|
||||||
|
API: &static.API{},
|
||||||
|
EntryPoints: map[string]*static.EntryPoint{
|
||||||
|
"bar": {Address: ":81"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
jsonFile: "testdata/entrypoint-bar.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one entry point by id, that does not exist",
|
||||||
|
path: "/api/entrypoints/foo",
|
||||||
|
conf: static.Configuration{
|
||||||
|
Global: &static.Global{},
|
||||||
|
API: &static.API{},
|
||||||
|
EntryPoints: map[string]*static.EntryPoint{
|
||||||
|
"bar": {Address: ":81"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one entry point by id, but no config",
|
||||||
|
path: "/api/entrypoints/foo",
|
||||||
|
conf: static.Configuration{API: &static.API{}, Global: &static.Global{}},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
handler := New(test.conf, &dynamic.RuntimeConfiguration{})
|
||||||
|
router := mux.NewRouter()
|
||||||
|
handler.Append(router)
|
||||||
|
|
||||||
|
server := httptest.NewServer(router)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Get(server.URL + test.path)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, test.expected.statusCode, resp.StatusCode)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected.nextPage, resp.Header.Get(nextPageHeader))
|
||||||
|
|
||||||
|
if test.expected.jsonFile == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, resp.Header.Get("Content-Type"), "application/json")
|
||||||
|
contents, err := ioutil.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if *updateExpected {
|
||||||
|
var results interface{}
|
||||||
|
err := json.Unmarshal(contents, &results)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
newJSON, err := json.MarshalIndent(results, "", "\t")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(test.expected.jsonFile, newJSON, 0644)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(test.expected.jsonFile)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.JSONEq(t, string(data), string(contents))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateEntryPoints(nb int) map[string]*static.EntryPoint {
|
||||||
|
eps := make(map[string]*static.EntryPoint, nb)
|
||||||
|
for i := 0; i < nb; i++ {
|
||||||
|
eps[fmt.Sprintf("ep%2d", i)] = &static.EntryPoint{
|
||||||
|
Address: ":" + strconv.Itoa(i),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return eps
|
||||||
|
}
|
198
pkg/api/handler_http.go
Normal file
198
pkg/api/handler_http.go
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/containous/mux"
|
||||||
|
"github.com/containous/traefik/pkg/config/dynamic"
|
||||||
|
"github.com/containous/traefik/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type routerRepresentation struct {
|
||||||
|
*dynamic.RouterInfo
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Provider string `json:"provider,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type serviceRepresentation struct {
|
||||||
|
*dynamic.ServiceInfo
|
||||||
|
ServerStatus map[string]string `json:"serverStatus,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Provider string `json:"provider,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type middlewareRepresentation struct {
|
||||||
|
*dynamic.MiddlewareInfo
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Provider string `json:"provider,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) getRouters(rw http.ResponseWriter, request *http.Request) {
|
||||||
|
results := make([]routerRepresentation, 0, len(h.runtimeConfiguration.Routers))
|
||||||
|
|
||||||
|
for name, rt := range h.runtimeConfiguration.Routers {
|
||||||
|
results = append(results, routerRepresentation{
|
||||||
|
RouterInfo: rt,
|
||||||
|
Name: name,
|
||||||
|
Provider: getProviderName(name),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(results, func(i, j int) bool {
|
||||||
|
return results[i].Name < results[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
|
pageInfo, err := pagination(request, len(results))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||||
|
|
||||||
|
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(request.Context()).Error(err)
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) getRouter(rw http.ResponseWriter, request *http.Request) {
|
||||||
|
routerID := mux.Vars(request)["routerID"]
|
||||||
|
|
||||||
|
router, ok := h.runtimeConfiguration.Routers[routerID]
|
||||||
|
if !ok {
|
||||||
|
http.NotFound(rw, request)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result := routerRepresentation{
|
||||||
|
RouterInfo: router,
|
||||||
|
Name: routerID,
|
||||||
|
Provider: getProviderName(routerID),
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
err := json.NewEncoder(rw).Encode(result)
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(request.Context()).Error(err)
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) getServices(rw http.ResponseWriter, request *http.Request) {
|
||||||
|
results := make([]serviceRepresentation, 0, len(h.runtimeConfiguration.Services))
|
||||||
|
|
||||||
|
for name, si := range h.runtimeConfiguration.Services {
|
||||||
|
results = append(results, serviceRepresentation{
|
||||||
|
ServiceInfo: si,
|
||||||
|
Name: name,
|
||||||
|
Provider: getProviderName(name),
|
||||||
|
ServerStatus: si.GetAllStatus(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(results, func(i, j int) bool {
|
||||||
|
return results[i].Name < results[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
|
pageInfo, err := pagination(request, len(results))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||||
|
|
||||||
|
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(request.Context()).Error(err)
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) getService(rw http.ResponseWriter, request *http.Request) {
|
||||||
|
serviceID := mux.Vars(request)["serviceID"]
|
||||||
|
|
||||||
|
service, ok := h.runtimeConfiguration.Services[serviceID]
|
||||||
|
if !ok {
|
||||||
|
http.NotFound(rw, request)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result := serviceRepresentation{
|
||||||
|
ServiceInfo: service,
|
||||||
|
Name: serviceID,
|
||||||
|
Provider: getProviderName(serviceID),
|
||||||
|
ServerStatus: service.GetAllStatus(),
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.Header().Add("Content-Type", "application/json")
|
||||||
|
|
||||||
|
err := json.NewEncoder(rw).Encode(result)
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(request.Context()).Error(err)
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) getMiddlewares(rw http.ResponseWriter, request *http.Request) {
|
||||||
|
results := make([]middlewareRepresentation, 0, len(h.runtimeConfiguration.Middlewares))
|
||||||
|
|
||||||
|
for name, mi := range h.runtimeConfiguration.Middlewares {
|
||||||
|
results = append(results, middlewareRepresentation{
|
||||||
|
MiddlewareInfo: mi,
|
||||||
|
Name: name,
|
||||||
|
Provider: getProviderName(name),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(results, func(i, j int) bool {
|
||||||
|
return results[i].Name < results[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
|
pageInfo, err := pagination(request, len(results))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||||
|
|
||||||
|
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(request.Context()).Error(err)
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) getMiddleware(rw http.ResponseWriter, request *http.Request) {
|
||||||
|
middlewareID := mux.Vars(request)["middlewareID"]
|
||||||
|
|
||||||
|
middleware, ok := h.runtimeConfiguration.Middlewares[middlewareID]
|
||||||
|
if !ok {
|
||||||
|
http.NotFound(rw, request)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result := middlewareRepresentation{
|
||||||
|
MiddlewareInfo: middleware,
|
||||||
|
Name: middlewareID,
|
||||||
|
Provider: getProviderName(middlewareID),
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
err := json.NewEncoder(rw).Encode(result)
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(request.Context()).Error(err)
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
575
pkg/api/handler_http_test.go
Normal file
575
pkg/api/handler_http_test.go
Normal file
|
@ -0,0 +1,575 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containous/mux"
|
||||||
|
"github.com/containous/traefik/pkg/config/dynamic"
|
||||||
|
"github.com/containous/traefik/pkg/config/static"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandler_HTTP(t *testing.T) {
|
||||||
|
type expected struct {
|
||||||
|
statusCode int
|
||||||
|
nextPage string
|
||||||
|
jsonFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
path string
|
||||||
|
conf dynamic.RuntimeConfiguration
|
||||||
|
expected expected
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "all routers, but no config",
|
||||||
|
path: "/api/http/routers",
|
||||||
|
conf: dynamic.RuntimeConfiguration{},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "1",
|
||||||
|
jsonFile: "testdata/routers-empty.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all routers",
|
||||||
|
path: "/api/http/routers",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
Routers: map[string]*dynamic.RouterInfo{
|
||||||
|
"test@myprovider": {
|
||||||
|
Router: &dynamic.Router{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar.other`)",
|
||||||
|
Middlewares: []string{"addPrefixTest", "auth"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"bar@myprovider": {
|
||||||
|
Router: &dynamic.Router{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar`)",
|
||||||
|
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "1",
|
||||||
|
jsonFile: "testdata/routers.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all routers, pagination, 1 res per page, want page 2",
|
||||||
|
path: "/api/http/routers?page=2&per_page=1",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
Routers: map[string]*dynamic.RouterInfo{
|
||||||
|
"bar@myprovider": {
|
||||||
|
Router: &dynamic.Router{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar`)",
|
||||||
|
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"baz@myprovider": {
|
||||||
|
Router: &dynamic.Router{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`toto.bar`)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test@myprovider": {
|
||||||
|
Router: &dynamic.Router{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar.other`)",
|
||||||
|
Middlewares: []string{"addPrefixTest", "auth"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "3",
|
||||||
|
jsonFile: "testdata/routers-page2.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all routers, pagination, 19 results overall, 7 res per page, want page 3",
|
||||||
|
path: "/api/http/routers?page=3&per_page=7",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
Routers: generateHTTPRouters(19),
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "1",
|
||||||
|
jsonFile: "testdata/routers-many-lastpage.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all routers, pagination, 5 results overall, 10 res per page, want page 2",
|
||||||
|
path: "/api/http/routers?page=2&per_page=10",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
Routers: generateHTTPRouters(5),
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all routers, pagination, 10 results overall, 10 res per page, want page 2",
|
||||||
|
path: "/api/http/routers?page=2&per_page=10",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
Routers: generateHTTPRouters(10),
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one router by id",
|
||||||
|
path: "/api/http/routers/bar@myprovider",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
Routers: map[string]*dynamic.RouterInfo{
|
||||||
|
"bar@myprovider": {
|
||||||
|
Router: &dynamic.Router{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar`)",
|
||||||
|
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
jsonFile: "testdata/router-bar.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one router by id, that does not exist",
|
||||||
|
path: "/api/http/routers/foo@myprovider",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
Routers: map[string]*dynamic.RouterInfo{
|
||||||
|
"bar@myprovider": {
|
||||||
|
Router: &dynamic.Router{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar`)",
|
||||||
|
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one router by id, but no config",
|
||||||
|
path: "/api/http/routers/foo@myprovider",
|
||||||
|
conf: dynamic.RuntimeConfiguration{},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all services, but no config",
|
||||||
|
path: "/api/http/services",
|
||||||
|
conf: dynamic.RuntimeConfiguration{},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "1",
|
||||||
|
jsonFile: "testdata/services-empty.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all services",
|
||||||
|
path: "/api/http/services",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
Services: map[string]*dynamic.ServiceInfo{
|
||||||
|
"bar@myprovider": func() *dynamic.ServiceInfo {
|
||||||
|
si := &dynamic.ServiceInfo{
|
||||||
|
Service: &dynamic.Service{
|
||||||
|
LoadBalancer: &dynamic.LoadBalancerService{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://127.0.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||||
|
}
|
||||||
|
si.UpdateStatus("http://127.0.0.1", "UP")
|
||||||
|
return si
|
||||||
|
}(),
|
||||||
|
"baz@myprovider": func() *dynamic.ServiceInfo {
|
||||||
|
si := &dynamic.ServiceInfo{
|
||||||
|
Service: &dynamic.Service{
|
||||||
|
LoadBalancer: &dynamic.LoadBalancerService{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://127.0.0.2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"foo@myprovider"},
|
||||||
|
}
|
||||||
|
si.UpdateStatus("http://127.0.0.2", "UP")
|
||||||
|
return si
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "1",
|
||||||
|
jsonFile: "testdata/services.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all services, 1 res per page, want page 2",
|
||||||
|
path: "/api/http/services?page=2&per_page=1",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
Services: map[string]*dynamic.ServiceInfo{
|
||||||
|
"bar@myprovider": func() *dynamic.ServiceInfo {
|
||||||
|
si := &dynamic.ServiceInfo{
|
||||||
|
Service: &dynamic.Service{
|
||||||
|
LoadBalancer: &dynamic.LoadBalancerService{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://127.0.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||||
|
}
|
||||||
|
si.UpdateStatus("http://127.0.0.1", "UP")
|
||||||
|
return si
|
||||||
|
}(),
|
||||||
|
"baz@myprovider": func() *dynamic.ServiceInfo {
|
||||||
|
si := &dynamic.ServiceInfo{
|
||||||
|
Service: &dynamic.Service{
|
||||||
|
LoadBalancer: &dynamic.LoadBalancerService{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://127.0.0.2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"foo@myprovider"},
|
||||||
|
}
|
||||||
|
si.UpdateStatus("http://127.0.0.2", "UP")
|
||||||
|
return si
|
||||||
|
}(),
|
||||||
|
"test@myprovider": func() *dynamic.ServiceInfo {
|
||||||
|
si := &dynamic.ServiceInfo{
|
||||||
|
Service: &dynamic.Service{
|
||||||
|
LoadBalancer: &dynamic.LoadBalancerService{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://127.0.0.3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||||
|
}
|
||||||
|
si.UpdateStatus("http://127.0.0.4", "UP")
|
||||||
|
return si
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "3",
|
||||||
|
jsonFile: "testdata/services-page2.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one service by id",
|
||||||
|
path: "/api/http/services/bar@myprovider",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
Services: map[string]*dynamic.ServiceInfo{
|
||||||
|
"bar@myprovider": func() *dynamic.ServiceInfo {
|
||||||
|
si := &dynamic.ServiceInfo{
|
||||||
|
Service: &dynamic.Service{
|
||||||
|
LoadBalancer: &dynamic.LoadBalancerService{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://127.0.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||||
|
}
|
||||||
|
si.UpdateStatus("http://127.0.0.1", "UP")
|
||||||
|
return si
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
jsonFile: "testdata/service-bar.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one service by id, that does not exist",
|
||||||
|
path: "/api/http/services/nono@myprovider",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
Services: map[string]*dynamic.ServiceInfo{
|
||||||
|
"bar@myprovider": func() *dynamic.ServiceInfo {
|
||||||
|
si := &dynamic.ServiceInfo{
|
||||||
|
Service: &dynamic.Service{
|
||||||
|
LoadBalancer: &dynamic.LoadBalancerService{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://127.0.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||||
|
}
|
||||||
|
si.UpdateStatus("http://127.0.0.1", "UP")
|
||||||
|
return si
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one service by id, but no config",
|
||||||
|
path: "/api/http/services/foo@myprovider",
|
||||||
|
conf: dynamic.RuntimeConfiguration{},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all middlewares, but no config",
|
||||||
|
path: "/api/http/middlewares",
|
||||||
|
conf: dynamic.RuntimeConfiguration{},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "1",
|
||||||
|
jsonFile: "testdata/middlewares-empty.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all middlewares",
|
||||||
|
path: "/api/http/middlewares",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
Middlewares: map[string]*dynamic.MiddlewareInfo{
|
||||||
|
"auth@myprovider": {
|
||||||
|
Middleware: &dynamic.Middleware{
|
||||||
|
BasicAuth: &dynamic.BasicAuth{
|
||||||
|
Users: []string{"admin:admin"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
||||||
|
},
|
||||||
|
"addPrefixTest@myprovider": {
|
||||||
|
Middleware: &dynamic.Middleware{
|
||||||
|
AddPrefix: &dynamic.AddPrefix{
|
||||||
|
Prefix: "/titi",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"test@myprovider"},
|
||||||
|
},
|
||||||
|
"addPrefixTest@anotherprovider": {
|
||||||
|
Middleware: &dynamic.Middleware{
|
||||||
|
AddPrefix: &dynamic.AddPrefix{
|
||||||
|
Prefix: "/toto",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"bar@myprovider"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "1",
|
||||||
|
jsonFile: "testdata/middlewares.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all middlewares, 1 res per page, want page 2",
|
||||||
|
path: "/api/http/middlewares?page=2&per_page=1",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
Middlewares: map[string]*dynamic.MiddlewareInfo{
|
||||||
|
"auth@myprovider": {
|
||||||
|
Middleware: &dynamic.Middleware{
|
||||||
|
BasicAuth: &dynamic.BasicAuth{
|
||||||
|
Users: []string{"admin:admin"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
||||||
|
},
|
||||||
|
"addPrefixTest@myprovider": {
|
||||||
|
Middleware: &dynamic.Middleware{
|
||||||
|
AddPrefix: &dynamic.AddPrefix{
|
||||||
|
Prefix: "/titi",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"test@myprovider"},
|
||||||
|
},
|
||||||
|
"addPrefixTest@anotherprovider": {
|
||||||
|
Middleware: &dynamic.Middleware{
|
||||||
|
AddPrefix: &dynamic.AddPrefix{
|
||||||
|
Prefix: "/toto",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"bar@myprovider"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "3",
|
||||||
|
jsonFile: "testdata/middlewares-page2.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one middleware by id",
|
||||||
|
path: "/api/http/middlewares/auth@myprovider",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
Middlewares: map[string]*dynamic.MiddlewareInfo{
|
||||||
|
"auth@myprovider": {
|
||||||
|
Middleware: &dynamic.Middleware{
|
||||||
|
BasicAuth: &dynamic.BasicAuth{
|
||||||
|
Users: []string{"admin:admin"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
||||||
|
},
|
||||||
|
"addPrefixTest@myprovider": {
|
||||||
|
Middleware: &dynamic.Middleware{
|
||||||
|
AddPrefix: &dynamic.AddPrefix{
|
||||||
|
Prefix: "/titi",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"test@myprovider"},
|
||||||
|
},
|
||||||
|
"addPrefixTest@anotherprovider": {
|
||||||
|
Middleware: &dynamic.Middleware{
|
||||||
|
AddPrefix: &dynamic.AddPrefix{
|
||||||
|
Prefix: "/toto",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"bar@myprovider"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
jsonFile: "testdata/middleware-auth.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one middleware by id, that does not exist",
|
||||||
|
path: "/api/http/middlewares/foo@myprovider",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
Middlewares: map[string]*dynamic.MiddlewareInfo{
|
||||||
|
"auth@myprovider": {
|
||||||
|
Middleware: &dynamic.Middleware{
|
||||||
|
BasicAuth: &dynamic.BasicAuth{
|
||||||
|
Users: []string{"admin:admin"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one middleware by id, but no config",
|
||||||
|
path: "/api/http/middlewares/foo@myprovider",
|
||||||
|
conf: dynamic.RuntimeConfiguration{},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
rtConf := &test.conf
|
||||||
|
handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf)
|
||||||
|
router := mux.NewRouter()
|
||||||
|
handler.Append(router)
|
||||||
|
|
||||||
|
server := httptest.NewServer(router)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Get(server.URL + test.path)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, test.expected.statusCode, resp.StatusCode)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected.nextPage, resp.Header.Get(nextPageHeader))
|
||||||
|
|
||||||
|
if test.expected.jsonFile == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, resp.Header.Get("Content-Type"), "application/json")
|
||||||
|
contents, err := ioutil.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if *updateExpected {
|
||||||
|
var results interface{}
|
||||||
|
err := json.Unmarshal(contents, &results)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
newJSON, err := json.MarshalIndent(results, "", "\t")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(test.expected.jsonFile, newJSON, 0644)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(test.expected.jsonFile)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.JSONEq(t, string(data), string(contents))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateHTTPRouters(nbRouters int) map[string]*dynamic.RouterInfo {
|
||||||
|
routers := make(map[string]*dynamic.RouterInfo, nbRouters)
|
||||||
|
for i := 0; i < nbRouters; i++ {
|
||||||
|
routers[fmt.Sprintf("bar%2d@myprovider", i)] = &dynamic.RouterInfo{
|
||||||
|
Router: &dynamic.Router{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar" + strconv.Itoa(i) + "`)",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return routers
|
||||||
|
}
|
200
pkg/api/handler_overview.go
Normal file
200
pkg/api/handler_overview.go
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/pkg/config/dynamic"
|
||||||
|
"github.com/containous/traefik/pkg/config/static"
|
||||||
|
"github.com/containous/traefik/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type schemeOverview struct {
|
||||||
|
Routers *section `json:"routers,omitempty"`
|
||||||
|
Services *section `json:"services,omitempty"`
|
||||||
|
Middlewares *section `json:"middlewares,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type section struct {
|
||||||
|
Total int `json:"total"`
|
||||||
|
Warnings int `json:"warnings"`
|
||||||
|
Errors int `json:"errors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type features struct {
|
||||||
|
Tracing string `json:"tracing"`
|
||||||
|
Metrics string `json:"metrics"`
|
||||||
|
AccessLog bool `json:"accessLog"`
|
||||||
|
// TODO add certificates resolvers
|
||||||
|
}
|
||||||
|
|
||||||
|
type overview struct {
|
||||||
|
HTTP schemeOverview `json:"http"`
|
||||||
|
TCP schemeOverview `json:"tcp"`
|
||||||
|
Features features `json:"features,omitempty"`
|
||||||
|
Providers []string `json:"providers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) getOverview(rw http.ResponseWriter, request *http.Request) {
|
||||||
|
result := overview{
|
||||||
|
HTTP: schemeOverview{
|
||||||
|
Routers: getHTTPRouterSection(h.runtimeConfiguration.Routers),
|
||||||
|
Services: getHTTPServiceSection(h.runtimeConfiguration.Services),
|
||||||
|
Middlewares: getHTTPMiddlewareSection(h.runtimeConfiguration.Middlewares),
|
||||||
|
},
|
||||||
|
TCP: schemeOverview{
|
||||||
|
Routers: getTCPRouterSection(h.runtimeConfiguration.TCPRouters),
|
||||||
|
Services: getTCPServiceSection(h.runtimeConfiguration.TCPServices),
|
||||||
|
},
|
||||||
|
Features: getFeatures(h.staticConfig),
|
||||||
|
Providers: getProviders(h.staticConfig),
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
err := json.NewEncoder(rw).Encode(result)
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(request.Context()).Error(err)
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHTTPRouterSection(routers map[string]*dynamic.RouterInfo) *section {
|
||||||
|
var countErrors int
|
||||||
|
for _, rt := range routers {
|
||||||
|
if rt.Err != "" {
|
||||||
|
countErrors++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return §ion{
|
||||||
|
Total: len(routers),
|
||||||
|
Warnings: 0, // TODO
|
||||||
|
Errors: countErrors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHTTPServiceSection(services map[string]*dynamic.ServiceInfo) *section {
|
||||||
|
var countErrors int
|
||||||
|
for _, svc := range services {
|
||||||
|
if svc.Err != nil {
|
||||||
|
countErrors++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return §ion{
|
||||||
|
Total: len(services),
|
||||||
|
Warnings: 0, // TODO
|
||||||
|
Errors: countErrors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHTTPMiddlewareSection(middlewares map[string]*dynamic.MiddlewareInfo) *section {
|
||||||
|
var countErrors int
|
||||||
|
for _, md := range middlewares {
|
||||||
|
if md.Err != nil {
|
||||||
|
countErrors++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return §ion{
|
||||||
|
Total: len(middlewares),
|
||||||
|
Warnings: 0, // TODO
|
||||||
|
Errors: countErrors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTCPRouterSection(routers map[string]*dynamic.TCPRouterInfo) *section {
|
||||||
|
var countErrors int
|
||||||
|
for _, rt := range routers {
|
||||||
|
if rt.Err != "" {
|
||||||
|
countErrors++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return §ion{
|
||||||
|
Total: len(routers),
|
||||||
|
Warnings: 0, // TODO
|
||||||
|
Errors: countErrors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTCPServiceSection(services map[string]*dynamic.TCPServiceInfo) *section {
|
||||||
|
var countErrors int
|
||||||
|
for _, svc := range services {
|
||||||
|
if svc.Err != nil {
|
||||||
|
countErrors++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return §ion{
|
||||||
|
Total: len(services),
|
||||||
|
Warnings: 0, // TODO
|
||||||
|
Errors: countErrors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProviders(conf static.Configuration) []string {
|
||||||
|
if conf.Providers == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var providers []string
|
||||||
|
|
||||||
|
v := reflect.ValueOf(conf.Providers).Elem()
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
field := v.Field(i)
|
||||||
|
if field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct {
|
||||||
|
if !field.IsNil() {
|
||||||
|
providers = append(providers, v.Type().Field(i).Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return providers
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFeatures(conf static.Configuration) features {
|
||||||
|
return features{
|
||||||
|
Tracing: getTracing(conf),
|
||||||
|
Metrics: getMetrics(conf),
|
||||||
|
AccessLog: conf.AccessLog != nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMetrics(conf static.Configuration) string {
|
||||||
|
if conf.Metrics == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
v := reflect.ValueOf(conf.Metrics).Elem()
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
field := v.Field(i)
|
||||||
|
if field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct {
|
||||||
|
if !field.IsNil() {
|
||||||
|
return v.Type().Field(i).Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTracing(conf static.Configuration) string {
|
||||||
|
if conf.Tracing == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
v := reflect.ValueOf(conf.Tracing).Elem()
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
field := v.Field(i)
|
||||||
|
if field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct {
|
||||||
|
if !field.IsNil() {
|
||||||
|
return v.Type().Field(i).Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
230
pkg/api/handler_overview_test.go
Normal file
230
pkg/api/handler_overview_test.go
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containous/mux"
|
||||||
|
"github.com/containous/traefik/pkg/config/dynamic"
|
||||||
|
"github.com/containous/traefik/pkg/config/static"
|
||||||
|
"github.com/containous/traefik/pkg/provider/docker"
|
||||||
|
"github.com/containous/traefik/pkg/provider/file"
|
||||||
|
"github.com/containous/traefik/pkg/provider/kubernetes/crd"
|
||||||
|
"github.com/containous/traefik/pkg/provider/kubernetes/ingress"
|
||||||
|
"github.com/containous/traefik/pkg/provider/marathon"
|
||||||
|
"github.com/containous/traefik/pkg/provider/rancher"
|
||||||
|
"github.com/containous/traefik/pkg/provider/rest"
|
||||||
|
"github.com/containous/traefik/pkg/tracing/jaeger"
|
||||||
|
"github.com/containous/traefik/pkg/types"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandler_Overview(t *testing.T) {
|
||||||
|
type expected struct {
|
||||||
|
statusCode int
|
||||||
|
jsonFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
path string
|
||||||
|
confStatic static.Configuration
|
||||||
|
confDyn dynamic.RuntimeConfiguration
|
||||||
|
expected expected
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "without data in the dynamic configuration",
|
||||||
|
path: "/api/overview",
|
||||||
|
confStatic: static.Configuration{API: &static.API{}, Global: &static.Global{}},
|
||||||
|
confDyn: dynamic.RuntimeConfiguration{},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
jsonFile: "testdata/overview-empty.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "with data in the dynamic configuration",
|
||||||
|
path: "/api/overview",
|
||||||
|
confStatic: static.Configuration{API: &static.API{}, Global: &static.Global{}},
|
||||||
|
confDyn: dynamic.RuntimeConfiguration{
|
||||||
|
Services: map[string]*dynamic.ServiceInfo{
|
||||||
|
"foo-service@myprovider": {
|
||||||
|
Service: &dynamic.Service{
|
||||||
|
LoadBalancer: &dynamic.LoadBalancerService{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://127.0.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.MiddlewareInfo{
|
||||||
|
"auth@myprovider": {
|
||||||
|
Middleware: &dynamic.Middleware{
|
||||||
|
BasicAuth: &dynamic.BasicAuth{
|
||||||
|
Users: []string{"admin:admin"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"addPrefixTest@myprovider": {
|
||||||
|
Middleware: &dynamic.Middleware{
|
||||||
|
AddPrefix: &dynamic.AddPrefix{
|
||||||
|
Prefix: "/titi",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"addPrefixTest@anotherprovider": {
|
||||||
|
Middleware: &dynamic.Middleware{
|
||||||
|
AddPrefix: &dynamic.AddPrefix{
|
||||||
|
Prefix: "/toto",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Routers: map[string]*dynamic.RouterInfo{
|
||||||
|
"bar@myprovider": {
|
||||||
|
Router: &dynamic.Router{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar`)",
|
||||||
|
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test@myprovider": {
|
||||||
|
Router: &dynamic.Router{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar.other`)",
|
||||||
|
Middlewares: []string{"addPrefixTest", "auth"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TCPServices: map[string]*dynamic.TCPServiceInfo{
|
||||||
|
"tcpfoo-service@myprovider": {
|
||||||
|
TCPService: &dynamic.TCPService{
|
||||||
|
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||||
|
Servers: []dynamic.TCPServer{
|
||||||
|
{
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TCPRouters: map[string]*dynamic.TCPRouterInfo{
|
||||||
|
"tcpbar@myprovider": {
|
||||||
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "tcpfoo-service@myprovider",
|
||||||
|
Rule: "HostSNI(`foo.bar`)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tcptest@myprovider": {
|
||||||
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "tcpfoo-service@myprovider",
|
||||||
|
Rule: "HostSNI(`foo.bar.other`)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
jsonFile: "testdata/overview-dynamic.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "with providers",
|
||||||
|
path: "/api/overview",
|
||||||
|
confStatic: static.Configuration{
|
||||||
|
Global: &static.Global{},
|
||||||
|
API: &static.API{},
|
||||||
|
Providers: &static.Providers{
|
||||||
|
Docker: &docker.Provider{},
|
||||||
|
File: &file.Provider{},
|
||||||
|
Marathon: &marathon.Provider{},
|
||||||
|
KubernetesIngress: &ingress.Provider{},
|
||||||
|
KubernetesCRD: &crd.Provider{},
|
||||||
|
Rest: &rest.Provider{},
|
||||||
|
Rancher: &rancher.Provider{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
confDyn: dynamic.RuntimeConfiguration{},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
jsonFile: "testdata/overview-providers.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "with features",
|
||||||
|
path: "/api/overview",
|
||||||
|
confStatic: static.Configuration{
|
||||||
|
Global: &static.Global{},
|
||||||
|
API: &static.API{},
|
||||||
|
Metrics: &types.Metrics{
|
||||||
|
Prometheus: &types.Prometheus{},
|
||||||
|
},
|
||||||
|
Tracing: &static.Tracing{
|
||||||
|
Jaeger: &jaeger.Config{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
confDyn: dynamic.RuntimeConfiguration{},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
jsonFile: "testdata/overview-features.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
handler := New(test.confStatic, &test.confDyn)
|
||||||
|
router := mux.NewRouter()
|
||||||
|
handler.Append(router)
|
||||||
|
|
||||||
|
server := httptest.NewServer(router)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Get(server.URL + test.path)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, test.expected.statusCode, resp.StatusCode)
|
||||||
|
|
||||||
|
if test.expected.jsonFile == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, resp.Header.Get("Content-Type"), "application/json")
|
||||||
|
contents, err := ioutil.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if *updateExpected {
|
||||||
|
var results interface{}
|
||||||
|
err := json.Unmarshal(contents, &results)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
newJSON, err := json.MarshalIndent(results, "", "\t")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(test.expected.jsonFile, newJSON, 0644)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(test.expected.jsonFile)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.JSONEq(t, string(data), string(contents))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
134
pkg/api/handler_tcp.go
Normal file
134
pkg/api/handler_tcp.go
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/containous/mux"
|
||||||
|
"github.com/containous/traefik/pkg/config/dynamic"
|
||||||
|
"github.com/containous/traefik/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tcpRouterRepresentation struct {
|
||||||
|
*dynamic.TCPRouterInfo
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Provider string `json:"provider,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type tcpServiceRepresentation struct {
|
||||||
|
*dynamic.TCPServiceInfo
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Provider string `json:"provider,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) getTCPRouters(rw http.ResponseWriter, request *http.Request) {
|
||||||
|
results := make([]tcpRouterRepresentation, 0, len(h.runtimeConfiguration.TCPRouters))
|
||||||
|
|
||||||
|
for name, rt := range h.runtimeConfiguration.TCPRouters {
|
||||||
|
results = append(results, tcpRouterRepresentation{
|
||||||
|
TCPRouterInfo: rt,
|
||||||
|
Name: name,
|
||||||
|
Provider: getProviderName(name),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(results, func(i, j int) bool {
|
||||||
|
return results[i].Name < results[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
|
pageInfo, err := pagination(request, len(results))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||||
|
|
||||||
|
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(request.Context()).Error(err)
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) getTCPRouter(rw http.ResponseWriter, request *http.Request) {
|
||||||
|
routerID := mux.Vars(request)["routerID"]
|
||||||
|
|
||||||
|
router, ok := h.runtimeConfiguration.TCPRouters[routerID]
|
||||||
|
if !ok {
|
||||||
|
http.NotFound(rw, request)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result := tcpRouterRepresentation{
|
||||||
|
TCPRouterInfo: router,
|
||||||
|
Name: routerID,
|
||||||
|
Provider: getProviderName(routerID),
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
err := json.NewEncoder(rw).Encode(result)
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(request.Context()).Error(err)
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) getTCPServices(rw http.ResponseWriter, request *http.Request) {
|
||||||
|
results := make([]tcpServiceRepresentation, 0, len(h.runtimeConfiguration.TCPServices))
|
||||||
|
|
||||||
|
for name, si := range h.runtimeConfiguration.TCPServices {
|
||||||
|
results = append(results, tcpServiceRepresentation{
|
||||||
|
TCPServiceInfo: si,
|
||||||
|
Name: name,
|
||||||
|
Provider: getProviderName(name),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(results, func(i, j int) bool {
|
||||||
|
return results[i].Name < results[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
|
pageInfo, err := pagination(request, len(results))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||||
|
|
||||||
|
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(request.Context()).Error(err)
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) getTCPService(rw http.ResponseWriter, request *http.Request) {
|
||||||
|
serviceID := mux.Vars(request)["serviceID"]
|
||||||
|
|
||||||
|
service, ok := h.runtimeConfiguration.TCPServices[serviceID]
|
||||||
|
if !ok {
|
||||||
|
http.NotFound(rw, request)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result := tcpServiceRepresentation{
|
||||||
|
TCPServiceInfo: service,
|
||||||
|
Name: serviceID,
|
||||||
|
Provider: getProviderName(serviceID),
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
err := json.NewEncoder(rw).Encode(result)
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(request.Context()).Error(err)
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
349
pkg/api/handler_tcp_test.go
Normal file
349
pkg/api/handler_tcp_test.go
Normal file
|
@ -0,0 +1,349 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containous/mux"
|
||||||
|
"github.com/containous/traefik/pkg/config/dynamic"
|
||||||
|
"github.com/containous/traefik/pkg/config/static"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandler_TCP(t *testing.T) {
|
||||||
|
type expected struct {
|
||||||
|
statusCode int
|
||||||
|
nextPage string
|
||||||
|
jsonFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
path string
|
||||||
|
conf dynamic.RuntimeConfiguration
|
||||||
|
expected expected
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "all TCP routers, but no config",
|
||||||
|
path: "/api/tcp/routers",
|
||||||
|
conf: dynamic.RuntimeConfiguration{},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "1",
|
||||||
|
jsonFile: "testdata/tcprouters-empty.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all TCP routers",
|
||||||
|
path: "/api/tcp/routers",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
TCPRouters: map[string]*dynamic.TCPRouterInfo{
|
||||||
|
"test@myprovider": {
|
||||||
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar.other`)",
|
||||||
|
TLS: &dynamic.RouterTCPTLSConfig{
|
||||||
|
Passthrough: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"bar@myprovider": {
|
||||||
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar`)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "1",
|
||||||
|
jsonFile: "testdata/tcprouters.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all TCP routers, pagination, 1 res per page, want page 2",
|
||||||
|
path: "/api/tcp/routers?page=2&per_page=1",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
TCPRouters: map[string]*dynamic.TCPRouterInfo{
|
||||||
|
"bar@myprovider": {
|
||||||
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar`)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"baz@myprovider": {
|
||||||
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`toto.bar`)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test@myprovider": {
|
||||||
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar.other`)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "3",
|
||||||
|
jsonFile: "testdata/tcprouters-page2.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one TCP router by id",
|
||||||
|
path: "/api/tcp/routers/bar@myprovider",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
TCPRouters: map[string]*dynamic.TCPRouterInfo{
|
||||||
|
"bar@myprovider": {
|
||||||
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar`)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
jsonFile: "testdata/tcprouter-bar.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one TCP router by id, that does not exist",
|
||||||
|
path: "/api/tcp/routers/foo@myprovider",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
TCPRouters: map[string]*dynamic.TCPRouterInfo{
|
||||||
|
"bar@myprovider": {
|
||||||
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar`)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one TCP router by id, but no config",
|
||||||
|
path: "/api/tcp/routers/bar@myprovider",
|
||||||
|
conf: dynamic.RuntimeConfiguration{},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all tcp services, but no config",
|
||||||
|
path: "/api/tcp/services",
|
||||||
|
conf: dynamic.RuntimeConfiguration{},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "1",
|
||||||
|
jsonFile: "testdata/tcpservices-empty.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all tcp services",
|
||||||
|
path: "/api/tcp/services",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
TCPServices: map[string]*dynamic.TCPServiceInfo{
|
||||||
|
"bar@myprovider": {
|
||||||
|
TCPService: &dynamic.TCPService{
|
||||||
|
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||||
|
Servers: []dynamic.TCPServer{
|
||||||
|
{
|
||||||
|
Address: "127.0.0.1:2345",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||||
|
},
|
||||||
|
"baz@myprovider": {
|
||||||
|
TCPService: &dynamic.TCPService{
|
||||||
|
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||||
|
Servers: []dynamic.TCPServer{
|
||||||
|
{
|
||||||
|
Address: "127.0.0.2:2345",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"foo@myprovider"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "1",
|
||||||
|
jsonFile: "testdata/tcpservices.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all tcp services, 1 res per page, want page 2",
|
||||||
|
path: "/api/tcp/services?page=2&per_page=1",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
TCPServices: map[string]*dynamic.TCPServiceInfo{
|
||||||
|
"bar@myprovider": {
|
||||||
|
TCPService: &dynamic.TCPService{
|
||||||
|
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||||
|
Servers: []dynamic.TCPServer{
|
||||||
|
{
|
||||||
|
Address: "127.0.0.1:2345",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||||
|
},
|
||||||
|
"baz@myprovider": {
|
||||||
|
TCPService: &dynamic.TCPService{
|
||||||
|
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||||
|
Servers: []dynamic.TCPServer{
|
||||||
|
{
|
||||||
|
Address: "127.0.0.2:2345",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"foo@myprovider"},
|
||||||
|
},
|
||||||
|
"test@myprovider": {
|
||||||
|
TCPService: &dynamic.TCPService{
|
||||||
|
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||||
|
Servers: []dynamic.TCPServer{
|
||||||
|
{
|
||||||
|
Address: "127.0.0.3:2345",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "3",
|
||||||
|
jsonFile: "testdata/tcpservices-page2.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one tcp service by id",
|
||||||
|
path: "/api/tcp/services/bar@myprovider",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
TCPServices: map[string]*dynamic.TCPServiceInfo{
|
||||||
|
"bar@myprovider": {
|
||||||
|
TCPService: &dynamic.TCPService{
|
||||||
|
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||||
|
Servers: []dynamic.TCPServer{
|
||||||
|
{
|
||||||
|
Address: "127.0.0.1:2345",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
jsonFile: "testdata/tcpservice-bar.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one tcp service by id, that does not exist",
|
||||||
|
path: "/api/tcp/services/nono@myprovider",
|
||||||
|
conf: dynamic.RuntimeConfiguration{
|
||||||
|
TCPServices: map[string]*dynamic.TCPServiceInfo{
|
||||||
|
"bar@myprovider": {
|
||||||
|
TCPService: &dynamic.TCPService{
|
||||||
|
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
||||||
|
Servers: []dynamic.TCPServer{
|
||||||
|
{
|
||||||
|
Address: "127.0.0.1:2345",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one tcp service by id, but no config",
|
||||||
|
path: "/api/tcp/services/foo@myprovider",
|
||||||
|
conf: dynamic.RuntimeConfiguration{},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
rtConf := &test.conf
|
||||||
|
handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf)
|
||||||
|
router := mux.NewRouter()
|
||||||
|
handler.Append(router)
|
||||||
|
|
||||||
|
server := httptest.NewServer(router)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Get(server.URL + test.path)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected.nextPage, resp.Header.Get(nextPageHeader))
|
||||||
|
|
||||||
|
require.Equal(t, test.expected.statusCode, resp.StatusCode)
|
||||||
|
|
||||||
|
if test.expected.jsonFile == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, resp.Header.Get("Content-Type"), "application/json")
|
||||||
|
|
||||||
|
contents, err := ioutil.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if *updateExpected {
|
||||||
|
var results interface{}
|
||||||
|
err := json.Unmarshal(contents, &results)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
newJSON, err := json.MarshalIndent(results, "", "\t")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(test.expected.jsonFile, newJSON, 0644)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(test.expected.jsonFile)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.JSONEq(t, string(data), string(contents))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,11 +3,9 @@ package api
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strconv"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/containous/mux"
|
"github.com/containous/mux"
|
||||||
|
@ -19,885 +17,7 @@ import (
|
||||||
|
|
||||||
var updateExpected = flag.Bool("update_expected", false, "Update expected files in testdata")
|
var updateExpected = flag.Bool("update_expected", false, "Update expected files in testdata")
|
||||||
|
|
||||||
func TestHandlerTCP_API(t *testing.T) {
|
func TestHandler_RawData(t *testing.T) {
|
||||||
type expected struct {
|
|
||||||
statusCode int
|
|
||||||
nextPage string
|
|
||||||
jsonFile string
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
desc string
|
|
||||||
path string
|
|
||||||
conf dynamic.RuntimeConfiguration
|
|
||||||
expected expected
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "all TCP routers, but no config",
|
|
||||||
path: "/api/tcp/routers",
|
|
||||||
conf: dynamic.RuntimeConfiguration{},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
nextPage: "1",
|
|
||||||
jsonFile: "testdata/tcprouters-empty.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "all TCP routers",
|
|
||||||
path: "/api/tcp/routers",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
TCPRouters: map[string]*dynamic.TCPRouterInfo{
|
|
||||||
"test@myprovider": {
|
|
||||||
TCPRouter: &dynamic.TCPRouter{
|
|
||||||
EntryPoints: []string{"web"},
|
|
||||||
Service: "foo-service@myprovider",
|
|
||||||
Rule: "Host(`foo.bar.other`)",
|
|
||||||
TLS: &dynamic.RouterTCPTLSConfig{
|
|
||||||
Passthrough: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"bar@myprovider": {
|
|
||||||
TCPRouter: &dynamic.TCPRouter{
|
|
||||||
EntryPoints: []string{"web"},
|
|
||||||
Service: "foo-service@myprovider",
|
|
||||||
Rule: "Host(`foo.bar`)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
nextPage: "1",
|
|
||||||
jsonFile: "testdata/tcprouters.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "all TCP routers, pagination, 1 res per page, want page 2",
|
|
||||||
path: "/api/tcp/routers?page=2&per_page=1",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
TCPRouters: map[string]*dynamic.TCPRouterInfo{
|
|
||||||
"bar@myprovider": {
|
|
||||||
TCPRouter: &dynamic.TCPRouter{
|
|
||||||
EntryPoints: []string{"web"},
|
|
||||||
Service: "foo-service@myprovider",
|
|
||||||
Rule: "Host(`foo.bar`)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"baz@myprovider": {
|
|
||||||
TCPRouter: &dynamic.TCPRouter{
|
|
||||||
EntryPoints: []string{"web"},
|
|
||||||
Service: "foo-service@myprovider",
|
|
||||||
Rule: "Host(`toto.bar`)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"test@myprovider": {
|
|
||||||
TCPRouter: &dynamic.TCPRouter{
|
|
||||||
EntryPoints: []string{"web"},
|
|
||||||
Service: "foo-service@myprovider",
|
|
||||||
Rule: "Host(`foo.bar.other`)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
nextPage: "3",
|
|
||||||
jsonFile: "testdata/tcprouters-page2.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "one TCP router by id",
|
|
||||||
path: "/api/tcp/routers/bar@myprovider",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
TCPRouters: map[string]*dynamic.TCPRouterInfo{
|
|
||||||
"bar@myprovider": {
|
|
||||||
TCPRouter: &dynamic.TCPRouter{
|
|
||||||
EntryPoints: []string{"web"},
|
|
||||||
Service: "foo-service@myprovider",
|
|
||||||
Rule: "Host(`foo.bar`)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
jsonFile: "testdata/tcprouter-bar.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "one TCP router by id, that does not exist",
|
|
||||||
path: "/api/tcp/routers/foo@myprovider",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
TCPRouters: map[string]*dynamic.TCPRouterInfo{
|
|
||||||
"bar@myprovider": {
|
|
||||||
TCPRouter: &dynamic.TCPRouter{
|
|
||||||
EntryPoints: []string{"web"},
|
|
||||||
Service: "foo-service@myprovider",
|
|
||||||
Rule: "Host(`foo.bar`)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusNotFound,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "one TCP router by id, but no config",
|
|
||||||
path: "/api/tcp/routers/bar@myprovider",
|
|
||||||
conf: dynamic.RuntimeConfiguration{},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusNotFound,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "all tcp services, but no config",
|
|
||||||
path: "/api/tcp/services",
|
|
||||||
conf: dynamic.RuntimeConfiguration{},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
nextPage: "1",
|
|
||||||
jsonFile: "testdata/tcpservices-empty.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "all tcp services",
|
|
||||||
path: "/api/tcp/services",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
TCPServices: map[string]*dynamic.TCPServiceInfo{
|
|
||||||
"bar@myprovider": {
|
|
||||||
TCPService: &dynamic.TCPService{
|
|
||||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
|
||||||
Servers: []dynamic.TCPServer{
|
|
||||||
{
|
|
||||||
Address: "127.0.0.1:2345",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
|
||||||
},
|
|
||||||
"baz@myprovider": {
|
|
||||||
TCPService: &dynamic.TCPService{
|
|
||||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
|
||||||
Servers: []dynamic.TCPServer{
|
|
||||||
{
|
|
||||||
Address: "127.0.0.2:2345",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"foo@myprovider"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
nextPage: "1",
|
|
||||||
jsonFile: "testdata/tcpservices.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "all tcp services, 1 res per page, want page 2",
|
|
||||||
path: "/api/tcp/services?page=2&per_page=1",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
TCPServices: map[string]*dynamic.TCPServiceInfo{
|
|
||||||
"bar@myprovider": {
|
|
||||||
TCPService: &dynamic.TCPService{
|
|
||||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
|
||||||
Servers: []dynamic.TCPServer{
|
|
||||||
{
|
|
||||||
Address: "127.0.0.1:2345",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
|
||||||
},
|
|
||||||
"baz@myprovider": {
|
|
||||||
TCPService: &dynamic.TCPService{
|
|
||||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
|
||||||
Servers: []dynamic.TCPServer{
|
|
||||||
{
|
|
||||||
Address: "127.0.0.2:2345",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"foo@myprovider"},
|
|
||||||
},
|
|
||||||
"test@myprovider": {
|
|
||||||
TCPService: &dynamic.TCPService{
|
|
||||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
|
||||||
Servers: []dynamic.TCPServer{
|
|
||||||
{
|
|
||||||
Address: "127.0.0.3:2345",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
nextPage: "3",
|
|
||||||
jsonFile: "testdata/tcpservices-page2.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "one tcp service by id",
|
|
||||||
path: "/api/tcp/services/bar@myprovider",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
TCPServices: map[string]*dynamic.TCPServiceInfo{
|
|
||||||
"bar@myprovider": {
|
|
||||||
TCPService: &dynamic.TCPService{
|
|
||||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
|
||||||
Servers: []dynamic.TCPServer{
|
|
||||||
{
|
|
||||||
Address: "127.0.0.1:2345",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
jsonFile: "testdata/tcpservice-bar.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "one tcp service by id, that does not exist",
|
|
||||||
path: "/api/tcp/services/nono@myprovider",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
TCPServices: map[string]*dynamic.TCPServiceInfo{
|
|
||||||
"bar@myprovider": {
|
|
||||||
TCPService: &dynamic.TCPService{
|
|
||||||
LoadBalancer: &dynamic.TCPLoadBalancerService{
|
|
||||||
Servers: []dynamic.TCPServer{
|
|
||||||
{
|
|
||||||
Address: "127.0.0.1:2345",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusNotFound,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "one tcp service by id, but no config",
|
|
||||||
path: "/api/tcp/services/foo@myprovider",
|
|
||||||
conf: dynamic.RuntimeConfiguration{},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusNotFound,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range testCases {
|
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
rtConf := &test.conf
|
|
||||||
handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf)
|
|
||||||
router := mux.NewRouter()
|
|
||||||
handler.Append(router)
|
|
||||||
|
|
||||||
server := httptest.NewServer(router)
|
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Get(server.URL + test.path)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, test.expected.nextPage, resp.Header.Get(nextPageHeader))
|
|
||||||
|
|
||||||
require.Equal(t, test.expected.statusCode, resp.StatusCode)
|
|
||||||
|
|
||||||
if test.expected.jsonFile == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, resp.Header.Get("Content-Type"), "application/json")
|
|
||||||
|
|
||||||
contents, err := ioutil.ReadAll(resp.Body)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = resp.Body.Close()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
if *updateExpected {
|
|
||||||
var results interface{}
|
|
||||||
err := json.Unmarshal(contents, &results)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
newJSON, err := json.MarshalIndent(results, "", "\t")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = ioutil.WriteFile(test.expected.jsonFile, newJSON, 0644)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(test.expected.jsonFile)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.JSONEq(t, string(data), string(contents))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandlerHTTP_API(t *testing.T) {
|
|
||||||
type expected struct {
|
|
||||||
statusCode int
|
|
||||||
nextPage string
|
|
||||||
jsonFile string
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
desc string
|
|
||||||
path string
|
|
||||||
conf dynamic.RuntimeConfiguration
|
|
||||||
expected expected
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "all routers, but no config",
|
|
||||||
path: "/api/http/routers",
|
|
||||||
conf: dynamic.RuntimeConfiguration{},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
nextPage: "1",
|
|
||||||
jsonFile: "testdata/routers-empty.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "all routers",
|
|
||||||
path: "/api/http/routers",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
Routers: map[string]*dynamic.RouterInfo{
|
|
||||||
"test@myprovider": {
|
|
||||||
Router: &dynamic.Router{
|
|
||||||
EntryPoints: []string{"web"},
|
|
||||||
Service: "foo-service@myprovider",
|
|
||||||
Rule: "Host(`foo.bar.other`)",
|
|
||||||
Middlewares: []string{"addPrefixTest", "auth"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"bar@myprovider": {
|
|
||||||
Router: &dynamic.Router{
|
|
||||||
EntryPoints: []string{"web"},
|
|
||||||
Service: "foo-service@myprovider",
|
|
||||||
Rule: "Host(`foo.bar`)",
|
|
||||||
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
nextPage: "1",
|
|
||||||
jsonFile: "testdata/routers.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "all routers, pagination, 1 res per page, want page 2",
|
|
||||||
path: "/api/http/routers?page=2&per_page=1",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
Routers: map[string]*dynamic.RouterInfo{
|
|
||||||
"bar@myprovider": {
|
|
||||||
Router: &dynamic.Router{
|
|
||||||
EntryPoints: []string{"web"},
|
|
||||||
Service: "foo-service@myprovider",
|
|
||||||
Rule: "Host(`foo.bar`)",
|
|
||||||
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"baz@myprovider": {
|
|
||||||
Router: &dynamic.Router{
|
|
||||||
EntryPoints: []string{"web"},
|
|
||||||
Service: "foo-service@myprovider",
|
|
||||||
Rule: "Host(`toto.bar`)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"test@myprovider": {
|
|
||||||
Router: &dynamic.Router{
|
|
||||||
EntryPoints: []string{"web"},
|
|
||||||
Service: "foo-service@myprovider",
|
|
||||||
Rule: "Host(`foo.bar.other`)",
|
|
||||||
Middlewares: []string{"addPrefixTest", "auth"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
nextPage: "3",
|
|
||||||
jsonFile: "testdata/routers-page2.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "all routers, pagination, 19 results overall, 7 res per page, want page 3",
|
|
||||||
path: "/api/http/routers?page=3&per_page=7",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
Routers: generateHTTPRouters(19),
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
nextPage: "1",
|
|
||||||
jsonFile: "testdata/routers-many-lastpage.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "all routers, pagination, 5 results overall, 10 res per page, want page 2",
|
|
||||||
path: "/api/http/routers?page=2&per_page=10",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
Routers: generateHTTPRouters(5),
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusBadRequest,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "all routers, pagination, 10 results overall, 10 res per page, want page 2",
|
|
||||||
path: "/api/http/routers?page=2&per_page=10",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
Routers: generateHTTPRouters(10),
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusBadRequest,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "one router by id",
|
|
||||||
path: "/api/http/routers/bar@myprovider",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
Routers: map[string]*dynamic.RouterInfo{
|
|
||||||
"bar@myprovider": {
|
|
||||||
Router: &dynamic.Router{
|
|
||||||
EntryPoints: []string{"web"},
|
|
||||||
Service: "foo-service@myprovider",
|
|
||||||
Rule: "Host(`foo.bar`)",
|
|
||||||
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
jsonFile: "testdata/router-bar.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "one router by id, that does not exist",
|
|
||||||
path: "/api/http/routers/foo@myprovider",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
Routers: map[string]*dynamic.RouterInfo{
|
|
||||||
"bar@myprovider": {
|
|
||||||
Router: &dynamic.Router{
|
|
||||||
EntryPoints: []string{"web"},
|
|
||||||
Service: "foo-service@myprovider",
|
|
||||||
Rule: "Host(`foo.bar`)",
|
|
||||||
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusNotFound,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "one router by id, but no config",
|
|
||||||
path: "/api/http/routers/foo@myprovider",
|
|
||||||
conf: dynamic.RuntimeConfiguration{},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusNotFound,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "all services, but no config",
|
|
||||||
path: "/api/http/services",
|
|
||||||
conf: dynamic.RuntimeConfiguration{},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
nextPage: "1",
|
|
||||||
jsonFile: "testdata/services-empty.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "all services",
|
|
||||||
path: "/api/http/services",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
Services: map[string]*dynamic.ServiceInfo{
|
|
||||||
"bar@myprovider": func() *dynamic.ServiceInfo {
|
|
||||||
si := &dynamic.ServiceInfo{
|
|
||||||
Service: &dynamic.Service{
|
|
||||||
LoadBalancer: &dynamic.LoadBalancerService{
|
|
||||||
Servers: []dynamic.Server{
|
|
||||||
{
|
|
||||||
URL: "http://127.0.0.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
|
||||||
}
|
|
||||||
si.UpdateStatus("http://127.0.0.1", "UP")
|
|
||||||
return si
|
|
||||||
}(),
|
|
||||||
"baz@myprovider": func() *dynamic.ServiceInfo {
|
|
||||||
si := &dynamic.ServiceInfo{
|
|
||||||
Service: &dynamic.Service{
|
|
||||||
LoadBalancer: &dynamic.LoadBalancerService{
|
|
||||||
Servers: []dynamic.Server{
|
|
||||||
{
|
|
||||||
URL: "http://127.0.0.2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"foo@myprovider"},
|
|
||||||
}
|
|
||||||
si.UpdateStatus("http://127.0.0.2", "UP")
|
|
||||||
return si
|
|
||||||
}(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
nextPage: "1",
|
|
||||||
jsonFile: "testdata/services.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "all services, 1 res per page, want page 2",
|
|
||||||
path: "/api/http/services?page=2&per_page=1",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
Services: map[string]*dynamic.ServiceInfo{
|
|
||||||
"bar@myprovider": func() *dynamic.ServiceInfo {
|
|
||||||
si := &dynamic.ServiceInfo{
|
|
||||||
Service: &dynamic.Service{
|
|
||||||
LoadBalancer: &dynamic.LoadBalancerService{
|
|
||||||
Servers: []dynamic.Server{
|
|
||||||
{
|
|
||||||
URL: "http://127.0.0.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
|
||||||
}
|
|
||||||
si.UpdateStatus("http://127.0.0.1", "UP")
|
|
||||||
return si
|
|
||||||
}(),
|
|
||||||
"baz@myprovider": func() *dynamic.ServiceInfo {
|
|
||||||
si := &dynamic.ServiceInfo{
|
|
||||||
Service: &dynamic.Service{
|
|
||||||
LoadBalancer: &dynamic.LoadBalancerService{
|
|
||||||
Servers: []dynamic.Server{
|
|
||||||
{
|
|
||||||
URL: "http://127.0.0.2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"foo@myprovider"},
|
|
||||||
}
|
|
||||||
si.UpdateStatus("http://127.0.0.2", "UP")
|
|
||||||
return si
|
|
||||||
}(),
|
|
||||||
"test@myprovider": func() *dynamic.ServiceInfo {
|
|
||||||
si := &dynamic.ServiceInfo{
|
|
||||||
Service: &dynamic.Service{
|
|
||||||
LoadBalancer: &dynamic.LoadBalancerService{
|
|
||||||
Servers: []dynamic.Server{
|
|
||||||
{
|
|
||||||
URL: "http://127.0.0.3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
|
||||||
}
|
|
||||||
si.UpdateStatus("http://127.0.0.4", "UP")
|
|
||||||
return si
|
|
||||||
}(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
nextPage: "3",
|
|
||||||
jsonFile: "testdata/services-page2.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "one service by id",
|
|
||||||
path: "/api/http/services/bar@myprovider",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
Services: map[string]*dynamic.ServiceInfo{
|
|
||||||
"bar@myprovider": func() *dynamic.ServiceInfo {
|
|
||||||
si := &dynamic.ServiceInfo{
|
|
||||||
Service: &dynamic.Service{
|
|
||||||
LoadBalancer: &dynamic.LoadBalancerService{
|
|
||||||
Servers: []dynamic.Server{
|
|
||||||
{
|
|
||||||
URL: "http://127.0.0.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
|
||||||
}
|
|
||||||
si.UpdateStatus("http://127.0.0.1", "UP")
|
|
||||||
return si
|
|
||||||
}(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
jsonFile: "testdata/service-bar.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "one service by id, that does not exist",
|
|
||||||
path: "/api/http/services/nono@myprovider",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
Services: map[string]*dynamic.ServiceInfo{
|
|
||||||
"bar@myprovider": func() *dynamic.ServiceInfo {
|
|
||||||
si := &dynamic.ServiceInfo{
|
|
||||||
Service: &dynamic.Service{
|
|
||||||
LoadBalancer: &dynamic.LoadBalancerService{
|
|
||||||
Servers: []dynamic.Server{
|
|
||||||
{
|
|
||||||
URL: "http://127.0.0.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"foo@myprovider", "test@myprovider"},
|
|
||||||
}
|
|
||||||
si.UpdateStatus("http://127.0.0.1", "UP")
|
|
||||||
return si
|
|
||||||
}(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusNotFound,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "one service by id, but no config",
|
|
||||||
path: "/api/http/services/foo@myprovider",
|
|
||||||
conf: dynamic.RuntimeConfiguration{},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusNotFound,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "all middlewares, but no config",
|
|
||||||
path: "/api/http/middlewares",
|
|
||||||
conf: dynamic.RuntimeConfiguration{},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
nextPage: "1",
|
|
||||||
jsonFile: "testdata/middlewares-empty.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "all middlewares",
|
|
||||||
path: "/api/http/middlewares",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
Middlewares: map[string]*dynamic.MiddlewareInfo{
|
|
||||||
"auth@myprovider": {
|
|
||||||
Middleware: &dynamic.Middleware{
|
|
||||||
BasicAuth: &dynamic.BasicAuth{
|
|
||||||
Users: []string{"admin:admin"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
|
||||||
},
|
|
||||||
"addPrefixTest@myprovider": {
|
|
||||||
Middleware: &dynamic.Middleware{
|
|
||||||
AddPrefix: &dynamic.AddPrefix{
|
|
||||||
Prefix: "/titi",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"test@myprovider"},
|
|
||||||
},
|
|
||||||
"addPrefixTest@anotherprovider": {
|
|
||||||
Middleware: &dynamic.Middleware{
|
|
||||||
AddPrefix: &dynamic.AddPrefix{
|
|
||||||
Prefix: "/toto",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"bar@myprovider"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
nextPage: "1",
|
|
||||||
jsonFile: "testdata/middlewares.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "all middlewares, 1 res per page, want page 2",
|
|
||||||
path: "/api/http/middlewares?page=2&per_page=1",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
Middlewares: map[string]*dynamic.MiddlewareInfo{
|
|
||||||
"auth@myprovider": {
|
|
||||||
Middleware: &dynamic.Middleware{
|
|
||||||
BasicAuth: &dynamic.BasicAuth{
|
|
||||||
Users: []string{"admin:admin"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
|
||||||
},
|
|
||||||
"addPrefixTest@myprovider": {
|
|
||||||
Middleware: &dynamic.Middleware{
|
|
||||||
AddPrefix: &dynamic.AddPrefix{
|
|
||||||
Prefix: "/titi",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"test@myprovider"},
|
|
||||||
},
|
|
||||||
"addPrefixTest@anotherprovider": {
|
|
||||||
Middleware: &dynamic.Middleware{
|
|
||||||
AddPrefix: &dynamic.AddPrefix{
|
|
||||||
Prefix: "/toto",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"bar@myprovider"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
nextPage: "3",
|
|
||||||
jsonFile: "testdata/middlewares-page2.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "one middleware by id",
|
|
||||||
path: "/api/http/middlewares/auth@myprovider",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
Middlewares: map[string]*dynamic.MiddlewareInfo{
|
|
||||||
"auth@myprovider": {
|
|
||||||
Middleware: &dynamic.Middleware{
|
|
||||||
BasicAuth: &dynamic.BasicAuth{
|
|
||||||
Users: []string{"admin:admin"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
|
||||||
},
|
|
||||||
"addPrefixTest@myprovider": {
|
|
||||||
Middleware: &dynamic.Middleware{
|
|
||||||
AddPrefix: &dynamic.AddPrefix{
|
|
||||||
Prefix: "/titi",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"test@myprovider"},
|
|
||||||
},
|
|
||||||
"addPrefixTest@anotherprovider": {
|
|
||||||
Middleware: &dynamic.Middleware{
|
|
||||||
AddPrefix: &dynamic.AddPrefix{
|
|
||||||
Prefix: "/toto",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"bar@myprovider"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
jsonFile: "testdata/middleware-auth.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "one middleware by id, that does not exist",
|
|
||||||
path: "/api/http/middlewares/foo@myprovider",
|
|
||||||
conf: dynamic.RuntimeConfiguration{
|
|
||||||
Middlewares: map[string]*dynamic.MiddlewareInfo{
|
|
||||||
"auth@myprovider": {
|
|
||||||
Middleware: &dynamic.Middleware{
|
|
||||||
BasicAuth: &dynamic.BasicAuth{
|
|
||||||
Users: []string{"admin:admin"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusNotFound,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "one middleware by id, but no config",
|
|
||||||
path: "/api/http/middlewares/foo@myprovider",
|
|
||||||
conf: dynamic.RuntimeConfiguration{},
|
|
||||||
expected: expected{
|
|
||||||
statusCode: http.StatusNotFound,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range testCases {
|
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
rtConf := &test.conf
|
|
||||||
handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf)
|
|
||||||
router := mux.NewRouter()
|
|
||||||
handler.Append(router)
|
|
||||||
|
|
||||||
server := httptest.NewServer(router)
|
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Get(server.URL + test.path)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, test.expected.statusCode, resp.StatusCode)
|
|
||||||
|
|
||||||
assert.Equal(t, test.expected.nextPage, resp.Header.Get(nextPageHeader))
|
|
||||||
|
|
||||||
if test.expected.jsonFile == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, resp.Header.Get("Content-Type"), "application/json")
|
|
||||||
contents, err := ioutil.ReadAll(resp.Body)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = resp.Body.Close()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
if *updateExpected {
|
|
||||||
var results interface{}
|
|
||||||
err := json.Unmarshal(contents, &results)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
newJSON, err := json.MarshalIndent(results, "", "\t")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = ioutil.WriteFile(test.expected.jsonFile, newJSON, 0644)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(test.expected.jsonFile)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.JSONEq(t, string(data), string(contents))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandler_Configuration(t *testing.T) {
|
|
||||||
type expected struct {
|
type expected struct {
|
||||||
statusCode int
|
statusCode int
|
||||||
json string
|
json string
|
||||||
|
@ -1053,17 +173,3 @@ func TestHandler_Configuration(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateHTTPRouters(nbRouters int) map[string]*dynamic.RouterInfo {
|
|
||||||
routers := make(map[string]*dynamic.RouterInfo, nbRouters)
|
|
||||||
for i := 0; i < nbRouters; i++ {
|
|
||||||
routers[fmt.Sprintf("bar%2d@myprovider", i)] = &dynamic.RouterInfo{
|
|
||||||
Router: &dynamic.Router{
|
|
||||||
EntryPoints: []string{"web"},
|
|
||||||
Service: "foo-service@myprovider",
|
|
||||||
Rule: "Host(`foo.bar" + strconv.Itoa(i) + "`)",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return routers
|
|
||||||
}
|
|
||||||
|
|
4
pkg/api/testdata/entrypoint-bar.json
vendored
Normal file
4
pkg/api/testdata/entrypoint-bar.json
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"address": ":81",
|
||||||
|
"name": "bar"
|
||||||
|
}
|
1
pkg/api/testdata/entrypoints-empty.json
vendored
Normal file
1
pkg/api/testdata/entrypoints-empty.json
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[]
|
22
pkg/api/testdata/entrypoints-many-lastpage.json
vendored
Normal file
22
pkg/api/testdata/entrypoints-many-lastpage.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"address": ":14",
|
||||||
|
"name": "ep14"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": ":15",
|
||||||
|
"name": "ep15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": ":16",
|
||||||
|
"name": "ep16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": ":17",
|
||||||
|
"name": "ep17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": ":18",
|
||||||
|
"name": "ep18"
|
||||||
|
}
|
||||||
|
]
|
6
pkg/api/testdata/entrypoints-page2.json
vendored
Normal file
6
pkg/api/testdata/entrypoints-page2.json
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"address": ":82",
|
||||||
|
"name": "web2"
|
||||||
|
}
|
||||||
|
]
|
60
pkg/api/testdata/entrypoints.json
vendored
Normal file
60
pkg/api/testdata/entrypoints.json
vendored
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"address": ":80",
|
||||||
|
"forwardedHeaders": {
|
||||||
|
"insecure": true,
|
||||||
|
"trustedIPs": [
|
||||||
|
"192.168.1.3",
|
||||||
|
"192.168.1.4"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "web",
|
||||||
|
"proxyProtocol": {
|
||||||
|
"insecure": true,
|
||||||
|
"trustedIPs": [
|
||||||
|
"192.168.1.1",
|
||||||
|
"192.168.1.2"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"transport": {
|
||||||
|
"lifeCycle": {
|
||||||
|
"graceTimeOut": 2,
|
||||||
|
"requestAcceptGraceTimeout": 1
|
||||||
|
},
|
||||||
|
"respondingTimeouts": {
|
||||||
|
"idleTimeout": 5,
|
||||||
|
"readTimeout": 3,
|
||||||
|
"writeTimeout": 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": ":443",
|
||||||
|
"forwardedHeaders": {
|
||||||
|
"insecure": true,
|
||||||
|
"trustedIPs": [
|
||||||
|
"192.168.1.30",
|
||||||
|
"192.168.1.40"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "web-secure",
|
||||||
|
"proxyProtocol": {
|
||||||
|
"insecure": true,
|
||||||
|
"trustedIPs": [
|
||||||
|
"192.168.1.10",
|
||||||
|
"192.168.1.20"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"transport": {
|
||||||
|
"lifeCycle": {
|
||||||
|
"graceTimeOut": 20,
|
||||||
|
"requestAcceptGraceTimeout": 10
|
||||||
|
},
|
||||||
|
"respondingTimeouts": {
|
||||||
|
"idleTimeout": 50,
|
||||||
|
"readTimeout": 30,
|
||||||
|
"writeTimeout": 40
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
36
pkg/api/testdata/overview-dynamic.json
vendored
Normal file
36
pkg/api/testdata/overview-dynamic.json
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"features": {
|
||||||
|
"accessLog": false,
|
||||||
|
"metrics": "",
|
||||||
|
"tracing": ""
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"middlewares": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 3,
|
||||||
|
"warnings": 0
|
||||||
|
},
|
||||||
|
"routers": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 2,
|
||||||
|
"warnings": 0
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 1,
|
||||||
|
"warnings": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tcp": {
|
||||||
|
"routers": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 2,
|
||||||
|
"warnings": 0
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 1,
|
||||||
|
"warnings": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
pkg/api/testdata/overview-empty.json
vendored
Normal file
36
pkg/api/testdata/overview-empty.json
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"features": {
|
||||||
|
"accessLog": false,
|
||||||
|
"metrics": "",
|
||||||
|
"tracing": ""
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"middlewares": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 0,
|
||||||
|
"warnings": 0
|
||||||
|
},
|
||||||
|
"routers": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 0,
|
||||||
|
"warnings": 0
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 0,
|
||||||
|
"warnings": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tcp": {
|
||||||
|
"routers": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 0,
|
||||||
|
"warnings": 0
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 0,
|
||||||
|
"warnings": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
pkg/api/testdata/overview-features.json
vendored
Normal file
36
pkg/api/testdata/overview-features.json
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"features": {
|
||||||
|
"accessLog": false,
|
||||||
|
"metrics": "Prometheus",
|
||||||
|
"tracing": "Jaeger"
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"middlewares": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 0,
|
||||||
|
"warnings": 0
|
||||||
|
},
|
||||||
|
"routers": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 0,
|
||||||
|
"warnings": 0
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 0,
|
||||||
|
"warnings": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tcp": {
|
||||||
|
"routers": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 0,
|
||||||
|
"warnings": 0
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 0,
|
||||||
|
"warnings": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
pkg/api/testdata/overview-providers.json
vendored
Normal file
45
pkg/api/testdata/overview-providers.json
vendored
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"features": {
|
||||||
|
"accessLog": false,
|
||||||
|
"metrics": "",
|
||||||
|
"tracing": ""
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"middlewares": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 0,
|
||||||
|
"warnings": 0
|
||||||
|
},
|
||||||
|
"routers": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 0,
|
||||||
|
"warnings": 0
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 0,
|
||||||
|
"warnings": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"providers": [
|
||||||
|
"Docker",
|
||||||
|
"File",
|
||||||
|
"Marathon",
|
||||||
|
"KubernetesIngress",
|
||||||
|
"KubernetesCRD",
|
||||||
|
"Rest",
|
||||||
|
"Rancher"
|
||||||
|
],
|
||||||
|
"tcp": {
|
||||||
|
"routers": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 0,
|
||||||
|
"warnings": 0
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"errors": 0,
|
||||||
|
"total": 0,
|
||||||
|
"warnings": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue