mirror of
https://github.com/containous/traefik.git
synced 2025-03-11 16:58:23 +03:00
Add support dump API endpoint
This commit is contained in:
parent
d953ee69b4
commit
e85d02c530
@ -145,34 +145,35 @@ All the following endpoints must be accessed with a `GET` HTTP request.
|
|||||||
curl https://traefik.example.com:8080/api/http/routers?page=2&per_page=20
|
curl https://traefik.example.com:8080/api/http/routers?page=2&per_page=20
|
||||||
```
|
```
|
||||||
|
|
||||||
| Path | Description |
|
| Path | Description |
|
||||||
|--------------------------------|---------------------------------------------------------------------------------------------|
|
|--------------------------------|-----------------------------------------------------------------------------------------------------|
|
||||||
| `/api/http/routers` | Lists all the HTTP routers information. |
|
| `/api/http/routers` | Lists all the HTTP routers information. |
|
||||||
| `/api/http/routers/{name}` | Returns the information of the HTTP router specified by `name`. |
|
| `/api/http/routers/{name}` | Returns the information of the HTTP router specified by `name`. |
|
||||||
| `/api/http/services` | Lists all the HTTP services information. |
|
| `/api/http/services` | Lists all the HTTP services information. |
|
||||||
| `/api/http/services/{name}` | Returns the information of the HTTP service specified by `name`. |
|
| `/api/http/services/{name}` | Returns the information of the HTTP service specified by `name`. |
|
||||||
| `/api/http/middlewares` | Lists all the HTTP middlewares information. |
|
| `/api/http/middlewares` | Lists all the HTTP middlewares information. |
|
||||||
| `/api/http/middlewares/{name}` | Returns the information of the HTTP middleware specified by `name`. |
|
| `/api/http/middlewares/{name}` | Returns the information of the HTTP middleware specified by `name`. |
|
||||||
| `/api/tcp/routers` | Lists all the TCP routers information. |
|
| `/api/tcp/routers` | Lists all the TCP routers information. |
|
||||||
| `/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/tcp/middlewares` | Lists all the TCP middlewares information. |
|
| `/api/tcp/middlewares` | Lists all the TCP middlewares information. |
|
||||||
| `/api/tcp/middlewares/{name}` | Returns the information of the TCP middleware specified by `name`. |
|
| `/api/tcp/middlewares/{name}` | Returns the information of the TCP middleware specified by `name`. |
|
||||||
| `/api/udp/routers` | Lists all the UDP routers information. |
|
| `/api/udp/routers` | Lists all the UDP routers information. |
|
||||||
| `/api/udp/routers/{name}` | Returns the information of the UDP router specified by `name`. |
|
| `/api/udp/routers/{name}` | Returns the information of the UDP router specified by `name`. |
|
||||||
| `/api/udp/services` | Lists all the UDP services information. |
|
| `/api/udp/services` | Lists all the UDP services information. |
|
||||||
| `/api/udp/services/{name}` | Returns the information of the UDP service specified by `name`. |
|
| `/api/udp/services/{name}` | Returns the information of the UDP service specified by `name`. |
|
||||||
| `/api/entrypoints` | Lists all the entry points information. |
|
| `/api/entrypoints` | Lists all the entry points information. |
|
||||||
| `/api/entrypoints/{name}` | Returns the information of the entry point specified by `name`. |
|
| `/api/entrypoints/{name}` | Returns the information of the entry point specified by `name`. |
|
||||||
| `/api/overview` | Returns statistic information about http and tcp as well as enabled features and providers. |
|
| `/api/overview` | Returns statistic information about http and tcp as well as enabled features and providers. |
|
||||||
| `/api/rawdata` | Returns information about dynamic configurations, errors, status and dependency relations. |
|
| `/api/support-dump` | Returns an archive that contains the anonymized static configuration and the runtime configuration. |
|
||||||
| `/api/version` | Returns information about Traefik version. |
|
| `/api/rawdata` | Returns information about dynamic configurations, errors, status and dependency relations. |
|
||||||
| `/debug/vars` | See the [expvar](https://golang.org/pkg/expvar/) Go documentation. |
|
| `/api/version` | Returns information about Traefik version. |
|
||||||
| `/debug/pprof/` | See the [pprof Index](https://golang.org/pkg/net/http/pprof/#Index) Go documentation. |
|
| `/debug/vars` | See the [expvar](https://golang.org/pkg/expvar/) Go documentation. |
|
||||||
| `/debug/pprof/cmdline` | See the [pprof Cmdline](https://golang.org/pkg/net/http/pprof/#Cmdline) Go documentation. |
|
| `/debug/pprof/` | See the [pprof Index](https://golang.org/pkg/net/http/pprof/#Index) Go documentation. |
|
||||||
| `/debug/pprof/profile` | See the [pprof Profile](https://golang.org/pkg/net/http/pprof/#Profile) Go documentation. |
|
| `/debug/pprof/cmdline` | See the [pprof Cmdline](https://golang.org/pkg/net/http/pprof/#Cmdline) Go documentation. |
|
||||||
| `/debug/pprof/symbol` | See the [pprof Symbol](https://golang.org/pkg/net/http/pprof/#Symbol) Go documentation. |
|
| `/debug/pprof/profile` | See the [pprof Profile](https://golang.org/pkg/net/http/pprof/#Profile) Go documentation. |
|
||||||
| `/debug/pprof/trace` | See the [pprof Trace](https://golang.org/pkg/net/http/pprof/#Trace) Go documentation. |
|
| `/debug/pprof/symbol` | See the [pprof Symbol](https://golang.org/pkg/net/http/pprof/#Symbol) Go documentation. |
|
||||||
|
| `/debug/pprof/trace` | See the [pprof Trace](https://golang.org/pkg/net/http/pprof/#Trace) Go documentation. |
|
||||||
|
|
||||||
{!traefik-for-business-applications.md!}
|
{!traefik-for-business-applications.md!}
|
||||||
|
@ -89,6 +89,8 @@ func (h Handler) createRouter() *mux.Router {
|
|||||||
// Experimental endpoint
|
// Experimental endpoint
|
||||||
apiRouter.Methods(http.MethodGet).Path("/api/overview").HandlerFunc(h.getOverview)
|
apiRouter.Methods(http.MethodGet).Path("/api/overview").HandlerFunc(h.getOverview)
|
||||||
|
|
||||||
|
apiRouter.Methods(http.MethodGet).Path("/api/support-dump").HandlerFunc(h.getSupportDump)
|
||||||
|
|
||||||
apiRouter.Methods(http.MethodGet).Path("/api/entrypoints").HandlerFunc(h.getEntryPoints)
|
apiRouter.Methods(http.MethodGet).Path("/api/entrypoints").HandlerFunc(h.getEntryPoints)
|
||||||
apiRouter.Methods(http.MethodGet).Path("/api/entrypoints/{entryPointID}").HandlerFunc(h.getEntryPoint)
|
apiRouter.Methods(http.MethodGet).Path("/api/entrypoints/{entryPointID}").HandlerFunc(h.getEntryPoint)
|
||||||
|
|
||||||
|
96
pkg/api/handler_support_dump.go
Normal file
96
pkg/api/handler_support_dump.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"compress/gzip"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/redactor"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h Handler) getSupportDump(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
logger := log.Ctx(req.Context())
|
||||||
|
|
||||||
|
staticConfig, err := redactor.Anonymize(h.staticConfig)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Err(err).Msg("Unable to anonymize and marshal static configuration")
|
||||||
|
writeError(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
runtimeConfig, err := json.Marshal(h.runtimeConfiguration)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Err(err).Msg("Unable to marshal runtime configuration")
|
||||||
|
writeError(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tVersion, err := json.Marshal(struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Codename string `json:"codename"`
|
||||||
|
StartDate time.Time `json:"startDate"`
|
||||||
|
}{
|
||||||
|
Version: version.Version,
|
||||||
|
Codename: version.Codename,
|
||||||
|
StartDate: version.StartDate,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Err(err).Msg("Unable to marshal version")
|
||||||
|
writeError(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.Header().Set("Content-Type", "application/gzip")
|
||||||
|
rw.Header().Set("Content-Disposition", "attachment; filename=support-dump.tar.gz")
|
||||||
|
|
||||||
|
// Create gzip writer.
|
||||||
|
gw := gzip.NewWriter(rw)
|
||||||
|
defer gw.Close()
|
||||||
|
|
||||||
|
// Create tar writer.
|
||||||
|
tw := tar.NewWriter(gw)
|
||||||
|
defer tw.Close()
|
||||||
|
|
||||||
|
// Add configuration files to the archive.
|
||||||
|
if err := addFile(tw, "version.json", tVersion); err != nil {
|
||||||
|
logger.Error().Err(err).Msg("Unable to archive version file")
|
||||||
|
writeError(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := addFile(tw, "static-config.json", []byte(staticConfig)); err != nil {
|
||||||
|
logger.Error().Err(err).Msg("Unable to archive static configuration")
|
||||||
|
writeError(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := addFile(tw, "runtime-config.json", runtimeConfig); err != nil {
|
||||||
|
logger.Error().Err(err).Msg("Unable to archive runtime configuration")
|
||||||
|
writeError(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFile(tw *tar.Writer, name string, content []byte) error {
|
||||||
|
header := &tar.Header{
|
||||||
|
Name: name,
|
||||||
|
Mode: 0o600,
|
||||||
|
Size: int64(len(content)),
|
||||||
|
ModTime: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tw.WriteHeader(header); err != nil {
|
||||||
|
return fmt.Errorf("writing tar header: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := tw.Write(content); err != nil {
|
||||||
|
return fmt.Errorf("writing tar content: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
144
pkg/api/handler_support_dump_test.go
Normal file
144
pkg/api/handler_support_dump_test.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"compress/gzip"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/config/runtime"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/config/static"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandler_SupportDump(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
path string
|
||||||
|
confStatic static.Configuration
|
||||||
|
confDyn runtime.Configuration
|
||||||
|
validate func(t *testing.T, files map[string][]byte)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "empty configurations",
|
||||||
|
path: "/api/support-dump",
|
||||||
|
confStatic: static.Configuration{API: &static.API{}, Global: &static.Global{}},
|
||||||
|
confDyn: runtime.Configuration{},
|
||||||
|
validate: func(t *testing.T, files map[string][]byte) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
require.Contains(t, files, "static-config.json")
|
||||||
|
require.Contains(t, files, "runtime-config.json")
|
||||||
|
require.Contains(t, files, "version.json")
|
||||||
|
|
||||||
|
// Verify version.json contains version information
|
||||||
|
assert.Contains(t, string(files["version.json"]), `"version":"dev"`)
|
||||||
|
|
||||||
|
assert.JSONEq(t, `{"global":{},"api":{}}`, string(files["static-config.json"]))
|
||||||
|
assert.Equal(t, `{}`, string(files["runtime-config.json"]))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "with configuration data",
|
||||||
|
path: "/api/support-dump",
|
||||||
|
confStatic: static.Configuration{
|
||||||
|
API: &static.API{},
|
||||||
|
Global: &static.Global{},
|
||||||
|
EntryPoints: map[string]*static.EntryPoint{
|
||||||
|
"web": {Address: ":80"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
confDyn: runtime.Configuration{
|
||||||
|
Services: map[string]*runtime.ServiceInfo{
|
||||||
|
"test-service": {
|
||||||
|
Service: &dynamic.Service{
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
Servers: []dynamic.Server{{URL: "http://127.0.0.1:8080"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: runtime.StatusEnabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validate: func(t *testing.T, files map[string][]byte) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
require.Contains(t, files, "static-config.json")
|
||||||
|
require.Contains(t, files, "runtime-config.json")
|
||||||
|
require.Contains(t, files, "version.json")
|
||||||
|
|
||||||
|
// Verify version.json contains version information
|
||||||
|
assert.Contains(t, string(files["version.json"]), `"version":"dev"`)
|
||||||
|
|
||||||
|
// Verify static config contains entry points
|
||||||
|
assert.Contains(t, string(files["static-config.json"]), `"entryPoints":{"web":{"address":"xxxx","http":{}}}`)
|
||||||
|
|
||||||
|
// Verify runtime config contains services
|
||||||
|
assert.Contains(t, string(files["runtime-config.json"]), `"services":`)
|
||||||
|
assert.Contains(t, string(files["runtime-config.json"]), `"test-service"`)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
handler := New(test.confStatic, &test.confDyn)
|
||||||
|
server := httptest.NewServer(handler.createRouter())
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Get(server.URL + test.path)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
assert.Equal(t, "application/gzip", resp.Header.Get("Content-Type"))
|
||||||
|
assert.Equal(t, `attachment; filename=support-dump.tar.gz`, resp.Header.Get("Content-Disposition"))
|
||||||
|
|
||||||
|
// Extract and validate the tar.gz contents.
|
||||||
|
files, err := extractTarGz(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
test.validate(t, files)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractTarGz reads a tar.gz archive and returns a map of filename to contents
|
||||||
|
func extractTarGz(r io.Reader) (map[string][]byte, error) {
|
||||||
|
files := make(map[string][]byte)
|
||||||
|
|
||||||
|
gzr, err := gzip.NewReader(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer gzr.Close()
|
||||||
|
|
||||||
|
tr := tar.NewReader(gzr)
|
||||||
|
for {
|
||||||
|
header, err := tr.Next()
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if header.Typeflag != tar.TypeReg {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
contents, err := io.ReadAll(tr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
files[header.Name] = contents
|
||||||
|
}
|
||||||
|
|
||||||
|
return files, nil
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user