diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 991fb24cdd..56e3a6313f 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -136,7 +136,7 @@ func init() { } func Execute() { - if err := rootCmd.ExecuteContext(registry.Context()); err != nil { + if cmd, err := rootCmd.ExecuteContextC(registry.Context()); err != nil { if registry.GetExitCode() == 0 { registry.SetExitCode(define.ExecErrorCodeGeneric) } @@ -145,7 +145,7 @@ func Execute() { fmt.Fprintln(os.Stderr, "Cannot connect to Podman. Please verify your connection to the Linux system using `podman system connection list`, or try `podman machine init` and `podman machine start` to manage a new Linux VM") } } - fmt.Fprintln(os.Stderr, formatError(err)) + fmt.Fprintln(os.Stderr, formatError(err, cmd)) } _ = shutdown.Stop() @@ -726,7 +726,7 @@ func rootFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig) { } } -func formatError(err error) string { +func formatError(err error, cmd *cobra.Command) string { var message string switch { case errors.Is(err, define.ErrOCIRuntime): @@ -737,7 +737,9 @@ func formatError(err error) string { define.ErrOCIRuntime.Error(), strings.TrimSuffix(err.Error(), ": "+define.ErrOCIRuntime.Error()), ) - case errors.Is(err, storage.ErrDuplicateName): + case errors.Is(err, storage.ErrDuplicateName) && cmd != nil && cmd.Flags().Lookup("replace") != nil: + // Only suggest --replace when the invoked command actually has + // the flag (e.g. "manifest create" does not). message = fmt.Sprintf("Error: %s, or use --replace to instruct Podman to do so.", err.Error()) default: if logrus.IsLevelEnabled(logrus.TraceLevel) { diff --git a/cmd/podman/root_test.go b/cmd/podman/root_test.go index 9964a342ae..9b68e30771 100644 --- a/cmd/podman/root_test.go +++ b/cmd/podman/root_test.go @@ -6,12 +6,14 @@ import ( "strings" "testing" + "github.com/spf13/cobra" "go.podman.io/podman/v6/libpod/define" + "go.podman.io/storage" ) func TestFormatError(t *testing.T) { err := errors.New("unknown error") - output := formatError(err) + output := formatError(err, nil) expected := fmt.Sprintf("Error: %v", err) if output != expected { @@ -19,6 +21,36 @@ func TestFormatError(t *testing.T) { } } +func TestFormatErrorDuplicateName(t *testing.T) { + withReplace := &cobra.Command{Use: "create"} + withReplace.Flags().Bool("replace", false, "") + withoutReplace := &cobra.Command{Use: "create"} + + err := fmt.Errorf("that name is already in use: %w", storage.ErrDuplicateName) + hint := "or use --replace to instruct Podman to do so." + + tests := []struct { + name string + cmd *cobra.Command + wantHint bool + }{ + {name: "command with --replace flag", cmd: withReplace, wantHint: true}, + {name: "command without --replace flag", cmd: withoutReplace, wantHint: false}, + {name: "no command", cmd: nil, wantHint: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + output := formatError(err, tt.cmd) + if got := strings.Contains(output, hint); got != tt.wantHint { + t.Errorf("formatError() = %q, want hint contained: %v", output, tt.wantHint) + } + if !strings.Contains(output, err.Error()) { + t.Errorf("formatError() = %q, want the original error message included", output) + } + }) + } +} + func TestIndentExamples(t *testing.T) { tests := []struct { name string @@ -65,7 +97,7 @@ func TestFormatOCIError(t *testing.T) { expectedPrefix := "Error: " expectedSuffix := "OCI runtime output" err := fmt.Errorf("%s: %w", expectedSuffix, define.ErrOCIRuntime) - output := formatError(err) + output := formatError(err, nil) if !strings.HasPrefix(output, expectedPrefix) { t.Errorf("Expected \"%s\" to start with \"%s\"", output, expectedPrefix)