diff options
Diffstat (limited to 'cmd')
68 files changed, 687 insertions, 396 deletions
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index a0aed984c..0f9476754 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -49,9 +49,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "cap-drop", []string{}, "Drop capabilities from the container", ) - cgroupNS := "" - createFlags.StringVar( - &cgroupNS, + createFlags.String( "cgroupns", containerConfig.CgroupNS(), "cgroup namespace to use", ) @@ -155,9 +153,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "device-write-iops", []string{}, "Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)", ) - createFlags.StringVar( - &cf.Entrypoint, - "entrypoint", "", + createFlags.String("entrypoint", "", "Overwrite the default ENTRYPOINT of the image", ) createFlags.StringArrayVarP( @@ -248,9 +244,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "interactive", "i", false, "Keep STDIN open even if not attached", ) - ipcNS := "" - createFlags.StringVar( - &ipcNS, + createFlags.String( "ipc", containerConfig.IPCNS(), "IPC namespace to use", ) @@ -331,9 +325,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "use `OS` instead of the running OS for choosing images", ) // markFlagHidden(createFlags, "override-os") - pid := "" - createFlags.StringVar( - &pid, + createFlags.String( "pid", containerConfig.PidNS(), "PID namespace to use", ) @@ -397,9 +389,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "security-opt", containerConfig.SecurityOptions(), "Security Options", ) - shmSize := "" - createFlags.StringVar( - &shmSize, + createFlags.String( "shm-size", containerConfig.ShmSize(), "Size of /dev/shm "+sizeWithUnitFormat, ) @@ -464,15 +454,11 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])", ) - userNS := "" - createFlags.StringVar( - &userNS, + createFlags.String( "userns", containerConfig.Containers.UserNS, "User namespace to use", ) - utsNS := "" - createFlags.StringVar( - &utsNS, + createFlags.String( "uts", containerConfig.Containers.UTSNS, "UTS namespace to use", ) diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index 2f08bb6a6..c275b1674 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -31,7 +31,7 @@ type ContainerCLIOpts struct { DeviceReadIOPs []string DeviceWriteBPs []string DeviceWriteIOPs []string - Entrypoint string + Entrypoint *string env []string EnvHost bool EnvFile []string diff --git a/cmd/podman/common/inspect.go b/cmd/podman/common/inspect.go deleted file mode 100644 index dfc6fe679..000000000 --- a/cmd/podman/common/inspect.go +++ /dev/null @@ -1,18 +0,0 @@ -package common - -import ( - "github.com/containers/libpod/pkg/domain/entities" - "github.com/spf13/cobra" -) - -// AddInspectFlagSet takes a command and adds the inspect flags and returns an InspectOptions object -// Since this cannot live in `package main` it lives here until a better home is found -func AddInspectFlagSet(cmd *cobra.Command) *entities.InspectOptions { - opts := entities.InspectOptions{} - - flags := cmd.Flags() - flags.BoolVarP(&opts.Size, "size", "s", false, "Display total file size") - flags.StringVarP(&opts.Format, "format", "f", "", "Change the output format to a Go template") - - return &opts -} diff --git a/cmd/podman/common/ports.go b/cmd/podman/common/ports.go index a96bafabd..2092bbe53 100644 --- a/cmd/podman/common/ports.go +++ b/cmd/podman/common/ports.go @@ -9,10 +9,10 @@ func verifyExpose(expose []string) error { // add the expose ports from the user (--expose) // can be single or a range for _, expose := range expose { - //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>] + // support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>] _, port := nat.SplitProtoPort(expose) - //parse the start and end port and create a sequence of ports to expose - //if expose a port, the start and end port are the same + // parse the start and end port and create a sequence of ports to expose + // if expose a port, the start and end port are the same _, _, err := nat.ParsePortRange(port) if err != nil { return errors.Wrapf(err, "invalid range format for --expose: %s", expose) diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 5d5816ea4..7250f88bb 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -192,7 +192,7 @@ func getMemoryLimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []strin func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) error { var ( err error - //namespaces map[string]string + // namespaces map[string]string ) // validate flags as needed @@ -364,20 +364,20 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.WorkDir = workDir entrypoint := []string{} userCommand := []string{} - if ep := c.Entrypoint; len(ep) > 0 { - // Check if entrypoint specified is json - if err := json.Unmarshal([]byte(c.Entrypoint), &entrypoint); err != nil { - entrypoint = append(entrypoint, ep) + if c.Entrypoint != nil { + if ep := *c.Entrypoint; len(ep) > 0 { + // Check if entrypoint specified is json + if err := json.Unmarshal([]byte(*c.Entrypoint), &entrypoint); err != nil { + entrypoint = append(entrypoint, ep) + } } + s.Entrypoint = entrypoint } - var command []string - s.Entrypoint = entrypoint - // Build the command // If we have an entry point, it goes first - if len(entrypoint) > 0 { + if c.Entrypoint != nil { command = entrypoint } if len(inputCommand) > 0 { @@ -386,9 +386,12 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string userCommand = append(userCommand, inputCommand...) } - if len(inputCommand) > 0 { + switch { + case len(inputCommand) > 0: s.Command = userCommand - } else { + case c.Entrypoint != nil: + s.Command = []string{} + default: s.Command = command } @@ -466,24 +469,6 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.CgroupParent = c.CGroupParent s.CgroupsMode = c.CGroupsMode s.Groups = c.GroupAdd - // TODO WTF - //cgroup := &cc.CgroupConfig{ - // Cgroupns: c.String("cgroupns"), - //} - // - //userns := &cc.UserConfig{ - // GroupAdd: c.StringSlice("group-add"), - // IDMappings: idmappings, - // UsernsMode: usernsMode, - // User: user, - //} - // - //uts := &cc.UtsConfig{ - // UtsMode: utsMode, - // NoHosts: c.Bool("no-hosts"), - // HostAdd: c.StringSlice("add-host"), - // Hostname: c.String("hostname"), - //} s.Hostname = c.Hostname sysctl := map[string]string{} @@ -503,7 +488,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string // TODO // ouitside of specgen and oci though // defaults to true, check spec/storage - //s.readon = c.ReadOnlyTmpFS + // s.readon = c.ReadOnlyTmpFS // TODO convert to map? // check if key=value and convert sysmap := make(map[string]string) @@ -546,7 +531,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string // Only add read-only tmpfs mounts in case that we are read-only and the // read-only tmpfs flag has been set. - mounts, volumes, err := parseVolumes(c.Volume, c.Mount, c.TmpFS, (c.ReadOnlyTmpFS && c.ReadOnly)) + mounts, volumes, err := parseVolumes(c.Volume, c.Mount, c.TmpFS, c.ReadOnlyTmpFS && c.ReadOnly) if err != nil { return err } @@ -554,12 +539,12 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.Volumes = volumes // TODO any idea why this was done - //devices := rtc.Containers.Devices + // devices := rtc.Containers.Devices // TODO conflict on populate? // - //if c.Changed("device") { + // if c.Changed("device") { // devices = append(devices, c.StringSlice("device")...) - //} + // } for _, dev := range c.Devices { s.Devices = append(s.Devices, specs.LinuxDevice{Path: dev}) @@ -571,7 +556,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string // initpath s.Stdin = c.Interactive // quiet - //DeviceCgroupRules: c.StringSlice("device-cgroup-rule"), + // DeviceCgroupRules: c.StringSlice("device-cgroup-rule"), // Rlimits/Ulimits for _, u := range c.Ulimit { @@ -591,10 +576,10 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.Rlimits = append(s.Rlimits, rl) } - //Tmpfs: c.StringArray("tmpfs"), + // Tmpfs: c.StringArray("tmpfs"), // TODO how to handle this? - //Syslog: c.Bool("syslog"), + // Syslog: c.Bool("syslog"), logOpts := make(map[string]string) for _, o := range c.LogOptions { @@ -620,7 +605,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.StopTimeout = &c.StopTimeout // TODO where should we do this? - //func verifyContainerResources(config *cc.CreateConfig, update bool) ([]string, error) { + // func verifyContainerResources(config *cc.CreateConfig, update bool) ([]string, error) { return nil } diff --git a/cmd/podman/containers/attach.go b/cmd/podman/containers/attach.go index 78b52ad1b..119b47d3f 100644 --- a/cmd/podman/containers/attach.go +++ b/cmd/podman/containers/attach.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/pkg/errors" "github.com/spf13/cobra" @@ -17,12 +18,7 @@ var ( Short: "Attach to a running container", Long: attachDescription, RunE: attach, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) > 1 || (len(args) == 0 && !cmd.Flag("latest").Changed) { - return errors.Errorf("attach requires the name or id of one running container or the latest flag") - } - return nil - }, + Args: validate.IdOrLatestArgs, Example: `podman attach ctrID podman attach 1234 podman attach --no-stdin foobar`, @@ -33,6 +29,7 @@ var ( Short: attachCommand.Short, Long: attachCommand.Long, RunE: attachCommand.RunE, + Args: validate.IdOrLatestArgs, Example: `podman container attach ctrID podman container attach 1234 podman container attach --no-stdin foobar`, @@ -71,11 +68,18 @@ func init() { } 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] + } attachOpts.Stdin = os.Stdin if attachOpts.NoStdin { attachOpts.Stdin = nil } attachOpts.Stdout = os.Stdout attachOpts.Stderr = os.Stderr - return registry.ContainerEngine().ContainerAttach(registry.GetContext(), args[0], attachOpts) + return registry.ContainerEngine().ContainerAttach(registry.GetContext(), name, attachOpts) } diff --git a/cmd/podman/containers/commit.go b/cmd/podman/containers/commit.go index 137e486eb..b3c3d7626 100644 --- a/cmd/podman/containers/commit.go +++ b/cmd/podman/containers/commit.go @@ -30,6 +30,7 @@ var ( } containerCommitCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), Use: commitCommand.Use, Short: commitCommand.Short, Long: commitCommand.Long, diff --git a/cmd/podman/containers/container.go b/cmd/podman/containers/container.go index 97b73cdd0..a102318fb 100644 --- a/cmd/podman/containers/container.go +++ b/cmd/podman/containers/container.go @@ -2,6 +2,7 @@ package containers import ( "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/spf13/cobra" @@ -17,7 +18,7 @@ var ( Short: "Manage containers", Long: "Manage containers", TraverseChildren: true, - RunE: registry.SubCommandExists, + RunE: validate.SubCommandExists, } containerConfig = util.DefaultContainerConfig() diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index da550b606..7927da04d 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -1,8 +1,10 @@ package containers import ( + "context" "fmt" "os" + "strings" "github.com/containers/common/pkg/config" "github.com/containers/libpod/cmd/podman/common" @@ -33,6 +35,7 @@ var ( } containerCreateCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), Use: createCommand.Use, Short: createCommand.Short, Long: createCommand.Long, @@ -59,7 +62,7 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: createCommand, }) - //common.GetCreateFlags(createCommand) + // common.GetCreateFlags(createCommand) flags := createCommand.Flags() createFlags(flags) @@ -105,6 +108,10 @@ func create(cmd *cobra.Command, args []string) error { return err } + if _, err := createPodIfNecessary(s); err != nil { + return err + } + report, err := registry.ContainerEngine().ContainerCreate(registry.GetContext(), s) if err != nil { return err @@ -159,6 +166,10 @@ func createInit(c *cobra.Command) error { if c.Flag("cgroupns").Changed { cliVals.CGroupsNS = c.Flag("cgroupns").Value.String() } + if c.Flag("entrypoint").Changed { + val := c.Flag("entrypoint").Value.String() + cliVals.Entrypoint = &val + } // Docker-compatibility: the "-h" flag for run/create is reserved for // the hostname (see https://github.com/containers/libpod/issues/1367). @@ -180,8 +191,10 @@ func pullImage(imageName string) error { return errors.New("unable to find a name and tag match for busybox in repotags: no such image") } _, pullErr := registry.ImageEngine().Pull(registry.GetContext(), imageName, entities.ImagePullOptions{ - Authfile: cliVals.Authfile, - Quiet: cliVals.Quiet, + Authfile: cliVals.Authfile, + Quiet: cliVals.Quiet, + OverrideArch: cliVals.OverrideArch, + OverrideOS: cliVals.OverrideOS, }) if pullErr != nil { return pullErr @@ -203,3 +216,25 @@ func openCidFile(cidfile string) (*os.File, error) { } return cidFile, nil } + +// createPodIfNecessary automatically creates a pod when requested. if the pod name +// has the form new:ID, the pod ID is created and the name in the spec generator is replaced +// with ID. +func createPodIfNecessary(s *specgen.SpecGenerator) (*entities.PodCreateReport, error) { + if !strings.HasPrefix(s.Pod, "new:") { + return nil, nil + } + podName := strings.Replace(s.Pod, "new:", "", 1) + if len(podName) < 1 { + return nil, errors.Errorf("new pod name must be at least one character") + } + createOptions := entities.PodCreateOptions{ + Name: podName, + Infra: true, + Net: &entities.NetOptions{ + PublishPorts: s.PortMappings, + }, + } + s.Pod = podName + return registry.ContainerEngine().PodCreate(context.Background(), createOptions) +} diff --git a/cmd/podman/containers/diff.go b/cmd/podman/containers/diff.go index 046dac53e..59b788010 100644 --- a/cmd/podman/containers/diff.go +++ b/cmd/podman/containers/diff.go @@ -3,6 +3,7 @@ package containers import ( "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/report" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -12,7 +13,7 @@ var ( // podman container _diff_ diffCmd = &cobra.Command{ Use: "diff [flags] CONTAINER", - Args: registry.IdOrLatestArgs, + 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.`, RunE: diff, diff --git a/cmd/podman/containers/exec.go b/cmd/podman/containers/exec.go index 3749c934a..0992b3862 100644 --- a/cmd/podman/containers/exec.go +++ b/cmd/podman/containers/exec.go @@ -16,20 +16,22 @@ var ( execDescription = `Execute the specified command inside a running container. ` execCommand = &cobra.Command{ - Use: "exec [flags] CONTAINER [COMMAND [ARG...]]", - Short: "Run a process in a running container", - Long: execDescription, - RunE: exec, + Use: "exec [flags] CONTAINER [COMMAND [ARG...]]", + Short: "Run a process in a running container", + Long: execDescription, + RunE: exec, + DisableFlagsInUseLine: true, Example: `podman exec -it ctrID ls podman exec -it -w /tmp myCtr pwd podman exec --user root ctrID ls`, } containerExecCommand = &cobra.Command{ - Use: execCommand.Use, - Short: execCommand.Short, - Long: execCommand.Long, - RunE: execCommand.RunE, + Use: execCommand.Use, + Short: execCommand.Short, + Long: execCommand.Long, + RunE: execCommand.RunE, + DisableFlagsInUseLine: true, Example: `podman container exec -it ctrID ls podman container exec -it -w /tmp myCtr pwd podman container exec --user root ctrID ls`, @@ -70,7 +72,7 @@ func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode}, Command: containerExecCommand, - Parent: containerCommitCommand, + Parent: containerCmd, }) containerExecFlags := containerExecCommand.Flags() @@ -79,6 +81,10 @@ func init() { func exec(cmd *cobra.Command, args []string) error { var nameOrId string + + if len(args) == 0 && !execOpts.Latest { + return errors.New("exec requires the name or ID of a container or the --latest flag") + } execOpts.Cmd = args if !execOpts.Latest { execOpts.Cmd = args[1:] diff --git a/cmd/podman/containers/exists.go b/cmd/podman/containers/exists.go index e640ca5e1..81ba8a282 100644 --- a/cmd/podman/containers/exists.go +++ b/cmd/podman/containers/exists.go @@ -17,8 +17,9 @@ var ( Long: containerExistsDescription, Example: `podman container exists containerID podman container exists myctr || podman run --name myctr [etc...]`, - RunE: exists, - Args: cobra.ExactArgs(1), + RunE: exists, + Args: cobra.ExactArgs(1), + DisableFlagsInUseLine: true, } ) diff --git a/cmd/podman/containers/export.go b/cmd/podman/containers/export.go index fb5bd468f..bbb6a6bc9 100644 --- a/cmd/podman/containers/export.go +++ b/cmd/podman/containers/export.go @@ -28,6 +28,7 @@ var ( } containerExportCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), Use: exportCommand.Use, Short: exportCommand.Short, Long: exportCommand.Long, diff --git a/cmd/podman/containers/inspect.go b/cmd/podman/containers/inspect.go index f9ef1ddbd..4549a4ef6 100644 --- a/cmd/podman/containers/inspect.go +++ b/cmd/podman/containers/inspect.go @@ -1,15 +1,8 @@ package containers import ( - "context" - "fmt" - "os" - "strings" - "text/template" - - "github.com/containers/libpod/cmd/podman/common" + "github.com/containers/libpod/cmd/podman/inspect" "github.com/containers/libpod/cmd/podman/registry" - "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -20,7 +13,7 @@ var ( Use: "inspect [flags] CONTAINER", Short: "Display the configuration of a container", Long: `Displays the low-level information on a container identified by name or ID.`, - RunE: inspect, + RunE: inspectExec, Example: `podman container inspect myCtr podman container inspect -l --format '{{.Id}} {{.Config.Labels}}'`, } @@ -33,45 +26,9 @@ func init() { Command: inspectCmd, Parent: containerCmd, }) - inspectOpts = common.AddInspectFlagSet(inspectCmd) - flags := inspectCmd.Flags() - - if !registry.IsRemote() { - flags.BoolVarP(&inspectOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - } - -} - -func inspect(cmd *cobra.Command, args []string) error { - responses, err := registry.ContainerEngine().ContainerInspect(context.Background(), args, *inspectOpts) - if err != nil { - return err - } - if inspectOpts.Format == "" { - b, err := json.MarshalIndent(responses, "", " ") - if err != nil { - return err - } - fmt.Println(string(b)) - return nil - } - format := inspectOpts.Format - if !strings.HasSuffix(format, "\n") { - format += "\n" - } - tmpl, err := template.New("inspect").Parse(format) - if err != nil { - return err - } - for _, i := range responses { - if err := tmpl.Execute(os.Stdout, i); err != nil { - return err - } - } - return nil + inspectOpts = inspect.AddInspectFlagSet(inspectCmd) } -func Inspect(cmd *cobra.Command, args []string, options *entities.InspectOptions) error { - inspectOpts = options - return inspect(cmd, args) +func inspectExec(cmd *cobra.Command, args []string) error { + return inspect.Inspect(args, *inspectOpts) } diff --git a/cmd/podman/containers/kill.go b/cmd/podman/containers/kill.go index 8b4a384fe..ef85aad7d 100644 --- a/cmd/podman/containers/kill.go +++ b/cmd/podman/containers/kill.go @@ -30,6 +30,9 @@ var ( } containerKillCommand = &cobra.Command{ + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, Use: killCommand.Use, Short: killCommand.Short, Long: killCommand.Long, diff --git a/cmd/podman/containers/list.go b/cmd/podman/containers/list.go index b5019ddd2..c200a49aa 100644 --- a/cmd/podman/containers/list.go +++ b/cmd/podman/containers/list.go @@ -2,6 +2,7 @@ package containers import ( "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" ) @@ -11,7 +12,7 @@ var ( listCmd = &cobra.Command{ Use: "list", Aliases: []string{"ls"}, - Args: cobra.NoArgs, + Args: validate.NoArgs, Short: "List containers", Long: "Prints out information about the containers", RunE: ps, diff --git a/cmd/podman/containers/port.go b/cmd/podman/containers/port.go index 0e50140ca..2e3386aa9 100644 --- a/cmd/podman/containers/port.go +++ b/cmd/podman/containers/port.go @@ -109,7 +109,7 @@ func port(cmd *cobra.Command, args []string) error { fmt.Printf("%d/%s -> %s:%d\n", v.ContainerPort, v.Protocol, hostIP, v.HostPort) continue } - if v == userPort { + if v.ContainerPort == userPort.ContainerPort { fmt.Printf("%s:%d\n", hostIP, v.HostPort) found = true break diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go index 82434e9cc..c5696a158 100644 --- a/cmd/podman/containers/ps.go +++ b/cmd/podman/containers/ps.go @@ -13,6 +13,7 @@ import ( tm "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/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-units" @@ -25,7 +26,7 @@ var ( psDescription = "Prints out information about the containers" psCommand = &cobra.Command{ Use: "ps", - Args: checkFlags, + Args: validate.NoArgs, Short: "List containers", Long: psDescription, RunE: ps, @@ -40,7 +41,7 @@ var ( } filters []string noTrunc bool - defaultHeaders string = "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES" + defaultHeaders = "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES" ) func init() { @@ -63,9 +64,12 @@ func listFlagSet(flags *pflag.FlagSet) { flags.BoolVarP(&listOpts.Pod, "pod", "p", false, "Print the ID and name of the pod the containers are associated with") flags.BoolVarP(&listOpts.Quiet, "quiet", "q", false, "Print the numeric IDs of the containers only") flags.BoolVarP(&listOpts.Size, "size", "s", false, "Display the total file sizes") - flags.StringVar(&listOpts.Sort, "sort", "created", "Sort output by command, created, id, image, names, runningfor, size, or status") flags.BoolVar(&listOpts.Sync, "sync", false, "Sync container state with OCI runtime") flags.UintVarP(&listOpts.Watch, "watch", "w", 0, "Watch the ps output on an interval in seconds") + + created := validate.ChoiceValue(&listOpts.Sort, "command", "created", "id", "image", "names", "runningfor", "size", "status") + flags.Var(created, "sort", "Sort output by: "+created.Choices()) + if registry.IsRemote() { _ = flags.MarkHidden("latest") } @@ -141,6 +145,9 @@ func getResponses() ([]entities.ListContainer, error) { func ps(cmd *cobra.Command, args []string) error { var responses []psReporter + if err := checkFlags(cmd, args); err != nil { + return err + } for _, f := range filters { split := strings.SplitN(f, "=", 2) if len(split) == 1 { @@ -171,7 +178,7 @@ func ps(cmd *cobra.Command, args []string) error { headers, format := createPsOut() if cmd.Flag("format").Changed { - format = listOpts.Format + format = strings.TrimPrefix(listOpts.Format, "table ") if !strings.HasPrefix(format, "\n") { format += "\n" } @@ -348,7 +355,7 @@ func portsToString(ports []ocicni.PortMapping) string { if len(ports) == 0 { return "" } - //Sort the ports, so grouping continuous ports become easy. + // Sort the ports, so grouping continuous ports become easy. sort.Slice(ports, func(i, j int) bool { return comparePorts(ports[i], ports[j]) }) diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go index 3021853a9..96549cead 100644 --- a/cmd/podman/containers/rm.go +++ b/cmd/podman/containers/rm.go @@ -38,6 +38,9 @@ var ( Short: rmCommand.Use, Long: rmCommand.Long, RunE: rmCommand.RunE, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, true) + }, Example: `podman container rm imageID podman container rm mywebserver myflaskserver 860a4b23 podman container rm --force --all diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index e3fe4cd0b..b13983e37 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -20,6 +20,7 @@ import ( var ( runDescription = "Runs a command in a new container from the given image" runCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), Use: "run [flags] IMAGE [COMMAND [ARG...]]", Short: "Run a command in a new container", Long: runDescription, @@ -30,6 +31,7 @@ var ( } containerRunCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), Use: runCommand.Use, Short: runCommand.Short, Long: runCommand.Long, @@ -144,6 +146,10 @@ func run(cmd *cobra.Command, args []string) error { } runOpts.Spec = s + if _, err := createPodIfNecessary(s); err != nil { + return err + } + report, err := registry.ContainerEngine().ContainerRun(registry.GetContext(), runOpts) // report.ExitCode is set by ContainerRun even it it returns an error if report != nil { diff --git a/cmd/podman/containers/start.go b/cmd/podman/containers/start.go index 381bf8e26..ce78d24ed 100644 --- a/cmd/podman/containers/start.go +++ b/cmd/podman/containers/start.go @@ -99,7 +99,7 @@ func start(cmd *cobra.Command, args []string) error { for _, r := range responses { if r.Err == nil { - fmt.Println(r.Id) + fmt.Println(r.RawInput) } else { errs = append(errs, r.Err) } diff --git a/cmd/podman/containers/stop.go b/cmd/podman/containers/stop.go index 4a451134a..22c487961 100644 --- a/cmd/podman/containers/stop.go +++ b/cmd/podman/containers/stop.go @@ -34,6 +34,9 @@ var ( Short: stopCommand.Short, Long: stopCommand.Long, RunE: stopCommand.RunE, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, true) + }, Example: `podman container stop ctrID podman container stop --latest podman container stop --time 2 mywebserver 6e534f14da9d`, diff --git a/cmd/podman/containers/unmount.go b/cmd/podman/containers/unmount.go index a4550abbd..d0ca202fe 100644 --- a/cmd/podman/containers/unmount.go +++ b/cmd/podman/containers/unmount.go @@ -27,6 +27,9 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) }, + Annotations: map[string]string{ + registry.ParentNSRequired: "", + }, Example: `podman umount ctrID podman umount ctrID1 ctrID2 ctrID3 podman umount --all`, @@ -37,6 +40,9 @@ var ( Short: umountCommand.Short, Long: umountCommand.Long, RunE: umountCommand.RunE, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, Example: `podman container umount ctrID podman container umount ctrID1 ctrID2 ctrID3 podman container umount --all`, diff --git a/cmd/podman/containers/wait.go b/cmd/podman/containers/wait.go index da746361d..1f4d4159b 100644 --- a/cmd/podman/containers/wait.go +++ b/cmd/podman/containers/wait.go @@ -7,6 +7,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" @@ -22,7 +23,7 @@ var ( Short: "Block on one or more containers", Long: waitDescription, RunE: wait, - Args: registry.IdOrLatestArgs, + Args: validate.IdOrLatestArgs, Example: `podman wait --latest podman wait --interval 5000 ctrID podman wait ctrID1 ctrID2`, @@ -33,6 +34,7 @@ var ( Short: waitCommand.Short, Long: waitCommand.Long, RunE: waitCommand.RunE, + Args: validate.IdOrLatestArgs, Example: `podman container wait --latest podman container wait --interval 5000 ctrID podman container wait ctrID1 ctrID2`, diff --git a/cmd/podman/diff.go b/cmd/podman/diff.go index ec94c0918..1ff2fce40 100644 --- a/cmd/podman/diff.go +++ b/cmd/podman/diff.go @@ -6,6 +6,7 @@ import ( "github.com/containers/libpod/cmd/podman/containers" "github.com/containers/libpod/cmd/podman/images" "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" ) @@ -17,7 +18,7 @@ var ( 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: registry.IdOrLatestArgs, + Args: validate.IdOrLatestArgs, Short: "Display the changes of object's file system", Long: diffDescription, TraverseChildren: true, diff --git a/cmd/podman/generate/generate.go b/cmd/podman/generate/generate.go new file mode 100644 index 000000000..b112e666a --- /dev/null +++ b/cmd/podman/generate/generate.go @@ -0,0 +1,28 @@ +package pods + +import ( + "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/spf13/cobra" +) + +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, + } + containerConfig = util.DefaultContainerConfig() +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: generateCmd, + }) +} diff --git a/cmd/podman/generate/systemd.go b/cmd/podman/generate/systemd.go new file mode 100644 index 000000000..55d770249 --- /dev/null +++ b/cmd/podman/generate/systemd.go @@ -0,0 +1,57 @@ +package pods + +import ( + "fmt" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + systemdTimeout uint + systemdOptions = entities.GenerateSystemdOptions{} + systemdDescription = `Generate systemd units for a pod or container. + The generated units can later be controlled via systemctl(1).` + + systemdCmd = &cobra.Command{ + Use: "systemd [flags] CTR|POD", + Short: "Generate systemd units.", + Long: systemdDescription, + RunE: systemd, + Args: cobra.MinimumNArgs(1), + Example: `podman generate systemd CTR + podman generate systemd --new --time 10 CTR + podman generate systemd --files --name POD`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: systemdCmd, + Parent: generateCmd, + }) + flags := systemdCmd.Flags() + flags.BoolVarP(&systemdOptions.Name, "name", "n", false, "Use container/pod names instead of IDs") + flags.BoolVarP(&systemdOptions.Files, "files", "f", false, "Generate .service files instead of printing to stdout") + flags.UintVarP(&systemdTimeout, "time", "t", containerConfig.Engine.StopTimeout, "Stop timeout override") + flags.StringVar(&systemdOptions.RestartPolicy, "restart-policy", "on-failure", "Systemd restart-policy") + flags.BoolVarP(&systemdOptions.New, "new", "", false, "Create a new container instead of starting an existing one") + flags.SetNormalizeFunc(utils.AliasFlags) +} + +func systemd(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("time") { + systemdOptions.StopTimeout = &systemdTimeout + } + + report, err := registry.ContainerEngine().GenerateSystemd(registry.GetContext(), args[0], systemdOptions) + if err != nil { + return err + } + + fmt.Println(report.Output) + return nil +} diff --git a/cmd/podman/healthcheck/healthcheck.go b/cmd/podman/healthcheck/healthcheck.go index 794a94615..ce90dba31 100644 --- a/cmd/podman/healthcheck/healthcheck.go +++ b/cmd/podman/healthcheck/healthcheck.go @@ -2,6 +2,7 @@ package healthcheck import ( "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" ) @@ -13,7 +14,7 @@ var ( Short: "Manage Healthcheck", Long: "Manage Healthcheck", TraverseChildren: true, - RunE: registry.SubCommandExists, + RunE: validate.SubCommandExists, } ) diff --git a/cmd/podman/images/exists.go b/cmd/podman/images/exists.go index 6464e6cd8..13191113f 100644 --- a/cmd/podman/images/exists.go +++ b/cmd/podman/images/exists.go @@ -15,6 +15,7 @@ var ( RunE: exists, Example: `podman image exists ID podman image exists IMAGE && podman pull IMAGE`, + DisableFlagsInUseLine: true, } ) diff --git a/cmd/podman/images/history.go b/cmd/podman/images/history.go index b8d216cc1..ce153aa46 100644 --- a/cmd/podman/images/history.go +++ b/cmd/podman/images/history.go @@ -89,22 +89,20 @@ func history(cmd *cobra.Command, args []string) error { hdr := "ID\tCREATED\tCREATED BY\tSIZE\tCOMMENT\n" row := "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" - if len(opts.format) > 0 { + switch { + case len(opts.format) > 0: hdr = "" row = opts.format if !strings.HasSuffix(opts.format, "\n") { row += "\n" } - } else { - switch { - case opts.human: - row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" - case opts.noTrunc: - row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" - case opts.quiet: - hdr = "" - row = "{{.ID}}\n" - } + case opts.quiet: + hdr = "" + row = "{{.ID}}\n" + case opts.human: + row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" + case opts.noTrunc: + row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" } format := hdr + "{{range . }}" + row + "{{end}}" diff --git a/cmd/podman/images/image.go b/cmd/podman/images/image.go index 604f49251..790c16c05 100644 --- a/cmd/podman/images/image.go +++ b/cmd/podman/images/image.go @@ -2,6 +2,7 @@ package images import ( "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" ) @@ -16,7 +17,7 @@ var ( Short: "Manage images", Long: "Manage images", TraverseChildren: true, - RunE: registry.SubCommandExists, + RunE: validate.SubCommandExists, } ) diff --git a/cmd/podman/images/images.go b/cmd/podman/images/images.go index fd3ede26a..96ef344bf 100644 --- a/cmd/podman/images/images.go +++ b/cmd/podman/images/images.go @@ -11,12 +11,13 @@ import ( var ( // podman _images_ Alias for podman image _list_ imagesCmd = &cobra.Command{ - Use: strings.Replace(listCmd.Use, "list", "images", 1), - Args: listCmd.Args, - Short: listCmd.Short, - Long: listCmd.Long, - RunE: listCmd.RunE, - Example: strings.Replace(listCmd.Example, "podman image list", "podman images", -1), + Use: strings.Replace(listCmd.Use, "list", "images", 1), + Args: listCmd.Args, + Short: listCmd.Short, + Long: listCmd.Long, + RunE: listCmd.RunE, + Example: strings.Replace(listCmd.Example, "podman image list", "podman images", -1), + DisableFlagsInUseLine: true, } ) diff --git a/cmd/podman/images/inspect.go b/cmd/podman/images/inspect.go index 91c9445eb..8c727eb07 100644 --- a/cmd/podman/images/inspect.go +++ b/cmd/podman/images/inspect.go @@ -1,18 +1,9 @@ package images import ( - "context" - "fmt" - "os" - "strings" - "text/tabwriter" - "text/template" - - "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/common" + "github.com/containers/libpod/cmd/podman/inspect" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -21,8 +12,8 @@ var ( inspectCmd = &cobra.Command{ Use: "inspect [flags] IMAGE", Short: "Display the configuration of an image", - Long: `Displays the low-level information on an image identified by name or ID.`, - RunE: inspect, + Long: `Displays the low-level information of an image identified by name or ID.`, + RunE: inspectExec, Example: `podman inspect alpine podman inspect --format "imageId: {{.Id}} size: {{.Size}}" alpine podman inspect --format "image: {{.ImageName}} driver: {{.Driver}}" myctr`, @@ -36,78 +27,11 @@ func init() { Command: inspectCmd, Parent: imageCmd, }) - inspectOpts = common.AddInspectFlagSet(inspectCmd) -} - -func inspect(cmd *cobra.Command, args []string) error { - if inspectOpts.Size { - return fmt.Errorf("--size can only be used for containers") - } - if inspectOpts.Latest { - return fmt.Errorf("--latest can only be used for containers") - } - if len(args) == 0 { - return errors.Errorf("image name must be specified: podman image inspect [options [...]] name") - } - - results, err := registry.ImageEngine().Inspect(context.Background(), args, *inspectOpts) - if err != nil { - return err - } - - if len(results.Images) > 0 { - if inspectOpts.Format == "" { - buf, err := json.MarshalIndent(results.Images, "", " ") - if err != nil { - return err - } - fmt.Println(string(buf)) - - for id, e := range results.Errors { - fmt.Fprintf(os.Stderr, "%s: %s\n", id, e.Error()) - } - return nil - } - row := inspectFormat(inspectOpts.Format) - format := "{{range . }}" + row + "{{end}}" - tmpl, err := template.New("inspect").Parse(format) - if err != nil { - return err - } - - w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) - defer func() { _ = w.Flush() }() - err = tmpl.Execute(w, results.Images) - if err != nil { - return err - } - } - - var lastErr error - for id, e := range results.Errors { - if lastErr != nil { - fmt.Fprintf(os.Stderr, "%s: %s\n", id, lastErr.Error()) - } - lastErr = e - } - return lastErr -} - -func inspectFormat(row string) string { - r := strings.NewReplacer("{{.Id}}", formats.IDString, - ".Src", ".Source", - ".Dst", ".Destination", - ".ImageID", ".Image", - ) - row = r.Replace(row) - - if !strings.HasSuffix(row, "\n") { - row += "\n" - } - return row + inspectOpts = inspect.AddInspectFlagSet(inspectCmd) + flags := inspectCmd.Flags() + _ = flags.MarkHidden("latest") // Shared with container-inspect but not wanted here. } -func Inspect(cmd *cobra.Command, args []string, options *entities.InspectOptions) error { - inspectOpts = options - return inspect(cmd, args) +func inspectExec(cmd *cobra.Command, args []string) error { + return inspect.Inspect(args, *inspectOpts) } diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go index b979cb6af..83c039ed3 100644 --- a/cmd/podman/images/list.go +++ b/cmd/podman/images/list.go @@ -32,7 +32,7 @@ type listFlagType struct { var ( // Command: podman image _list_ listCmd = &cobra.Command{ - Use: "list [flag] [IMAGE]", + Use: "list [FLAGS] [IMAGE]", Aliases: []string{"ls"}, Args: cobra.MaximumNArgs(1), Short: "List images in local storage", @@ -41,6 +41,7 @@ var ( Example: `podman image list --format json podman image list --sort repository --format "table {{.ID}} {{.Repository}} {{.Tag}}" podman image list --filter dangling=true`, + DisableFlagsInUseLine: true, } // Options to pull data @@ -98,14 +99,29 @@ func images(cmd *cobra.Command, args []string) error { return err } - imageS := summaries - sort.Slice(imageS, sortFunc(listFlag.sort, imageS)) + switch { + case listFlag.quiet: + return writeId(summaries) + case cmd.Flag("format").Changed && listFlag.format == "json": + return writeJSON(summaries) + default: + return writeTemplate(summaries) + } +} - if cmd.Flag("format").Changed && listFlag.format == "json" { - return writeJSON(imageS) - } else { - return writeTemplate(imageS, err) +func writeId(imageS []*entities.ImageSummary) error { + var ids = map[string]struct{}{} + for _, e := range imageS { + i := "sha256:" + e.ID + if !listFlag.noTrunc { + i = fmt.Sprintf("%12.12s", e.ID) + } + ids[i] = struct{}{} } + for k := range ids { + fmt.Fprint(os.Stdout, k+"\n") + } + return nil } func writeJSON(imageS []*entities.ImageSummary) error { @@ -130,7 +146,7 @@ func writeJSON(imageS []*entities.ImageSummary) error { return enc.Encode(imgs) } -func writeTemplate(imageS []*entities.ImageSummary, err error) error { +func writeTemplate(imageS []*entities.ImageSummary) error { var ( hdr, row string ) @@ -142,10 +158,11 @@ func writeTemplate(imageS []*entities.ImageSummary, err error) error { h.Repository, h.Tag = tokenRepoTag(tag) imgs = append(imgs, h) } - if e.IsReadOnly() { - listFlag.readOnly = true - } + listFlag.readOnly = e.IsReadOnly() } + + sort.Slice(imgs, sortFunc(listFlag.sort, imgs)) + if len(listFlag.format) < 1 { hdr, row = imageListFormat(listFlag) } else { @@ -175,37 +192,33 @@ func tokenRepoTag(tag string) (string, string) { } } -func sortFunc(key string, data []*entities.ImageSummary) func(i, j int) bool { +func sortFunc(key string, data []imageReporter) func(i, j int) bool { switch key { case "id": return func(i, j int) bool { - return data[i].ID < data[j].ID + return data[i].ID() < data[j].ID() } case "repository": return func(i, j int) bool { - return data[i].RepoTags[0] < data[j].RepoTags[0] + return data[i].Repository < data[j].Repository } case "size": return func(i, j int) bool { - return data[i].Size < data[j].Size + return data[i].size() < data[j].size() } case "tag": return func(i, j int) bool { - return data[i].RepoTags[0] < data[j].RepoTags[0] + return data[i].Tag < data[j].Tag } default: // case "created": return func(i, j int) bool { - return data[i].Created.After(data[j].Created) + return data[i].created().After(data[j].created()) } } } func imageListFormat(flags listFlagType) (string, string) { - if flags.quiet { - return "", "{{.ID}}\n" - } - // Defaults hdr := "REPOSITORY\tTAG" row := "{{.Repository}}\t{{if .Tag}}{{.Tag}}{{else}}<none>{{end}}" @@ -262,6 +275,10 @@ func (i imageReporter) Created() string { return units.HumanDuration(time.Since(i.ImageSummary.Created)) + " ago" } +func (i imageReporter) created() time.Time { + return i.ImageSummary.Created +} + func (i imageReporter) Size() string { s := units.HumanSizeWithPrecision(float64(i.ImageSummary.Size), 3) j := strings.LastIndexFunc(s, unicode.IsNumber) @@ -271,3 +288,19 @@ func (i imageReporter) Size() string { func (i imageReporter) History() string { return strings.Join(i.ImageSummary.History, ", ") } + +func (i imageReporter) CreatedAt() string { + return i.ImageSummary.Created.String() +} + +func (i imageReporter) CreatedSince() string { + return i.Created() +} + +func (i imageReporter) CreatedTime() string { + return i.CreatedAt() +} + +func (i imageReporter) size() int64 { + return i.ImageSummary.Size +} diff --git a/cmd/podman/images/prune.go b/cmd/podman/images/prune.go index b90d889be..53a1966c1 100644 --- a/cmd/podman/images/prune.go +++ b/cmd/podman/images/prune.go @@ -7,6 +7,7 @@ import ( "strings" "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" @@ -18,7 +19,7 @@ var ( If an image is not being used by a container, it will be removed from the system.` pruneCmd = &cobra.Command{ Use: "prune", - Args: cobra.NoArgs, + Args: validate.NoArgs, Short: "Remove unused images", Long: pruneDescription, RunE: prune, diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go index f996d0681..9f4cbc50e 100644 --- a/cmd/podman/images/pull.go +++ b/cmd/podman/images/pull.go @@ -45,6 +45,7 @@ var ( Short: pullCmd.Short, Long: pullCmd.Long, RunE: pullCmd.RunE, + Args: cobra.ExactArgs(1), Example: `podman image pull imageName podman image pull fedora:latest`, } @@ -93,23 +94,22 @@ func pullFlags(flags *pflag.FlagSet) { // imagePull is implement the command for pulling images. func imagePull(cmd *cobra.Command, args []string) error { - pullOptsAPI := pullOptions.ImagePullOptions // TLS verification in c/image is controlled via a `types.OptionalBool` // which allows for distinguishing among set-true, set-false, unspecified // which is important to implement a sane way of dealing with defaults of // boolean CLI flags. if cmd.Flags().Changed("tls-verify") { - pullOptsAPI.TLSVerify = types.NewOptionalBool(pullOptions.TLSVerifyCLI) + pullOptions.SkipTLSVerify = types.NewOptionalBool(!pullOptions.TLSVerifyCLI) } - if pullOptsAPI.Authfile != "" { - if _, err := os.Stat(pullOptsAPI.Authfile); err != nil { - return errors.Wrapf(err, "error getting authfile %s", pullOptsAPI.Authfile) + if pullOptions.Authfile != "" { + if _, err := os.Stat(pullOptions.Authfile); err != nil { + return errors.Wrapf(err, "error getting authfile %s", pullOptions.Authfile) } } // Let's do all the remaining Yoga in the API to prevent us from // scattering logic across (too) many parts of the code. - pullReport, err := registry.ImageEngine().Pull(registry.GetContext(), args[0], pullOptsAPI) + pullReport, err := registry.ImageEngine().Pull(registry.GetContext(), args[0], pullOptions.ImagePullOptions) if err != nil { return err } diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go index ef2ffd0d7..0b3502d61 100644 --- a/cmd/podman/images/push.go +++ b/cmd/podman/images/push.go @@ -98,6 +98,7 @@ func imagePush(cmd *cobra.Command, args []string) error { switch len(args) { case 1: source = args[0] + destination = args[0] case 2: source = args[0] destination = args[1] @@ -107,22 +108,21 @@ func imagePush(cmd *cobra.Command, args []string) error { return errors.New("push requires at least one image name, or optionally a second to specify a different destination") } - pushOptsAPI := pushOptions.ImagePushOptions // TLS verification in c/image is controlled via a `types.OptionalBool` // which allows for distinguishing among set-true, set-false, unspecified // which is important to implement a sane way of dealing with defaults of // boolean CLI flags. if cmd.Flags().Changed("tls-verify") { - pushOptsAPI.TLSVerify = types.NewOptionalBool(pushOptions.TLSVerifyCLI) + pushOptions.SkipTLSVerify = types.NewOptionalBool(!pushOptions.TLSVerifyCLI) } - if pushOptsAPI.Authfile != "" { - if _, err := os.Stat(pushOptsAPI.Authfile); err != nil { - return errors.Wrapf(err, "error getting authfile %s", pushOptsAPI.Authfile) + if pushOptions.Authfile != "" { + if _, err := os.Stat(pushOptions.Authfile); err != nil { + return errors.Wrapf(err, "error getting authfile %s", pushOptions.Authfile) } } // Let's do all the remaining Yoga in the API to prevent us from scattering // logic across (too) many parts of the code. - return registry.ImageEngine().Push(registry.GetContext(), source, destination, pushOptsAPI) + return registry.ImageEngine().Push(registry.GetContext(), source, destination, pushOptions.ImagePushOptions) } diff --git a/cmd/podman/images/rm.go b/cmd/podman/images/rm.go index da6a90d2b..1cf5fa365 100644 --- a/cmd/podman/images/rm.go +++ b/cmd/podman/images/rm.go @@ -54,7 +54,10 @@ func rm(cmd *cobra.Command, args []string) error { fmt.Println("Untagged: " + u) } for _, d := range report.Deleted { - fmt.Println("Deleted: " + d) + // Make sure an image was deleted (and not just untagged); else print it + if len(d) > 0 { + fmt.Println("Deleted: " + d) + } } registry.SetExitCode(report.ExitCode) } diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go index fdad94d45..a8abfb339 100644 --- a/cmd/podman/images/search.go +++ b/cmd/podman/images/search.go @@ -1,6 +1,7 @@ package images import ( + "os" "reflect" "strings" @@ -47,14 +48,15 @@ var ( // Command: podman image search imageSearchCmd = &cobra.Command{ - Use: searchCmd.Use, - Short: searchCmd.Short, - Long: searchCmd.Long, - RunE: searchCmd.RunE, - Args: searchCmd.Args, + Use: searchCmd.Use, + Short: searchCmd.Short, + Long: searchCmd.Long, + RunE: searchCmd.RunE, + Args: searchCmd.Args, + Annotations: searchCmd.Annotations, Example: `podman image search --filter=is-official --limit 3 alpine - podman image search registry.fedoraproject.org/ # only works with v2 registries - podman image search --format "table {{.Index}} {{.Name}}" registry.fedoraproject.org/fedora`, + podman image search registry.fedoraproject.org/ # only works with v2 registries + podman image search --format "table {{.Index}} {{.Name}}" registry.fedoraproject.org/fedora`, } ) @@ -103,16 +105,21 @@ func imageSearch(cmd *cobra.Command, args []string) error { return errors.Errorf("search requires exactly one argument") } - sarchOptsAPI := searchOptions.ImageSearchOptions // TLS verification in c/image is controlled via a `types.OptionalBool` // which allows for distinguishing among set-true, set-false, unspecified // which is important to implement a sane way of dealing with defaults of // boolean CLI flags. if cmd.Flags().Changed("tls-verify") { - sarchOptsAPI.TLSVerify = types.NewOptionalBool(pullOptions.TLSVerifyCLI) + searchOptions.SkipTLSVerify = types.NewOptionalBool(!searchOptions.TLSVerifyCLI) } - searchReport, err := registry.ImageEngine().Search(registry.GetContext(), searchTerm, sarchOptsAPI) + if searchOptions.Authfile != "" { + if _, err := os.Stat(searchOptions.Authfile); err != nil { + return errors.Wrapf(err, "error getting authfile %s", searchOptions.Authfile) + } + } + + searchReport, err := registry.ImageEngine().Search(registry.GetContext(), searchTerm, searchOptions.ImageSearchOptions) if err != nil { return err } diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index 93bf58bdd..a5fdaedc2 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -1,31 +1,26 @@ package main import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/common" - "github.com/containers/libpod/cmd/podman/containers" - "github.com/containers/libpod/cmd/podman/images" + "github.com/containers/libpod/cmd/podman/inspect" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) -// Inspect is one of the outlier commands in that it operates on images/containers/... - var ( - inspectOpts *entities.InspectOptions - // 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: inspect, - Example: `podman inspect alpine - podman inspect --format "imageId: {{.Id}} size: {{.Size}}" alpine`, + RunE: inspectExec, + Example: `podman inspect fedora + podman inspect --type image fedora + podman inspect CtrID ImgID + podman inspect --format "imageId: {{.Id}} size: {{.Size}}" fedora`, } + inspectOpts *entities.InspectOptions ) func init() { @@ -33,26 +28,9 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: inspectCmd, }) - inspectOpts = common.AddInspectFlagSet(inspectCmd) - flags := inspectCmd.Flags() - flags.StringVarP(&inspectOpts.Type, "type", "t", "", "Return JSON for specified type, (image or container) (default \"all\")") - if !registry.IsRemote() { - flags.BoolVarP(&inspectOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of (containers only)") - } + inspectOpts = inspect.AddInspectFlagSet(inspectCmd) } -func inspect(cmd *cobra.Command, args []string) error { - switch inspectOpts.Type { - case "image": - return images.Inspect(cmd, args, inspectOpts) - case "container": - return containers.Inspect(cmd, args, inspectOpts) - case "": - if err := images.Inspect(cmd, args, inspectOpts); err == nil { - return nil - } - return containers.Inspect(cmd, args, inspectOpts) - default: - return fmt.Errorf("invalid type %q is must be 'container' or 'image'", inspectOpts.Type) - } +func inspectExec(cmd *cobra.Command, args []string) error { + return inspect.Inspect(args, *inspectOpts) } diff --git a/cmd/podman/inspect/inspect.go b/cmd/podman/inspect/inspect.go new file mode 100644 index 000000000..223ce00f0 --- /dev/null +++ b/cmd/podman/inspect/inspect.go @@ -0,0 +1,159 @@ +package inspect + +import ( + "context" + "fmt" + "strings" + + "github.com/containers/buildah/pkg/formats" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +const ( + // ImageType is the image type. + ImageType = "image" + // ContainerType is the container type. + ContainerType = "container" + // AllType can be of type ImageType or ContainerType. + AllType = "all" +) + +// AddInspectFlagSet takes a command and adds the inspect flags and returns an +// InspectOptions object. +func AddInspectFlagSet(cmd *cobra.Command) *entities.InspectOptions { + opts := entities.InspectOptions{} + + flags := cmd.Flags() + 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") + + return &opts +} + +// Inspect inspects the specified container/image names or IDs. +func Inspect(namesOrIDs []string, options entities.InspectOptions) error { + inspector, err := newInspector(options) + if err != nil { + return err + } + return inspector.inspect(namesOrIDs) +} + +// inspector allows for inspecting images and containers. +type inspector struct { + containerEngine entities.ContainerEngine + imageEngine entities.ImageEngine + options entities.InspectOptions +} + +// newInspector creates a new inspector based on the specified options. +func newInspector(options entities.InspectOptions) (*inspector, error) { + switch options.Type { + case ImageType, ContainerType, AllType: + // Valid types. + default: + return nil, errors.Errorf("invalid type %q: must be %q, %q or %q", options.Type, ImageType, ContainerType, AllType) + } + if options.Type == ImageType { + if options.Latest { + return nil, errors.Errorf("latest is not supported for type %q", ImageType) + } + if options.Size { + return nil, errors.Errorf("size is not supported for type %q", ImageType) + } + } + return &inspector{ + containerEngine: registry.ContainerEngine(), + imageEngine: registry.ImageEngine(), + options: options, + }, nil +} + +// inspect inspects the specified container/image names or IDs. +func (i *inspector) inspect(namesOrIDs []string) error { + // data - dumping place for inspection results. + var data []interface{} + ctx := context.Background() + + if len(namesOrIDs) == 0 { + if !i.options.Latest { + return errors.New("no containers or images specified") + } + } + + tmpType := i.options.Type + if i.options.Latest { + if len(namesOrIDs) > 0 { + return errors.New("latest and containers are not allowed") + } + tmpType = ContainerType // -l works with --type=all + } + + // Inspect - note that AllType requires us to expensively query one-by-one. + switch tmpType { + case AllType: + all, err := i.inspectAll(ctx, namesOrIDs) + if err != nil { + return err + } + data = all + case ImageType: + imgData, err := i.imageEngine.Inspect(ctx, namesOrIDs, i.options) + if err != nil { + return err + } + for i := range imgData { + data = append(data, imgData[i]) + } + case ContainerType: + ctrData, err := i.containerEngine.ContainerInspect(ctx, namesOrIDs, i.options) + if err != nil { + return err + } + for i := range ctrData { + data = append(data, ctrData[i]) + } + default: + return errors.Errorf("invalid type %q: must be %q, %q or %q", i.options.Type, ImageType, ContainerType, AllType) + } + + var out formats.Writer + if i.options.Format == "json" || i.options.Format == "" { // "" for backwards compat + out = formats.JSONStructArray{Output: data} + } else { + out = formats.StdoutTemplateArray{Output: data, Template: inspectFormat(i.options.Format)} + } + return out.Out() +} + +func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]interface{}, error) { + var data []interface{} + for _, name := range namesOrIDs { + imgData, err := i.imageEngine.Inspect(ctx, []string{name}, i.options) + if err == nil { + data = append(data, imgData[0]) + continue + } + ctrData, err := i.containerEngine.ContainerInspect(ctx, []string{name}, i.options) + if err != nil { + return nil, err + } + data = append(data, ctrData[0]) + } + return data, nil +} + +func inspectFormat(row string) string { + r := strings.NewReplacer( + "{{.Id}}", formats.IDString, + ".Src", ".Source", + ".Dst", ".Destination", + ".ImageID", ".Image", + ) + return r.Replace(row) +} diff --git a/cmd/podman/login.go b/cmd/podman/login.go index 1843a764d..9de805d15 100644 --- a/cmd/podman/login.go +++ b/cmd/podman/login.go @@ -19,7 +19,7 @@ type loginOptionsWrapper struct { var ( loginOptions = loginOptionsWrapper{} loginCommand = &cobra.Command{ - Use: "login [flags] REGISTRY", + Use: "login [flags] [REGISTRY]", Short: "Login to a container registry", Long: "Login to a container registry on a specified server.", RunE: login, diff --git a/cmd/podman/logout.go b/cmd/podman/logout.go index 77bdc92b4..c21711fc0 100644 --- a/cmd/podman/logout.go +++ b/cmd/podman/logout.go @@ -14,7 +14,7 @@ import ( var ( logoutOptions = auth.LogoutOptions{} logoutCommand = &cobra.Command{ - Use: "logout [flags] REGISTRY", + Use: "logout [flags] [REGISTRY]", Short: "Logout of a container registry", Long: "Remove the cached username and password for the registry.", RunE: logout, diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 8109eca2f..3a8958b6d 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -1,18 +1,21 @@ package main import ( + "fmt" "os" _ "github.com/containers/libpod/cmd/podman/containers" + _ "github.com/containers/libpod/cmd/podman/generate" _ "github.com/containers/libpod/cmd/podman/healthcheck" _ "github.com/containers/libpod/cmd/podman/images" _ "github.com/containers/libpod/cmd/podman/manifest" - _ "github.com/containers/libpod/cmd/podman/networks" _ "github.com/containers/libpod/cmd/podman/pods" "github.com/containers/libpod/cmd/podman/registry" _ "github.com/containers/libpod/cmd/podman/system" _ "github.com/containers/libpod/cmd/podman/volumes" + "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage/pkg/reexec" + "github.com/spf13/cobra" ) func main() { @@ -26,6 +29,14 @@ func main() { for _, c := range registry.Commands { for _, m := range c.Mode { if cfg.EngineMode == m { + // Command cannot be run rootless + _, found := c.Command.Annotations[registry.ParentNSRequired] + if rootless.IsRootless() && found { + c.Command.RunE = func(cmd *cobra.Command, args []string) error { + return fmt.Errorf("cannot `%s` in rootless mode", cmd.CommandPath()) + } + } + parent := rootCmd if c.Parent != nil { parent = c.Parent diff --git a/cmd/podman/manifest/add.go b/cmd/podman/manifest/add.go index c83beff7a..38f832fad 100644 --- a/cmd/podman/manifest/add.go +++ b/cmd/podman/manifest/add.go @@ -13,7 +13,7 @@ import ( var ( manifestAddOpts = entities.ManifestAddOptions{} addCmd = &cobra.Command{ - Use: "add", + Use: "add [flags] LIST LIST", Short: "Add images to a manifest list or image index", Long: "Adds an image to a manifest list or image index.", RunE: add, diff --git a/cmd/podman/manifest/create.go b/cmd/podman/manifest/create.go index 4f3e27774..9c0097b90 100644 --- a/cmd/podman/manifest/create.go +++ b/cmd/podman/manifest/create.go @@ -13,7 +13,7 @@ import ( var ( manifestCreateOpts = entities.ManifestCreateOptions{} createCmd = &cobra.Command{ - Use: "create", + Use: "create [flags] LIST [IMAGE]", Short: "Create manifest list or image index", Long: "Creates manifest lists or image indexes.", RunE: create, diff --git a/cmd/podman/manifest/inspect.go b/cmd/podman/manifest/inspect.go index 36ecdc87b..5112aa5b2 100644 --- a/cmd/podman/manifest/inspect.go +++ b/cmd/podman/manifest/inspect.go @@ -12,7 +12,7 @@ import ( var ( inspectCmd = &cobra.Command{ - Use: "inspect IMAGE", + 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, diff --git a/cmd/podman/manifest/manifest.go b/cmd/podman/manifest/manifest.go index b9ac7ea68..b78879b34 100644 --- a/cmd/podman/manifest/manifest.go +++ b/cmd/podman/manifest/manifest.go @@ -2,6 +2,7 @@ package manifest import ( "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" ) @@ -13,7 +14,7 @@ var ( Short: "Manipulate manifest lists and image indexes", Long: manifestDescription, TraverseChildren: true, - RunE: registry.SubCommandExists, + RunE: validate.SubCommandExists, Example: `podman manifest create localhost/list podman manifest inspect localhost/list`, } diff --git a/cmd/podman/networks/network.go b/cmd/podman/networks/network.go index 3cee86bcc..e2a928312 100644 --- a/cmd/podman/networks/network.go +++ b/cmd/podman/networks/network.go @@ -2,6 +2,7 @@ package images import ( "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" ) @@ -13,10 +14,13 @@ var ( Short: "Manage networks", Long: "Manage networks", TraverseChildren: true, - RunE: registry.SubCommandExists, + RunE: validate.SubCommandExists, } ) +// TODO add the following to main.go to get networks back onto the +// command list. +// _ "github.com/containers/libpod/cmd/podman/networks" func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode}, diff --git a/cmd/podman/parse/net.go b/cmd/podman/parse/net.go index 03cda268c..f93c4ab1e 100644 --- a/cmd/podman/parse/net.go +++ b/cmd/podman/parse/net.go @@ -1,4 +1,4 @@ -//nolint +// nolint // most of these validate and parse functions have been taken from projectatomic/docker // and modified for cri-o package parse @@ -46,7 +46,7 @@ var ( // validateExtraHost validates that the specified string is a valid extrahost and returns it. // ExtraHost is in the form of name:ip where the ip has to be a valid ip (ipv4 or ipv6). // for add-host flag -func ValidateExtraHost(val string) (string, error) { //nolint +func ValidateExtraHost(val string) (string, error) { // nolint // allow for IPv6 addresses in extra hosts by only splitting on first ":" arr := strings.SplitN(val, ":", 2) if len(arr) != 2 || len(arr[0]) == 0 { diff --git a/cmd/podman/parse/net_test.go b/cmd/podman/parse/net_test.go index a6ddc2be9..51c8509df 100644 --- a/cmd/podman/parse/net_test.go +++ b/cmd/podman/parse/net_test.go @@ -1,4 +1,4 @@ -//nolint +// nolint // most of these validate and parse functions have been taken from projectatomic/docker // and modified for cri-o package parse @@ -41,7 +41,7 @@ func TestValidateExtraHost(t *testing.T) { want string wantErr bool }{ - //2001:0db8:85a3:0000:0000:8a2e:0370:7334 + // 2001:0db8:85a3:0000:0000:8a2e:0370:7334 {name: "good-ipv4", args: args{val: "foobar:192.168.1.1"}, want: "foobar:192.168.1.1", wantErr: false}, {name: "bad-ipv4", args: args{val: "foobar:999.999.999.99"}, want: "", wantErr: true}, {name: "bad-ipv4", args: args{val: "foobar:999.999.999"}, want: "", wantErr: true}, diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index ff21166f3..85b96d37b 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -9,6 +9,7 @@ import ( "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/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/errorhandling" "github.com/containers/libpod/pkg/specgen" @@ -24,7 +25,7 @@ var ( createCommand = &cobra.Command{ Use: "create", - Args: cobra.NoArgs, + Args: validate.NoArgs, Short: "Create a new empty pod", Long: podCreateDescription, RunE: create, @@ -116,7 +117,7 @@ func create(cmd *cobra.Command, args []string) error { case "slip4netns": n.NSMode = specgen.Slirp default: - if strings.HasPrefix(netInput, "container:") { //nolint + if strings.HasPrefix(netInput, "container:") { // nolint split := strings.Split(netInput, ":") if len(split) != 2 { return errors.Errorf("invalid network paramater: %q", netInput) diff --git a/cmd/podman/pods/exists.go b/cmd/podman/pods/exists.go index 5a94bf150..cf3e3eae5 100644 --- a/cmd/podman/pods/exists.go +++ b/cmd/podman/pods/exists.go @@ -19,6 +19,7 @@ var ( Args: cobra.ExactArgs(1), Example: `podman pod exists podID podman pod exists mypod || podman pod create --name mypod`, + DisableFlagsInUseLine: true, } ) diff --git a/cmd/podman/pods/pod.go b/cmd/podman/pods/pod.go index e86b8aba4..edca08202 100644 --- a/cmd/podman/pods/pod.go +++ b/cmd/podman/pods/pod.go @@ -2,6 +2,7 @@ package pods import ( "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/spf13/cobra" @@ -17,7 +18,7 @@ var ( Short: "Manage pods", Long: "Manage pods", TraverseChildren: true, - RunE: registry.SubCommandExists, + RunE: validate.SubCommandExists, } containerConfig = util.DefaultContainerConfig() ) diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go index 6d0d9cf7f..b97dfeb66 100644 --- a/cmd/podman/pods/ps.go +++ b/cmd/podman/pods/ps.go @@ -12,6 +12,7 @@ import ( "time" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/docker/go-units" "github.com/pkg/errors" @@ -28,11 +29,12 @@ var ( Short: "list pods", Long: psDescription, RunE: pods, + Args: validate.NoArgs, } ) var ( - defaultHeaders string = "POD ID\tNAME\tSTATUS\tCREATED" + defaultHeaders = "POD ID\tNAME\tSTATUS\tCREATED" inputFilters []string noTrunc bool psInput entities.PodPSOptions diff --git a/cmd/podman/pods/rm.go b/cmd/podman/pods/rm.go index ea3a6476a..4b9882f8a 100644 --- a/cmd/podman/pods/rm.go +++ b/cmd/podman/pods/rm.go @@ -41,10 +41,10 @@ func init() { }) flags := rmCommand.Flags() - flags.BoolVarP(&rmOptions.All, "all", "a", false, "Restart all running pods") + 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, "Restart the latest pod podman is aware of") + flags.BoolVarP(&rmOptions.Latest, "latest", "l", false, "Remove the latest pod podman is aware of") if registry.IsRemote() { _ = flags.MarkHidden("latest") _ = flags.MarkHidden("ignore") diff --git a/cmd/podman/registry/registry.go b/cmd/podman/registry/registry.go index 2e9d59d10..69e2babfc 100644 --- a/cmd/podman/registry/registry.go +++ b/cmd/podman/registry/registry.go @@ -5,7 +5,6 @@ import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/domain/infra" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -76,21 +75,6 @@ func NewContainerEngine(cmd *cobra.Command, args []string) (entities.ContainerEn return containerEngine, nil } -func SubCommandExists(cmd *cobra.Command, args []string) error { - if len(args) > 0 { - return errors.Errorf("unrecognized command `%[1]s %[2]s`\nTry '%[1]s --help' for more information.", cmd.CommandPath(), args[0]) - } - return errors.Errorf("missing command '%[1]s COMMAND'\nTry '%[1]s --help' for more information.", cmd.CommandPath()) -} - -// IdOrLatestArgs used to validate a nameOrId was provided or the "--latest" flag -func IdOrLatestArgs(cmd *cobra.Command, args []string) error { - if len(args) > 1 || (len(args) == 0 && !cmd.Flag("latest").Changed) { - return errors.New(`command requires a name, id or the "--latest" flag`) - } - return nil -} - type PodmanOptionsKey struct{} func Context() context.Context { diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 84c3867f2..375faf8b1 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -9,6 +9,7 @@ import ( "strings" "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/rootless" "github.com/containers/libpod/pkg/tracing" @@ -34,7 +35,7 @@ Description: // UsageTemplate is the usage template for podman commands // This blocks the displaying of the global options. The main podman // command should not use this. -const usageTemplate = `Usage(v2):{{if (and .Runnable (not .HasAvailableSubCommands))}} +const usageTemplate = `Usage:{{if (and .Runnable (not .HasAvailableSubCommands))}} {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} @@ -60,7 +61,7 @@ var ( SilenceErrors: true, TraverseChildren: true, PersistentPreRunE: persistentPreRunE, - RunE: registry.SubCommandExists, + RunE: validate.SubCommandExists, PersistentPostRunE: persistentPostRunE, Version: version.Version, } @@ -78,6 +79,10 @@ func init() { ) rootFlags(registry.PodmanConfig(), rootCmd.PersistentFlags()) + + // "version" is a local flag to avoid collisions with sub-commands that use "-v" + var dummyVersion bool + rootCmd.Flags().BoolVarP(&dummyVersion, "version", "v", false, "Version of Podman") } func Execute() { @@ -96,7 +101,7 @@ func Execute() { func persistentPreRunE(cmd *cobra.Command, args []string) error { // TODO: Remove trace statement in podman V2.1 - logrus.Debugf("Called %s.PersistentPreRunE()", cmd.Name()) + logrus.Debugf("Called %s.PersistentPreRunE(%s)", cmd.Name(), strings.Join(os.Args, " ")) cfg := registry.PodmanConfig() @@ -145,7 +150,7 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { func persistentPostRunE(cmd *cobra.Command, args []string) error { // TODO: Remove trace statement in podman V2.1 - logrus.Debugf("Called %s.PersistentPostRunE()", cmd.Name()) + logrus.Debugf("Called %s.PersistentPostRunE(%s)", cmd.Name(), strings.Join(os.Args, " ")) cfg := registry.PodmanConfig() if cmd.Flag("cpu-profile").Changed { @@ -206,11 +211,6 @@ func rootFlags(opts *entities.PodmanConfig, flags *pflag.FlagSet) { flags.StringVarP(&opts.Uri, "remote", "r", "", "URL to access Podman service") flags.StringSliceVar(&opts.Identities, "identity", []string{}, "path to SSH identity file") - // Override default --help information of `--version` global flag - // TODO: restore -v option for version without breaking -v for volumes - var dummyVersion bool - flags.BoolVar(&dummyVersion, "version", false, "Version of Podman") - cfg := opts.Config flags.StringVar(&cfg.Engine.CgroupManager, "cgroup-manager", cfg.Engine.CgroupManager, opts.CGroupUsage) flags.StringVar(&opts.CpuProfile, "cpu-profile", "", "Path for the cpu profiling results") diff --git a/cmd/podman/system/events.go b/cmd/podman/system/events.go index 3c1943b55..6aae62dc0 100644 --- a/cmd/podman/system/events.go +++ b/cmd/podman/system/events.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/libpod/events" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" @@ -18,7 +19,7 @@ var ( eventsDescription = "Monitor podman events" eventsCommand = &cobra.Command{ Use: "events", - Args: cobra.NoArgs, + Args: validate.NoArgs, Short: "Show podman events", Long: eventsDescription, RunE: eventsCmd, diff --git a/cmd/podman/system/info.go b/cmd/podman/system/info.go index 8b36ef549..26be794c5 100644 --- a/cmd/podman/system/info.go +++ b/cmd/podman/system/info.go @@ -6,6 +6,7 @@ import ( "text/template" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/ghodss/yaml" "github.com/spf13/cobra" @@ -18,7 +19,7 @@ var ( ` infoCommand = &cobra.Command{ Use: "info", - Args: cobra.NoArgs, + Args: validate.NoArgs, Long: infoDescription, Short: "Display podman system information", RunE: info, diff --git a/cmd/podman/system/system.go b/cmd/podman/system/system.go index 2d55e8c13..d9691ad2a 100644 --- a/cmd/podman/system/system.go +++ b/cmd/podman/system/system.go @@ -2,6 +2,7 @@ package system import ( "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" ) @@ -16,7 +17,7 @@ var ( Short: "Manage podman", Long: "Manage podman", TraverseChildren: true, - RunE: registry.SubCommandExists, + RunE: validate.SubCommandExists, } ) diff --git a/cmd/podman/system/version.go b/cmd/podman/system/version.go index 5d3874de3..065eef309 100644 --- a/cmd/podman/system/version.go +++ b/cmd/podman/system/version.go @@ -10,6 +10,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/libpod/define" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" @@ -19,7 +20,7 @@ import ( var ( versionCommand = &cobra.Command{ Use: "version", - Args: cobra.NoArgs, + Args: validate.NoArgs, Short: "Display the Podman Version Information", RunE: version, Annotations: map[string]string{ @@ -55,14 +56,14 @@ func version(cmd *cobra.Command, args []string) error { // TODO we need to discuss how to implement // this more. current endpoints dont have a // version endpoint. maybe we use info? - //if remote { + // if remote { // v.Server, err = getRemoteVersion(c) // if err != nil { // return err // } - //} else { + // } else { v.Server = v.Client - //} + // } versionOutputFormat := versionFormat if versionOutputFormat != "" { diff --git a/cmd/podman/utils/alias.go b/cmd/podman/utils/alias.go index 54b3c5e89..e484461c5 100644 --- a/cmd/podman/utils/alias.go +++ b/cmd/podman/utils/alias.go @@ -2,7 +2,7 @@ package utils import "github.com/spf13/pflag" -// AliasFlags is a function to handle backwards compatability with old flags +// AliasFlags is a function to handle backwards compatibility with old flags func AliasFlags(f *pflag.FlagSet, name string) pflag.NormalizedName { switch name { case "healthcheck-command": diff --git a/cmd/podman/validate/args.go b/cmd/podman/validate/args.go new file mode 100644 index 000000000..14b4d7897 --- /dev/null +++ b/cmd/podman/validate/args.go @@ -0,0 +1,32 @@ +package validate + +import ( + "fmt" + + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +// NoArgs returns an error if any args are included. +func NoArgs(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + return fmt.Errorf("`%s` takes no arguments", cmd.CommandPath()) + } + return nil +} + +// SubCommandExists returns an error if no sub command is provided +func SubCommandExists(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + return errors.Errorf("unrecognized command `%[1]s %[2]s`\nTry '%[1]s --help' for more information.", cmd.CommandPath(), args[0]) + } + return errors.Errorf("missing command '%[1]s COMMAND'\nTry '%[1]s --help' for more information.", cmd.CommandPath()) +} + +// IdOrLatestArgs used to validate a nameOrId was provided or the "--latest" flag +func IdOrLatestArgs(cmd *cobra.Command, args []string) error { + if len(args) > 1 || (len(args) == 0 && !cmd.Flag("latest").Changed) { + return fmt.Errorf("`%s` requires a name, id or the \"--latest\" flag", cmd.CommandPath()) + } + return nil +} diff --git a/cmd/podman/validate/choice.go b/cmd/podman/validate/choice.go new file mode 100644 index 000000000..572c5f4a5 --- /dev/null +++ b/cmd/podman/validate/choice.go @@ -0,0 +1,46 @@ +package validate + +import ( + "fmt" + "strings" +) + +// Honors cobra.Value interface +type choiceValue struct { + value *string + choices []string +} + +// ChoiceValue may be used in cobra FlagSet methods Var/VarP/VarPF() to select from a set of values +// +// Example: +// created := validate.ChoiceValue(&opts.Sort, "command", "created", "id", "image", "names", "runningfor", "size", "status") +// flags.Var(created, "sort", "Sort output by: "+created.Choices()) +func ChoiceValue(p *string, choices ...string) *choiceValue { + return &choiceValue{ + value: p, + choices: choices, + } +} + +func (c *choiceValue) String() string { + return *c.value +} + +func (c *choiceValue) Set(value string) error { + for _, v := range c.choices { + if v == value { + *c.value = value + return nil + } + } + return fmt.Errorf("%q is not a valid value. Choose from: %q", value, c.Choices()) +} + +func (c *choiceValue) Choices() string { + return strings.Join(c.choices, ", ") +} + +func (c *choiceValue) Type() string { + return "choice" +} diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go index f75de6b4b..72bf9f25b 100644 --- a/cmd/podman/volumes/list.go +++ b/cmd/podman/volumes/list.go @@ -2,6 +2,7 @@ package volumes import ( "context" + "fmt" "html/template" "io" "os" @@ -9,6 +10,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/pkg/errors" "github.com/spf13/cobra" @@ -23,7 +25,7 @@ and the output format can be changed to JSON or a user specified Go template.` lsCommand = &cobra.Command{ Use: "ls", Aliases: []string{"list"}, - Args: cobra.NoArgs, + Args: validate.NoArgs, Short: "List volumes", Long: volumeLsDescription, RunE: list, @@ -57,6 +59,9 @@ func list(cmd *cobra.Command, args []string) error { if cliOpts.Quiet && cmd.Flag("format").Changed { return errors.New("quiet and format flags cannot be used together") } + if len(cliOpts.Filter) > 0 { + lsOpts.Filter = make(map[string][]string) + } for _, f := range cliOpts.Filter { filterSplit := strings.Split(f, "=") if len(filterSplit) < 2 { @@ -68,6 +73,10 @@ func list(cmd *cobra.Command, args []string) error { if err != nil { return err } + if cliOpts.Format == "json" { + return outputJSON(responses) + } + if len(responses) < 1 { return nil } @@ -99,3 +108,12 @@ func list(cmd *cobra.Command, args []string) error { } return nil } + +func outputJSON(vols []*entities.VolumeListReport) error { + b, err := json.MarshalIndent(vols, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil +} diff --git a/cmd/podman/volumes/prune.go b/cmd/podman/volumes/prune.go index 197a9da9b..2c3ed88f3 100644 --- a/cmd/podman/volumes/prune.go +++ b/cmd/podman/volumes/prune.go @@ -9,6 +9,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/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -21,7 +22,7 @@ var ( Note all data will be destroyed.` pruneCommand = &cobra.Command{ Use: "prune", - Args: cobra.NoArgs, + Args: validate.NoArgs, Short: "Remove all unused volumes", Long: volumePruneDescription, RunE: prune, diff --git a/cmd/podman/volumes/volume.go b/cmd/podman/volumes/volume.go index 4d74ff084..3e90d178c 100644 --- a/cmd/podman/volumes/volume.go +++ b/cmd/podman/volumes/volume.go @@ -2,6 +2,7 @@ package volumes import ( "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" ) @@ -16,7 +17,7 @@ var ( Short: "Manage volumes", Long: "Volumes are created in and can be shared between containers", TraverseChildren: true, - RunE: registry.SubCommandExists, + RunE: validate.SubCommandExists, } ) |