Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: remove panic case from conflicting configs #39

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions application.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ type Initializer func(*State) error

type PostRun func(*State, error)

type postConstruct func(*application)
type postConstruct func(*application) error

type Application interface {
ID() Identification
AddFlags(flags *pflag.FlagSet, cfgs ...any)
SetupCommand(cmd *cobra.Command, cfgs ...any) *cobra.Command
SetupRootCommand(cmd *cobra.Command, cfgs ...any) *cobra.Command
SetupRootCommand(cmd *cobra.Command, cfgs ...any) (*cobra.Command, error)
Run()
}

Expand Down Expand Up @@ -276,12 +276,12 @@ func (a *application) Run() {
}
}

func (a *application) SetupRootCommand(cmd *cobra.Command, cfgs ...any) *cobra.Command {
func (a *application) SetupRootCommand(cmd *cobra.Command, cfgs ...any) (*cobra.Command, error) {
a.root = cmd
return a.setupRootCommand(cmd, cfgs...)
}

func (a *application) setupRootCommand(cmd *cobra.Command, cfgs ...any) *cobra.Command {
func (a *application) setupRootCommand(cmd *cobra.Command, cfgs ...any) (*cobra.Command, error) {
if !strings.HasPrefix(cmd.Use, a.setupConfig.ID.Name) {
cmd.Use = a.setupConfig.ID.Name
}
Expand All @@ -295,10 +295,13 @@ func (a *application) setupRootCommand(cmd *cobra.Command, cfgs ...any) *cobra.C
a.state.Config.Dev = cp(a.setupConfig.DefaultDevelopmentConfig)

for _, pc := range a.setupConfig.postConstructs {
pc(a)
err := pc(a)
if err != nil {
return nil, err
}
}

return a.setupCommand(cmd, cmd.Flags(), &cmd.PreRunE, cfgs...)
return a.setupCommand(cmd, cmd.Flags(), &cmd.PreRunE, cfgs...), nil
}

func cp[T any](value *T) *T {
Expand Down
8 changes: 4 additions & 4 deletions application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func Test_Application_Setup_WiresFangs(t *testing.T) {

app := New(*cfg)

cmd := app.SetupRootCommand(&cobra.Command{
cmd, _ := app.SetupRootCommand(&cobra.Command{
DisableFlagParsing: true,
Args: cobra.ArbitraryArgs,
Run: func(cmd *cobra.Command, args []string) {
Expand Down Expand Up @@ -163,7 +163,7 @@ func Test_Application_Setup_PassLoggerConstructor(t *testing.T) {

app := New(*cfg)

cmd := app.SetupRootCommand(&cobra.Command{
cmd, _ := app.SetupRootCommand(&cobra.Command{
DisableFlagParsing: true,
Args: cobra.ArbitraryArgs,
Run: func(cmd *cobra.Command, args []string) {},
Expand Down Expand Up @@ -192,7 +192,7 @@ func Test_Application_Setup_ConfigureLogger(t *testing.T) {

app := New(*cfg)

cmd := app.SetupRootCommand(&cobra.Command{
cmd, _ := app.SetupRootCommand(&cobra.Command{
DisableFlagParsing: true,
Args: cobra.ArbitraryArgs,
Run: func(cmd *cobra.Command, args []string) {},
Expand Down Expand Up @@ -280,7 +280,7 @@ func Test_SetupCommand(t *testing.T) {

app := New(*cfg)

root := app.SetupRootCommand(&cobra.Command{})
root, _ := app.SetupRootCommand(&cobra.Command{})

app.AddFlags(root.PersistentFlags(), p)

Expand Down
2 changes: 1 addition & 1 deletion cliotestutils/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (a *testApplication) SetupCommand(cmd *cobra.Command, cfgs ...any) *cobra.C
return a.Application.SetupCommand(cmd, cfgs...)
}

func (a *testApplication) SetupRootCommand(cmd *cobra.Command, cfgs ...any) *cobra.Command {
func (a *testApplication) SetupRootCommand(cmd *cobra.Command, cfgs ...any) (*cobra.Command, error) {
cmd.RunE = func(cmd *cobra.Command, args []string) error {
a.assertion(cmd, args, cfgs...)
return nil
Expand Down
24 changes: 17 additions & 7 deletions help_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package clio

import (
"fmt"
"reflect"
"log"

"github.com/spf13/cobra"

Expand All @@ -11,7 +11,7 @@ import (

var _ postConstruct = updateHelpUsageTemplate

func updateHelpUsageTemplate(a *application) {
func updateHelpUsageTemplate(a *application) error {
cmd := a.root

var helpUsageTemplate = fmt.Sprintf(`{{if (or .Long .Short)}}{{.Long}}{{if not .Long}}{{.Short}}{{end}}
Expand Down Expand Up @@ -42,27 +42,36 @@ Use "{{if .CommandPath}}{{.CommandPath}} {{end}}[command] --help" for more infor

cmd.SetUsageTemplate(helpUsageTemplate)
cmd.SetHelpTemplate(helpUsageTemplate)
return nil
}

var _ postConstruct = showConfigInRootHelp

func showConfigInRootHelp(a *application) {
func showConfigInRootHelp(a *application) error {
cmd := a.root

helpFn := cmd.HelpFunc()
// check if the config can load before setting the help function
cfgs := append([]any{&a.state.Config, a}, a.state.Config.FromCommands...)
for _, cfg := range cfgs {
if err := fangs.Load(a.setupConfig.FangsConfig, cmd, cfg); err != nil {
return fmt.Errorf("error loading config object; do you have a config that conflicts with a newer version? %w", err)
}
}
cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
// root.Example is set _after all added commands_ because it collects all the
// options structs in order to output an accurate "config file" summary
// note: since all commands tend to share help functions it's important to only patch the example
// when there is no parent command (i.e. the root command).
if cmd == a.root {
cfgs := append([]any{&a.state.Config, a}, a.state.Config.FromCommands...)
for _, cfg := range cfgs {
helpCfgs := append([]any{&a.state.Config, a}, a.state.Config.FromCommands...)
for _, cfg := range helpCfgs {
// load each config individually, as there may be conflicting names / types that will cause
// viper to fail to read them all and panic
if err := fangs.Load(a.setupConfig.FangsConfig, cmd, cfg); err != nil {
t := reflect.TypeOf(cfg)
panic(fmt.Sprintf("error loading config object: `%s:%s`: %s", t.PkgPath(), t.Name(), err.Error()))
// log default prints to stderr
log.Printf("error loading config object; do you have a config that conflicts with a newer version? %v", err)
return
}
}
summary := a.summarizeConfig(cmd)
Expand All @@ -73,4 +82,5 @@ func showConfigInRootHelp(a *application) {
}
helpFn(cmd, args)
})
return nil
}
26 changes: 25 additions & 1 deletion help_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,33 @@ import (
"github.com/spf13/cobra"
"github.com/stretchr/testify/require"

"github.com/anchore/fangs"
"github.com/anchore/go-logger/adapter/redact"
)

// regression test for https://github.com/anchore/clio/issues/40
func Test_conflictingConfigErrors(t *testing.T) {
fangsConfig := fangs.NewConfig("test")
fangsConfig.File = "testdata/conflicting_config.yaml"
application := &application{
root: &cobra.Command{},
state: State{
Config: Config{
FromCommands: []any{&struct {
UnderTest string `mapstructure:"test" yaml:"test"`
}{UnderTest: "test"}},
},
},
setupConfig: SetupConfig{
FangsConfig: fangsConfig,
},
}
err := showConfigInRootHelp(application)
require.Error(t, err)
msg := err.Error()
require.Contains(t, msg, "error loading config object")
}

func Test_redactingHelpText(t *testing.T) {
cfg := NewSetupConfig(Identification{
Name: "app",
Expand All @@ -27,7 +51,7 @@ func Test_redactingHelpText(t *testing.T) {

app := New(*cfg)

root := app.SetupRootCommand(&cobra.Command{}, r)
root, _ := app.SetupRootCommand(&cobra.Command{}, r)

r.store = app.(*application).state.RedactStore

Expand Down
6 changes: 4 additions & 2 deletions setup_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,17 @@ func (c *SetupConfig) withPostConstructs(postConstructs ...postConstruct) *Setup

// WithGlobalConfigFlag adds the global `-c` / `--config` flags to the root command
func (c *SetupConfig) WithGlobalConfigFlag() *SetupConfig {
return c.withPostConstructs(func(a *application) {
return c.withPostConstructs(func(a *application) error {
a.AddFlags(a.root.PersistentFlags(), &a.setupConfig.FangsConfig)
return nil
})
}

// WithGlobalLoggingFlags adds the global logging flags to the root command.
func (c *SetupConfig) WithGlobalLoggingFlags() *SetupConfig {
return c.withPostConstructs(func(a *application) {
return c.withPostConstructs(func(a *application) error {
a.AddFlags(a.root.PersistentFlags(), &a.state.Config)
return nil
})
}

Expand Down
3 changes: 3 additions & 0 deletions testdata/conflicting_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
test:
bad: ""
fail: ""
Loading