diff options
146 files changed, 1519 insertions, 837 deletions
@@ -478,6 +478,7 @@ podman-remote-release-%.zip: cp release.txt "$(TMPDIR)/" cp ./bin/podman-remote-$*$(BINSFX) "$(TMPDIR)/$(SUBDIR)/podman$(BINSFX)" cp -r ./docs/build/remote/$* "$(TMPDIR)/$(SUBDIR)/docs/" + cp ./contrib/remote/containers.conf "$(TMPDIR)/$(SUBDIR)/" cd "$(TMPDIR)/$(SUBDIR)" && \ zip --recurse-paths "$(CURDIR)/$@" "./release.txt" "./" -rm -rf "$(TMPDIR)" diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index fbb7f449e..ec3251ae1 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -57,7 +57,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { createFlags.StringVar( &cf.CGroupsMode, "cgroups", containerConfig.Cgroups(), - `control container cgroup configuration ("enabled"|"disabled"|"no-conmon")`, + `control container cgroup configuration ("enabled"|"disabled"|"no-conmon"|"split")`, ) createFlags.StringVar( &cf.CGroupParent, diff --git a/cmd/podman/common/util.go b/cmd/podman/common/util.go index ce323a4ba..6c8c22147 100644 --- a/cmd/podman/common/util.go +++ b/cmd/podman/common/util.go @@ -184,22 +184,24 @@ func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string) } if hostPort != nil { if *hostPort == "" { - return newPort, errors.Errorf("must provide a non-empty container host port to publish") - } - hostStart, hostLen, err := parseAndValidateRange(*hostPort) - if err != nil { - return newPort, errors.Wrapf(err, "error parsing host port") - } - if hostLen != ctrLen { - return newPort, errors.Errorf("host and container port ranges have different lengths: %d vs %d", hostLen, ctrLen) + // Set 0 as a placeholder. The server side of Specgen + // will find a random, open, unused port to use. + newPort.HostPort = 0 + } else { + hostStart, hostLen, err := parseAndValidateRange(*hostPort) + if err != nil { + return newPort, errors.Wrapf(err, "error parsing host port") + } + if hostLen != ctrLen { + return newPort, errors.Errorf("host and container port ranges have different lengths: %d vs %d", hostLen, ctrLen) + } + newPort.HostPort = hostStart } - newPort.HostPort = hostStart + } else { + newPort.HostPort = newPort.ContainerPort } hport := newPort.HostPort - if hport == 0 { - hport = newPort.ContainerPort - } logrus.Debugf("Adding port mapping from %d to %d length %d protocol %q", hport, newPort.ContainerPort, newPort.Range, newPort.Protocol) return newPort, nil diff --git a/cmd/podman/containers/attach.go b/cmd/podman/containers/attach.go index 9ef9d79f0..cb3b1bd3c 100644 --- a/cmd/podman/containers/attach.go +++ b/cmd/podman/containers/attach.go @@ -44,10 +44,6 @@ func attachFlags(flags *pflag.FlagSet) { flags.StringVar(&attachOpts.DetachKeys, "detach-keys", containerConfig.DetachKeys(), "Select the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`") flags.BoolVar(&attachOpts.NoStdin, "no-stdin", false, "Do not attach STDIN. The default is false") flags.BoolVar(&attachOpts.SigProxy, "sig-proxy", true, "Proxy received signals to the process") - flags.BoolVarP(&attachOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } } func init() { @@ -55,22 +51,24 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: attachCommand, }) - flags := attachCommand.Flags() - attachFlags(flags) + attachFlags(attachCommand.Flags()) + validate.AddLatestFlag(attachCommand, &attachOpts.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: containerAttachCommand, Parent: containerCmd, }) - containerAttachFlags := containerAttachCommand.Flags() - attachFlags(containerAttachFlags) + attachFlags(containerAttachCommand.Flags()) + validate.AddLatestFlag(containerAttachCommand, &attachOpts.Latest) + } func attach(cmd *cobra.Command, args []string) error { if len(args) > 1 || (len(args) == 0 && !attachOpts.Latest) { return errors.Errorf("attach requires the name or id of one running container or the latest flag") } + var name string if len(args) > 0 { name = args[0] diff --git a/cmd/podman/containers/checkpoint.go b/cmd/podman/containers/checkpoint.go index c4723af21..fa1bc899b 100644 --- a/cmd/podman/containers/checkpoint.go +++ b/cmd/podman/containers/checkpoint.go @@ -4,9 +4,9 @@ import ( "context" "fmt" - "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" @@ -25,7 +25,7 @@ var ( Long: checkpointDescription, RunE: checkpoint, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, Example: `podman container checkpoint --keep ctrID podman container checkpoint --all @@ -48,12 +48,9 @@ func init() { flags.BoolVarP(&checkpointOptions.LeaveRunning, "leave-running", "R", false, "Leave the container running after writing checkpoint to disk") flags.BoolVar(&checkpointOptions.TCPEstablished, "tcp-established", false, "Checkpoint a container with established TCP connections") flags.BoolVarP(&checkpointOptions.All, "all", "a", false, "Checkpoint all running containers") - flags.BoolVarP(&checkpointOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") flags.StringVarP(&checkpointOptions.Export, "export", "e", "", "Export the checkpoint image to a tar.gz") flags.BoolVar(&checkpointOptions.IgnoreRootFS, "ignore-rootfs", false, "Do not include root file-system changes when exporting") - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } + validate.AddLatestFlag(checkpointCommand, &checkpointOptions.Latest) } func checkpoint(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/cleanup.go b/cmd/podman/containers/cleanup.go index 619031208..7e9e7e9ef 100644 --- a/cmd/podman/containers/cleanup.go +++ b/cmd/podman/containers/cleanup.go @@ -3,9 +3,9 @@ package containers import ( "fmt" - "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -24,7 +24,7 @@ var ( Long: cleanupDescription, RunE: cleanup, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, Example: `podman container cleanup --latest podman container cleanup ctrID1 ctrID2 ctrID3 @@ -44,11 +44,10 @@ func init() { }) flags := cleanupCommand.Flags() flags.BoolVarP(&cleanupOptions.All, "all", "a", false, "Cleans up all containers") - flags.BoolVarP(&cleanupOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") flags.StringVar(&cleanupOptions.Exec, "exec", "", "Clean up the given exec session instead of the container") flags.BoolVar(&cleanupOptions.Remove, "rm", false, "After cleanup, remove the container entirely") flags.BoolVar(&cleanupOptions.RemoveImage, "rmi", false, "After cleanup, remove the image entirely") - + validate.AddLatestFlag(cleanupCommand, &cleanupOptions.Latest) } func cleanup(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index 45ce00c86..60e9aa815 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -6,11 +6,12 @@ import ( "os" "strings" - "github.com/containers/libpod/libpod/define" - "github.com/containers/common/pkg/config" + "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/transports/alltransports" "github.com/containers/libpod/cmd/podman/common" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/errorhandling" "github.com/containers/libpod/pkg/specgen" @@ -57,6 +58,7 @@ func createFlags(flags *pflag.FlagSet) { flags.AddFlagSet(common.GetCreateFlags(&cliVals)) flags.AddFlagSet(common.GetNetFlags()) flags.SetNormalizeFunc(common.AliasFlags) + if registry.IsRemote() { _ = flags.MarkHidden("authfile") _ = flags.MarkHidden("env-host") @@ -108,12 +110,15 @@ func create(cmd *cobra.Command, args []string) error { return err } + imageName := args[0] if !cliVals.RootFS { - if err := pullImage(args[0]); err != nil { + name, err := pullImage(args[0]) + if err != nil { return err } + imageName = name } - s := specgen.NewSpecGenerator(args[0], cliVals.RootFS) + s := specgen.NewSpecGenerator(imageName, cliVals.RootFS) if err := common.FillOutSpecGen(s, &cliVals, args); err != nil { return err } @@ -205,36 +210,54 @@ func createInit(c *cobra.Command) error { cliVals.Env = env } + if c.Flag("cgroups").Changed && cliVals.CGroupsMode == "split" && registry.IsRemote() { + return errors.Errorf("the option --cgroups=%q is not supported in remote mode", cliVals.CGroupsMode) + } + // Docker-compatibility: the "-h" flag for run/create is reserved for // the hostname (see https://github.com/containers/libpod/issues/1367). return nil } -func pullImage(imageName string) error { - br, err := registry.ImageEngine().Exists(registry.GetContext(), imageName) - if err != nil { - return err - } +func pullImage(imageName string) (string, error) { pullPolicy, err := config.ValidatePullPolicy(cliVals.Pull) if err != nil { - return err + return "", err } - if !br.Value || pullPolicy == config.PullImageAlways { + + // Check if the image is missing and hence if we need to pull it. + imageMissing := true + imageRef, err := alltransports.ParseImageName(imageName) + switch { + case err != nil: + // Assume we specified a local image withouth the explicit storage transport. + fallthrough + + case imageRef.Transport().Name() == storage.Transport.Name(): + br, err := registry.ImageEngine().Exists(registry.GetContext(), imageName) + if err != nil { + return "", err + } + imageMissing = !br.Value + } + + if imageMissing || pullPolicy == config.PullImageAlways { if pullPolicy == config.PullImageNever { - return errors.Wrapf(define.ErrNoSuchImage, "unable to find a name and tag match for %s in repotags", imageName) + return "", errors.Wrapf(define.ErrNoSuchImage, "unable to find a name and tag match for %s in repotags", imageName) } - _, pullErr := registry.ImageEngine().Pull(registry.GetContext(), imageName, entities.ImagePullOptions{ + pullReport, pullErr := registry.ImageEngine().Pull(registry.GetContext(), imageName, entities.ImagePullOptions{ Authfile: cliVals.Authfile, Quiet: cliVals.Quiet, OverrideArch: cliVals.OverrideArch, OverrideOS: cliVals.OverrideOS, }) if pullErr != nil { - return pullErr + return "", pullErr } + imageName = pullReport.Images[0] } - return nil + return imageName, nil } func openCidFile(cidfile string) (*os.File, error) { diff --git a/cmd/podman/containers/diff.go b/cmd/podman/containers/diff.go index 33b1c1126..812a61a78 100644 --- a/cmd/podman/containers/diff.go +++ b/cmd/podman/containers/diff.go @@ -14,8 +14,8 @@ var ( diffCmd = &cobra.Command{ Use: "diff [flags] CONTAINER", Args: validate.IDOrLatestArgs, - Short: "Inspect changes on container's file systems", - Long: `Displays changes on a container filesystem. The container will be compared to its parent layer.`, + Short: "Inspect changes to the container's file systems", + Long: `Displays changes to the container filesystem's'. The container will be compared to its parent layer.`, RunE: diff, Example: `podman container diff myCtr podman container diff -l --format json myCtr`, @@ -35,10 +35,7 @@ func init() { flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive") _ = flags.MarkHidden("archive") flags.StringVar(&diffOpts.Format, "format", "", "Change the output format") - - if !registry.IsRemote() { - flags.BoolVarP(&diffOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - } + validate.AddLatestFlag(diffCmd, &diffOpts.Latest) } func diff(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/exec.go b/cmd/podman/containers/exec.go index ce48af618..3c713a7c4 100644 --- a/cmd/podman/containers/exec.go +++ b/cmd/podman/containers/exec.go @@ -6,6 +6,7 @@ import ( "os" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/domain/entities" envLib "github.com/containers/libpod/pkg/env" @@ -53,14 +54,13 @@ func execFlags(flags *pflag.FlagSet) { flags.StringArrayVarP(&envInput, "env", "e", []string{}, "Set environment variables") flags.StringSliceVar(&envFile, "env-file", []string{}, "Read in a file of environment variables") flags.BoolVarP(&execOpts.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached") - flags.BoolVarP(&execOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") flags.BoolVar(&execOpts.Privileged, "privileged", false, "Give the process extended Linux capabilities inside the container. The default is false") flags.BoolVarP(&execOpts.Tty, "tty", "t", false, "Allocate a pseudo-TTY. The default is false") flags.StringVarP(&execOpts.User, "user", "u", "", "Sets the username or UID used and optionally the groupname or GID for the specified command") flags.UintVar(&execOpts.PreserveFDs, "preserve-fds", 0, "Pass N additional file descriptors to the container") flags.StringVarP(&execOpts.WorkDir, "workdir", "w", "", "Working directory inside the container") + if registry.IsRemote() { - _ = flags.MarkHidden("latest") _ = flags.MarkHidden("preserve-fds") } } @@ -70,20 +70,19 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: execCommand, }) - flags := execCommand.Flags() - execFlags(flags) + execFlags(execCommand.Flags()) + validate.AddLatestFlag(execCommand, &execOpts.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: containerExecCommand, Parent: containerCmd, }) - - containerExecFlags := containerExecCommand.Flags() - execFlags(containerExecFlags) + execFlags(containerExecCommand.Flags()) + validate.AddLatestFlag(containerExecCommand, &execOpts.Latest) } -func exec(cmd *cobra.Command, args []string) error { +func exec(_ *cobra.Command, args []string) error { var nameOrID string if len(args) == 0 && !execOpts.Latest { diff --git a/cmd/podman/containers/init.go b/cmd/podman/containers/init.go index 417f170c3..6ae52986c 100644 --- a/cmd/podman/containers/init.go +++ b/cmd/podman/containers/init.go @@ -3,9 +3,9 @@ package containers import ( "fmt" - "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -20,7 +20,7 @@ var ( Long: initDescription, RunE: initContainer, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, Example: `podman init --latest podman init 3c45ef19d893 @@ -45,10 +45,6 @@ var ( func initFlags(flags *pflag.FlagSet) { flags.BoolVarP(&initOptions.All, "all", "a", false, "Initialize all containers") - flags.BoolVarP(&initOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } } func init() { @@ -58,15 +54,16 @@ func init() { }) flags := initCommand.Flags() initFlags(flags) + validate.AddLatestFlag(initCommand, &initOptions.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Parent: containerCmd, Command: containerInitCommand, }) - containerInitFlags := containerInitCommand.Flags() initFlags(containerInitFlags) + validate.AddLatestFlag(containerInitCommand, &initOptions.Latest) } func initContainer(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/inspect.go b/cmd/podman/containers/inspect.go index e49fcc2e0..1798fa99c 100644 --- a/cmd/podman/containers/inspect.go +++ b/cmd/podman/containers/inspect.go @@ -3,6 +3,7 @@ package containers import ( "github.com/containers/libpod/cmd/podman/inspect" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -30,7 +31,7 @@ func init() { flags := inspectCmd.Flags() flags.BoolVarP(&inspectOpts.Size, "size", "s", false, "Display total file size") flags.StringVarP(&inspectOpts.Format, "format", "f", "json", "Format the output to a Go template or json") - flags.BoolVarP(&inspectOpts.Latest, "latest", "l", false, "Act on the latest container Podman is aware of") + validate.AddLatestFlag(inspectCmd, &inspectOpts.Latest) } func inspectExec(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/kill.go b/cmd/podman/containers/kill.go index ef85aad7d..5289a2a0e 100644 --- a/cmd/podman/containers/kill.go +++ b/cmd/podman/containers/kill.go @@ -5,9 +5,9 @@ import ( "errors" "fmt" - "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/signal" "github.com/spf13/cobra" @@ -22,7 +22,7 @@ var ( Long: killDescription, RunE: kill, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, Example: `podman kill mywebserver podman kill 860a4b23 @@ -31,7 +31,7 @@ var ( containerKillCommand = &cobra.Command{ Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, Use: killCommand.Use, Short: killCommand.Short, @@ -50,10 +50,6 @@ var ( func killFlags(flags *pflag.FlagSet) { flags.BoolVarP(&killOptions.All, "all", "a", false, "Signal all running containers") flags.StringVarP(&killOptions.Signal, "signal", "s", "KILL", "Signal to send to the container") - flags.BoolVarP(&killOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } } func init() { @@ -61,20 +57,19 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: killCommand, }) - flags := killCommand.Flags() - killFlags(flags) + killFlags(killCommand.Flags()) + validate.AddLatestFlag(killCommand, &killOptions.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: containerKillCommand, Parent: containerCmd, }) - - containerKillFlags := containerKillCommand.Flags() - killFlags(containerKillFlags) + killFlags(containerKillCommand.Flags()) + validate.AddLatestFlag(containerKillCommand, &killOptions.Latest) } -func kill(cmd *cobra.Command, args []string) error { +func kill(_ *cobra.Command, args []string) error { var ( err error errs utils.OutputErrors diff --git a/cmd/podman/containers/list.go b/cmd/podman/containers/list.go index c200a49aa..6aadbcc75 100644 --- a/cmd/podman/containers/list.go +++ b/cmd/podman/containers/list.go @@ -29,4 +29,5 @@ func init() { Parent: containerCmd, }) listFlagSet(listCmd.Flags()) + validate.AddLatestFlag(listCmd, &listOpts.Latest) } diff --git a/cmd/podman/containers/logs.go b/cmd/podman/containers/logs.go index de5234044..351a055df 100644 --- a/cmd/podman/containers/logs.go +++ b/cmd/podman/containers/logs.go @@ -4,6 +4,7 @@ import ( "os" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" @@ -68,9 +69,8 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: logsCommand, }) - - flags := logsCommand.Flags() - logsFlags(flags) + logsFlags(logsCommand.Flags()) + validate.AddLatestFlag(logsCommand, &logsOptions.Latest) // container logs registry.Commands = append(registry.Commands, registry.CliCommand{ @@ -78,15 +78,13 @@ func init() { Command: containerLogsCommand, Parent: containerCmd, }) - - containerLogsFlags := containerLogsCommand.Flags() - logsFlags(containerLogsFlags) + logsFlags(containerLogsCommand.Flags()) + validate.AddLatestFlag(containerLogsCommand, &logsOptions.Latest) } func logsFlags(flags *pflag.FlagSet) { flags.BoolVar(&logsOptions.Details, "details", false, "Show extra details provided to the logs") flags.BoolVarP(&logsOptions.Follow, "follow", "f", false, "Follow log output. The default is false") - flags.BoolVarP(&logsOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") flags.StringVar(&logsOptions.SinceRaw, "since", "", "Show logs since TIMESTAMP") flags.Int64Var(&logsOptions.Tail, "tail", -1, "Output the specified number of LINES at the end of the logs. Defaults to -1, which prints all lines") flags.BoolVarP(&logsOptions.Timestamps, "timestamps", "t", false, "Output the timestamps in the log") @@ -95,7 +93,7 @@ func logsFlags(flags *pflag.FlagSet) { _ = flags.MarkHidden("details") } -func logs(cmd *cobra.Command, args []string) error { +func logs(_ *cobra.Command, args []string) error { if logsOptions.SinceRaw != "" { // parse time, error out if something is wrong since, err := util.ParseInputTime(logsOptions.SinceRaw) diff --git a/cmd/podman/containers/mount.go b/cmd/podman/containers/mount.go index 5c73cceaa..ddde0cc9f 100644 --- a/cmd/podman/containers/mount.go +++ b/cmd/podman/containers/mount.go @@ -6,9 +6,9 @@ import ( "text/tabwriter" "text/template" - "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -28,7 +28,7 @@ var ( Long: mountDescription, RunE: mount, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, true, false) + return validate.CheckAllLatestAndCIDFile(cmd, args, true, false) }, } @@ -47,7 +47,6 @@ var ( func mountFlags(flags *pflag.FlagSet) { flags.BoolVarP(&mountOpts.All, "all", "a", false, "Mount all containers") flags.StringVar(&mountOpts.Format, "format", "", "Change the output format to Go template") - flags.BoolVarP(&mountOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") flags.BoolVar(&mountOpts.NoTruncate, "notruncate", false, "Do not truncate output") } @@ -56,19 +55,19 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode}, Command: mountCommand, }) - flags := mountCommand.Flags() - mountFlags(flags) + mountFlags(mountCommand.Flags()) + validate.AddLatestFlag(mountCommand, &mountOpts.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode}, Command: containerMountCommmand, Parent: containerCmd, }) - containerMountFlags := containerMountCommmand.Flags() - mountFlags(containerMountFlags) + mountFlags(containerMountCommmand.Flags()) + validate.AddLatestFlag(containerMountCommmand, &mountOpts.Latest) } -func mount(cmd *cobra.Command, args []string) error { +func mount(_ *cobra.Command, args []string) error { var ( errs utils.OutputErrors ) diff --git a/cmd/podman/containers/pause.go b/cmd/podman/containers/pause.go index b932c4539..19d97f196 100644 --- a/cmd/podman/containers/pause.go +++ b/cmd/podman/containers/pause.go @@ -66,6 +66,7 @@ func pause(cmd *cobra.Command, args []string) error { if rootless.IsRootless() && !registry.IsRemote() { return errors.New("pause is not supported for rootless containers") } + if len(args) < 1 && !pauseOpts.All { return errors.Errorf("you must provide at least one container name or id") } diff --git a/cmd/podman/containers/port.go b/cmd/podman/containers/port.go index 115adc2a7..3ad6c507b 100644 --- a/cmd/podman/containers/port.go +++ b/cmd/podman/containers/port.go @@ -5,8 +5,8 @@ import ( "strconv" "strings" - "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/pkg/errors" @@ -23,7 +23,7 @@ var ( Long: portDescription, RunE: port, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, true, false) + return validate.CheckAllLatestAndCIDFile(cmd, args, true, false) }, Example: `podman port --all podman port ctrID 80/tcp @@ -36,7 +36,7 @@ var ( Long: portDescription, RunE: portCommand.RunE, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, true, false) + return validate.CheckAllLatestAndCIDFile(cmd, args, true, false) }, Example: `podman container port --all podman container port --latest 80`, @@ -49,10 +49,6 @@ var ( func portFlags(flags *pflag.FlagSet) { flags.BoolVarP(&portOpts.All, "all", "a", false, "Display port information for all containers") - flags.BoolVarP(&portOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } } func init() { @@ -60,22 +56,19 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: portCommand, }) - - flags := portCommand.Flags() - portFlags(flags) + portFlags(portCommand.Flags()) + validate.AddLatestFlag(portCommand, &portOpts.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: containerPortCommand, Parent: containerCmd, }) - - containerPortflags := containerPortCommand.Flags() - portFlags(containerPortflags) - + portFlags(containerPortCommand.Flags()) + validate.AddLatestFlag(containerPortCommand, &portOpts.Latest) } -func port(cmd *cobra.Command, args []string) error { +func port(_ *cobra.Command, args []string) error { var ( container string err error diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go index 5d3c9263e..d10cda609 100644 --- a/cmd/podman/containers/ps.go +++ b/cmd/podman/containers/ps.go @@ -50,6 +50,7 @@ func init() { Command: psCommand, }) listFlagSet(psCommand.Flags()) + validate.AddLatestFlag(psCommand, &listOpts.Latest) } func listFlagSet(flags *pflag.FlagSet) { @@ -57,7 +58,6 @@ func listFlagSet(flags *pflag.FlagSet) { flags.StringSliceVarP(&filters, "filter", "f", []string{}, "Filter output based on conditions given") flags.StringVar(&listOpts.Format, "format", "", "Pretty-print containers to JSON or using a Go template") flags.IntVarP(&listOpts.Last, "last", "n", -1, "Print the n last created containers (all states)") - flags.BoolVarP(&listOpts.Latest, "latest", "l", false, "Show the latest container created (all states)") flags.BoolVar(&listOpts.Namespace, "namespace", false, "Display namespace information") flags.BoolVar(&listOpts.Namespace, "ns", false, "Display namespace information") flags.BoolVar(&noTrunc, "no-trunc", false, "Display the extended information") @@ -69,10 +69,6 @@ func listFlagSet(flags *pflag.FlagSet) { sort := validate.Value(&listOpts.Sort, "command", "created", "id", "image", "names", "runningfor", "size", "status") flags.Var(sort, "sort", "Sort output by: "+sort.Choices()) - - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } } func checkFlags(c *cobra.Command) error { // latest, and last are mutually exclusive. diff --git a/cmd/podman/containers/restart.go b/cmd/podman/containers/restart.go index 1a9d7f6c7..83d6a13ca 100644 --- a/cmd/podman/containers/restart.go +++ b/cmd/podman/containers/restart.go @@ -4,9 +4,9 @@ import ( "context" "fmt" - "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" @@ -25,7 +25,7 @@ var ( Long: restartDescription, RunE: restart, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, Example: `podman restart ctrID podman restart --latest @@ -50,12 +50,9 @@ var ( func restartFlags(flags *pflag.FlagSet) { flags.BoolVarP(&restartOptions.All, "all", "a", false, "Restart all non-running containers") - flags.BoolVarP(&restartOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") flags.BoolVar(&restartOptions.Running, "running", false, "Restart only running containers when --all is used") flags.UintVarP(&restartTimeout, "time", "t", containerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container") - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } + flags.SetNormalizeFunc(utils.AliasFlags) } @@ -64,17 +61,16 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: restartCommand, }) - flags := restartCommand.Flags() - restartFlags(flags) + restartFlags(restartCommand.Flags()) + validate.AddLatestFlag(restartCommand, &restartOptions.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: containerRestartCommand, Parent: containerCmd, }) - - containerRestartFlags := containerRestartCommand.Flags() - restartFlags(containerRestartFlags) + restartFlags(containerRestartCommand.Flags()) + validate.AddLatestFlag(containerRestartCommand, &restartOptions.Latest) } func restart(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/restore.go b/cmd/podman/containers/restore.go index 3bc17206a..a5e328e8e 100644 --- a/cmd/podman/containers/restore.go +++ b/cmd/podman/containers/restore.go @@ -4,9 +4,9 @@ import ( "context" "fmt" - "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" @@ -25,7 +25,7 @@ var ( Long: restoreDescription, RunE: restore, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, true, false) + return validate.CheckAllLatestAndCIDFile(cmd, args, true, false) }, Example: `podman container restore ctrID podman container restore --latest @@ -46,19 +46,16 @@ func init() { flags := restoreCommand.Flags() flags.BoolVarP(&restoreOptions.All, "all", "a", false, "Restore all checkpointed containers") flags.BoolVarP(&restoreOptions.Keep, "keep", "k", false, "Keep all temporary checkpoint files") - flags.BoolVarP(&restoreOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") flags.BoolVar(&restoreOptions.TCPEstablished, "tcp-established", false, "Restore a container with established TCP connections") flags.StringVarP(&restoreOptions.Import, "import", "i", "", "Restore from exported checkpoint archive (tar.gz)") flags.StringVarP(&restoreOptions.Name, "name", "n", "", "Specify new name for container restored from exported checkpoint (only works with --import)") flags.BoolVar(&restoreOptions.IgnoreRootFS, "ignore-rootfs", false, "Do not apply root file-system changes when importing from exported checkpoint") flags.BoolVar(&restoreOptions.IgnoreStaticIP, "ignore-static-ip", false, "Ignore IP address set via --static-ip") flags.BoolVar(&restoreOptions.IgnoreStaticMAC, "ignore-static-mac", false, "Ignore MAC address set via --mac-address") - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } + validate.AddLatestFlag(restoreCommand, &restoreOptions.Latest) } -func restore(cmd *cobra.Command, args []string) error { +func restore(_ *cobra.Command, args []string) error { var errs utils.OutputErrors if rootless.IsRootless() { return errors.New("restoring a container requires root") diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go index 22d6d59b4..bb7bca081 100644 --- a/cmd/podman/containers/rm.go +++ b/cmd/podman/containers/rm.go @@ -5,9 +5,9 @@ import ( "fmt" "strings" - "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" @@ -26,7 +26,7 @@ var ( Long: rmDescription, RunE: rm, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) }, Example: `podman rm imageID podman rm mywebserver myflaskserver 860a4b23 @@ -40,7 +40,7 @@ var ( Long: rmCommand.Long, RunE: rmCommand.RunE, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) }, Example: `podman container rm imageID podman container rm mywebserver myflaskserver 860a4b23 @@ -57,12 +57,11 @@ func rmFlags(flags *pflag.FlagSet) { flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all containers") flags.BoolVarP(&rmOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing") flags.BoolVarP(&rmOptions.Force, "force", "f", false, "Force removal of a running or unusable container. The default is false") - flags.BoolVarP(&rmOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") flags.BoolVar(&rmOptions.Storage, "storage", false, "Remove container from storage library") flags.BoolVarP(&rmOptions.Volumes, "volumes", "v", false, "Remove anonymous volumes associated with the container") flags.StringArrayVarP(&rmOptions.CIDFiles, "cidfile", "", nil, "Read the container ID from the file") + if registry.IsRemote() { - _ = flags.MarkHidden("latest") _ = flags.MarkHidden("ignore") _ = flags.MarkHidden("cidfile") _ = flags.MarkHidden("storage") @@ -75,18 +74,18 @@ func init() { Command: rmCommand, }) rmFlags(rmCommand.Flags()) + validate.AddLatestFlag(rmCommand, &rmOptions.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: containerRmCommand, Parent: containerCmd, }) - - containerRmFlags := containerRmCommand.Flags() - rmFlags(containerRmFlags) + rmFlags(containerRmCommand.Flags()) + validate.AddLatestFlag(containerRmCommand, &rmOptions.Latest) } -func rm(cmd *cobra.Command, args []string) error { +func rm(_ *cobra.Command, args []string) error { return removeContainers(args, rmOptions, true) } diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index cb307c38f..dda22e539 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -62,6 +62,7 @@ func runFlags(flags *pflag.FlagSet) { flags.BoolVar(&runOpts.SigProxy, "sig-proxy", true, "Proxy received signals to the process") flags.BoolVar(&runRmi, "rmi", false, "Remove container image unless used by other containers") flags.UintVar(&runOpts.PreserveFDs, "preserve-fds", 0, "Pass a number of additional file descriptors into the container") + if registry.IsRemote() { _ = flags.MarkHidden("authfile") _ = flags.MarkHidden("env-host") @@ -125,10 +126,13 @@ func run(cmd *cobra.Command, args []string) error { return err } + imageName := args[0] if !cliVals.RootFS { - if err := pullImage(args[0]); err != nil { + name, err := pullImage(args[0]) + if err != nil { return err } + imageName = name } if cliVals.Replace { @@ -166,7 +170,7 @@ func run(cmd *cobra.Command, args []string) error { runOpts.Detach = cliVals.Detach runOpts.DetachKeys = cliVals.DetachKeys cliVals.PreserveFDs = runOpts.PreserveFDs - s := specgen.NewSpecGenerator(args[0], cliVals.RootFS) + s := specgen.NewSpecGenerator(imageName, cliVals.RootFS) if err := common.FillOutSpecGen(s, &cliVals, args); err != nil { return err } @@ -196,7 +200,7 @@ func run(cmd *cobra.Command, args []string) error { return nil } if runRmi { - _, rmErrors := registry.ImageEngine().Remove(registry.GetContext(), []string{args[0]}, entities.ImageRemoveOptions{}) + _, rmErrors := registry.ImageEngine().Remove(registry.GetContext(), []string{imageName}, entities.ImageRemoveOptions{}) if len(rmErrors) > 0 { logrus.Errorf("%s", errors.Wrapf(errorhandling.JoinErrors(rmErrors), "failed removing image")) } diff --git a/cmd/podman/containers/start.go b/cmd/podman/containers/start.go index 751fec65f..05e1c38ef 100644 --- a/cmd/podman/containers/start.go +++ b/cmd/podman/containers/start.go @@ -6,6 +6,7 @@ import ( "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" @@ -44,10 +45,9 @@ func startFlags(flags *pflag.FlagSet) { flags.BoolVarP(&startOptions.Attach, "attach", "a", false, "Attach container's STDOUT and STDERR") flags.StringVar(&startOptions.DetachKeys, "detach-keys", containerConfig.DetachKeys(), "Select the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`") flags.BoolVarP(&startOptions.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached") - flags.BoolVarP(&startOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") flags.BoolVar(&startOptions.SigProxy, "sig-proxy", false, "Proxy received signals to the process (default true if attaching, false otherwise)") + if registry.IsRemote() { - _ = flags.MarkHidden("latest") _ = flags.MarkHidden("sig-proxy") } } @@ -56,17 +56,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: startCommand, }) - flags := startCommand.Flags() - startFlags(flags) + startFlags(startCommand.Flags()) + validate.AddLatestFlag(startCommand, &startOptions.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: containerStartCommand, Parent: containerCmd, }) + startFlags(containerStartCommand.Flags()) + validate.AddLatestFlag(containerStartCommand, &startOptions.Latest) - containerStartFlags := containerStartCommand.Flags() - startFlags(containerStartFlags) } func start(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/stats.go b/cmd/podman/containers/stats.go index 260cbd25d..3da68949a 100644 --- a/cmd/podman/containers/stats.go +++ b/cmd/podman/containers/stats.go @@ -10,6 +10,7 @@ import ( tm "github.com/buger/goterm" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/domain/entities" @@ -56,12 +57,8 @@ var ( func statFlags(flags *pflag.FlagSet) { flags.BoolVarP(&statsOptions.All, "all", "a", false, "Show all containers. Only running containers are shown by default. The default is false") flags.StringVar(&statsOptions.Format, "format", "", "Pretty-print container statistics to JSON or using a Go template") - flags.BoolVarP(&statsOptions.Latest, "latest", "l", false, "Act on the latest container Podman is aware of") flags.BoolVar(&statsOptions.NoReset, "no-reset", false, "Disable resetting the screen between intervals") flags.BoolVar(&statsOptions.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result, default setting is false") - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } } func init() { @@ -69,17 +66,16 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: statsCommand, }) - flags := statsCommand.Flags() - statFlags(flags) + statFlags(statsCommand.Flags()) + validate.AddLatestFlag(statsCommand, &statsOptions.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: containerStatsCommand, Parent: containerCmd, }) - - containerStatsFlags := containerStatsCommand.Flags() - statFlags(containerStatsFlags) + statFlags(containerStatsCommand.Flags()) + validate.AddLatestFlag(containerStatsCommand, &statsOptions.Latest) } // stats is different in that it will assume running containers if diff --git a/cmd/podman/containers/stop.go b/cmd/podman/containers/stop.go index 0f2a91af0..b525fc97a 100644 --- a/cmd/podman/containers/stop.go +++ b/cmd/podman/containers/stop.go @@ -4,9 +4,9 @@ import ( "context" "fmt" - "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -22,7 +22,7 @@ var ( Long: stopDescription, RunE: stop, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) }, Example: `podman stop ctrID podman stop --latest @@ -35,7 +35,7 @@ var ( Long: stopCommand.Long, RunE: stopCommand.RunE, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) }, Example: `podman container stop ctrID podman container stop --latest @@ -52,11 +52,9 @@ func stopFlags(flags *pflag.FlagSet) { flags.BoolVarP(&stopOptions.All, "all", "a", false, "Stop all running containers") flags.BoolVarP(&stopOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing") flags.StringArrayVarP(&stopOptions.CIDFiles, "cidfile", "", nil, "Read the container ID from the file") - flags.BoolVarP(&stopOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") flags.UintVarP(&stopTimeout, "time", "t", containerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container") if registry.IsRemote() { - _ = flags.MarkHidden("latest") _ = flags.MarkHidden("cidfile") _ = flags.MarkHidden("ignore") } @@ -68,8 +66,8 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: stopCommand, }) - flags := stopCommand.Flags() - stopFlags(flags) + stopFlags(stopCommand.Flags()) + validate.AddLatestFlag(stopCommand, &stopOptions.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, @@ -77,8 +75,8 @@ func init() { Parent: containerCmd, }) - containerStopFlags := containerStopCommand.Flags() - stopFlags(containerStopFlags) + stopFlags(containerStopCommand.Flags()) + validate.AddLatestFlag(containerStopCommand, &stopOptions.Latest) } func stop(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/top.go b/cmd/podman/containers/top.go index afab12a14..cd01046cc 100644 --- a/cmd/podman/containers/top.go +++ b/cmd/podman/containers/top.go @@ -8,6 +8,7 @@ import ( "text/tabwriter" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" @@ -51,11 +52,7 @@ podman container top ctrID -eo user,pid,comm`, func topFlags(flags *pflag.FlagSet) { flags.SetInterspersed(false) flags.BoolVar(&topOptions.ListDescriptors, "list-descriptors", false, "") - flags.BoolVarP(&topOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") _ = flags.MarkHidden("list-descriptors") // meant only for bash completion - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } } func init() { @@ -63,8 +60,8 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: topCommand, }) - flags := topCommand.Flags() - topFlags(flags) + topFlags(topCommand.Flags()) + validate.AddLatestFlag(topCommand, &topOptions.Latest) descriptors, err := util.GetContainerPidInformationDescriptors() if err == nil { @@ -77,8 +74,8 @@ func init() { Command: containerTopCommand, Parent: containerCmd, }) - containerTopFlags := containerTopCommand.Flags() - topFlags(containerTopFlags) + topFlags(containerTopCommand.Flags()) + validate.AddLatestFlag(containerTopCommand, &topOptions.Latest) } func top(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/unmount.go b/cmd/podman/containers/unmount.go index c8e551e28..c061a6df3 100644 --- a/cmd/podman/containers/unmount.go +++ b/cmd/podman/containers/unmount.go @@ -3,9 +3,9 @@ package containers import ( "fmt" - "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -25,7 +25,7 @@ var ( Long: description, RunE: unmount, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, Example: `podman umount ctrID podman umount ctrID1 ctrID2 ctrID3 @@ -38,7 +38,7 @@ var ( Long: umountCommand.Long, RunE: umountCommand.RunE, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, Example: `podman container umount ctrID podman container umount ctrID1 ctrID2 ctrID3 @@ -53,7 +53,6 @@ var ( func umountFlags(flags *pflag.FlagSet) { flags.BoolVarP(&unmountOpts.All, "all", "a", false, "Umount all of the currently mounted containers") flags.BoolVarP(&unmountOpts.Force, "force", "f", false, "Force the complete umount all of the currently mounted containers") - flags.BoolVarP(&unmountOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") } func init() { @@ -61,17 +60,16 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode}, Command: umountCommand, }) - flags := umountCommand.Flags() - umountFlags(flags) + umountFlags(umountCommand.Flags()) + validate.AddLatestFlag(umountCommand, &unmountOpts.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode}, Command: containerUnmountCommand, Parent: containerCmd, }) - - containerUmountFlags := containerUnmountCommand.Flags() - umountFlags(containerUmountFlags) + umountFlags(containerUnmountCommand.Flags()) + validate.AddLatestFlag(containerUnmountCommand, &unmountOpts.Latest) } func unmount(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/wait.go b/cmd/podman/containers/wait.go index 115bb3eea..be5cfce9a 100644 --- a/cmd/podman/containers/wait.go +++ b/cmd/podman/containers/wait.go @@ -47,9 +47,6 @@ var ( func waitFlags(flags *pflag.FlagSet) { flags.DurationVarP(&waitOptions.Interval, "interval", "i", time.Duration(250), "Milliseconds to wait before polling for completion") flags.StringVar(&waitCondition, "condition", "stopped", "Condition to wait on") - if !registry.IsRemote() { - flags.BoolVarP(&waitOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - } } func init() { @@ -57,17 +54,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: waitCommand, }) - flags := waitCommand.Flags() - waitFlags(flags) + waitFlags(waitCommand.Flags()) + validate.AddLatestFlag(waitCommand, &waitOptions.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: containerWaitCommand, Parent: containerCmd, }) + waitFlags(containerWaitCommand.Flags()) + validate.AddLatestFlag(containerWaitCommand, &waitOptions.Latest) - containerWaitFlags := containerWaitCommand.Flags() - waitFlags(containerWaitFlags) } func wait(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/diff.go b/cmd/podman/diff.go index d635ea57a..eaf6abf02 100644 --- a/cmd/podman/diff.go +++ b/cmd/podman/diff.go @@ -17,12 +17,11 @@ var ( // Command: podman _diff_ Object_ID diffDescription = `Displays changes on a container or image's filesystem. The container or image will be compared to its parent layer.` diffCmd = &cobra.Command{ - Use: "diff [flags] {CONTAINER_ID | IMAGE_ID}", - Args: validate.IDOrLatestArgs, - Short: "Display the changes of object's file system", - Long: diffDescription, - TraverseChildren: true, - RunE: diff, + Use: "diff [flags] {CONTAINER_ID | IMAGE_ID}", + Args: validate.IDOrLatestArgs, + Short: "Display the changes to the object's file system", + Long: diffDescription, + RunE: diff, Example: `podman diff imageID podman diff ctrID podman diff --format json redis:alpine`, @@ -40,10 +39,7 @@ func init() { flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive") _ = flags.MarkHidden("archive") flags.StringVar(&diffOpts.Format, "format", "", "Change the output format") - - if !registry.IsRemote() { - flags.BoolVarP(&diffOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - } + validate.AddLatestFlag(diffCmd, &diffOpts.Latest) } func diff(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/generate/generate.go b/cmd/podman/generate/generate.go index 7803c0c78..a13e50903 100644 --- a/cmd/podman/generate/generate.go +++ b/cmd/podman/generate/generate.go @@ -11,11 +11,10 @@ import ( var ( // Command: podman _generate_ generateCmd = &cobra.Command{ - Use: "generate", - Short: "Generate structured data based on containers and pods.", - Long: "Generate structured data (e.g., Kubernetes yaml or systemd units) based on containers and pods.", - TraverseChildren: true, - RunE: validate.SubCommandExists, + Use: "generate", + Short: "Generate structured data based on containers and pods.", + Long: "Generate structured data (e.g., Kubernetes yaml or systemd units) based on containers and pods.", + RunE: validate.SubCommandExists, } containerConfig = util.DefaultContainerConfig() ) diff --git a/cmd/podman/healthcheck/healthcheck.go b/cmd/podman/healthcheck/healthcheck.go index f48701624..15ed47d39 100644 --- a/cmd/podman/healthcheck/healthcheck.go +++ b/cmd/podman/healthcheck/healthcheck.go @@ -10,11 +10,10 @@ import ( var ( // Command: healthcheck healthCmd = &cobra.Command{ - Use: "healthcheck", - Short: "Manage health checks on containers", - Long: "Run health checks on containers", - TraverseChildren: true, - RunE: validate.SubCommandExists, + Use: "healthcheck", + Short: "Manage health checks on containers", + Long: "Run health checks on containers", + RunE: validate.SubCommandExists, } ) diff --git a/cmd/podman/healthcheck/run.go b/cmd/podman/healthcheck/run.go index 5612910cb..17ddf17b6 100644 --- a/cmd/podman/healthcheck/run.go +++ b/cmd/podman/healthcheck/run.go @@ -12,12 +12,13 @@ import ( var ( healthcheckRunDescription = "run the health check of a container" healthcheckrunCommand = &cobra.Command{ - Use: "run [flags] CONTAINER", - Short: "run the health check of a container", - Long: healthcheckRunDescription, - Example: `podman healthcheck run mywebapp`, - RunE: run, - Args: cobra.ExactArgs(1), + Use: "run CONTAINER", + Short: "run the health check of a container", + Long: healthcheckRunDescription, + Example: `podman healthcheck run mywebapp`, + RunE: run, + Args: cobra.ExactArgs(1), + DisableFlagsInUseLine: true, } ) diff --git a/cmd/podman/images/build.go b/cmd/podman/images/build.go index dfde896a1..eefc488d3 100644 --- a/cmd/podman/images/build.go +++ b/cmd/podman/images/build.go @@ -40,12 +40,11 @@ var ( // Command: podman _diff_ Object_ID buildDescription = "Builds an OCI or Docker image using instructions from one or more Containerfiles and a specified build context directory." buildCmd = &cobra.Command{ - Use: "build [flags] [CONTEXT]", - Short: "Build an image using instructions from Containerfiles", - Long: buildDescription, - TraverseChildren: true, - RunE: build, - Args: cobra.MaximumNArgs(1), + Use: "build [flags] [CONTEXT]", + Short: "Build an image using instructions from Containerfiles", + Long: buildDescription, + Args: cobra.MaximumNArgs(1), + RunE: build, Example: `podman build . podman build --creds=username:password -t imageName -f Containerfile.simple . podman build --layers --force-rm --tag imageName .`, diff --git a/cmd/podman/images/diff.go b/cmd/podman/images/diff.go index c24f98369..53165543c 100644 --- a/cmd/podman/images/diff.go +++ b/cmd/podman/images/diff.go @@ -14,8 +14,8 @@ var ( diffCmd = &cobra.Command{ Use: "diff [flags] IMAGE", Args: cobra.ExactArgs(1), - Short: "Inspect changes on image's file systems", - Long: `Displays changes on a image's filesystem. The image will be compared to its parent layer.`, + Short: "Inspect changes to the image's file systems", + Long: `Displays changes to the image's filesystem. The image will be compared to its parent layer.`, RunE: diff, Example: `podman image diff myImage podman image diff --format json redis:alpine`, diff --git a/cmd/podman/images/image.go b/cmd/podman/images/image.go index ebef126c0..2d1974d5c 100644 --- a/cmd/podman/images/image.go +++ b/cmd/podman/images/image.go @@ -13,11 +13,10 @@ var ( // Command: podman _image_ imageCmd = &cobra.Command{ - Use: "image", - Short: "Manage images", - Long: "Manage images", - TraverseChildren: true, - RunE: validate.SubCommandExists, + Use: "image", + Short: "Manage images", + Long: "Manage images", + RunE: validate.SubCommandExists, } ) diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go index ccac7e3fe..d7756433b 100644 --- a/cmd/podman/images/search.go +++ b/cmd/podman/images/search.go @@ -86,6 +86,7 @@ func searchFlags(flags *pflag.FlagSet) { flags.BoolVar(&searchOptions.NoTrunc, "no-trunc", false, "Do not truncate the output") flags.StringVar(&searchOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") flags.BoolVar(&searchOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") + if registry.IsRemote() { _ = flags.MarkHidden("authfile") _ = flags.MarkHidden("tls-verify") diff --git a/cmd/podman/images/tag.go b/cmd/podman/images/tag.go index dae3416c4..859489552 100644 --- a/cmd/podman/images/tag.go +++ b/cmd/podman/images/tag.go @@ -9,22 +9,24 @@ import ( var ( tagDescription = "Adds one or more additional names to locally-stored image." tagCommand = &cobra.Command{ - Use: "tag [flags] IMAGE TARGET_NAME [TARGET_NAME...]", - Short: "Add an additional name to a local image", - Long: tagDescription, - RunE: tag, - Args: cobra.MinimumNArgs(2), + Use: "tag IMAGE TARGET_NAME [TARGET_NAME...]", + Short: "Add an additional name to a local image", + Long: tagDescription, + RunE: tag, + Args: cobra.MinimumNArgs(2), + DisableFlagsInUseLine: true, Example: `podman tag 0e3bbc2 fedora:latest podman tag imageID:latest myNewImage:newTag podman tag httpd myregistryhost:5000/fedora/httpd:v2`, } imageTagCommand = &cobra.Command{ - Args: tagCommand.Args, - Use: tagCommand.Use, - Short: tagCommand.Short, - Long: tagCommand.Long, - RunE: tagCommand.RunE, + Args: tagCommand.Args, + DisableFlagsInUseLine: true, + Use: tagCommand.Use, + Short: tagCommand.Short, + Long: tagCommand.Long, + RunE: tagCommand.RunE, Example: `podman image tag 0e3bbc2 fedora:latest podman image tag imageID:latest myNewImage:newTag podman image tag httpd myregistryhost:5000/fedora/httpd:v2`, diff --git a/cmd/podman/images/untag.go b/cmd/podman/images/untag.go index 266a3f115..5d1274895 100644 --- a/cmd/podman/images/untag.go +++ b/cmd/podman/images/untag.go @@ -8,22 +8,24 @@ import ( var ( untagCommand = &cobra.Command{ - Use: "untag [flags] IMAGE [NAME...]", - Short: "Remove a name from a local image", - Long: "Removes one or more names from a locally-stored image.", - RunE: untag, - Args: cobra.MinimumNArgs(1), + Use: "untag IMAGE [NAME...]", + Short: "Remove a name from a local image", + Long: "Removes one or more names from a locally-stored image.", + RunE: untag, + Args: cobra.MinimumNArgs(1), + DisableFlagsInUseLine: true, Example: `podman untag 0e3bbc2 podman untag imageID:latest otherImageName:latest podman untag httpd myregistryhost:5000/fedora/httpd:v2`, } imageUntagCommand = &cobra.Command{ - Args: untagCommand.Args, - Use: untagCommand.Use, - Short: untagCommand.Short, - Long: untagCommand.Long, - RunE: untagCommand.RunE, + Args: untagCommand.Args, + DisableFlagsInUseLine: true, + Use: untagCommand.Use, + Short: untagCommand.Short, + Long: untagCommand.Long, + RunE: untagCommand.RunE, Example: `podman image untag 0e3bbc2 podman image untag imageID:latest otherImageName:latest podman image untag httpd myregistryhost:5000/fedora/httpd:v2`, diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index 6c4607d88..12e11d0f5 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -10,11 +10,10 @@ import ( var ( // Command: podman _inspect_ Object_ID inspectCmd = &cobra.Command{ - Use: "inspect [flags] {CONTAINER_ID | IMAGE_ID} [...]", - Short: "Display the configuration of object denoted by ID", - Long: "Displays the low-level information on an object identified by name or ID", - TraverseChildren: true, - RunE: inspectExec, + Use: "inspect [flags] {CONTAINER_ID | IMAGE_ID} [...]", + Short: "Display the configuration of object denoted by ID", + Long: "Displays the low-level information on an object identified by name or ID", + RunE: inspectExec, Example: `podman inspect fedora podman inspect --type image fedora podman inspect CtrID ImgID diff --git a/cmd/podman/inspect/inspect.go b/cmd/podman/inspect/inspect.go index d80bbffdd..1c1e68d6f 100644 --- a/cmd/podman/inspect/inspect.go +++ b/cmd/podman/inspect/inspect.go @@ -8,6 +8,7 @@ import ( "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -32,8 +33,8 @@ func AddInspectFlagSet(cmd *cobra.Command) *entities.InspectOptions { flags.BoolVarP(&opts.Size, "size", "s", false, "Display total file size") flags.StringVarP(&opts.Format, "format", "f", "json", "Format the output to a Go template or json") flags.StringVarP(&opts.Type, "type", "t", AllType, fmt.Sprintf("Specify inspect-oject type (%q, %q or %q)", ImageType, ContainerType, AllType)) - flags.BoolVarP(&opts.Latest, "latest", "l", false, "Act on the latest container Podman is aware of") + validate.AddLatestFlag(cmd, &opts.Latest) return &opts } diff --git a/cmd/podman/main.go b/cmd/podman/main.go index f502e7a67..636538131 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -16,7 +16,9 @@ import ( _ "github.com/containers/libpod/cmd/podman/system" _ "github.com/containers/libpod/cmd/podman/volumes" "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/terminal" "github.com/containers/storage/pkg/reexec" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -27,6 +29,11 @@ func main() { return } + // Hard code TMPDIR functions to use /var/tmp, if user did not override + if _, ok := os.LookupEnv("TMPDIR"); !ok { + os.Setenv("TMPDIR", "/var/tmp") + } + cfg := registry.PodmanConfig() for _, c := range registry.Commands { for _, m := range c.Mode { @@ -53,6 +60,10 @@ func main() { } } } + if err := terminal.SetConsole(); err != nil { + logrus.Error(err) + os.Exit(1) + } Execute() os.Exit(0) diff --git a/cmd/podman/manifest/inspect.go b/cmd/podman/manifest/inspect.go index 5112aa5b2..861f4be4f 100644 --- a/cmd/podman/manifest/inspect.go +++ b/cmd/podman/manifest/inspect.go @@ -12,12 +12,13 @@ import ( var ( inspectCmd = &cobra.Command{ - Use: "inspect [flags] IMAGE", - Short: "Display the contents of a manifest list or image index", - Long: "Display the contents of a manifest list or image index.", - RunE: inspect, - Example: "podman manifest inspect localhost/list", - Args: cobra.ExactArgs(1), + Use: "inspect IMAGE", + Short: "Display the contents of a manifest list or image index", + Long: "Display the contents of a manifest list or image index.", + RunE: inspect, + Example: "podman manifest inspect localhost/list", + Args: cobra.ExactArgs(1), + DisableFlagsInUseLine: true, } ) diff --git a/cmd/podman/manifest/manifest.go b/cmd/podman/manifest/manifest.go index d7f042a56..26a516ec8 100644 --- a/cmd/podman/manifest/manifest.go +++ b/cmd/podman/manifest/manifest.go @@ -10,11 +10,10 @@ import ( var ( manifestDescription = "Creates, modifies, and pushes manifest lists and image indexes." manifestCmd = &cobra.Command{ - Use: "manifest", - Short: "Manipulate manifest lists and image indexes", - Long: manifestDescription, - TraverseChildren: true, - RunE: validate.SubCommandExists, + Use: "manifest", + Short: "Manipulate manifest lists and image indexes", + Long: manifestDescription, + RunE: validate.SubCommandExists, Example: `podman manifest add mylist:v1.11 image:v1.11-amd64 podman manifest create localhost/list podman manifest inspect localhost/list diff --git a/cmd/podman/manifest/push.go b/cmd/podman/manifest/push.go index a2e68aff1..e3073faea 100644 --- a/cmd/podman/manifest/push.go +++ b/cmd/podman/manifest/push.go @@ -49,6 +49,7 @@ func init() { flags.StringVar(&manifestPushOpts.SignBy, "sign-by", "", "sign the image using a GPG key with the specified `FINGERPRINT`") flags.BoolVar(&manifestPushOpts.TLSVerifyCLI, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry") flags.BoolVarP(&manifestPushOpts.Quiet, "quiet", "q", false, "don't output progress information when pushing lists") + if registry.IsRemote() { _ = flags.MarkHidden("authfile") _ = flags.MarkHidden("cert-dir") diff --git a/cmd/podman/manifest/remove.go b/cmd/podman/manifest/remove.go index 4d345efc0..815a3f0a8 100644 --- a/cmd/podman/manifest/remove.go +++ b/cmd/podman/manifest/remove.go @@ -12,12 +12,13 @@ import ( var ( removeCmd = &cobra.Command{ - Use: "remove [flags] LIST IMAGE", - Short: "Remove an entry from a manifest list or image index", - Long: "Removes an image from a manifest list or image index.", - RunE: remove, - Example: `podman manifest remove mylist:v1.11 sha256:15352d97781ffdf357bf3459c037be3efac4133dc9070c2dce7eca7c05c3e736`, - Args: cobra.ExactArgs(2), + Use: "remove LIST IMAGE", + Short: "Remove an entry from a manifest list or image index", + Long: "Removes an image from a manifest list or image index.", + RunE: remove, + Example: `podman manifest remove mylist:v1.11 sha256:15352d97781ffdf357bf3459c037be3efac4133dc9070c2dce7eca7c05c3e736`, + Args: cobra.ExactArgs(2), + DisableFlagsInUseLine: true, } ) diff --git a/cmd/podman/networks/network.go b/cmd/podman/networks/network.go index 7f38cd2cd..8f605f995 100644 --- a/cmd/podman/networks/network.go +++ b/cmd/podman/networks/network.go @@ -10,11 +10,10 @@ import ( var ( // Command: podman _network_ networkCmd = &cobra.Command{ - Use: "network", - Short: "Manage networks", - Long: "Manage networks", - TraverseChildren: true, - RunE: validate.SubCommandExists, + Use: "network", + Short: "Manage networks", + Long: "Manage networks", + RunE: validate.SubCommandExists, } ) diff --git a/cmd/podman/parse/common.go b/cmd/podman/parse/common.go deleted file mode 100644 index b3aa88da2..000000000 --- a/cmd/podman/parse/common.go +++ /dev/null @@ -1,112 +0,0 @@ -package parse - -import ( - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -// TODO: the two functions here 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. -func CheckAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool, cidfile bool) error { - argLen := len(args) - 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") - specifiedLatest, _ := c.Flags().GetBool("latest") - 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("no arguments are needed with --latest") - } 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 -} - -// CheckAllLatestAndPodIDFile checks that --all and --latest are used correctly. -// If withIDFile is set, also check for the --pod-id-file flag. -func CheckAllLatestAndPodIDFile(c *cobra.Command, args []string, ignoreArgLen bool, withIDFile bool) error { - argLen := len(args) - if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { - if !withIDFile { - 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'") - } - } - - specifiedAll, _ := c.Flags().GetBool("all") - specifiedLatest, _ := c.Flags().GetBool("latest") - specifiedPodIDFile := false - if pid, _ := c.Flags().GetStringArray("pod-id-file"); len(pid) > 0 { - specifiedPodIDFile = true - } - - if specifiedPodIDFile && (specifiedAll || specifiedLatest) { - return errors.Errorf("--all, --latest and --pod-id-file 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("no arguments are needed with --latest") - } else if withIDFile && (specifiedLatest || specifiedPodIDFile) { - return errors.Errorf("no arguments are needed with --latest or --pod-id-file") - } - } - - if specifiedPodIDFile { - return nil - } - - if argLen < 1 && !specifiedAll && !specifiedLatest && !specifiedPodIDFile { - return errors.Errorf("you must provide at least one name or id") - } - return nil -} diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go index c26ca9853..909f225a8 100644 --- a/cmd/podman/play/kube.go +++ b/cmd/podman/play/kube.go @@ -61,7 +61,6 @@ func init() { flags.StringVar(&kubeOptions.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") flags.StringVar(&kubeOptions.SeccompProfileRoot, "seccomp-profile-root", defaultSeccompRoot, "Directory path for seccomp profiles") } - _ = flags.MarkHidden("signature-policy") } diff --git a/cmd/podman/play/play.go b/cmd/podman/play/play.go index b151e5f5d..bc2aa18be 100644 --- a/cmd/podman/play/play.go +++ b/cmd/podman/play/play.go @@ -10,11 +10,10 @@ import ( var ( // Command: podman _play_ playCmd = &cobra.Command{ - Use: "play", - Short: "Play a pod and its containers from a structured file.", - Long: "Play structured data (e.g., Kubernetes pod or service yaml) based on containers and pods.", - TraverseChildren: true, - RunE: validate.SubCommandExists, + Use: "play", + Short: "Play a pod and its containers from a structured file.", + Long: "Play structured data (e.g., Kubernetes pod or service yaml) based on containers and pods.", + RunE: validate.SubCommandExists, } ) diff --git a/cmd/podman/pods/inspect.go b/cmd/podman/pods/inspect.go index 34c07c11b..db2217aea 100644 --- a/cmd/podman/pods/inspect.go +++ b/cmd/podman/pods/inspect.go @@ -6,6 +6,7 @@ import ( "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -36,11 +37,8 @@ func init() { Parent: podCmd, }) flags := inspectCmd.Flags() - flags.BoolVarP(&inspectOptions.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") flags.StringVarP(&inspectOptions.Format, "format", "f", "json", "Format the output to a Go template or json") - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } + validate.AddLatestFlag(inspectCmd, &inspectOptions.Latest) } func inspect(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/pods/kill.go b/cmd/podman/pods/kill.go index 02089016e..d83e4d77e 100644 --- a/cmd/podman/pods/kill.go +++ b/cmd/podman/pods/kill.go @@ -4,9 +4,9 @@ import ( "context" "fmt" - "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -21,7 +21,7 @@ var ( Long: podKillDescription, RunE: kill, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, Example: `podman pod kill podID podman pod kill --signal TERM mywebserver @@ -41,14 +41,11 @@ func init() { }) flags := killCommand.Flags() flags.BoolVarP(&killOpts.All, "all", "a", false, "Kill all containers in all pods") - flags.BoolVarP(&killOpts.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") flags.StringVarP(&killOpts.Signal, "signal", "s", "KILL", "Signal to send to the containers in the pod") - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } - + validate.AddLatestFlag(killCommand, &killOpts.Latest) } -func kill(cmd *cobra.Command, args []string) error { + +func kill(_ *cobra.Command, args []string) error { var ( errs utils.OutputErrors ) diff --git a/cmd/podman/pods/pause.go b/cmd/podman/pods/pause.go index 4ee182661..0ed3db936 100644 --- a/cmd/podman/pods/pause.go +++ b/cmd/podman/pods/pause.go @@ -4,9 +4,9 @@ import ( "context" "fmt" - "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -21,7 +21,7 @@ var ( Long: podPauseDescription, RunE: pause, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, Example: `podman pod pause podID1 podID2 podman pod pause --latest @@ -41,12 +41,9 @@ func init() { }) flags := pauseCommand.Flags() flags.BoolVarP(&pauseOptions.All, "all", "a", false, "Pause all running pods") - flags.BoolVarP(&pauseOptions.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } + validate.AddLatestFlag(pauseCommand, &pauseOptions.Latest) } -func pause(cmd *cobra.Command, args []string) error { +func pause(_ *cobra.Command, args []string) error { var ( errs utils.OutputErrors ) diff --git a/cmd/podman/pods/pod.go b/cmd/podman/pods/pod.go index 9dc538c71..ca0283b11 100644 --- a/cmd/podman/pods/pod.go +++ b/cmd/podman/pods/pod.go @@ -14,11 +14,10 @@ var ( // Command: podman _pod_ podCmd = &cobra.Command{ - Use: "pod", - Short: "Manage pods", - Long: "Pods are a group of one or more containers sharing the same network, pid and ipc namespaces.", - TraverseChildren: true, - RunE: validate.SubCommandExists, + Use: "pod", + Short: "Manage pods", + Long: "Pods are a group of one or more containers sharing the same network, pid and ipc namespaces.", + RunE: validate.SubCommandExists, } containerConfig = util.DefaultContainerConfig() ) diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go index 0171bb243..395acd78f 100644 --- a/cmd/podman/pods/ps.go +++ b/cmd/podman/pods/ps.go @@ -53,18 +53,15 @@ func init() { // TODO should we make this a [] ? flags.StringSliceVarP(&inputFilters, "filter", "f", []string{}, "Filter output based on conditions given") flags.StringVar(&psInput.Format, "format", "", "Pretty-print pods to JSON or using a Go template") - flags.BoolVarP(&psInput.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") flags.BoolVar(&psInput.Namespace, "namespace", false, "Display namespace information of the pod") flags.BoolVar(&psInput.Namespace, "ns", false, "Display namespace information of the pod") flags.BoolVar(&noTrunc, "no-trunc", false, "Do not truncate pod and container IDs") flags.BoolVarP(&psInput.Quiet, "quiet", "q", false, "Print the numeric IDs of the pods only") flags.StringVar(&psInput.Sort, "sort", "created", "Sort output by created, id, name, or number") - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } + validate.AddLatestFlag(psCmd, &psInput.Latest) } -func pods(cmd *cobra.Command, args []string) error { +func pods(cmd *cobra.Command, _ []string) error { var ( w io.Writer = os.Stdout row string diff --git a/cmd/podman/pods/restart.go b/cmd/podman/pods/restart.go index 1f617a277..8bbbbb05d 100644 --- a/cmd/podman/pods/restart.go +++ b/cmd/podman/pods/restart.go @@ -4,9 +4,9 @@ import ( "context" "fmt" - "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -21,7 +21,7 @@ var ( Long: podRestartDescription, RunE: restart, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, Example: `podman pod restart podID1 podID2 podman pod restart --latest @@ -42,10 +42,7 @@ func init() { flags := restartCommand.Flags() flags.BoolVarP(&restartOptions.All, "all", "a", false, "Restart all running pods") - flags.BoolVarP(&restartOptions.Latest, "latest", "l", false, "Restart the latest pod podman is aware of") - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } + validate.AddLatestFlag(restartCommand, &restartOptions.Latest) } func restart(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/pods/rm.go b/cmd/podman/pods/rm.go index ec8dae1d1..5a5aa699b 100644 --- a/cmd/podman/pods/rm.go +++ b/cmd/podman/pods/rm.go @@ -5,9 +5,9 @@ import ( "fmt" "github.com/containers/libpod/cmd/podman/common" - "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -30,7 +30,7 @@ var ( Long: podRmDescription, RunE: rm, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndPodIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndPodIDFile(cmd, args, false, true) }, Example: `podman pod rm mywebserverpod podman pod rm -f 860a4b23 @@ -49,15 +49,15 @@ func init() { flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all running pods") flags.BoolVarP(&rmOptions.Force, "force", "f", false, "Force removal of a running pod by first stopping all containers, then removing all containers in the pod. The default is false") flags.BoolVarP(&rmOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified pod is missing") - flags.BoolVarP(&rmOptions.Latest, "latest", "l", false, "Remove the latest pod podman is aware of") flags.StringArrayVarP(&rmOptions.PodIDFiles, "pod-id-file", "", nil, "Read the pod ID from the file") + validate.AddLatestFlag(rmCommand, &rmOptions.Latest) + if registry.IsRemote() { - _ = flags.MarkHidden("latest") _ = flags.MarkHidden("ignore") } } -func rm(cmd *cobra.Command, args []string) error { +func rm(_ *cobra.Command, args []string) error { ids, err := common.ReadPodIDFiles(rmOptions.PodIDFiles) if err != nil { return err diff --git a/cmd/podman/pods/start.go b/cmd/podman/pods/start.go index 97020b360..7ca211baa 100644 --- a/cmd/podman/pods/start.go +++ b/cmd/podman/pods/start.go @@ -5,9 +5,9 @@ import ( "fmt" "github.com/containers/libpod/cmd/podman/common" - "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -29,7 +29,7 @@ var ( Long: podStartDescription, RunE: start, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndPodIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndPodIDFile(cmd, args, false, true) }, Example: `podman pod start podID podman pod start --latest @@ -50,11 +50,8 @@ func init() { flags := startCommand.Flags() flags.BoolVarP(&startOptions.All, "all", "a", false, "Restart all running pods") - flags.BoolVarP(&startOptions.Latest, "latest", "l", false, "Restart the latest pod podman is aware of") flags.StringArrayVarP(&startOptions.PodIDFiles, "pod-id-file", "", nil, "Read the pod ID from the file") - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } + validate.AddLatestFlag(startCommand, &startOptions.Latest) } func start(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/pods/stats.go b/cmd/podman/pods/stats.go index d14632f01..619e49520 100644 --- a/cmd/podman/pods/stats.go +++ b/cmd/podman/pods/stats.go @@ -13,6 +13,7 @@ import ( "github.com/buger/goterm" "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/util/camelcase" "github.com/spf13/cobra" @@ -55,13 +56,9 @@ func init() { flags := statsCmd.Flags() flags.BoolVarP(&statsOptions.All, "all", "a", false, "Provide stats for all pods") flags.StringVar(&statsOptions.Format, "format", "", "Pretty-print container statistics to JSON or using a Go template") - flags.BoolVarP(&statsOptions.Latest, "latest", "l", false, "Provide stats on the latest pod Podman is aware of") flags.BoolVar(&statsOptions.NoReset, "no-reset", false, "Disable resetting the screen when streaming") flags.BoolVar(&statsOptions.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result") - - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } + validate.AddLatestFlag(statsCmd, &statsOptions.Latest) } func stats(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/pods/stop.go b/cmd/podman/pods/stop.go index 628e8a536..cb052575a 100644 --- a/cmd/podman/pods/stop.go +++ b/cmd/podman/pods/stop.go @@ -5,9 +5,9 @@ import ( "fmt" "github.com/containers/libpod/cmd/podman/common" - "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -34,7 +34,7 @@ var ( Long: podStopDescription, RunE: stop, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndPodIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndPodIDFile(cmd, args, false, true) }, Example: `podman pod stop mywebserverpod podman pod stop --latest @@ -51,13 +51,14 @@ func init() { flags := stopCommand.Flags() flags.BoolVarP(&stopOptions.All, "all", "a", false, "Stop all running pods") flags.BoolVarP(&stopOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified pod is missing") - flags.BoolVarP(&stopOptions.Latest, "latest", "l", false, "Stop the latest pod podman is aware of") flags.UintVarP(&stopOptions.TimeoutCLI, "time", "t", containerConfig.Engine.StopTimeout, "Seconds to wait for pod stop before killing the container") flags.StringArrayVarP(&stopOptions.PodIDFiles, "pod-id-file", "", nil, "Read the pod ID from the file") + validate.AddLatestFlag(stopCommand, &stopOptions.Latest) + if registry.IsRemote() { - _ = flags.MarkHidden("latest") _ = flags.MarkHidden("ignore") } + flags.SetNormalizeFunc(utils.AliasFlags) } diff --git a/cmd/podman/pods/top.go b/cmd/podman/pods/top.go index 8df00f92a..d790869a4 100644 --- a/cmd/podman/pods/top.go +++ b/cmd/podman/pods/top.go @@ -8,6 +8,7 @@ import ( "text/tabwriter" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" @@ -50,15 +51,11 @@ func init() { flags := topCommand.Flags() flags.SetInterspersed(false) flags.BoolVar(&topOptions.ListDescriptors, "list-descriptors", false, "") - flags.BoolVarP(&topOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - _ = flags.MarkHidden("list-descriptors") // meant only for bash completion - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } + validate.AddLatestFlag(topCommand, &topOptions.Latest) } -func top(cmd *cobra.Command, args []string) error { +func top(_ *cobra.Command, args []string) error { if topOptions.ListDescriptors { descriptors, err := util.GetContainerPidInformationDescriptors() if err != nil { diff --git a/cmd/podman/pods/unpause.go b/cmd/podman/pods/unpause.go index b30bd930a..54553e4a8 100644 --- a/cmd/podman/pods/unpause.go +++ b/cmd/podman/pods/unpause.go @@ -4,9 +4,9 @@ import ( "context" "fmt" - "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -21,7 +21,7 @@ var ( Long: podUnpauseDescription, RunE: unpause, Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, Example: `podman pod unpause podID1 podID2 podman pod unpause --all @@ -41,12 +41,10 @@ func init() { }) flags := unpauseCommand.Flags() flags.BoolVarP(&unpauseOptions.All, "all", "a", false, "Pause all running pods") - flags.BoolVarP(&unpauseOptions.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } + validate.AddLatestFlag(unpauseCommand, &unpauseOptions.Latest) } -func unpause(cmd *cobra.Command, args []string) error { + +func unpause(_ *cobra.Command, args []string) error { var ( errs utils.OutputErrors ) diff --git a/cmd/podman/registry/config.go b/cmd/podman/registry/config.go index a67568d73..85a63402b 100644 --- a/cmd/podman/registry/config.go +++ b/cmd/podman/registry/config.go @@ -5,7 +5,6 @@ import ( "os" "path/filepath" "runtime" - "strings" "sync" "github.com/containers/common/pkg/config" @@ -45,7 +44,7 @@ func newPodmanConfig() { case "linux": // Some linux clients might only be compiled without ABI // support (e.g., podman-remote). - if abiSupport { + if abiSupport && !remoteOverride { mode = entities.ABIMode } else { mode = entities.TunnelMode @@ -55,19 +54,6 @@ func newPodmanConfig() { os.Exit(1) } - // Check if need to fallback to the tunnel mode if --remote is used. - if abiSupport && mode == entities.ABIMode { - // cobra.Execute() may not be called yet, so we peek at os.Args. - for _, v := range os.Args { - // Prefix checking works because of how default EngineMode's - // have been defined. - if strings.HasPrefix(v, "--remote") { - mode = entities.TunnelMode - break - } - } - } - cfg, err := config.NewConfig("") if err != nil { fmt.Fprint(os.Stderr, "Failed to obtain podman configuration: "+err.Error()) diff --git a/cmd/podman/registry/config_tunnel.go b/cmd/podman/registry/config_tunnel.go index 4f9f51163..bb3da947e 100644 --- a/cmd/podman/registry/config_tunnel.go +++ b/cmd/podman/registry/config_tunnel.go @@ -2,13 +2,6 @@ package registry -import ( - "os" -) - func init() { abiSupport = false - - // Enforce that podman-remote == podman --remote - os.Args = append(os.Args, "--remote") } diff --git a/cmd/podman/registry/remote.go b/cmd/podman/registry/remote.go index 95870750e..ed1a874d6 100644 --- a/cmd/podman/registry/remote.go +++ b/cmd/podman/registry/remote.go @@ -1,9 +1,26 @@ package registry import ( + "os" + "sync" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + // Was --remote given on command line + remoteOverride bool + remoteSync sync.Once ) +// IsRemote returns true if podman was built to run remote +// Use in init() functions as a initialization check func IsRemote() bool { - return podmanOptions.EngineMode == entities.TunnelMode + remoteSync.Do(func() { + remote := &cobra.Command{} + remote.Flags().BoolVarP(&remoteOverride, "remote", "r", false, "") + _ = remote.ParseFlags(os.Args) + }) + return podmanOptions.EngineMode == entities.TunnelMode || remoteOverride } diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 25e53cbee..45ca48c39 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -6,6 +6,7 @@ import ( "path" "runtime" "runtime/pprof" + "strconv" "strings" "github.com/containers/common/pkg/config" @@ -20,7 +21,6 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) // HelpTemplate is the help template for podman commands @@ -79,7 +79,7 @@ func init() { syslogHook, ) - rootFlags(registry.PodmanConfig(), rootCmd.PersistentFlags()) + rootFlags(rootCmd, registry.PodmanConfig()) // "version" is a local flag to avoid collisions with sub-commands that use "-v" var dummyVersion bool @@ -111,6 +111,15 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { cfg := registry.PodmanConfig() + // Validate --remote and --latest not given on same command + latest := cmd.Flags().Lookup("latest") + if latest != nil { + value, _ := strconv.ParseBool(latest.Value.String()) + if cfg.Remote && value { + return errors.Errorf("For %s \"--remote\" and \"--latest\", are mutually exclusive flags", cmd.CommandPath()) + } + } + // Prep the engines if _, err := registry.NewImageEngine(cmd, args); err != nil { return err @@ -193,7 +202,7 @@ func loggingHook() { } } if !found { - fmt.Fprintf(os.Stderr, "Log Level \"%s\" is not supported, choose from: %s\n", logLevel, strings.Join(logLevels, ", ")) + fmt.Fprintf(os.Stderr, "Log Level %q is not supported, choose from: %s\n", logLevel, strings.Join(logLevels, ", ")) os.Exit(1) } @@ -209,44 +218,45 @@ func loggingHook() { } } -func rootFlags(opts *entities.PodmanConfig, flags *pflag.FlagSet) { - // V2 flags - flags.BoolVarP(&opts.Remote, "remote", "r", false, "Access remote Podman service (default false)") +func rootFlags(cmd *cobra.Command, opts *entities.PodmanConfig) { + cfg := opts.Config + lFlags := cmd.Flags() custom, _ := config.ReadCustomConfig() defaultURI := custom.Engine.RemoteURI if defaultURI == "" { defaultURI = registry.DefaultAPIAddress() } - flags.StringVar(&opts.URI, "url", defaultURI, "URL to access Podman service (CONTAINER_HOST)") - flags.StringVar(&opts.Identity, "identity", custom.Engine.RemoteIdentity, "path to SSH identity file, (CONTAINER_SSHKEY)") - - cfg := opts.Config - flags.StringVar(&cfg.Engine.CgroupManager, "cgroup-manager", cfg.Engine.CgroupManager, "Cgroup manager to use (\"cgroupfs\"|\"systemd\")") - flags.StringVar(&opts.CPUProfile, "cpu-profile", "", "Path for the cpu profiling results") - flags.StringVar(&opts.ConmonPath, "conmon", "", "Path of the conmon binary") - flags.StringVar(&cfg.Engine.NetworkCmdPath, "network-cmd-path", cfg.Engine.NetworkCmdPath, "Path to the command for configuring the network") - flags.StringVar(&cfg.Network.NetworkConfigDir, "cni-config-dir", cfg.Network.NetworkConfigDir, "Path of the configuration directory for CNI networks") - flags.StringVar(&cfg.Containers.DefaultMountsFile, "default-mounts-file", cfg.Containers.DefaultMountsFile, "Path to default mounts file") - flags.StringVar(&cfg.Engine.EventsLogger, "events-backend", cfg.Engine.EventsLogger, `Events backend to use ("file"|"journald"|"none")`) - flags.StringSliceVar(&cfg.Engine.HooksDir, "hooks-dir", cfg.Engine.HooksDir, "Set the OCI hooks directory path (may be set multiple times)") - flags.IntVar(&opts.MaxWorks, "max-workers", (runtime.NumCPU()*3)+1, "The maximum number of workers for parallel operations") - flags.StringVar(&cfg.Engine.Namespace, "namespace", cfg.Engine.Namespace, "Set the libpod namespace, used to create separate views of the containers and pods on the system") - flags.StringVar(&cfg.Engine.StaticDir, "root", "", "Path to the root directory in which data, including images, is stored") - flags.StringVar(&opts.RegistriesConf, "registries-conf", "", "Path to a registries.conf to use for image processing") - flags.StringVar(&opts.Runroot, "runroot", "", "Path to the 'run directory' where all state information is stored") - flags.StringVar(&opts.RuntimePath, "runtime", "", "Path to the OCI-compatible binary used to run containers, default is /usr/bin/runc") + lFlags.BoolVarP(&opts.Remote, "remote", "r", false, "Access remote Podman service (default false)") + lFlags.StringVar(&opts.URI, "url", defaultURI, "URL to access Podman service (CONTAINER_HOST)") + lFlags.StringVar(&opts.Identity, "identity", custom.Engine.RemoteIdentity, "path to SSH identity file, (CONTAINER_SSHKEY)") + + pFlags := cmd.PersistentFlags() + pFlags.StringVar(&cfg.Engine.CgroupManager, "cgroup-manager", cfg.Engine.CgroupManager, "Cgroup manager to use (\"cgroupfs\"|\"systemd\")") + pFlags.StringVar(&opts.CPUProfile, "cpu-profile", "", "Path for the cpu profiling results") + pFlags.StringVar(&opts.ConmonPath, "conmon", "", "Path of the conmon binary") + pFlags.StringVar(&cfg.Engine.NetworkCmdPath, "network-cmd-path", cfg.Engine.NetworkCmdPath, "Path to the command for configuring the network") + pFlags.StringVar(&cfg.Network.NetworkConfigDir, "cni-config-dir", cfg.Network.NetworkConfigDir, "Path of the configuration directory for CNI networks") + pFlags.StringVar(&cfg.Containers.DefaultMountsFile, "default-mounts-file", cfg.Containers.DefaultMountsFile, "Path to default mounts file") + pFlags.StringVar(&cfg.Engine.EventsLogger, "events-backend", cfg.Engine.EventsLogger, `Events backend to use ("file"|"journald"|"none")`) + pFlags.StringSliceVar(&cfg.Engine.HooksDir, "hooks-dir", cfg.Engine.HooksDir, "Set the OCI hooks directory path (may be set multiple times)") + pFlags.IntVar(&opts.MaxWorks, "max-workers", (runtime.NumCPU()*3)+1, "The maximum number of workers for parallel operations") + pFlags.StringVar(&cfg.Engine.Namespace, "namespace", cfg.Engine.Namespace, "Set the libpod namespace, used to create separate views of the containers and pods on the system") + pFlags.StringVar(&cfg.Engine.StaticDir, "root", "", "Path to the root directory in which data, including images, is stored") + pFlags.StringVar(&opts.RegistriesConf, "registries-conf", "", "Path to a registries.conf to use for image processing") + pFlags.StringVar(&opts.Runroot, "runroot", "", "Path to the 'run directory' where all state information is stored") + pFlags.StringVar(&opts.RuntimePath, "runtime", "", "Path to the OCI-compatible binary used to run containers, default is /usr/bin/runc") // -s is deprecated due to conflict with -s on subcommands - flags.StringVar(&opts.StorageDriver, "storage-driver", "", "Select which storage driver is used to manage storage of images and containers (default is overlay)") - flags.StringArrayVar(&opts.StorageOpts, "storage-opt", []string{}, "Used to pass an option to the storage driver") + pFlags.StringVar(&opts.StorageDriver, "storage-driver", "", "Select which storage driver is used to manage storage of images and containers (default is overlay)") + pFlags.StringArrayVar(&opts.StorageOpts, "storage-opt", []string{}, "Used to pass an option to the storage driver") - flags.StringVar(&opts.Engine.TmpDir, "tmpdir", "", "Path to the tmp directory for libpod state content.\n\nNote: use the environment variable 'TMPDIR' to change the temporary storage location for container images, '/var/tmp'.\n") - flags.BoolVar(&opts.Trace, "trace", false, "Enable opentracing output (default false)") + pFlags.StringVar(&opts.Engine.TmpDir, "tmpdir", "", "Path to the tmp directory for libpod state content.\n\nNote: use the environment variable 'TMPDIR' to change the temporary storage location for container images, '/var/tmp'.\n") + pFlags.BoolVar(&opts.Trace, "trace", false, "Enable opentracing output (default false)") // Override default --help information of `--help` global flag var dummyHelp bool - flags.BoolVar(&dummyHelp, "help", false, "Help for podman") - flags.StringVar(&logLevel, "log-level", logLevel, fmt.Sprintf("Log messages above specified level (%s)", strings.Join(logLevels, ", "))) + pFlags.BoolVar(&dummyHelp, "help", false, "Help for podman") + pFlags.StringVar(&logLevel, "log-level", logLevel, fmt.Sprintf("Log messages above specified level (%s)", strings.Join(logLevels, ", "))) // Hide these flags for both ABI and Tunneling for _, f := range []string{ @@ -256,13 +266,13 @@ func rootFlags(opts *entities.PodmanConfig, flags *pflag.FlagSet) { "registries-conf", "trace", } { - if err := flags.MarkHidden(f); err != nil { + if err := pFlags.MarkHidden(f); err != nil { logrus.Warnf("unable to mark %s flag as hidden: %s", f, err.Error()) } } // Only create these flags for ABI connections if !registry.IsRemote() { - flags.BoolVar(&useSyslog, "syslog", false, "Output logging information to syslog as well as the console (default false)") + pFlags.BoolVar(&useSyslog, "syslog", false, "Output logging information to syslog as well as the console (default false)") } } diff --git a/cmd/podman/system/connection.go b/cmd/podman/system/connection.go index 9f80a454b..8ee441446 100644 --- a/cmd/podman/system/connection.go +++ b/cmd/podman/system/connection.go @@ -60,7 +60,6 @@ func init() { }) flags := connectionCmd.Flags() - flags.StringVar(&cOpts.Identity, "identity", "", "path to ssh identity file") flags.IntVarP(&cOpts.Port, "port", "p", 22, "port number for destination") flags.StringVar(&cOpts.UDSPath, "socket-path", "", "path to podman socket on remote host. (default '/run/podman/podman.sock' or '/run/user/{uid}/podman/podman.sock)") } diff --git a/cmd/podman/system/renumber.go b/cmd/podman/system/renumber.go index b90640de3..39cd15dd7 100644 --- a/cmd/podman/system/renumber.go +++ b/cmd/podman/system/renumber.go @@ -22,11 +22,12 @@ var ( ` renumberCommand = &cobra.Command{ - Use: "renumber", - Args: validate.NoArgs, - Short: "Migrate lock numbers", - Long: renumberDescription, - Run: renumber, + Use: "renumber", + Args: validate.NoArgs, + DisableFlagsInUseLine: true, + Short: "Migrate lock numbers", + Long: renumberDescription, + Run: renumber, } ) diff --git a/cmd/podman/system/system.go b/cmd/podman/system/system.go index acf41a32d..7937e6df8 100644 --- a/cmd/podman/system/system.go +++ b/cmd/podman/system/system.go @@ -13,11 +13,10 @@ var ( // Command: podman _system_ systemCmd = &cobra.Command{ - Use: "system", - Short: "Manage podman", - Long: "Manage podman", - TraverseChildren: true, - RunE: validate.SubCommandExists, + Use: "system", + Short: "Manage podman", + Long: "Manage podman", + RunE: validate.SubCommandExists, } ) diff --git a/cmd/podman/system/unshare.go b/cmd/podman/system/unshare.go index e5d41e06d..109bab9ed 100644 --- a/cmd/podman/system/unshare.go +++ b/cmd/podman/system/unshare.go @@ -13,10 +13,11 @@ import ( var ( unshareDescription = "Runs a command in a modified user namespace." unshareCommand = &cobra.Command{ - Use: "unshare [flags] [COMMAND [ARG ...]]", - Short: "Run a command in a modified user namespace", - Long: unshareDescription, - RunE: unshare, + Use: "unshare [COMMAND [ARG ...]]", + DisableFlagsInUseLine: true, + Short: "Run a command in a modified user namespace", + Long: unshareDescription, + RunE: unshare, Example: `podman unshare id podman unshare cat /proc/self/uid_map, podman unshare podman-script.sh`, diff --git a/cmd/podman/validate/args.go b/cmd/podman/validate/args.go index d170447ee..a33f47959 100644 --- a/cmd/podman/validate/args.go +++ b/cmd/podman/validate/args.go @@ -2,6 +2,7 @@ package validate import ( "fmt" + "strconv" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -28,8 +29,119 @@ func IDOrLatestArgs(cmd *cobra.Command, args []string) error { if len(args) > 1 { return fmt.Errorf("`%s` accepts at most one argument", cmd.CommandPath()) } - if len(args) == 0 && !cmd.Flag("latest").Changed { - return fmt.Errorf("`%s` requires a name, id, or the \"--latest\" flag", cmd.CommandPath()) + + latest := cmd.Flag("latest") + if latest != nil { + given, _ := strconv.ParseBool(cmd.Flag("latest").Value.String()) + if len(args) == 0 && !given { + return fmt.Errorf("%q requires a name, id, or the \"--latest\" flag", cmd.CommandPath()) + } + } + 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. +func CheckAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool, cidfile bool) error { + argLen := len(args) + 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") + specifiedLatest, _ := c.Flags().GetBool("latest") + 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("no arguments are needed with --latest") + } 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 +} + +// CheckAllLatestAndPodIDFile checks that --all and --latest are used correctly. +// If withIDFile is set, also check for the --pod-id-file flag. +func CheckAllLatestAndPodIDFile(c *cobra.Command, args []string, ignoreArgLen bool, withIDFile bool) error { + argLen := len(args) + if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { + if !withIDFile { + 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'") + } + } + + specifiedAll, _ := c.Flags().GetBool("all") + specifiedLatest, _ := c.Flags().GetBool("latest") + specifiedPodIDFile := false + if pid, _ := c.Flags().GetStringArray("pod-id-file"); len(pid) > 0 { + specifiedPodIDFile = true + } + + if specifiedPodIDFile && (specifiedAll || specifiedLatest) { + return errors.Errorf("--all, --latest and --pod-id-file 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("no arguments are needed with --latest") + } else if withIDFile && (specifiedLatest || specifiedPodIDFile) { + return errors.Errorf("no arguments are needed with --latest or --pod-id-file") + } + } + + if specifiedPodIDFile { + return nil + } + + if argLen < 1 && !specifiedAll && !specifiedLatest && !specifiedPodIDFile { + return errors.Errorf("you must provide at least one name or id") } return nil } diff --git a/cmd/podman/validate/latest.go b/cmd/podman/validate/latest.go new file mode 100644 index 000000000..6e2aa063b --- /dev/null +++ b/cmd/podman/validate/latest.go @@ -0,0 +1,15 @@ +package validate + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/spf13/cobra" +) + +func AddLatestFlag(cmd *cobra.Command, b *bool) { + // Initialization flag verification + cmd.Flags().BoolVarP(b, "latest", "l", false, + "Act on the latest container podman is aware of\nNot supported with the \"--remote\" flag") + if registry.IsRemote() { + _ = cmd.Flags().MarkHidden("latest") + } +} diff --git a/cmd/podman/volumes/volume.go b/cmd/podman/volumes/volume.go index 12947a6b1..93b15eb38 100644 --- a/cmd/podman/volumes/volume.go +++ b/cmd/podman/volumes/volume.go @@ -13,11 +13,10 @@ var ( // Command: podman _volume_ volumeCmd = &cobra.Command{ - Use: "volume", - Short: "Manage volumes", - Long: "Volumes are created in and can be shared between containers", - TraverseChildren: true, - RunE: validate.SubCommandExists, + Use: "volume", + Short: "Manage volumes", + Long: "Volumes are created in and can be shared between containers", + RunE: validate.SubCommandExists, } ) diff --git a/contrib/remote/containers.conf b/contrib/remote/containers.conf new file mode 100644 index 000000000..45f58171a --- /dev/null +++ b/contrib/remote/containers.conf @@ -0,0 +1,11 @@ +# The containers configuration file specifies all of the available configuration +# command-line options/flags for container engine tools like Podman +# but in a TOML format that can be easily modified and versioned. + +[engine] + +# Default Remote URI to access the Podman service. +# Examples: +# remote rootless ssh://engineering.lab.company.com/run/user/1000/podman/podman.sock +# remote rootfull ssh://root@10.10.1.136:22/run/podman/podman.sock +# remote_uri= "" diff --git a/docs/source/markdown/podman-auto-update.1.md b/docs/source/markdown/podman-auto-update.1.md index 90e581e42..f37280cda 100644 --- a/docs/source/markdown/podman-auto-update.1.md +++ b/docs/source/markdown/podman-auto-update.1.md @@ -4,7 +4,7 @@ podman-auto-update - Auto update containers according to their auto-update policy ## SYNOPSIS -**podman auto-update** +**podman auto-update** [*options*] ## DESCRIPTION `podman auto-update` looks up containers with a specified "io.containers.autoupdate" label (i.e., the auto-update policy). diff --git a/docs/source/markdown/podman-container-exists.1.md b/docs/source/markdown/podman-container-exists.1.md index 3b4ca33e4..d24df2fc8 100644 --- a/docs/source/markdown/podman-container-exists.1.md +++ b/docs/source/markdown/podman-container-exists.1.md @@ -4,7 +4,7 @@ podman-container-exists - Check if a container exists in local storage ## SYNOPSIS -**podman container exists** [*options*] *container* +**podman container exists** *container* ## DESCRIPTION **podman container exists** checks if a container exists in local storage. The **ID** or **Name** diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index 3ec91a3ad..30ac54de2 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -78,9 +78,12 @@ If the host uses cgroups v1, the default is set to **host**. On cgroups v2 the **--cgroups**=*mode* Determines whether the container will create CGroups. -Valid values are *enabled*, *disabled*, *no-conmon*, which the default being *enabled*. +Valid values are *enabled*, *disabled*, *no-conmon*, *split*, which the default being *enabled*. + +The *enabled* option will create a new cgroup under the cgroup-parent. The *disabled* option will force the container to not create CGroups, and thus conflicts with CGroup options (**--cgroupns** and **--cgroup-parent**). The *no-conmon* option disables a new CGroup only for the conmon process. +The *split* option splits the current cgroup in two sub-cgroups: one for conmon and one for the container payload. It is not possible to set *--cgroup-parent* with *split*. **--cgroup-parent**=*path* @@ -623,6 +626,8 @@ When specifying ranges for both, the number of container ports in the range must (e.g., `podman run -p 1234-1236:1222-1224 --name thisWorks -t busybox` but not `podman run -p 1230-1236:1230-1240 --name RangeContainerPortsBiggerThanRangeHostPorts -t busybox`) With ip: `podman run -p 127.0.0.1:$HOSTPORT:$CONTAINERPORT --name CONTAINER -t someimage` +Host port does not have to be specified (e.g. `podman run -p 127.0.0.1::80`). +If it is not, the container port will be randomly assigned a port on the host. Use `podman port` to see the actual mapping: `podman port CONTAINER $CONTAINERPORT` **--publish-all**, **-P**=*true|false* diff --git a/docs/source/markdown/podman-healthcheck-run.1.md b/docs/source/markdown/podman-healthcheck-run.1.md index 21f2d9b20..546d847eb 100644 --- a/docs/source/markdown/podman-healthcheck-run.1.md +++ b/docs/source/markdown/podman-healthcheck-run.1.md @@ -4,7 +4,7 @@ podman\-healthcheck\-run - Run a container healthcheck ## SYNOPSIS -**podman healthcheck run** [*options*] *container* +**podman healthcheck run** *container* ## DESCRIPTION diff --git a/docs/source/markdown/podman-image-exists.1.md b/docs/source/markdown/podman-image-exists.1.md index 3b7127b64..59f2145cc 100644 --- a/docs/source/markdown/podman-image-exists.1.md +++ b/docs/source/markdown/podman-image-exists.1.md @@ -4,7 +4,7 @@ podman-image-exists - Check if an image exists in local storage ## SYNOPSIS -**podman image exists** [*options*] *image* +**podman image exists** *image* ## DESCRIPTION **podman image exists** checks if an image exists in local storage. The **ID** or **Name** diff --git a/docs/source/markdown/podman-manifest-add.1.md b/docs/source/markdown/podman-manifest-add.1.md index 82f2071b9..44815def5 100644 --- a/docs/source/markdown/podman-manifest-add.1.md +++ b/docs/source/markdown/podman-manifest-add.1.md @@ -4,7 +4,7 @@ podman\-manifest\-add - Add an image to a manifest list or image index ## SYNOPSIS -**podman manifest add** *listnameorindexname* *imagename* +**podman manifest add** [*options*] *listnameorindexname* *imagename* ## DESCRIPTION diff --git a/docs/source/markdown/podman-manifest-annotate.1.md b/docs/source/markdown/podman-manifest-annotate.1.md index 4450de7fd..25ad4642e 100644 --- a/docs/source/markdown/podman-manifest-annotate.1.md +++ b/docs/source/markdown/podman-manifest-annotate.1.md @@ -4,7 +4,7 @@ podman\-manifest\-annotate - Add or update information about an entry in a manifest list or image index ## SYNOPSIS -**podman manifest annotate** [options...] *listnameorindexname* *imagemanifestdigest* +**podman manifest annotate** [*options*] *listnameorindexname* *imagemanifestdigest* ## DESCRIPTION diff --git a/docs/source/markdown/podman-manifest-push.1.md b/docs/source/markdown/podman-manifest-push.1.md index ab3287a7c..33b2a24c5 100644 --- a/docs/source/markdown/podman-manifest-push.1.md +++ b/docs/source/markdown/podman-manifest-push.1.md @@ -4,7 +4,7 @@ podman\-manifest\-push - Push a manifest list or image index to a registry ## SYNOPSIS -**podman manifest push** [options...] *listnameorindexname* *transport:details* +**podman manifest push** [*options*] *listnameorindexname* *transport:details* ## DESCRIPTION Pushes a manifest list or image index to a registry. diff --git a/docs/source/markdown/podman-mount.1.md b/docs/source/markdown/podman-mount.1.md index c7bfedb48..eaed1051e 100644 --- a/docs/source/markdown/podman-mount.1.md +++ b/docs/source/markdown/podman-mount.1.md @@ -4,9 +4,9 @@ podman\-mount - Mount a working container's root filesystem ## SYNOPSIS -**podman mount** [*container* ...] +**podman mount** [*options*] [*container* ...] -**podman container mount** [*container* ...] +**podman container mount** [*options*] [*container* ...] ## DESCRIPTION Mounts the specified containers' root file system in a location which can be diff --git a/docs/source/markdown/podman-network-inspect.1.md b/docs/source/markdown/podman-network-inspect.1.md index 86fa2552e..c75c6788a 100644 --- a/docs/source/markdown/podman-network-inspect.1.md +++ b/docs/source/markdown/podman-network-inspect.1.md @@ -4,7 +4,7 @@ podman\-network\-inspect - Displays the raw CNI network configuration for one or more networks ## SYNOPSIS -**podman network inspect** [*network* ...] +**podman network inspect** [*options*] [*network* ...] ## DESCRIPTION Display the raw (JSON format) network configuration. This command is not available for rootless users. diff --git a/docs/source/markdown/podman-network-rm.1.md b/docs/source/markdown/podman-network-rm.1.md index c71f0d8fd..9ce4d1cd8 100644 --- a/docs/source/markdown/podman-network-rm.1.md +++ b/docs/source/markdown/podman-network-rm.1.md @@ -4,7 +4,7 @@ podman\-network\-rm - Remove one or more CNI networks ## SYNOPSIS -**podman network rm** [*network...*] +**podman network rm** [*options*] [*network...*] ## DESCRIPTION Delete one or more Podman networks. diff --git a/docs/source/markdown/podman-pod-prune.1.md b/docs/source/markdown/podman-pod-prune.1.md index 5b74adade..5b4c4661c 100644 --- a/docs/source/markdown/podman-pod-prune.1.md +++ b/docs/source/markdown/podman-pod-prune.1.md @@ -4,7 +4,7 @@ podman-pod-prune - Remove all stopped pods and their containers ## SYNOPSIS -**podman pod prune** +**podman pod prune** [*options*] ## DESCRIPTION **podman pod prune** removes all stopped pods and their containers from local storage. diff --git a/docs/source/markdown/podman-rmi.1.md b/docs/source/markdown/podman-rmi.1.md index 2e093e9c8..58280e831 100644 --- a/docs/source/markdown/podman-rmi.1.md +++ b/docs/source/markdown/podman-rmi.1.md @@ -4,9 +4,9 @@ podman\-rmi - Removes one or more locally stored images ## SYNOPSIS -**podman rmi** *image* [...] +**podman rmi** [*options*] *image* [...] -**podman image rm** *image* [...] +**podman image rm** [*options*] *image* [...] ## DESCRIPTION Removes one or more locally stored images. diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 88666d595..2e2adbc7e 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -89,14 +89,16 @@ Set the cgroup namespace mode for the container. If the host uses cgroups v1, the default is set to **host**. On cgroups v2, the default is **private**. -**--cgroups**=**enabled**|**disabled**|**no-conmon** +**--cgroups**=**enabled**|**disabled**|**no-conmon**|**split** Determines whether the container will create CGroups. -Default is **enabled**. The **disabled** option will force the container -to not create CGroups, and thus conflicts with CGroup options -(**--cgroupns** and **--cgroup-parent**). +Default is **enabled**. + +The **enabled** option will create a new cgroup under the cgroup-parent. +The **disabled** option will force the container to not create CGroups, and thus conflicts with CGroup options (**--cgroupns** and **--cgroup-parent**). The **no-conmon** option disables a new CGroup only for the **conmon** process. +The **split** option splits the current cgroup in two sub-cgroups: one for conmon and one for the container payload. It is not possible to set **--cgroup-parent** with **split**. **--cgroup-parent**=*path* @@ -636,6 +638,9 @@ Both hostPort and containerPort can be specified as a range of ports. When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. +Host port does not have to be specified (e.g. `podman run -p 127.0.0.1::80`). +If it is not, the container port will be randomly assigned a port on the host. + Use **podman port** to see the actual mapping: **podman port $CONTAINER $CONTAINERPORT**. **--publish-all**, **-P**=**true**|**false** diff --git a/docs/source/markdown/podman-system-migrate.1.md b/docs/source/markdown/podman-system-migrate.1.md index 28db56dee..baabfd14b 100644 --- a/docs/source/markdown/podman-system-migrate.1.md +++ b/docs/source/markdown/podman-system-migrate.1.md @@ -4,7 +4,7 @@ podman\-system\-migrate - Migrate existing containers to a new podman version ## SYNOPSIS -**podman system migrate** +**podman system migrate** [*options*] ## DESCRIPTION **podman system migrate** migrates containers to the latest podman version. diff --git a/docs/source/markdown/podman-system-reset.1.md b/docs/source/markdown/podman-system-reset.1.md index 432f275f4..f290e26d5 100644 --- a/docs/source/markdown/podman-system-reset.1.md +++ b/docs/source/markdown/podman-system-reset.1.md @@ -4,7 +4,7 @@ podman\-system\-reset - Reset storage back to initial state ## SYNOPSIS -**podman system reset** +**podman system reset** [*options*] ## DESCRIPTION **podman system reset** removes all pods, containers, images and volumes. diff --git a/docs/source/markdown/podman-system-service.1.md b/docs/source/markdown/podman-system-service.1.md index 3ae414f7a..7d18a6832 100644 --- a/docs/source/markdown/podman-system-service.1.md +++ b/docs/source/markdown/podman-system-service.1.md @@ -13,6 +13,10 @@ If no endpoint is provided, defaults will be used. The default endpoint for a r service is *unix:/run/podman/podman.sock* and rootless is *unix:/$XDG_RUNTIME_DIR/podman/podman.sock* (for example *unix:/run/user/1000/podman/podman.sock*) +The REST API provided by **podman system service** is split into two parts: a compatibility layer offering support for the Docker v1.40 API, and a Podman-native Libpod layer. +Documentation for the latter is available at *https://docs.podman.io/en/latest/_static/api.html*. +Both APIs are versioned, but the server will not reject requests with an unsupported version set. + ## OPTIONS **--time**, **-t** diff --git a/docs/source/markdown/podman-umount.1.md b/docs/source/markdown/podman-umount.1.md index 100c47b32..31a213f28 100644 --- a/docs/source/markdown/podman-umount.1.md +++ b/docs/source/markdown/podman-umount.1.md @@ -4,13 +4,13 @@ podman\-umount - Unmount a working container's root filesystem ## SYNOPSIS -**podman umount** *container* [...] +**podman umount** [*options*] *container* [...] -**podman container umount** *container* [...] +**podman container umount** [*options*] *container* [...] -**podman container unmount** *container* [...] +**podman container unmount** [*options*] *container* [...] -**podman unmount** *container* [...] +**podman unmount** [*options*] *container* [...] ## DESCRIPTION Unmounts the specified containers' root file system, if no other processes diff --git a/docs/source/markdown/podman-unshare.1.md b/docs/source/markdown/podman-unshare.1.md index f2eb02814..239213981 100644 --- a/docs/source/markdown/podman-unshare.1.md +++ b/docs/source/markdown/podman-unshare.1.md @@ -4,7 +4,7 @@ podman\-unshare - Run a command inside of a modified user namespace ## SYNOPSIS -**podman unshare** [*options*] [*--*] [*command*] +**podman unshare** [*--*] [*command*] ## DESCRIPTION Launches a process (by default, *$SHELL*) in a new user namespace. The user diff --git a/docs/source/markdown/podman-untag.1.md b/docs/source/markdown/podman-untag.1.md index c83a0544c..d6ed7f3ea 100644 --- a/docs/source/markdown/podman-untag.1.md +++ b/docs/source/markdown/podman-untag.1.md @@ -4,9 +4,9 @@ podman\-untag - Removes one or more names from a locally-stored image ## SYNOPSIS -**podman untag** [*options*] *image* [*name*[:*tag*]...] +**podman untag** *image* [*name*[:*tag*]...] -**podman image untag** [*options*] *image* [*name*[:*tag*]...] +**podman image untag** *image* [*name*[:*tag*]...] ## DESCRIPTION Remove one or more names from an image in the local storage. The image can be referred to by ID or reference. If a no name is specified, all names are removed the image. If a specified name is a short name and does not include a registry `localhost/` will be prefixed (e.g., `fedora` -> `localhost/fedora`). If a specified name does not include a tag `:latest` will be appended (e.g., `localhost/fedora` -> `localhost/fedora:latest`). @@ -11,7 +11,7 @@ require ( github.com/containernetworking/cni v0.7.2-0.20200304161608-4fae32b84921 github.com/containernetworking/plugins v0.8.6 github.com/containers/buildah v1.15.0 - github.com/containers/common v0.14.0 + github.com/containers/common v0.14.3 github.com/containers/conmon v2.0.18+incompatible github.com/containers/image/v5 v5.5.1 github.com/containers/psgo v1.5.1 @@ -70,6 +70,8 @@ github.com/containers/buildah v1.15.0 h1:p9cYJwcQ5Fnv0iBeHAFwHR0K+kcv7LbyAjUtc+H github.com/containers/buildah v1.15.0/go.mod h1:j0AY2kWpmaOPPV5GKDJY9dMtekk5WMmMhcB+z0OW+vc= github.com/containers/common v0.14.0 h1:hiZFDPf6ajKiDmojN5f5X3gboKPO73NLrYb0RXfrQiA= github.com/containers/common v0.14.0/go.mod h1:9olhlE+WhYof1npnMJdyRMX14/yIUint6zyHzcyRVAg= +github.com/containers/common v0.14.3 h1:LNsRPkap5Q/EqPyhiLKRZg8u629U8CEeoB49ilG6ZR4= +github.com/containers/common v0.14.3/go.mod h1:9olhlE+WhYof1npnMJdyRMX14/yIUint6zyHzcyRVAg= github.com/containers/conmon v2.0.18+incompatible h1:rjwjNnE756NuXcdE/uUmj4kDbrykslPuBMHI31wh43E= github.com/containers/conmon v2.0.18+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/image/v5 v5.4.4/go.mod h1:g7cxNXitiLi6pEr9/L9n/0wfazRuhDKXU15kV86N8h8= diff --git a/hack/man-page-checker b/hack/man-page-checker index 17d85d65d..d2cc6c6e1 100755 --- a/hack/man-page-checker +++ b/hack/man-page-checker @@ -3,6 +3,14 @@ # man-page-checker - validate and cross-reference man page names # +verbose= +for i; do + case "$i" in + -v|--verbose) verbose=verbose ;; + esac +done + + die() { echo "$(basename $0): $*" >&2 exit 1 @@ -65,6 +73,61 @@ for md in $(ls -1 *-*.1.md | grep -v remote);do fi done +# Helper function: compares man page synopsis vs --help usage message +function compare_usage() { + local cmd="$1" + local from_man="$2" + + # Sometimes in CI we run before podman gets built. + test -x ../../../bin/podman || return + + # Run 'cmd --help', grab the line immediately after 'Usage:' + local help_output=$(../../../bin/$cmd --help) + local from_help=$(echo "$help_output" | grep -A1 '^Usage:' | tail -1) + + # strip off command name from both + from_man=$(sed -e "s/\*\*$cmd\*\*[[:space:]]*//" <<<"$from_man") + from_help=$(sed -e "s/^[[:space:]]*$cmd[[:space:]]*//" <<<"$from_help") + + # man page lists 'foo [*options*]', help msg shows 'foo [flags]'. + # Make sure if one has it, the other does too. + if expr "$from_man" : "\[\*options\*\]" >/dev/null; then + if expr "$from_help" : "\[flags\]" >/dev/null; then + : + else + echo "WARNING: $cmd: man page shows '[*options*]', help does not show [flags]" + rc=1 + fi + elif expr "$from_help" : "\[flags\]" >/dev/null; then + echo "WARNING: $cmd: --help shows [flags], man page does not show [*options*]" + rc=1 + fi + + # Strip off options and flags; start comparing arguments + from_man=$(sed -e 's/^\[\*options\*\][[:space:]]*//' <<<"$from_man") + from_help=$(sed -e 's/^\[flags\][[:space:]]*//' <<<"$from_help") + + # Args in man page are '*foo*', in --help are 'FOO'. Convert all to + # UPCASE simply because it stands out better to the eye. + from_man=$(sed -e 's/\*\([a-z-]\+\)\*/\U\1/g' <<<"$from_man") + + # FIXME: one of the common patterns is for --help to show 'POD [POD...]' + # but man page show 'pod ...'. This conversion may help one day, but + # not yet: there are too many inconsistencies such as '[pod ...]' + # (brackets) and 'pod...' (no space between). +# from_help=$(sed -e 's/\([A-Z]\+\)[[:space:]]\+\[\1[[:space:]]*\.\.\.\]/\1 .../' <<<"$from_help") + + # Compare man-page and --help usage strings. For now, do so only + # when run with --verbose. + if [[ "$from_man" != "$from_help" ]]; then + if [ -n "$verbose" ]; then + printf "%-25s man='%s' help='%s'\n" "$cmd:" "$from_man" "$from_help" + # Yeah, we're not going to enable this as a blocker any time soon. + # rc=1 + fi + fi +} + # Pass 3: compare synopses. # # Make sure the SYNOPSIS line in podman-foo.1.md reads '**podman foo** ...' @@ -87,9 +150,7 @@ for md in *.1.md;do cmd=$(echo "$synopsis" | sed -e 's/\(.*\)\*\*.*/\1/' | tr -d \*) md_nodash=$(basename "$md" .1.md | tr '-' ' ') if [[ $md_nodash = 'podman auto update' ]]; then - # podman-auto-update.1.md is special cased as it's structure differs - # from that of other man pages where main and sub-commands split by - # dashes. + # special case: the command is "auto-update", with a hyphen md_nodash='podman auto-update' fi if [ "$cmd" != "$md_nodash" -a "$cmd" != "podman-remote" ]; then @@ -111,8 +172,9 @@ for md in *.1.md;do # (for debugging, and getting a sense of standard conventions) #printf " %-32s ------ '%s'\n" $md "$synopsis" - # FIXME: some day: run ./bin/podman "args", extract Usage, - # strip off [flags] and [options], then compare arguments + # If bin/podman is available, run "cmd --help" and compare Usage + # messages. This is complicated, so do it in a helper function. + compare_usage "$md_nodash" "$synopsis" done diff --git a/libpod/container.go b/libpod/container.go index c85249676..20688e3ee 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -17,6 +17,7 @@ import ( "github.com/containers/libpod/libpod/lock" "github.com/containers/libpod/pkg/namespaces" "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/utils" "github.com/containers/storage" "github.com/cri-o/ocicni/pkg/ocicni" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -1089,10 +1090,25 @@ func (c *Container) NamespacePath(linuxNS LinuxNS) (string, error) { //nolint:in // CGroupPath returns a cgroups "path" for a given container. func (c *Container) CGroupPath() (string, error) { - switch c.runtime.config.Engine.CgroupManager { - case config.CgroupfsCgroupsManager: + switch { + case c.config.CgroupsMode == cgroupSplit: + if c.config.CgroupParent != "" { + return "", errors.Errorf("cannot specify cgroup-parent with cgroup-mode %q", cgroupSplit) + } + cg, err := utils.GetCgroupProcess(c.state.ConmonPID) + if err != nil { + return "", err + } + // Use the conmon cgroup for two reasons: we validate the container + // delegation was correct, and the conmon cgroup doesn't change at runtime + // while we are not sure about the container that can create sub cgroups. + if !strings.HasSuffix(cg, "supervisor") { + return "", errors.Errorf("invalid cgroup for conmon %q", cg) + } + return strings.TrimSuffix(cg, "/supervisor") + "/container", nil + case c.runtime.config.Engine.CgroupManager == config.CgroupfsCgroupsManager: return filepath.Join(c.config.CgroupParent, fmt.Sprintf("libpod-%s", c.ID())), nil - case config.SystemdCgroupsManager: + case c.runtime.config.Engine.CgroupManager == config.SystemdCgroupsManager: if rootless.IsRootless() { uid := rootless.GetRootlessUID() parts := strings.SplitN(c.config.CgroupParent, "/", 2) diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 5ee6726e0..2c78f6bd2 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -31,6 +31,7 @@ import ( "github.com/containers/libpod/pkg/resolvconf" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" + "github.com/containers/libpod/utils" "github.com/containers/storage/pkg/archive" securejoin "github.com/cyphar/filepath-securejoin" "github.com/opencontainers/runc/libcontainer/user" @@ -1505,8 +1506,17 @@ func (c *Container) getOCICgroupPath() (string, error) { switch { case (rootless.IsRootless() && !unified) || c.config.NoCgroups: return "", nil + case c.config.CgroupsMode == cgroupSplit: + if c.config.CgroupParent != "" { + return c.config.CgroupParent, nil + } + selfCgroup, err := utils.GetOwnCgroup() + if err != nil { + return "", err + } + return filepath.Join(selfCgroup, "container"), nil case c.runtime.config.Engine.CgroupManager == config.SystemdCgroupsManager: - // When runc is set to use Systemd as a cgroup manager, it + // When the OCI runtime is set to use Systemd as a cgroup manager, it // expects cgroups to be passed as follows: // slice:prefix:name systemdCgroups := fmt.Sprintf("%s:libpod:%s", path.Base(c.config.CgroupParent), c.ID()) diff --git a/libpod/container_validate.go b/libpod/container_validate.go index b7f0aadff..a53a1839d 100644 --- a/libpod/container_validate.go +++ b/libpod/container_validate.go @@ -34,6 +34,10 @@ func (c *Container) validate() error { return errors.Wrapf(define.ErrInvalidArg, "cannot both create a network namespace and join another container's network namespace") } + if c.config.CgroupsMode == cgroupSplit && c.config.CgroupParent != "" { + return errors.Wrapf(define.ErrInvalidArg, "cannot specify --cgroup-mode=split with a cgroup-parent") + } + // Not creating cgroups has a number of requirements, mostly related to // the PID namespace. if c.config.NoCgroups || c.config.CgroupsMode == "disabled" { diff --git a/libpod/oci_conmon.go b/libpod/oci_conmon.go new file mode 100644 index 000000000..74060b357 --- /dev/null +++ b/libpod/oci_conmon.go @@ -0,0 +1,7 @@ +package libpod + +const ( + // cgroupSplit is the cgroup mode for reusing the current cgroup both + // for conmon and for the container payload. + cgroupSplit = "split" +) diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index d8a89047e..26e5d70b0 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -881,6 +881,12 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co return err } + if ctr.config.CgroupsMode == cgroupSplit { + if err := utils.MoveUnderCgroupSubtree("supervisor"); err != nil { + return err + } + } + args := r.sharedConmonArgs(ctr, ctr.ID(), ctr.bundlePath(), filepath.Join(ctr.state.RunDir, "pidfile"), ctr.LogPath(), r.exitsDir, ociLog, ctr.LogDriver(), logTag) if ctr.config.Spec.Process.Terminal { @@ -1173,7 +1179,7 @@ func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, p "--socket-dir-path", r.socketsDir, } - if r.cgroupManager == config.SystemdCgroupsManager && !ctr.config.NoCgroups { + if r.cgroupManager == config.SystemdCgroupsManager && !ctr.config.NoCgroups && ctr.config.CgroupsMode != cgroupSplit { args = append(args, "-s") } @@ -1275,7 +1281,7 @@ func (r *ConmonOCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec // If cgroup creation is disabled - just signal. switch ctr.config.CgroupsMode { - case "disabled", "no-conmon": + case "disabled", "no-conmon", cgroupSplit: mustCreateCgroup = false } diff --git a/libpod/options.go b/libpod/options.go index cfc0397e3..4041fb1cf 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1049,7 +1049,7 @@ func WithCgroupsMode(mode string) CtrCreateOption { case "disabled": ctr.config.NoCgroups = true ctr.config.CgroupsMode = mode - case "enabled", "no-conmon": + case "enabled", "no-conmon", cgroupSplit: ctr.config.CgroupsMode = mode default: return errors.Wrapf(define.ErrInvalidArg, "Invalid cgroup mode %q", mode) diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 8bb6a4bcf..74647dab8 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -233,9 +233,9 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai return nil, errors.Wrapf(err, "error retrieving pod %s cgroup", pod.ID()) } ctr.config.CgroupParent = podCgroup - case rootless.IsRootless(): + case rootless.IsRootless() && ctr.config.CgroupsMode != cgroupSplit: ctr.config.CgroupParent = SystemdDefaultRootlessCgroupParent - default: + case ctr.config.CgroupsMode != cgroupSplit: ctr.config.CgroupParent = SystemdDefaultCgroupParent } } else if len(ctr.config.CgroupParent) < 6 || !strings.HasSuffix(path.Base(ctr.config.CgroupParent), ".slice") { diff --git a/libpod/volume.go b/libpod/volume.go index ac5f61255..58d1f81a6 100644 --- a/libpod/volume.go +++ b/libpod/volume.go @@ -165,7 +165,7 @@ func (v *Volume) Config() (*VolumeConfig, error) { // VolumeInUse goes through the container dependencies of a volume // and checks if the volume is being used by any container. -func (v *Volume) VolumesInUse() ([]string, error) { +func (v *Volume) VolumeInUse() ([]string, error) { v.lock.Lock() defer v.lock.Unlock() @@ -174,3 +174,13 @@ func (v *Volume) VolumesInUse() ([]string, error) { } return v.runtime.state.VolumeInUse(v) } + +// IsDangling returns whether this volume is dangling (unused by any +// containers). +func (v *Volume) IsDangling() (bool, error) { + ctrs, err := v.VolumeInUse() + if err != nil { + return false, err + } + return len(ctrs) == 0, nil +} diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go index 0f1eca5e5..7209255d7 100644 --- a/pkg/api/handlers/compat/networks.go +++ b/pkg/api/handlers/compat/networks.go @@ -285,7 +285,7 @@ func RemoveNetwork(w http.ResponseWriter, r *http.Request) { return } if !exists { - utils.Error(w, "network not found", http.StatusNotFound, err) + utils.Error(w, "network not found", http.StatusNotFound, network.ErrNetworkNotFound) return } if err := network.RemoveNetwork(config, name); err != nil { diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index bd6a99b96..5b2f8bea2 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -173,6 +173,10 @@ func (s *APIServer) Serve() error { }() } + // Before we start serving, ensure umask is properly set for container + // creation. + _ = syscall.Umask(0022) + go func() { err := s.Server.Serve(s.Listener) if err != nil && err != http.ErrServerClosed { diff --git a/pkg/domain/filters/volumes.go b/pkg/domain/filters/volumes.go index f97c3f570..b1b5e6319 100644 --- a/pkg/domain/filters/volumes.go +++ b/pkg/domain/filters/volumes.go @@ -61,6 +61,29 @@ func GenerateVolumeFilters(filters map[string][]string) ([]libpod.VolumeFilter, } return false }) + case "dangling": + danglingVal := val + invert := false + switch strings.ToLower(danglingVal) { + case "true", "1": + // Do nothing + case "false", "0": + // Dangling=false requires that we + // invert the result of IsDangling. + invert = true + default: + return nil, errors.Errorf("%q is not a valid value for the \"dangling\" filter - must be true or false", danglingVal) + } + vf = append(vf, func(v *libpod.Volume) bool { + dangling, err := v.IsDangling() + if err != nil { + return false + } + if invert { + return !dangling + } + return dangling + }) default: return nil, errors.Errorf("%q is in an invalid volume filter", filter) } diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 90002326e..0511289ab 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -330,7 +330,7 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System if err != nil { return nil, err } - inUse, err := v.VolumesInUse() + inUse, err := v.VolumeInUse() if err != nil { return nil, err } diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go index b07eb7f9a..cbac2cb06 100644 --- a/pkg/ps/ps.go +++ b/pkg/ps/ps.go @@ -145,11 +145,15 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities } return nil }) - if batchErr != nil { return entities.ListContainer{}, batchErr } + portMappings, err := ctr.PortMappings() + if err != nil { + return entities.ListContainer{}, err + } + ps := entities.ListContainer{ Command: conConfig.Command, Created: conConfig.CreatedTime.Unix(), @@ -165,7 +169,7 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities Names: []string{conConfig.Name}, Pid: pid, Pod: conConfig.Pod, - Ports: conConfig.PortMappings, + Ports: portMappings, Size: size, StartedAt: startedTime.Unix(), State: conState.String(), diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index e19c582b5..a04afa00f 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -287,10 +287,11 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l options = append(options, libpod.WithCommand(c.UserCommand)) } - // Add entrypoint unconditionally - // If it's empty it's because it was explicitly set to "" or the image - // does not have one - options = append(options, libpod.WithEntrypoint(c.Entrypoint)) + // Add entrypoint if it was set + // If it's empty it's because it was explicitly set to "" + if c.Entrypoint != nil { + options = append(options, libpod.WithEntrypoint(c.Entrypoint)) + } // TODO: MNT, USER, CGROUP options = append(options, libpod.WithStopSignal(c.StopSignal)) diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index 1c34f622b..badb34999 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -52,10 +52,14 @@ func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error { if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rlimit); err != nil { logrus.Warnf("failed to return RLIMIT_NOFILE ulimit %q", err) } - current = rlimit.Cur - max = rlimit.Max + if rlimit.Cur < current { + current = rlimit.Cur + } + if rlimit.Max < max { + max = rlimit.Max + } } - g.AddProcessRlimits("RLIMIT_NOFILE", current, max) + g.AddProcessRlimits("RLIMIT_NOFILE", max, current) } if !nprocSet { max := kernelMax @@ -65,10 +69,14 @@ func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error { if err := unix.Getrlimit(unix.RLIMIT_NPROC, &rlimit); err != nil { logrus.Warnf("failed to return RLIMIT_NPROC ulimit %q", err) } - current = rlimit.Cur - max = rlimit.Max + if rlimit.Cur < current { + current = rlimit.Cur + } + if rlimit.Max < max { + max = rlimit.Max + } } - g.AddProcessRlimits("RLIMIT_NPROC", current, max) + g.AddProcessRlimits("RLIMIT_NPROC", max, current) } return nil diff --git a/pkg/specgen/generate/ports.go b/pkg/specgen/generate/ports.go index 91c8e68d1..5c06d3bc3 100644 --- a/pkg/specgen/generate/ports.go +++ b/pkg/specgen/generate/ports.go @@ -43,6 +43,8 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, containerPortValidate[proto] = make(map[string]map[uint16]uint16) } + postAssignHostPort := false + // Iterate through all port mappings, generating OCICNI PortMapping // structs and validating there is no overlap. for _, port := range portMappings { @@ -71,9 +73,6 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, return nil, nil, nil, errors.Errorf("container port number must be non-0") } hostPort := port.HostPort - if hostPort == 0 { - hostPort = containerPort - } if uint32(len-1)+uint32(containerPort) > 65535 { return nil, nil, nil, errors.Errorf("container port range exceeds maximum allowable port number") } @@ -105,26 +104,42 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, cPort := containerPort + index hPort := hostPort + index - if cPort == 0 || hPort == 0 { - return nil, nil, nil, errors.Errorf("host and container ports cannot be 0") - } - - testCPort := ctrPortMap[cPort] - if testCPort != 0 && testCPort != hPort { - // This is an attempt to redefine a port - return nil, nil, nil, errors.Errorf("conflicting port mappings for container port %d (protocol %s)", cPort, p) + if cPort == 0 { + return nil, nil, nil, errors.Errorf("container port cannot be 0") } - ctrPortMap[cPort] = hPort - testHPort := hostPortMap[hPort] - if testHPort != 0 && testHPort != cPort { - return nil, nil, nil, errors.Errorf("conflicting port mappings for host port %d (protocol %s)", hPort, p) - } - hostPortMap[hPort] = cPort - - // If we have an exact duplicate, just continue - if testCPort == hPort && testHPort == cPort { - continue + // Host port is allowed to be 0. If it is, we + // select a random port on the host. + // This will happen *after* all other ports are + // placed, to ensure we don't accidentally + // select a port that a later mapping wanted. + if hPort == 0 { + // If we already have a host port + // assigned to their container port - + // just use that. + if ctrPortMap[cPort] != 0 { + hPort = ctrPortMap[cPort] + } else { + postAssignHostPort = true + } + } else { + testCPort := ctrPortMap[cPort] + if testCPort != 0 && testCPort != hPort { + // This is an attempt to redefine a port + return nil, nil, nil, errors.Errorf("conflicting port mappings for container port %d (protocol %s)", cPort, p) + } + ctrPortMap[cPort] = hPort + + testHPort := hostPortMap[hPort] + if testHPort != 0 && testHPort != cPort { + return nil, nil, nil, errors.Errorf("conflicting port mappings for host port %d (protocol %s)", hPort, p) + } + hostPortMap[hPort] = cPort + + // If we have an exact duplicate, just continue + if testCPort == hPort && testHPort == cPort { + continue + } } // We appear to be clear. Make an OCICNI port @@ -142,6 +157,61 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, } } + // Handle any 0 host ports now by setting random container ports. + if postAssignHostPort { + remadeMappings := make([]ocicni.PortMapping, 0, len(finalMappings)) + + // Iterate over all + for _, p := range finalMappings { + if p.HostPort != 0 { + remadeMappings = append(remadeMappings, p) + continue + } + + hostIPMap := hostPortValidate[p.Protocol] + ctrIPMap := containerPortValidate[p.Protocol] + + hostPortMap, ok := hostIPMap[p.HostIP] + if !ok { + hostPortMap = make(map[uint16]uint16) + hostIPMap[p.HostIP] = hostPortMap + } + ctrPortMap, ok := ctrIPMap[p.HostIP] + if !ok { + ctrPortMap = make(map[uint16]uint16) + ctrIPMap[p.HostIP] = ctrPortMap + } + + // See if container port has been used elsewhere + if ctrPortMap[uint16(p.ContainerPort)] != 0 { + // Duplicate definition. Let's not bother + // including it. + continue + } + + // Max retries to ensure we don't loop forever. + for i := 0; i < 15; i++ { + candidate, err := getRandomPort() + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "error getting candidate host port for container port %d", p.ContainerPort) + } + + if hostPortMap[uint16(candidate)] == 0 { + logrus.Debugf("Successfully assigned container port %d to host port %d (IP %s Protocol %s)", p.ContainerPort, candidate, p.HostIP, p.Protocol) + hostPortMap[uint16(candidate)] = uint16(p.ContainerPort) + ctrPortMap[uint16(p.ContainerPort)] = uint16(candidate) + p.HostPort = int32(candidate) + break + } + } + if p.HostPort == 0 { + return nil, nil, nil, errors.Errorf("could not find open host port to map container port %d to", p.ContainerPort) + } + remadeMappings = append(remadeMappings, p) + } + return remadeMappings, containerPortValidate, hostPortValidate, nil + } + return finalMappings, containerPortValidate, hostPortValidate, nil } diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index 3d5bf03e5..361f09379 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -435,7 +435,8 @@ type PortMapping struct { ContainerPort uint16 `json:"container_port"` // HostPort is the port number that will be forwarded from the host into // the container. - // If omitted, will be assumed to be identical to + // If omitted, a random port on the host (guaranteed to be over 1024) + // will be assigned. HostPort uint16 `json:"host_port,omitempty"` // Range is the number of ports that will be forwarded, starting at // HostPort and ContainerPort and counting up. diff --git a/pkg/systemd/generate/common.go b/pkg/systemd/generate/common.go index fe56dc874..d6d18a810 100644 --- a/pkg/systemd/generate/common.go +++ b/pkg/systemd/generate/common.go @@ -1,6 +1,8 @@ package generate import ( + "strings" + "github.com/pkg/errors" ) @@ -44,6 +46,9 @@ func filterPodFlags(command []string) []string { i++ continue } + if strings.HasPrefix(s, "--pod=") || strings.HasPrefix(s, "--pod-id-file=") { + continue + } processed = append(processed, s) } return processed diff --git a/pkg/systemd/generate/common_test.go b/pkg/systemd/generate/common_test.go index f53bb7828..389c30f59 100644 --- a/pkg/systemd/generate/common_test.go +++ b/pkg/systemd/generate/common_test.go @@ -1,6 +1,7 @@ package generate import ( + "strings" "testing" "github.com/stretchr/testify/assert" @@ -14,12 +15,16 @@ func TestFilterPodFlags(t *testing.T) { {[]string{"podman", "pod", "create"}}, {[]string{"podman", "pod", "create", "--name", "foo"}}, {[]string{"podman", "pod", "create", "--pod-id-file", "foo"}}, + {[]string{"podman", "pod", "create", "--pod-id-file=foo"}}, {[]string{"podman", "run", "--pod", "foo"}}, + {[]string{"podman", "run", "--pod=foo"}}, } for _, test := range tests { processed := filterPodFlags(test.input) - assert.NotContains(t, processed, "--pod-id-file") - assert.NotContains(t, processed, "--pod") + for _, s := range processed { + assert.False(t, strings.HasPrefix(s, "--pod-id-file")) + assert.False(t, strings.HasPrefix(s, "--pod")) + } } } diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go index bf6cb81b8..333f8ef88 100644 --- a/pkg/systemd/generate/containers.go +++ b/pkg/systemd/generate/containers.go @@ -69,8 +69,6 @@ type containerInfo struct { const containerTemplate = headerTemplate + ` {{- if .BoundToServices}} -RefuseManualStart=yes -RefuseManualStop=yes BindsTo={{- range $index, $value := .BoundToServices -}}{{if $index}} {{end}}{{ $value }}.service{{end}} After={{- range $index, $value := .BoundToServices -}}{{if $index}} {{end}}{{ $value }}.service{{end}} {{- end}} diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/containers_test.go index 80f0996a1..e108251ea 100644 --- a/pkg/systemd/generate/containers_test.go +++ b/pkg/systemd/generate/containers_test.go @@ -88,8 +88,6 @@ Description=Podman container-foobar.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RefuseManualStart=yes -RefuseManualStop=yes BindsTo=a.service b.service c.service pod.service After=a.service b.service c.service pod.service diff --git a/pkg/terminal/console_unix.go b/pkg/terminal/console_unix.go new file mode 100644 index 000000000..6eee6aa2f --- /dev/null +++ b/pkg/terminal/console_unix.go @@ -0,0 +1,8 @@ +// +build !windows + +package terminal + +// SetConsole for non-windows environments is a no-op +func SetConsole() error { + return nil +} diff --git a/pkg/terminal/console_windows.go b/pkg/terminal/console_windows.go new file mode 100644 index 000000000..c7691857c --- /dev/null +++ b/pkg/terminal/console_windows.go @@ -0,0 +1,37 @@ +// +build windows + +package terminal + +import ( + "github.com/sirupsen/logrus" + "golang.org/x/sys/windows" +) + +// SetConsole switches the windows terminal mode to be able to handle colors, etc +func SetConsole() error { + if err := setConsoleMode(windows.Stdout, windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err != nil { + return err + } + if err := setConsoleMode(windows.Stderr, windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err != nil { + return err + } + if err := setConsoleMode(windows.Stdin, windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err != nil { + return err + } + return nil +} + +func setConsoleMode(handle windows.Handle, flags uint32) error { + var mode uint32 + err := windows.GetConsoleMode(handle, &mode) + if err != nil { + return err + } + if err := windows.SetConsoleMode(handle, mode|flags); err != nil { + // In similar code, it is not considered an error if we cannot set the + // console mode. Following same line of thinking here. + logrus.WithError(err).Error("Failed to set console mode for cli") + } + + return nil +} diff --git a/test/e2e/inspect_test.go b/test/e2e/inspect_test.go index 2fad38a36..ed7876d8a 100644 --- a/test/e2e/inspect_test.go +++ b/test/e2e/inspect_test.go @@ -7,6 +7,7 @@ import ( . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/opencontainers/selinux/go-selinux" ) var _ = Describe("Podman inspect", func() { @@ -263,4 +264,29 @@ var _ = Describe("Podman inspect", func() { Expect(len(baseJSON)).To(Equal(1)) Expect(baseJSON[0].Name).To(Equal(ctrName)) }) + + It("podman inspect - HostConfig.SecurityOpt ", func() { + if !selinux.GetEnabled() { + Skip("SELinux not enabled") + } + + ctrName := "hugo" + create := podmanTest.PodmanNoCache([]string{ + "create", "--name", ctrName, + "--security-opt", "seccomp=unconfined", + "--security-opt", "label=type:spc_t", + "--security-opt", "label=level:s0", + ALPINE, "sh"}) + + create.WaitWithDefaultTimeout() + Expect(create.ExitCode()).To(Equal(0)) + + baseInspect := podmanTest.Podman([]string{"inspect", ctrName}) + baseInspect.WaitWithDefaultTimeout() + Expect(baseInspect.ExitCode()).To(Equal(0)) + baseJSON := baseInspect.InspectContainerToJSON() + Expect(len(baseJSON)).To(Equal(1)) + Expect(baseJSON[0].HostConfig.SecurityOpt).To(Equal([]string{"label=type:spc_t,label=level:s0", "seccomp=unconfined"})) + }) + }) diff --git a/test/e2e/ps_test.go b/test/e2e/ps_test.go index 0dc8e01af..cfc0a415e 100644 --- a/test/e2e/ps_test.go +++ b/test/e2e/ps_test.go @@ -449,4 +449,21 @@ var _ = Describe("Podman ps", func() { Expect(len(output)).To(Equal(1)) Expect(output[0]).To(Equal(ctrName)) }) + + It("podman ps test with port shared with pod", func() { + podName := "testPod" + pod := podmanTest.Podman([]string{"pod", "create", "-p", "8080:80", "--name", podName}) + pod.WaitWithDefaultTimeout() + Expect(pod.ExitCode()).To(Equal(0)) + + ctrName := "testCtr" + session := podmanTest.Podman([]string{"run", "--name", ctrName, "-dt", "--pod", podName, ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + ps := podmanTest.Podman([]string{"ps", "--filter", fmt.Sprintf("name=%s", ctrName), "--format", "{{.Ports}}"}) + ps.WaitWithDefaultTimeout() + Expect(ps.ExitCode()).To(Equal(0)) + Expect(ps.OutputToString()).To(ContainSubstring("0.0.0.0:8080->80/tcp")) + }) }) diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index afba12ccd..cdfbd5530 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -184,6 +184,30 @@ var _ = Describe("Podman run networking", func() { Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostIP).To(Equal("")) }) + It("podman run -p 127.0.0.1::8080/udp", func() { + name := "testctr" + session := podmanTest.Podman([]string{"create", "-t", "-p", "127.0.0.1::8080/udp", "--name", name, ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + inspectOut := podmanTest.InspectContainer(name) + Expect(len(inspectOut)).To(Equal(1)) + Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1)) + Expect(len(inspectOut[0].NetworkSettings.Ports["8080/udp"])).To(Equal(1)) + Expect(inspectOut[0].NetworkSettings.Ports["8080/udp"][0].HostPort).To(Not(Equal("8080"))) + Expect(inspectOut[0].NetworkSettings.Ports["8080/udp"][0].HostIP).To(Equal("127.0.0.1")) + }) + + It("podman run -p :8080", func() { + name := "testctr" + session := podmanTest.Podman([]string{"create", "-t", "-p", ":8080", "--name", name, ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + inspectOut := podmanTest.InspectContainer(name) + Expect(len(inspectOut)).To(Equal(1)) + Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1)) + Expect(len(inspectOut[0].NetworkSettings.Ports["8080/tcp"])).To(Equal(1)) + Expect(inspectOut[0].NetworkSettings.Ports["8080/tcp"][0].HostPort).To(Not(Equal("8080"))) + Expect(inspectOut[0].NetworkSettings.Ports["8080/tcp"][0].HostIP).To(Equal("")) + }) + It("podman run network expose host port 80 to container port 8000", func() { SkipIfRootless() session := podmanTest.Podman([]string{"run", "-dt", "-p", "80:8000", ALPINE, "/bin/sh"}) diff --git a/test/e2e/volume_ls_test.go b/test/e2e/volume_ls_test.go index 7664e64bb..d2d75af9e 100644 --- a/test/e2e/volume_ls_test.go +++ b/test/e2e/volume_ls_test.go @@ -1,6 +1,7 @@ package integration import ( + "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -82,4 +83,30 @@ var _ = Describe("Podman volume ls", func() { Expect(len(session.OutputToStringArray())).To(Equal(2)) Expect(session.OutputToStringArray()[1]).To(ContainSubstring(volName)) }) + + It("podman volume ls with --filter dangling", func() { + volName1 := "volume1" + session := podmanTest.Podman([]string{"volume", "create", volName1}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + volName2 := "volume2" + session2 := podmanTest.Podman([]string{"volume", "create", volName2}) + session2.WaitWithDefaultTimeout() + Expect(session2.ExitCode()).To(Equal(0)) + + ctr := podmanTest.Podman([]string{"create", "-v", fmt.Sprintf("%s:/test", volName2), ALPINE, "sh"}) + ctr.WaitWithDefaultTimeout() + Expect(ctr.ExitCode()).To(Equal(0)) + + lsNoDangling := podmanTest.Podman([]string{"volume", "ls", "--filter", "dangling=false", "--quiet"}) + lsNoDangling.WaitWithDefaultTimeout() + Expect(lsNoDangling.ExitCode()).To(Equal(0)) + Expect(lsNoDangling.OutputToString()).To(ContainSubstring(volName2)) + + lsDangling := podmanTest.Podman([]string{"volume", "ls", "--filter", "dangling=true", "--quiet"}) + lsDangling.WaitWithDefaultTimeout() + Expect(lsDangling.ExitCode()).To(Equal(0)) + Expect(lsDangling.OutputToString()).To(ContainSubstring(volName1)) + }) }) diff --git a/test/python/dockerpy/README.md b/test/python/dockerpy/README.md new file mode 100644 index 000000000..22908afc6 --- /dev/null +++ b/test/python/dockerpy/README.md @@ -0,0 +1,40 @@ +# Dockerpy regression test + +Python test suite to validate Podman endpoints using dockerpy library + +## Running Tests + +To run the tests locally in your sandbox (Fedora 32): + +```shell script +# dnf install python3-docker +``` + +### Run the entire test suite + +```shell +# cd test/python/dockerpy +# PYTHONPATH=/usr/bin/python python -m unittest discover . +``` + +Passing the -v option to your test script will instruct unittest.main() to enable a higher level of verbosity, and produce detailed output: + +```shell +# cd test/python/dockerpy +# PYTHONPATH=/usr/bin/python python -m unittest -v discover . +``` + +### Run a specific test class + +```shell +# cd test/python/dockerpy +# PYTHONPATH=/usr/bin/python python -m unittest -v tests.test_images +``` + +### Run a specific test within the test class + +```shell +# cd test/python/dockerpy +# PYTHONPATH=/usr/bin/python python -m unittest tests.test_images.TestImages.test_import_image + +``` diff --git a/test/test_dockerpy/__init__.py b/test/python/dockerpy/__init__.py index e69de29bb..e69de29bb 100644 --- a/test/test_dockerpy/__init__.py +++ b/test/python/dockerpy/__init__.py diff --git a/test/python/dockerpy/tests/__init__.py b/test/python/dockerpy/tests/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/test/python/dockerpy/tests/__init__.py diff --git a/test/python/dockerpy/tests/common.py b/test/python/dockerpy/tests/common.py new file mode 100644 index 000000000..f83f4076f --- /dev/null +++ b/test/python/dockerpy/tests/common.py @@ -0,0 +1,105 @@ +import os +import pathlib +import subprocess +import sys +import time + +from docker import APIClient + +from . import constant + +alpineDict = { + "name": "docker.io/library/alpine:latest", + "shortName": "alpine", + "tarballName": "alpine.tar" +} + + +def get_client(): + client = APIClient(base_url="http://localhost:8080", timeout=15) + return client + + +client = get_client() + + +def podman(): + binary = os.getenv("PODMAN_BINARY") + if binary is None: + binary = "../../../bin/podman" + return binary + + +def restore_image_from_cache(TestClass): + alpineImage = os.path.join(constant.ImageCacheDir, + alpineDict["tarballName"]) + if not os.path.exists(alpineImage): + os.makedirs(constant.ImageCacheDir, exist_ok=True) + client.pull(constant.ALPINE) + image = client.get_image(constant.ALPINE) + tarball = open(alpineImage, mode="wb") + for frame in image: + tarball.write(frame) + tarball.close() + else: + subprocess.run( + [podman(), "load", "-i", alpineImage], + shell=False, + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True, + ) + + +def flush_image_cache(TestCase): + for f in pathlib.Path(constant.ImageCacheDir).glob("*"): + f.unlink(f) + + +def run_top_container(): + c = client.create_container(image=constant.ALPINE, + command='/bin/sleep 5', + name=constant.TOP) + client.start(container=c.get("Id")) + return c.get("Id") + + +def enable_sock(TestClass): + TestClass.podman = subprocess.Popen( + [ + podman(), "system", "service", "tcp:localhost:8080", + "--log-level=debug", "--time=0" + ], + shell=False, + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + time.sleep(2) + + +def terminate_connection(TestClass): + TestClass.podman.terminate() + stdout, stderr = TestClass.podman.communicate(timeout=0.5) + if stdout: + print("\nService Stdout:\n" + stdout.decode('utf-8')) + if stderr: + print("\nService Stderr:\n" + stderr.decode('utf-8')) + + if TestClass.podman.returncode > 0: + sys.stderr.write("podman exited with error code {}\n".format( + TestClass.podman.returncode)) + sys.exit(2) + + +def remove_all_containers(): + containers = client.containers(quiet=True) + for c in containers: + client.remove_container(container=c.get("Id"), force=True) + + +def remove_all_images(): + allImages = client.images() + for image in allImages: + client.remove_image(image, force=True) diff --git a/test/python/dockerpy/tests/constant.py b/test/python/dockerpy/tests/constant.py new file mode 100644 index 000000000..b44442d02 --- /dev/null +++ b/test/python/dockerpy/tests/constant.py @@ -0,0 +1,13 @@ +BB = "docker.io/library/busybox:latest" +NGINX = "docker.io/library/nginx:latest" +ALPINE = "docker.io/library/alpine:latest" +ALPINE_SHORTNAME = "alpine" +ALPINELISTTAG = "docker.io/library/alpine:3.10.2" +ALPINELISTDIGEST = "docker.io/library/alpine@sha256:72c42ed48c3a2db31b7dafe17d275b634664a708d901ec9fd57b1529280f01fb" +ALPINEAMD64DIGEST = "docker.io/library/alpine@sha256:acd3ca9941a85e8ed16515bfc5328e4e2f8c128caa72959a58a127b7801ee01f" +ALPINEAMD64ID = "961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4" +ALPINEARM64DIGEST = "docker.io/library/alpine@sha256:db7f3dcef3d586f7dd123f107c93d7911515a5991c4b9e51fa2a43e46335a43e" +ALPINEARM64ID = "915beeae46751fc564998c79e73a1026542e945ca4f73dc841d09ccc6c2c0672" +infra = "k8s.gcr.io/pause:3.2" +TOP = "top" +ImageCacheDir = "/tmp/podman/imagecachedir" diff --git a/test/test_dockerpy/test_containers.py b/test/python/dockerpy/tests/test_containers.py index 34fe82c18..6b89688d4 100644 --- a/test/test_dockerpy/test_containers.py +++ b/test/python/dockerpy/tests/test_containers.py @@ -1,18 +1,15 @@ - -import unittest -import docker -import requests import os -from docker import Client -from . import constant -from . import common import time +import unittest + +import requests + +from . import common, constant client = common.get_client() -class TestContainers(unittest.TestCase): - podman = None +class TestContainers(unittest.TestCase): topContainerId = "" def setUp(self): @@ -33,6 +30,7 @@ class TestContainers(unittest.TestCase): @classmethod def tearDownClass(cls): common.terminate_connection(cls) + common.flush_image_cache(cls) return super().tearDownClass() def test_inspect_container(self): @@ -42,16 +40,15 @@ class TestContainers(unittest.TestCase): self.assertEqual(error.exception.response.status_code, 404) # Inspect valid container by name container = client.inspect_container(constant.TOP) - self.assertIn(TestContainers.topContainerId , container["Id"]) + self.assertIn(TestContainers.topContainerId, container["Id"]) # Inspect valid container by Id container = client.inspect_container(TestContainers.topContainerId) - self.assertIn(constant.TOP , container["Name"]) + self.assertIn(constant.TOP, container["Name"]) def test_create_container(self): # Run a container with detach mode container = client.create_container(image="alpine", detach=True) - self.assertEqual(len(container),2) - + self.assertEqual(len(container), 2) def test_start_container(self): # Start bogus container @@ -65,9 +62,9 @@ class TestContainers(unittest.TestCase): # self.assertEqual(error.exception.response.status_code, 304) # Create a new container and validate the count - client.create_container(image=constant.ALPINE,name="container2") - containers = client.containers(quiet=True,all=True) - self.assertEqual(len(containers),2) + client.create_container(image=constant.ALPINE, name="container2") + containers = client.containers(quiet=True, all=True) + self.assertEqual(len(containers), 2) def test_stop_container(self): # Stop bogus container @@ -82,7 +79,10 @@ class TestContainers(unittest.TestCase): # Stop a running container and validate the state client.stop(TestContainers.topContainerId) container = client.inspect_container(constant.TOP) - self.assertIn(container["State"]["Status"],"stopped exited",) + self.assertIn( + container["State"]["Status"], + "stopped exited", + ) def test_restart_container(self): # Restart bogus container @@ -109,12 +109,12 @@ class TestContainers(unittest.TestCase): # Remove container by ID with force client.remove_container(TestContainers.topContainerId, force=True) containers = client.containers() - self.assertEqual(len(containers),0) + self.assertEqual(len(containers), 0) def test_remove_container_without_force(self): # Validate current container count containers = client.containers() - self.assertTrue(len(containers),1) + self.assertTrue(len(containers), 1) # Remove running container should throw error with self.assertRaises(requests.HTTPError) as error: @@ -125,7 +125,7 @@ class TestContainers(unittest.TestCase): client.stop(TestContainers.topContainerId) client.remove_container(TestContainers.topContainerId) containers = client.containers() - self.assertEqual(len(containers),0) + self.assertEqual(len(containers), 0) def test_pause_container(self): # Pause bogus container @@ -151,7 +151,6 @@ class TestContainers(unittest.TestCase): client.pause(TestContainers.topContainerId) self.assertEqual(error.exception.response.status_code, 500) - def test_unpause_container(self): # Unpause bogus container with self.assertRaises(requests.HTTPError) as error: @@ -173,7 +172,7 @@ class TestContainers(unittest.TestCase): # Add container and validate the count client.create_container(image="alpine", detach=True) containers = client.containers(all=True) - self.assertEqual(len(containers),2) + self.assertEqual(len(containers), 2) # Not working for now......checking # # List container with filter by id diff --git a/test/test_dockerpy/test_images.py b/test/python/dockerpy/tests/test_images.py index c88353b79..5eae61c2f 100644 --- a/test/test_dockerpy/test_images.py +++ b/test/python/dockerpy/tests/test_images.py @@ -1,17 +1,18 @@ - +import os +import stat import unittest +from os import remove +from stat import ST_SIZE + import docker import requests -import os -from docker import Client -from . import constant -from . import common + +from . import common, constant client = common.get_client() -class TestImages(unittest.TestCase): - podman = None +class TestImages(unittest.TestCase): def setUp(self): super().setUp() common.restore_image_from_cache(self) @@ -25,13 +26,12 @@ class TestImages(unittest.TestCase): super().setUpClass() common.enable_sock(cls) - @classmethod def tearDownClass(cls): common.terminate_connection(cls) + common.flush_image_cache(cls) return super().tearDownClass() - # Inspect Image def test_inspect_image(self): @@ -43,46 +43,46 @@ class TestImages(unittest.TestCase): # Tag Image - # Validates if invalid image name is given a bad response is encountered. +# Validates if invalid image name is given a bad response is encountered. + def test_tag_invalid_image(self): with self.assertRaises(requests.HTTPError): - client.tag("dummy","demo") - - + client.tag("dummy", "demo") # Validates if the image is tagged successfully. def test_tag_valid_image(self): - client.tag(constant.ALPINE,"demo",constant.ALPINE_SHORTNAME) + client.tag(constant.ALPINE, "demo", constant.ALPINE_SHORTNAME) alpine_image = client.inspect_image(constant.ALPINE) for x in alpine_image["RepoTags"]: - if("demo:alpine" in x): + if ("demo:alpine" in x): self.assertTrue self.assertFalse # Validates if name updates when the image is retagged. @unittest.skip("dosent work now") def test_retag_valid_image(self): - client.tag(constant.ALPINE_SHORTNAME, "demo","rename") + client.tag(constant.ALPINE_SHORTNAME, "demo", "rename") alpine_image = client.inspect_image(constant.ALPINE) self.assertNotIn("demo:test", alpine_image["RepoTags"]) # List Image - # List All Images +# List All Images + def test_list_images(self): allImages = client.images() self.assertEqual(len(allImages), 1) # Add more images client.pull(constant.BB) allImages = client.images() - self.assertEqual(len(allImages) , 2) - + self.assertEqual(len(allImages), 2) - # List images with filter - filters = {'reference':'alpine'} - allImages = client.images(filters = filters) - self.assertEqual(len(allImages) , 1) + # List images with filter + filters = {'reference': 'alpine'} + allImages = client.images(filters=filters) + self.assertEqual(len(allImages), 1) # Search Image + def test_search_image(self): response = client.search("alpine") for i in response: @@ -94,22 +94,25 @@ class TestImages(unittest.TestCase): # Image Exist (No docker-py support yet) # Remove Image + def test_remove_image(self): # Check for error with wrong image name with self.assertRaises(requests.HTTPError): client.remove_image("dummy") allImages = client.images() - self.assertEqual(len(allImages) , 1) + self.assertEqual(len(allImages), 1) alpine_image = client.inspect_image(constant.ALPINE) client.remove_image(alpine_image) allImages = client.images() - self.assertEqual(len(allImages) , 0) + self.assertEqual(len(allImages), 0) # Image History + def test_image_history(self): # Check for error with wrong image name with self.assertRaises(requests.HTTPError): - client.remove_image("dummy") + client.history("dummy") + imageHistory = client.history(constant.ALPINE) alpine_image = client.inspect_image(constant.ALPINE) for h in imageHistory: @@ -119,28 +122,37 @@ class TestImages(unittest.TestCase): # Prune Image (No docker-py support yet) + def test_get_image_dummy(self): + # FIXME: seems to be an error in the library + self.skipTest("Documentation and library do not match") + # Check for error with wrong image name + with self.assertRaises(docker.errors.ImageNotFound): + client.get_image("dummy") + # Export Image def test_export_image(self): client.pull(constant.BB) - file = os.path.join(constant.ImageCacheDir , "busybox.tar") if not os.path.exists(constant.ImageCacheDir): os.makedirs(constant.ImageCacheDir) - # Check for error with wrong image name - with self.assertRaises(requests.HTTPError): - client.get_image("dummy") - response = client.get_image(constant.BB) - image_tar = open(file,mode="wb") - image_tar.write(response.data) - image_tar.close() - os.stat(file) + + image = client.get_image(constant.BB) + + file = os.path.join(constant.ImageCacheDir, "busybox.tar") + tarball = open(file, mode="wb") + for frame in image: + tarball.write(frame) + tarball.close() + sz = os.path.getsize(file) + self.assertGreater(sz, 0) + # Import|Load Image def test_import_image(self): allImages = client.images() self.assertEqual(len(allImages), 1) - file = os.path.join(constant.ImageCacheDir , "busybox.tar") + file = os.path.join(constant.ImageCacheDir, "alpine.tar") client.import_image_from_file(filename=file) allImages = client.images() self.assertEqual(len(allImages), 2) diff --git a/test/test_dockerpy/test_info_version.py b/test/python/dockerpy/tests/test_info_version.py index be1a2aab9..e3ee18ec7 100644 --- a/test/test_dockerpy/test_info_version.py +++ b/test/python/dockerpy/tests/test_info_version.py @@ -1,11 +1,10 @@ import unittest -import docker -from docker import Client -from . import constant -from . import common + +from . import common, constant client = common.get_client() + class TestInfo_Version(unittest.TestCase): podman = None @@ -31,16 +30,15 @@ class TestInfo_Version(unittest.TestCase): common.terminate_connection(cls) return super().tearDownClass() - def test_Info(self): self.assertIsNotNone(client.info()) def test_info_container_details(self): info = client.info() - self.assertEqual(info["Containers"],1) + self.assertEqual(info["Containers"], 1) client.create_container(image=constant.ALPINE) info = client.info() - self.assertEqual(info["Containers"],2) + self.assertEqual(info["Containers"], 2) def test_version(self): self.assertIsNotNone(client.version()) diff --git a/test/system/015-help.bats b/test/system/015-help.bats index 14af8e1a4..3d05b44fe 100644 --- a/test/system/015-help.bats +++ b/test/system/015-help.bats @@ -34,13 +34,16 @@ function check_help() { dprint "$command_string --help" run_podman "$@" $cmd --help + local full_help="$output" # The line immediately after 'Usage:' gives us a 1-line synopsis - usage=$(echo "$output" | grep -A1 '^Usage:' | tail -1) + usage=$(echo "$full_help" | grep -A1 '^Usage:' | tail -1) [ -n "$usage" ] || die "podman $cmd: no Usage message found" # e.g. 'podman ps' should not show 'podman container ps' in usage - is "$usage" " $command_string .*" "Usage string matches command" + # Trailing space in usage handles 'podman system renumber' which + # has no ' [flags]' + is "$usage " " $command_string .*" "Usage string matches command" # If usage ends in '[command]', recurse into subcommands if expr "$usage" : '.*\[command\]$' >/dev/null; then @@ -59,6 +62,17 @@ function check_help() { die "'flags' must precede arguments in usage: $usage" fi + # Cross-check: if usage includes '[flags]', there must be a + # longer 'Flags:' section in the full --help output; vice-versa, + # if 'Flags:' is in full output, usage line must have '[flags]'. + if expr "$usage" : '.*\[flag' >/dev/null; then + if ! expr "$full_help" : ".*Flags:" >/dev/null; then + die "$command_string: Usage includes '[flags]' but has no 'Flags:' subsection" + fi + elif expr "$full_help" : ".*Flags:" >/dev/null; then + die "$command_string: --help has 'Flags:' section but no '[flags]' in synopsis" + fi + # If usage lists no arguments (strings in ALL CAPS), confirm # by running with 'invalid-arg' and expecting failure. if ! expr "$usage" : '.*[A-Z]' >/dev/null; then diff --git a/test/system/030-run.bats b/test/system/030-run.bats index eeecea2e5..bc6347012 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -201,4 +201,45 @@ echo $rand | 0 | $rand "podman will not overwrite existing cidfile" } +@test "podman run docker-archive" { + # Create an image that, when run, outputs a random magic string + expect=$(random_string 20) + run_podman run --name myc --entrypoint="[\"/bin/echo\",\"$expect\"]" $IMAGE + is "$output" "$expect" "podman run --entrypoint echo-randomstring" + + # Save it as a tar archive + run_podman commit myc myi + archive=$PODMAN_TMPDIR/archive.tar + run_podman save myi -o $archive + is "$output" "" "podman save" + + # Clean up image and container from container storage... + run_podman rmi myi + run_podman rm myc + + # ... then confirm we can run from archive. This re-imports the image + # and runs it, producing our random string as the last line. + run_podman run docker-archive:$archive + is "${lines[0]}" "Getting image source signatures" "podman run docker-archive, first line of output" + is "$output" ".*Copying blob" "podman run docker-archive" + is "$output" ".*Copying config" "podman run docker-archive" + is "$output" ".*Writing manifest" "podman run docker-archive" + is "${lines[-1]}" "$expect" "podman run docker-archive: expected random string output" + + # Clean up container as well as re-imported image + run_podman rm -a + run_podman rmi myi + + # Repeat the above, with podman-create and podman-start. + run_podman create docker-archive:$archive + cid=${lines[-1]} + + run_podman start --attach $cid + is "$output" "$expect" "'podman run' of 'podman-create docker-archive'" + + # Clean up. + run_podman rm $cid + run_podman rmi myi +} + # vim: filetype=sh diff --git a/test/system/200-pod.bats b/test/system/200-pod.bats index 9a6b39057..0e9d9132e 100644 --- a/test/system/200-pod.bats +++ b/test/system/200-pod.bats @@ -150,6 +150,18 @@ function random_ip() { pod_id_file=${PODMAN_TMPDIR}/pod-id-file + # Randomly-assigned ports in the 5xxx and 6xxx range + for port_in in $(shuf -i 5000-5999);do + if ! { exec 3<> /dev/tcp/127.0.0.1/$port_in; } &>/dev/null; then + break + fi + done + for port_out in $(shuf -i 6000-6999);do + if ! { exec 3<> /dev/tcp/127.0.0.1/$port_out; } &>/dev/null; then + break + fi + done + # Create a pod with all the desired options # FIXME: --ip=$ip fails: # Error adding network: failed to allocate all requested IPs @@ -161,6 +173,7 @@ function random_ip() { --dns "$dns_server" \ --dns-search "$dns_search" \ --dns-opt "$dns_opt" \ + --publish "$port_out:$port_in" \ --label "${labelname}=${labelvalue}" pod_id="$output" @@ -199,6 +212,34 @@ function random_ip() { run_podman pod ps --no-trunc --filter "label=${labelname}=${labelvalue}" --format '{{.ID}}' is "$output" "$pod_id" "pod ps --filter label=..." + # Test local port forwarding, as well as 'ps' output showing ports + # Run 'nc' in a container, waiting for input on the published port. + c_name=$(random_string 15) + run_podman run -d --pod mypod --name $c_name $IMAGE nc -l -p $port_in + cid="$output" + + # Try running another container also listening on the same port. + run_podman 1 run --pod mypod --name dsfsdfsdf $IMAGE nc -l -p $port_in + is "$output" "nc: bind: Address in use" \ + "two containers cannot bind to same port" + + # While the container is still running, run 'podman ps' (no --format) + # and confirm that the output includes the published port + run_podman ps --filter id=$cid + is "${lines[1]}" "${cid:0:12} $IMAGE nc -l -p $port_in .* 0.0.0.0:$port_out->$port_in/tcp $c_name" \ + "output of 'podman ps'" + + # send a random string to the container. This will cause the container + # to output the string to its logs, then exit. + teststring=$(random_string 30) + echo "$teststring" | nc 127.0.0.1 $port_out + + # Confirm that the container log output is the string we sent it. + run_podman logs $cid + is "$output" "$teststring" "test string received on container" + + # Clean up + run_podman rm $cid run_podman pod rm -f mypod } diff --git a/test/system/250-systemd.bats b/test/system/250-systemd.bats index 4bee13414..b7035cdda 100644 --- a/test/system/250-systemd.bats +++ b/test/system/250-systemd.bats @@ -41,7 +41,7 @@ function teardown() { fi cname=$(random_string) - run_podman create --name $cname --detach $IMAGE top + run_podman create --name $cname --label "io.containers.autoupdate=image" --detach $IMAGE top run_podman generate systemd --new $cname echo "$output" > "$UNIT_FILE" @@ -64,6 +64,12 @@ function teardown() { run_podman logs $cname is "$output" ".*Load average:.*" "running container 'top'-like output" + # Exercise `podman auto-update`. + # TODO: this will at least run auto-update code but won't perform an update + # since the image didn't change. We need to improve on that and run + # an image from a local registry instead. + run_podman auto-update + # All good. Stop service, clean up. run $SYSTEMCTL stop "$SERVICE_NAME" if [ $status -ne 0 ]; then diff --git a/test/system/410-selinux.bats b/test/system/410-selinux.bats index 8a0477eff..1769730f0 100644 --- a/test/system/410-selinux.bats +++ b/test/system/410-selinux.bats @@ -63,4 +63,23 @@ function check_label() { check_label "--security-opt label=level:s0:c1,c2" "container_t" "s0:c1,c2" } +# pr #6752 +@test "podman selinux: inspect multiple labels" { + if [ ! -e /usr/sbin/selinuxenabled ] || ! /usr/sbin/selinuxenabled; then + skip "selinux disabled or not available" + fi + + run_podman run -d --name myc \ + --security-opt seccomp=unconfined \ + --security-opt label=type:spc_t \ + --security-opt label=level:s0 \ + $IMAGE sh -c 'while test ! -e /stop; do sleep 0.1; done' + run_podman inspect --format='{{ .HostConfig.SecurityOpt }}' myc + is "$output" "\[label=type:spc_t,label=level:s0 seccomp=unconfined]" \ + "'podman inspect' preserves all --security-opts" + + run_podman exec myc touch /stop + run_podman rm -f myc +} + # vim: filetype=sh diff --git a/test/test_dockerpy/README.md b/test/test_dockerpy/README.md deleted file mode 100644 index 32e426d58..000000000 --- a/test/test_dockerpy/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Dockerpy regression test - -Python test suite to validate Podman endpoints using dockerpy library - -Running tests -============= -To run the tests locally in your sandbox: - -#### Run the entire test - -``` -sudo PYTHONPATH=/usr/bin/python python -m dockerpy.images -``` - -Passing the -v option to your test script will instruct unittest.main() to enable a higher level of verbosity, and produce detailed output: - -``` -sudo PYTHONPATH=/usr/bin/python python -m unittest -v dockerpy.images -``` -#### Run a specific test class - -``` -sudo PYTHONPATH=/usr/bin/python python -m unittest -v dockerpy.images.TestImages -``` - -#### Run a specific test within the test class - -``` -sudo PYTHONPATH=/usr/bin/python python -m unittest -v dockerpy.images.TestImages.test_list_images -``` diff --git a/test/test_dockerpy/common.py b/test/test_dockerpy/common.py deleted file mode 100644 index 975b13dc6..000000000 --- a/test/test_dockerpy/common.py +++ /dev/null @@ -1,85 +0,0 @@ -import docker -import subprocess -import os -import sys -import time -from docker import Client -from . import constant - -alpineDict = { - "name": "docker.io/library/alpine:latest", - "shortName": "alpine", - "tarballName": "alpine.tar"} - -def get_client(): - client = docker.Client(base_url="http://localhost:8080",timeout=15) - return client - -client = get_client() - -def podman(): - binary = os.getenv("PODMAN_BINARY") - if binary is None: - binary = "bin/podman" - return binary - -def restore_image_from_cache(TestClass): - alpineImage = os.path.join(constant.ImageCacheDir , alpineDict["tarballName"]) - if not os.path.exists(alpineImage): - os.makedirs(constant.ImageCacheDir) - client.pull(constant.ALPINE) - response = client.get_image(constant.ALPINE) - image_tar = open(alpineImage,mode="wb") - image_tar.write(response.data) - image_tar.close() - else : - TestClass.podman = subprocess.run( - [ - podman(), "load", "-i", alpineImage - ], - shell=False, - stdin=subprocess.DEVNULL, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - -def run_top_container(): - c = client.create_container(image=constant.ALPINE,command='/bin/sleep 5',name=constant.TOP) - client.start(container=c.get("Id")) - return c.get("Id") - -def enable_sock(TestClass): - TestClass.podman = subprocess.Popen( - [ - podman(), "system", "service", "tcp:localhost:8080", - "--log-level=debug", "--time=0" - ], - shell=False, - stdin=subprocess.DEVNULL, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - time.sleep(2) - -def terminate_connection(TestClass): - TestClass.podman.terminate() - stdout, stderr = TestClass.podman.communicate(timeout=0.5) - if stdout: - print("\nService Stdout:\n" + stdout.decode('utf-8')) - if stderr: - print("\nService Stderr:\n" + stderr.decode('utf-8')) - - if TestClass.podman.returncode > 0: - sys.stderr.write("podman exited with error code {}\n".format( - TestClass.podman.returncode)) - sys.exit(2) - -def remove_all_containers(): - containers = client.containers(quiet=True) - for c in containers: - client.remove_container(container=c.get("Id"), force=True) - -def remove_all_images(): - allImages = client.images() - for image in allImages: - client.remove_image(image,force=True) diff --git a/test/test_dockerpy/constant.py b/test/test_dockerpy/constant.py deleted file mode 100644 index 8a3f1d984..000000000 --- a/test/test_dockerpy/constant.py +++ /dev/null @@ -1,13 +0,0 @@ -BB = "docker.io/library/busybox:latest" -NGINX = "docker.io/library/nginx:latest" -ALPINE = "docker.io/library/alpine:latest" -ALPINE_SHORTNAME = "alpine" -ALPINELISTTAG = "docker.io/library/alpine:3.10.2" -ALPINELISTDIGEST = "docker.io/library/alpine@sha256:72c42ed48c3a2db31b7dafe17d275b634664a708d901ec9fd57b1529280f01fb" -ALPINEAMD64DIGEST = "docker.io/library/alpine@sha256:acd3ca9941a85e8ed16515bfc5328e4e2f8c128caa72959a58a127b7801ee01f" -ALPINEAMD64ID = "961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4" -ALPINEARM64DIGEST = "docker.io/library/alpine@sha256:db7f3dcef3d586f7dd123f107c93d7911515a5991c4b9e51fa2a43e46335a43e" -ALPINEARM64ID = "915beeae46751fc564998c79e73a1026542e945ca4f73dc841d09ccc6c2c0672" -infra = "k8s.gcr.io/pause:3.2" -TOP = "top" -ImageCacheDir = "/tmp/podman/imagecachedir" diff --git a/utils/utils_supported.go b/utils/utils_supported.go index ce9fd5604..201ddb57b 100644 --- a/utils/utils_supported.go +++ b/utils/utils_supported.go @@ -3,10 +3,20 @@ package utils import ( + "bufio" + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/rootless" systemdDbus "github.com/coreos/go-systemd/v22/dbus" "github.com/godbus/dbus/v5" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) // RunUnderSystemdScope adds the specified pid to a systemd scope @@ -43,6 +53,118 @@ func RunUnderSystemdScope(pid int, slice string, unitName string) error { return nil } +func getCgroupProcess(procFile string) (string, error) { + f, err := os.Open(procFile) + if err != nil { + return "", errors.Wrapf(err, "open file %q", procFile) + } + defer f.Close() + + scanner := bufio.NewScanner(f) + cgroup := "/" + for scanner.Scan() { + line := scanner.Text() + parts := strings.Split(line, ":") + if len(parts) != 3 { + return "", errors.Errorf("cannot parse cgroup line %q", line) + } + if strings.HasPrefix(line, "0::") { + cgroup = line[3:] + break + } + // root cgroup, skip it + if parts[2] == "/" { + continue + } + // The process must have the same cgroup path for all controllers + // The OCI runtime spec file allow us to specify only one path. + if cgroup != "/" && cgroup != parts[2] { + return "", errors.Errorf("cgroup configuration not supported, the process is in two different cgroups") + } + cgroup = parts[2] + } + if cgroup == "/" { + return "", errors.Errorf("could not find cgroup mount in %q", procFile) + } + return cgroup, nil +} + +// GetOwnCgroup returns the cgroup for the current process. +func GetOwnCgroup() (string, error) { + return getCgroupProcess("/proc/self/cgroup") +} + +// GetCgroupProcess returns the cgroup for the specified process process. +func GetCgroupProcess(pid int) (string, error) { + return getCgroupProcess(fmt.Sprintf("/proc/%d/cgroup", pid)) +} + +// MoveUnderCgroupSubtree moves the PID under a cgroup subtree. +func MoveUnderCgroupSubtree(subtree string) error { + procFile := "/proc/self/cgroup" + f, err := os.Open(procFile) + if err != nil { + return errors.Wrapf(err, "open file %q", procFile) + } + defer f.Close() + + unifiedMode, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return err + } + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + parts := strings.Split(line, ":") + if len(parts) != 3 { + return errors.Errorf("cannot parse cgroup line %q", line) + } + + // root cgroup, skip it + if parts[2] == "/" { + continue + } + + cgroupRoot := "/sys/fs/cgroup" + // Special case the unified mount on hybrid cgroup and named hierarchies. + // This works on Fedora 31, but we should really parse the mounts to see + // where the cgroup hierarchy is mounted. + if parts[1] == "" && !unifiedMode { + // If it is not using unified mode, the cgroup v2 hierarchy is + // usually mounted under /sys/fs/cgroup/unified + cgroupRoot = filepath.Join(cgroupRoot, "unified") + } else if parts[1] != "" { + // Assume the controller is mounted at /sys/fs/cgroup/$CONTROLLER. + controller := strings.TrimPrefix(parts[1], "name=") + cgroupRoot = filepath.Join(cgroupRoot, controller) + } + + processes, err := ioutil.ReadFile(filepath.Join(cgroupRoot, parts[2], "cgroup.procs")) + if err != nil { + return err + } + + newCgroup := filepath.Join(cgroupRoot, parts[2], subtree) + if err := os.Mkdir(newCgroup, 0755); err != nil { + return err + } + + f, err := os.OpenFile(filepath.Join(newCgroup, "cgroup.procs"), os.O_RDWR, 0755) + if err != nil { + return err + } + defer f.Close() + + for _, pid := range bytes.Split(processes, []byte("\n")) { + if _, err := f.Write(pid); err != nil { + logrus.Warnf("Cannot move process %s to cgroup %q", pid, newCgroup) + } + } + } + return nil +} + func newProp(name string, units interface{}) systemdDbus.Property { return systemdDbus.Property{ Name: name, diff --git a/utils/utils_windows.go b/utils/utils_windows.go index db27877d9..1a2196029 100644 --- a/utils/utils_windows.go +++ b/utils/utils_windows.go @@ -7,3 +7,15 @@ import "github.com/pkg/errors" func RunUnderSystemdScope(pid int, slice string, unitName string) error { return errors.New("not implemented for windows") } + +func MoveUnderCgroupSubtree(subtree string) error { + return errors.New("not implemented for windows") +} + +func GetOwnCgroup() (string, error) { + return "", errors.New("not implemented for windows") +} + +func GetCgroupProcess(pid int) (string, error) { + return "", errors.New("not implemented for windows") +} diff --git a/vendor/github.com/containers/common/pkg/apparmor/apparmor.go b/vendor/github.com/containers/common/pkg/apparmor/apparmor.go index 8b4207efc..8046f45f5 100644 --- a/vendor/github.com/containers/common/pkg/apparmor/apparmor.go +++ b/vendor/github.com/containers/common/pkg/apparmor/apparmor.go @@ -2,14 +2,16 @@ package apparmor import ( "errors" + + "github.com/containers/common/version" ) const ( // ProfilePrefix is used for version-independent presence checks. - ProfilePrefix = "apparmor_profile" + ProfilePrefix = "containers-default-" // Profile default name - Profile = "container-default" + Profile = ProfilePrefix + version.Version ) var ( diff --git a/vendor/github.com/containers/common/pkg/apparmor/apparmor_linux.go b/vendor/github.com/containers/common/pkg/apparmor/apparmor_linux.go index f0fab4597..307249f3d 100644 --- a/vendor/github.com/containers/common/pkg/apparmor/apparmor_linux.go +++ b/vendor/github.com/containers/common/pkg/apparmor/apparmor_linux.go @@ -255,9 +255,11 @@ func CheckProfileAndLoadDefault(name string) (string, error) { } } - // If the specified name is not empty or is not a default libpod one, - // ignore it and return the name. - if name != "" && !strings.HasPrefix(name, ProfilePrefix) { + if name == "" { + name = Profile + } else if !strings.HasPrefix(name, ProfilePrefix) { + // If the specified name is not a default one, ignore it and return the + // name. isLoaded, err := IsLoaded(name) if err != nil { return "", err @@ -268,7 +270,6 @@ func CheckProfileAndLoadDefault(name string) (string, error) { return name, nil } - name = Profile // To avoid expensive redundant loads on each invocation, check // if it's loaded before installing it. isLoaded, err := IsLoaded(name) diff --git a/vendor/github.com/containers/common/pkg/config/config.go b/vendor/github.com/containers/common/pkg/config/config.go index ce479088e..d60464739 100644 --- a/vendor/github.com/containers/common/pkg/config/config.go +++ b/vendor/github.com/containers/common/pkg/config/config.go @@ -822,6 +822,7 @@ func stringsEq(a, b []string) bool { var ( configOnce sync.Once + configErr error config *Config ) @@ -837,11 +838,10 @@ var ( // The system defaults container config files can be overwritten using the // CONTAINERS_CONF environment variable. This is usually done for testing. func Default() (*Config, error) { - var err error configOnce.Do(func() { - config, err = NewConfig("") + config, configErr = NewConfig("") }) - return config, err + return config, configErr } func Path() string { diff --git a/vendor/github.com/containers/common/version/version.go b/vendor/github.com/containers/common/version/version.go new file mode 100644 index 000000000..38a45dfb2 --- /dev/null +++ b/vendor/github.com/containers/common/version/version.go @@ -0,0 +1,4 @@ +package version + +// Version is the version of the build. +const Version = "0.14.3" diff --git a/vendor/modules.txt b/vendor/modules.txt index 882ea5d25..497381b52 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -84,13 +84,14 @@ github.com/containers/buildah/pkg/secrets github.com/containers/buildah/pkg/supplemented github.com/containers/buildah/pkg/umask github.com/containers/buildah/util -# github.com/containers/common v0.14.0 +# github.com/containers/common v0.14.3 github.com/containers/common/pkg/apparmor github.com/containers/common/pkg/auth github.com/containers/common/pkg/capabilities github.com/containers/common/pkg/cgroupv2 github.com/containers/common/pkg/config github.com/containers/common/pkg/sysinfo +github.com/containers/common/version # github.com/containers/conmon v2.0.18+incompatible github.com/containers/conmon/runner/config # github.com/containers/image/v5 v5.5.1 |