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 }