diff options
Diffstat (limited to 'cmd/podman')
40 files changed, 273 insertions, 219 deletions
diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 3720e9608..5eef5f982 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -4,6 +4,7 @@ import ( "bufio" "fmt" "os" + "path" "reflect" "strconv" "strings" @@ -21,6 +22,7 @@ import ( "github.com/containers/podman/v4/pkg/signal" systemdDefine "github.com/containers/podman/v4/pkg/systemd/define" "github.com/containers/podman/v4/pkg/util" + securejoin "github.com/cyphar/filepath-securejoin" "github.com/spf13/cobra" ) @@ -282,6 +284,61 @@ func getNetworks(cmd *cobra.Command, toComplete string, cType completeType) ([]s return suggestions, cobra.ShellCompDirectiveNoFileComp } +func getPathCompletion(root string, toComplete string) []string { + if toComplete == "" { + toComplete = "/" + } + // Important: securejoin is required to make sure we never leave the root mount point + userpath, err := securejoin.SecureJoin(root, toComplete) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil + } + var base string + f, err := os.Open(userpath) + if err != nil { + // Do not use path.Dir() since this cleans the paths which + // then no longer matches the user input. + userpath, base = path.Split(userpath) + toComplete, _ = path.Split(toComplete) + f, err = os.Open(userpath) + if err != nil { + return nil + } + } + stat, err := f.Stat() + if err != nil { + cobra.CompErrorln(err.Error()) + return nil + } + if !stat.IsDir() { + // nothing to complete since it is no dir + return nil + } + entries, err := f.ReadDir(-1) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil + } + completions := make([]string, 0, len(entries)) + for _, e := range entries { + if strings.HasPrefix(e.Name(), base) { + completions = append(completions, simplePathJoinUnix(toComplete, e.Name())) + } + } + return completions +} + +// simplePathJoinUnix joins to path components by adding a slash only if p1 doesn't end with one. +// We cannot use path.Join() for the completions logic because this one always calls Clean() on +// the path which changes it from the input. +func simplePathJoinUnix(p1, p2 string) string { + if p1[len(p1)-1] == '/' { + return p1 + p2 + } + return p1 + "/" + p2 +} + // validCurrentCmdLine validates the current cmd line // It utilizes the Args function from the cmd struct // In most cases the Args function validates the args length but it @@ -523,8 +580,32 @@ func AutocompleteCreateRun(cmd *cobra.Command, args []string, toComplete string) } return getImages(cmd, toComplete) } - // TODO: add path completion for files in the image - return nil, cobra.ShellCompDirectiveDefault + // Mount the image and provide path completion + engine, err := setupImageEngine(cmd) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveDefault + } + + resp, err := engine.Mount(registry.Context(), []string{args[0]}, entities.ImageMountOptions{}) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveDefault + } + defer func() { + _, err := engine.Unmount(registry.Context(), []string{args[0]}, entities.ImageUnmountOptions{}) + if err != nil { + cobra.CompErrorln(err.Error()) + } + }() + if len(resp) != 1 { + return nil, cobra.ShellCompDirectiveDefault + } + + // So this uses ShellCompDirectiveDefault to also still provide normal shell + // completion in case no path matches. This is useful if someone tries to get + // completion for paths that are not available in the image, e.g. /proc/... + return getPathCompletion(resp[0].Path, toComplete), cobra.ShellCompDirectiveDefault | cobra.ShellCompDirectiveNoSpace } // AutocompleteRegistries - Autocomplete registries. @@ -572,14 +653,39 @@ func AutocompleteCpCommand(cmd *cobra.Command, args []string, toComplete string) return nil, cobra.ShellCompDirectiveNoFileComp } if len(args) < 2 { + if i := strings.IndexByte(toComplete, ':'); i > -1 { + // Looks like the user already set the container. + // Lets mount it and provide path completion for files in the container. + engine, err := setupContainerEngine(cmd) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveDefault + } + + resp, err := engine.ContainerMount(registry.Context(), []string{toComplete[:i]}, entities.ContainerMountOptions{}) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveDefault + } + defer func() { + _, err := engine.ContainerUnmount(registry.Context(), []string{toComplete[:i]}, entities.ContainerUnmountOptions{}) + if err != nil { + cobra.CompErrorln(err.Error()) + } + }() + if len(resp) != 1 { + return nil, cobra.ShellCompDirectiveDefault + } + return prefixSlice(toComplete[:i+1], getPathCompletion(resp[0].Path, toComplete[i+1:])), cobra.ShellCompDirectiveDefault | cobra.ShellCompDirectiveNoSpace + } + // Suggest containers when they match the input otherwise normal shell completion is used containers, _ := getContainers(cmd, toComplete, completeDefault) for _, container := range containers { - // TODO: Add path completion for inside the container if possible if strings.HasPrefix(container, toComplete) { - return containers, cobra.ShellCompDirectiveNoSpace + return suffixCompSlice(":", containers), cobra.ShellCompDirectiveNoSpace } } - // else complete paths + // else complete paths on the host return nil, cobra.ShellCompDirectiveDefault } // don't complete more than 2 args diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index d28becc8a..886c10cb5 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -255,9 +255,8 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, _ = cmd.RegisterFlagCompletionFunc(hostUserFlagName, completion.AutocompleteNone) imageVolumeFlagName := "image-volume" - createFlags.StringVar( - &cf.ImageVolume, - imageVolumeFlagName, DefaultImageVolume, + createFlags.String( + imageVolumeFlagName, containerConfig.Engine.ImageVolumeMode, `Tells podman how to handle the builtin image volumes ("bind"|"tmpfs"|"ignore")`, ) _ = cmd.RegisterFlagCompletionFunc(imageVolumeFlagName, AutocompleteImageVolume) diff --git a/cmd/podman/common/default.go b/cmd/podman/common/default.go index 7caec50ff..6f78d3d29 100644 --- a/cmd/podman/common/default.go +++ b/cmd/podman/common/default.go @@ -5,9 +5,6 @@ import ( ) var ( - - // DefaultImageVolume default value - DefaultImageVolume = "bind" // Pull in configured json library json = registry.JSONLibrary() ) diff --git a/cmd/podman/containers/checkpoint.go b/cmd/podman/containers/checkpoint.go index 40d689c4d..e0891f7a1 100644 --- a/cmd/podman/containers/checkpoint.go +++ b/cmd/podman/containers/checkpoint.go @@ -31,7 +31,7 @@ var ( Long: checkpointDescription, RunE: checkpoint, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompleteContainersRunning, Example: `podman container checkpoint --keep ctrID diff --git a/cmd/podman/containers/cleanup.go b/cmd/podman/containers/cleanup.go index aa2734607..a63e413fe 100644 --- a/cmd/podman/containers/cleanup.go +++ b/cmd/podman/containers/cleanup.go @@ -27,7 +27,7 @@ var ( Long: cleanupDescription, RunE: cleanup, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompleteContainersExited, Example: `podman container cleanup --latest diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index c62ddd6eb..0a513c606 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -102,16 +102,25 @@ func init() { createFlags(containerCreateCommand) } -func create(cmd *cobra.Command, args []string) error { - var ( - err error - ) +func commonFlags(cmd *cobra.Command) error { + var err error flags := cmd.Flags() cliVals.Net, err = common.NetFlagsToNetOptions(nil, *flags) if err != nil { return err } + if cmd.Flags().Changed("image-volume") { + cliVals.ImageVolume = cmd.Flag("image-volume").Value.String() + } + return nil +} + +func create(cmd *cobra.Command, args []string) error { + if err := commonFlags(cmd); err != nil { + return err + } + // Check if initctr is used with --pod and the value is correct if initctr := InitContainerType; cmd.Flags().Changed("init-ctr") { if !cmd.Flags().Changed("pod") { @@ -123,7 +132,7 @@ func create(cmd *cobra.Command, args []string) error { cliVals.InitContainerType = initctr } - cliVals, err = CreateInit(cmd, cliVals, false) + cliVals, err := CreateInit(cmd, cliVals, false) if err != nil { return err } diff --git a/cmd/podman/containers/diff.go b/cmd/podman/containers/diff.go index e1a8ea729..15d3a3eff 100644 --- a/cmd/podman/containers/diff.go +++ b/cmd/podman/containers/diff.go @@ -32,13 +32,9 @@ func init() { Parent: containerCmd, }) - diffOpts = &entities.DiffOptions{} + diffOpts = new(entities.DiffOptions) flags := diffCmd.Flags() - // FIXME: Why does this exists? It is not used anywhere. - flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive") - _ = flags.MarkHidden("archive") - formatFlagName := "format" flags.StringVar(&diffOpts.Format, formatFlagName, "", "Change the output format (json)") _ = diffCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(nil)) diff --git a/cmd/podman/containers/init.go b/cmd/podman/containers/init.go index 7336a2332..649cdf1c9 100644 --- a/cmd/podman/containers/init.go +++ b/cmd/podman/containers/init.go @@ -21,7 +21,7 @@ var ( Long: initDescription, RunE: initContainer, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompleteContainersCreated, Example: `podman init --latest diff --git a/cmd/podman/containers/kill.go b/cmd/podman/containers/kill.go index e994fbf2c..eddefd196 100644 --- a/cmd/podman/containers/kill.go +++ b/cmd/podman/containers/kill.go @@ -25,7 +25,7 @@ var ( Long: killDescription, RunE: kill, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "cidfile") }, ValidArgsFunction: common.AutocompleteContainersRunning, Example: `podman kill mywebserver @@ -35,7 +35,7 @@ var ( containerKillCommand = &cobra.Command{ Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "cidfile") }, Use: killCommand.Use, Short: killCommand.Short, diff --git a/cmd/podman/containers/mount.go b/cmd/podman/containers/mount.go index 18177e3ce..16eb5d452 100644 --- a/cmd/podman/containers/mount.go +++ b/cmd/podman/containers/mount.go @@ -33,7 +33,7 @@ var ( Long: mountDescription, RunE: mount, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, true, false) + return validate.CheckAllLatestAndIDFile(cmd, args, true, "") }, ValidArgsFunction: common.AutocompleteContainers, } diff --git a/cmd/podman/containers/port.go b/cmd/podman/containers/port.go index 22d1d16d3..f10bdd5b4 100644 --- a/cmd/podman/containers/port.go +++ b/cmd/podman/containers/port.go @@ -23,7 +23,7 @@ var ( Long: portDescription, RunE: port, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, true, false) + return validate.CheckAllLatestAndIDFile(cmd, args, true, "") }, ValidArgsFunction: common.AutocompleteContainerOneArg, Example: `podman port --all @@ -37,7 +37,7 @@ var ( Long: portDescription, RunE: portCommand.RunE, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, true, false) + return validate.CheckAllLatestAndIDFile(cmd, args, true, "") }, ValidArgsFunction: portCommand.ValidArgsFunction, Example: `podman container port --all diff --git a/cmd/podman/containers/restart.go b/cmd/podman/containers/restart.go index 69d8d71ea..25bbb61e3 100644 --- a/cmd/podman/containers/restart.go +++ b/cmd/podman/containers/restart.go @@ -26,7 +26,7 @@ var ( Long: restartDescription, RunE: restart, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompleteContainers, Example: `podman restart ctrID diff --git a/cmd/podman/containers/restore.go b/cmd/podman/containers/restore.go index eeda5a05f..1e4745354 100644 --- a/cmd/podman/containers/restore.go +++ b/cmd/podman/containers/restore.go @@ -28,7 +28,7 @@ var ( Long: restoreDescription, RunE: restore, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, true, false) + return validate.CheckAllLatestAndIDFile(cmd, args, true, "") }, ValidArgsFunction: common.AutocompleteContainersAndImages, Example: `podman container restore ctrID diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go index 420e3c38d..bcbe86947 100644 --- a/cmd/podman/containers/rm.go +++ b/cmd/podman/containers/rm.go @@ -28,7 +28,7 @@ var ( Long: rmDescription, RunE: rm, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "cidfile") }, ValidArgsFunction: common.AutocompleteContainers, Example: `podman rm imageID @@ -123,9 +123,7 @@ func rm(cmd *cobra.Command, args []string) error { // removeContainers will set the exit code according to the `podman-rm` man // page. func removeContainers(namesOrIDs []string, rmOptions entities.RmOptions, setExit bool) error { - var ( - errs utils.OutputErrors - ) + var errs utils.OutputErrors responses, err := registry.ContainerEngine().ContainerRm(context.Background(), namesOrIDs, rmOptions) if err != nil { if setExit { @@ -135,8 +133,9 @@ func removeContainers(namesOrIDs []string, rmOptions entities.RmOptions, setExit } for _, r := range responses { if r.Err != nil { - // TODO this will not work with the remote client - if errors.Cause(err) == define.ErrWillDeadlock { + // When using the API, errors.Cause(err) will never equal constant define.ErrWillDeadLock + if errors.Cause(r.Err) == define.ErrWillDeadlock || + errors.Cause(r.Err).Error() == define.ErrWillDeadlock.Error() { logrus.Errorf("Potential deadlock detected - please run 'podman system renumber' to resolve") } if setExit { diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index 951981293..a6c500afa 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -109,7 +109,9 @@ func init() { } func run(cmd *cobra.Command, args []string) error { - var err error + if err := commonFlags(cmd); err != nil { + return err + } // TODO: Breaking change should be made fatal in next major Release if cliVals.TTY && cliVals.Interactive && !term.IsTerminal(int(os.Stdin.Fd())) { @@ -122,14 +124,10 @@ func run(cmd *cobra.Command, args []string) error { } } - flags := cmd.Flags() - cliVals.Net, err = common.NetFlagsToNetOptions(nil, *flags) - if err != nil { - return err - } runOpts.CIDFile = cliVals.CIDFile runOpts.Rm = cliVals.Rm - if cliVals, err = CreateInit(cmd, cliVals, false); err != nil { + cliVals, err := CreateInit(cmd, cliVals, false) + if err != nil { return err } diff --git a/cmd/podman/containers/stop.go b/cmd/podman/containers/stop.go index af2250abb..def608fea 100644 --- a/cmd/podman/containers/stop.go +++ b/cmd/podman/containers/stop.go @@ -26,7 +26,7 @@ var ( Long: stopDescription, RunE: stop, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "cidfile") }, ValidArgsFunction: common.AutocompleteContainersRunning, Example: `podman stop ctrID @@ -40,7 +40,7 @@ var ( Long: stopCommand.Long, RunE: stopCommand.RunE, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "cidfile") }, ValidArgsFunction: stopCommand.ValidArgsFunction, Example: `podman container stop ctrID diff --git a/cmd/podman/containers/unmount.go b/cmd/podman/containers/unmount.go index 26b8cfcc5..6869de2e2 100644 --- a/cmd/podman/containers/unmount.go +++ b/cmd/podman/containers/unmount.go @@ -27,7 +27,7 @@ var ( Long: description, RunE: unmount, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompleteContainers, Example: `podman unmount ctrID @@ -43,7 +43,7 @@ var ( Long: unmountCommand.Long, RunE: unmountCommand.RunE, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompleteContainers, Example: `podman container unmount ctrID diff --git a/cmd/podman/diff.go b/cmd/podman/diff.go index 7b78c8312..ec98fb5b5 100644 --- a/cmd/podman/diff.go +++ b/cmd/podman/diff.go @@ -35,9 +35,6 @@ func init() { Command: diffCmd, }) flags := diffCmd.Flags() - // FIXME: Why does this exists? It is not used anywhere. - flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive") - _ = flags.MarkHidden("archive") formatFlagName := "format" flags.StringVar(&diffOpts.Format, formatFlagName, "", "Change the output format (json)") diff --git a/cmd/podman/diff/diff.go b/cmd/podman/diff/diff.go index a26502de9..15c55852a 100644 --- a/cmd/podman/diff/diff.go +++ b/cmd/podman/diff/diff.go @@ -13,7 +13,7 @@ import ( "github.com/spf13/cobra" ) -func Diff(cmd *cobra.Command, args []string, options entities.DiffOptions) error { +func Diff(_ *cobra.Command, args []string, options entities.DiffOptions) error { results, err := registry.ContainerEngine().Diff(registry.GetContext(), args, options) if err != nil { return err @@ -63,7 +63,7 @@ func changesToTable(diffs *entities.DiffReport) error { return nil } -// IDOrLatestArgs used to validate a nameOrId was provided or the "--latest" flag +// ValidateContainerDiffArgs used to validate a nameOrId was provided or the "--latest" flag func ValidateContainerDiffArgs(cmd *cobra.Command, args []string) error { given, _ := cmd.Flags().GetBool("latest") if len(args) > 0 && !given { diff --git a/cmd/podman/images/build.go b/cmd/podman/images/build.go index 3ea60e18a..940ea6e42 100644 --- a/cmd/podman/images/build.go +++ b/cmd/podman/images/build.go @@ -197,9 +197,8 @@ func buildFlags(cmd *cobra.Command) { // build executes the build command. func build(cmd *cobra.Command, args []string) error { if (cmd.Flags().Changed("squash") && cmd.Flags().Changed("layers")) || - (cmd.Flags().Changed("squash-all") && cmd.Flags().Changed("layers")) || (cmd.Flags().Changed("squash-all") && cmd.Flags().Changed("squash")) { - return errors.New("cannot specify --squash, --squash-all and --layers options together") + return errors.New("cannot specify --squash with --layers and --squash-all with --squash") } if cmd.Flag("output").Changed && registry.IsRemote() { @@ -418,7 +417,13 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil // Squash-all invoked, squash both new and old layers into one. if c.Flags().Changed("squash-all") { flags.Squash = true - flags.Layers = false + if !c.Flags().Changed("layers") { + // Buildah supports using layers and --squash together + // after https://github.com/containers/buildah/pull/3674 + // so podman must honor if user wants to still use layers + // with --squash-all. + flags.Layers = false + } } var stdin io.Reader @@ -442,22 +447,6 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil return nil, err } - // `buildah bud --layers=false` acts like `docker build --squash` does. - // That is all of the new layers created during the build process are - // condensed into one, any layers present prior to this build are retained - // without condensing. `buildah bud --squash` squashes both new and old - // layers down into one. Translate Podman commands into Buildah. - // Squash invoked, retain old layers, squash new layers into one. - if c.Flags().Changed("squash") && flags.Squash { - flags.Squash = false - flags.Layers = false - } - // Squash-all invoked, squash both new and old layers into one. - if c.Flags().Changed("squash-all") { - flags.Squash = true - flags.Layers = false - } - compression := buildahDefine.Gzip if flags.DisableCompression { compression = buildahDefine.Uncompressed @@ -513,9 +502,26 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil return nil, errors.Wrapf(err, "unable to obtain decrypt config") } + additionalBuildContext := make(map[string]*buildahDefine.AdditionalBuildContext) + if c.Flag("build-context").Changed { + for _, contextString := range flags.BuildContext { + av := strings.SplitN(contextString, "=", 2) + if len(av) > 1 { + parseAdditionalBuildContext, err := parse.GetAdditionalBuildContext(av[1]) + if err != nil { + return nil, errors.Wrapf(err, "while parsing additional build context") + } + additionalBuildContext[av[0]] = &parseAdditionalBuildContext + } else { + return nil, fmt.Errorf("while parsing additional build context: %q, accepts value in the form of key=value", av) + } + } + } + opts := buildahDefine.BuildOptions{ AddCapabilities: flags.CapAdd, AdditionalTags: tags, + AdditionalBuildContexts: additionalBuildContext, AllPlatforms: flags.AllPlatforms, Annotations: flags.Annotation, Args: args, @@ -525,6 +531,7 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil Compression: compression, ConfigureNetwork: networkPolicy, ContextDirectory: contextDir, + CPPFlags: flags.CPPFlags, DefaultMountsFilePath: containerConfig.Containers.DefaultMountsFile, Devices: flags.Devices, DropCapabilities: flags.CapDrop, diff --git a/cmd/podman/images/diff.go b/cmd/podman/images/diff.go index 13a8f1d9d..a017d569d 100644 --- a/cmd/podman/images/diff.go +++ b/cmd/podman/images/diff.go @@ -34,9 +34,7 @@ func init() { } func diffFlags(flags *pflag.FlagSet) { - diffOpts = &entities.DiffOptions{} - flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive") - _ = flags.MarkDeprecated("archive", "Provided for backwards compatibility, has no impact on output.") + diffOpts = new(entities.DiffOptions) formatFlagName := "format" flags.StringVar(&diffOpts.Format, formatFlagName, "", "Change the output format (json)") diff --git a/cmd/podman/machine/init.go b/cmd/podman/machine/init.go index 6c31f3531..612c36057 100644 --- a/cmd/podman/machine/init.go +++ b/cmd/podman/machine/init.go @@ -25,16 +25,14 @@ var ( Example: `podman machine init myvm`, ValidArgsFunction: completion.AutocompleteNone, } -) -var ( initOpts = machine.InitOptions{} defaultMachineName = machine.DefaultMachineName now bool ) // maxMachineNameSize is set to thirty to limit huge machine names primarily -// because macos has a much smaller file size limit. +// because macOS has a much smaller file size limit. const maxMachineNameSize = 30 func init() { @@ -111,8 +109,7 @@ func init() { flags.BoolVar(&initOpts.Rootful, rootfulFlagName, false, "Whether this machine should prefer rootful container execution") } -// TODO should we allow for a users to append to the qemu cmdline? -func initMachine(cmd *cobra.Command, args []string) error { +func initMachine(_ *cobra.Command, args []string) error { var ( err error vm machine.VM @@ -122,7 +119,7 @@ func initMachine(cmd *cobra.Command, args []string) error { initOpts.Name = defaultMachineName if len(args) > 0 { if len(args[0]) > maxMachineNameSize { - return errors.New("machine name must be 30 characters or less") + return errors.Errorf("machine name %q must be %d characters or less", args[0], maxMachineNameSize) } initOpts.Name = args[0] } diff --git a/cmd/podman/machine/ssh.go b/cmd/podman/machine/ssh.go index 4a86da67a..8261f3607 100644 --- a/cmd/podman/machine/ssh.go +++ b/cmd/podman/machine/ssh.go @@ -9,6 +9,7 @@ import ( "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/pkg/machine" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -89,7 +90,8 @@ func ssh(cmd *cobra.Command, args []string) error { if err != nil { return errors.Wrapf(err, "vm %s not found", vmName) } - return vm.SSH(vmName, sshOpts) + err = vm.SSH(vmName, sshOpts) + return utils.HandleOSExecError(err) } func remoteConnectionUsername() (string, error) { diff --git a/cmd/podman/networks/reload.go b/cmd/podman/networks/reload.go index 7b6323187..66248e9fb 100644 --- a/cmd/podman/networks/reload.go +++ b/cmd/podman/networks/reload.go @@ -21,7 +21,7 @@ var ( Long: networkReloadDescription, RunE: networkReload, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompleteContainers, Example: `podman network reload --latest diff --git a/cmd/podman/parse/net.go b/cmd/podman/parse/net.go index 870690db3..b616e1029 100644 --- a/cmd/podman/parse/net.go +++ b/cmd/podman/parse/net.go @@ -18,6 +18,8 @@ import ( const ( Protocol_TCP Protocol = 0 Protocol_UDP Protocol = 1 + LabelType string = "label" + ENVType string = "env" ) type Protocol int32 @@ -89,9 +91,7 @@ func GetAllLabels(labelFile, inputLabels []string) (map[string]string, error) { // There's an argument that we SHOULD be doing that parsing for // all environment variables, even those sourced from files, but // that would require a substantial rework. - if err := parseEnvFile(labels, file); err != nil { - // FIXME: parseEnvFile is using parseEnv, so we need to add extra - // logic for labels. + if err := parseEnvOrLabelFile(labels, file, LabelType); err != nil { return nil, err } } @@ -109,7 +109,7 @@ func GetAllLabels(labelFile, inputLabels []string) (map[string]string, error) { return labels, nil } -func parseEnv(env map[string]string, line string) error { +func parseEnvOrLabel(env map[string]string, line, configType string) error { data := strings.SplitN(line, "=", 2) // catch invalid variables such as "=" or "=A" @@ -137,7 +137,7 @@ func parseEnv(env map[string]string, line string) error { env[part[0]] = part[1] } } - } else { + } else if configType == ENVType { // if only a pass-through variable is given, clean it up. if val, ok := os.LookupEnv(name); ok { env[name] = val @@ -147,8 +147,9 @@ func parseEnv(env map[string]string, line string) error { return nil } -// parseEnvFile reads a file with environment variables enumerated by lines -func parseEnvFile(env map[string]string, filename string) error { +// parseEnvOrLabelFile reads a file with environment variables enumerated by lines +// configType should be set to either "label" or "env" based on what type is being parsed +func parseEnvOrLabelFile(envOrLabel map[string]string, filename, configType string) error { fh, err := os.Open(filename) if err != nil { return err @@ -161,7 +162,7 @@ func parseEnvFile(env map[string]string, filename string) error { line := strings.TrimLeft(scanner.Text(), whiteSpaces) // line is not empty, and not starting with '#' if len(line) > 0 && !strings.HasPrefix(line, "#") { - if err := parseEnv(env, line); err != nil { + if err := parseEnvOrLabel(envOrLabel, line, configType); err != nil { return err } } diff --git a/cmd/podman/pods/kill.go b/cmd/podman/pods/kill.go index 7216e08bb..5d3b15dc3 100644 --- a/cmd/podman/pods/kill.go +++ b/cmd/podman/pods/kill.go @@ -22,7 +22,7 @@ var ( Long: podKillDescription, RunE: kill, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompletePodsRunning, Example: `podman pod kill podID diff --git a/cmd/podman/pods/pause.go b/cmd/podman/pods/pause.go index adc54d171..389fb8415 100644 --- a/cmd/podman/pods/pause.go +++ b/cmd/podman/pods/pause.go @@ -22,7 +22,7 @@ var ( Long: podPauseDescription, RunE: pause, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompletePodsRunning, Example: `podman pod pause podID1 podID2 diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go index a89448275..aa42e1983 100644 --- a/cmd/podman/pods/ps.go +++ b/cmd/podman/pods/ps.go @@ -49,7 +49,6 @@ func init() { flags.BoolVar(&psInput.CtrNames, "ctr-names", false, "Display the container names") flags.BoolVar(&psInput.CtrIds, "ctr-ids", false, "Display the container UUIDs. If no-trunc is not set they will be truncated") flags.BoolVar(&psInput.CtrStatus, "ctr-status", false, "Display the container status") - // TODO should we make this a [] ? filterFlagName := "filter" flags.StringSliceVarP(&inputFilters, filterFlagName, "f", []string{}, "Filter output based on conditions given") diff --git a/cmd/podman/pods/restart.go b/cmd/podman/pods/restart.go index 6d624806a..a8e31ce07 100644 --- a/cmd/podman/pods/restart.go +++ b/cmd/podman/pods/restart.go @@ -22,7 +22,7 @@ var ( Long: podRestartDescription, RunE: restart, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompletePods, Example: `podman pod restart podID1 podID2 diff --git a/cmd/podman/pods/rm.go b/cmd/podman/pods/rm.go index 52a815534..16b7191c9 100644 --- a/cmd/podman/pods/rm.go +++ b/cmd/podman/pods/rm.go @@ -35,7 +35,7 @@ var ( Long: podRmDescription, RunE: rm, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndPodIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "pod-id-file") }, ValidArgsFunction: common.AutocompletePods, Example: `podman pod rm mywebserverpod diff --git a/cmd/podman/pods/start.go b/cmd/podman/pods/start.go index b668cdd61..9436d34a5 100644 --- a/cmd/podman/pods/start.go +++ b/cmd/podman/pods/start.go @@ -31,7 +31,7 @@ var ( Long: podStartDescription, RunE: start, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndPodIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "pod-id-file") }, ValidArgsFunction: common.AutocompletePods, Example: `podman pod start podID diff --git a/cmd/podman/pods/stop.go b/cmd/podman/pods/stop.go index c8c3d2732..e8f82bee9 100644 --- a/cmd/podman/pods/stop.go +++ b/cmd/podman/pods/stop.go @@ -36,7 +36,7 @@ var ( Long: podStopDescription, RunE: stop, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndPodIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "pod-id-file") }, ValidArgsFunction: common.AutocompletePodsRunning, Example: `podman pod stop mywebserverpod diff --git a/cmd/podman/pods/unpause.go b/cmd/podman/pods/unpause.go index a308a82c3..8a0a24e98 100644 --- a/cmd/podman/pods/unpause.go +++ b/cmd/podman/pods/unpause.go @@ -22,7 +22,7 @@ var ( Long: podUnpauseDescription, RunE: unpause, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, // TODO have a function which shows only pods which could be unpaused // for now show all diff --git a/cmd/podman/system/unshare.go b/cmd/podman/system/unshare.go index 0ae5b81ad..1ed08eac3 100644 --- a/cmd/podman/system/unshare.go +++ b/cmd/podman/system/unshare.go @@ -2,10 +2,10 @@ package system import ( "os" - "os/exec" "github.com/containers/common/pkg/completion" "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/rootless" "github.com/pkg/errors" @@ -60,22 +60,5 @@ func unshare(cmd *cobra.Command, args []string) error { } err := registry.ContainerEngine().Unshare(registry.Context(), args, unshareOptions) - if err != nil { - if exitError, ok := err.(*exec.ExitError); ok { - // the user command inside the unshare env has failed - // we set the exit code, do not return the error to the user - // otherwise "exit status X" will be printed - registry.SetExitCode(exitError.ExitCode()) - return nil - } - // cmd.Run() can return fs.ErrNotExist, fs.ErrPermission or exec.ErrNotFound - // follow podman run/exec standard with the exit codes - if errors.Is(err, os.ErrNotExist) || errors.Is(err, exec.ErrNotFound) { - registry.SetExitCode(127) - } else if errors.Is(err, os.ErrPermission) { - registry.SetExitCode(126) - } - return err - } - return nil + return utils.HandleOSExecError(err) } diff --git a/cmd/podman/utils/error.go b/cmd/podman/utils/error.go index 2aaa71373..3efff0301 100644 --- a/cmd/podman/utils/error.go +++ b/cmd/podman/utils/error.go @@ -4,10 +4,12 @@ import ( "errors" "fmt" "os" + "os/exec" "strconv" "strings" buildahCLI "github.com/containers/buildah/pkg/cli" + "github.com/containers/podman/v4/cmd/podman/registry" ) type OutputErrors []error @@ -43,3 +45,33 @@ func ExitCodeFromBuildError(errorMsg string) (int, error) { } return buildahCLI.ExecErrorCodeGeneric, errors.New("message does not contains a valid exit code") } + +// HandleOSExecError checks the given error for an exec.ExitError error and +// sets the same podman exit code as the error. +// No error will be returned in this case to make sure things like podman +// unshare false work correctly without extra output. +// When the exec file does not exists we set the exit code to 127, for +// permission errors 126 is used as exit code. In this case we still return +// the error so the user gets an error message. +// If the error is nil it returns nil. +func HandleOSExecError(err error) error { + if err == nil { + return nil + } + var exitError *exec.ExitError + if errors.As(err, &exitError) { + // the user command inside the unshare/ssh env has failed + // we set the exit code, do not return the error to the user + // otherwise "exit status X" will be printed + registry.SetExitCode(exitError.ExitCode()) + return nil + } + // cmd.Run() can return fs.ErrNotExist, fs.ErrPermission or exec.ErrNotFound + // follow podman run/exec standard with the exit codes + if errors.Is(err, os.ErrNotExist) || errors.Is(err, exec.ErrNotFound) { + registry.SetExitCode(127) + } else if errors.Is(err, os.ErrPermission) { + registry.SetExitCode(126) + } + return err +} diff --git a/cmd/podman/utils/signals_linux.go b/cmd/podman/utils/signals_linux.go deleted file mode 100644 index dd0507c0e..000000000 --- a/cmd/podman/utils/signals_linux.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !windows -// +build !windows - -package utils - -import ( - "os" - - "golang.org/x/sys/unix" -) - -// Platform specific signal synonyms -var ( - SIGHUP os.Signal = unix.SIGHUP -) diff --git a/cmd/podman/utils/signals_windows.go b/cmd/podman/utils/signals_windows.go deleted file mode 100644 index e6fcc1b32..000000000 --- a/cmd/podman/utils/signals_windows.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build windows -// +build windows - -package utils - -import ( - "os" - - "golang.org/x/sys/windows" -) - -// Platform specific signal synonyms -var ( - SIGHUP os.Signal = windows.SIGHUP -) diff --git a/cmd/podman/validate/args.go b/cmd/podman/validate/args.go index 669456bd3..b9b468d34 100644 --- a/cmd/podman/validate/args.go +++ b/cmd/podman/validate/args.go @@ -50,89 +50,44 @@ func IDOrLatestArgs(cmd *cobra.Command, args []string) error { return nil } -// TODO: the two functions CheckAllLatestAndCIDFile and CheckAllLatestAndPodIDFile are almost identical. -// It may be worth looking into generalizing the two a bit more and share code but time is scarce and -// we only live once. - -// CheckAllLatestAndCIDFile checks that --all and --latest are used correctly. -// If cidfile is set, also check for the --cidfile flag. +// CheckAllLatestAndCIDFile checks that --all and --latest are used correctly for containers and pods +// If idFileFlag is set is set, also checks for the --cidfile or --pod-id-file flag. +// Note: this has been deprecated, use CheckAllLatestAndIDFile instead func CheckAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool, cidfile bool) error { - var specifiedLatest bool - argLen := len(args) - if !registry.IsRemote() { - specifiedLatest, _ = c.Flags().GetBool("latest") - if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { - if !cidfile { - return errors.New("unable to lookup values for 'latest' or 'all'") - } else if c.Flags().Lookup("cidfile") == nil { - return errors.New("unable to lookup values for 'latest', 'all' or 'cidfile'") - } - } - } - - specifiedAll, _ := c.Flags().GetBool("all") - specifiedCIDFile := false - if cid, _ := c.Flags().GetStringArray("cidfile"); len(cid) > 0 { - specifiedCIDFile = true - } - - if specifiedCIDFile && (specifiedAll || specifiedLatest) { - return errors.Errorf("--all, --latest and --cidfile cannot be used together") - } else if specifiedAll && specifiedLatest { - return errors.Errorf("--all and --latest cannot be used together") - } - - if (argLen > 0) && specifiedAll { - return errors.Errorf("no arguments are needed with --all") - } - - if ignoreArgLen { - return nil - } - - if argLen > 0 { - if specifiedLatest { - return errors.Errorf("--latest and containers cannot be used together") - } else if cidfile && (specifiedLatest || specifiedCIDFile) { - return errors.Errorf("no arguments are needed with --latest or --cidfile") - } - } - - if specifiedCIDFile { - return nil - } - - if argLen < 1 && !specifiedAll && !specifiedLatest && !specifiedCIDFile { - return errors.Errorf("you must provide at least one name or id") - } - return nil + return CheckAllLatestAndIDFile(c, args, ignoreArgLen, "cidfile") } // CheckAllLatestAndPodIDFile checks that --all and --latest are used correctly. // If withIDFile is set, also check for the --pod-id-file flag. +// Note: this has been deprecated, use CheckAllLatestAndIDFile instead func CheckAllLatestAndPodIDFile(c *cobra.Command, args []string, ignoreArgLen bool, withIDFile bool) error { + return CheckAllLatestAndIDFile(c, args, ignoreArgLen, "pod-id-file") +} + +// CheckAllLatestAndIDFile checks that --all and --latest are used correctly for containers and pods +// If idFileFlag is set is set, also checks for the --cidfile or --pod-id-file flag. +func CheckAllLatestAndIDFile(c *cobra.Command, args []string, ignoreArgLen bool, idFileFlag string) error { var specifiedLatest bool argLen := len(args) if !registry.IsRemote() { - // remote clients have no latest flag specifiedLatest, _ = c.Flags().GetBool("latest") if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { - if !withIDFile { + if idFileFlag == "" { return errors.New("unable to lookup values for 'latest' or 'all'") - } else if c.Flags().Lookup("pod-id-file") == nil { - return errors.New("unable to lookup values for 'latest', 'all' or 'pod-id-file'") + } else if c.Flags().Lookup(idFileFlag) == nil { + return errors.Errorf("unable to lookup values for 'latest', 'all', or '%s'", idFileFlag) } } } specifiedAll, _ := c.Flags().GetBool("all") - specifiedPodIDFile := false - if pid, _ := c.Flags().GetStringArray("pod-id-file"); len(pid) > 0 { - specifiedPodIDFile = true + specifiedIDFile := false + if cid, _ := c.Flags().GetStringArray(idFileFlag); len(cid) > 0 { + specifiedIDFile = true } - if specifiedPodIDFile && (specifiedAll || specifiedLatest) { - return errors.Errorf("--all, --latest and --pod-id-file cannot be used together") + if specifiedIDFile && (specifiedAll || specifiedLatest) { + return errors.Errorf("--all, --latest, and --%s cannot be used together", idFileFlag) } else if specifiedAll && specifiedLatest { return errors.Errorf("--all and --latest cannot be used together") } @@ -147,17 +102,17 @@ func CheckAllLatestAndPodIDFile(c *cobra.Command, args []string, ignoreArgLen bo if argLen > 0 { if specifiedLatest { - return errors.Errorf("--latest and pods cannot be used together") - } else if withIDFile && (specifiedLatest || specifiedPodIDFile) { - return errors.Errorf("no arguments are needed with --latest or --pod-id-file") + return errors.Errorf("--latest and containers cannot be used together") + } else if idFileFlag != "" && (specifiedLatest || specifiedIDFile) { + return errors.Errorf("no arguments are needed with --latest or --%s", idFileFlag) } } - if specifiedPodIDFile { + if specifiedIDFile { return nil } - if argLen < 1 && !specifiedAll && !specifiedLatest && !specifiedPodIDFile { + if argLen < 1 && !specifiedAll && !specifiedLatest && !specifiedIDFile { return errors.Errorf("you must provide at least one name or id") } return nil diff --git a/cmd/podman/volumes/export.go b/cmd/podman/volumes/export.go index 5086323f9..113f79a0b 100644 --- a/cmd/podman/volumes/export.go +++ b/cmd/podman/volumes/export.go @@ -8,6 +8,7 @@ import ( "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" + "github.com/containers/podman/v4/pkg/errorhandling" "github.com/containers/podman/v4/utils" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -58,10 +59,13 @@ func export(cmd *cobra.Command, args []string) error { return errors.New("expects output path, use --output=[path]") } inspectOpts.Type = common.VolumeType - volumeData, _, err := containerEngine.VolumeInspect(ctx, args, inspectOpts) + volumeData, errs, err := containerEngine.VolumeInspect(ctx, args, inspectOpts) if err != nil { return err } + if len(errs) > 0 { + return errorhandling.JoinErrors(errs) + } if len(volumeData) < 1 { return errors.New("no volume data found") } diff --git a/cmd/podman/volumes/import.go b/cmd/podman/volumes/import.go index 988c5536d..76a311643 100644 --- a/cmd/podman/volumes/import.go +++ b/cmd/podman/volumes/import.go @@ -8,6 +8,7 @@ import ( "github.com/containers/podman/v4/cmd/podman/parse" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" + "github.com/containers/podman/v4/pkg/errorhandling" "github.com/containers/podman/v4/utils" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -60,10 +61,14 @@ func importVol(cmd *cobra.Command, args []string) error { } inspectOpts.Type = common.VolumeType - volumeData, _, err := containerEngine.VolumeInspect(ctx, volumes, inspectOpts) + inspectOpts.Type = common.VolumeType + volumeData, errs, err := containerEngine.VolumeInspect(ctx, volumes, inspectOpts) if err != nil { return err } + if len(errs) > 0 { + return errorhandling.JoinErrors(errs) + } if len(volumeData) < 1 { return errors.New("no volume data found") } |