From 29cef7cb0bde15dde18686453229c0f585886877 Mon Sep 17 00:00:00 2001 From: John Schnake Date: Wed, 25 Sep 2019 14:05:17 -0500 Subject: [PATCH] Prints all plugins when invoking `sonobuoy results` by default If given a tarball, there is not a way to detect which plugins were actually run without opening it up first. This means that the `sonobuoy results` command is less useful since you have to have preexisting knowledge about the tarball/run. You'd also have to manually execute the command numerous times if you want all the summaries of the plugins. This change: - makes the default value for the `--plugin` flag the empty string - when a string value is given, it will print a single report like the existing behavior - when the default, empty string is used, we will list the report for each plugin - adds a new file, meta/info.json which will contain the list of plugins that were actually run so that it is easier to grab this list (rather than more complicated logic using the other data in the tarball) Fixes #905 Signed-off-by: John Schnake --- cmd/sonobuoy/app/results.go | 55 +++++++++++++++++++++++++++++++- cmd/sonobuoy/app/results_test.go | 7 ++-- pkg/client/results/reader.go | 11 +++++++ pkg/discovery/discovery.go | 18 +++++++++++ 4 files changed, 88 insertions(+), 3 deletions(-) diff --git a/cmd/sonobuoy/app/results.go b/cmd/sonobuoy/app/results.go index c8f210641..2ffc4732b 100644 --- a/cmd/sonobuoy/app/results.go +++ b/cmd/sonobuoy/app/results.go @@ -26,6 +26,7 @@ import ( "strings" "github.com/heptio/sonobuoy/pkg/client/results" + "github.com/heptio/sonobuoy/pkg/discovery" "github.com/heptio/sonobuoy/pkg/errlog" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -66,7 +67,11 @@ func NewCmdResults() *cobra.Command { }, Args: cobra.ExactArgs(1), } - AddPluginFlag(&data.plugin, cmd.Flags()) + + cmd.Flags().StringVarP( + &data.plugin, "plugin", "p", "", + "Which plugin to show results for. Defaults to printing them all.", + ) cmd.Flags().StringVarP( &data.mode, "mode", "m", resultModeReport, `Modifies the format of the output. Valid options are report, detailed, or dump.`, @@ -100,6 +105,9 @@ func getReader(filepath string) (*results.Reader, func(), error) { return r, func() { gzr.Close(); f.Close() }, nil } +// result takes the resultsInput and tries to print the requested infromation from the archive. +// If there is an error printing any individual plugin, only the last error is printed and all plugins +// continue to be processed. func result(input resultsInput) error { r, cleanup, err := getReader(input.archive) defer cleanup() @@ -107,6 +115,42 @@ func result(input resultsInput) error { return err } + // Report on all plugins or the specified one. + plugins := []string{input.plugin} + if len(input.plugin) == 0 { + plugins, err = getPluginList(r) + if err != nil { + return errors.Wrapf(err, "unable to determine plugins to report on") + } + if len(plugins) == 0 { + return fmt.Errorf("no plugins specified by either the --plugin flag or tarball metadata") + } + } + + var lastErr error + for i, plugin := range plugins { + input.plugin = plugin + + // Load file with a new reader since we can't assume this reader has rewind + // capabilities. + r, cleanup, err := getReader(input.archive) + defer cleanup() + + err = printSinglePlugin(input, r) + if err != nil { + lastErr = err + } + + // Seperator line, but don't print a needless one at the end. + if i+1 < len(plugins) { + fmt.Println() + } + } + + return lastErr +} + +func printSinglePlugin(input resultsInput, r *results.Reader) error { // If we want to dump the whole file, don't decode to an Item object first. if input.mode == resultModeDump { fReader, err := r.PluginResultsReader(input.plugin) @@ -136,6 +180,15 @@ func result(input resultsInput) error { } } +func getPluginList(r *results.Reader) ([]string, error) { + runInfo := discovery.RunInfo{} + err := r.WalkFiles(func(path string, info os.FileInfo, err error) error { + return results.ExtractFileIntoStruct(r.RunInfoFile(), path, info, &runInfo) + }) + + return runInfo.LoadedPlugins, errors.Wrap(err, "finding plugin list") +} + func getItemInTree(i *results.Item, root string) *results.Item { if i == nil { return nil diff --git a/cmd/sonobuoy/app/results_test.go b/cmd/sonobuoy/app/results_test.go index b932e4b2c..e7cca2fff 100644 --- a/cmd/sonobuoy/app/results_test.go +++ b/cmd/sonobuoy/app/results_test.go @@ -21,7 +21,10 @@ import ( func ExampleNewCmdResults() { cmd := NewCmdResults() - cmd.SetArgs([]string{filepath.Join("testdata", "testResultsOutput.tar.gz")}) + cmd.SetArgs([]string{ + filepath.Join("testdata", "testResultsOutput.tar.gz"), + "--plugin=e2e", + }) cmd.Execute() // Output: // Plugin: e2e @@ -39,7 +42,7 @@ func ExampleNewCmdResults_detailed() { cmd := NewCmdResults() cmd.SetArgs([]string{ filepath.Join("testdata", "testResultsOutput.tar.gz"), - "--mode", "detailed", + "--mode", "detailed", "--plugin=e2e", }) cmd.Execute() // Output: diff --git a/pkg/client/results/reader.go b/pkg/client/results/reader.go index bb2537560..85f3567d4 100644 --- a/pkg/client/results/reader.go +++ b/pkg/client/results/reader.go @@ -53,6 +53,10 @@ const ( defaultNodesFile = "Nodes.json" defaultServerVersionFile = "serverversion.json" defaultServerGroupsFile = "servergroups.json" + + // InfoFile contains data not that isn't strictly in another location + // but still relevent to post-processing or understanding the run in some way. + InfoFile = "info.json" ) // Versions corresponding to Kubernetes minor version values. We used to @@ -323,6 +327,13 @@ func ConfigFile(version string) string { } } +// RunInfoFile returns the path to the Sonobuoy RunInfo file which is extra metadata about the run. +// This was added in v0.16.1. The function will return the same string even for earlier +// versions where that file does not exist. +func (r *Reader) RunInfoFile() string { + return path.Join(metadataDir, InfoFile) +} + // PluginResultsItem returns the results file from the given plugin if found, error otherwise. func (r *Reader) PluginResultsItem(plugin string) (*Item, error) { resultObj := &Item{} diff --git a/pkg/discovery/discovery.go b/pkg/discovery/discovery.go index 5884935ec..5f68b715a 100644 --- a/pkg/discovery/discovery.go +++ b/pkg/discovery/discovery.go @@ -47,6 +47,10 @@ const ( pluginDefinitionFilename = "defintion.json" ) +type RunInfo struct { + LoadedPlugins []string `json:"plugins,omitempty"` +} + // Run is the main entrypoint for discovery. func Run(restConf *rest.Config, cfg *config.Config) (errCount int) { // Adjust QPS/Burst so that the queries execute as quickly as possible. @@ -130,6 +134,11 @@ func Run(restConf *rest.Config, cfg *config.Config) (errCount int) { } } + // runInfo is for dumping additional information to help enable processing of the resulting tarball. + runInfo := RunInfo{ + LoadedPlugins: []string{}, + } + // 4. Run the plugin aggregator trackErrorsFor("running plugins")( pluginaggregation.Run(kubeClient, cfg.LoadedPlugins, cfg.Aggregation, cfg.ProgressUpdatesPort, cfg.Namespace, outpath), @@ -207,11 +216,20 @@ func Run(restConf *rest.Config, cfg *config.Config) (errCount int) { // Saving plugin definitions in their respective folders for easy reference. for _, p := range cfg.LoadedPlugins { + runInfo.LoadedPlugins = append(runInfo.LoadedPlugins, p.GetName()) trackErrorsFor("saving plugin info")( dumpPlugin(p, outpath), ) } + // Dump extra metadata that may be useful to postprocessors or analysis. + blob, err := json.Marshal(runInfo) + trackErrorsFor("marshalling run info")(err) + if err == nil { + err = ioutil.WriteFile(path.Join(metapath, results.InfoFile), blob, 0644) + trackErrorsFor("saving" + results.InfoFile)(err) + } + // 8. tarball up results YYYYMMDDHHMM_sonobuoy_UID.tar.gz tb := cfg.ResultsDir + "/" + t.Format("200601021504") + "_sonobuoy_" + cfg.UUID + ".tar.gz" err = tarx.Compress(tb, outpath, &tarx.CompressOptions{Compression: tarx.Gzip})