diff --git a/changes/issue-5504-improve-debug-output b/changes/issue-5504-improve-debug-output new file mode 100644 index 0000000000..d71a96e1c5 --- /dev/null +++ b/changes/issue-5504-improve-debug-output @@ -0,0 +1 @@ +* Improve the output of `fleetclt debug errors` and add the ability to print the errors to stdout via the `-stdout` flag. diff --git a/cmd/fleetctl/debug.go b/cmd/fleetctl/debug.go index 80a7f00d94..ac90eedf0f 100644 --- a/cmd/fleetctl/debug.go +++ b/cmd/fleetctl/debug.go @@ -22,6 +22,9 @@ import ( "github.com/urfave/cli/v2" ) +// Defining here for testing purposes +var nowFn = time.Now + func debugCommand() *cli.Command { return &cli.Command{ Name: "debug", @@ -58,7 +61,11 @@ func writeFile(filename string, bytes []byte, mode os.FileMode) error { } func outfileName(name string) string { - return fmt.Sprintf("fleet-%s-%s", name, time.Now().Format(time.RFC3339)) + return fmt.Sprintf("fleet-%s-%s", name, nowFn().Format(time.RFC3339)) +} + +func outfileNameWithExt(name string, ext string) string { + return fmt.Sprintf("%s.%s", outfileName(name), ext) } func debugProfileCommand() *cli.Command { @@ -285,11 +292,10 @@ func debugArchiveCommand() *cli.Command { "db-process-list", } - outpath := getOutfile(c) - if outpath == "" { - outpath = outfileName("profiles-archive") + outfile := getOutfile(c) + if outfile == "" { + outfile = outfileNameWithExt("profiles-archive", "tar.gz") } - outfile := outpath + ".tar.gz" f, err := secure.OpenFile(outfile, os.O_CREATE|os.O_WRONLY, defaultFileMode) if err != nil { @@ -334,7 +340,7 @@ func debugArchiveCommand() *cli.Command { if err := tarwriter.WriteHeader( &tar.Header{ - Name: outpath + "/" + profile, + Name: outfile + "/" + profile, Size: int64(len(res)), Mode: defaultFileMode, }, @@ -347,7 +353,14 @@ func debugArchiveCommand() *cli.Command { } } - fmt.Fprintf(os.Stderr, "Archive written to %s\n", outfile) + fmt.Fprintf(os.Stderr, "################################################################################\n"+ + "# WARNING:\n"+ + "# The files in the generated archive may contain sensitive data.\n"+ + "# Please review them before sharing.\n"+ + "#\n"+ + "# Archive written to: %s\n"+ + "################################################################################\n", + outfile) return nil }, @@ -530,6 +543,7 @@ func debugErrorsCommand() *cli.Command { configFlag(), contextFlag(), debugFlag(), + stdoutFlag(), }, Action: func(c *cli.Context) error { fleet, err := clientFromCLI(c) @@ -538,23 +552,45 @@ func debugErrorsCommand() *cli.Command { } outfile := getOutfile(c) - if outfile == "" { - outfile = outfileName(name) + stdout := getStdout(c) + + if stdout && outfile != "" { + return errors.New("-stdout and -outfile must not be specified together") } - f, err := os.OpenFile(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, defaultFileMode) - if err != nil { - return err - } - defer f.Close() + out := os.Stdout - if err := fleet.DebugErrors(f); err != nil { + if !stdout { + if outfile == "" { + outfile = outfileNameWithExt(name, "json") + } + + f, err := os.OpenFile(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, defaultFileMode) + if err != nil { + return err + } + defer f.Close() + out = f + } + + if err := fleet.DebugErrors(out); err != nil { return err } - if err := f.Close(); err != nil { - return fmt.Errorf("write errors to file: %w", err) + + if !stdout { + if err := out.Close(); err != nil { + return fmt.Errorf("write errors to file: %w", err) + } + + fmt.Fprintf(os.Stderr, "################################################################################\n"+ + "# WARNING:\n"+ + "# The generated file may contain sensitive data.\n"+ + "# Please review the file before sharing.\n"+ + "#\n"+ + "# Output written to: %s\n"+ + "################################################################################\n", + outfile) } - fmt.Fprintf(os.Stderr, "Output written to %s\n", outfile) return nil }, diff --git a/cmd/fleetctl/debug_test.go b/cmd/fleetctl/debug_test.go index a3c7468571..22458cf93c 100644 --- a/cmd/fleetctl/debug_test.go +++ b/cmd/fleetctl/debug_test.go @@ -19,6 +19,7 @@ import ( "time" "github.com/fleetdm/fleet/v4/server/fleet" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -200,3 +201,21 @@ func TestDebugResolveHostname(t *testing.T) { err = resolveHostname(context.Background(), timeout, noSuchHost) require.Error(t, err) } + +func TestFilenameFunctions(t *testing.T) { + nowFn = func() time.Time { + now, _ := time.Parse(time.RFC3339, "1969-06-19T21:44:05Z") + return now + } + defer func() { nowFn = time.Now }() + + t.Run("outfileName builds a file name using the name provided + current time ", func(t *testing.T) { + name := outfileName("test") + assert.Equal(t, name, "fleet-test-1969-06-19T21:44:05Z") + }) + + t.Run("outfileNameWithExt builds a file name using the name and extension provided + current time ", func(t *testing.T) { + name := outfileNameWithExt("test", "go") + assert.Equal(t, name, "fleet-test-1969-06-19T21:44:05Z.go") + }) +} diff --git a/cmd/fleetctl/flags.go b/cmd/fleetctl/flags.go index 3d264842ea..c6a7679276 100644 --- a/cmd/fleetctl/flags.go +++ b/cmd/fleetctl/flags.go @@ -6,6 +6,7 @@ const ( outfileFlagName = "outfile" debugFlagName = "debug" fleetCertificateFlagName = "fleet-certificate" + stdoutFlagName = "stdout" ) func outfileFlag() cli.Flag { @@ -44,3 +45,15 @@ func fleetCertificateFlag() cli.Flag { func getFleetCertificate(c *cli.Context) string { return c.String(fleetCertificateFlagName) } + +func stdoutFlag() cli.Flag { + return &cli.BoolFlag{ + Name: stdoutFlagName, + EnvVars: []string{"STDOUT"}, + Usage: "Print contents to stdout", + } +} + +func getStdout(c *cli.Context) bool { + return c.Bool(stdoutFlagName) +} diff --git a/cmd/fleetctl/get.go b/cmd/fleetctl/get.go index b77ef177f2..7943a81f4e 100644 --- a/cmd/fleetctl/get.go +++ b/cmd/fleetctl/get.go @@ -24,7 +24,6 @@ const ( jsonFlagName = "json" withQueriesFlagName = "with-queries" expiredFlagName = "expired" - stdoutFlagName = "stdout" includeServerConfigFlagName = "include-server-config" ) @@ -753,10 +752,7 @@ func getCarveCommand() *cli.Command { Name: "carve", Usage: "Retrieve details for a carve by ID", Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: stdoutFlagName, - Usage: "Print carve contents to stdout", - }, + stdoutFlag(), configFlag(), contextFlag(), outfileFlag(),