diff --git a/.changelog/11466.txt b/.changelog/11466.txt new file mode 100644 index 00000000000..7c1d1c63398 --- /dev/null +++ b/.changelog/11466.txt @@ -0,0 +1,3 @@ +```release-note:improvement +cli: Improve debug capture for Consul/Vault +``` diff --git a/client/testutil/driver_compatible.go b/client/testutil/driver_compatible.go index 24e1abea644..ff0ecc747f3 100644 --- a/client/testutil/driver_compatible.go +++ b/client/testutil/driver_compatible.go @@ -24,6 +24,14 @@ func RequireConsul(t *testing.T) { } } +// RequireVault skips tests unless a Vault binary is available on $PATH. +func RequireVault(t *testing.T) { + _, err := exec.Command("vault", "version").CombinedOutput() + if err != nil { + t.Skipf("Test requires Vault: %v", err) + } +} + func ExecCompatible(t *testing.T) { if runtime.GOOS != "linux" || syscall.Geteuid() != 0 { t.Skip("Test only available running as root on linux") diff --git a/command/operator_debug.go b/command/operator_debug.go index 6633f9c4f1c..245be9086f1 100644 --- a/command/operator_debug.go +++ b/command/operator_debug.go @@ -6,6 +6,7 @@ import ( "context" "crypto/tls" "encoding/json" + "flag" "fmt" "html/template" "io" @@ -24,6 +25,7 @@ import ( "github.com/hashicorp/nomad/api/contexts" "github.com/hashicorp/nomad/helper" "github.com/hashicorp/nomad/nomad/structs" + "github.com/hashicorp/nomad/version" "github.com/posener/complete" ) @@ -391,6 +393,9 @@ func (c *OperatorDebugCommand) Run(args []string) int { c.collectDir = tmp + // Write CLI flags to JSON file + c.writeFlags(flags) + // Create an instance of the API client client, err := c.Meta.Client() if err != nil { @@ -496,6 +501,7 @@ func (c *OperatorDebugCommand) Run(args []string) int { // Display general info about the capture c.Ui.Output("Starting debugger...") c.Ui.Output("") + c.Ui.Output(fmt.Sprintf("Nomad CLI Version: %s", version.GetVersion().FullVersionNumber(true))) c.Ui.Output(fmt.Sprintf(" Region: %s", c.region)) c.Ui.Output(fmt.Sprintf(" Namespace: %s", c.namespace)) c.Ui.Output(fmt.Sprintf(" Servers: (%d/%d) %v", serverCaptureCount, serversFound, c.serverIDs)) @@ -560,39 +566,19 @@ func (c *OperatorDebugCommand) collect(client *api.Client) error { regions, err := client.Regions().List() c.writeJSON(clusterDir, "regions.json", regions, err) - // Fetch data directly from consul and vault. Ignore errors - var consul, vault string - - if self != nil { - r, ok := self.Config["Consul"] - if ok { - m, ok := r.(map[string]interface{}) - if ok { - - raw := m["Addr"] - consul, _ = raw.(string) - raw = m["EnableSSL"] - ssl, _ := raw.(bool) - if ssl { - consul = "https://" + consul - } else { - consul = "http://" + consul - } - } - } + // Collect data from Consul + if c.consul.addrVal == "" { + c.getConsulAddrFromSelf(self) + } + c.collectConsul(clusterDir) - r, ok = self.Config["Vault"] - if ok { - m, ok := r.(map[string]interface{}) - if ok { - raw := m["Addr"] - vault, _ = raw.(string) - } - } + // Collect data from Vault + vaultAddr := c.vault.addrVal + if vaultAddr == "" { + vaultAddr = c.getVaultAddrFromSelf(self) } + c.collectVault(clusterDir, vaultAddr) - c.collectConsul(clusterDir, consul) - c.collectVault(clusterDir, vault) c.collectAgentHosts(client) c.collectPprofs(client) @@ -894,42 +880,94 @@ func (c *OperatorDebugCommand) collectNomad(dir string, client *api.Client) erro return nil } -// collectConsul calls the Consul API directly to collect data -func (c *OperatorDebugCommand) collectConsul(dir, consul string) error { - addr := c.consul.addr(consul) - if addr == "" { - return nil +// collectConsul calls the Consul API to collect data +func (c *OperatorDebugCommand) collectConsul(dir string) { + if c.consul.addrVal == "" { + c.Ui.Output("Consul - Skipping, no API address found") + return } - client := defaultHttpClient() - api.ConfigureTLS(client, c.consul.tls) + c.Ui.Info(fmt.Sprintf("Consul - Collecting Consul API data from: %s", c.consul.addrVal)) + + client, err := c.consulAPIClient() + if err != nil { + c.Ui.Error(fmt.Sprintf("failed to create Consul API client: %s", err)) + return + } + + // Exit if we are unable to retrieve the leader + err = c.collectConsulAPIRequest(client, "/v1/status/leader", dir, "consul-leader.json") + if err != nil { + c.Ui.Output(fmt.Sprintf("Unable to contact Consul leader, skipping: %s", err)) + return + } + + c.collectConsulAPI(client, "/v1/agent/host", dir, "consul-agent-host.json") + c.collectConsulAPI(client, "/v1/agent/members", dir, "consul-agent-members.json") + c.collectConsulAPI(client, "/v1/agent/metrics", dir, "consul-agent-metrics.json") + c.collectConsulAPI(client, "/v1/agent/self", dir, "consul-agent-self.json") +} + +func (c *OperatorDebugCommand) consulAPIClient() (*http.Client, error) { + httpClient := defaultHttpClient() + + err := api.ConfigureTLS(httpClient, c.consul.tls) + if err != nil { + return nil, fmt.Errorf("failed to configure TLS: %w", err) + } + + return httpClient, nil +} + +func (c *OperatorDebugCommand) collectConsulAPI(client *http.Client, urlPath string, dir string, file string) { + err := c.collectConsulAPIRequest(client, urlPath, dir, file) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error collecting from Consul API: %s", err.Error())) + } +} + +func (c *OperatorDebugCommand) collectConsulAPIRequest(client *http.Client, urlPath string, dir string, file string) error { + url := c.consul.addrVal + urlPath + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return fmt.Errorf("failed to create HTTP request for Consul API URL=%q: %w", url, err) + } - req, _ := http.NewRequest("GET", addr+"/v1/agent/self", nil) req.Header.Add("X-Consul-Token", c.consul.token()) req.Header.Add("User-Agent", userAgent) + resp, err := client.Do(req) - c.writeBody(dir, "consul-agent-self.json", resp, err) + if err != nil { + return err + } - req, _ = http.NewRequest("GET", addr+"/v1/agent/members", nil) - req.Header.Add("X-Consul-Token", c.consul.token()) - req.Header.Add("User-Agent", userAgent) - resp, err = client.Do(req) - c.writeBody(dir, "consul-agent-members.json", resp, err) + c.writeBody(dir, file, resp, err) return nil } // collectVault calls the Vault API directly to collect data func (c *OperatorDebugCommand) collectVault(dir, vault string) error { - addr := c.vault.addr(vault) - if addr == "" { + vaultAddr := c.vault.addr(vault) + if vaultAddr == "" { return nil } + c.Ui.Info(fmt.Sprintf("Vault - Collecting Vault API data from: %s", vaultAddr)) client := defaultHttpClient() - api.ConfigureTLS(client, c.vault.tls) + if c.vault.ssl { + err := api.ConfigureTLS(client, c.vault.tls) + if err != nil { + return fmt.Errorf("failed to configure TLS: %w", err) + } + } + + req, err := http.NewRequest("GET", vaultAddr+"/v1/sys/health", nil) + if err != nil { + return fmt.Errorf("failed to create HTTP request for Vault API URL=%q: %w", vaultAddr, err) + } - req, _ := http.NewRequest("GET", addr+"/sys/health", nil) req.Header.Add("X-Vault-Token", c.vault.token()) req.Header.Add("User-Agent", userAgent) resp, err := client.Do(req) @@ -1028,6 +1066,46 @@ func (c *OperatorDebugCommand) writeBody(dir, file string, resp *http.Response, } } +type flagExport struct { + Name string + Parsed bool + Actual map[string]*flag.Flag + Formal map[string]*flag.Flag + Effective map[string]*flag.Flag // All flags with non-empty value + Args []string // arguments after flags + OsArgs []string +} + +// writeFlags exports the CLI flags to JSON file +func (c *OperatorDebugCommand) writeFlags(flags *flag.FlagSet) { + // c.writeJSON(clusterDir, "cli-flags-complete.json", flags, nil) + + var f flagExport + f.Name = flags.Name() + f.Parsed = flags.Parsed() + f.Formal = make(map[string]*flag.Flag) + f.Actual = make(map[string]*flag.Flag) + f.Effective = make(map[string]*flag.Flag) + f.Args = flags.Args() + f.OsArgs = os.Args + + // Formal flags (all flags) + flags.VisitAll(func(flagA *flag.Flag) { + f.Formal[flagA.Name] = flagA + + // Determine which of thees are "effective" flags by comparing to empty string + if flagA.Value.String() != "" { + f.Effective[flagA.Name] = flagA + } + }) + // Actual flags (everything passed on cmdline) + flags.Visit(func(flag *flag.Flag) { + f.Actual[flag.Name] = flag + }) + + c.writeJSON(clusterDir, "cli-flags.json", f, nil) +} + // writeManifest creates the index files func (c *OperatorDebugCommand) writeManifest() error { // Write the JSON @@ -1214,27 +1292,34 @@ func (e *external) addr(defaultAddr string) string { return defaultAddr } - if !e.ssl { - if strings.HasPrefix(e.addrVal, "http:") { - return e.addrVal - } - if strings.HasPrefix(e.addrVal, "https:") { - // Mismatch: e.ssl=false but addrVal is https - return strings.ReplaceAll(e.addrVal, "https://", "http://") - } - return "http://" + e.addrVal + // Return address as-is if it contains a protocol + if strings.Contains(e.addrVal, "://") { + return e.addrVal } - if strings.HasPrefix(e.addrVal, "https:") { - return e.addrVal + if e.ssl { + return "https://" + e.addrVal } - if strings.HasPrefix(e.addrVal, "http:") { - // Mismatch: e.ssl=true but addrVal is http - return strings.ReplaceAll(e.addrVal, "http://", "https://") + return "http://" + e.addrVal +} + +func (e *external) setAddr(addr string) { + // Handle no protocol scenario first + if !strings.Contains(addr, "://") { + e.addrVal = "http://" + addr + if e.ssl { + e.addrVal = "https://" + addr + } + return } - return "https://" + e.addrVal + // Set SSL boolean based on protocol + e.ssl = false + if strings.Contains(addr, "https") { + e.ssl = true + } + e.addrVal = addr } func (e *external) token() string { @@ -1252,6 +1337,56 @@ func (e *external) token() string { return "" } +func (c *OperatorDebugCommand) getConsulAddrFromSelf(self *api.AgentSelf) string { + if self == nil { + return "" + } + + var consulAddr string + r, ok := self.Config["Consul"] + if ok { + m, ok := r.(map[string]interface{}) + if ok { + raw := m["EnableSSL"] + c.consul.ssl, _ = raw.(bool) + raw = m["Addr"] + c.consul.setAddr(raw.(string)) + raw = m["Auth"] + c.consul.auth, _ = raw.(string) + raw = m["Token"] + c.consul.tokenVal = raw.(string) + + consulAddr = c.consul.addr("") + } + } + return consulAddr +} + +func (c *OperatorDebugCommand) getVaultAddrFromSelf(self *api.AgentSelf) string { + if self == nil { + return "" + } + + var vaultAddr string + r, ok := self.Config["Vault"] + if ok { + m, ok := r.(map[string]interface{}) + if ok { + raw := m["EnableSSL"] + c.vault.ssl, _ = raw.(bool) + raw = m["Addr"] + c.vault.setAddr(raw.(string)) + raw = m["Auth"] + c.vault.auth, _ = raw.(string) + raw = m["Token"] + c.vault.tokenVal = raw.(string) + + vaultAddr = c.vault.addr("") + } + } + return vaultAddr +} + // defaultHttpClient configures a basic httpClient func defaultHttpClient() *http.Client { httpClient := cleanhttp.DefaultClient() diff --git a/command/operator_debug_test.go b/command/operator_debug_test.go index 8ecea5d0037..c61419faf15 100644 --- a/command/operator_debug_test.go +++ b/command/operator_debug_test.go @@ -2,11 +2,16 @@ package command import ( "fmt" + "io/ioutil" "os" "path/filepath" "testing" "time" + consulapi "github.com/hashicorp/consul/api" + consultest "github.com/hashicorp/consul/sdk/testutil" + "github.com/hashicorp/nomad/api" + clienttest "github.com/hashicorp/nomad/client/testutil" "github.com/hashicorp/nomad/command/agent" "github.com/hashicorp/nomad/helper" "github.com/hashicorp/nomad/nomad/state" @@ -159,65 +164,86 @@ func TestDebug_ClientToServer(t *testing.T) { runTestCases(t, cases) } -func TestDebug_ClientToServer_Region(t *testing.T) { - region := "testregion" - - // Start test server and API client - srv, _, url := testServer(t, false, func(c *agent.Config) { - c.Region = region - }) - - // Wait for leadership to establish - testutil.WaitForLeader(t, srv.Agent.RPC) - - // Retrieve server RPC address to join client - srvRPCAddr := srv.GetConfig().AdvertiseAddrs.RPC - t.Logf("[TEST] Leader started, srv.GetConfig().AdvertiseAddrs.RPC: %s", srvRPCAddr) - - // Start client - agent1, _, _ := testClient(t, "client1", newClientAgentConfigFunc(region, "", srvRPCAddr)) - - // Get API addresses - addrServer := srv.HTTPAddr() - addrClient1 := agent1.HTTPAddr() - - t.Logf("[TEST] testAgent api address: %s", url) - t.Logf("[TEST] Server api address: %s", addrServer) - t.Logf("[TEST] Client1 api address: %s", addrClient1) +func TestDebug_MultiRegion(t *testing.T) { + region1 := "region1" + region2 := "region2" + + // Start region1 server + server1, _, addrServer1 := testServer(t, false, func(c *agent.Config) { c.Region = region1 }) + testutil.WaitForLeader(t, server1.Agent.RPC) + rpcAddrServer1 := server1.GetConfig().AdvertiseAddrs.RPC + t.Logf("[TEST] %s: Leader started, HTTPAddr: %s, RPC: %s", region1, addrServer1, rpcAddrServer1) + + // Start region1 client + agent1, _, addrClient1 := testClient(t, "client1", newClientAgentConfigFunc(region1, "", rpcAddrServer1)) + nodeIdClient1 := agent1.Agent.Client().NodeID() + t.Logf("[TEST] %s: Client1 started, ID: %s, HTTPAddr: %s", region1, nodeIdClient1, addrClient1) + + // Start region2 server + server2, _, addrServer2 := testServer(t, false, func(c *agent.Config) { c.Region = region2 }) + testutil.WaitForLeader(t, server2.Agent.RPC) + rpcAddrServer2 := server2.GetConfig().AdvertiseAddrs.RPC + t.Logf("[TEST] %s: Leader started, HTTPAddr: %s, RPC: %s", region2, addrServer2, rpcAddrServer2) + + // Start client2 + agent2, _, addrClient2 := testClient(t, "client2", newClientAgentConfigFunc(region2, "", rpcAddrServer2)) + nodeIdClient2 := agent2.Agent.Client().NodeID() + t.Logf("[TEST] %s: Client1 started, ID: %s, HTTPAddr: %s", region2, nodeIdClient2, addrClient2) + + t.Logf("[TEST] Region: %s, Server1 api address: %s", region1, addrServer1) + t.Logf("[TEST] Region: %s, Client1 api address: %s", region1, addrClient1) + t.Logf("[TEST] Region: %s, Server2 api address: %s", region2, addrServer2) + t.Logf("[TEST] Region: %s, Client2 api address: %s", region2, addrClient2) // Setup test cases var cases = testCases{ // Good { - name: "region - testAgent api server", - args: []string{"-address", url, "-region", region, "-duration", "250ms", "-interval", "250ms", "-server-id", "all", "-node-id", "all"}, + name: "no region - all servers, all clients", + args: []string{"-address", addrServer1, "-duration", "250ms", "-interval", "250ms", "-server-id", "all", "-node-id", "all", "-pprof-duration", "0"}, + expectedCode: 0, + }, + { + name: "region1 - server1 address", + args: []string{"-address", addrServer1, "-region", region1, "-duration", "50ms", "-interval", "50ms", "-server-id", "all", "-node-id", "all", "-pprof-duration", "0"}, expectedCode: 0, expectedOutputs: []string{ - "Region: " + region + "\n", - "Servers: (1/1)", - "Clients: (1/1)", + "Region: " + region1 + "\n", + "Servers: (1/1) [TestDebug_MultiRegion.region1]", + "Clients: (1/1) [" + nodeIdClient1 + "]", "Created debug archive", }, }, { - name: "region - server address", - args: []string{"-address", addrServer, "-region", region, "-duration", "250ms", "-interval", "250ms", "-server-id", "all", "-node-id", "all"}, + name: "region1 - client1 address", + args: []string{"-address", addrClient1, "-region", region1, "-duration", "50ms", "-interval", "50ms", "-server-id", "all", "-node-id", "all", "-pprof-duration", "0"}, expectedCode: 0, expectedOutputs: []string{ - "Region: " + region + "\n", - "Servers: (1/1)", - "Clients: (1/1)", + "Region: " + region1 + "\n", + "Servers: (1/1) [TestDebug_MultiRegion.region1]", + "Clients: (1/1) [" + nodeIdClient1 + "]", "Created debug archive", }, }, { - name: "region - client1 address - verify no SIGSEGV panic", - args: []string{"-address", addrClient1, "-region", region, "-duration", "250ms", "-interval", "250ms", "-server-id", "all", "-node-id", "all"}, + name: "region2 - server2 address", + args: []string{"-address", addrServer2, "-region", region2, "-duration", "50ms", "-interval", "50ms", "-server-id", "all", "-node-id", "all", "-pprof-duration", "0"}, expectedCode: 0, expectedOutputs: []string{ - "Region: " + region + "\n", - "Servers: (1/1)", - "Clients: (1/1)", + "Region: " + region2 + "\n", + "Servers: (1/1) [TestDebug_MultiRegion.region2]", + "Clients: (1/1) [" + nodeIdClient2 + "]", + "Created debug archive", + }, + }, + { + name: "region2 - client2 address", + args: []string{"-address", addrClient2, "-region", region2, "-duration", "50ms", "-interval", "50ms", "-server-id", "all", "-node-id", "all", "-pprof-duration", "0"}, + expectedCode: 0, + expectedOutputs: []string{ + "Region: " + region2 + "\n", + "Servers: (1/1) [TestDebug_MultiRegion.region2]", + "Clients: (1/1) [" + nodeIdClient2 + "]", "Created debug archive", }, }, @@ -225,7 +251,7 @@ func TestDebug_ClientToServer_Region(t *testing.T) { // Bad { name: "invalid region - all servers, all clients", - args: []string{"-address", url, "-region", "never", "-duration", "250ms", "-interval", "250ms", "-server-id", "all", "-node-id", "all"}, + args: []string{"-address", addrServer1, "-region", "never", "-duration", "50ms", "-interval", "50ms", "-server-id", "all", "-node-id", "all", "-pprof-duration", "0"}, expectedCode: 1, expectedError: "500 (No path to region)", }, @@ -375,12 +401,9 @@ func TestDebug_CapturedFiles(t *testing.T) { // Setup file slices clusterFiles := []string{ "agent-self.json", - "consul-agent-members.json", - "consul-agent-self.json", "members.json", "namespaces.json", "regions.json", - "vault-sys-health.json", } pprofFiles := []string{ @@ -424,13 +447,18 @@ func TestDebug_CapturedFiles(t *testing.T) { ui := cli.NewMockUi() cmd := &OperatorDebugCommand{Meta: Meta{Ui: ui}} + duration := 2 * time.Second + interval := 750 * time.Millisecond + waitTime := 2 * duration + code := cmd.Run([]string{ "-address", url, "-output", os.TempDir(), "-server-id", serverName, "-node-id", clientID, - "-duration", "1300ms", - "-interval", "600ms", + "-duration", duration.String(), + "-interval", interval.String(), + "-pprof-duration", "0", }) // Get capture directory @@ -445,27 +473,27 @@ func TestDebug_CapturedFiles(t *testing.T) { // Verify cluster files clusterPaths := buildPathSlice(cmd.path(clusterDir), clusterFiles) t.Logf("Waiting for cluster files in path: %s", clusterDir) - testutil.WaitForFilesUntil(t, clusterPaths, 2*time.Minute) + testutil.WaitForFilesUntil(t, clusterPaths, waitTime) // Verify client files clientPaths := buildPathSlice(cmd.path(clientDir, clientID), clientFiles) t.Logf("Waiting for client files in path: %s", clientDir) - testutil.WaitForFilesUntil(t, clientPaths, 2*time.Minute) + testutil.WaitForFilesUntil(t, clientPaths, waitTime) // Verify server files serverPaths := buildPathSlice(cmd.path(serverDir, serverName), serverFiles) t.Logf("Waiting for server files in path: %s", serverDir) - testutil.WaitForFilesUntil(t, serverPaths, 2*time.Minute) + testutil.WaitForFilesUntil(t, serverPaths, waitTime) // Verify interval 0000 files intervalPaths0 := buildPathSlice(cmd.path(intervalDir, "0000"), intervalFiles) t.Logf("Waiting for interval 0000 files in path: %s", intervalDir) - testutil.WaitForFilesUntil(t, intervalPaths0, 2*time.Minute) + testutil.WaitForFilesUntil(t, intervalPaths0, waitTime) // Verify interval 0001 files intervalPaths1 := buildPathSlice(cmd.path(intervalDir, "0001"), intervalFiles) t.Logf("Waiting for interval 0001 files in path: %s", intervalDir) - testutil.WaitForFilesUntil(t, intervalPaths1, 2*time.Minute) + testutil.WaitForFilesUntil(t, intervalPaths1, waitTime) } func TestDebug_ExistingOutput(t *testing.T) { @@ -538,9 +566,10 @@ func TestDebug_External(t *testing.T) { require.Equal(t, "https://127.0.0.1:8500", addr) // ssl: true - protocol incorrect + // NOTE: Address with protocol now overrides ssl flag e = &external{addrVal: "http://127.0.0.1:8500", ssl: true} addr = e.addr("foo") - require.Equal(t, "https://127.0.0.1:8500", addr) + require.Equal(t, "http://127.0.0.1:8500", addr) // ssl: true - protocol missing e = &external{addrVal: "127.0.0.1:8500", ssl: true} @@ -553,14 +582,20 @@ func TestDebug_External(t *testing.T) { require.Equal(t, "http://127.0.0.1:8500", addr) // ssl: false - protocol incorrect + // NOTE: Address with protocol now overrides ssl flag e = &external{addrVal: "https://127.0.0.1:8500", ssl: false} addr = e.addr("foo") - require.Equal(t, "http://127.0.0.1:8500", addr) + require.Equal(t, "https://127.0.0.1:8500", addr) // ssl: false - protocol missing e = &external{addrVal: "127.0.0.1:8500", ssl: false} addr = e.addr("foo") require.Equal(t, "http://127.0.0.1:8500", addr) + + // Address through proxy might not have a port + e = &external{addrVal: "https://127.0.0.1", ssl: true} + addr = e.addr("foo") + require.Equal(t, "https://127.0.0.1", addr) } func TestDebug_WriteBytes_Nil(t *testing.T) { @@ -608,3 +643,100 @@ func TestDebug_WriteBytes_PathEscapesSandbox(t *testing.T) { err := cmd.writeBytes(testDir, testFile, testBytes) require.Error(t, err) } + +func TestDebug_CollectConsul(t *testing.T) { + t.Parallel() + if testing.Short() { + t.Skip("-short set; skipping") + } + + // Skip test if Consul binary cannot be found + clienttest.RequireConsul(t) + + // Create an embedded Consul server + testconsul, err := consultest.NewTestServerConfigT(t, func(c *consultest.TestServerConfig) { + // If -v wasn't specified squelch consul logging + if !testing.Verbose() { + c.Stdout = ioutil.Discard + c.Stderr = ioutil.Discard + } + }) + require.NoError(t, err) + if err != nil { + t.Fatalf("error starting test consul server: %v", err) + } + defer testconsul.Stop() + + consulConfig := consulapi.DefaultConfig() + consulConfig.Address = testconsul.HTTPAddr + + // Setup mock UI + ui := cli.NewMockUi() + c := &OperatorDebugCommand{Meta: Meta{Ui: ui}} + + // Setup Consul *external + ce := &external{} + ce.setAddr(consulConfig.Address) + if ce.ssl { + ce.tls = &api.TLSConfig{} + } + + // Set global client + c.consul = ce + + // Setup capture directory + testDir := os.TempDir() + defer os.Remove(testDir) + c.collectDir = testDir + + // Collect data from Consul into folder "test" + c.collectConsul("test") + + require.Empty(t, ui.ErrorWriter.String()) + require.FileExists(t, filepath.Join(testDir, "test", "consul-agent-host.json")) + require.FileExists(t, filepath.Join(testDir, "test", "consul-agent-members.json")) + require.FileExists(t, filepath.Join(testDir, "test", "consul-agent-metrics.json")) + require.FileExists(t, filepath.Join(testDir, "test", "consul-leader.json")) +} + +func TestDebug_CollectVault(t *testing.T) { + t.Parallel() + if testing.Short() { + t.Skip("-short set; skipping") + } + + // Skip test if Consul binary cannot be found + clienttest.RequireVault(t) + + // Create a Vault server + v := testutil.NewTestVault(t) + defer v.Stop() + + // Setup mock UI + ui := cli.NewMockUi() + c := &OperatorDebugCommand{Meta: Meta{Ui: ui}} + + // Setup Vault *external + ve := &external{} + ve.tokenVal = v.RootToken + ve.setAddr(v.HTTPAddr) + if ve.ssl { + ve.tls = &api.TLSConfig{} + } + + // Set global client + c.vault = ve + + // Set capture directory + testDir := os.TempDir() + defer os.Remove(testDir) + c.collectDir = testDir + + // Collect data from Vault + err := c.collectVault("test", "") + + require.NoError(t, err) + require.Empty(t, ui.ErrorWriter.String()) + + require.FileExists(t, filepath.Join(testDir, "test", "vault-sys-health.json")) +} diff --git a/testutil/vault.go b/testutil/vault.go index 54691f79d1f..4c5aa05f46e 100644 --- a/testutil/vault.go +++ b/testutil/vault.go @@ -130,7 +130,7 @@ func NewTestVaultFromPath(t testing.T, binary string) *TestVault { } -// NewTestVault returns a new TestVault instance that has yet to be started +// NewTestVault returns a new TestVault instance that is ready for API calls func NewTestVault(t testing.T) *TestVault { // Lookup vault from the path return NewTestVaultFromPath(t, "vault")