Skip to content

Commit

Permalink
Refactor query logic and add to CLI
Browse files Browse the repository at this point in the history
- Run queries via CLI command 'sonobuoy query'
- Modifies config to support overriding query output location so that
it works via CLI and via aggregator.
- Refactor the aggregator logic to utilize the new query organization
and not error the run if queries fail.

Signed-off-by: John Schnake <jschnake@vmware.com>
  • Loading branch information
johnSchnake committed Dec 16, 2021
1 parent c7c47dc commit 5577b2d
Show file tree
Hide file tree
Showing 13 changed files with 645 additions and 621 deletions.
81 changes: 81 additions & 0 deletions cmd/sonobuoy/app/query.go
@@ -0,0 +1,81 @@
/*
Copyright 2021 Sonobuoy Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package app

import (
"fmt"
"os"

"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/vmware-tanzu/sonobuoy/pkg/config"
"github.com/vmware-tanzu/sonobuoy/pkg/discovery"
"github.com/vmware-tanzu/sonobuoy/pkg/errlog"
)

type queryInput struct {
kubecfg Kubeconfig
cfg *SonobuoyConfig
outDir string
}

func NewCmdQuery() *cobra.Command {
// Default to an empty config for CLI users but allow us to load
// it from disc for the in-cluster case.
input := queryInput{
cfg: &SonobuoyConfig{},
}
input.cfg.Config = *config.New()
input.cfg.Config.Resolve()

cmd := &cobra.Command{
Use: "query",
Short: "Runs queries against your cluster in order to aid in debugging.",
Run: func(cmd *cobra.Command, args []string) {
restConf, err := input.kubecfg.Get()
if err != nil {
errlog.LogError(errors.Wrap(err, "getting kubeconfig"))
os.Exit(1)
}

// Override the query results directory. Since config is a param too just avoid complication
// and override the default value here.
if len(input.outDir) > 0 {
input.cfg.QueryDir = input.outDir
} else {
// UUID instead of default (aggregatorResultsPath) since we need to be OS-agnostic.
input.cfg.QueryDir = input.cfg.UUID
}

logrus.Tracef("Querying using config %#v", &input.cfg.Config)
logrus.Tracef("Query results will be placed in %v", input.cfg.Config.QueryOutputDir())
if err := discovery.QueryCluster(restConf, &input.cfg.Config); err != nil {
errlog.LogError(err)
os.Exit(1)
}

// Print the output directory for scripting.
fmt.Println(input.cfg.Config.QueryOutputDir())
},
}

AddSonobuoyConfigFlag(input.cfg, cmd.Flags())
cmd.Flags().StringVarP(&input.outDir, "output", "o", "", "Directory to output results into. If empty, will default to a UUID folder in the pwd.")

return cmd
}
2 changes: 2 additions & 0 deletions cmd/sonobuoy/app/root.go
Expand Up @@ -63,6 +63,8 @@ func NewSonobuoyCommand() *cobra.Command {
cmds.AddCommand(NewCmdSplat())
cmds.AddCommand(NewCmdWait())

cmds.AddCommand(NewCmdQuery())

cmds.AddCommand(NewCmdPlugin())

initKlog(cmds)
Expand Down
16 changes: 14 additions & 2 deletions pkg/config/config.go
Expand Up @@ -110,6 +110,7 @@ type Config struct {
UUID string `json:"UUID" mapstructure:"UUID"`
Version string `json:"Version" mapstructure:"Version"`
ResultsDir string `json:"ResultsDir" mapstructure:"ResultsDir"`
QueryDir string `json:"QueryDir,omitempty" mapstructure:"QueryDir"`

///////////////////////////////////////////////
// Query options
Expand Down Expand Up @@ -250,12 +251,23 @@ func (cfg *Config) FilterResources(filter []string) []string {
return results
}

// OutputDir returns the aggregator directory under the ResultsDir containing the
// UUID for this run.
// OutputDir returns the AggregatorResultsPath/:UUID. Hard-coded aggregator results path
// to avoid miscommunication between the local host and aggregator during retrieve.
func (cfg *Config) OutputDir() string {
return path.Join(AggregatorResultsPath, cfg.UUID)
}

// QueryOutputDir returns the QueryDir if set and falls back to the
// AggregatorResultsPath/:UUID to work on the aggregator by default.
func (cfg *Config) QueryOutputDir() string {
// Override option for when running via CLI.
if len(cfg.QueryDir) > 0 {
return cfg.QueryDir
}
// Default for when running via aggregator.
return cfg.OutputDir()
}

// New returns a newly-constructed Config object with default values.
func New() *Config {
var cfg Config
Expand Down
58 changes: 31 additions & 27 deletions pkg/config/loader.go
Expand Up @@ -23,6 +23,7 @@ import (
"os"
"strings"

"github.com/sirupsen/logrus"
"github.com/vmware-tanzu/sonobuoy/pkg/buildinfo"
"github.com/vmware-tanzu/sonobuoy/pkg/plugin"
pluginloader "github.com/vmware-tanzu/sonobuoy/pkg/plugin/loader"
Expand All @@ -38,22 +39,22 @@ const (

// LoadConfig will load the current sonobuoy configuration using the filesystem
// and environment variables, and returns a config object
func LoadConfig() (*Config, error) {
func LoadConfig(pathsToTry ...string) (*Config, error) {
cfg := &Config{}

var pathsToTry []string
envCfgFileName := os.Getenv("SONOBUOY_CONFIG")
if envCfgFileName != "" {
pathsToTry = []string{envCfgFileName}
pathsToTry = append(pathsToTry, envCfgFileName)
} else {
pathsToTry = []string{defaultCfgFileName, fallbackCfgFileName}
pathsToTry = append(pathsToTry, defaultCfgFileName, fallbackCfgFileName)
}

jsonFile, fpath, err := openFiles(pathsToTry...)
if err != nil {
return nil, errors.Wrap(err, "open config")
}
defer jsonFile.Close()
logrus.Tracef("Loading config from file: %v", fpath)

b, err := ioutil.ReadAll(jsonFile)
if err != nil {
Expand All @@ -64,7 +65,29 @@ func LoadConfig() (*Config, error) {
return nil, errors.Wrapf(err, "unmarshal config file %q", fpath)
}

// 3 - figure out what address we will tell pods to dial for aggregation
cfg.Resolve()

if err := loadAllPlugins(cfg); err != nil {
return nil, err
}

// 6 - Return any validation errors
validationErrs := cfg.Validate()
if len(validationErrs) > 0 {
errstrs := make([]string, len(validationErrs))
for i := range validationErrs {
errstrs[i] = validationErrs[i].Error()
}

return nil, errors.Errorf("invalid configuration: %v", strings.Join(errstrs, ", "))
}

logrus.Tracef("Config loaded: %#v", *cfg)
return cfg, err
}

func (cfg *Config) Resolve() {
// Figure out what address we will tell pods to dial for aggregation
if cfg.Aggregation.AdvertiseAddress == "" {
if ip := os.Getenv("SONOBUOY_ADVERTISE_IP"); ip != "" {
cfg.Aggregation.AdvertiseAddress = fmt.Sprintf("[%v]:%d", ip, cfg.Aggregation.BindPort)
Expand All @@ -76,44 +99,23 @@ func LoadConfig() (*Config, error) {
}
}

// 4 - Any other settings
cfg.Version = buildinfo.Version

// Make the results dir overridable with an environment variable
if resultsDir, ok := os.LookupEnv("RESULTS_DIR"); ok {
cfg.ResultsDir = resultsDir
}

// If the loaded config doesn't have its own UUID, create one
if cfg.UUID == "" {
cfgUuid, _ := uuid.NewV4()
cfg.UUID = cfgUuid.String()
}

// 5 - Load any plugins we have
err = loadAllPlugins(cfg)
if err != nil {
return nil, err
}

// 6 - Return any validation errors
validationErrs := cfg.Validate()
if len(validationErrs) > 0 {
errstrs := make([]string, len(validationErrs))
for i := range validationErrs {
errstrs[i] = validationErrs[i].Error()
}

return nil, errors.Errorf("invalid configuration: %v", strings.Join(errstrs, ", "))
}

return cfg, err
}

// Validate returns a list of errors for the configuration, if any are found.
func (cfg *Config) Validate() (errorsList []error) {
podLogLimits := &cfg.Limits.PodLogs

if podLogLimits.SinceTime != nil && podLogLimits.SinceSeconds != nil {
errorsList = append(errorsList, errors.New("Only one of sinceSeconds or sinceTime may be specified."))
}
Expand Down Expand Up @@ -170,11 +172,13 @@ func openFiles(paths ...string) (*os.File, string, error) {
var err error

for _, path = range paths {
logrus.Tracef("Trying path: %v", path)
f, err = os.Open(path)
switch {
case err == nil:
return f, path, nil
case err != nil && os.IsNotExist(err):
logrus.Tracef("File %q does not exist", path)
continue
default:
return nil, path, errors.Wrap(err, "opening config file")
Expand Down

0 comments on commit 5577b2d

Please sign in to comment.