diff options
Diffstat (limited to 'cmd/podman')
99 files changed, 5670 insertions, 5079 deletions
diff --git a/cmd/podman/attach.go b/cmd/podman/attach.go index bcfee6891..074675e45 100644 --- a/cmd/podman/attach.go +++ b/cmd/podman/attach.go @@ -3,57 +3,57 @@ package main import ( "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - attachFlags = []cli.Flag{ - cli.StringFlag{ - Name: "detach-keys", - Usage: "Override the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _.", - }, - cli.BoolFlag{ - Name: "no-stdin", - Usage: "Do not attach STDIN. The default is false.", - }, - cli.BoolTFlag{ - Name: "sig-proxy", - Usage: "Proxy received signals to the process (default true)", - }, - LatestFlag, - } + attachCommand cliconfig.AttachValues attachDescription = "The podman attach command allows you to attach to a running container using the container's ID or name, either to view its ongoing output or to control it interactively." - attachCommand = cli.Command{ - Name: "attach", - Usage: "Attach to a running container", - Description: attachDescription, - Flags: sortFlags(attachFlags), - Action: attachCmd, - ArgsUsage: "", - OnUsageError: usageErrorHandler, + _attachCommand = &cobra.Command{ + Use: "attach", + Short: "Attach to a running container", + Long: attachDescription, + RunE: func(cmd *cobra.Command, args []string) error { + attachCommand.InputArgs = args + attachCommand.GlobalFlags = MainGlobalOpts + return attachCmd(&attachCommand) + }, + Example: `podman attach ctrID + podman attach 1234 + podman attach --no-stdin foobar`, } ) -func attachCmd(c *cli.Context) error { - args := c.Args() +func init() { + attachCommand.Command = _attachCommand + attachCommand.SetUsageTemplate(UsageTemplate()) + flags := attachCommand.Flags() + flags.StringVar(&attachCommand.DetachKeys, "detach-keys", "", "Override the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _") + flags.BoolVar(&attachCommand.NoStdin, "no-stdin", false, "Do not attach STDIN. The default is false") + flags.BoolVar(&attachCommand.SigProxy, "sig-proxy", true, "Proxy received signals to the process (default true)") + flags.BoolVarP(&attachCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + markFlagHiddenForRemoteClient("latest", flags) +} + +func attachCmd(c *cliconfig.AttachValues) error { + args := c.InputArgs var ctr *libpod.Container - if err := validateFlags(c, attachFlags); err != nil { - return err - } - if len(c.Args()) > 1 || (len(c.Args()) == 0 && !c.Bool("latest")) { + + if len(c.InputArgs) > 1 || (len(c.InputArgs) == 0 && !c.Latest) { return errors.Errorf("attach requires the name or id of one running container or the latest flag") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - if c.Bool("latest") { + if c.Latest { ctr, err = runtime.GetLatestContainer() } else { ctr, err = runtime.LookupContainer(args[0]) @@ -72,11 +72,12 @@ func attachCmd(c *cli.Context) error { } inputStream := os.Stdin - if c.Bool("no-stdin") { + if c.NoStdin { inputStream = nil } - if err := startAttachCtr(ctr, os.Stdout, os.Stderr, inputStream, c.String("detach-keys"), c.BoolT("sig-proxy"), false); err != nil { + // If the container is in a pod, also set to recursively start dependencies + if err := startAttachCtr(ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, c.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != libpod.ErrDetach { return errors.Wrapf(err, "error attaching to container %s", ctr.ID()) } diff --git a/cmd/podman/build.go b/cmd/podman/build.go index 0cd1bfbe3..e40e35cb5 100644 --- a/cmd/podman/build.go +++ b/cmd/podman/build.go @@ -1,7 +1,6 @@ package main import ( - "io/ioutil" "os" "path/filepath" "strings" @@ -9,39 +8,69 @@ import ( "github.com/containers/buildah" "github.com/containers/buildah/imagebuildah" buildahcli "github.com/containers/buildah/pkg/cli" - "github.com/containers/buildah/pkg/parse" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" + "github.com/docker/go-units" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - layerFlags = []cli.Flag{ - cli.BoolTFlag{ - Name: "force-rm", - Usage: "Always remove intermediate containers after a build, even if the build is unsuccessful. (default true)", - }, - cli.BoolTFlag{ - Name: "layers", - Usage: "Cache intermediate layers during build. Use BUILDAH_LAYERS environment variable to override. ", - }, - } + buildCommand cliconfig.BuildValues buildDescription = "Builds an OCI or Docker image using instructions from one\n" + "or more Dockerfiles and a specified build context directory." - buildCommand = cli.Command{ - Name: "build", - Usage: "Build an image using instructions from Dockerfiles", - Description: buildDescription, - Flags: sortFlags(append(append(buildahcli.BudFlags, layerFlags...), buildahcli.FromAndBudFlags...)), - Action: buildCmd, - ArgsUsage: "CONTEXT-DIRECTORY | URL", - SkipArgReorder: true, - OnUsageError: usageErrorHandler, + layerValues buildahcli.LayerResults + budFlagsValues buildahcli.BudResults + fromAndBudValues buildahcli.FromAndBudResults + userNSValues buildahcli.UserNSResults + namespaceValues buildahcli.NameSpaceResults + + _buildCommand = &cobra.Command{ + Use: "build", + Short: "Build an image using instructions from Dockerfiles", + Long: buildDescription, + RunE: func(cmd *cobra.Command, args []string) error { + buildCommand.InputArgs = args + buildCommand.GlobalFlags = MainGlobalOpts + buildCommand.BudResults = &budFlagsValues + buildCommand.UserNSResults = &userNSValues + buildCommand.FromAndBudResults = &fromAndBudValues + buildCommand.LayerResults = &layerValues + buildCommand.NameSpaceResults = &namespaceValues + return buildCmd(&buildCommand) + }, + Example: `podman build . + podman build --cert-dir ~/auth --creds=username:password -t imageName -f Dockerfile.simple . + podman build --layers --force-rm --tag imageName .`, } ) +func init() { + buildCommand.Command = _buildCommand + buildCommand.SetUsageTemplate(UsageTemplate()) + flags := buildCommand.Flags() + flags.SetInterspersed(false) + + budFlags := buildahcli.GetBudFlags(&budFlagsValues) + flag := budFlags.Lookup("pull-always") + flag.Value.Set("true") + flag.DefValue = "true" + layerFlags := buildahcli.GetLayerFlags(&layerValues) + flag = layerFlags.Lookup("layers") + flag.Value.Set(useLayers()) + flag.DefValue = (useLayers()) + flag = layerFlags.Lookup("force-rm") + flag.Value.Set("true") + flag.DefValue = "true" + + fromAndBugFlags := buildahcli.GetFromAndBudFlags(&fromAndBudValues, &userNSValues, &namespaceValues) + + flags.AddFlagSet(&budFlags) + flags.AddFlagSet(&layerFlags) + flags.AddFlagSet(&fromAndBugFlags) +} + func getDockerfiles(files []string) []string { var dockerfiles []string for _, f := range files { @@ -54,30 +83,30 @@ func getDockerfiles(files []string) []string { return dockerfiles } -func buildCmd(c *cli.Context) error { +func buildCmd(c *cliconfig.BuildValues) error { // The following was taken directly from containers/buildah/cmd/bud.go // TODO Find a away to vendor more of this in rather than copy from bud - output := "" tags := []string{} - if c.IsSet("tag") || c.IsSet("t") { - tags = c.StringSlice("tag") + if c.Flag("tag").Changed { + tags = c.Tag if len(tags) > 0 { output = tags[0] tags = tags[1:] } } + pullPolicy := imagebuildah.PullNever - if c.BoolT("pull") { + if c.Pull { pullPolicy = imagebuildah.PullIfMissing } - if c.Bool("pull-always") { + if c.PullAlways { pullPolicy = imagebuildah.PullAlways } args := make(map[string]string) - if c.IsSet("build-arg") { - for _, arg := range c.StringSlice("build-arg") { + if c.Flag("build-arg").Changed { + for _, arg := range c.BuildArg { av := strings.SplitN(arg, "=", 2) if len(av) > 1 { args[av[0]] = av[1] @@ -87,15 +116,15 @@ func buildCmd(c *cli.Context) error { } } - dockerfiles := getDockerfiles(c.StringSlice("file")) - format, err := getFormat(c) + dockerfiles := getDockerfiles(c.File) + format, err := getFormat(&c.PodmanCommand) if err != nil { return nil } contextDir := "" - cliArgs := c.Args() + cliArgs := c.InputArgs - layers := c.BoolT("layers") // layers for podman defaults to true + layers := c.Layers // layers for podman defaults to true // Check to see if the BUILDAH_LAYERS environment variable is set and override command-line if _, ok := os.LookupEnv("BUILDAH_LAYERS"); ok { layers = buildahcli.UseLayers() @@ -124,7 +153,7 @@ func buildCmd(c *cli.Context) error { } contextDir = absDir } - cliArgs = cliArgs.Tail() + cliArgs = Tail(cliArgs) } else { // No context directory or URL was specified. Try to use the // home of the first locally-available Dockerfile. @@ -153,30 +182,28 @@ func buildCmd(c *cli.Context) error { if len(dockerfiles) == 0 { dockerfiles = append(dockerfiles, filepath.Join(contextDir, "Dockerfile")) } - if err := parse.ValidateFlags(c, buildahcli.BudFlags); err != nil { - return err + + runtime, err := adapter.GetRuntime(&c.PodmanCommand) + if err != nil { + return errors.Wrapf(err, "could not get runtime") } runtimeFlags := []string{} - for _, arg := range c.StringSlice("runtime-flag") { + for _, arg := range c.RuntimeFlags { runtimeFlags = append(runtimeFlags, "--"+arg) } // end from buildah - runtime, err := libpodruntime.GetRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } defer runtime.Shutdown(false) var stdout, stderr, reporter *os.File stdout = os.Stdout stderr = os.Stderr reporter = os.Stderr - if c.IsSet("logfile") { - f, err := os.OpenFile(c.String("logfile"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) + if c.Flag("logfile").Changed { + f, err := os.OpenFile(c.Logfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { - return errors.Errorf("error opening logfile %q: %v", c.String("logfile"), err) + return errors.Errorf("error opening logfile %q: %v", c.Logfile, err) } defer f.Close() logrus.SetOutput(f) @@ -185,70 +212,83 @@ func buildCmd(c *cli.Context) error { reporter = f } - systemContext, err := parse.SystemContextFromOptions(c) - if err != nil { - return errors.Wrapf(err, "error building system context") - } - systemContext.AuthFilePath = getAuthFile(c.String("authfile")) - commonOpts, err := parse.CommonBuildOptions(c) - if err != nil { - return err + var memoryLimit, memorySwap int64 + if c.Flags().Changed("memory") { + memoryLimit, err = units.RAMInBytes(c.Memory) + if err != nil { + return err + } } - namespaceOptions, networkPolicy, err := parse.NamespaceOptions(c) - if err != nil { - return errors.Wrapf(err, "error parsing namespace-related options") - } - usernsOption, idmappingOptions, err := parse.IDMappingOptions(c) - if err != nil { - return errors.Wrapf(err, "error parsing ID mapping options") + if c.Flags().Changed("memory-swap") { + memorySwap, err = units.RAMInBytes(c.MemorySwap) + if err != nil { + return err + } } - namespaceOptions.AddOrReplace(usernsOption...) - ociruntime := runtime.GetOCIRuntimePath() - if c.IsSet("runtime") { - ociruntime = c.String("runtime") + buildOpts := buildah.CommonBuildOptions{ + AddHost: c.AddHost, + CgroupParent: c.CgroupParent, + CPUPeriod: c.CPUPeriod, + CPUQuota: c.CPUQuota, + CPUShares: c.CPUShares, + CPUSetCPUs: c.CPUSetCPUs, + CPUSetMems: c.CPUSetMems, + Memory: memoryLimit, + MemorySwap: memorySwap, + ShmSize: c.ShmSize, + Ulimit: c.Ulimit, + Volumes: c.Volume, } + options := imagebuildah.BuildOptions{ - ContextDirectory: contextDir, - PullPolicy: pullPolicy, - Compression: imagebuildah.Gzip, - Quiet: c.Bool("quiet"), - SignaturePolicyPath: c.String("signature-policy"), - Args: args, - Output: output, + CommonBuildOpts: &buildOpts, AdditionalTags: tags, - Out: stdout, + Annotations: c.Annotation, + Args: args, + CNIConfigDir: c.CNIConfigDir, + CNIPluginPath: c.CNIPlugInPath, + Compression: imagebuildah.Gzip, + ContextDirectory: contextDir, + DefaultMountsFilePath: c.GlobalFlags.DefaultMountsFile, Err: stderr, + ForceRmIntermediateCtrs: c.ForceRm, + IIDFile: c.Iidfile, + Labels: c.Label, + Layers: layers, + NoCache: c.NoCache, + Out: stdout, + Output: output, + OutputFormat: format, + PullPolicy: pullPolicy, + Quiet: c.Quiet, + RemoveIntermediateCtrs: c.Rm, ReportWriter: reporter, - Runtime: ociruntime, RuntimeArgs: runtimeFlags, - OutputFormat: format, - SystemContext: systemContext, - NamespaceOptions: namespaceOptions, - ConfigureNetwork: networkPolicy, - CNIPluginPath: c.String("cni-plugin-path"), - CNIConfigDir: c.String("cni-config-dir"), - IDMappingOptions: idmappingOptions, - CommonBuildOpts: commonOpts, - DefaultMountsFilePath: c.GlobalString("default-mounts-file"), - IIDFile: c.String("iidfile"), - Squash: c.Bool("squash"), - Labels: c.StringSlice("label"), - Annotations: c.StringSlice("annotation"), - Layers: layers, - NoCache: c.Bool("no-cache"), - RemoveIntermediateCtrs: c.BoolT("rm"), - ForceRmIntermediateCtrs: c.BoolT("force-rm"), + SignaturePolicyPath: c.SignaturePolicy, + Squash: c.Squash, + Target: c.Target, } + return runtime.Build(getContext(), c, options, dockerfiles) +} - if c.Bool("quiet") { - options.ReportWriter = ioutil.Discard +// Tail returns a string slice after the first element unless there are +// not enough elements, then it returns an empty slice. This is to replace +// the urfavecli Tail method for args +func Tail(a []string) []string { + if len(a) >= 2 { + return []string(a)[1:] } + return []string{} +} - if rootless.IsRootless() { - options.Isolation = buildah.IsolationOCIRootless +// useLayers returns false if BUILDAH_LAYERS is set to "0" or "false" +// otherwise it returns true +func useLayers() string { + layers := os.Getenv("BUILDAH_LAYERS") + if strings.ToLower(layers) == "false" || layers == "0" { + return "false" } - - return runtime.Build(getContext(), options, dockerfiles...) + return "true" } diff --git a/cmd/podman/checkpoint.go b/cmd/podman/checkpoint.go index 5c3363147..c9de5638b 100644 --- a/cmd/podman/checkpoint.go +++ b/cmd/podman/checkpoint.go @@ -5,70 +5,69 @@ import ( "fmt" "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + checkpointCommand cliconfig.CheckpointValues checkpointDescription = ` podman container checkpoint Checkpoints one or more running containers. The container name or ID can be used. ` - checkpointFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "keep, k", - Usage: "Keep all temporary checkpoint files", + _checkpointCommand = &cobra.Command{ + Use: "checkpoint", + Short: "Checkpoints one or more containers", + Long: checkpointDescription, + RunE: func(cmd *cobra.Command, args []string) error { + checkpointCommand.InputArgs = args + checkpointCommand.GlobalFlags = MainGlobalOpts + return checkpointCmd(&checkpointCommand) }, - cli.BoolFlag{ - Name: "leave-running, R", - Usage: "Leave the container running after writing checkpoint to disk", + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, false) }, - cli.BoolFlag{ - Name: "tcp-established", - Usage: "Checkpoint a container with established TCP connections", - }, - cli.BoolFlag{ - Name: "all, a", - Usage: "Checkpoint all running containers", - }, - LatestFlag, - } - checkpointCommand = cli.Command{ - Name: "checkpoint", - Usage: "Checkpoints one or more containers", - Description: checkpointDescription, - Flags: sortFlags(checkpointFlags), - Action: checkpointCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", + Example: `podman checkpoint --keep ctrID + podman checkpoint --all + podman checkpoint --leave-running --latest`, } ) -func checkpointCmd(c *cli.Context) error { +func init() { + checkpointCommand.Command = _checkpointCommand + checkpointCommand.SetUsageTemplate(UsageTemplate()) + + flags := checkpointCommand.Flags() + flags.BoolVarP(&checkpointCommand.Keep, "keep", "k", false, "Keep all temporary checkpoint files") + flags.BoolVarP(&checkpointCommand.LeaveRunning, "leave-running", "R", false, "Leave the container running after writing checkpoint to disk") + flags.BoolVar(&checkpointCommand.TcpEstablished, "tcp-established", false, "Checkpoint a container with established TCP connections") + flags.BoolVarP(&checkpointCommand.All, "all", "a", false, "Checkpoint all running containers") + flags.BoolVarP(&checkpointCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + markFlagHiddenForRemoteClient("latest", flags) +} + +func checkpointCmd(c *cliconfig.CheckpointValues) error { if rootless.IsRootless() { return errors.New("checkpointing a container requires root") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) options := libpod.ContainerCheckpointOptions{ - Keep: c.Bool("keep"), - KeepRunning: c.Bool("leave-running"), - TCPEstablished: c.Bool("tcp-established"), - } - - if err := checkAllAndLatest(c); err != nil { - return err + Keep: c.Keep, + KeepRunning: c.LeaveRunning, + TCPEstablished: c.TcpEstablished, } - - containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + containers, lastError := getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStateRunning, "running") for _, ctr := range containers { if err = ctr.Checkpoint(context.TODO(), options); err != nil { diff --git a/cmd/podman/cleanup.go b/cmd/podman/cleanup.go index bc4af9f50..d68255aa2 100644 --- a/cmd/podman/cleanup.go +++ b/cmd/podman/cleanup.go @@ -4,60 +4,79 @@ import ( "fmt" "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - cleanupFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Cleans up all containers", - }, - LatestFlag, - } + cleanupCommand cliconfig.CleanupValues cleanupDescription = ` podman container cleanup Cleans up mount points and network stacks on one or more containers from the host. The container name or ID can be used. This command is used internally when running containers, but can also be used if container cleanup has failed when a container exits. ` - cleanupCommand = cli.Command{ - Name: "cleanup", - Usage: "Cleanup network and mountpoints of one or more containers", - Description: cleanupDescription, - Flags: sortFlags(cleanupFlags), - Action: cleanupCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", - OnUsageError: usageErrorHandler, + _cleanupCommand = &cobra.Command{ + Use: "cleanup", + Short: "Cleanup network and mountpoints of one or more containers", + Long: cleanupDescription, + RunE: func(cmd *cobra.Command, args []string) error { + cleanupCommand.InputArgs = args + cleanupCommand.GlobalFlags = MainGlobalOpts + return cleanupCmd(&cleanupCommand) + }, + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, false) + }, + Example: `podman container cleanup --latest + podman container cleanup ctrID1 ctrID2 ctrID3 + podman container cleanup --all`, } ) -func cleanupCmd(c *cli.Context) error { - if err := validateFlags(c, cleanupFlags); err != nil { - return err - } - runtime, err := libpodruntime.GetRuntime(c) +func init() { + cleanupCommand.Command = _cleanupCommand + cleanupCommand.SetUsageTemplate(UsageTemplate()) + flags := cleanupCommand.Flags() + + flags.BoolVarP(&cleanupCommand.All, "all", "a", false, "Cleans up all containers") + flags.BoolVarP(&cleanupCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&cleanupCommand.Remove, "rm", false, "After cleanup, remove the container entirely") + markFlagHiddenForRemoteClient("latest", flags) +} + +func cleanupCmd(c *cliconfig.CleanupValues) error { + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if err := checkAllAndLatest(c); err != nil { - return err - } - - cleanupContainers, lastError := getAllOrLatestContainers(c, runtime, -1, "all") + cleanupContainers, lastError := getAllOrLatestContainers(&c.PodmanCommand, runtime, -1, "all") ctx := getContext() for _, ctr := range cleanupContainers { - if err = ctr.Cleanup(ctx); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) + hadError := false + if c.Remove { + if err := runtime.RemoveContainer(ctx, ctr, false, false); err != nil { + if lastError != nil { + fmt.Fprintln(os.Stderr, lastError) + } + lastError = errors.Wrapf(err, "failed to cleanup and remove container %v", ctr.ID()) + hadError = true } - lastError = errors.Wrapf(err, "failed to cleanup container %v", ctr.ID()) } else { + if err := ctr.Cleanup(ctx); err != nil { + if lastError != nil { + fmt.Fprintln(os.Stderr, lastError) + } + lastError = errors.Wrapf(err, "failed to cleanup container %v", ctr.ID()) + hadError = true + } + } + if !hadError { fmt.Println(ctr.ID()) } } diff --git a/cmd/podman/cliconfig/commands.go b/cmd/podman/cliconfig/commands.go new file mode 100644 index 000000000..7d1e762d9 --- /dev/null +++ b/cmd/podman/cliconfig/commands.go @@ -0,0 +1,109 @@ +package cliconfig + +// GlobalIsSet is a compatibility method for urfave +func (p *PodmanCommand) GlobalIsSet(opt string) bool { + flag := p.PersistentFlags().Lookup(opt) + if flag == nil { + return false + } + return flag.Changed +} + +// IsSet is a compatibility method for urfave +func (p *PodmanCommand) IsSet(opt string) bool { + flag := p.Flags().Lookup(opt) + if flag == nil { + return false + } + return flag.Changed +} + +// Bool is a compatibility method for urfave +func (p *PodmanCommand) Bool(opt string) bool { + flag := p.Flags().Lookup(opt) + if flag == nil { + return false + } + val, _ := p.Flags().GetBool(opt) + return val +} + +// String is a compatibility method for urfave +func (p *PodmanCommand) String(opt string) string { + flag := p.Flags().Lookup(opt) + if flag == nil { + return "" + } + val, _ := p.Flags().GetString(opt) + return val +} + +// StringArray is a compatibility method for urfave +func (p *PodmanCommand) StringArray(opt string) []string { + flag := p.Flags().Lookup(opt) + if flag == nil { + return []string{} + } + val, _ := p.Flags().GetStringArray(opt) + return val +} + +// StringSlice is a compatibility method for urfave +func (p *PodmanCommand) StringSlice(opt string) []string { + flag := p.Flags().Lookup(opt) + if flag == nil { + return []string{} + } + val, _ := p.Flags().GetStringSlice(opt) + return val +} + +// Int is a compatibility method for urfave +func (p *PodmanCommand) Int(opt string) int { + flag := p.Flags().Lookup(opt) + if flag == nil { + return 0 + } + val, _ := p.Flags().GetInt(opt) + return val +} + +// Unt is a compatibility method for urfave +func (p *PodmanCommand) Uint(opt string) uint { + flag := p.Flags().Lookup(opt) + if flag == nil { + return 0 + } + val, _ := p.Flags().GetUint(opt) + return val +} + +// Int64 is a compatibility method for urfave +func (p *PodmanCommand) Int64(opt string) int64 { + flag := p.Flags().Lookup(opt) + if flag == nil { + return 0 + } + val, _ := p.Flags().GetInt64(opt) + return val +} + +// Unt64 is a compatibility method for urfave +func (p *PodmanCommand) Uint64(opt string) uint64 { + flag := p.Flags().Lookup(opt) + if flag == nil { + return 0 + } + val, _ := p.Flags().GetUint64(opt) + return val +} + +// Float64 is a compatibility method for urfave +func (p *PodmanCommand) Float64(opt string) float64 { + flag := p.Flags().Lookup(opt) + if flag == nil { + return 0 + } + val, _ := p.Flags().GetFloat64(opt) + return val +} diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go new file mode 100644 index 000000000..a9032202f --- /dev/null +++ b/cmd/podman/cliconfig/config.go @@ -0,0 +1,554 @@ +package cliconfig + +import ( + "github.com/spf13/cobra" +) + +type PodmanCommand struct { + *cobra.Command + InputArgs []string + GlobalFlags MainFlags +} + +type MainFlags struct { + CGroupManager string + CniConfigDir string + ConmonPath string + DefaultMountsFile string + HooksDir []string + MaxWorks int + Namespace string + Root string + Runroot string + Runtime string + StorageDriver string + StorageOpts []string + Syslog bool + Trace bool + + Config string + CpuProfile string + LogLevel string + TmpDir string +} + +type AttachValues struct { + PodmanCommand + DetachKeys string + Latest bool + NoStdin bool + SigProxy bool +} + +type ImagesValues struct { + PodmanCommand + All bool + Digests bool + Filter []string + Format string + Noheading bool + NoTrunc bool + Quiet bool + Sort string +} + +type TagValues struct { + PodmanCommand +} + +type WaitValues struct { + PodmanCommand + Interval uint + Latest bool +} + +type CheckpointValues struct { + PodmanCommand + Keep bool + LeaveRunning bool + TcpEstablished bool + All bool + Latest bool +} + +type CommitValues struct { + PodmanCommand + Change []string + Format string + Message string + Author string + Pause bool + Quiet bool +} + +type ContainersPrune struct { + PodmanCommand +} + +type DiffValues struct { + PodmanCommand + Archive bool + Format string +} + +type ExecValues struct { + PodmanCommand + Env []string + Privileged bool + Interfactive bool + Tty bool + User string + Latest bool + Workdir string +} + +type ImageExistsValues struct { + PodmanCommand +} + +type ContainerExistsValues struct { + PodmanCommand +} + +type PodExistsValues struct { + PodmanCommand +} + +type ExportValues struct { + PodmanCommand + Output string +} + +type GenerateKubeValues struct { + PodmanCommand + Service bool +} + +type HistoryValues struct { + PodmanCommand + Human bool + NoTrunc bool + Quiet bool + Format string +} +type PruneImagesValues struct { + PodmanCommand + All bool +} + +type PruneContainersValues struct { + PodmanCommand + Force bool +} + +type ImportValues struct { + PodmanCommand + Change []string + Message string + Quiet bool +} + +type InfoValues struct { + PodmanCommand + Debug bool + Format string +} + +type InspectValues struct { + PodmanCommand + TypeObject string + Format string + Size bool + Latest bool +} + +type KillValues struct { + PodmanCommand + All bool + Signal string + Latest bool +} + +type LoadValues struct { + PodmanCommand + Input string + Quiet bool + SignaturePolicy string +} + +type LoginValues struct { + PodmanCommand + Password string + StdinPassword bool + Username string + Authfile string + CertDir string + GetLogin bool + TlsVerify bool +} + +type LogoutValues struct { + PodmanCommand + Authfile string + All bool +} + +type LogsValues struct { + PodmanCommand + Details bool + Follow bool + Since string + Tail uint64 + Timestamps bool + Latest bool +} + +type MountValues struct { + PodmanCommand + All bool + Format string + NoTrunc bool + Latest bool +} + +type PauseValues struct { + PodmanCommand + All bool +} + +type KubePlayValues struct { + PodmanCommand + Authfile string + CertDir string + Creds string + Quiet bool + SignaturePolicy string + TlsVerify bool +} + +type PodCreateValues struct { + PodmanCommand + CgroupParent string + Infra bool + InfraImage string + InfraCommand string + LabelFile []string + Labels []string + Name string + PodIDFile string + Publish []string + Share string +} + +type PodInspectValues struct { + PodmanCommand + Latest bool +} + +type PodKillValues struct { + PodmanCommand + All bool + Signal string + Latest bool +} + +type PodPauseValues struct { + PodmanCommand + All bool + Latest bool +} + +type PodPsValues struct { + PodmanCommand + CtrNames bool + CtrIDs bool + CtrStatus bool + Filter string + Format string + Latest bool + Namespace bool + NoTrunc bool + Quiet bool + Sort string +} + +type PodRestartValues struct { + PodmanCommand + All bool + Latest bool +} + +type PodRmValues struct { + PodmanCommand + All bool + Force bool + Latest bool +} + +type PodStartValues struct { + PodmanCommand + All bool + Latest bool +} +type PodStatsValues struct { + PodmanCommand + All bool + NoStream bool + NoReset bool + Format string + Latest bool +} + +type PodStopValues struct { + PodmanCommand + All bool + Latest bool + Timeout uint +} + +type PodTopValues struct { + PodmanCommand + Latest bool + ListDescriptors bool +} +type PodUnpauseValues struct { + PodmanCommand + All bool + Latest bool +} + +type PortValues struct { + PodmanCommand + All bool + Latest bool +} + +type PsValues struct { + PodmanCommand + All bool + Filter []string + Format string + Last int + Latest bool + Namespace bool + NoTrunct bool + Pod bool + Quiet bool + Size bool + Sort string + Sync bool +} + +type PullValues struct { + PodmanCommand + AllTags bool + Authfile string + CertDir string + Creds string + Quiet bool + SignaturePolicy string + TlsVerify bool +} + +type PushValues struct { + PodmanCommand + Authfile string + CertDir string + Compress bool + Creds string + Format string + Quiet bool + RemoveSignatures bool + SignBy string + SignaturePolicy string + TlsVerify bool +} + +type RefreshValues struct { + PodmanCommand +} + +type RestartValues struct { + PodmanCommand + All bool + Latest bool + Running bool + Timeout uint +} + +type RestoreValues struct { + PodmanCommand + All bool + Keep bool + Latest bool + TcpEstablished bool +} + +type RmValues struct { + PodmanCommand + All bool + Force bool + Latest bool + Volumes bool +} + +type RmiValues struct { + PodmanCommand + All bool + Force bool +} + +type RunlabelValues struct { + PodmanCommand + Authfile string + Display bool + CertDir string + Creds string + Name string + Opt1 string + Opt2 string + Opt3 string + Quiet bool + Pull bool + SignaturePolicy string + TlsVerify bool +} +type SaveValues struct { + PodmanCommand + Compress bool + Format string + Output string + Quiet bool +} + +type SearchValues struct { + PodmanCommand + Authfile string + Filter []string + Format string + Limit int + NoTrunc bool + TlsVerify bool +} + +type SignValues struct { + PodmanCommand + Directory string + SignBy string +} + +type StartValues struct { + PodmanCommand + Attach bool + DetachKeys string + Interactive bool + Latest bool + SigProxy bool +} + +type StatsValues struct { + PodmanCommand + All bool + Format string + Latest bool + NoReset bool + NoStream bool +} + +type StopValues struct { + PodmanCommand + All bool + Latest bool + Timeout uint +} + +type TopValues struct { + PodmanCommand + Latest bool + ListDescriptors bool +} + +type UmountValues struct { + PodmanCommand + All bool + Force bool + Latest bool +} + +type UnpauseValues struct { + PodmanCommand + All bool +} + +type VarlinkValues struct { + PodmanCommand + Timeout int64 +} + +type SetTrustValues struct { + PodmanCommand + PolicyPath string + PubKeysFile []string + TrustType string +} + +type ShowTrustValues struct { + PodmanCommand + Json bool + PolicyPath string + Raw bool + RegistryPath string +} + +type VersionValues struct { + PodmanCommand + Format string +} + +type VolumeCreateValues struct { + PodmanCommand + Driver string + Label []string + Opt []string +} +type VolumeInspectValues struct { + PodmanCommand + All bool + Format string +} + +type VolumeLsValues struct { + PodmanCommand + Filter string + Format string + Quiet bool +} + +type VolumePruneValues struct { + PodmanCommand + Force bool +} + +type VolumeRmValues struct { + PodmanCommand + All bool + Force bool +} + +type CleanupValues struct { + PodmanCommand + All bool + Latest bool + Remove bool +} + +type SystemPruneValues struct { + PodmanCommand + All bool + Force bool + Volume bool +} + +type SystemRenumberValues struct { + PodmanCommand +} diff --git a/cmd/podman/cliconfig/create.go b/cmd/podman/cliconfig/create.go new file mode 100644 index 000000000..b5ca1be9c --- /dev/null +++ b/cmd/podman/cliconfig/create.go @@ -0,0 +1,26 @@ +package cliconfig + +import ( + buildahcli "github.com/containers/buildah/pkg/cli" +) + +type CreateValues struct { + PodmanCommand +} + +type RunValues struct { + PodmanCommand +} + +type BuildValues struct { + PodmanCommand + *buildahcli.BudResults + *buildahcli.UserNSResults + *buildahcli.FromAndBudResults + *buildahcli.NameSpaceResults + *buildahcli.LayerResults +} + +type CpValues struct { + PodmanCommand +} diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go index 57126da4a..fadcca689 100644 --- a/cmd/podman/commands.go +++ b/cmd/podman/commands.go @@ -2,151 +2,126 @@ package main -import "github.com/urfave/cli" +import ( + "github.com/spf13/cobra" +) -func getAppCommands() []cli.Command { - return []cli.Command{ - attachCommand, - commitCommand, - buildCommand, - createCommand, - diffCommand, - execCommand, - killCommand, - kubeCommand, - loadCommand, - loginCommand, - logoutCommand, - logsCommand, - mountCommand, - pauseCommand, - psCommand, - podCommand, - portCommand, - pushCommand, - playCommand, - restartCommand, - rmCommand, - runCommand, - saveCommand, - searchCommand, - startCommand, - statsCommand, - stopCommand, - topCommand, - umountCommand, - unpauseCommand, - volumeCommand, - waitCommand, +const remoteclient = false + +// Commands that the local client implements +func getMainCommands() []*cobra.Command { + rootCommands := []*cobra.Command{ + _attachCommand, + _commitCommand, + _createCommand, + _diffCommand, + _execCommand, + _generateCommand, + _playCommand, + _psCommand, + _loginCommand, + _logoutCommand, + _logsCommand, + _mountCommand, + _pauseCommand, + _portCommand, + _refreshCommand, + _restartCommand, + _restoreCommand, + _rmCommand, + _runCommand, + _searchCommand, + _signCommand, + _startCommand, + _statsCommand, + _stopCommand, + _topCommand, + _umountCommand, + _unpauseCommand, + _waitCommand, + } + + if len(_varlinkCommand.Use) > 0 { + rootCommands = append(rootCommands, _varlinkCommand) + } + return rootCommands +} + +// Commands that the local client implements +func getImageSubCommands() []*cobra.Command { + return []*cobra.Command{ + _loadCommand, + _signCommand, + } +} + +// Commands that the local client implements +func getContainerSubCommands() []*cobra.Command { + return []*cobra.Command{ + _attachCommand, + _checkpointCommand, + _cleanupCommand, + _commitCommand, + _createCommand, + _diffCommand, + _execCommand, + _exportCommand, + _killCommand, + _logsCommand, + _psCommand, + _mountCommand, + _pauseCommand, + _portCommand, + _pruneContainersCommand, + _refreshCommand, + _restartCommand, + _restoreCommand, + _rmCommand, + _runCommand, + _runlabelCommand, + _startCommand, + _statsCommand, + _stopCommand, + _topCommand, + _umountCommand, + _unpauseCommand, + _waitCommand, } } -func getImageSubCommands() []cli.Command { - return []cli.Command{ - buildCommand, - importCommand, - loadCommand, - pullCommand, - saveCommand, - trustCommand, - signCommand, +// Commands that the local client implements +func getPodSubCommands() []*cobra.Command { + return []*cobra.Command{ + _podStatsCommand, + _podTopCommand, } } -func getSystemSubCommands() []cli.Command { - return []cli.Command{infoCommand} +func getGenerateSubCommands() []*cobra.Command { + return []*cobra.Command{ + _containerKubeCommand, + } } -func getContainerSubCommands() []cli.Command { - return []cli.Command{ - attachCommand, - checkpointCommand, - cleanupCommand, - containerExistsCommand, - commitCommand, - createCommand, - diffCommand, - execCommand, - exportCommand, - killCommand, - logsCommand, - psCommand, - mountCommand, - pauseCommand, - portCommand, - pruneContainersCommand, - refreshCommand, - restartCommand, - restoreCommand, - rmCommand, - runCommand, - runlabelCommand, - startCommand, - statsCommand, - stopCommand, - topCommand, - umountCommand, - unpauseCommand, - // updateCommand, - waitCommand, +// Commands that the local client implements +func getPlaySubCommands() []*cobra.Command { + return []*cobra.Command{ + _playKubeCommand, } } -func getMainAppFlags() []cli.Flag { - return []cli.Flag{ - cli.StringFlag{ - Name: "cgroup-manager", - Usage: "Cgroup manager to use (cgroupfs or systemd, default systemd)", - }, - cli.StringFlag{ - Name: "cni-config-dir", - Usage: "Path of the configuration directory for CNI networks", - }, - cli.StringFlag{ - Name: "conmon", - Usage: "Path of the conmon binary", - }, - cli.StringFlag{ - Name: "default-mounts-file", - Usage: "Path to default mounts file", - Hidden: true, - }, - cli.StringSliceFlag{ - Name: "hooks-dir", - Usage: "Set the OCI hooks directory path (may be set multiple times)", - }, - cli.IntFlag{ - Name: "max-workers", - Usage: "The maximum number of workers for parallel operations", - Hidden: true, - }, - cli.StringFlag{ - Name: "namespace", - Usage: "Set the libpod namespace, used to create separate views of the containers and pods on the system", - Value: "", - }, - cli.StringFlag{ - Name: "root", - Usage: "Path to the root directory in which data, including images, is stored", - }, - cli.StringFlag{ - Name: "runroot", - Usage: "Path to the 'run directory' where all state information is stored", - }, - cli.StringFlag{ - Name: "runtime", - Usage: "Path to the OCI-compatible binary used to run containers, default is /usr/bin/runc", - }, - cli.StringFlag{ - Name: "storage-driver, s", - Usage: "Select which storage driver is used to manage storage of images and containers (default is overlay)", - }, - cli.StringSliceFlag{ - Name: "storage-opt", - Usage: "Used to pass an option to the storage driver", - }, - cli.BoolFlag{ - Name: "syslog", - Usage: "Output logging information to syslog as well as the console", - }, + +// Commands that the local client implements +func getTrustSubCommands() []*cobra.Command { + return []*cobra.Command{ + _setTrustCommand, + _showTrustCommand, + } +} + +// Commands that the local client implements +func getSystemSubCommands() []*cobra.Command { + return []*cobra.Command{ + _pruneSystemCommand, + _renumberCommand, } } diff --git a/cmd/podman/commands_remoteclient.go b/cmd/podman/commands_remoteclient.go index 0adbd7b4c..081043b25 100644 --- a/cmd/podman/commands_remoteclient.go +++ b/cmd/podman/commands_remoteclient.go @@ -2,24 +2,53 @@ package main -import "github.com/urfave/cli" +import ( + "github.com/spf13/cobra" +) -func getAppCommands() []cli.Command { - return []cli.Command{} +const remoteclient = true + +// commands that only the remoteclient implements +func getMainCommands() []*cobra.Command { + return []*cobra.Command{} +} + +// commands that only the remoteclient implements +func getAppCommands() []*cobra.Command { + return []*cobra.Command{} +} + +// commands that only the remoteclient implements +func getImageSubCommands() []*cobra.Command { + return []*cobra.Command{} +} + +// commands that only the remoteclient implements +func getContainerSubCommands() []*cobra.Command { + return []*cobra.Command{} +} + +// commands that only the remoteclient implements +func getPodSubCommands() []*cobra.Command { + return []*cobra.Command{} } -func getImageSubCommands() []cli.Command { - return []cli.Command{} +// commands that only the remoteclient implements +func getGenerateSubCommands() []*cobra.Command { + return []*cobra.Command{} } -func getContainerSubCommands() []cli.Command { - return []cli.Command{} +// commands that only the remoteclient implements +func getPlaySubCommands() []*cobra.Command { + return []*cobra.Command{} } -func getSystemSubCommands() []cli.Command { - return []cli.Command{} +// commands that only the remoteclient implements +func getTrustSubCommands() []*cobra.Command { + return []*cobra.Command{} } -func getMainAppFlags() []cli.Flag { - return []cli.Flag{} +// commands that only the remoteclient implements +func getSystemSubCommands() []*cobra.Command { + return []*cobra.Command{} } diff --git a/cmd/podman/commit.go b/cmd/podman/commit.go index 02ede4f73..d8ced0e36 100644 --- a/cmd/podman/commit.go +++ b/cmd/podman/commit.go @@ -2,6 +2,8 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" "io" "os" "strings" @@ -13,57 +15,45 @@ import ( "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" - "github.com/urfave/cli" ) var ( - commitFlags = []cli.Flag{ - cli.StringSliceFlag{ - Name: "change, c", - Usage: fmt.Sprintf("Apply the following possible instructions to the created image (default []): %s", strings.Join(libpod.ChangeCmds, " | ")), - }, - cli.StringFlag{ - Name: "format, f", - Usage: "`format` of the image manifest and metadata", - Value: "oci", - }, - cli.StringFlag{ - Name: "message, m", - Usage: "Set commit message for imported image", - }, - cli.StringFlag{ - Name: "author, a", - Usage: "Set the author for the image committed", - }, - cli.BoolTFlag{ - Name: "pause, p", - Usage: "Pause container during commit", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Suppress output", - }, - } + commitCommand cliconfig.CommitValues commitDescription = `Create an image from a container's changes. Optionally tag the image created, set the author with the --author flag, set the commit message with the --message flag, and make changes to the instructions with the --change flag.` - commitCommand = cli.Command{ - Name: "commit", - Usage: "Create new image based on the changed container", - Description: commitDescription, - Flags: sortFlags(commitFlags), - Action: commitCmd, - ArgsUsage: "CONTAINER [REPOSITORY[:TAG]]", - OnUsageError: usageErrorHandler, + + _commitCommand = &cobra.Command{ + Use: "commit", + Short: "Create new image based on the changed container", + Long: commitDescription, + RunE: func(cmd *cobra.Command, args []string) error { + commitCommand.InputArgs = args + commitCommand.GlobalFlags = MainGlobalOpts + return commitCmd(&commitCommand) + }, + Example: `podman commit -q --message "committing container to image" reverent_golick image-commited + podman commit -q --author "firstName lastName" reverent_golick image-commited + podman commit -q --pause=false containerID image-commited`, } ) -func commitCmd(c *cli.Context) error { - if err := validateFlags(c, commitFlags); err != nil { - return err - } - runtime, err := libpodruntime.GetRuntime(c) +func init() { + commitCommand.Command = _commitCommand + commitCommand.SetUsageTemplate(UsageTemplate()) + flags := commitCommand.Flags() + flags.StringSliceVarP(&commitCommand.Change, "change", "c", []string{}, fmt.Sprintf("Apply the following possible instructions to the created image (default []): %s", strings.Join(libpod.ChangeCmds, " | "))) + flags.StringVarP(&commitCommand.Format, "format", "f", "oci", "`Format` of the image manifest and metadata") + flags.StringVarP(&commitCommand.Message, "message", "m", "", "Set commit message for imported image") + flags.StringVarP(&commitCommand.Author, "author", "a", "", "Set the author for the image committed") + flags.BoolVarP(&commitCommand.Pause, "pause", "p", false, "Pause container during commit") + flags.BoolVarP(&commitCommand.Quiet, "quiet", "q", false, "Suppress output") + +} + +func commitCmd(c *cliconfig.CommitValues) error { + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -73,26 +63,26 @@ func commitCmd(c *cli.Context) error { writer io.Writer mimeType string ) - args := c.Args() + args := c.InputArgs if len(args) != 2 { return errors.Errorf("you must provide a container name or ID and a target image name") } - switch c.String("format") { + switch c.Format { case "oci": mimeType = buildah.OCIv1ImageManifest - if c.IsSet("message") || c.IsSet("m") { + if c.Flag("message").Changed { return errors.Errorf("messages are only compatible with the docker image format (-f docker)") } case "docker": mimeType = manifest.DockerV2Schema2MediaType default: - return errors.Errorf("unrecognized image format %q", c.String("format")) + return errors.Errorf("unrecognized image format %q", c.Format) } container := args[0] reference := args[1] - if c.IsSet("change") || c.IsSet("c") { - for _, change := range c.StringSlice("change") { + if c.Flag("change").Changed { + for _, change := range c.Change { splitChange := strings.Split(strings.ToUpper(change), "=") if !util.StringInSlice(splitChange[0], libpod.ChangeCmds) { return errors.Errorf("invalid syntax for --change: %s", change) @@ -100,7 +90,7 @@ func commitCmd(c *cli.Context) error { } } - if !c.Bool("quiet") { + if !c.Quiet { writer = os.Stderr } ctr, err := runtime.LookupContainer(container) @@ -117,10 +107,10 @@ func commitCmd(c *cli.Context) error { } options := libpod.ContainerCommitOptions{ CommitOptions: coptions, - Pause: c.Bool("pause"), - Message: c.String("message"), - Changes: c.StringSlice("change"), - Author: c.String("author"), + Pause: c.Pause, + Message: c.Message, + Changes: c.Change, + Author: c.Author, } newImage, err := ctr.Commit(getContext(), reference, options) if err != nil { diff --git a/cmd/podman/common.go b/cmd/podman/common.go index 82d60d62e..e297f3921 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -3,35 +3,21 @@ package main import ( "context" "fmt" + "github.com/spf13/cobra" "os" - "reflect" - "regexp" - "sort" "strings" "github.com/containers/buildah" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/fatih/camelcase" "github.com/pkg/errors" - "github.com/urfave/cli" ) var ( - stores = make(map[storage.Store]struct{}) - LatestFlag = cli.BoolFlag{ - Name: "latest, l", - Usage: "Act on the latest container podman is aware of", - } - LatestPodFlag = cli.BoolFlag{ - Name: "latest, l", - Usage: "Act on the latest pod podman is aware of", - } - WorkDirFlag = cli.StringFlag{ - Name: "workdir, w", - Usage: "Working directory inside the container", - } + stores = make(map[storage.Store]struct{}) ) const ( @@ -50,61 +36,25 @@ func shortID(id string) string { return id } -func usageErrorHandler(context *cli.Context, err error, _ bool) error { - cmd := context.App.Name - if len(context.Command.Name) > 0 { - cmd = cmd + " " + context.Command.Name - } - return fmt.Errorf("%s\nSee '%s --help'.", err, cmd) -} - -func commandNotFoundHandler(context *cli.Context, command string) { - fmt.Fprintf(os.Stderr, "Command %q not found.\nSee `%s --help`.\n", command, context.App.Name) - os.Exit(exitCode) -} - -// validateFlags searches for StringFlags or StringSlice flags that never had -// a value set. This commonly occurs when the CLI mistakenly takes the next -// option and uses it as a value. -func validateFlags(c *cli.Context, flags []cli.Flag) error { - for _, flag := range flags { - switch reflect.TypeOf(flag).String() { - case "cli.StringSliceFlag": - { - f := flag.(cli.StringSliceFlag) - name := strings.Split(f.Name, ",") - val := c.StringSlice(name[0]) - for _, v := range val { - if ok, _ := regexp.MatchString("^-.+", v); ok { - return errors.Errorf("option --%s requires a value", name[0]) - } - } - } - case "cli.StringFlag": - { - f := flag.(cli.StringFlag) - name := strings.Split(f.Name, ",") - val := c.String(name[0]) - if ok, _ := regexp.MatchString("^-.+", val); ok { - return errors.Errorf("option --%s requires a value", name[0]) - } - } - } - } - return nil -} - // checkAllAndLatest checks that --all and --latest are used correctly -func checkAllAndLatest(c *cli.Context) error { - argLen := len(c.Args()) - if (c.Bool("all") || c.Bool("latest")) && argLen > 0 { - return errors.Errorf("no arguments are needed with --all or --latest") +func checkAllAndLatest(c *cobra.Command, args []string, ignoreArgLen bool) error { + argLen := len(args) + if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { + return errors.New("unable to lookup values for 'latest' or 'all'") } - if c.Bool("all") && c.Bool("latest") { + all, _ := c.Flags().GetBool("all") + latest, _ := c.Flags().GetBool("latest") + if all && latest { return errors.Errorf("--all and --latest cannot be used together") } - if argLen < 1 && !c.Bool("all") && !c.Bool("latest") { - return errors.Errorf("you must provide at least one pod name or id") + if ignoreArgLen { + return nil + } + if (all || latest) && argLen > 0 { + return errors.Errorf("no arguments are needed with --all or --latest") + } + if argLen < 1 && !all && !latest { + return errors.Errorf("you must provide at least one name or id") } return nil } @@ -117,7 +67,7 @@ func checkAllAndLatest(c *cli.Context) error { // is desired a -1 can be used to get all containers. For a better // error message, if the filter fails, a corresponding verb can be // specified which will then appear in the error message. -func getAllOrLatestContainers(c *cli.Context, runtime *libpod.Runtime, filterState libpod.ContainerStatus, verb string) ([]*libpod.Container, error) { +func getAllOrLatestContainers(c *cliconfig.PodmanCommand, runtime *libpod.Runtime, filterState libpod.ContainerStatus, verb string) ([]*libpod.Container, error) { var containers []*libpod.Container var lastError error var err error @@ -142,7 +92,7 @@ func getAllOrLatestContainers(c *cli.Context, runtime *libpod.Runtime, filterSta } containers = append(containers, lastCtr) } else { - args := c.Args() + args := c.InputArgs for _, i := range args { container, err := runtime.LookupContainer(i) if err != nil { @@ -163,6 +113,9 @@ func getAllOrLatestContainers(c *cli.Context, runtime *libpod.Runtime, filterSta // getContext returns a non-nil, empty context func getContext() context.Context { + if Ctx != nil { + return Ctx + } return context.TODO() } @@ -173,363 +126,367 @@ func getDefaultNetwork() string { return "bridge" } -// Common flags shared between commands -var createFlags = []cli.Flag{ - cli.StringSliceFlag{ - Name: "add-host", - Usage: "Add a custom host-to-IP mapping (host:ip) (default [])", - }, - cli.StringSliceFlag{ - Name: "annotation", - Usage: "Add annotations to container (key:value) (default [])", - }, - cli.StringSliceFlag{ - Name: "attach, a", - Usage: "Attach to STDIN, STDOUT or STDERR (default [])", - }, - cli.StringFlag{ - Name: "blkio-weight", - Usage: "Block IO weight (relative weight) accepts a weight value between 10 and 1000.", - }, - cli.StringSliceFlag{ - Name: "blkio-weight-device", - Usage: "Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`)", - }, - cli.StringSliceFlag{ - Name: "cap-add", - Usage: "Add capabilities to the container", - }, - cli.StringSliceFlag{ - Name: "cap-drop", - Usage: "Drop capabilities from the container", - }, - cli.StringFlag{ - Name: "cgroup-parent", - Usage: "Optional parent cgroup for the container", - }, - cli.StringFlag{ - Name: "cidfile", - Usage: "Write the container ID to the file", - }, - cli.StringFlag{ - Name: "conmon-pidfile", - Usage: "Path to the file that will receive the PID of conmon", - }, - cli.Uint64Flag{ - Name: "cpu-period", - Usage: "Limit the CPU CFS (Completely Fair Scheduler) period", - }, - cli.Int64Flag{ - Name: "cpu-quota", - Usage: "Limit the CPU CFS (Completely Fair Scheduler) quota", - }, - cli.Uint64Flag{ - Name: "cpu-rt-period", - Usage: "Limit the CPU real-time period in microseconds", - }, - cli.Int64Flag{ - Name: "cpu-rt-runtime", - Usage: "Limit the CPU real-time runtime in microseconds", - }, - cli.Uint64Flag{ - Name: "cpu-shares", - Usage: "CPU shares (relative weight)", - }, - cli.Float64Flag{ - Name: "cpus", - Usage: "Number of CPUs. The default is 0.000 which means no limit", - }, - cli.StringFlag{ - Name: "cpuset-cpus", - Usage: "CPUs in which to allow execution (0-3, 0,1)", - }, - cli.StringFlag{ - Name: "cpuset-mems", - Usage: "Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.", - }, - cli.BoolFlag{ - Name: "detach, d", - Usage: "Run container in background and print container ID", - }, - cli.StringFlag{ - Name: "detach-keys", - Usage: "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`", - }, - cli.StringSliceFlag{ - Name: "device", - Usage: "Add a host device to the container (default [])", - }, - cli.StringSliceFlag{ - Name: "device-read-bps", - Usage: "Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)", - }, - cli.StringSliceFlag{ - Name: "device-read-iops", - Usage: "Limit read rate (IO per second) from a device (e.g. --device-read-iops=/dev/sda:1000)", - }, - cli.StringSliceFlag{ - Name: "device-write-bps", - Usage: "Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)", - }, - cli.StringSliceFlag{ - Name: "device-write-iops", - Usage: "Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)", - }, - cli.StringSliceFlag{ - Name: "dns", - Usage: "Set custom DNS servers", - }, - cli.StringSliceFlag{ - Name: "dns-opt", - Usage: "Set custom DNS options", - }, - cli.StringSliceFlag{ - Name: "dns-search", - Usage: "Set custom DNS search domains", - }, - cli.StringFlag{ - Name: "entrypoint", - Usage: "Overwrite the default ENTRYPOINT of the image", - }, - cli.StringSliceFlag{ - Name: "env, e", - Usage: "Set environment variables in container", - }, - cli.StringSliceFlag{ - Name: "env-file", - Usage: "Read in a file of environment variables", - }, - cli.StringSliceFlag{ - Name: "expose", - Usage: "Expose a port or a range of ports (default [])", - }, - cli.StringSliceFlag{ - Name: "gidmap", - Usage: "GID map to use for the user namespace", - }, - cli.StringSliceFlag{ - Name: "group-add", - Usage: "Add additional groups to join (default [])", - }, - cli.BoolFlag{ - Name: "help", - Hidden: true, - }, - cli.StringFlag{ - Name: "hostname, h", - Usage: "Set container hostname", - }, - cli.StringFlag{ - Name: "image-volume, builtin-volume", - Usage: "Tells podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore' (default 'bind')", - Value: "bind", - }, - cli.BoolFlag{ - Name: "init", - Usage: "Run an init binary inside the container that forwards signals and reaps processes", - }, - cli.StringFlag{ - Name: "init-path", +func getCreateFlags(c *cliconfig.PodmanCommand) { + + createFlags := c.Flags() + + createFlags.StringSlice( + "add-host", []string{}, + "Add a custom host-to-IP mapping (host:ip) (default [])", + ) + createFlags.StringSlice( + "annotation", []string{}, + "Add annotations to container (key:value) (default [])", + ) + createFlags.StringSliceP( + "attach", "a", []string{}, + "Attach to STDIN, STDOUT or STDERR (default [])", + ) + createFlags.String( + "blkio-weight", "", + "Block IO weight (relative weight) accepts a weight value between 10 and 1000.", + ) + createFlags.StringSlice( + "blkio-weight-device", []string{}, + "Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`)", + ) + createFlags.StringSlice( + "cap-add", []string{}, + "Add capabilities to the container", + ) + createFlags.StringSlice( + "cap-drop", []string{}, + "Drop capabilities from the container", + ) + createFlags.String( + "cgroup-parent", "", + "Optional parent cgroup for the container", + ) + createFlags.String( + "cidfile", "", + "Write the container ID to the file", + ) + createFlags.String( + "conmon-pidfile", "", + "Path to the file that will receive the PID of conmon", + ) + createFlags.Uint64( + "cpu-period", 0, + "Limit the CPU CFS (Completely Fair Scheduler) period", + ) + createFlags.Int64( + "cpu-quota", 0, + "Limit the CPU CFS (Completely Fair Scheduler) quota", + ) + createFlags.Uint64( + "cpu-rt-period", 0, + "Limit the CPU real-time period in microseconds", + ) + createFlags.Int64( + "cpu-rt-runtime", 0, + "Limit the CPU real-time runtime in microseconds", + ) + createFlags.Uint64( + "cpu-shares", 0, + "CPU shares (relative weight)", + ) + createFlags.Float64( + "cpus", 0, + "Number of CPUs. The default is 0.000 which means no limit", + ) + createFlags.String( + "cpuset-cpus", "", + "CPUs in which to allow execution (0-3, 0,1)", + ) + createFlags.String( + "cpuset-mems", "", + "Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.", + ) + createFlags.BoolP( + "detach", "d", false, + "Run container in background and print container ID", + ) + createFlags.String( + "detach-keys", "", + "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`", + ) + createFlags.StringSlice( + "device", []string{}, + "Add a host device to the container (default [])", + ) + createFlags.StringSlice( + "device-read-bps", []string{}, + "Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)", + ) + createFlags.StringSlice( + "device-read-iops", []string{}, + "Limit read rate (IO per second) from a device (e.g. --device-read-iops=/dev/sda:1000)", + ) + createFlags.StringSlice( + "device-write-bps", []string{}, + "Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)", + ) + createFlags.StringSlice( + "device-write-iops", []string{}, + "Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)", + ) + createFlags.StringSlice( + "dns", []string{}, + "Set custom DNS servers", + ) + createFlags.StringSlice( + "dns-opt", []string{}, + "Set custom DNS options", + ) + createFlags.StringSlice( + "dns-search", []string{}, + "Set custom DNS search domains", + ) + createFlags.String( + "entrypoint", "", + "Overwrite the default ENTRYPOINT of the image", + ) + createFlags.StringSliceP( + "env", "e", []string{}, + "Set environment variables in container", + ) + createFlags.StringSlice( + "env-file", []string{}, + "Read in a file of environment variables", + ) + createFlags.StringSlice( + "expose", []string{}, + "Expose a port or a range of ports (default [])", + ) + createFlags.StringSlice( + "gidmap", []string{}, + "GID map to use for the user namespace", + ) + createFlags.StringSlice( + "group-add", []string{}, + "Add additional groups to join (default [])", + ) + createFlags.Bool( + "help", false, "", + ) + + createFlags.StringP( + "hostname", "h", "", + "Set container hostname", + ) + createFlags.String( + "image-volume", "bind", + "Tells podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore' (default 'bind')", + ) + createFlags.Bool( + "init", false, + "Run an init binary inside the container that forwards signals and reaps processes", + ) + createFlags.String( + "init-path", "", // Do not use the Value field for setting the default value to determine user input (i.e., non-empty string) - Usage: fmt.Sprintf("Path to the container-init binary (default: %q)", libpod.DefaultInitPath), - }, - cli.BoolFlag{ - Name: "interactive, i", - Usage: "Keep STDIN open even if not attached", - }, - cli.StringFlag{ - Name: "ip", - Usage: "Specify a static IPv4 address for the container", - }, - cli.StringFlag{ - Name: "ipc", - Usage: "IPC namespace to use", - }, - cli.StringFlag{ - Name: "kernel-memory", - Usage: "Kernel memory limit (format: `<number>[<unit>]`, where unit = b, k, m or g)", - }, - cli.StringSliceFlag{ - Name: "label", - Usage: "Set metadata on container (default [])", - }, - cli.StringSliceFlag{ - Name: "label-file", - Usage: "Read in a line delimited file of labels (default [])", - }, - cli.StringFlag{ - Name: "log-driver", - Usage: "Logging driver for the container", - }, - cli.StringSliceFlag{ - Name: "log-opt", - Usage: "Logging driver options (default [])", - }, - cli.StringFlag{ - Name: "mac-address", - Usage: "Container MAC address (e.g. 92:d0:c6:0a:29:33), not currently supported", - }, - cli.StringFlag{ - Name: "memory, m", - Usage: "Memory limit (format: <number>[<unit>], where unit = b, k, m or g)", - }, - cli.StringFlag{ - Name: "memory-reservation", - Usage: "Memory soft limit (format: <number>[<unit>], where unit = b, k, m or g)", - }, - cli.StringFlag{ - Name: "memory-swap", - Usage: "Swap limit equal to memory plus swap: '-1' to enable unlimited swap", - }, - cli.Int64Flag{ - Name: "memory-swappiness", - Usage: "Tune container memory swappiness (0 to 100) (default -1)", - Value: -1, - }, - cli.StringFlag{ - Name: "name", - Usage: "Assign a name to the container", - }, - cli.StringFlag{ - Name: "net, network", - Usage: "Connect a container to a network", - Value: getDefaultNetwork(), - }, - cli.BoolFlag{ - Name: "oom-kill-disable", - Usage: "Disable OOM Killer", - }, - cli.StringFlag{ - Name: "oom-score-adj", - Usage: "Tune the host's OOM preferences (-1000 to 1000)", - }, - cli.StringFlag{ - Name: "pid", - Usage: "PID namespace to use", - }, - cli.Int64Flag{ - Name: "pids-limit", - Usage: "Tune container pids limit (set -1 for unlimited)", - }, - cli.StringFlag{ - Name: "pod", - Usage: "Run container in an existing pod", - }, - cli.BoolFlag{ - Name: "privileged", - Usage: "Give extended privileges to container", - }, - cli.StringSliceFlag{ - Name: "publish, p", - Usage: "Publish a container's port, or a range of ports, to the host (default [])", - }, - cli.BoolFlag{ - Name: "publish-all, P", - Usage: "Publish all exposed ports to random ports on the host interface", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Suppress output information when pulling images", - }, - cli.BoolFlag{ - Name: "read-only", - Usage: "Make containers root filesystem read-only", - }, - cli.StringFlag{ - Name: "restart", - Usage: "Restart is not supported. Please use a systemd unit file for restart", - }, - cli.BoolFlag{ - Name: "rm", - Usage: "Remove container (and pod if created) after exit", - }, - cli.BoolFlag{ - Name: "rootfs", - Usage: "The first argument is not an image but the rootfs to the exploded container", - }, - cli.StringSliceFlag{ - Name: "security-opt", - Usage: "Security Options (default [])", - }, - cli.StringFlag{ - Name: "shm-size", - Usage: "Size of `/dev/shm`. The format is `<number><unit>`.", - Value: "65536k", - }, - cli.StringFlag{ - Name: "stop-signal", - Usage: "Signal to stop a container. Default is SIGTERM", - }, - cli.IntFlag{ - Name: "stop-timeout", - Usage: "Timeout (in seconds) to stop a container. Default is 10", - Value: libpod.CtrRemoveTimeout, - }, - cli.StringSliceFlag{ - Name: "storage-opt", - Usage: "Storage driver options per container (default [])", - }, - cli.StringFlag{ - Name: "subgidname", - Usage: "Name of range listed in /etc/subgid for use in user namespace", - }, - cli.StringFlag{ - Name: "subuidname", - Usage: "Name of range listed in /etc/subuid for use in user namespace", - }, + fmt.Sprintf("Path to the container-init binary (default: %q)", libpod.DefaultInitPath), + ) + createFlags.BoolP( + "interactive", "i", false, + "Keep STDIN open even if not attached", + ) + createFlags.String( + "ip", "", + "Specify a static IPv4 address for the container", + ) + createFlags.String( + "ipc", "", + "IPC namespace to use", + ) + createFlags.String( + "kernel-memory", "", + "Kernel memory limit (format: `<number>[<unit>]`, where unit = b, k, m or g)", + ) + createFlags.StringSlice( + "label", []string{}, + "Set metadata on container (default [])", + ) + createFlags.StringSlice( + "label-file", []string{}, + "Read in a line delimited file of labels (default [])", + ) + createFlags.String( + "log-driver", "", + "Logging driver for the container", + ) + createFlags.StringSlice( + "log-opt", []string{}, + "Logging driver options (default [])", + ) + createFlags.String( + "mac-address", "", + "Container MAC address (e.g. 92:d0:c6:0a:29:33), not currently supported", + ) + createFlags.StringP( + "memory", "m", "", + "Memory limit (format: <number>[<unit>], where unit = b, k, m or g)", + ) + createFlags.String( + "memory-reservation", "", + "Memory soft limit (format: <number>[<unit>], where unit = b, k, m or g)", + ) + createFlags.String( + "memory-swap", "", + "Swap limit equal to memory plus swap: '-1' to enable unlimited swap", + ) + createFlags.Int64( + "memory-swappiness", -1, + "Tune container memory swappiness (0 to 100) (default -1)", + ) + createFlags.String( + "name", "", + "Assign a name to the container", + ) + createFlags.String( + "net", getDefaultNetwork(), + "Connect a container to a network", + ) + createFlags.String( + "network", getDefaultNetwork(), + "Connect a container to a network", + ) + createFlags.Bool( + "oom-kill-disable", false, + "Disable OOM Killer", + ) + createFlags.Int( + "oom-score-adj", 0, + "Tune the host's OOM preferences (-1000 to 1000)", + ) + createFlags.String( + "pid", "", + "PID namespace to use", + ) + createFlags.Int64( + "pids-limit", 0, + "Tune container pids limit (set -1 for unlimited)", + ) + createFlags.String( + "pod", "", + "Run container in an existing pod", + ) + createFlags.Bool( + "privileged", false, + "Give extended privileges to container", + ) + createFlags.StringSliceP( + "publish", "p", []string{}, + "Publish a container's port, or a range of ports, to the host (default [])", + ) + createFlags.BoolP( + "publish-all", "P", false, + "Publish all exposed ports to random ports on the host interface", + ) + createFlags.BoolP( + "quiet", "q", false, + "Suppress output information when pulling images", + ) + createFlags.Bool( + "read-only", false, + "Make containers root filesystem read-only", + ) + createFlags.String( + "restart", "", + "Restart is not supported. Please use a systemd unit file for restart", + ) + createFlags.Bool( + "rm", false, + "Remove container (and pod if created) after exit", + ) + createFlags.Bool( + "rootfs", false, + "The first argument is not an image but the rootfs to the exploded container", + ) + createFlags.StringArray( + "security-opt", []string{}, + "Security Options (default [])", + ) + createFlags.String( + "shm-size", "65536k", + "Size of `/dev/shm`. The format is `<number><unit>`", + ) + createFlags.String( + "stop-signal", "", + "Signal to stop a container. Default is SIGTERM", + ) + createFlags.Int( + "stop-timeout", libpod.CtrRemoveTimeout, + "Timeout (in seconds) to stop a container. Default is 10", + ) + createFlags.StringSlice( + "storage-opt", []string{}, + "Storage driver options per container (default [])", + ) + createFlags.String( + "subgidname", "", + "Name of range listed in /etc/subgid for use in user namespace", + ) + createFlags.String( + "subuidname", "", + "Name of range listed in /etc/subuid for use in user namespace", + ) - cli.StringSliceFlag{ - Name: "sysctl", - Usage: "Sysctl options (default [])", - }, - cli.BoolTFlag{ - Name: "systemd", - Usage: "Run container in systemd mode if the command executable is systemd or init", - }, - cli.StringSliceFlag{ - Name: "tmpfs", - Usage: "Mount a temporary filesystem (`tmpfs`) into a container (default [])", - }, - cli.BoolFlag{ - Name: "tty, t", - Usage: "Allocate a pseudo-TTY for container", - }, - cli.StringSliceFlag{ - Name: "uidmap", - Usage: "UID map to use for the user namespace", - }, - cli.StringSliceFlag{ - Name: "ulimit", - Usage: "Ulimit options (default [])", - }, - cli.StringFlag{ - Name: "user, u", - Usage: "Username or UID (format: <name|uid>[:<group|gid>])", - }, - cli.StringFlag{ - Name: "userns", - Usage: "User namespace to use", - }, - cli.StringFlag{ - Name: "uts", - Usage: "UTS namespace to use", - }, - cli.StringSliceFlag{ - Name: "mount", - Usage: "Attach a filesystem mount to the container (default [])", - }, - cli.StringSliceFlag{ - Name: "volume, v", - Usage: "Bind mount a volume into the container (default [])", - }, - cli.StringSliceFlag{ - Name: "volumes-from", - Usage: "Mount volumes from the specified container(s) (default [])", - }, - WorkDirFlag, + createFlags.StringSlice( + "sysctl", []string{}, + "Sysctl options (default [])", + ) + createFlags.Bool( + "systemd", true, + "Run container in systemd mode if the command executable is systemd or init", + ) + createFlags.StringSlice( + "tmpfs", []string{}, + "Mount a temporary filesystem (`tmpfs`) into a container (default [])", + ) + createFlags.BoolP( + "tty", "t", false, + "Allocate a pseudo-TTY for container", + ) + createFlags.StringSlice( + "uidmap", []string{}, + "UID map to use for the user namespace", + ) + createFlags.StringSlice( + "ulimit", []string{}, + "Ulimit options (default [])", + ) + createFlags.StringP( + "user", "u", "", + "Username or UID (format: <name|uid>[:<group|gid>])", + ) + createFlags.String( + "userns", "", + "User namespace to use", + ) + createFlags.String( + "uts", "", + "UTS namespace to use", + ) + createFlags.StringArray( + "mount", []string{}, + "Attach a filesystem mount to the container (default [])", + ) + createFlags.StringArrayP( + "volume", "v", []string{}, + "Bind mount a volume into the container (default [])", + ) + createFlags.StringSlice( + "volumes-from", []string{}, + "Mount volumes from the specified container(s) (default [])", + ) + createFlags.StringP( + "workdir", "w", "", + "Working directory inside the container", + ) } -func getFormat(c *cli.Context) (string, error) { +func getFormat(c *cliconfig.PodmanCommand) (string, error) { format := strings.ToLower(c.String("format")) if strings.HasPrefix(format, buildah.OCI) { return buildah.OCIv1ImageManifest, nil @@ -541,13 +498,6 @@ func getFormat(c *cli.Context) (string, error) { return "", errors.Errorf("unrecognized image type %q", format) } -func sortFlags(flags []cli.Flag) []cli.Flag { - sort.Slice(flags, func(i, j int) bool { - return strings.Compare(flags[i].GetName(), flags[j].GetName()) < 0 - }) - return flags -} - func getAuthFile(authfile string) string { if authfile != "" { return authfile @@ -562,3 +512,27 @@ func scrubServer(server string) string { server = strings.TrimPrefix(server, "https://") return strings.TrimPrefix(server, "http://") } + +// UsageTemplate returns the usage template for podman commands +// This blocks the desplaying of the global options. The main podman +// command should not use this. +func UsageTemplate() string { + return `Usage:{{if .Runnable}} + {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} + + {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} + +Aliases: + {{.NameAndAliases}}{{end}}{{if .HasExample}} + +Examples: + {{.Example}}{{end}}{{if .HasAvailableSubCommands}} + +Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} + +Flags: +{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} +{{end}} +` +} diff --git a/cmd/podman/common_test.go b/cmd/podman/common_test.go index 042568d7e..a24173003 100644 --- a/cmd/podman/common_test.go +++ b/cmd/podman/common_test.go @@ -3,34 +3,8 @@ package main import ( "os/user" "testing" - - "flag" - - "github.com/urfave/cli" ) -func TestGetStore(t *testing.T) { - t.Skip("FIX THIS!") - - //cmd/podman/common_test.go:27: cannot use c (type *cli.Context) as type *libkpod.Config in argument to getStore - - // Make sure the tests are running as root - skipTestIfNotRoot(t) - - set := flag.NewFlagSet("test", 0) - globalSet := flag.NewFlagSet("test", 0) - globalSet.String("root", "", "path to the root directory in which data, including images, is stored") - globalCtx := cli.NewContext(nil, globalSet, nil) - command := cli.Command{Name: "imagesCommand"} - c := cli.NewContext(nil, set, globalCtx) - c.Command = command - - //_, err := getStore(c) - //if err != nil { - //t.Error(err) - //} -} - func skipTestIfNotRoot(t *testing.T) { u, err := user.Current() if err != nil { diff --git a/cmd/podman/container.go b/cmd/podman/container.go index 29300a6a4..d2450fdd3 100644 --- a/cmd/podman/container.go +++ b/cmd/podman/container.go @@ -1,30 +1,28 @@ package main import ( - "sort" - - "github.com/urfave/cli" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" ) -var ( - containerSubCommands = []cli.Command{ - exportCommand, - inspectCommand, - } - containerDescription = "Manage containers" - containerCommand = cli.Command{ - Name: "container", - Usage: "Manage Containers", - Description: containerDescription, - ArgsUsage: "", - Subcommands: getContainerSubCommandsSorted(), - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, - } -) +var containerDescription = "Manage containers" +var containerCommand = cliconfig.PodmanCommand{ + Command: &cobra.Command{ + Use: "container", + Short: "Manage Containers", + Long: containerDescription, + TraverseChildren: true, + }, +} + +// Commands that are universally implemented. +var containerCommands = []*cobra.Command{ + _containerExistsCommand, +} -func getContainerSubCommandsSorted() []cli.Command { - containerSubCommands = append(containerSubCommands, getContainerSubCommands()...) - sort.Sort(commandSortedAlpha{containerSubCommands}) - return containerSubCommands +func init() { + containerCommand.AddCommand(containerCommands...) + containerCommand.AddCommand(getContainerSubCommands()...) + containerCommand.SetUsageTemplate(UsageTemplate()) + rootCmd.AddCommand(containerCommand.Command) } diff --git a/cmd/podman/containers_prune.go b/cmd/podman/containers_prune.go index 09141e9a3..6e4960429 100644 --- a/cmd/podman/containers_prune.go +++ b/cmd/podman/containers_prune.go @@ -3,31 +3,42 @@ package main import ( "context" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/adapter" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + pruneContainersCommand cliconfig.PruneContainersValues pruneContainersDescription = ` podman container prune Removes all exited containers ` - - pruneContainersCommand = cli.Command{ - Name: "prune", - Usage: "Remove all stopped containers", - Description: pruneContainersDescription, - Action: pruneContainersCmd, - OnUsageError: usageErrorHandler, + _pruneContainersCommand = &cobra.Command{ + Use: "prune", + Short: "Remove all stopped containers", + Long: pruneContainersDescription, + RunE: func(cmd *cobra.Command, args []string) error { + pruneContainersCommand.InputArgs = args + pruneContainersCommand.GlobalFlags = MainGlobalOpts + return pruneContainersCmd(&pruneContainersCommand) + }, } ) -func pruneContainers(runtime *adapter.LocalRuntime, ctx context.Context, maxWorkers int, force bool) error { +func init() { + pruneContainersCommand.Command = _pruneContainersCommand + pruneContainersCommand.SetUsageTemplate(UsageTemplate()) + flags := pruneContainersCommand.Flags() + flags.BoolVarP(&pruneContainersCommand.Force, "force", "f", false, "Force removal of a running container. The default is false") +} + +func pruneContainers(runtime *adapter.LocalRuntime, ctx context.Context, maxWorkers int, force, volumes bool) error { var deleteFuncs []shared.ParallelWorkerInput filter := func(c *libpod.Container) bool { @@ -47,7 +58,7 @@ func pruneContainers(runtime *adapter.LocalRuntime, ctx context.Context, maxWork for _, container := range delContainers { con := container f := func() error { - return runtime.RemoveContainer(ctx, con, force) + return runtime.RemoveContainer(ctx, con, force, volumes) } deleteFuncs = append(deleteFuncs, shared.ParallelWorkerInput{ @@ -60,8 +71,8 @@ func pruneContainers(runtime *adapter.LocalRuntime, ctx context.Context, maxWork return printParallelOutput(deleteErrors, errCount) } -func pruneContainersCmd(c *cli.Context) error { - runtime, err := adapter.GetRuntime(c) +func pruneContainersCmd(c *cliconfig.PruneContainersValues) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -69,9 +80,9 @@ func pruneContainersCmd(c *cli.Context) error { maxWorkers := shared.Parallelize("rm") if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalInt("max-workers") + maxWorkers = c.GlobalFlags.MaxWorks } logrus.Debugf("Setting maximum workers to %d", maxWorkers) - return pruneContainers(runtime, getContext(), maxWorkers, c.Bool("force")) + return pruneContainers(runtime, getContext(), maxWorkers, c.Bool("force"), c.Bool("volumes")) } diff --git a/cmd/podman/cp.go b/cmd/podman/cp.go new file mode 100644 index 000000000..d9f230b67 --- /dev/null +++ b/cmd/podman/cp.go @@ -0,0 +1,291 @@ +package main + +import ( + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/containers/buildah/util" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/chrootuser" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/storage" + "github.com/containers/storage/pkg/archive" + "github.com/containers/storage/pkg/chrootarchive" + "github.com/containers/storage/pkg/idtools" + digest "github.com/opencontainers/go-digest" + specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + cpCommand cliconfig.CpValues + + cpDescription = "Copy files/folders between a container and the local filesystem" + _cpCommand = &cobra.Command{ + Use: "cp", + Short: "Copy files/folders between a container and the local filesystem", + Long: cpDescription, + RunE: func(cmd *cobra.Command, args []string) error { + cpCommand.InputArgs = args + cpCommand.GlobalFlags = MainGlobalOpts + return cpCmd(&cpCommand) + }, + Example: "[CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH", + } +) + +func init() { + cpCommand.Command = _cpCommand + rootCmd.AddCommand(cpCommand.Command) +} + +func cpCmd(c *cliconfig.CpValues) error { + args := c.InputArgs + if len(args) != 2 { + return errors.Errorf("you must provide a source path and a destination path") + } + if os.Geteuid() != 0 { + rootless.SetSkipStorageSetup(true) + } + + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + return copyBetweenHostAndContainer(runtime, args[0], args[1]) +} + +func copyBetweenHostAndContainer(runtime *libpod.Runtime, src string, dest string) error { + + srcCtr, srcPath := parsePath(runtime, src) + destCtr, destPath := parsePath(runtime, dest) + + if (srcCtr == nil && destCtr == nil) || (srcCtr != nil && destCtr != nil) { + return errors.Errorf("invalid arguments %s, %s you must use just one container", src, dest) + } + + if len(srcPath) == 0 || len(destPath) == 0 { + return errors.Errorf("invalid arguments %s, %s you must specify paths", src, dest) + } + ctr := srcCtr + isFromHostToCtr := (ctr == nil) + if isFromHostToCtr { + ctr = destCtr + } + + if os.Geteuid() != 0 { + s, err := ctr.State() + if err != nil { + return err + } + var became bool + var ret int + if s == libpod.ContainerStateRunning || s == libpod.ContainerStatePaused { + data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) + if err != nil { + return errors.Wrapf(err, "cannot read conmon PID file %q", ctr.Config().ConmonPidFile) + } + conmonPid, err := strconv.Atoi(string(data)) + if err != nil { + return errors.Wrapf(err, "cannot parse PID %q", data) + } + became, ret, err = rootless.JoinDirectUserAndMountNS(uint(conmonPid)) + } else { + became, ret, err = rootless.BecomeRootInUserNS() + } + if err != nil { + return err + } + if became { + os.Exit(ret) + } + } + + mountPoint, err := ctr.Mount() + if err != nil { + return err + } + defer ctr.Unmount(false) + user, err := getUser(mountPoint, ctr.User()) + if err != nil { + return err + } + idMappingOpts, err := ctr.IDMappings() + if err != nil { + return errors.Wrapf(err, "error getting IDMappingOptions") + } + containerOwner := idtools.IDPair{UID: int(user.UID), GID: int(user.GID)} + hostUID, hostGID, err := util.GetHostIDs(convertIDMap(idMappingOpts.UIDMap), convertIDMap(idMappingOpts.GIDMap), user.UID, user.GID) + if err != nil { + return err + } + + hostOwner := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)} + + var glob []string + if isFromHostToCtr { + if filepath.IsAbs(destPath) { + destPath = filepath.Join(mountPoint, destPath) + + } else { + if err = idtools.MkdirAllAndChownNew(filepath.Join(mountPoint, ctr.WorkingDir()), 0755, hostOwner); err != nil { + return errors.Wrapf(err, "error creating directory %q", destPath) + } + destPath = filepath.Join(mountPoint, ctr.WorkingDir(), destPath) + } + } else { + if filepath.IsAbs(srcPath) { + srcPath = filepath.Join(mountPoint, srcPath) + } else { + srcPath = filepath.Join(mountPoint, ctr.WorkingDir(), srcPath) + } + } + glob, err = filepath.Glob(srcPath) + if err != nil { + return errors.Wrapf(err, "invalid glob %q", srcPath) + } + if len(glob) == 0 { + glob = append(glob, srcPath) + } + if !filepath.IsAbs(destPath) { + dir, err := os.Getwd() + if err != nil { + return errors.Wrapf(err, "err getting current working directory") + } + destPath = filepath.Join(dir, destPath) + } + + var lastError error + for _, src := range glob { + err := copy(src, destPath, dest, idMappingOpts, &containerOwner) + if lastError != nil { + logrus.Error(lastError) + } + lastError = err + } + return lastError +} + +func getUser(mountPoint string, userspec string) (specs.User, error) { + uid, gid, err := chrootuser.GetUser(mountPoint, userspec) + u := specs.User{ + UID: uid, + GID: gid, + Username: userspec, + } + if !strings.Contains(userspec, ":") { + groups, err2 := chrootuser.GetAdditionalGroupsForUser(mountPoint, uint64(u.UID)) + if err2 != nil { + if errors.Cause(err2) != chrootuser.ErrNoSuchUser && err == nil { + err = err2 + } + } else { + u.AdditionalGids = groups + } + + } + return u, err +} + +func parsePath(runtime *libpod.Runtime, path string) (*libpod.Container, string) { + pathArr := strings.SplitN(path, ":", 2) + if len(pathArr) == 2 { + ctr, err := runtime.LookupContainer(pathArr[0]) + if err == nil { + return ctr, pathArr[1] + } + } + return nil, path +} + +func getPathInfo(path string) (string, os.FileInfo, error) { + path, err := filepath.EvalSymlinks(path) + if err != nil { + return "", nil, errors.Wrapf(err, "error evaluating symlinks %q", path) + } + srcfi, err := os.Stat(path) + if err != nil { + return "", nil, errors.Wrapf(err, "error reading path %q", path) + } + return path, srcfi, nil +} + +func copy(src, destPath, dest string, idMappingOpts storage.IDMappingOptions, chownOpts *idtools.IDPair) error { + srcPath, err := filepath.EvalSymlinks(src) + if err != nil { + return errors.Wrapf(err, "error evaluating symlinks %q", srcPath) + } + + srcPath, srcfi, err := getPathInfo(srcPath) + if err != nil { + return err + } + destdir := destPath + if !srcfi.IsDir() && !strings.HasSuffix(dest, string(os.PathSeparator)) { + destdir = filepath.Dir(destPath) + } + if err = os.MkdirAll(destdir, 0755); err != nil { + return errors.Wrapf(err, "error creating directory %q", destdir) + } + + // return functions for copying items + copyFileWithTar := chrootarchive.CopyFileWithTarAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap) + copyWithTar := chrootarchive.CopyWithTarAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap) + untarPath := chrootarchive.UntarPathAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap) + + if srcfi.IsDir() { + + logrus.Debugf("copying %q to %q", srcPath+string(os.PathSeparator)+"*", dest+string(os.PathSeparator)+"*") + if err = copyWithTar(srcPath, destPath); err != nil { + return errors.Wrapf(err, "error copying %q to %q", srcPath, dest) + } + return nil + } + if !archive.IsArchivePath(srcPath) { + // This srcPath is a file, and either it's not an + // archive, or we don't care whether or not it's an + // archive. + destfi, err := os.Stat(destPath) + if err != nil { + if !os.IsNotExist(err) { + return errors.Wrapf(err, "failed to get stat of dest path %s", destPath) + } + } + if destfi != nil && destfi.IsDir() { + destPath = filepath.Join(destPath, filepath.Base(srcPath)) + } + // Copy the file, preserving attributes. + logrus.Debugf("copying %q to %q", srcPath, destPath) + if err = copyFileWithTar(srcPath, destPath); err != nil { + return errors.Wrapf(err, "error copying %q to %q", srcPath, destPath) + } + return nil + } + // We're extracting an archive into the destination directory. + logrus.Debugf("extracting contents of %q into %q", srcPath, destPath) + if err = untarPath(srcPath, destPath); err != nil { + return errors.Wrapf(err, "error extracting %q into %q", srcPath, destPath) + } + return nil +} + +func convertIDMap(idMaps []idtools.IDMap) (convertedIDMap []specs.LinuxIDMapping) { + for _, idmap := range idMaps { + tempIDMap := specs.LinuxIDMapping{ + ContainerID: uint32(idmap.ContainerID), + HostID: uint32(idmap.HostID), + Size: uint32(idmap.Size), + } + convertedIDMap = append(convertedIDMap, tempIDMap) + } + return convertedIDMap +} diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 2d85abd35..868f90d54 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -12,6 +12,7 @@ import ( "strings" "syscall" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" @@ -27,39 +28,56 @@ import ( "github.com/docker/go-units" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/selinux/go-selinux/label" + opentracing "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + createCommand cliconfig.CreateValues + createDescription = "Creates a new container from the given image or" + + " storage and prepares it for running the specified command. The" + + " container ID is then printed to stdout. You can then start it at" + + " any time with the podman start <container_id> command. The container" + + " will be created with the initial state 'created'." + _createCommand = &cobra.Command{ + Use: "create", + Short: "Create but do not start a container", + Long: createDescription, + RunE: func(cmd *cobra.Command, args []string) error { + createCommand.InputArgs = args + createCommand.GlobalFlags = MainGlobalOpts + return createCmd(&createCommand) + }, + Example: `podman create alpine ls + podman create --annotation HELLO=WORLD alpine ls + podman create -t -i --name myctr alpine ls`, + } + defaultEnvVariables = map[string]string{ "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "TERM": "xterm", } ) -var createDescription = "Creates a new container from the given image or" + - " storage and prepares it for running the specified command. The" + - " container ID is then printed to stdout. You can then start it at" + - " any time with the podman start <container_id> command. The container" + - " will be created with the initial state 'created'." - -var createCommand = cli.Command{ - Name: "create", - Usage: "Create but do not start a container", - Description: createDescription, - Flags: sortFlags(createFlags), - Action: createCmd, - ArgsUsage: "IMAGE [COMMAND [ARG...]]", - HideHelp: true, - SkipArgReorder: true, - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, +func init() { + createCommand.PodmanCommand.Command = _createCommand + createCommand.SetUsageTemplate(UsageTemplate()) + + getCreateFlags(&createCommand.PodmanCommand) + flags := createCommand.Flags() + flags.SetInterspersed(true) + } -func createCmd(c *cli.Context) error { - if err := createInit(c); err != nil { +func createCmd(c *cliconfig.CreateValues) error { + if c.Bool("trace") { + span, _ := opentracing.StartSpanFromContext(Ctx, "createCmd") + defer span.Finish() + } + + if err := createInit(&c.PodmanCommand); err != nil { return err } @@ -67,13 +85,13 @@ func createCmd(c *cli.Context) error { rootless.SetSkipStorageSetup(true) } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - ctr, _, err := createContainer(c, runtime) + ctr, _, err := createContainer(&c.PodmanCommand, runtime) if err != nil { return err } @@ -82,33 +100,33 @@ func createCmd(c *cli.Context) error { return nil } -func createInit(c *cli.Context) error { - // TODO should allow user to create based off a directory on the host not just image - // Need CLI support for this +func createInit(c *cliconfig.PodmanCommand) error { + if c.Bool("trace") { + span, _ := opentracing.StartSpanFromContext(Ctx, "createInit") + defer span.Finish() + } // Docker-compatibility: the "-h" flag for run/create is reserved for // the hostname (see https://github.com/containers/libpod/issues/1367). - if c.Bool("help") { - cli.ShowCommandHelpAndExit(c, "run", 0) - } - - if err := validateFlags(c, createFlags); err != nil { - return err - } - if len(c.Args()) < 1 { + if len(c.InputArgs) < 1 { return errors.Errorf("image name or ID is required") } return nil } -func createContainer(c *cli.Context, runtime *libpod.Runtime) (*libpod.Container, *cc.CreateConfig, error) { +func createContainer(c *cliconfig.PodmanCommand, runtime *libpod.Runtime) (*libpod.Container, *cc.CreateConfig, error) { + if c.Bool("trace") { + span, _ := opentracing.StartSpanFromContext(Ctx, "createContainer") + defer span.Finish() + } + rtc := runtime.GetConfig() ctx := getContext() rootfs := "" if c.Bool("rootfs") { - rootfs = c.Args()[0] + rootfs = c.InputArgs[0] } var err error @@ -134,7 +152,7 @@ func createContainer(c *cli.Context, runtime *libpod.Runtime) (*libpod.Container writer = os.Stderr } - newImage, err := runtime.ImageRuntime().New(ctx, c.Args()[0], rtc.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, false, nil) + newImage, err := runtime.ImageRuntime().New(ctx, c.InputArgs[0], rtc.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, false, nil) if err != nil { return nil, nil, err } @@ -264,7 +282,7 @@ func isPortInImagePorts(exposedPorts map[string]struct{}, port string) bool { return false } -func configureEntrypoint(c *cli.Context, data *inspect.ImageData) []string { +func configureEntrypoint(c *cliconfig.PodmanCommand, data *inspect.ImageData) []string { entrypoint := []string{} if c.IsSet("entrypoint") { // Force entrypoint to "" @@ -284,7 +302,7 @@ func configureEntrypoint(c *cli.Context, data *inspect.ImageData) []string { return entrypoint } -func configurePod(c *cli.Context, runtime *libpod.Runtime, namespaces map[string]string, podName string) (map[string]string, error) { +func configurePod(c *cliconfig.PodmanCommand, runtime *libpod.Runtime, namespaces map[string]string, podName string) (map[string]string, error) { pod, err := runtime.LookupPod(podName) if err != nil { return namespaces, err @@ -296,7 +314,7 @@ func configurePod(c *cli.Context, runtime *libpod.Runtime, namespaces map[string if (namespaces["pid"] == cc.Pod) || (!c.IsSet("pid") && pod.SharesPID()) { namespaces["pid"] = fmt.Sprintf("container:%s", podInfraID) } - if (namespaces["net"] == cc.Pod) || (!c.IsSet("net") && pod.SharesNet()) { + if (namespaces["net"] == cc.Pod) || (!c.IsSet("net") && !c.IsSet("network") && pod.SharesNet()) { namespaces["net"] = fmt.Sprintf("container:%s", podInfraID) } if (namespaces["user"] == cc.Pod) || (!c.IsSet("user") && pod.SharesUser()) { @@ -313,7 +331,7 @@ func configurePod(c *cli.Context, runtime *libpod.Runtime, namespaces map[string // Parses CLI options related to container creation into a config which can be // parsed into an OCI runtime spec -func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtime, imageName string, data *inspect.ImageData) (*cc.CreateConfig, error) { +func parseCreateOpts(ctx context.Context, c *cliconfig.PodmanCommand, runtime *libpod.Runtime, imageName string, data *inspect.ImageData) (*cc.CreateConfig, error) { var ( inputCommand, command []string memoryLimit, memoryReservation, memorySwap, memoryKernel int64 @@ -335,14 +353,14 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim imageID := "" - inputCommand = c.Args()[1:] + inputCommand = c.InputArgs[1:] if data != nil { imageID = data.ID } rootfs := "" if c.Bool("rootfs") { - rootfs = c.Args()[0] + rootfs = c.InputArgs[0] } sysctl, err := validateSysctl(c.StringSlice("sysctl")) @@ -382,27 +400,24 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim blkioWeight = uint16(u) } var mountList []spec.Mount - if mountList, err = parseMounts(c.StringSlice("mount")); err != nil { + if mountList, err = parseMounts(c.StringArray("mount")); err != nil { return nil, err } - if err = parseVolumes(c.StringSlice("volume")); err != nil { + if err = parseVolumes(c.StringArray("volume")); err != nil { return nil, err } - if err = parseVolumesFrom(c.StringSlice("volumes-from")); err != nil { + if err = parseVolumesFrom(c.StringArray("volumes-from")); err != nil { return nil, err } tty := c.Bool("tty") - if c.Bool("detach") && c.Bool("rm") { - return nil, errors.Errorf("--rm and --detach cannot be specified together") - } - if c.Int64("cpu-period") != 0 && c.Float64("cpus") > 0 { + if c.Flag("cpu-period").Changed && c.Flag("cpus").Changed { return nil, errors.Errorf("--cpu-period and --cpus cannot be set together") } - if c.Int64("cpu-quota") != 0 && c.Float64("cpus") > 0 { + if c.Flag("cpu-quota").Changed && c.Flag("cpus").Changed { return nil, errors.Errorf("--cpu-quota and --cpus cannot be set together") } @@ -420,9 +435,13 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim // Instead of integrating here, should be done in libpod // However, that also involves setting up security opts // when the pod's namespace is integrated + namespaceNet := c.String("network") + if c.Flag("net").Changed { + namespaceNet = c.String("net") + } namespaces = map[string]string{ "pid": c.String("pid"), - "net": c.String("net"), + "net": namespaceNet, "ipc": c.String("ipc"), "user": c.String("userns"), "uts": c.String("uts"), @@ -627,11 +646,6 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim if util.StringInSlice(".", c.StringSlice("dns-search")) && len(c.StringSlice("dns-search")) > 1 { return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'") } - if !netMode.IsPrivate() { - if c.IsSet("dns-search") || c.IsSet("dns") || c.IsSet("dns-opt") { - return nil, errors.Errorf("specifying DNS flags when network mode is shared with the host or another container is not allowed") - } - } // Validate domains are good for _, dom := range c.StringSlice("dns-search") { @@ -641,9 +655,10 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim } var ImageVolumes map[string]struct{} - if data != nil { + if data != nil && c.String("image-volume") != "ignore" { ImageVolumes = data.Config.Volumes } + var imageVolType = map[string]string{ "bind": "", "tmpfs": "", @@ -654,7 +669,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim } var systemd bool - if command != nil && c.BoolT("systemd") && ((filepath.Base(command[0]) == "init") || (filepath.Base(command[0]) == "systemd")) { + if command != nil && c.Bool("systemd") && ((filepath.Base(command[0]) == "init") || (filepath.Base(command[0]) == "systemd")) { systemd = true if signalString == "" { stopSignal, err = signal.ParseSignal("RTMIN+3") @@ -663,7 +678,17 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim } } } + // This is done because cobra cannot have two aliased flags. So we have to check + // both + network := c.String("network") + if c.Flag("net").Changed { + network = c.String("net") + } + var memorySwappiness int64 + if c.Flags().Lookup("memory-swappiness") != nil { + memorySwappiness, _ = c.Flags().GetInt64("memory-swappiness") + } config := &cc.CreateConfig{ Runtime: runtime, Annotations: annotations, @@ -697,7 +722,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim LogDriverOpt: c.StringSlice("log-opt"), MacAddress: c.String("mac-address"), Name: c.String("name"), - Network: c.String("network"), + Network: network, NetworkAlias: c.StringSlice("network-alias"), IpcMode: ipcMode, NetMode: netMode, @@ -730,12 +755,11 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim Memory: memoryLimit, MemoryReservation: memoryReservation, MemorySwap: memorySwap, - MemorySwappiness: c.Int("memory-swappiness"), + MemorySwappiness: int(memorySwappiness), KernelMemory: memoryKernel, OomScoreAdj: c.Int("oom-score-adj"), - - PidsLimit: c.Int64("pids-limit"), - Ulimit: c.StringSlice("ulimit"), + PidsLimit: c.Int64("pids-limit"), + Ulimit: c.StringSlice("ulimit"), }, Rm: c.Bool("rm"), StopSignal: stopSignal, @@ -747,13 +771,12 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim User: user, UsernsMode: usernsMode, Mounts: mountList, - Volumes: c.StringSlice("volume"), + Volumes: c.StringArray("volume"), WorkDir: workDir, Rootfs: rootfs, VolumesFrom: c.StringSlice("volumes-from"), - Syslog: c.GlobalBool("syslog"), + Syslog: c.GlobalFlags.Syslog, } - if c.Bool("init") { initPath := c.String("init-path") if initPath == "" { @@ -767,11 +790,11 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim if config.Privileged { config.LabelOpts = label.DisableSecOpt() } else { - if err := parseSecurityOpt(config, c.StringSlice("security-opt")); err != nil { + if err := parseSecurityOpt(config, c.StringArray("security-opt")); err != nil { return nil, err } } - config.SecurityOpts = c.StringSlice("security-opt") + config.SecurityOpts = c.StringArray("security-opt") warnings, err := verifyContainerResources(config, false) if err != nil { return nil, err @@ -840,6 +863,12 @@ func joinOrCreateRootlessUserNamespace(createConfig *cc.CreateConfig, runtime *l if err != nil { return false, -1, err } + if pid == 0 { + if createConfig.Pod != "" { + continue + } + return false, -1, errors.Errorf("dependency container %s is not running", ctr.ID()) + } return rootless.JoinNS(uint(pid)) } } diff --git a/cmd/podman/create_cli.go b/cmd/podman/create_cli.go index 95b9321fd..ae0549687 100644 --- a/cmd/podman/create_cli.go +++ b/cmd/podman/create_cli.go @@ -80,19 +80,22 @@ func addWarning(warnings []string, msg string) []string { // podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ... // podman run --mount type=tmpfs,target=/dev/shm .. func parseMounts(mounts []string) ([]spec.Mount, error) { + // TODO(vrothberg): the manual parsing can be replaced with a regular expression + // to allow a more robust parsing of the mount format and to give + // precise errors regarding supported format versus suppored options. var mountList []spec.Mount - errInvalidSyntax := errors.Errorf("incorrect mount format : should be --mount type=<bind|tmpfs>,[src=<host-dir>,]target=<ctr-dir>,[options]") + errInvalidSyntax := errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs>,[src=<host-dir>,]target=<ctr-dir>[,options]") for _, mount := range mounts { var tokenCount int var mountInfo spec.Mount arr := strings.SplitN(mount, ",", 2) if len(arr) < 2 { - return nil, errInvalidSyntax + return nil, errors.Wrapf(errInvalidSyntax, "%q", mount) } kv := strings.Split(arr[0], "=") if kv[0] != "type" { - return nil, errInvalidSyntax + return nil, errors.Wrapf(errInvalidSyntax, "%q", mount) } switch kv[1] { case "bind": @@ -168,7 +171,7 @@ func parseVolumes(volumes []string) error { for _, volume := range volumes { arr := strings.SplitN(volume, ":", 3) if len(arr) < 2 { - return errors.Errorf("incorrect volume format %q, should be host-dir:ctr-dir:[option]", volume) + return errors.Errorf("incorrect volume format %q, should be host-dir:ctr-dir[:option]", volume) } if err := validateVolumeHostDir(arr[0]); err != nil { return err diff --git a/cmd/podman/diff.go b/cmd/podman/diff.go index 5f813699f..e2d258ad4 100644 --- a/cmd/podman/diff.go +++ b/cmd/podman/diff.go @@ -2,12 +2,12 @@ package main import ( "fmt" - + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) type diffJSONOutput struct { @@ -33,31 +33,37 @@ func (so stdoutStruct) Out() error { } var ( - diffFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "archive", - Usage: "Save the diff as a tar archive", - Hidden: true, - }, - cli.StringFlag{ - Name: "format", - Usage: "Change the output format.", - }, - } + diffCommand cliconfig.DiffValues diffDescription = fmt.Sprint(`Displays changes on a container or image's filesystem. The container or image will be compared to its parent layer`) - diffCommand = cli.Command{ - Name: "diff", - Usage: "Inspect changes on container's file systems", - Description: diffDescription, - Flags: sortFlags(diffFlags), - Action: diffCmd, - ArgsUsage: "ID-NAME", - OnUsageError: usageErrorHandler, + _diffCommand = &cobra.Command{ + Use: "diff", + Short: "Inspect changes on container's file systems", + Long: diffDescription, + RunE: func(cmd *cobra.Command, args []string) error { + diffCommand.InputArgs = args + diffCommand.GlobalFlags = MainGlobalOpts + return diffCmd(&diffCommand) + }, + Example: `podman diff imageID + podman diff ctrID + podman diff --format json redis:alpine`, } ) +func init() { + diffCommand.Command = _diffCommand + diffCommand.SetUsageTemplate(UsageTemplate()) + flags := diffCommand.Flags() + + flags.BoolVar(&diffCommand.Archive, "archive", true, "Save the diff as a tar archive") + flags.StringVar(&diffCommand.Format, "format", "", "Change the output format") + + flags.MarkHidden("archive") + +} + func formatJSON(output []diffOutputParams) (diffJSONOutput, error) { jsonStruct := diffJSONOutput{} for _, output := range output { @@ -75,29 +81,25 @@ func formatJSON(output []diffOutputParams) (diffJSONOutput, error) { return jsonStruct, nil } -func diffCmd(c *cli.Context) error { - if err := validateFlags(c, diffFlags); err != nil { - return err - } - - if len(c.Args()) != 1 { +func diffCmd(c *cliconfig.DiffValues) error { + if len(c.InputArgs) != 1 { return errors.Errorf("container, image, or layer name must be specified: podman diff [options [...]] ID-NAME") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - to := c.Args().Get(0) + to := c.InputArgs[0] changes, err := runtime.GetDiff("", to) if err != nil { return errors.Wrapf(err, "could not get changes for %q", to) } diffOutput := []diffOutputParams{} - outputFormat := c.String("format") + outputFormat := c.Format for _, change := range changes { diff --git a/cmd/podman/docker/types.go b/cmd/podman/docker/types.go deleted file mode 100644 index eda618e40..000000000 --- a/cmd/podman/docker/types.go +++ /dev/null @@ -1,272 +0,0 @@ -package docker - -// -// Types extracted from Docker -// - -import ( - "time" - - "github.com/containers/image/pkg/strslice" - "github.com/opencontainers/go-digest" -) - -// TypeLayers github.com/docker/docker/image/rootfs.go -const TypeLayers = "layers" - -// V2S2MediaTypeManifest github.com/docker/distribution/manifest/schema2/manifest.go -const V2S2MediaTypeManifest = "application/vnd.docker.distribution.manifest.v2+json" - -// V2S2MediaTypeImageConfig github.com/docker/distribution/manifest/schema2/manifest.go -const V2S2MediaTypeImageConfig = "application/vnd.docker.container.image.v1+json" - -// V2S2MediaTypeLayer github.com/docker/distribution/manifest/schema2/manifest.go -const V2S2MediaTypeLayer = "application/vnd.docker.image.rootfs.diff.tar.gzip" - -// V2S2MediaTypeUncompressedLayer github.com/docker/distribution/manifest/schema2/manifest.go -const V2S2MediaTypeUncompressedLayer = "application/vnd.docker.image.rootfs.diff.tar" - -// V2S2RootFS describes images root filesystem -// This is currently a placeholder that only supports layers. In the future -// this can be made into an interface that supports different implementations. -// github.com/docker/docker/image/rootfs.go -type V2S2RootFS struct { - Type string `json:"type"` - DiffIDs []digest.Digest `json:"diff_ids,omitempty"` -} - -// V2S2History stores build commands that were used to create an image -// github.com/docker/docker/image/image.go -type V2S2History struct { - // Created is the timestamp at which the image was created - Created time.Time `json:"created"` - // Author is the name of the author that was specified when committing the image - Author string `json:"author,omitempty"` - // CreatedBy keeps the Dockerfile command used while building the image - CreatedBy string `json:"created_by,omitempty"` - // Comment is the commit message that was set when committing the image - Comment string `json:"comment,omitempty"` - // EmptyLayer is set to true if this history item did not generate a - // layer. Otherwise, the history item is associated with the next - // layer in the RootFS section. - EmptyLayer bool `json:"empty_layer,omitempty"` -} - -// ID is the content-addressable ID of an image. -// github.com/docker/docker/image/image.go -type ID digest.Digest - -// HealthConfig holds configuration settings for the HEALTHCHECK feature. -// github.com/docker/docker/api/types/container/config.go -type HealthConfig struct { - // Test is the test to perform to check that the container is healthy. - // An empty slice means to inherit the default. - // The options are: - // {} : inherit healthcheck - // {"NONE"} : disable healthcheck - // {"CMD", args...} : exec arguments directly - // {"CMD-SHELL", command} : run command with system's default shell - Test []string `json:",omitempty"` - - // Zero means to inherit. Durations are expressed as integer nanoseconds. - Interval time.Duration `json:",omitempty"` // Interval is the time to wait between checks. - Timeout time.Duration `json:",omitempty"` // Timeout is the time to wait before considering the check to have hung. - StartPeriod time.Duration `json:",omitempty"` // Time to wait after the container starts before running the first check. - - // Retries is the number of consecutive failures needed to consider a container as unhealthy. - // Zero means inherit. - Retries int `json:",omitempty"` -} - -// PortSet is a collection of structs indexed by Port -// github.com/docker/go-connections/nat/nat.go -type PortSet map[Port]struct{} - -// Port is a string containing port number and protocol in the format "80/tcp" -// github.com/docker/go-connections/nat/nat.go -type Port string - -// Config contains the configuration data about a container. -// It should hold only portable information about the container. -// Here, "portable" means "independent from the host we are running on". -// Non-portable information *should* appear in HostConfig. -// All fields added to this struct must be marked `omitempty` to keep getting -// predictable hashes from the old `v1Compatibility` configuration. -// github.com/docker/docker/api/types/container/config.go -type Config struct { - Hostname string // Hostname - Domainname string // Domainname - User string // User that will run the command(s) inside the container, also support user:group - AttachStdin bool // Attach the standard input, makes possible user interaction - AttachStdout bool // Attach the standard output - AttachStderr bool // Attach the standard error - ExposedPorts PortSet `json:",omitempty"` // List of exposed ports - Tty bool // Attach standard streams to a tty, including stdin if it is not closed. - OpenStdin bool // Open stdin - StdinOnce bool // If true, close stdin after the 1 attached client disconnects. - Env []string // List of environment variable to set in the container - Cmd strslice.StrSlice // Command to run when starting the container - Healthcheck *HealthConfig `json:",omitempty"` // Healthcheck describes how to check the container is healthy - ArgsEscaped bool `json:",omitempty"` // True if command is already escaped (Windows specific) - Image string // Name of the image as it was passed by the operator (e.g. could be symbolic) - Volumes map[string]struct{} // List of volumes (mounts) used for the container - WorkingDir string // Current directory (PWD) in the command will be launched - Entrypoint strslice.StrSlice // Entrypoint to run when starting the container - NetworkDisabled bool `json:",omitempty"` // Is network disabled - MacAddress string `json:",omitempty"` // Mac Address of the container - OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile - Labels map[string]string // List of labels set to this container - StopSignal string `json:",omitempty"` // Signal to stop a container - StopTimeout *int `json:",omitempty"` // Timeout (in seconds) to stop a container - Shell strslice.StrSlice `json:",omitempty"` // Shell for shell-form of RUN, CMD, ENTRYPOINT -} - -// V1Compatibility - For non-top-level layers, create fake V1Compatibility -// strings that fit the format and don't collide with anything else, but -// don't result in runnable images on their own. -// github.com/docker/distribution/manifest/schema1/config_builder.go -type V1Compatibility struct { - ID string `json:"id"` - Parent string `json:"parent,omitempty"` - Comment string `json:"comment,omitempty"` - Created time.Time `json:"created"` - Config struct { - Cmd []string - } `json:"container_config,omitempty"` - Author string `json:"author,omitempty"` - ThrowAway bool `json:"throwaway,omitempty"` -} - -// V1Image stores the V1 image configuration. -// github.com/docker/docker/image/image.go -type V1Image struct { - // ID is a unique 64 character identifier of the image - ID string `json:"id,omitempty"` - // Parent is the ID of the parent image - Parent string `json:"parent,omitempty"` - // Comment is the commit message that was set when committing the image - Comment string `json:"comment,omitempty"` - // Created is the timestamp at which the image was created - Created time.Time `json:"created"` - // Container is the id of the container used to commit - Container string `json:"container,omitempty"` - // ContainerConfig is the configuration of the container that is committed into the image - ContainerConfig Config `json:"container_config,omitempty"` - // DockerVersion specifies the version of Docker that was used to build the image - DockerVersion string `json:"docker_version,omitempty"` - // Author is the name of the author that was specified when committing the image - Author string `json:"author,omitempty"` - // Config is the configuration of the container received from the client - Config *Config `json:"config,omitempty"` - // Architecture is the hardware that the image is build and runs on - Architecture string `json:"architecture,omitempty"` - // OS is the operating system used to build and run the image - OS string `json:"os,omitempty"` - // Size is the total size of the image including all layers it is composed of - Size int64 `json:",omitempty"` -} - -// V2Image stores the image configuration -// github.com/docker/docker/image/image.go -type V2Image struct { - V1Image - Parent ID `json:"parent,omitempty"` - RootFS *V2S2RootFS `json:"rootfs,omitempty"` - History []V2S2History `json:"history,omitempty"` - OSVersion string `json:"os.version,omitempty"` - OSFeatures []string `json:"os.features,omitempty"` - - // rawJSON caches the immutable JSON associated with this image. - //rawJSON []byte - - // computedID is the ID computed from the hash of the image config. - // Not to be confused with the legacy V1 ID in V1Image. - //computedID ID -} - -// V2Versioned provides a struct with the manifest schemaVersion and mediaType. -// Incoming content with unknown schema version can be decoded against this -// struct to check the version. -// github.com/docker/distribution/manifest/versioned.go -type V2Versioned struct { - // SchemaVersion is the image manifest schema that this image follows - SchemaVersion int `json:"schemaVersion"` - - // MediaType is the media type of this schema. - MediaType string `json:"mediaType,omitempty"` -} - -// V2S1FSLayer is a container struct for BlobSums defined in an image manifest -// github.com/docker/distribution/manifest/schema1/manifest.go -type V2S1FSLayer struct { - // BlobSum is the tarsum of the referenced filesystem image layer - BlobSum digest.Digest `json:"blobSum"` -} - -// V2S1History stores unstructured v1 compatibility information -// github.com/docker/distribution/manifest/schema1/manifest.go -type V2S1History struct { - // V1Compatibility is the raw v1 compatibility information - V1Compatibility string `json:"v1Compatibility"` -} - -// V2S1Manifest provides the base accessible fields for working with V2 image -// format in the registry. -// github.com/docker/distribution/manifest/schema1/manifest.go -type V2S1Manifest struct { - V2Versioned - - // Name is the name of the image's repository - Name string `json:"name"` - - // Tag is the tag of the image specified by this manifest - Tag string `json:"tag"` - - // Architecture is the host architecture on which this image is intended to - // run - Architecture string `json:"architecture"` - - // FSLayers is a list of filesystem layer blobSums contained in this image - FSLayers []V2S1FSLayer `json:"fsLayers"` - - // History is a list of unstructured historical data for v1 compatibility - History []V2S1History `json:"history"` -} - -// V2S2Descriptor describes targeted content. Used in conjunction with a blob -// store, a descriptor can be used to fetch, store and target any kind of -// blob. The struct also describes the wire protocol format. Fields should -// only be added but never changed. -// github.com/docker/distribution/blobs.go -type V2S2Descriptor struct { - // MediaType describe the type of the content. All text based formats are - // encoded as utf-8. - MediaType string `json:"mediaType,omitempty"` - - // Size in bytes of content. - Size int64 `json:"size,omitempty"` - - // Digest uniquely identifies the content. A byte stream can be verified - // against against this digest. - Digest digest.Digest `json:"digest,omitempty"` - - // URLs contains the source URLs of this content. - URLs []string `json:"urls,omitempty"` - - // NOTE: Before adding a field here, please ensure that all - // other options have been exhausted. Much of the type relationships - // depend on the simplicity of this type. -} - -// V2S2Manifest defines a schema2 manifest. -// github.com/docker/distribution/manifest/schema2/manifest.go -type V2S2Manifest struct { - V2Versioned - - // Config references the image configuration as a blob. - Config V2S2Descriptor `json:"config"` - - // Layers lists descriptors for the layers referenced by the - // configuration. - Layers []V2S2Descriptor `json:"layers"` -} diff --git a/cmd/podman/errors.go b/cmd/podman/errors.go new file mode 100644 index 000000000..2572b8779 --- /dev/null +++ b/cmd/podman/errors.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "syscall" + + "github.com/sirupsen/logrus" +) + +func outputError(err error) { + if MainGlobalOpts.LogLevel == "debug" { + logrus.Errorf(err.Error()) + } else { + if ee, ok := err.(*exec.ExitError); ok { + if status, ok := ee.Sys().(syscall.WaitStatus); ok { + exitCode = status.ExitStatus() + } + } + fmt.Fprintln(os.Stderr, "Error:", err.Error()) + } +} diff --git a/cmd/podman/exec.go b/cmd/podman/exec.go index 073e72e64..7040a7b09 100644 --- a/cmd/podman/exec.go +++ b/cmd/podman/exec.go @@ -2,82 +2,78 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" "os" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" - "github.com/urfave/cli" ) var ( - execFlags = []cli.Flag{ - cli.StringSliceFlag{ - Name: "env, e", - Usage: "Set environment variables", - }, - cli.BoolFlag{ - Name: "privileged", - Usage: "Give the process extended Linux capabilities inside the container. The default is false", - }, - cli.BoolFlag{ - Name: "interactive, i", - Usage: "Not supported. All exec commands are interactive by default.", - }, - cli.BoolFlag{ - Name: "tty, t", - Usage: "Allocate a pseudo-TTY. The default is false", - }, - cli.StringFlag{ - Name: "user, u", - Usage: "Sets the username or UID used and optionally the groupname or GID for the specified command", - }, - LatestFlag, - WorkDirFlag, - } + execCommand cliconfig.ExecValues + execDescription = ` podman exec Run a command in a running container ` - - execCommand = cli.Command{ - Name: "exec", - Usage: "Run a process in a running container", - Description: execDescription, - Flags: sortFlags(execFlags), - Action: execCmd, - ArgsUsage: "CONTAINER-NAME", - SkipArgReorder: true, - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _execCommand = &cobra.Command{ + Use: "exec", + Short: "Run a process in a running container", + Long: execDescription, + RunE: func(cmd *cobra.Command, args []string) error { + execCommand.InputArgs = args + execCommand.GlobalFlags = MainGlobalOpts + return execCmd(&execCommand) + }, + Example: `podman exec -it ctrID ls + podman exec -it -w /tmp myCtr pwd + podman exec --user root ctrID ls`, } ) -func execCmd(c *cli.Context) error { - args := c.Args() +func init() { + execCommand.Command = _execCommand + execCommand.SetUsageTemplate(UsageTemplate()) + flags := execCommand.Flags() + flags.SetInterspersed(false) + flags.StringSliceVarP(&execCommand.Env, "env", "e", []string{}, "Set environment variables") + flags.BoolVarP(&execCommand.Interfactive, "interactive", "i", false, "Not supported. All exec commands are interactive by default") + flags.BoolVarP(&execCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&execCommand.Privileged, "privileged", false, "Give the process extended Linux capabilities inside the container. The default is false") + flags.BoolVarP(&execCommand.Tty, "tty", "t", false, "Allocate a pseudo-TTY. The default is false") + flags.StringVarP(&execCommand.User, "user", "u", "", "Sets the username or UID used and optionally the groupname or GID for the specified command") + + flags.StringVarP(&execCommand.Workdir, "workdir", "w", "", "Working directory inside the container") + markFlagHiddenForRemoteClient("latest", flags) +} + +func execCmd(c *cliconfig.ExecValues) error { + args := c.InputArgs var ctr *libpod.Container var err error argStart := 1 - if len(args) < 1 && !c.Bool("latest") { + if len(args) < 1 && !c.Latest { return errors.Errorf("you must provide one container name or id") } - if len(args) < 2 && !c.Bool("latest") { + if len(args) < 2 && !c.Latest { return errors.Errorf("you must provide a command to exec") } - if c.Bool("latest") { + if c.Latest { argStart = 0 } rootless.SetSkipStorageSetup(true) cmd := args[argStart:] - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - if c.Bool("latest") { + if c.Latest { ctr, err = runtime.GetLatestContainer() } else { ctr, err = runtime.LookupContainer(args[0]) @@ -101,7 +97,7 @@ func execCmd(c *cli.Context) error { // ENVIRONMENT VARIABLES env := map[string]string{} - if err := readKVStrings(env, []string{}, c.StringSlice("env")); err != nil { + if err := readKVStrings(env, []string{}, c.Env); err != nil { return errors.Wrapf(err, "unable to process environment variables") } envs := []string{} @@ -109,5 +105,5 @@ func execCmd(c *cli.Context) error { envs = append(envs, fmt.Sprintf("%s=%s", k, v)) } - return ctr.Exec(c.Bool("tty"), c.Bool("privileged"), envs, cmd, c.String("user"), c.String("workdir")) + return ctr.Exec(c.Tty, c.Privileged, envs, cmd, c.User, c.Workdir) } diff --git a/cmd/podman/exists.go b/cmd/podman/exists.go index a7601aaa2..74a4c841b 100644 --- a/cmd/podman/exists.go +++ b/cmd/podman/exists.go @@ -1,73 +1,89 @@ package main import ( + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" "os" - "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/adapter" "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" - "github.com/urfave/cli" ) var ( + imageExistsCommand cliconfig.ImageExistsValues + containerExistsCommand cliconfig.ContainerExistsValues + podExistsCommand cliconfig.PodExistsValues + imageExistsDescription = ` podman image exists Check if an image exists in local storage ` - - imageExistsCommand = cli.Command{ - Name: "exists", - Usage: "Check if an image exists in local storage", - Description: imageExistsDescription, - Action: imageExistsCmd, - ArgsUsage: "IMAGE-NAME", - OnUsageError: usageErrorHandler, - } -) - -var ( containerExistsDescription = ` podman container exists Check if a container exists in local storage ` - - containerExistsCommand = cli.Command{ - Name: "exists", - Usage: "Check if a container exists in local storage", - Description: containerExistsDescription, - Action: containerExistsCmd, - ArgsUsage: "CONTAINER-NAME", - OnUsageError: usageErrorHandler, - } -) - -var ( podExistsDescription = ` podman pod exists Check if a pod exists in local storage ` + _imageExistsCommand = &cobra.Command{ + Use: "exists", + Short: "Check if an image exists in local storage", + Long: imageExistsDescription, + RunE: func(cmd *cobra.Command, args []string) error { + imageExistsCommand.InputArgs = args + imageExistsCommand.GlobalFlags = MainGlobalOpts + return imageExistsCmd(&imageExistsCommand) + }, + Example: `podman image exists imageID`, + } + + _containerExistsCommand = &cobra.Command{ + Use: "exists", + Short: "Check if a container exists in local storage", + Long: containerExistsDescription, + RunE: func(cmd *cobra.Command, args []string) error { + containerExistsCommand.InputArgs = args + containerExistsCommand.GlobalFlags = MainGlobalOpts + return containerExistsCmd(&containerExistsCommand) - podExistsCommand = cli.Command{ - Name: "exists", - Usage: "Check if a pod exists in local storage", - Description: podExistsDescription, - Action: podExistsCmd, - ArgsUsage: "POD-NAME", - OnUsageError: usageErrorHandler, + }, + Example: `podman container exists containerID`, + } + + _podExistsCommand = &cobra.Command{ + Use: "exists", + Short: "Check if a pod exists in local storage", + Long: podExistsDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podExistsCommand.InputArgs = args + podExistsCommand.GlobalFlags = MainGlobalOpts + return podExistsCmd(&podExistsCommand) + }, + Example: `podman pod exists podID`, } ) -func imageExistsCmd(c *cli.Context) error { - args := c.Args() +func init() { + imageExistsCommand.Command = _imageExistsCommand + imageExistsCommand.SetUsageTemplate(UsageTemplate()) + containerExistsCommand.Command = _containerExistsCommand + containerExistsCommand.SetUsageTemplate(UsageTemplate()) + podExistsCommand.Command = _podExistsCommand + podExistsCommand.SetUsageTemplate(UsageTemplate()) +} + +func imageExistsCmd(c *cliconfig.ImageExistsValues) error { + args := c.InputArgs if len(args) > 1 || len(args) < 1 { return errors.New("you may only check for the existence of one image at a time") } - runtime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -83,12 +99,12 @@ func imageExistsCmd(c *cli.Context) error { return nil } -func containerExistsCmd(c *cli.Context) error { - args := c.Args() +func containerExistsCmd(c *cliconfig.ContainerExistsValues) error { + args := c.InputArgs if len(args) > 1 || len(args) < 1 { return errors.New("you may only check for the existence of one container at a time") } - runtime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -102,19 +118,19 @@ func containerExistsCmd(c *cli.Context) error { return nil } -func podExistsCmd(c *cli.Context) error { - args := c.Args() +func podExistsCmd(c *cliconfig.PodExistsValues) error { + args := c.InputArgs if len(args) > 1 || len(args) < 1 { return errors.New("you may only check for the existence of one pod at a time") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) if _, err := runtime.LookupPod(args[0]); err != nil { - if errors.Cause(err) == libpod.ErrNoSuchPod { + if errors.Cause(err) == libpod.ErrNoSuchPod || err.Error() == "io.podman.PodNotFound" { os.Exit(1) } return err diff --git a/cmd/podman/export.go b/cmd/podman/export.go index eaa4e38a2..5873bad3d 100644 --- a/cmd/podman/export.go +++ b/cmd/podman/export.go @@ -3,50 +3,53 @@ package main import ( "os" - "github.com/containers/libpod/libpod/adapter" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - exportFlags = []cli.Flag{ - cli.StringFlag{ - Name: "output, o", - Usage: "Write to a file, default is STDOUT", - Value: "/dev/stdout", - }, - } + exportCommand cliconfig.ExportValues exportDescription = "Exports container's filesystem contents as a tar archive" + " and saves it on the local machine." - exportCommand = cli.Command{ - Name: "export", - Usage: "Export container's filesystem contents as a tar archive", - Description: exportDescription, - Flags: sortFlags(exportFlags), - Action: exportCmd, - ArgsUsage: "CONTAINER", - OnUsageError: usageErrorHandler, + + _exportCommand = &cobra.Command{ + Use: "export", + Short: "Export container's filesystem contents as a tar archive", + Long: exportDescription, + RunE: func(cmd *cobra.Command, args []string) error { + exportCommand.InputArgs = args + exportCommand.GlobalFlags = MainGlobalOpts + return exportCmd(&exportCommand) + }, + Example: `podman export ctrID > myCtr.tar + podman export --output="myCtr.tar" ctrID`, } ) +func init() { + exportCommand.Command = _exportCommand + exportCommand.SetUsageTemplate(UsageTemplate()) + flags := exportCommand.Flags() + flags.StringVarP(&exportCommand.Output, "output", "o", "/dev/stdout", "Write to a file, default is STDOUT") +} + // exportCmd saves a container to a tarball on disk -func exportCmd(c *cli.Context) error { - if err := validateFlags(c, exportFlags); err != nil { - return err - } +func exportCmd(c *cliconfig.ExportValues) error { if os.Geteuid() != 0 { rootless.SetSkipStorageSetup(true) } - runtime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - args := c.Args() + args := c.InputArgs if len(args) == 0 { return errors.Errorf("container id must be specified") } @@ -54,10 +57,11 @@ func exportCmd(c *cli.Context) error { return errors.Errorf("too many arguments given, need 1 at most.") } - output := c.String("output") + output := c.Output if runtime.Remote && (output == "/dev/stdout" || len(output) == 0) { return errors.New("remote client usage must specify an output file (-o)") } + if output == "/dev/stdout" { file := os.Stdout if logrus.IsTerminal(file) { @@ -68,5 +72,5 @@ func exportCmd(c *cli.Context) error { if err := validateFileName(output); err != nil { return err } - return runtime.Export(args[0], c.String("output")) + return runtime.Export(args[0], output) } diff --git a/cmd/podman/formats/formats.go b/cmd/podman/formats/formats.go index c454c39bd..37f9b8a20 100644 --- a/cmd/podman/formats/formats.go +++ b/cmd/podman/formats/formats.go @@ -120,11 +120,8 @@ func (t StdoutTemplateArray) Out() error { fmt.Fprintln(w, "") continue } - // Only print new line at the end of the output if stdout is the terminal - if terminal.IsTerminal(int(os.Stdout.Fd())) { - fmt.Fprintln(w, "") - } } + fmt.Fprintln(w, "") return w.Flush() } diff --git a/cmd/podman/generate.go b/cmd/podman/generate.go index 20a4a61ab..773d625ee 100644 --- a/cmd/podman/generate.go +++ b/cmd/podman/generate.go @@ -1,23 +1,22 @@ package main import ( - "github.com/urfave/cli" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" ) var ( - generateSubCommands = []cli.Command{ - containerKubeCommand, - } - + generateCommand cliconfig.PodmanCommand generateDescription = "Generate structured data based for a containers and pods" - kubeCommand = cli.Command{ - Name: "generate", - Usage: "Generate structured data", - Description: generateDescription, - ArgsUsage: "", - Subcommands: generateSubCommands, - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, - Hidden: true, + _generateCommand = &cobra.Command{ + Use: "generate", + Short: "Generated structured data", + Long: generateDescription, } ) + +func init() { + generateCommand.Command = _generateCommand + generateCommand.AddCommand(getGenerateSubCommands()...) + generateCommand.SetUsageTemplate(UsageTemplate()) +} diff --git a/cmd/podman/generate_kube.go b/cmd/podman/generate_kube.go index fc667fb5b..15f374c73 100644 --- a/cmd/podman/generate_kube.go +++ b/cmd/podman/generate_kube.go @@ -2,38 +2,43 @@ package main import ( "fmt" - + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" podmanVersion "github.com/containers/libpod/version" "github.com/ghodss/yaml" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" "k8s.io/api/core/v1" ) var ( - containerKubeFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "service, s", - Usage: "Generate YAML for kubernetes service object", - }, - } + containerKubeCommand cliconfig.GenerateKubeValues containerKubeDescription = "Generate Kubernetes Pod YAML" - containerKubeCommand = cli.Command{ - Name: "kube", - Usage: "Generate Kubernetes pod YAML for a container or pod", - Description: containerKubeDescription, - Flags: sortFlags(containerKubeFlags), - Action: generateKubeYAMLCmd, - ArgsUsage: "CONTAINER|POD-NAME", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _containerKubeCommand = &cobra.Command{ + Use: "kube", + Short: "Generate Kubernetes pod YAML for a container or pod", + Long: containerKubeDescription, + RunE: func(cmd *cobra.Command, args []string) error { + containerKubeCommand.InputArgs = args + containerKubeCommand.GlobalFlags = MainGlobalOpts + return generateKubeYAMLCmd(&containerKubeCommand) + }, + Example: `podman generate kube ctrID + podman generate kube podID + podman generate kube --service podID`, } ) -func generateKubeYAMLCmd(c *cli.Context) error { +func init() { + containerKubeCommand.Command = _containerKubeCommand + containerKubeCommand.SetUsageTemplate(UsageTemplate()) + flags := containerKubeCommand.Flags() + flags.BoolVarP(&containerKubeCommand.Service, "service", "s", false, "Generate YAML for kubernetes service object") +} + +func generateKubeYAMLCmd(c *cliconfig.GenerateKubeValues) error { var ( podYAML *v1.Pod container *libpod.Container @@ -48,12 +53,12 @@ func generateKubeYAMLCmd(c *cli.Context) error { if rootless.IsRootless() { return errors.Wrapf(libpod.ErrNotImplemented, "rootless users") } - args := c.Args() + args := c.InputArgs if len(args) > 1 || (len(args) < 1 && !c.Bool("latest")) { return errors.Errorf("you must provide one container|pod ID or name or --latest") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -77,7 +82,7 @@ func generateKubeYAMLCmd(c *cli.Context) error { return err } - if c.Bool("service") { + if c.Service { serviceYAML := libpod.GenerateKubeServiceFromV1Pod(podYAML, servicePorts) marshalledService, err = yaml.Marshal(serviceYAML) if err != nil { diff --git a/cmd/podman/history.go b/cmd/podman/history.go index 8a9b6cd94..103ef08e8 100644 --- a/cmd/podman/history.go +++ b/cmd/podman/history.go @@ -6,12 +6,13 @@ import ( "strings" "time" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" - "github.com/containers/libpod/libpod/adapter" "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/adapter" "github.com/docker/go-units" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) const createdByTruncLength = 45 @@ -34,53 +35,44 @@ type historyOptions struct { } var ( - historyFlags = []cli.Flag{ - cli.BoolTFlag{ - Name: "human, H", - Usage: "Display sizes and dates in human readable format", - }, - cli.BoolFlag{ - Name: "no-trunc, notruncate", - Usage: "Do not truncate the output", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Display the numeric IDs only", - }, - cli.StringFlag{ - Name: "format", - Usage: "Change the output to JSON or a Go template", - }, - } + historyCommand cliconfig.HistoryValues historyDescription = "Displays the history of an image. The information can be printed out in an easy to read, " + "or user specified format, and can be truncated." - historyCommand = cli.Command{ - Name: "history", - Usage: "Show history of a specified image", - Description: historyDescription, - Flags: sortFlags(historyFlags), - Action: historyCmd, - ArgsUsage: "", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _historyCommand = &cobra.Command{ + Use: "history", + Short: "Show history of a specified image", + Long: historyDescription, + RunE: func(cmd *cobra.Command, args []string) error { + historyCommand.InputArgs = args + historyCommand.GlobalFlags = MainGlobalOpts + return historyCmd(&historyCommand) + }, } ) -func historyCmd(c *cli.Context) error { - if err := validateFlags(c, historyFlags); err != nil { - return err - } +func init() { + historyCommand.Command = _historyCommand + historyCommand.SetUsageTemplate(UsageTemplate()) + flags := historyCommand.Flags() + flags.StringVar(&historyCommand.Format, "format", "", "Change the output to JSON or a Go template") + flags.BoolVarP(&historyCommand.Human, "human", "H", true, "Display sizes and dates in human readable format") + // notrucate needs to be added + flags.BoolVar(&historyCommand.NoTrunc, "no-trunc", false, "Do not truncate the output") + flags.BoolVarP(&historyCommand.Quiet, "quiet", "q", false, "Display the numeric IDs only") + +} - runtime, err := adapter.GetRuntime(c) +func historyCmd(c *cliconfig.HistoryValues) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - format := genHistoryFormat(c.String("format"), c.Bool("quiet")) + format := genHistoryFormat(c.Format, c.Quiet) - args := c.Args() + args := c.InputArgs if len(args) == 0 { return errors.Errorf("an image name must be specified") } @@ -93,9 +85,9 @@ func historyCmd(c *cli.Context) error { return err } opts := historyOptions{ - human: c.BoolT("human"), - noTrunc: c.Bool("no-trunc"), - quiet: c.Bool("quiet"), + human: c.Human, + noTrunc: c.NoTrunc, + quiet: c.Quiet, format: format, } diff --git a/cmd/podman/image.go b/cmd/podman/image.go index 6b451a1ca..14053cb0d 100644 --- a/cmd/podman/image.go +++ b/cmd/podman/image.go @@ -1,37 +1,40 @@ package main import ( - "sort" - - "github.com/urfave/cli" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" ) var ( - imageSubCommands = []cli.Command{ - importCommand, - historyCommand, - imageExistsCommand, - inspectCommand, - lsImagesCommand, - pruneImagesCommand, - pullCommand, - rmImageCommand, - tagCommand, - } imageDescription = "Manage images" - imageCommand = cli.Command{ - Name: "image", - Usage: "Manage images", - Description: imageDescription, - ArgsUsage: "", - Subcommands: getImageSubCommandsSorted(), - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + imageCommand = cliconfig.PodmanCommand{ + Command: &cobra.Command{ + Use: "image", + Short: "Manage images", + Long: imageDescription, + }, } ) -func getImageSubCommandsSorted() []cli.Command { - imageSubCommands = append(imageSubCommands, getImageSubCommands()...) - sort.Sort(commandSortedAlpha{imageSubCommands}) - return imageSubCommands +//imageSubCommands are implemented both in local and remote clients +var imageSubCommands = []*cobra.Command{ + _buildCommand, + _historyCommand, + _imageExistsCommand, + _imagesCommand, + _importCommand, + _inspectCommand, + _loadCommand, + _pruneImagesCommand, + _pullCommand, + _pushCommand, + _rmiCommand, + _saveCommand, + _tagCommand, +} + +func init() { + imageCommand.SetUsageTemplate(UsageTemplate()) + imageCommand.AddCommand(imageSubCommands...) + imageCommand.AddCommand(getImageSubCommands()...) } diff --git a/cmd/podman/imagefilters/filters.go b/cmd/podman/imagefilters/filters.go index 366510202..d01eb7436 100644 --- a/cmd/podman/imagefilters/filters.go +++ b/cmd/podman/imagefilters/filters.go @@ -5,7 +5,7 @@ import ( "strings" "time" - "github.com/containers/libpod/libpod/adapter" + "github.com/containers/libpod/pkg/adapter" "github.com/containers/libpod/pkg/inspect" ) diff --git a/cmd/podman/images.go b/cmd/podman/images.go index 9fdf0a780..6e82195a9 100644 --- a/cmd/podman/images.go +++ b/cmd/podman/images.go @@ -8,15 +8,16 @@ import ( "time" "unicode" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/imagefilters" - "github.com/containers/libpod/libpod/adapter" "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/adapter" "github.com/docker/go-units" "github.com/opencontainers/go-digest" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) type imagesTemplateParams struct { @@ -83,108 +84,80 @@ func (a imagesSortedSize) Less(i, j int) bool { } var ( - imagesFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Show all images (default hides intermediate images)", - }, - cli.BoolFlag{ - Name: "digests", - Usage: "Show digests", - }, - cli.StringSliceFlag{ - Name: "filter, f", - Usage: "Filter output based on conditions provided (default [])", - }, - cli.StringFlag{ - Name: "format", - Usage: "Change the output format to JSON or a Go template", - }, - cli.BoolFlag{ - Name: "noheading, n", - Usage: "Do not print column headings", - }, - cli.BoolFlag{ - Name: "no-trunc, notruncate", - Usage: "Do not truncate output", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Display only image IDs", - }, - cli.StringFlag{ - Name: "sort", - Usage: "Sort by created, id, repository, size, or tag", - Value: "Created", - }, - } - + imagesCommand cliconfig.ImagesValues imagesDescription = "lists locally stored images." - imagesCommand = cli.Command{ - Name: "images", - Usage: "List images in local storage", - Description: imagesDescription, - Flags: sortFlags(imagesFlags), - Action: imagesCmd, - ArgsUsage: "", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, - } - lsImagesCommand = cli.Command{ - Name: "list", - Aliases: []string{"ls"}, - Usage: "List images in local storage", - Description: imagesDescription, - Flags: imagesFlags, - Action: imagesCmd, - ArgsUsage: "", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + + _imagesCommand = &cobra.Command{ + Use: "images", + Short: "List images in local storage", + Long: imagesDescription, + RunE: func(cmd *cobra.Command, args []string) error { + imagesCommand.InputArgs = args + imagesCommand.GlobalFlags = MainGlobalOpts + return imagesCmd(&imagesCommand) + }, + Example: `podman images --format json + podman images --sort repository --format "table {{.ID}} {{.Repository}} {{.Tag}}" + podman images --filter dangling=true`, } ) -func imagesCmd(c *cli.Context) error { +func init() { + imagesCommand.Command = _imagesCommand + imagesCommand.SetUsageTemplate(UsageTemplate()) + flags := imagesCommand.Flags() + flags.BoolVarP(&imagesCommand.All, "all", "a", false, "Show all images (default hides intermediate images)") + flags.BoolVar(&imagesCommand.Digests, "digests", false, "Show digests") + flags.StringSliceVarP(&imagesCommand.Filter, "filter", "f", []string{}, "Filter output based on conditions provided (default [])") + flags.StringVar(&imagesCommand.Format, "format", "", "Change the output format to JSON or a Go template") + flags.BoolVarP(&imagesCommand.Noheading, "noheading", "n", false, "Do not print column headings") + // TODO Need to learn how to deal with second name being a string instead of a char. + // This needs to be "no-trunc, notruncate" + flags.BoolVar(&imagesCommand.NoTrunc, "no-trunc", false, "Do not truncate output") + flags.BoolVarP(&imagesCommand.Quiet, "quiet", "q", false, "Display only image IDs") + flags.StringVar(&imagesCommand.Sort, "sort", "created", "Sort by created, id, repository, size, or tag") + +} + +func imagesCmd(c *cliconfig.ImagesValues) error { var ( filterFuncs []imagefilters.ResultFilter newImage *adapter.ContainerImage ) - if err := validateFlags(c, imagesFlags); err != nil { - return err - } - runtime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "Could not get runtime") } defer runtime.Shutdown(false) - if len(c.Args()) == 1 { - newImage, err = runtime.NewImageFromLocal(c.Args().Get(0)) + if len(c.InputArgs) == 1 { + newImage, err = runtime.NewImageFromLocal(c.InputArgs[0]) if err != nil { return err } } - if len(c.Args()) > 1 { + if len(c.InputArgs) > 1 { return errors.New("'podman images' requires at most 1 argument") } ctx := getContext() - if len(c.StringSlice("filter")) > 0 || newImage != nil { - filterFuncs, err = CreateFilterFuncs(ctx, runtime, c, newImage) + if len(c.Filter) > 0 || newImage != nil { + filterFuncs, err = CreateFilterFuncs(ctx, runtime, c.Filter, newImage) if err != nil { return err } } opts := imagesOptions{ - quiet: c.Bool("quiet"), - noHeading: c.Bool("noheading"), - noTrunc: c.Bool("no-trunc"), - digests: c.Bool("digests"), - format: c.String("format"), - sort: c.String("sort"), - all: c.Bool("all"), + quiet: c.Quiet, + noHeading: c.Noheading, + noTrunc: c.NoTrunc, + digests: c.Digests, + format: c.Format, + sort: c.Sort, + all: c.All, } opts.outputformat = opts.setOutputFormat() @@ -195,7 +168,7 @@ func imagesCmd(c *cli.Context) error { var filteredImages []*adapter.ContainerImage //filter the images - if len(c.StringSlice("filter")) > 0 || newImage != nil { + if len(c.Filter) > 0 || newImage != nil { filteredImages = imagefilters.FilterImages(images, filterFuncs) } else { filteredImages = images @@ -276,8 +249,12 @@ func getImagesTemplateOutput(ctx context.Context, images []*adapter.ContainerIma } // get all specified repo:tag pairs and print them separately + repopairs, err := image.ReposToMap(img.Names()) + if err != nil { + logrus.Errorf("error finding tag/digest for %s", img.ID()) + } outer: - for repo, tags := range image.ReposToMap(img.Names()) { + for repo, tags := range repopairs { for _, tag := range tags { size, err := img.Size(ctx) var sizeStr string @@ -333,6 +310,7 @@ func getImagesJSONOutput(ctx context.Context, images []*adapter.ContainerImage) // generateImagesOutput generates the images based on the format provided func generateImagesOutput(ctx context.Context, images []*adapter.ContainerImage, opts imagesOptions) error { + templateMap := GenImageOutputMap() if len(images) == 0 { return nil } @@ -344,15 +322,17 @@ func generateImagesOutput(ctx context.Context, images []*adapter.ContainerImage, out = formats.JSONStructArray{Output: imagesToGeneric([]imagesTemplateParams{}, imagesOutput)} default: imagesOutput := getImagesTemplateOutput(ctx, images, opts) - out = formats.StdoutTemplateArray{Output: imagesToGeneric(imagesOutput, []imagesJSONParams{}), Template: opts.outputformat, Fields: imagesOutput[0].HeaderMap()} + out = formats.StdoutTemplateArray{Output: imagesToGeneric(imagesOutput, []imagesJSONParams{}), Template: opts.outputformat, Fields: templateMap} } return formats.Writer(out).Out() } -// HeaderMap produces a generic map of "headers" based on a line -// of output -func (i *imagesTemplateParams) HeaderMap() map[string]string { - v := reflect.Indirect(reflect.ValueOf(i)) +// GenImageOutputMap generates the map used for outputting the images header +// without requiring a populated image. This replaces the previous HeaderMap +// call. +func GenImageOutputMap() map[string]string { + io := imagesTemplateParams{} + v := reflect.Indirect(reflect.ValueOf(io)) values := make(map[string]string) for i := 0; i < v.NumField(); i++ { @@ -368,9 +348,9 @@ func (i *imagesTemplateParams) HeaderMap() map[string]string { // CreateFilterFuncs returns an array of filter functions based on the user inputs // and is later used to filter images for output -func CreateFilterFuncs(ctx context.Context, r *adapter.LocalRuntime, c *cli.Context, img *adapter.ContainerImage) ([]imagefilters.ResultFilter, error) { +func CreateFilterFuncs(ctx context.Context, r *adapter.LocalRuntime, filters []string, img *adapter.ContainerImage) ([]imagefilters.ResultFilter, error) { var filterFuncs []imagefilters.ResultFilter - for _, filter := range c.StringSlice("filter") { + for _, filter := range filters { splitFilter := strings.Split(filter, "=") switch splitFilter[0] { case "before": diff --git a/cmd/podman/images_prune.go b/cmd/podman/images_prune.go index 844984bb9..79dcd097c 100644 --- a/cmd/podman/images_prune.go +++ b/cmd/podman/images_prune.go @@ -3,35 +3,40 @@ package main import ( "fmt" - "github.com/containers/libpod/libpod/adapter" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + pruneImagesCommand cliconfig.PruneImagesValues pruneImagesDescription = ` podman image prune Removes all unnamed images from local storage ` - pruneImageFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Remove all unused images, not just dangling ones", + _pruneImagesCommand = &cobra.Command{ + Use: "prune", + Short: "Remove unused images", + Long: pruneImagesDescription, + RunE: func(cmd *cobra.Command, args []string) error { + pruneImagesCommand.InputArgs = args + pruneImagesCommand.GlobalFlags = MainGlobalOpts + return pruneImagesCmd(&pruneImagesCommand) }, } - pruneImagesCommand = cli.Command{ - Name: "prune", - Usage: "Remove unused images", - Description: pruneImagesDescription, - Action: pruneImagesCmd, - OnUsageError: usageErrorHandler, - Flags: pruneImageFlags, - } ) -func pruneImagesCmd(c *cli.Context) error { - runtime, err := adapter.GetRuntime(c) +func init() { + pruneImagesCommand.Command = _pruneImagesCommand + pruneImagesCommand.SetUsageTemplate(UsageTemplate()) + flags := pruneImagesCommand.Flags() + flags.BoolVarP(&pruneImagesCommand.All, "all", "a", false, "Remove all unused images, not just dangling ones") +} + +func pruneImagesCmd(c *cliconfig.PruneImagesValues) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -39,7 +44,7 @@ func pruneImagesCmd(c *cli.Context) error { // Call prune; if any cids are returned, print them and then // return err in case an error also came up - pruneCids, err := runtime.PruneImages(c.Bool("all")) + pruneCids, err := runtime.PruneImages(c.All) if len(pruneCids) > 0 { for _, cid := range pruneCids { fmt.Println(cid) diff --git a/cmd/podman/import.go b/cmd/podman/import.go index 661bd5a65..a64b03d6d 100644 --- a/cmd/podman/import.go +++ b/cmd/podman/import.go @@ -3,47 +3,46 @@ package main import ( "fmt" - "github.com/containers/libpod/libpod/adapter" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - importFlags = []cli.Flag{ - cli.StringSliceFlag{ - Name: "change, c", - Usage: "Apply the following possible instructions to the created image (default []): CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR", - }, - cli.StringFlag{ - Name: "message, m", - Usage: "Set commit message for imported image", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Suppress output", - }, - } + importCommand cliconfig.ImportValues + importDescription = `Create a container image from the contents of the specified tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz). Note remote tar balls can be specified, via web address. Optionally tag the image. You can specify the instructions using the --change option. ` - importCommand = cli.Command{ - Name: "import", - Usage: "Import a tarball to create a filesystem image", - Description: importDescription, - Flags: sortFlags(importFlags), - Action: importCmd, - ArgsUsage: "TARBALL [REFERENCE]", - OnUsageError: usageErrorHandler, + _importCommand = &cobra.Command{ + Use: "import", + Short: "Import a tarball to create a filesystem image", + Long: importDescription, + RunE: func(cmd *cobra.Command, args []string) error { + importCommand.InputArgs = args + importCommand.GlobalFlags = MainGlobalOpts + return importCmd(&importCommand) + }, + Example: `podman import http://example.com/ctr.tar url-image + cat ctr.tar | podman -q import --message "importing the ctr.tar tarball" - image-imported + cat ctr.tar | podman import -`, } ) -func importCmd(c *cli.Context) error { - if err := validateFlags(c, importFlags); err != nil { - return err - } +func init() { + importCommand.Command = _importCommand + importCommand.SetUsageTemplate(UsageTemplate()) + flags := importCommand.Flags() + flags.StringSliceVarP(&importCommand.Change, "change", "c", []string{}, "Apply the following possible instructions to the created image (default []): CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR") + flags.StringVarP(&importCommand.Message, "message", "m", "", "Set commit message for imported image") + flags.BoolVarP(&importCommand.Quiet, "quiet", "q", false, "Suppress output") + +} - runtime, err := adapter.GetRuntime(c) +func importCmd(c *cliconfig.ImportValues) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -54,7 +53,7 @@ func importCmd(c *cli.Context) error { reference string ) - args := c.Args() + args := c.InputArgs switch len(args) { case 0: return errors.Errorf("need to give the path to the tarball, or must specify a tarball of '-' for stdin") @@ -71,7 +70,7 @@ func importCmd(c *cli.Context) error { return err } - quiet := c.Bool("quiet") + quiet := c.Quiet if runtime.Remote { quiet = false } diff --git a/cmd/podman/info.go b/cmd/podman/info.go index 19078cea5..a1473dac9 100644 --- a/cmd/podman/info.go +++ b/cmd/podman/info.go @@ -4,45 +4,47 @@ import ( "fmt" rt "runtime" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/adapter" + "github.com/containers/libpod/pkg/adapter" "github.com/containers/libpod/version" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + infoCommand cliconfig.InfoValues + infoDescription = "Display podman system information" - infoCommand = cli.Command{ - Name: "info", - Usage: infoDescription, - Description: `Information display here pertain to the host, current storage stats, and build of podman. Useful for the user and when reporting issues.`, - Flags: sortFlags(infoFlags), - Action: infoCmd, - ArgsUsage: "", - OnUsageError: usageErrorHandler, - } - infoFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "debug, D", - Usage: "Display additional debug information", - }, - cli.StringFlag{ - Name: "format, f", - Usage: "Change the output format to JSON or a Go template", + _infoCommand = &cobra.Command{ + Use: "info", + Long: infoDescription, + Short: `Display information pertaining to the host, current storage stats, and build of podman. Useful for the user and when reporting issues.`, + RunE: func(cmd *cobra.Command, args []string) error { + infoCommand.InputArgs = args + infoCommand.GlobalFlags = MainGlobalOpts + return infoCmd(&infoCommand) }, + Example: `podman info`, } ) -func infoCmd(c *cli.Context) error { - if err := validateFlags(c, infoFlags); err != nil { - return err - } +func init() { + infoCommand.Command = _infoCommand + infoCommand.SetUsageTemplate(UsageTemplate()) + flags := infoCommand.Flags() + + flags.BoolVarP(&infoCommand.Debug, "debug", "D", false, "Display additional debug information") + flags.StringVarP(&infoCommand.Format, "format", "f", "", "Change the output format to JSON or a Go template") + +} + +func infoCmd(c *cliconfig.InfoValues) error { info := map[string]interface{}{} remoteClientInfo := map[string]interface{}{} - runtime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -59,7 +61,7 @@ func infoCmd(c *cli.Context) error { infoArr = append(infoArr, libpod.InfoData{Type: "client", Data: remoteClientInfo}) } - if !runtime.Remote && c.Bool("debug") { + if !runtime.Remote && c.Debug { debugInfo := debugInfo(c) infoArr = append(infoArr, libpod.InfoData{Type: "debug", Data: debugInfo}) } @@ -69,7 +71,7 @@ func infoCmd(c *cli.Context) error { } var out formats.Writer - infoOutputFormat := c.String("format") + infoOutputFormat := c.Format switch infoOutputFormat { case formats.JSONString: out = formats.JSONStruct{Output: info} @@ -85,11 +87,11 @@ func infoCmd(c *cli.Context) error { } // top-level "debug" info -func debugInfo(c *cli.Context) map[string]interface{} { +func debugInfo(c *cliconfig.InfoValues) map[string]interface{} { info := map[string]interface{}{} info["compiler"] = rt.Compiler info["go version"] = rt.Version() - info["podman version"] = c.App.Version + info["podman version"] = version.Version version, _ := libpod.GetVersion() info["git commit"] = version.GitCommit return info diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index 1346da9fb..46883b31d 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -5,13 +5,14 @@ import ( "encoding/json" "strings" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/libpod/adapter" + "github.com/containers/libpod/pkg/adapter" cc "github.com/containers/libpod/pkg/spec" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) const ( @@ -21,38 +22,39 @@ const ( ) var ( - inspectFlags = []cli.Flag{ - cli.StringFlag{ - Name: "type, t", - Value: inspectAll, - Usage: "Return JSON for specified type, (e.g image, container or task)", - }, - cli.StringFlag{ - Name: "format, f", - Usage: "Change the output format to a Go template", - }, - cli.BoolFlag{ - Name: "size, s", - Usage: "Display total file size if the type is container", - }, - LatestFlag, - } + inspectCommand cliconfig.InspectValues + inspectDescription = "This displays the low-level information on containers and images identified by name or ID. By default, this will render all results in a JSON array. If the container and image have the same name, this will return container JSON for unspecified type." - inspectCommand = cli.Command{ - Name: "inspect", - Usage: "Display the configuration of a container or image", - Description: inspectDescription, - Flags: sortFlags(inspectFlags), - Action: inspectCmd, - ArgsUsage: "CONTAINER-OR-IMAGE [CONTAINER-OR-IMAGE]...", - OnUsageError: usageErrorHandler, + _inspectCommand = &cobra.Command{ + Use: "inspect", + Short: "Display the configuration of a container or image", + Long: inspectDescription, + RunE: func(cmd *cobra.Command, args []string) error { + inspectCommand.InputArgs = args + inspectCommand.GlobalFlags = MainGlobalOpts + return inspectCmd(&inspectCommand) + }, + Example: `podman inspect alpine + podman inspect --format "imageId: {{.Id}} size: {{.Size}}" alpine + podman inspect --format "image: {{.ImageName}} driver: {{.Driver}}" myctr`, } ) -func inspectCmd(c *cli.Context) error { - args := c.Args() - inspectType := c.String("type") - latestContainer := c.Bool("latest") +func init() { + inspectCommand.Command = _inspectCommand + inspectCommand.SetUsageTemplate(UsageTemplate()) + flags := inspectCommand.Flags() + flags.StringVarP(&inspectCommand.TypeObject, "type", "t", inspectAll, "Return JSON for specified type, (e.g image, container or task)") + flags.StringVarP(&inspectCommand.Format, "format", "f", "", "Change the output format to a Go template") + flags.BoolVarP(&inspectCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of if the type is a container") + flags.BoolVarP(&inspectCommand.Size, "size", "s", false, "Display total file size if the type is container") + markFlagHiddenForRemoteClient("latest", flags) +} + +func inspectCmd(c *cliconfig.InspectValues) error { + args := c.InputArgs + inspectType := c.TypeObject + latestContainer := c.Latest if len(args) == 0 && !latestContainer { return errors.Errorf("container or image name must be specified: podman inspect [options [...]] name") } @@ -60,11 +62,8 @@ func inspectCmd(c *cli.Context) error { if len(args) > 0 && latestContainer { return errors.Errorf("you cannot provide additional arguments with --latest") } - if err := validateFlags(c, inspectFlags); err != nil { - return err - } - runtime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } @@ -74,7 +73,7 @@ func inspectCmd(c *cli.Context) error { return errors.Errorf("the only recognized types are %q, %q, and %q", inspectTypeContainer, inspectTypeImage, inspectAll) } - outputFormat := c.String("format") + outputFormat := c.Format if strings.Contains(outputFormat, "{{.Id}}") { outputFormat = strings.Replace(outputFormat, "{{.Id}}", formats.IDString, -1) } @@ -87,7 +86,7 @@ func inspectCmd(c *cli.Context) error { inspectType = inspectTypeContainer } - inspectedObjects, iterateErr := iterateInput(getContext(), c, args, runtime, inspectType) + inspectedObjects, iterateErr := iterateInput(getContext(), c.Size, args, runtime, inspectType) if iterateErr != nil { return iterateErr } @@ -105,7 +104,7 @@ func inspectCmd(c *cli.Context) error { } // func iterateInput iterates the images|containers the user has requested and returns the inspect data and error -func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *adapter.LocalRuntime, inspectType string) ([]interface{}, error) { +func iterateInput(ctx context.Context, size bool, args []string, runtime *adapter.LocalRuntime, inspectType string) ([]interface{}, error) { var ( data interface{} inspectedItems []interface{} @@ -120,7 +119,7 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *a inspectError = errors.Wrapf(err, "error looking up container %q", input) break } - libpodInspectData, err := ctr.Inspect(c.Bool("size")) + libpodInspectData, err := ctr.Inspect(size) if err != nil { inspectError = errors.Wrapf(err, "error getting libpod container inspect data %s", ctr.ID()) break @@ -160,7 +159,7 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *a break } } else { - libpodInspectData, err := ctr.Inspect(c.Bool("size")) + libpodInspectData, err := ctr.Inspect(size) if err != nil { inspectError = errors.Wrapf(err, "error getting libpod container inspect data %s", ctr.ID()) break diff --git a/cmd/podman/kill.go b/cmd/podman/kill.go index cfe4b4218..eb72d53e7 100644 --- a/cmd/podman/kill.go +++ b/cmd/podman/kill.go @@ -4,6 +4,7 @@ import ( "fmt" "syscall" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" @@ -11,68 +12,68 @@ import ( "github.com/docker/docker/pkg/signal" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - killFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Signal all running containers", + killCommand cliconfig.KillValues + + killDescription = "The main process inside each container specified will be sent SIGKILL, or any signal specified with option --signal." + _killCommand = &cobra.Command{ + Use: "kill", + Short: "Kill one or more running containers with a specific signal", + Long: killDescription, + RunE: func(cmd *cobra.Command, args []string) error { + killCommand.InputArgs = args + killCommand.GlobalFlags = MainGlobalOpts + return killCmd(&killCommand) }, - cli.StringFlag{ - Name: "signal, s", - Usage: "Signal to send to the container", - Value: "KILL", + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, false) }, - LatestFlag, - } - killDescription = "The main process inside each container specified will be sent SIGKILL, or any signal specified with option --signal." - killCommand = cli.Command{ - Name: "kill", - Usage: "Kill one or more running containers with a specific signal", - Description: killDescription, - Flags: sortFlags(killFlags), - Action: killCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + Example: `podman kill mywebserver + podman kill 860a4b23 + podman kill --signal TERM ctrID`, } ) +func init() { + killCommand.Command = _killCommand + killCommand.SetUsageTemplate(UsageTemplate()) + flags := killCommand.Flags() + + flags.BoolVarP(&killCommand.All, "all", "a", false, "Signal all running containers") + flags.StringVarP(&killCommand.Signal, "signal", "s", "KILL", "Signal to send to the container") + flags.BoolVarP(&killCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + + markFlagHiddenForRemoteClient("latest", flags) +} + // killCmd kills one or more containers with a signal -func killCmd(c *cli.Context) error { +func killCmd(c *cliconfig.KillValues) error { var ( killFuncs []shared.ParallelWorkerInput killSignal uint = uint(syscall.SIGTERM) ) - if err := checkAllAndLatest(c); err != nil { - return err - } - - if err := validateFlags(c, killFlags); err != nil { - return err - } - rootless.SetSkipStorageSetup(true) - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if c.String("signal") != "" { + if c.Signal != "" { // Check if the signalString provided by the user is valid // Invalid signals will return err - sysSignal, err := signal.ParseSignal(c.String("signal")) + sysSignal, err := signal.ParseSignal(c.Signal) if err != nil { return err } killSignal = uint(sysSignal) } - containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + containers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStateRunning, "running") if err != nil { if len(containers) == 0 { return err @@ -94,7 +95,7 @@ func killCmd(c *cli.Context) error { maxWorkers := shared.Parallelize("kill") if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalInt("max-workers") + maxWorkers = c.GlobalFlags.MaxWorks } logrus.Debugf("Setting maximum workers to %d", maxWorkers) diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index dca2f5022..880b281bd 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -1,15 +1,24 @@ package libpodruntime import ( + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" - "github.com/urfave/cli" ) +// GetRuntimeRenumber gets a libpod runtime that will perform a lock renumber +func GetRuntimeRenumber(c *cliconfig.PodmanCommand) (*libpod.Runtime, error) { + return getRuntime(c, true) +} + // GetRuntime generates a new libpod runtime configured by command line options -func GetRuntime(c *cli.Context) (*libpod.Runtime, error) { +func GetRuntime(c *cliconfig.PodmanCommand) (*libpod.Runtime, error) { + return getRuntime(c, false) +} + +func getRuntime(c *cliconfig.PodmanCommand, renumber bool) (*libpod.Runtime, error) { options := []libpod.RuntimeOption{} storageOpts, volumePath, err := util.GetDefaultStoreOptions() @@ -17,29 +26,39 @@ func GetRuntime(c *cli.Context) (*libpod.Runtime, error) { return nil, err } - if c.IsSet("uidmap") || c.IsSet("gidmap") || c.IsSet("subuidmap") || c.IsSet("subgidmap") { - mappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidmap"), c.String("subgidmap")) + uidmapFlag := c.Flags().Lookup("uidmap") + gidmapFlag := c.Flags().Lookup("gidmap") + subuidname := c.Flags().Lookup("subuidname") + subgidname := c.Flags().Lookup("subgidname") + if (uidmapFlag != nil && gidmapFlag != nil && subuidname != nil && subgidname != nil) && + (uidmapFlag.Changed || gidmapFlag.Changed || subuidname.Changed || subgidname.Changed) { + uidmapVal, _ := c.Flags().GetStringSlice("uidmap") + gidmapVal, _ := c.Flags().GetStringSlice("gidmap") + subuidVal, _ := c.Flags().GetString("subuidname") + subgidVal, _ := c.Flags().GetString("subgidname") + mappings, err := util.ParseIDMapping(uidmapVal, gidmapVal, subuidVal, subgidVal) if err != nil { return nil, err } storageOpts.UIDMap = mappings.UIDMap storageOpts.GIDMap = mappings.GIDMap + } - if c.GlobalIsSet("root") { - storageOpts.GraphRoot = c.GlobalString("root") + if c.Flags().Changed("root") { + storageOpts.GraphRoot = c.GlobalFlags.Root } - if c.GlobalIsSet("runroot") { - storageOpts.RunRoot = c.GlobalString("runroot") + if c.Flags().Changed("runroot") { + storageOpts.RunRoot = c.GlobalFlags.Runroot } if len(storageOpts.RunRoot) > 50 { return nil, errors.New("the specified runroot is longer than 50 characters") } - if c.GlobalIsSet("storage-driver") { - storageOpts.GraphDriverName = c.GlobalString("storage-driver") + if c.Flags().Changed("storage-driver") { + storageOpts.GraphDriverName = c.GlobalFlags.StorageDriver } - if c.GlobalIsSet("storage-opt") { - storageOpts.GraphDriverOptions = c.GlobalStringSlice("storage-opt") + if len(c.GlobalFlags.StorageOpts) > 0 { + storageOpts.GraphDriverOptions = c.GlobalFlags.StorageOpts } options = append(options, libpod.WithStorageConfig(storageOpts)) @@ -47,23 +66,23 @@ func GetRuntime(c *cli.Context) (*libpod.Runtime, error) { // TODO CLI flags for image config? // TODO CLI flag for signature policy? - if c.GlobalIsSet("namespace") { - options = append(options, libpod.WithNamespace(c.GlobalString("namespace"))) + if len(c.GlobalFlags.Namespace) > 0 { + options = append(options, libpod.WithNamespace(c.GlobalFlags.Namespace)) } - if c.GlobalIsSet("runtime") { - options = append(options, libpod.WithOCIRuntime(c.GlobalString("runtime"))) + if c.Flags().Changed("runtime") { + options = append(options, libpod.WithOCIRuntime(c.GlobalFlags.Runtime)) } - if c.GlobalIsSet("conmon") { - options = append(options, libpod.WithConmonPath(c.GlobalString("conmon"))) + if c.Flags().Changed("conmon") { + options = append(options, libpod.WithConmonPath(c.GlobalFlags.ConmonPath)) } - if c.GlobalIsSet("tmpdir") { - options = append(options, libpod.WithTmpDir(c.GlobalString("tmpdir"))) + if c.Flags().Changed("tmpdir") { + options = append(options, libpod.WithTmpDir(c.GlobalFlags.TmpDir)) } - if c.GlobalIsSet("cgroup-manager") { - options = append(options, libpod.WithCgroupManager(c.GlobalString("cgroup-manager"))) + if c.Flags().Changed("cgroup-manager") { + options = append(options, libpod.WithCgroupManager(c.GlobalFlags.CGroupManager)) } else { if rootless.IsRootless() { options = append(options, libpod.WithCgroupManager("cgroupfs")) @@ -73,29 +92,37 @@ func GetRuntime(c *cli.Context) (*libpod.Runtime, error) { // TODO flag to set libpod static dir? // TODO flag to set libpod tmp dir? - if c.GlobalIsSet("cni-config-dir") { - options = append(options, libpod.WithCNIConfigDir(c.GlobalString("cni-config-dir"))) + if c.Flags().Changed("cni-config-dir") { + options = append(options, libpod.WithCNIConfigDir(c.GlobalFlags.CniConfigDir)) } - if c.GlobalIsSet("default-mounts-file") { - options = append(options, libpod.WithDefaultMountsFile(c.GlobalString("default-mounts-file"))) + if c.Flags().Changed("default-mounts-file") { + options = append(options, libpod.WithDefaultMountsFile(c.GlobalFlags.DefaultMountsFile)) } - if c.GlobalIsSet("hooks-dir") { - options = append(options, libpod.WithHooksDir(c.GlobalStringSlice("hooks-dir")...)) + if c.Flags().Changed("hooks-dir") { + options = append(options, libpod.WithHooksDir(c.GlobalFlags.HooksDir...)) } // TODO flag to set CNI plugins dir? + // TODO I dont think these belong here? + // Will follow up with a different PR to address + // // Pod create options - if c.IsSet("infra-image") { - options = append(options, libpod.WithDefaultInfraImage(c.String("infra-image"))) + + infraImageFlag := c.Flags().Lookup("infra-image") + if infraImageFlag != nil && infraImageFlag.Changed { + infraImage, _ := c.Flags().GetString("infra-image") + options = append(options, libpod.WithDefaultInfraImage(infraImage)) } - if c.IsSet("infra-command") { - options = append(options, libpod.WithDefaultInfraCommand(c.String("infra-command"))) + infraCommandFlag := c.Flags().Lookup("infra-command") + if infraCommandFlag != nil && infraImageFlag.Changed { + infraCommand, _ := c.Flags().GetString("infra-command") + options = append(options, libpod.WithDefaultInfraCommand(infraCommand)) } options = append(options, libpod.WithVolumePath(volumePath)) - if c.IsSet("config") { - return libpod.NewRuntimeFromConfig(c.String("config"), options...) + if c.Flags().Changed("config") { + return libpod.NewRuntimeFromConfig(c.GlobalFlags.Config, options...) } return libpod.NewRuntime(options...) } diff --git a/cmd/podman/load.go b/cmd/podman/load.go index f39ee4487..272cd78d2 100644 --- a/cmd/podman/load.go +++ b/cmd/podman/load.go @@ -6,48 +6,43 @@ import ( "io/ioutil" "os" - "github.com/containers/image/directory" - dockerarchive "github.com/containers/image/docker/archive" - ociarchive "github.com/containers/image/oci/archive" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - loadFlags = []cli.Flag{ - cli.StringFlag{ - Name: "input, i", - Usage: "Read from archive file, default is STDIN", - Value: "/dev/stdin", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Suppress the output", - }, - cli.StringFlag{ - Name: "signature-policy", - Usage: "`pathname` of signature policy file (not usually used)", - }, - } + loadCommand cliconfig.LoadValues + loadDescription = "Loads the image from docker-archive stored on the local machine." - loadCommand = cli.Command{ - Name: "load", - Usage: "Load an image from docker archive", - Description: loadDescription, - Flags: sortFlags(loadFlags), - Action: loadCmd, - ArgsUsage: "", - OnUsageError: usageErrorHandler, + _loadCommand = &cobra.Command{ + Use: "load", + Short: "Load an image from docker archive", + Long: loadDescription, + RunE: func(cmd *cobra.Command, args []string) error { + loadCommand.InputArgs = args + loadCommand.GlobalFlags = MainGlobalOpts + return loadCmd(&loadCommand) + }, } ) +func init() { + loadCommand.Command = _loadCommand + loadCommand.SetUsageTemplate(UsageTemplate()) + flags := loadCommand.Flags() + flags.StringVarP(&loadCommand.Input, "input", "i", "/dev/stdin", "Read from archive file, default is STDIN") + flags.BoolVarP(&loadCommand.Quiet, "quiet", "q", false, "Suppress the output") + flags.StringVar(&loadCommand.SignaturePolicy, "signature-policy", "", "Pathname of signature policy file (not usually used)") + +} + // loadCmd gets the image/file to be loaded from the command line // and calls loadImage to load the image to containers-storage -func loadCmd(c *cli.Context) error { +func loadCmd(c *cliconfig.LoadValues) error { - args := c.Args() + args := c.InputArgs var imageName string if len(args) == 1 { @@ -56,18 +51,17 @@ func loadCmd(c *cli.Context) error { if len(args) > 1 { return errors.New("too many arguments. Requires exactly 1") } - if err := validateFlags(c, loadFlags); err != nil { - return err - } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - input := c.String("input") - + input := c.Input + if runtime.Remote && len(input) == 0 { + return errors.New("the remote client requires you to load via -i and a tarball") + } if input == "/dev/stdin" { fi, err := os.Stdin.Stat() if err != nil { @@ -100,46 +94,10 @@ func loadCmd(c *cli.Context) error { return err } - var writer io.Writer - if !c.Bool("quiet") { - writer = os.Stderr - } - - ctx := getContext() - - var newImages []*image.Image - src, err := dockerarchive.ParseReference(input) // FIXME? We should add dockerarchive.NewReference() - if err == nil { - newImages, err = runtime.ImageRuntime().LoadFromArchiveReference(ctx, src, c.String("signature-policy"), writer) - } + names, err := runtime.LoadImage(getContext(), imageName, c) if err != nil { - // generate full src name with specified image:tag - src, err := ociarchive.NewReference(input, imageName) // imageName may be "" - if err == nil { - newImages, err = runtime.ImageRuntime().LoadFromArchiveReference(ctx, src, c.String("signature-policy"), writer) - } - if err != nil { - src, err := directory.NewReference(input) - if err == nil { - newImages, err = runtime.ImageRuntime().LoadFromArchiveReference(ctx, src, c.String("signature-policy"), writer) - } - if err != nil { - return errors.Wrapf(err, "error pulling %q", input) - } - } + return err } - fmt.Println("Loaded image(s): " + getImageNames(newImages)) + fmt.Println("Loaded image(s): " + names) return nil } - -func getImageNames(images []*image.Image) string { - var names string - for i := range images { - if i == 0 { - names = images[i].InputName - } else { - names += ", " + images[i].InputName - } - } - return names -} diff --git a/cmd/podman/login.go b/cmd/podman/login.go index fc7b39ed8..b02a4b3f9 100644 --- a/cmd/podman/login.go +++ b/cmd/podman/login.go @@ -9,55 +9,51 @@ import ( "github.com/containers/image/docker" "github.com/containers/image/pkg/docker/config" "github.com/containers/image/types" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod/common" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" "golang.org/x/crypto/ssh/terminal" ) var ( - loginFlags = []cli.Flag{ - cli.StringFlag{ - Name: "password, p", - Usage: "Password for registry", - }, - cli.StringFlag{ - Name: "username, u", - Usage: "Username for registry", - }, - cli.StringFlag{ - Name: "authfile", - Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override. ", - }, - cli.StringFlag{ - Name: "cert-dir", - Usage: "Pathname of a directory containing TLS certificates and keys used to connect to the registry", - }, - cli.BoolTFlag{ - Name: "get-login", - Usage: "Return the current login user for the registry", - }, - cli.BoolTFlag{ - Name: "tls-verify", - Usage: "Require HTTPS and verify certificates when contacting registries (default: true)", - }, - } + loginCommand cliconfig.LoginValues + loginDescription = "Login to a container registry on a specified server." - loginCommand = cli.Command{ - Name: "login", - Usage: "Login to a container registry", - Description: loginDescription, - Flags: sortFlags(loginFlags), - Action: loginCmd, - ArgsUsage: "REGISTRY", - OnUsageError: usageErrorHandler, + _loginCommand = &cobra.Command{ + Use: "login", + Short: "Login to a container registry", + Long: loginDescription, + RunE: func(cmd *cobra.Command, args []string) error { + loginCommand.InputArgs = args + loginCommand.GlobalFlags = MainGlobalOpts + return loginCmd(&loginCommand) + }, + Example: `podman login -u testuser -p testpassword localhost:5000 + podman login --authfile authdir/myauths.json quay.io + podman login -u testuser -p testpassword localhost:5000`, } ) +func init() { + loginCommand.Command = _loginCommand + loginCommand.SetUsageTemplate(UsageTemplate()) + flags := loginCommand.Flags() + + flags.StringVar(&loginCommand.Authfile, "authfile", "", "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&loginCommand.CertDir, "cert-dir", "", "Pathname of a directory containing TLS certificates and keys used to connect to the registry") + flags.BoolVar(&loginCommand.GetLogin, "get-login", true, "Return the current login user for the registry") + flags.StringVarP(&loginCommand.Password, "password", "p", "", "Password for registry") + flags.BoolVar(&loginCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") + flags.StringVarP(&loginCommand.Username, "username", "u", "", "Username for registry") + flags.BoolVar(&loginCommand.StdinPassword, "password-stdin", false, "Take the password from stdin") + +} + // loginCmd uses the authentication package to store a user's authenticated credentials // in an auth.json file for future use -func loginCmd(c *cli.Context) error { - args := c.Args() +func loginCmd(c *cliconfig.LoginValues) error { + args := c.InputArgs if len(args) > 1 { return errors.Errorf("too many arguments, login takes only 1 argument") } @@ -65,17 +61,17 @@ func loginCmd(c *cli.Context) error { return errors.Errorf("please specify a registry to login to") } server := registryFromFullName(scrubServer(args[0])) - authfile := getAuthFile(c.String("authfile")) + authfile := getAuthFile(c.Authfile) sc := common.GetSystemContext("", authfile, false) - if c.IsSet("tls-verify") { - sc.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify")) + if c.Flag("tls-verify").Changed { + sc.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) } - if c.String("cert-dir") != "" { - sc.DockerCertPath = c.String("cert-dir") + if c.CertDir != "" { + sc.DockerCertPath = c.CertDir } - if c.IsSet("get-login") { + if c.Flag("get-login").Changed { user, err := config.GetUserLoggedIn(sc, server) if err != nil { @@ -97,8 +93,26 @@ func loginCmd(c *cli.Context) error { } ctx := getContext() + + password := c.Password + + if c.Flag("password-stdin").Changed { + var stdinPasswordStrBuilder strings.Builder + if c.Password != "" { + return errors.Errorf("Can't specify both --password-stdin and --password") + } + if c.Username == "" { + return errors.Errorf("Must provide --username with --password-stdin") + } + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + fmt.Fprint(&stdinPasswordStrBuilder, scanner.Text()) + } + password = stdinPasswordStrBuilder.String() + } + // If no username and no password is specified, try to use existing ones. - if c.String("username") == "" && c.String("password") == "" { + if c.Username == "" && password == "" && userFromAuthFile != "" && passFromAuthFile != "" { fmt.Println("Authenticating with existing credentials...") if err := docker.CheckAuth(ctx, sc, userFromAuthFile, passFromAuthFile, server); err == nil { fmt.Println("Existing credentials are valid. Already logged in to", server) @@ -107,7 +121,7 @@ func loginCmd(c *cli.Context) error { fmt.Println("Existing credentials are invalid, please enter valid username and password") } - username, password, err := getUserAndPass(c.String("username"), c.String("password"), userFromAuthFile) + username, password, err := getUserAndPass(c.Username, password, userFromAuthFile) if err != nil { return errors.Wrapf(err, "error getting username and password") } diff --git a/cmd/podman/logout.go b/cmd/podman/logout.go index 3cdb606b5..4108887f0 100644 --- a/cmd/podman/logout.go +++ b/cmd/podman/logout.go @@ -4,53 +4,58 @@ import ( "fmt" "github.com/containers/image/pkg/docker/config" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod/common" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - logoutFlags = []cli.Flag{ - cli.StringFlag{ - Name: "authfile", - Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override. ", - }, - cli.BoolFlag{ - Name: "all, a", - Usage: "Remove the cached credentials for all registries in the auth file", - }, - } + logoutCommand cliconfig.LogoutValues logoutDescription = "Remove the cached username and password for the registry." - logoutCommand = cli.Command{ - Name: "logout", - Usage: "Logout of a container registry", - Description: logoutDescription, - Flags: sortFlags(logoutFlags), - Action: logoutCmd, - ArgsUsage: "REGISTRY", - OnUsageError: usageErrorHandler, + _logoutCommand = &cobra.Command{ + Use: "logout", + Short: "Logout of a container registry", + Long: logoutDescription, + RunE: func(cmd *cobra.Command, args []string) error { + logoutCommand.InputArgs = args + logoutCommand.GlobalFlags = MainGlobalOpts + return logoutCmd(&logoutCommand) + }, + Example: `podman logout docker.io + podman logout --authfile authdir/myauths.json docker.io + podman logout --all`, } ) +func init() { + logoutCommand.Command = _logoutCommand + logoutCommand.SetUsageTemplate(UsageTemplate()) + flags := logoutCommand.Flags() + flags.BoolVarP(&logoutCommand.All, "all", "a", false, "Remove the cached credentials for all registries in the auth file") + flags.StringVar(&logoutCommand.Authfile, "authfile", "", "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override") + +} + // logoutCmd uses the authentication package to remove the authenticated of a registry // stored in the auth.json file -func logoutCmd(c *cli.Context) error { - args := c.Args() +func logoutCmd(c *cliconfig.LogoutValues) error { + args := c.InputArgs if len(args) > 1 { return errors.Errorf("too many arguments, logout takes at most 1 argument") } - if len(args) == 0 && !c.IsSet("all") { + if len(args) == 0 && !c.All { return errors.Errorf("registry must be given") } var server string if len(args) == 1 { server = scrubServer(args[0]) } - authfile := getAuthFile(c.String("authfile")) + authfile := getAuthFile(c.Authfile) sc := common.GetSystemContext("", authfile, false) - if c.Bool("all") { + if c.All { if err := config.RemoveAllAuthentication(sc); err != nil { return err } diff --git a/cmd/podman/logs.go b/cmd/podman/logs.go index 75947c34e..97d835d8f 100644 --- a/cmd/podman/logs.go +++ b/cmd/podman/logs.go @@ -4,91 +4,85 @@ import ( "os" "time" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/logs" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - logsFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "details", - Usage: "Show extra details provided to the logs", - Hidden: true, - }, - cli.BoolFlag{ - Name: "follow, f", - Usage: "Follow log output. The default is false", - }, - cli.StringFlag{ - Name: "since", - Usage: "Show logs since TIMESTAMP", - }, - cli.Uint64Flag{ - Name: "tail", - Usage: "Output the specified number of LINES at the end of the logs. Defaults to 0, which prints all lines", - }, - cli.BoolFlag{ - Name: "timestamps, t", - Usage: "Output the timestamps in the log", - }, - LatestFlag, - } + logsCommand cliconfig.LogsValues logsDescription = "The podman logs command batch-retrieves whatever logs are present for a container at the time of execution. This does not guarantee execution" + "order when combined with podman run (i.e. your run may not have generated any logs at the time you execute podman logs" - logsCommand = cli.Command{ - Name: "logs", - Usage: "Fetch the logs of a container", - Description: logsDescription, - Flags: sortFlags(logsFlags), - Action: logsCmd, - ArgsUsage: "CONTAINER", - SkipArgReorder: true, - OnUsageError: usageErrorHandler, - UseShortOptionHandling: true, + _logsCommand = &cobra.Command{ + Use: "logs", + Short: "Fetch the logs of a container", + Long: logsDescription, + RunE: func(cmd *cobra.Command, args []string) error { + logsCommand.InputArgs = args + logsCommand.GlobalFlags = MainGlobalOpts + return logsCmd(&logsCommand) + }, + Example: `podman logs ctrID + podman logs --tail 2 mywebserver + podman logs --follow=true --since 10m ctrID`, } ) -func logsCmd(c *cli.Context) error { +func init() { + logsCommand.Command = _logsCommand + logsCommand.SetUsageTemplate(UsageTemplate()) + flags := logsCommand.Flags() + flags.BoolVar(&logsCommand.Details, "details", false, "Show extra details provided to the logs") + flags.BoolVarP(&logsCommand.Follow, "follow", "f", false, "Follow log output. The default is false") + flags.BoolVarP(&waitCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.StringVar(&logsCommand.Since, "since", "", "Show logs since TIMESTAMP") + flags.Uint64Var(&logsCommand.Tail, "tail", 0, "Output the specified number of LINES at the end of the logs. Defaults to 0, which prints all lines") + flags.BoolVarP(&logsCommand.Timestamps, "timestamps", "t", false, "Output the timestamps in the log") + flags.MarkHidden("details") + + flags.SetInterspersed(false) + + markFlagHiddenForRemoteClient("latest", flags) +} + +func logsCmd(c *cliconfig.LogsValues) error { var ctr *libpod.Container var err error - if err := validateFlags(c, logsFlags); err != nil { - return err - } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - args := c.Args() - if len(args) != 1 && !c.Bool("latest") { + args := c.InputArgs + if len(args) != 1 && !c.Latest { return errors.Errorf("'podman logs' requires exactly one container name/ID") } sinceTime := time.Time{} - if c.IsSet("since") { + if c.Flag("since").Changed { // parse time, error out if something is wrong - since, err := parseInputTime(c.String("since")) + since, err := parseInputTime(c.Since) if err != nil { - return errors.Wrapf(err, "could not parse time: %q", c.String("since")) + return errors.Wrapf(err, "could not parse time: %q", c.Since) } sinceTime = since } opts := &logs.LogOptions{ - Details: c.Bool("details"), - Follow: c.Bool("follow"), + Details: c.Details, + Follow: c.Follow, Since: sinceTime, - Tail: c.Uint64("tail"), - Timestamps: c.Bool("timestamps"), + Tail: c.Tail, + Timestamps: c.Timestamps, } - if c.Bool("latest") { + if c.Latest { ctr, err = runtime.GetLatestContainer() } else { ctr, err = runtime.LookupContainer(args[0]) diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 8c08b2bfb..19bdb40d6 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -1,230 +1,228 @@ package main import ( - "fmt" + "context" + "io" "log/syslog" "os" - "os/exec" "runtime/pprof" - "sort" + "strings" "syscall" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod" _ "github.com/containers/libpod/pkg/hooks/0.1.0" "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/tracing" "github.com/containers/libpod/version" "github.com/containers/storage/pkg/reexec" + "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" lsyslog "github.com/sirupsen/logrus/hooks/syslog" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) // This is populated by the Makefile from the VERSION file // in the repository var ( exitCode = 125 + Ctx context.Context + span opentracing.Span + closer io.Closer ) -var cmdsNotRequiringRootless = map[string]bool{ - "help": true, - "version": true, - "create": true, - "exec": true, - "export": true, - // `info` must be executed in an user namespace. - // If this change, please also update libpod.refreshRootless() - "login": true, - "logout": true, - "mount": true, - "kill": true, - "pause": true, - "restart": true, - "run": true, - "unpause": true, - "search": true, - "stats": true, - "stop": true, - "top": true, +// Commands that the remote and local client have +// implemented. +var mainCommands = []*cobra.Command{ + _buildCommand, + _exportCommand, + _historyCommand, + _imagesCommand, + _importCommand, + _infoCommand, + _inspectCommand, + _killCommand, + _loadCommand, + podCommand.Command, + _pullCommand, + _pushCommand, + _rmiCommand, + _saveCommand, + _tagCommand, + _versionCommand, + imageCommand.Command, + systemCommand.Command, } -type commandSorted []cli.Command - -func (a commandSorted) Len() int { return len(a) } -func (a commandSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -type commandSortedAlpha struct{ commandSorted } - -func (a commandSortedAlpha) Less(i, j int) bool { - return a.commandSorted[i].Name < a.commandSorted[j].Name +var cmdsNotRequiringRootless = map[*cobra.Command]bool{ + _versionCommand: true, + _createCommand: true, + _execCommand: true, + _cpCommand: true, + _exportCommand: true, + //// `info` must be executed in an user namespace. + //// If this change, please also update libpod.refreshRootless() + _loginCommand: true, + _logoutCommand: true, + _mountCommand: true, + _killCommand: true, + _pauseCommand: true, + _restartCommand: true, + _runCommand: true, + _unpauseCommand: true, + _searchCommand: true, + _statsCommand: true, + _stopCommand: true, + _topCommand: true, } -type flagSorted []cli.Flag - -func (a flagSorted) Len() int { return len(a) } -func (a flagSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -type flagSortedAlpha struct{ flagSorted } - -func (a flagSortedAlpha) Less(i, j int) bool { - return a.flagSorted[i].GetName() < a.flagSorted[j].GetName() +var rootCmd = &cobra.Command{ + Use: "podman", + Long: "manage pods and images", + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return before(cmd, args) + }, + PersistentPostRunE: func(cmd *cobra.Command, args []string) error { + return after(cmd, args) + }, + SilenceUsage: true, + SilenceErrors: true, } -func main() { - debug := false - cpuProfile := false - - if reexec.Init() { - return - } +var MainGlobalOpts cliconfig.MainFlags + +func init() { + cobra.OnInitialize(initConfig) + rootCmd.TraverseChildren = true + rootCmd.Version = version.Version + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.CGroupManager, "cgroup-manager", "", "Cgroup manager to use (cgroupfs or systemd, default systemd)") + // -c is deprecated due to conflict with -c on subcommands + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.CpuProfile, "cpu-profile", "", "Path for the cpu profiling results") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.Config, "config", "", "Path of a libpod config file detailing container server configuration options") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.ConmonPath, "conmon", "", "Path of the conmon binary") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.CniConfigDir, "cni-config-dir", "", "Path of the configuration directory for CNI networks") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.DefaultMountsFile, "default-mounts-file", "", "Path to default mounts file") + rootCmd.PersistentFlags().MarkHidden("defaults-mount-file") + rootCmd.PersistentFlags().StringSliceVar(&MainGlobalOpts.HooksDir, "hooks-dir", []string{}, "Set the OCI hooks directory path (may be set multiple times)") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.LogLevel, "log-level", "error", "Log messages above specified level: debug, info, warn, error (default), fatal or panic") + rootCmd.PersistentFlags().IntVar(&MainGlobalOpts.MaxWorks, "max-workers", 0, "The maximum number of workers for parallel operations") + rootCmd.PersistentFlags().MarkHidden("max-workers") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.Namespace, "namespace", "", "Set the libpod namespace, used to create separate views of the containers and pods on the system") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.Root, "root", "", "Path to the root directory in which data, including images, is stored") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.Runroot, "runroot", "", "Path to the 'run directory' where all state information is stored") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.Runtime, "runtime", "", "Path to the OCI-compatible binary used to run containers, default is /usr/bin/runc") + // -s is depracated due to conflict with -s on subcommands + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.StorageDriver, "storage-driver", "", "Select which storage driver is used to manage storage of images and containers (default is overlay)") + rootCmd.PersistentFlags().StringSliceVar(&MainGlobalOpts.StorageOpts, "storage-opt", []string{}, "Used to pass an option to the storage driver") + rootCmd.PersistentFlags().BoolVar(&MainGlobalOpts.Syslog, "syslog", false, "Output logging information to syslog as well as the console") + + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.TmpDir, "tmpdir", "", "Path to the tmp directory") + rootCmd.PersistentFlags().BoolVar(&MainGlobalOpts.Trace, "trace", false, "enable opentracing output") + rootCmd.AddCommand(mainCommands...) + rootCmd.AddCommand(getMainCommands()...) - app := cli.NewApp() - app.Name = "podman" - app.Usage = "manage pods and images" - app.OnUsageError = usageErrorHandler - app.CommandNotFound = commandNotFoundHandler - - app.Version = version.Version - - app.Commands = []cli.Command{ - containerCommand, - exportCommand, - historyCommand, - imageCommand, - imagesCommand, - importCommand, - infoCommand, - inspectCommand, - pullCommand, - rmiCommand, - systemCommand, - tagCommand, - versionCommand, - } - - app.Commands = append(app.Commands, getAppCommands()...) - sort.Sort(commandSortedAlpha{app.Commands}) +} +func initConfig() { + // we can do more stuff in here. +} - if varlinkCommand != nil { - app.Commands = append(app.Commands, *varlinkCommand) +func before(cmd *cobra.Command, args []string) error { + if err := libpod.SetXdgRuntimeDir(""); err != nil { + logrus.Errorf(err.Error()) + os.Exit(1) } - - app.Before = func(c *cli.Context) error { - if err := libpod.SetXdgRuntimeDir(""); err != nil { - logrus.Errorf(err.Error()) - os.Exit(1) - } - args := c.Args() - if args.Present() && rootless.IsRootless() { - if _, notRequireRootless := cmdsNotRequiringRootless[args.First()]; !notRequireRootless { - became, ret, err := rootless.BecomeRootInUserNS() - if err != nil { - logrus.Errorf(err.Error()) - os.Exit(1) - } - if became { - os.Exit(ret) - } + if rootless.IsRootless() { + notRequireRootless := cmdsNotRequiringRootless[cmd] + if !notRequireRootless && !strings.HasPrefix(cmd.Use, "help") { + became, ret, err := rootless.BecomeRootInUserNS() + if err != nil { + logrus.Errorf(err.Error()) + os.Exit(1) } - } - if c.GlobalBool("syslog") { - hook, err := lsyslog.NewSyslogHook("", "", syslog.LOG_INFO, "") - if err == nil { - logrus.AddHook(hook) + if became { + os.Exit(ret) } } - logLevel := c.GlobalString("log-level") - if logLevel != "" { - level, err := logrus.ParseLevel(logLevel) - if err != nil { - return err - } + } - logrus.SetLevel(level) + if MainGlobalOpts.Syslog { + hook, err := lsyslog.NewSyslogHook("", "", syslog.LOG_INFO, "") + if err == nil { + logrus.AddHook(hook) } + } - rlimits := new(syscall.Rlimit) - rlimits.Cur = 1048576 - rlimits.Max = 1048576 - if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { - if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { - return errors.Wrapf(err, "error getting rlimits") - } - rlimits.Cur = rlimits.Max - if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { - return errors.Wrapf(err, "error setting new rlimits") - } + // Set log level + level, err := logrus.ParseLevel(MainGlobalOpts.LogLevel) + if err != nil { + return err + } + logrus.SetLevel(level) + + rlimits := new(syscall.Rlimit) + rlimits.Cur = 1048576 + rlimits.Max = 1048576 + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { + return errors.Wrapf(err, "error getting rlimits") } - - if rootless.IsRootless() { - logrus.Info("running as rootless") + rlimits.Cur = rlimits.Max + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { + return errors.Wrapf(err, "error setting new rlimits") } + } - // Be sure we can create directories with 0755 mode. - syscall.Umask(0022) + if rootless.IsRootless() { + logrus.Info("running as rootless") + } - if logLevel == "debug" { - debug = true + // Be sure we can create directories with 0755 mode. + syscall.Umask(0022) + if cmd.Flag("cpu-profile").Changed { + f, err := os.Create(MainGlobalOpts.CpuProfile) + if err != nil { + return errors.Wrapf(err, "unable to create cpu profiling file %s", + MainGlobalOpts.CpuProfile) } - if c.GlobalIsSet("cpu-profile") { - f, err := os.Create(c.GlobalString("cpu-profile")) - if err != nil { - return errors.Wrapf(err, "unable to create cpu profiling file %s", - c.GlobalString("cpu-profile")) - } - cpuProfile = true - pprof.StartCPUProfile(f) - } - return nil - } - app.After = func(*cli.Context) error { - // called by Run() when the command handler succeeds - if cpuProfile { - pprof.StopCPUProfile() - } - return nil + pprof.StartCPUProfile(f) } - app.Flags = []cli.Flag{ - cli.StringFlag{ - Name: "config, c", - Usage: "Path of a libpod config file detailing container server configuration options", - Hidden: true, - }, - cli.StringFlag{ - Name: "cpu-profile", - Usage: "Path for the cpu profiling results", - }, - cli.StringFlag{ - Name: "log-level", - Usage: "Log messages above specified level: debug, info, warn, error (default), fatal or panic", - Value: "error", - }, - cli.StringFlag{ - Name: "tmpdir", - Usage: "Path to the tmp directory", - }, + if cmd.Flag("trace").Changed { + var tracer opentracing.Tracer + tracer, closer = tracing.Init("podman") + opentracing.SetGlobalTracer(tracer) + + span = tracer.StartSpan("before-context") + + Ctx = opentracing.ContextWithSpan(context.Background(), span) } + return nil +} - app.Flags = append(app.Flags, getMainAppFlags()...) - sort.Sort(flagSortedAlpha{app.Flags}) +func after(cmd *cobra.Command, args []string) error { + if cmd.Flag("cpu-profile").Changed { + pprof.StopCPUProfile() + } + if cmd.Flag("trace").Changed { + span.Finish() + closer.Close() + } + return nil +} - // Check if /etc/containers/registries.conf exists when running in - // in a local environment. - CheckForRegistries() +func main() { + //debug := false + //cpuProfile := false - if err := app.Run(os.Args); err != nil { - if debug { - logrus.Errorf(err.Error()) - } else { - // Retrieve the exit error from the exec call, if it exists - if ee, ok := err.(*exec.ExitError); ok { - if status, ok := ee.Sys().(syscall.WaitStatus); ok { - exitCode = status.ExitStatus() - } - } - fmt.Fprintln(os.Stderr, err.Error()) - } + if reexec.Init() { + return + } + if err := rootCmd.Execute(); err != nil { + outputError(err) } else { // The exitCode modified from 125, indicates an application // running inside of a container failed, as opposed to the @@ -233,6 +231,11 @@ func main() { if exitCode == 125 { exitCode = 0 } + } + + // Check if /etc/containers/registries.conf exists when running in + // in a local environment. + CheckForRegistries() os.Exit(exitCode) } diff --git a/cmd/podman/mount.go b/cmd/podman/mount.go index f71d47434..f4a7bd5ea 100644 --- a/cmd/podman/mount.go +++ b/cmd/podman/mount.go @@ -5,15 +5,18 @@ import ( "fmt" "os" + "github.com/containers/libpod/cmd/podman/cliconfig" of "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + mountCommand cliconfig.MountValues + mountDescription = ` podman mount Lists all mounted containers mount points @@ -22,32 +25,33 @@ var ( Mounts the specified container and outputs the mountpoint ` - mountFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Mount all containers", - }, - cli.StringFlag{ - Name: "format", - Usage: "Change the output format to Go template", + _mountCommand = &cobra.Command{ + Use: "mount", + Short: "Mount a working container's root filesystem", + Long: mountDescription, + RunE: func(cmd *cobra.Command, args []string) error { + mountCommand.InputArgs = args + mountCommand.GlobalFlags = MainGlobalOpts + return mountCmd(&mountCommand) }, - cli.BoolFlag{ - Name: "notruncate", - Usage: "Do not truncate output", + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, true) }, - LatestFlag, - } - mountCommand = cli.Command{ - Name: "mount", - Usage: "Mount a working container's root filesystem", - Description: mountDescription, - Action: mountCmd, - ArgsUsage: "[CONTAINER-NAME-OR-ID [...]]", - Flags: sortFlags(mountFlags), - OnUsageError: usageErrorHandler, } ) +func init() { + mountCommand.Command = _mountCommand + mountCommand.SetUsageTemplate(UsageTemplate()) + flags := mountCommand.Flags() + flags.BoolVarP(&mountCommand.All, "all", "a", false, "Mount all containers") + flags.StringVar(&mountCommand.Format, "format", "", "Change the output format to Go template") + flags.BoolVarP(&mountCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&mountCommand.NoTrunc, "notruncate", false, "Do not truncate output") + + markFlagHiddenForRemoteClient("latest", flags) +} + // jsonMountPoint stores info about each container type jsonMountPoint struct { ID string `json:"id"` @@ -55,15 +59,12 @@ type jsonMountPoint struct { MountPoint string `json:"mountpoint"` } -func mountCmd(c *cli.Context) error { - if err := validateFlags(c, mountFlags); err != nil { - return err - } +func mountCmd(c *cliconfig.MountValues) error { if os.Geteuid() != 0 { rootless.SetSkipStorageSetup(true) } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -85,11 +86,11 @@ func mountCmd(c *cli.Context) error { } } - if c.Bool("all") && c.Bool("latest") { + if c.All && c.Latest { return errors.Errorf("--all and --latest cannot be used together") } - mountContainers, err := getAllOrLatestContainers(c, runtime, -1, "all") + mountContainers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, -1, "all") if err != nil { if len(mountContainers) == 0 { return err @@ -102,9 +103,9 @@ func mountCmd(c *cli.Context) error { of.JSONString: true, } - json := c.String("format") == of.JSONString - if !formats[c.String("format")] { - return errors.Errorf("%q is not a supported format", c.String("format")) + json := c.Format == of.JSONString + if !formats[c.Format] { + return errors.Errorf("%q is not a supported format", c.Format) } var lastError error @@ -149,7 +150,7 @@ func mountCmd(c *cli.Context) error { continue } - if c.Bool("notruncate") { + if c.NoTrunc { fmt.Printf("%-64s %s\n", container.ID(), mountPoint) } else { fmt.Printf("%-12.12s %s\n", container.ID(), mountPoint) diff --git a/cmd/podman/pause.go b/cmd/podman/pause.go index 2e7182871..94bb0edfe 100644 --- a/cmd/podman/pause.go +++ b/cmd/podman/pause.go @@ -3,38 +3,46 @@ package main import ( "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - pauseFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Pause all running containers", - }, - } + pauseCommand cliconfig.PauseValues pauseDescription = ` podman pause Pauses one or more running containers. The container name or ID can be used. ` - pauseCommand = cli.Command{ - Name: "pause", - Usage: "Pause all the processes in one or more containers", - Description: pauseDescription, - Flags: pauseFlags, - Action: pauseCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", - OnUsageError: usageErrorHandler, + _pauseCommand = &cobra.Command{ + Use: "pause", + Short: "Pause all the processes in one or more containers", + Long: pauseDescription, + RunE: func(cmd *cobra.Command, args []string) error { + pauseCommand.InputArgs = args + pauseCommand.GlobalFlags = MainGlobalOpts + return pauseCmd(&pauseCommand) + }, + Example: `podman pause mywebserver + podman pause 860a4b23 + podman stop -a`, } ) -func pauseCmd(c *cli.Context) error { +func init() { + pauseCommand.Command = _pauseCommand + pauseCommand.SetUsageTemplate(UsageTemplate()) + flags := pauseCommand.Flags() + flags.BoolVarP(&pauseCommand.All, "all", "a", false, "Pause all running containers") + +} + +func pauseCmd(c *cliconfig.PauseValues) error { var ( pauseContainers []*libpod.Container pauseFuncs []shared.ParallelWorkerInput @@ -43,18 +51,18 @@ func pauseCmd(c *cli.Context) error { return errors.New("pause is not supported for rootless containers") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - args := c.Args() - if len(args) < 1 && !c.Bool("all") { + args := c.InputArgs + if len(args) < 1 && !c.All { return errors.Errorf("you must provide at least one container name or id") } - if c.Bool("all") { - containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + if c.All { + containers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStateRunning, "running") if err != nil { return err } @@ -84,7 +92,7 @@ func pauseCmd(c *cli.Context) error { maxWorkers := shared.Parallelize("pause") if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalInt("max-workers") + maxWorkers = c.GlobalFlags.MaxWorks } logrus.Debugf("Setting maximum workers to %d", maxWorkers) diff --git a/cmd/podman/play.go b/cmd/podman/play.go index 4e09b2689..495a1f170 100644 --- a/cmd/podman/play.go +++ b/cmd/podman/play.go @@ -1,23 +1,22 @@ package main import ( - "github.com/urfave/cli" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" ) var ( - playSubCommands = []cli.Command{ - playKubeCommand, - } - + playCommand cliconfig.PodmanCommand playDescription = "Play a pod and its containers from a structured file." - playCommand = cli.Command{ - Name: "play", - Usage: "Play a container or pod", - Description: playDescription, - ArgsUsage: "", - Subcommands: playSubCommands, - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, - Hidden: true, + _playCommand = &cobra.Command{ + Use: "play", + Short: "Play a pod", + Long: playDescription, } ) + +func init() { + playCommand.Command = _playCommand + playCommand.SetUsageTemplate(UsageTemplate()) + playCommand.AddCommand(getPlaySubCommands()...) +} diff --git a/cmd/podman/play_kube.go b/cmd/podman/play_kube.go index 2d97e0e95..a59460b71 100644 --- a/cmd/podman/play_kube.go +++ b/cmd/podman/play_kube.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/containers/image/types" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" @@ -20,51 +21,40 @@ import ( "github.com/ghodss/yaml" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" "k8s.io/api/core/v1" ) var ( - playKubeFlags = []cli.Flag{ - cli.StringFlag{ - Name: "authfile", - Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override. ", - }, - cli.StringFlag{ - Name: "cert-dir", - Usage: "`Pathname` of a directory containing TLS certificates and keys", - }, - cli.StringFlag{ - Name: "creds", - Usage: "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Suppress output information when pulling images", - }, - cli.StringFlag{ - Name: "signature-policy", - Usage: "`Pathname` of signature policy file (not usually used)", - }, - cli.BoolTFlag{ - Name: "tls-verify", - Usage: "Require HTTPS and verify certificates when contacting registries (default: true)", - }, - } + playKubeCommand cliconfig.KubePlayValues playKubeDescription = "Play a Pod and its containers based on a Kubrernetes YAML" - playKubeCommand = cli.Command{ - Name: "kube", - Usage: "Play a pod based on Kubernetes YAML", - Description: playKubeDescription, - Action: playKubeYAMLCmd, - Flags: sortFlags(playKubeFlags), - ArgsUsage: "Kubernetes YAML file", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _playKubeCommand = &cobra.Command{ + Use: "kube", + Short: "Play a pod based on Kubernetes YAML", + Long: playKubeDescription, + RunE: func(cmd *cobra.Command, args []string) error { + playKubeCommand.InputArgs = args + playKubeCommand.GlobalFlags = MainGlobalOpts + return playKubeYAMLCmd(&playKubeCommand) + }, + Example: `podman play kube demo.yml + podman play kube --cert-dir /mycertsdir --tls-verify=true --quiet myWebPod`, } ) -func playKubeYAMLCmd(c *cli.Context) error { +func init() { + playKubeCommand.Command = _playKubeCommand + playKubeCommand.SetUsageTemplate(UsageTemplate()) + flags := playKubeCommand.Flags() + flags.StringVar(&playKubeCommand.Authfile, "authfile", "", "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&playKubeCommand.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") + flags.StringVar(&playKubeCommand.Creds, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") + flags.BoolVarP(&playKubeCommand.Quiet, "quiet", "q", false, "Suppress output information when pulling images") + flags.StringVar(&playKubeCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") + flags.BoolVar(&playKubeCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") +} + +func playKubeYAMLCmd(c *cliconfig.KubePlayValues) error { var ( podOptions []libpod.PodCreateOption podYAML v1.Pod @@ -77,7 +67,7 @@ func playKubeYAMLCmd(c *cli.Context) error { if rootless.IsRootless() { return errors.Wrapf(libpod.ErrNotImplemented, "rootless users") } - args := c.Args() + args := c.InputArgs if len(args) > 1 { return errors.New("you can only play one kubernetes file at a time") } @@ -85,7 +75,7 @@ func playKubeYAMLCmd(c *cli.Context) error { return errors.New("you must supply at least one file") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -133,20 +123,20 @@ func playKubeYAMLCmd(c *cli.Context) error { "ipc": fmt.Sprintf("container:%s", podInfraID), "uts": fmt.Sprintf("container:%s", podInfraID), } - if !c.Bool("quiet") { + if !c.Quiet { writer = os.Stderr } dockerRegistryOptions := image2.DockerRegistryOptions{ DockerRegistryCreds: registryCreds, - DockerCertPath: c.String("cert-dir"), + DockerCertPath: c.CertDir, } - if c.IsSet("tls-verify") { - dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify")) + if c.Flag("tls-verify").Changed { + dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) } for _, container := range podYAML.Spec.Containers { - newImage, err := runtime.ImageRuntime().New(ctx, container.Image, c.String("signature-policy"), c.String("authfile"), writer, &dockerRegistryOptions, image2.SigningOptions{}, false, nil) + newImage, err := runtime.ImageRuntime().New(ctx, container.Image, c.SignaturePolicy, c.Authfile, writer, &dockerRegistryOptions, image2.SigningOptions{}, false, nil) if err != nil { return err } @@ -163,7 +153,7 @@ func playKubeYAMLCmd(c *cli.Context) error { // start the containers for _, ctr := range containers { - if err := ctr.Start(ctx); err != nil { + if err := ctr.Start(ctx, false); err != nil { // Making this a hard failure here to avoid a mess // the other containers are in created status return err diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go index a30361134..c1350bd4d 100644 --- a/cmd/podman/pod.go +++ b/cmd/podman/pod.go @@ -1,35 +1,40 @@ package main import ( - "github.com/urfave/cli" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" ) var ( podDescription = `Manage container pods. -Pods are a group of one or more containers sharing the same network, pid and ipc namespaces. -` - podSubCommands = []cli.Command{ - podCreateCommand, - podExistsCommand, - podInspectCommand, - podKillCommand, - podPauseCommand, - podPsCommand, - podRestartCommand, - podRmCommand, - podStartCommand, - podStatsCommand, - podStopCommand, - podTopCommand, - podUnpauseCommand, - } - podCommand = cli.Command{ - Name: "pod", - Usage: "Manage pods", - Description: podDescription, - UseShortOptionHandling: true, - Subcommands: podSubCommands, - OnUsageError: usageErrorHandler, - } +Pods are a group of one or more containers sharing the same network, pid and ipc namespaces.` ) +var podCommand = cliconfig.PodmanCommand{ + Command: &cobra.Command{ + Use: "pod", + Short: "Manage pods", + Long: podDescription, + }, +} + +//podSubCommands are implemented both in local and remote clients +var podSubCommands = []*cobra.Command{ + _podCreateCommand, + _podExistsCommand, + _podInspectCommand, + _podKillCommand, + _podPauseCommand, + _podPsCommand, + _podRestartCommand, + _podRmCommand, + _podStartCommand, + _podStopCommand, + _podUnpauseCommand, +} + +func init() { + podCommand.AddCommand(podSubCommands...) + podCommand.AddCommand(getPodSubCommands()...) + podCommand.SetUsageTemplate(UsageTemplate()) +} diff --git a/cmd/podman/pod_create.go b/cmd/podman/pod_create.go index 967ce7610..f1bbecb84 100644 --- a/cmd/podman/pod_create.go +++ b/cmd/podman/pod_create.go @@ -3,175 +3,107 @@ package main import ( "fmt" "os" - "strings" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( // Kernel namespaces shared by default within a pod DefaultKernelNamespaces = "cgroup,ipc,net,uts" + podCreateCommand cliconfig.PodCreateValues + + podCreateDescription = "Creates a new empty pod. The pod ID is then" + + " printed to stdout. You can then start it at any time with the" + + " podman pod start <pod_id> command. The pod will be created with the" + + " initial state 'created'." + + _podCreateCommand = &cobra.Command{ + Use: "create", + Short: "Create a new empty pod", + Long: podCreateDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podCreateCommand.InputArgs = args + podCreateCommand.GlobalFlags = MainGlobalOpts + return podCreateCmd(&podCreateCommand) + }, + } ) -var podCreateDescription = "Creates a new empty pod. The pod ID is then" + - " printed to stdout. You can then start it at any time with the" + - " podman pod start <pod_id> command. The pod will be created with the" + - " initial state 'created'." - -var podCreateFlags = []cli.Flag{ - cli.StringFlag{ - Name: "cgroup-parent", - Usage: "Set parent cgroup for the pod", - }, - cli.BoolTFlag{ - Name: "infra", - Usage: "Create an infra container associated with the pod to share namespaces with", - }, - cli.StringFlag{ - Name: "infra-image", - Usage: "The image of the infra container to associate with the pod", - Value: libpod.DefaultInfraImage, - }, - cli.StringFlag{ - Name: "infra-command", - Usage: "The command to run on the infra container when the pod is started", - Value: libpod.DefaultInfraCommand, - }, - cli.StringSliceFlag{ - Name: "label-file", - Usage: "Read in a line delimited file of labels (default [])", - }, - cli.StringSliceFlag{ - Name: "label, l", - Usage: "Set metadata on pod (default [])", - }, - cli.StringFlag{ - Name: "name, n", - Usage: "Assign a name to the pod", - }, - cli.StringFlag{ - Name: "pod-id-file", - Usage: "Write the pod ID to the file", - }, - cli.StringSliceFlag{ - Name: "publish, p", - Usage: "Publish a container's port, or a range of ports, to the host (default [])", - }, - cli.StringFlag{ - Name: "share", - Usage: "A comma delimited list of kernel namespaces the pod will share", - Value: DefaultKernelNamespaces, - }, -} +func init() { + podCreateCommand.Command = _podCreateCommand + podCreateCommand.SetUsageTemplate(UsageTemplate()) + flags := podCreateCommand.Flags() + flags.SetInterspersed(false) + + flags.StringVar(&podCreateCommand.CgroupParent, "cgroup-parent", "", "Set parent cgroup for the pod") + flags.BoolVar(&podCreateCommand.Infra, "infra", true, "Create an infra container associated with the pod to share namespaces with") + flags.StringVar(&podCreateCommand.InfraImage, "infra-image", libpod.DefaultInfraImage, "The image of the infra container to associate with the pod") + flags.StringVar(&podCreateCommand.InfraCommand, "infra-command", libpod.DefaultInfraCommand, "The command to run on the infra container when the pod is started") + flags.StringSliceVar(&podCreateCommand.LabelFile, "label-file", []string{}, "Read in a line delimited file of labels") + flags.StringSliceVarP(&podCreateCommand.Labels, "label", "l", []string{}, "Set metadata on pod (default [])") + flags.StringVarP(&podCreateCommand.Name, "name", "n", "", "Assign a name to the pod") + flags.StringVar(&podCreateCommand.PodIDFile, "pod-id-file", "", "Write the pod ID to the file") + flags.StringSliceVarP(&podCreateCommand.Publish, "publish", "p", []string{}, "Publish a container's port, or a range of ports, to the host (default [])") + flags.StringVar(&podCreateCommand.Share, "share", DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share") -var podCreateCommand = cli.Command{ - Name: "create", - Usage: "Create a new empty pod", - Description: podCreateDescription, - Flags: sortFlags(podCreateFlags), - Action: podCreateCmd, - SkipArgReorder: true, - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, } -func podCreateCmd(c *cli.Context) error { - var options []libpod.PodCreateOption - var err error +func podCreateCmd(c *cliconfig.PodCreateValues) error { + var ( + err error + podIdFile *os.File + ) - if err = validateFlags(c, createFlags); err != nil { - return err + if len(c.InputArgs) > 0 { + return errors.New("podman pod create does not accept any arguments") } - - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - var podIdFile *os.File - if c.IsSet("pod-id-file") && os.Geteuid() == 0 { - podIdFile, err = libpod.OpenExclusiveFile(c.String("pod-id-file")) - if err != nil && os.IsExist(err) { - return errors.Errorf("pod id file exists. Ensure another pod is not using it or delete %s", c.String("pod-id-file")) - } - if err != nil { - return errors.Errorf("error opening pod-id-file %s", c.String("pod-id-file")) - } - defer podIdFile.Close() - defer podIdFile.Sync() - } - - if len(c.StringSlice("publish")) > 0 { - if !c.BoolT("infra") { + if len(c.Publish) > 0 { + if !c.Infra { return errors.Errorf("you must have an infra container to publish port bindings to the host") } - if rootless.IsRootless() { - return errors.Errorf("rootless networking does not allow port binding to the host") - } } - if !c.BoolT("infra") && c.IsSet("share") && c.String("share") != "none" && c.String("share") != "" { + if !c.Infra && c.Flag("share").Changed && c.Share != "none" && c.Share != "" { return errors.Errorf("You cannot share kernel namespaces on the pod level without an infra container") } - - if c.IsSet("cgroup-parent") { - options = append(options, libpod.WithPodCgroupParent(c.String("cgroup-parent"))) - } - - labels, err := getAllLabels(c.StringSlice("label-file"), c.StringSlice("label")) - if err != nil { - return errors.Wrapf(err, "unable to process labels") - } - if len(labels) != 0 { - options = append(options, libpod.WithPodLabels(labels)) - } - - if c.IsSet("name") { - options = append(options, libpod.WithPodName(c.String("name"))) - } - - if c.BoolT("infra") { - options = append(options, libpod.WithInfraContainer()) - nsOptions, err := shared.GetNamespaceOptions(strings.Split(c.String("share"), ",")) - if err != nil { - return err + if c.Flag("pod-id-file").Changed && os.Geteuid() == 0 { + podIdFile, err = libpod.OpenExclusiveFile(c.PodIDFile) + if err != nil && os.IsExist(err) { + return errors.Errorf("pod id file exists. Ensure another pod is not using it or delete %s", c.PodIDFile) } - options = append(options, nsOptions...) - } - - if len(c.StringSlice("publish")) > 0 { - portBindings, err := shared.CreatePortBindings(c.StringSlice("publish")) if err != nil { - return err + return errors.Errorf("error opening pod-id-file %s", c.PodIDFile) } - options = append(options, libpod.WithInfraContainerPorts(portBindings)) - + defer podIdFile.Close() + defer podIdFile.Sync() } - // always have containers use pod cgroups - // User Opt out is not yet supported - options = append(options, libpod.WithPodCgroups()) - ctx := getContext() - pod, err := runtime.NewPod(ctx, options...) + labels, err := getAllLabels(c.LabelFile, c.Labels) if err != nil { - return err + return errors.Wrapf(err, "unable to process labels") } + podID, err := runtime.CreatePod(getContext(), c, labels) + if err != nil { + return errors.Wrapf(err, "unable to create pod") + } if podIdFile != nil { - _, err = podIdFile.WriteString(pod.ID()) + _, err = podIdFile.WriteString(podID) if err != nil { logrus.Error(err) } } - - fmt.Printf("%s\n", pod.ID()) - + fmt.Printf("%s\n", podID) return nil } diff --git a/cmd/podman/pod_inspect.go b/cmd/podman/pod_inspect.go index c7bbf31cd..5a32b5c5d 100644 --- a/cmd/podman/pod_inspect.go +++ b/cmd/podman/pod_inspect.go @@ -2,46 +2,51 @@ package main import ( "encoding/json" - "fmt" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod" + + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - podInspectFlags = []cli.Flag{ - LatestPodFlag, - } + podInspectCommand cliconfig.PodInspectValues podInspectDescription = "Display the configuration for a pod by name or id" - podInspectCommand = cli.Command{ - Name: "inspect", - Usage: "Displays a pod configuration", - Description: podInspectDescription, - Flags: sortFlags(podInspectFlags), - Action: podInspectCmd, - UseShortOptionHandling: true, - ArgsUsage: "[POD_NAME_OR_ID]", - OnUsageError: usageErrorHandler, + _podInspectCommand = &cobra.Command{ + Use: "inspect", + Short: "Displays a pod configuration", + Long: podInspectDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podInspectCommand.InputArgs = args + podInspectCommand.GlobalFlags = MainGlobalOpts + return podInspectCmd(&podInspectCommand) + }, + Example: `podman pod inspect podID`, } ) -func podInspectCmd(c *cli.Context) error { +func init() { + podInspectCommand.Command = _podInspectCommand + podInspectCommand.SetUsageTemplate(UsageTemplate()) + flags := podInspectCommand.Flags() + flags.BoolVarP(&podInspectCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + + markFlagHiddenForRemoteClient("latest", flags) +} + +func podInspectCmd(c *cliconfig.PodInspectValues) error { var ( - pod *libpod.Pod + pod *adapter.Pod ) - if err := checkMutuallyExclusiveFlags(c); err != nil { - return err - } - args := c.Args() - runtime, err := libpodruntime.GetRuntime(c) + args := c.InputArgs + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if c.Bool("latest") { + if c.Latest { pod, err = runtime.GetLatestPod() if err != nil { return errors.Wrapf(err, "unable to get latest pod") diff --git a/cmd/podman/pod_kill.go b/cmd/podman/pod_kill.go index c8029eb46..aaaae0f7d 100644 --- a/cmd/podman/pod_kill.go +++ b/cmd/podman/pod_kill.go @@ -4,46 +4,48 @@ import ( "fmt" "syscall" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" "github.com/docker/docker/pkg/signal" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - podKillFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Kill all containers in all pods", + podKillCommand cliconfig.PodKillValues + podKillDescription = "The main process of each container inside the specified pod will be sent SIGKILL, or any signal specified with option --signal." + _podKillCommand = &cobra.Command{ + Use: "kill", + Short: "Send the specified signal or SIGKILL to containers in pod", + Long: podKillDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podKillCommand.InputArgs = args + podKillCommand.GlobalFlags = MainGlobalOpts + return podKillCmd(&podKillCommand) }, - cli.StringFlag{ - Name: "signal, s", - Usage: "Signal to send to the containers in the pod", - Value: "KILL", + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, false) }, - LatestPodFlag, - } - podKillDescription = "The main process of each container inside the specified pod will be sent SIGKILL, or any signal specified with option --signal." - podKillCommand = cli.Command{ - Name: "kill", - Usage: "Send the specified signal or SIGKILL to containers in pod", - Description: podKillDescription, - Flags: sortFlags(podKillFlags), - Action: podKillCmd, - ArgsUsage: "[POD_NAME_OR_ID]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + Example: `podman pod kill podID + podman pod kill --signal TERM mywebserver + podman pod kill --latest`, } ) -// podKillCmd kills one or more pods with a signal -func podKillCmd(c *cli.Context) error { - if err := checkMutuallyExclusiveFlags(c); err != nil { - return err - } +func init() { + podKillCommand.Command = _podKillCommand + podKillCommand.SetUsageTemplate(UsageTemplate()) + flags := podKillCommand.Flags() + flags.BoolVarP(&podKillCommand.All, "all", "a", false, "Kill all containers in all pods") + flags.BoolVarP(&podKillCommand.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") + flags.StringVarP(&podKillCommand.Signal, "signal", "s", "KILL", "Signal to send to the containers in the pod") + markFlagHiddenForRemoteClient("latest", flags) +} - runtime, err := libpodruntime.GetRuntime(c) +// podKillCmd kills one or more pods with a signal +func podKillCmd(c *cliconfig.PodKillValues) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -51,40 +53,30 @@ func podKillCmd(c *cli.Context) error { var killSignal uint = uint(syscall.SIGTERM) - if c.String("signal") != "" { + if c.Signal != "" { // Check if the signalString provided by the user is valid // Invalid signals will return err - sysSignal, err := signal.ParseSignal(c.String("signal")) + sysSignal, err := signal.ParseSignal(c.Signal) if err != nil { return err } killSignal = uint(sysSignal) } - // getPodsFromContext returns an error when a requested pod - // isn't found. The only fatal error scenerio is when there are no pods - // in which case the following loop will be skipped. - pods, lastError := getPodsFromContext(c, runtime) + podKillIds, podKillErrors := runtime.KillPods(getContext(), c, killSignal) + for _, p := range podKillIds { + fmt.Println(p) + } + if len(podKillErrors) == 0 { + return nil + } + // Grab the last error + lastError := podKillErrors[len(podKillErrors)-1] + // Remove the last error from the error slice + podKillErrors = podKillErrors[:len(podKillErrors)-1] - for _, pod := range pods { - ctr_errs, err := pod.Kill(killSignal) - if ctr_errs != nil { - for ctr, err := range ctr_errs { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "unable to kill container %q in pod %q", ctr, pod.ID()) - } - continue - } - if err != nil { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "unable to kill pod %q", pod.ID()) - continue - } - fmt.Println(pod.ID()) + for _, err := range podKillErrors { + logrus.Errorf("%q", err) } return lastError } diff --git a/cmd/podman/pod_pause.go b/cmd/podman/pod_pause.go index f29a0b8d1..284740d22 100644 --- a/cmd/podman/pod_pause.go +++ b/cmd/podman/pod_pause.go @@ -2,73 +2,71 @@ package main import ( "fmt" - - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - podPauseFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Pause all running pods", + podPauseCommand cliconfig.PodPauseValues + podPauseDescription = `Pauses one or more pods. The pod name or ID can be used.` + _podPauseCommand = &cobra.Command{ + Use: "pause", + Short: "Pause one or more pods", + Long: podPauseDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podPauseCommand.InputArgs = args + podPauseCommand.GlobalFlags = MainGlobalOpts + return podPauseCmd(&podPauseCommand) }, - LatestPodFlag, - } - podPauseDescription = ` - Pauses one or more pods. The pod name or ID can be used. -` - - podPauseCommand = cli.Command{ - Name: "pause", - Usage: "Pause one or more pods", - Description: podPauseDescription, - Flags: sortFlags(podPauseFlags), - Action: podPauseCmd, - ArgsUsage: "POD-NAME|POD-ID [POD-NAME|POD-ID ...]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, false) + }, + Example: `podman pod pause podID1 podID2 + podman pod pause --latest + podman pod pause --all`, } ) -func podPauseCmd(c *cli.Context) error { - if err := checkMutuallyExclusiveFlags(c); err != nil { - return err - } +func init() { + podPauseCommand.Command = _podPauseCommand + podPauseCommand.SetUsageTemplate(UsageTemplate()) + flags := podPauseCommand.Flags() + flags.BoolVarP(&podPauseCommand.All, "all", "a", false, "Pause all running pods") + flags.BoolVarP(&podPauseCommand.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") + markFlagHiddenForRemoteClient("latest", flags) +} - runtime, err := libpodruntime.GetRuntime(c) +func podPauseCmd(c *cliconfig.PodPauseValues) error { + var lastError error + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - // getPodsFromContext returns an error when a requested pod - // isn't found. The only fatal error scenerio is when there are no pods - // in which case the following loop will be skipped. - pods, lastError := getPodsFromContext(c, runtime) + pauseIDs, conErrors, pauseErrors := runtime.PausePods(c) - for _, pod := range pods { - ctr_errs, err := pod.Pause() - if ctr_errs != nil { - for ctr, err := range ctr_errs { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "unable to pause container %q on pod %q", ctr, pod.ID()) - } - continue - } - if err != nil { + for _, p := range pauseIDs { + fmt.Println(p) + } + if conErrors != nil && len(conErrors) > 0 { + for ctr, err := range conErrors { if lastError != nil { logrus.Errorf("%q", lastError) } - lastError = errors.Wrapf(err, "unable to pause pod %q", pod.ID()) - continue + lastError = errors.Wrapf(err, "unable to pause container %s", ctr) } - fmt.Println(pod.ID()) } - + if len(pauseErrors) > 0 { + lastError = pauseErrors[len(pauseErrors)-1] + // Remove the last error from the error slice + pauseErrors = pauseErrors[:len(pauseErrors)-1] + } + for _, err := range pauseErrors { + logrus.Errorf("%q", err) + } return lastError } diff --git a/cmd/podman/pod_ps.go b/cmd/podman/pod_ps.go index 2030b9b04..70e077651 100644 --- a/cmd/podman/pod_ps.go +++ b/cmd/podman/pod_ps.go @@ -8,14 +8,15 @@ import ( "strings" "time" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" - "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/adapter" "github.com/containers/libpod/pkg/util" "github.com/docker/go-units" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) const ( @@ -28,6 +29,8 @@ const ( NUM_CTR_INFO = 10 ) +type PodFilter func(*adapter.Pod) bool + var ( bc_opts shared.PsOptions ) @@ -112,101 +115,75 @@ func (a podPsSortedStatus) Less(i, j int) bool { } var ( - podPsFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "ctr-names", - Usage: "Display the container names", - }, - cli.BoolFlag{ - Name: "ctr-ids", - Usage: "Display the container UUIDs. If no-trunc is not set they will be truncated", - }, - cli.BoolFlag{ - Name: "ctr-status", - Usage: "Display the container status", - }, - cli.StringFlag{ - Name: "filter, f", - Usage: "Filter output based on conditions given", - }, - cli.StringFlag{ - Name: "format", - Usage: "Pretty-print pods to JSON or using a Go template", - }, - cli.BoolFlag{ - Name: "latest, l", - Usage: "Show the latest pod created", - }, - cli.BoolFlag{ - Name: "namespace, ns", - Usage: "Display namespace information of the pod", - }, - cli.BoolFlag{ - Name: "no-trunc", - Usage: "Do not truncate pod and container IDs", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Print the numeric IDs of the pods only", - }, - cli.StringFlag{ - Name: "sort", - Usage: "Sort output by created, id, name, or number", - Value: "created", - }, - } + podPsCommand cliconfig.PodPsValues + podPsDescription = "List all pods on system including their names, ids and current state." - podPsCommand = cli.Command{ - Name: "ps", - Aliases: []string{"ls", "list"}, - Usage: "List pods", - Description: podPsDescription, - Flags: sortFlags(podPsFlags), - Action: podPsCmd, - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _podPsCommand = &cobra.Command{ + Use: "ps", + Aliases: []string{"ls", "list"}, + Short: "List pods", + Long: podPsDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podPsCommand.InputArgs = args + podPsCommand.GlobalFlags = MainGlobalOpts + return podPsCmd(&podPsCommand) + }, } ) -func podPsCmd(c *cli.Context) error { - if err := validateFlags(c, podPsFlags); err != nil { - return err - } +func init() { + podPsCommand.Command = _podPsCommand + podPsCommand.SetUsageTemplate(UsageTemplate()) + flags := podPsCommand.Flags() + flags.BoolVar(&podPsCommand.CtrNames, "ctr-names", false, "Display the container names") + flags.BoolVar(&podPsCommand.CtrIDs, "ctr-ids", false, "Display the container UUIDs. If no-trunc is not set they will be truncated") + flags.BoolVar(&podPsCommand.CtrStatus, "ctr-status", false, "Display the container status") + flags.StringVarP(&podPsCommand.Filter, "filter", "f", "", "Filter output based on conditions given") + flags.StringVar(&podPsCommand.Format, "format", "", "Pretty-print pods to JSON or using a Go template") + flags.BoolVarP(&podPsCommand.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") + flags.BoolVar(&podPsCommand.Namespace, "namespace", false, "Display namespace information of the pod") + flags.BoolVar(&podPsCommand.Namespace, "ns", false, "Display namespace information of the pod") + flags.BoolVar(&podPsCommand.NoTrunc, "no-trunc", false, "Do not truncate pod and container IDs") + flags.BoolVarP(&podPsCommand.Quiet, "quiet", "q", false, "Print the numeric IDs of the pods only") + flags.StringVar(&podPsCommand.Sort, "sort", "created", "Sort output by created, id, name, or number") + markFlagHiddenForRemoteClient("latest", flags) +} +func podPsCmd(c *cliconfig.PodPsValues) error { if err := podPsCheckFlagsPassed(c); err != nil { return errors.Wrapf(err, "error with flags passed") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - if len(c.Args()) > 0 { + if len(c.InputArgs) > 0 { return errors.Errorf("too many arguments, ps takes no arguments") } opts := podPsOptions{ - NoTrunc: c.Bool("no-trunc"), - Quiet: c.Bool("quiet"), - Sort: c.String("sort"), - IdsOfContainers: c.Bool("ctr-ids"), - NamesOfContainers: c.Bool("ctr-names"), - StatusOfContainers: c.Bool("ctr-status"), + NoTrunc: c.NoTrunc, + Quiet: c.Quiet, + Sort: c.Sort, + IdsOfContainers: c.CtrIDs, + NamesOfContainers: c.CtrNames, + StatusOfContainers: c.CtrStatus, } opts.Format = genPodPsFormat(c) - var filterFuncs []libpod.PodFilter - if c.String("filter") != "" { - filters := strings.Split(c.String("filter"), ",") + var filterFuncs []PodFilter + if c.Filter != "" { + filters := strings.Split(c.Filter, ",") for _, f := range filters { filterSplit := strings.Split(f, "=") if len(filterSplit) < 2 { return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) } - generatedFunc, err := generatePodFilterFuncs(filterSplit[0], filterSplit[1], runtime) + generatedFunc, err := generatePodFilterFuncs(filterSplit[0], filterSplit[1]) if err != nil { return errors.Wrapf(err, "invalid filter") } @@ -214,8 +191,8 @@ func podPsCmd(c *cli.Context) error { } } - var pods []*libpod.Pod - if c.IsSet("latest") { + var pods []*adapter.Pod + if c.Latest { pod, err := runtime.GetLatestPod() if err != nil { return err @@ -228,7 +205,7 @@ func podPsCmd(c *cli.Context) error { } } - podsFiltered := make([]*libpod.Pod, 0, len(pods)) + podsFiltered := make([]*adapter.Pod, 0, len(pods)) for _, pod := range pods { include := true for _, filter := range filterFuncs { @@ -240,17 +217,17 @@ func podPsCmd(c *cli.Context) error { } } - return generatePodPsOutput(podsFiltered, opts, runtime) + return generatePodPsOutput(podsFiltered, opts) } // podPsCheckFlagsPassed checks if mutually exclusive flags are passed together -func podPsCheckFlagsPassed(c *cli.Context) error { +func podPsCheckFlagsPassed(c *cliconfig.PodPsValues) error { // quiet, and format with Go template are mutually exclusive flags := 0 - if c.Bool("quiet") { + if c.Quiet { flags++ } - if c.IsSet("format") && c.String("format") != formats.JSONString { + if c.Flag("format").Changed && c.Format != formats.JSONString { flags++ } if flags > 1 { @@ -259,10 +236,10 @@ func podPsCheckFlagsPassed(c *cli.Context) error { return nil } -func generatePodFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) (func(pod *libpod.Pod) bool, error) { +func generatePodFilterFuncs(filter, filterValue string) (func(pod *adapter.Pod) bool, error) { switch filter { case "ctr-ids": - return func(p *libpod.Pod) bool { + return func(p *adapter.Pod) bool { ctrIds, err := p.AllContainersByID() if err != nil { return false @@ -270,7 +247,7 @@ func generatePodFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) return util.StringInSlice(filterValue, ctrIds) }, nil case "ctr-names": - return func(p *libpod.Pod) bool { + return func(p *adapter.Pod) bool { ctrs, err := p.AllContainers() if err != nil { return false @@ -283,7 +260,7 @@ func generatePodFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) return false }, nil case "ctr-number": - return func(p *libpod.Pod) bool { + return func(p *adapter.Pod) bool { ctrIds, err := p.AllContainersByID() if err != nil { return false @@ -299,7 +276,7 @@ func generatePodFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) if !util.StringInSlice(filterValue, []string{"created", "restarting", "running", "paused", "exited", "unknown"}) { return nil, errors.Errorf("%s is not a valid status", filterValue) } - return func(p *libpod.Pod) bool { + return func(p *adapter.Pod) bool { ctr_statuses, err := p.Status() if err != nil { return false @@ -316,19 +293,19 @@ func generatePodFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) return false }, nil case "id": - return func(p *libpod.Pod) bool { + return func(p *adapter.Pod) bool { return strings.Contains(p.ID(), filterValue) }, nil case "name": - return func(p *libpod.Pod) bool { + return func(p *adapter.Pod) bool { return strings.Contains(p.Name(), filterValue) }, nil case "status": if !util.StringInSlice(filterValue, []string{"stopped", "running", "paused", "exited", "dead", "created"}) { return nil, errors.Errorf("%s is not a valid pod status", filterValue) } - return func(p *libpod.Pod) bool { - status, err := shared.GetPodStatus(p) + return func(p *adapter.Pod) bool { + status, err := p.GetPodStatus() if err != nil { return false } @@ -342,20 +319,20 @@ func generatePodFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) } // generate the template based on conditions given -func genPodPsFormat(c *cli.Context) string { +func genPodPsFormat(c *cliconfig.PodPsValues) string { format := "" - if c.String("format") != "" { + if c.Format != "" { // "\t" from the command line is not being recognized as a tab // replacing the string "\t" to a tab character if the user passes in "\t" - format = strings.Replace(c.String("format"), `\t`, "\t", -1) - } else if c.Bool("quiet") { + format = strings.Replace(c.Format, `\t`, "\t", -1) + } else if c.Quiet { format = formats.IDString } else { format = "table {{.ID}}\t{{.Name}}\t{{.Status}}\t{{.Created}}" if c.Bool("namespace") { format += "\t{{.Cgroup}}\t{{.Namespaces}}" } - if c.Bool("ctr-names") || c.Bool("ctr-ids") || c.Bool("ctr-status") { + if c.CtrNames || c.CtrIDs || c.CtrStatus { format += "\t{{.ContainerInfo}}" } else { format += "\t{{.NumberOfContainers}}" @@ -473,7 +450,7 @@ func getPodTemplateOutput(psParams []podPsJSONParams, opts podPsOptions) ([]podP return psOutput, nil } -func getNamespaces(pod *libpod.Pod) []string { +func getNamespaces(pod *adapter.Pod) []string { var shared []string if pod.SharesPID() { shared = append(shared, "pid") @@ -500,7 +477,7 @@ func getNamespaces(pod *libpod.Pod) []string { } // getAndSortPodJSONOutput returns the container info in its raw, sorted form -func getAndSortPodJSONParams(pods []*libpod.Pod, opts podPsOptions, runtime *libpod.Runtime) ([]podPsJSONParams, error) { +func getAndSortPodJSONParams(pods []*adapter.Pod, opts podPsOptions) ([]podPsJSONParams, error) { var ( psOutput []podPsJSONParams ) @@ -512,7 +489,7 @@ func getAndSortPodJSONParams(pods []*libpod.Pod, opts podPsOptions, runtime *lib return nil, err } ctrNum := len(ctrs) - status, err := shared.GetPodStatus(pod) + status, err := pod.GetPodStatus() if err != nil { return nil, err } @@ -522,7 +499,7 @@ func getAndSortPodJSONParams(pods []*libpod.Pod, opts podPsOptions, runtime *lib return nil, err } for _, ctr := range ctrs { - batchInfo, err := shared.BatchContainerOp(ctr, bc_opts) + batchInfo, err := adapter.BatchContainerOp(ctr, bc_opts) if err != nil { return nil, err } @@ -564,11 +541,11 @@ func getAndSortPodJSONParams(pods []*libpod.Pod, opts podPsOptions, runtime *lib return sortPodPsOutput(opts.Sort, psOutput) } -func generatePodPsOutput(pods []*libpod.Pod, opts podPsOptions, runtime *libpod.Runtime) error { +func generatePodPsOutput(pods []*adapter.Pod, opts podPsOptions) error { if len(pods) == 0 && opts.Format != formats.JSONString { return nil } - psOutput, err := getAndSortPodJSONParams(pods, opts, runtime) + psOutput, err := getAndSortPodJSONParams(pods, opts) if err != nil { return err } diff --git a/cmd/podman/pod_restart.go b/cmd/podman/pod_restart.go index d9800cbf7..741fce588 100644 --- a/cmd/podman/pod_restart.go +++ b/cmd/podman/pod_restart.go @@ -3,70 +3,72 @@ package main import ( "fmt" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - podRestartFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Restart all pods", - }, - LatestPodFlag, - } + podRestartCommand cliconfig.PodRestartValues podRestartDescription = `Restarts one or more pods. The pod ID or name can be used.` - - podRestartCommand = cli.Command{ - Name: "restart", - Usage: "Restart one or more pods", - Description: podRestartDescription, - Flags: sortFlags(podRestartFlags), - Action: podRestartCmd, - ArgsUsage: "POD-NAME|POD-ID [POD-NAME|POD-ID ...]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _podRestartCommand = &cobra.Command{ + Use: "restart", + Short: "Restart one or more pods", + Long: podRestartDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podRestartCommand.InputArgs = args + podRestartCommand.GlobalFlags = MainGlobalOpts + return podRestartCmd(&podRestartCommand) + }, + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, false) + }, + Example: `podman pod restart podID1 podID2 + podman pod restart --latest + podman pod restart --all`, } ) -func podRestartCmd(c *cli.Context) error { - if err := checkMutuallyExclusiveFlags(c); err != nil { - return err - } +func init() { + podRestartCommand.Command = _podRestartCommand + podRestartCommand.SetUsageTemplate(UsageTemplate()) + flags := podRestartCommand.Flags() + flags.BoolVarP(&podRestartCommand.All, "all", "a", false, "Restart all running pods") + flags.BoolVarP(&podRestartCommand.Latest, "latest", "l", false, "Restart the latest pod podman is aware of") - runtime, err := libpodruntime.GetRuntime(c) + markFlagHiddenForRemoteClient("latest", flags) +} + +func podRestartCmd(c *cliconfig.PodRestartValues) error { + var lastError error + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - // getPodsFromContext returns an error when a requested pod - // isn't found. The only fatal error scenerio is when there are no pods - // in which case the following loop will be skipped. - pods, lastError := getPodsFromContext(c, runtime) + restartIDs, conErrors, restartErrors := runtime.RestartPods(getContext(), c) - ctx := getContext() - for _, pod := range pods { - ctr_errs, err := pod.Restart(ctx) - if ctr_errs != nil { - for ctr, err := range ctr_errs { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "unable to restart container %q on pod %q", ctr, pod.ID()) - } - continue - } - if err != nil { + for _, p := range restartIDs { + fmt.Println(p) + } + if conErrors != nil && len(conErrors) > 0 { + for ctr, err := range conErrors { if lastError != nil { logrus.Errorf("%q", lastError) } - lastError = errors.Wrapf(err, "unable to restart pod %q", pod.ID()) - continue + lastError = errors.Wrapf(err, "unable to pause container %s", ctr) } - fmt.Println(pod.ID()) + } + if len(restartErrors) > 0 { + lastError = restartErrors[len(restartErrors)-1] + // Remove the last error from the error slice + restartErrors = restartErrors[:len(restartErrors)-1] + } + for _, err := range restartErrors { + logrus.Errorf("%q", err) } return lastError } diff --git a/cmd/podman/pod_rm.go b/cmd/podman/pod_rm.go index 49f2104cf..ba16d03c7 100644 --- a/cmd/podman/pod_rm.go +++ b/cmd/podman/pod_rm.go @@ -3,71 +3,69 @@ package main import ( "fmt" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - podRmFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Remove all pods", - }, - cli.BoolFlag{ - Name: "force, f", - Usage: "Force removal of a running pod by first stopping all containers, then removing all containers in the pod. The default is false", - }, - LatestPodFlag, - } + podRmCommand cliconfig.PodRmValues podRmDescription = fmt.Sprintf(` podman rm will remove one or more pods from the host. The pod name or ID can be used. A pod with containers will not be removed without --force. If --force is specified, all containers will be stopped, then removed. `) - podRmCommand = cli.Command{ - Name: "rm", - Usage: "Remove one or more pods", - Description: podRmDescription, - Flags: sortFlags(podRmFlags), - Action: podRmCmd, - ArgsUsage: "[POD ...]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _podRmCommand = &cobra.Command{ + Use: "rm", + Short: "Remove one or more pods", + Long: podRmDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podRmCommand.InputArgs = args + podRmCommand.GlobalFlags = MainGlobalOpts + return podRmCmd(&podRmCommand) + }, + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, false) + }, + Example: `podman pod rm mywebserverpod + podman pod rm -f 860a4b23 + podman pod rm -f -a`, } ) -// saveCmd saves the image to either docker-archive or oci -func podRmCmd(c *cli.Context) error { - if err := checkMutuallyExclusiveFlags(c); err != nil { - return err - } +func init() { + podRmCommand.Command = _podRmCommand + podRmCommand.SetUsageTemplate(UsageTemplate()) + flags := podRmCommand.Flags() + flags.BoolVarP(&podRmCommand.All, "all", "a", false, "Remove all running pods") + flags.BoolVarP(&podRmCommand.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(&podRmCommand.Latest, "latest", "l", false, "Remove the latest pod podman is aware of") + markFlagHiddenForRemoteClient("latest", flags) +} - runtime, err := libpodruntime.GetRuntime(c) +// podRmCmd deletes pods +func podRmCmd(c *cliconfig.PodRmValues) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) + podRmIds, podRmErrors := runtime.RemovePods(getContext(), c) + for _, p := range podRmIds { + fmt.Println(p) + } + if len(podRmErrors) == 0 { + return nil + } + // Grab the last error + lastError := podRmErrors[len(podRmErrors)-1] + // Remove the last error from the error slice + podRmErrors = podRmErrors[:len(podRmErrors)-1] - ctx := getContext() - force := c.Bool("force") - - // getPodsFromContext returns an error when a requested pod - // isn't found. The only fatal error scenerio is when there are no pods - // in which case the following loop will be skipped. - pods, lastError := getPodsFromContext(c, runtime) - - for _, pod := range pods { - err = runtime.RemovePod(ctx, pod, force, force) - if err != nil { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "failed to delete pod %v", pod.ID()) - } else { - fmt.Println(pod.ID()) - } + for _, err := range podRmErrors { + logrus.Errorf("%q", err) } return lastError } diff --git a/cmd/podman/pod_start.go b/cmd/podman/pod_start.go index 2178340a4..5761afd52 100644 --- a/cmd/podman/pod_start.go +++ b/cmd/podman/pod_start.go @@ -3,75 +3,68 @@ package main import ( "fmt" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - podStartFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Start all running pods", - }, - LatestPodFlag, - } + podStartCommand cliconfig.PodStartValues podStartDescription = ` podman pod start Starts one or more pods. The pod name or ID can be used. ` - - podStartCommand = cli.Command{ - Name: "start", - Usage: "Start one or more pods", - Description: podStartDescription, - Flags: sortFlags(podStartFlags), - Action: podStartCmd, - ArgsUsage: "POD-NAME [POD-NAME ...]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _podStartCommand = &cobra.Command{ + Use: "start", + Short: "Start one or more pods", + Long: podStartDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podStartCommand.InputArgs = args + podStartCommand.GlobalFlags = MainGlobalOpts + return podStartCmd(&podStartCommand) + }, + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, false) + }, + Example: `podman pod start podID + podman pod start --latest + podman pod start --all`, } ) -func podStartCmd(c *cli.Context) error { - if err := checkMutuallyExclusiveFlags(c); err != nil { - return err - } +func init() { + podStartCommand.Command = _podStartCommand + podStartCommand.SetUsageTemplate(UsageTemplate()) + flags := podStartCommand.Flags() + flags.BoolVarP(&podStartCommand.All, "all", "a", false, "Start all pods") + flags.BoolVarP(&podStartCommand.Latest, "latest", "l", false, "Start the latest pod podman is aware of") + markFlagHiddenForRemoteClient("latest", flags) +} - runtime, err := libpodruntime.GetRuntime(c) +func podStartCmd(c *cliconfig.PodStartValues) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - // getPodsFromContext returns an error when a requested pod - // isn't found. The only fatal error scenerio is when there are no pods - // in which case the following loop will be skipped. - pods, lastError := getPodsFromContext(c, runtime) - - ctx := getContext() - for _, pod := range pods { - ctr_errs, err := pod.Start(ctx) - if ctr_errs != nil { - for ctr, err := range ctr_errs { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "unable to start container %q on pod %q", ctr, pod.ID()) - } - continue - } - if err != nil { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "unable to start pod %q", pod.ID()) - continue - } - fmt.Println(pod.ID()) + podStartIDs, podStartErrors := runtime.StartPods(getContext(), c) + for _, p := range podStartIDs { + fmt.Println(p) } + if len(podStartErrors) == 0 { + return nil + } + // Grab the last error + lastError := podStartErrors[len(podStartErrors)-1] + // Remove the last error from the error slice + podStartErrors = podStartErrors[:len(podStartErrors)-1] + for _, err := range podStartErrors { + logrus.Errorf("%q", err) + } return lastError } diff --git a/cmd/podman/pod_stats.go b/cmd/podman/pod_stats.go index 0f0e215e6..907d6a547 100644 --- a/cmd/podman/pod_stats.go +++ b/cmd/podman/pod_stats.go @@ -2,59 +2,62 @@ package main import ( "fmt" + "html/template" + "os" + "reflect" "strings" + "text/tabwriter" "time" "encoding/json" tm "github.com/buger/goterm" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/pkg/errors" + "github.com/spf13/cobra" "github.com/ulule/deepcopier" - "github.com/urfave/cli" ) var ( - podStatsFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Show stats for all pods. Only running pods are shown by default.", - }, - cli.BoolFlag{ - Name: "no-stream", - Usage: "Disable streaming stats and only pull the first result, default setting is false", - }, - cli.BoolFlag{ - Name: "no-reset", - Usage: "Disable resetting the screen between intervals", - }, - cli.StringFlag{ - Name: "format", - Usage: "Pretty-print container statistics to JSON or using a Go template", - }, LatestPodFlag, - } + podStatsCommand cliconfig.PodStatsValues podStatsDescription = "Display a live stream of resource usage statistics for the containers in or more pods" - podStatsCommand = cli.Command{ - Name: "stats", - Usage: "Display percentage of CPU, memory, network I/O, block I/O and PIDs for containers in one or more pods", - Description: podStatsDescription, - Flags: sortFlags(podStatsFlags), - Action: podStatsCmd, - ArgsUsage: "[POD_NAME_OR_ID]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _podStatsCommand = &cobra.Command{ + Use: "stats", + Short: "Display percentage of CPU, memory, network I/O, block I/O and PIDs for containers in one or more pods", + Long: podStatsDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podStatsCommand.InputArgs = args + podStatsCommand.GlobalFlags = MainGlobalOpts + return podStatsCmd(&podStatsCommand) + }, + Example: `podman stats -a --no-stream + podman stats --no-reset ctrID + podman stats --no-stream --format "table {{.ID}} {{.Name}} {{.MemUsage}}" ctrID`, } ) -func podStatsCmd(c *cli.Context) error { +func init() { + podStatsCommand.Command = _podStatsCommand + podStatsCommand.SetUsageTemplate(UsageTemplate()) + flags := podStatsCommand.Flags() + flags.BoolVarP(&podStatsCommand.All, "all", "a", false, "Provide stats for all running pods") + flags.StringVar(&podStatsCommand.Format, "format", "", "Pretty-print container statistics to JSON or using a Go template") + flags.BoolVarP(&podStatsCommand.Latest, "latest", "l", false, "Provide stats on the latest pod podman is aware of") + flags.BoolVar(&podStatsCommand.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result, default setting is false") + flags.BoolVar(&podStatsCommand.NoReset, "no-reset", false, "Disable resetting the screen between intervals") + markFlagHiddenForRemoteClient("latest", flags) +} + +func podStatsCmd(c *cliconfig.PodStatsValues) error { var ( podFunc func() ([]*libpod.Pod, error) ) - format := c.String("format") - all := c.Bool("all") - latest := c.Bool("latest") + format := c.Format + all := c.All + latest := c.Latest ctr := 0 if all { ctr += 1 @@ -62,7 +65,7 @@ func podStatsCmd(c *cli.Context) error { if latest { ctr += 1 } - if len(c.Args()) > 0 { + if len(c.InputArgs) > 0 { ctr += 1 } @@ -73,19 +76,19 @@ func podStatsCmd(c *cli.Context) error { all = true } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) times := -1 - if c.Bool("no-stream") { + if c.NoStream { times = 1 } - if len(c.Args()) > 0 { - podFunc = func() ([]*libpod.Pod, error) { return getPodsByList(c.Args(), runtime) } + if len(c.InputArgs) > 0 { + podFunc = func() ([]*libpod.Pod, error) { return getPodsByList(c.InputArgs, runtime) } } else if latest { podFunc = func() ([]*libpod.Pod, error) { latestPod, err := runtime.GetLatestPod() @@ -141,6 +144,25 @@ func podStatsCmd(c *cli.Context) error { step = 0 } + headerNames := make(map[string]string) + if c.Format != "" { + // Make a map of the field names for the headers + v := reflect.ValueOf(podStatOut{}) + t := v.Type() + for i := 0; i < t.NumField(); i++ { + value := strings.ToUpper(splitCamelCase(t.Field(i).Name)) + switch value { + case "CPU": + value = value + " %" + case "MEM": + value = value + " %" + case "MEM USAGE": + value = "MEM USAGE / LIMIT" + } + headerNames[t.Field(i).Name] = value + } + } + for i := 0; i < times; i += step { var newStats []*libpod.PodContainerStats for _, p := range pods { @@ -159,7 +181,7 @@ func podStatsCmd(c *cli.Context) error { newStats = append(newStats, &newPod) } //Output - if strings.ToLower(format) != formats.JSONString && !c.Bool("no-reset") { + if strings.ToLower(format) != formats.JSONString && !c.NoReset { tm.Clear() tm.MoveCursor(1, 1) tm.Flush() @@ -168,7 +190,14 @@ func podStatsCmd(c *cli.Context) error { outputJson(newStats) } else { - outputToStdOut(newStats) + results := podContainerStatsToPodStatOut(newStats) + if len(format) == 0 { + outputToStdOut(results) + } else { + if err := printPSFormat(c.Format, results, headerNames); err != nil { + return err + } + } } time.Sleep(time.Second) previousPodStats := new([]*libpod.PodContainerStats) @@ -182,28 +211,88 @@ func podStatsCmd(c *cli.Context) error { return nil } -func outputToStdOut(stats []*libpod.PodContainerStats) { - outFormat := ("%-14s %-14s %-12s %-6s %-19s %-6s %-19s %-19s %-4s\n") - fmt.Printf(outFormat, "POD", "CID", "NAME", "CPU %", "MEM USAGE/ LIMIT", "MEM %", "NET IO", "BLOCK IO", "PIDS") - for _, i := range stats { - if len(i.ContainerStats) == 0 { - fmt.Printf(outFormat, i.Pod.ID()[:12], "--", "--", "--", "--", "--", "--", "--", "--") - } - for _, c := range i.ContainerStats { - cpu := floatToPercentString(c.CPU) - memUsage := combineHumanValues(c.MemUsage, c.MemLimit) - memPerc := floatToPercentString(c.MemPerc) - netIO := combineHumanValues(c.NetInput, c.NetOutput) - blockIO := combineHumanValues(c.BlockInput, c.BlockOutput) - pids := pidsToString(c.PIDs) - containerName := c.Name - if len(c.Name) > 10 { - containerName = containerName[:10] +func podContainerStatsToPodStatOut(stats []*libpod.PodContainerStats) []*podStatOut { + var out []*podStatOut + for _, p := range stats { + for _, c := range p.ContainerStats { + o := podStatOut{ + CPU: floatToPercentString(c.CPU), + MemUsage: combineHumanValues(c.MemUsage, c.MemLimit), + Mem: floatToPercentString(c.MemPerc), + NetIO: combineHumanValues(c.NetInput, c.NetOutput), + BlockIO: combineHumanValues(c.BlockInput, c.BlockOutput), + PIDS: pidsToString(c.PIDs), + CID: c.ContainerID[:12], + Name: c.Name, + Pod: p.Pod.ID()[:12], } - fmt.Printf(outFormat, i.Pod.ID()[:12], c.ContainerID[:12], containerName, cpu, memUsage, memPerc, netIO, blockIO, pids) + out = append(out, &o) + } + } + return out +} + +type podStatOut struct { + CPU string + MemUsage string + Mem string + NetIO string + BlockIO string + PIDS string + Pod string + CID string + Name string +} + +func printPSFormat(format string, stats []*podStatOut, headerNames map[string]string) error { + if len(stats) == 0 { + return nil + } + + // Use a tabwriter to align column format + w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) + // Spit out the header if "table" is present in the format + if strings.HasPrefix(format, "table") { + hformat := strings.Replace(strings.TrimSpace(format[5:]), " ", "\t", -1) + format = hformat + headerTmpl, err := template.New("header").Parse(hformat) + if err != nil { + return err + } + if err := headerTmpl.Execute(w, headerNames); err != nil { + return err + } + fmt.Fprintln(w, "") + } + + // Spit out the data rows now + dataTmpl, err := template.New("data").Parse(format) + if err != nil { + return err + } + for _, container := range stats { + if err := dataTmpl.Execute(w, container); err != nil { + return err + } + fmt.Fprintln(w, "") + } + // Flush the writer + return w.Flush() + +} + +func outputToStdOut(stats []*podStatOut) { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + outFormat := ("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n") + fmt.Fprintf(w, outFormat, "POD", "CID", "NAME", "CPU %", "MEM USAGE/ LIMIT", "MEM %", "NET IO", "BLOCK IO", "PIDS") + for _, i := range stats { + if len(stats) == 0 { + fmt.Fprintf(w, outFormat, i.Pod, "--", "--", "--", "--", "--", "--", "--", "--") + } else { + fmt.Fprintf(w, outFormat, i.Pod, i.CID, i.Name, i.CPU, i.MemUsage, i.Mem, i.NetIO, i.BlockIO, i.PIDS) } } - fmt.Println() + w.Flush() } func getPreviousPodContainerStats(podID string, prev []*libpod.PodContainerStats) map[string]*libpod.ContainerStats { diff --git a/cmd/podman/pod_stop.go b/cmd/podman/pod_stop.go index 148b4d518..62d0d4aa5 100644 --- a/cmd/podman/pod_stop.go +++ b/cmd/podman/pod_stop.go @@ -2,83 +2,71 @@ package main import ( "fmt" - "github.com/containers/libpod/cmd/podman/libpodruntime" + + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - podStopFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Stop all running pods", - }, - LatestPodFlag, - cli.UintFlag{ - Name: "timeout, time, t", - Usage: "Seconds to wait for pod stop before killing the container", - }, - } + podStopCommand cliconfig.PodStopValues podStopDescription = ` podman pod stop Stops one or more running pods. The pod name or ID can be used. ` - podStopCommand = cli.Command{ - Name: "stop", - Usage: "Stop one or more pods", - Description: podStopDescription, - Flags: sortFlags(podStopFlags), - Action: podStopCmd, - ArgsUsage: "POD-NAME [POD-NAME ...]", - OnUsageError: usageErrorHandler, + _podStopCommand = &cobra.Command{ + Use: "stop", + Short: "Stop one or more pods", + Long: podStopDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podStopCommand.InputArgs = args + podStopCommand.GlobalFlags = MainGlobalOpts + return podStopCmd(&podStopCommand) + }, + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, false) + }, + Example: `podman pod stop mywebserverpod + podman pod stop --latest + podman pod stop --timeout 0 490eb 3557fb`, } ) -func podStopCmd(c *cli.Context) error { - timeout := -1 - if err := checkMutuallyExclusiveFlags(c); err != nil { - return err - } +func init() { + podStopCommand.Command = _podStopCommand + podStopCommand.SetUsageTemplate(UsageTemplate()) + flags := podStopCommand.Flags() + flags.BoolVarP(&podStopCommand.All, "all", "a", false, "Stop all running pods") + flags.BoolVarP(&podStopCommand.Latest, "latest", "l", false, "Stop the latest pod podman is aware of") + flags.UintVarP(&podStopCommand.Timeout, "timeout", "t", 0, "Seconds to wait for pod stop before killing the container") + markFlagHiddenForRemoteClient("latest", flags) +} - runtime, err := libpodruntime.GetRuntime(c) +func podStopCmd(c *cliconfig.PodStopValues) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - // getPodsFromContext returns an error when a requested pod - // isn't found. The only fatal error scenerio is when there are no pods - // in which case the following loop will be skipped. - pods, lastError := getPodsFromContext(c, runtime) - - ctx := getContext() - - if c.IsSet("timeout") { - timeout = int(c.Uint("timeout")) + podStopIds, podStopErrors := runtime.StopPods(getContext(), c) + for _, p := range podStopIds { + fmt.Println(p) + } + if len(podStopErrors) == 0 { + return nil } - for _, pod := range pods { - // set cleanup to true to clean mounts and namespaces - ctr_errs, err := pod.StopWithTimeout(ctx, true, timeout) - if ctr_errs != nil { - for ctr, err := range ctr_errs { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "unable to stop container %q on pod %q", ctr, pod.ID()) - } - continue - } - if err != nil { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "unable to stop pod %q", pod.ID()) - continue - } - fmt.Println(pod.ID()) + // Grab the last error + lastError := podStopErrors[len(podStopErrors)-1] + // Remove the last error from the error slice + podStopErrors = podStopErrors[:len(podStopErrors)-1] + + for _, err := range podStopErrors { + logrus.Errorf("%q", err) } return lastError } diff --git a/cmd/podman/pod_top.go b/cmd/podman/pod_top.go index 1bd1287db..790118496 100644 --- a/cmd/podman/pod_top.go +++ b/cmd/podman/pod_top.go @@ -6,21 +6,17 @@ import ( "strings" "text/tabwriter" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - podTopFlags = []cli.Flag{ - LatestFlag, - cli.BoolFlag{ - Name: "list-descriptors", - Hidden: true, - }, - } + podTopCommand cliconfig.PodTopValues + podTopDescription = fmt.Sprintf(`Display the running processes containers in a pod. Specify format descriptors to alter the output. You may run "podman pod top -l pid pcpu seccomp" to print the process ID, the CPU percentage and the seccomp mode of each process of @@ -28,24 +24,37 @@ the latest pod. %s `, getDescriptorString()) - podTopCommand = cli.Command{ - Name: "top", - Usage: "Display the running processes of containers in a pod", - Description: podTopDescription, - Flags: sortFlags(podTopFlags), - Action: podTopCmd, - ArgsUsage: "POD-NAME [format descriptors]", - SkipArgReorder: true, - OnUsageError: usageErrorHandler, + _podTopCommand = &cobra.Command{ + Use: "top", + Short: "Display the running processes of containers in a pod", + Long: podTopDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podTopCommand.InputArgs = args + podTopCommand.GlobalFlags = MainGlobalOpts + return podTopCmd(&podTopCommand) + }, + Example: `podman top ctrID + podman top --latest + podman top --latest pid seccomp args %C`, } ) -func podTopCmd(c *cli.Context) error { +func init() { + podTopCommand.Command = _podTopCommand + podTopCommand.SetUsageTemplate(UsageTemplate()) + flags := podTopCommand.Flags() + flags.BoolVarP(&podTopCommand.Latest, "latest,", "l", false, "Act on the latest pod podman is aware of") + flags.BoolVar(&podTopCommand.ListDescriptors, "list-descriptors", false, "") + flags.MarkHidden("list-descriptors") + +} + +func podTopCmd(c *cliconfig.PodTopValues) error { var pod *libpod.Pod var err error - args := c.Args() + args := c.InputArgs - if c.Bool("list-descriptors") { + if c.ListDescriptors { descriptors, err := libpod.GetContainerPidInformationDescriptors() if err != nil { return err @@ -54,21 +63,18 @@ func podTopCmd(c *cli.Context) error { return nil } - if len(args) < 1 && !c.Bool("latest") { + if len(args) < 1 && !c.Latest { return errors.Errorf("you must provide the name or id of a running pod") } - if err := validateFlags(c, podTopFlags); err != nil { - return err - } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) var descriptors []string - if c.Bool("latest") { + if c.Latest { descriptors = args pod, err = runtime.GetLatestPod() } else { diff --git a/cmd/podman/pod_unpause.go b/cmd/podman/pod_unpause.go index ed1a00cf8..16481d0e2 100644 --- a/cmd/podman/pod_unpause.go +++ b/cmd/podman/pod_unpause.go @@ -3,72 +3,71 @@ package main import ( "fmt" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - podUnpauseFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Unpause all paused pods", + podUnpauseCommand cliconfig.PodUnpauseValues + podUnpauseDescription = `Unpauses one or more pods. The pod name or ID can be used.` + _podUnpauseCommand = &cobra.Command{ + Use: "unpause", + Short: "Unpause one or more pods", + Long: podUnpauseDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podUnpauseCommand.InputArgs = args + podUnpauseCommand.GlobalFlags = MainGlobalOpts + return podUnpauseCmd(&podUnpauseCommand) }, - LatestPodFlag, - } - podUnpauseDescription = ` - Unpauses one or more pods. The pod name or ID can be used. -` - - podUnpauseCommand = cli.Command{ - Name: "unpause", - Usage: "Unpause one or more pods", - Description: podUnpauseDescription, - Flags: sortFlags(podUnpauseFlags), - Action: podUnpauseCmd, - ArgsUsage: "POD-NAME|POD-ID [POD-NAME|POD-ID ...]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, false) + }, + Example: `podman pod unpause podID1 podID2 + podman pod unpause --all + podman pod unpause --latest`, } ) -func podUnpauseCmd(c *cli.Context) error { - if err := checkMutuallyExclusiveFlags(c); err != nil { - return err - } +func init() { + podUnpauseCommand.Command = _podUnpauseCommand + podUnpauseCommand.SetUsageTemplate(UsageTemplate()) + flags := podUnpauseCommand.Flags() + flags.BoolVarP(&podUnpauseCommand.All, "all", "a", false, "Unpause all running pods") + flags.BoolVarP(&podUnpauseCommand.Latest, "latest", "l", false, "Unpause the latest pod podman is aware of") + markFlagHiddenForRemoteClient("latest", flags) +} - runtime, err := libpodruntime.GetRuntime(c) +func podUnpauseCmd(c *cliconfig.PodUnpauseValues) error { + var lastError error + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - // getPodsFromContext returns an error when a requested pod - // isn't found. The only fatal error scenerio is when there are no pods - // in which case the following loop will be skipped. - pods, lastError := getPodsFromContext(c, runtime) + unpauseIDs, conErrors, unpauseErrors := runtime.UnpausePods(c) - for _, pod := range pods { - ctr_errs, err := pod.Unpause() - if ctr_errs != nil { - for ctr, err := range ctr_errs { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "unable to unpause container %q on pod %q", ctr, pod.ID()) - } - continue - } - if err != nil { + for _, p := range unpauseIDs { + fmt.Println(p) + } + if conErrors != nil && len(conErrors) > 0 { + for ctr, err := range conErrors { if lastError != nil { logrus.Errorf("%q", lastError) } - lastError = errors.Wrapf(err, "unable to unpause pod %q", pod.ID()) - continue + lastError = errors.Wrapf(err, "unable to unpause container %s", ctr) } - fmt.Println(pod.ID()) } - + if len(unpauseErrors) > 0 { + lastError = unpauseErrors[len(unpauseErrors)-1] + // Remove the last error from the error slice + unpauseErrors = unpauseErrors[:len(unpauseErrors)-1] + } + for _, err := range unpauseErrors { + logrus.Errorf("%q", err) + } return lastError } diff --git a/cmd/podman/port.go b/cmd/podman/port.go index 6875c648a..bcf372a51 100644 --- a/cmd/podman/port.go +++ b/cmd/podman/port.go @@ -5,38 +5,50 @@ import ( "strconv" "strings" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - portFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Display port information for all containers", - }, - LatestFlag, - } + portCommand cliconfig.PortValues portDescription = ` podman port List port mappings for the CONTAINER, or lookup the public-facing port that is NAT-ed to the PRIVATE_PORT ` - - portCommand = cli.Command{ - Name: "port", - Usage: "List port mappings or a specific mapping for the container", - Description: portDescription, - Flags: sortFlags(portFlags), - Action: portCmd, - ArgsUsage: "CONTAINER-NAME [mapping]", - OnUsageError: usageErrorHandler, + _portCommand = &cobra.Command{ + Use: "port", + Short: "List port mappings or a specific mapping for the container", + Long: portDescription, + RunE: func(cmd *cobra.Command, args []string) error { + portCommand.InputArgs = args + portCommand.GlobalFlags = MainGlobalOpts + return portCmd(&portCommand) + }, + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, true) + }, + Example: `podman port --all + podman port ctrID 80/tcp + podman port --latest 80`, } ) -func portCmd(c *cli.Context) error { +func init() { + portCommand.Command = _portCommand + portCommand.SetUsageTemplate(UsageTemplate()) + flags := portCommand.Flags() + + flags.BoolVarP(&portCommand.All, "all", "a", false, "Display port information for all containers") + flags.BoolVarP(&portCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + + markFlagHiddenForRemoteClient("latest", flags) +} + +func portCmd(c *cliconfig.PortValues) error { var ( userProto, containerName string userPort int @@ -44,29 +56,26 @@ func portCmd(c *cli.Context) error { containers []*libpod.Container ) - args := c.Args() - if err := validateFlags(c, portFlags); err != nil { - return err - } + args := c.InputArgs - if c.Bool("latest") && c.Bool("all") { + if c.Latest && c.All { return errors.Errorf("the 'all' and 'latest' options cannot be used together") } - if c.Bool("all") && len(args) > 0 { + if c.All && len(args) > 0 { return errors.Errorf("no additional arguments can be used with 'all'") } - if len(args) == 0 && !c.Bool("latest") && !c.Bool("all") { + if len(args) == 0 && !c.Latest && !c.All { return errors.Errorf("you must supply a running container name or id") } - if !c.Bool("latest") && !c.Bool("all") { + if !c.Latest && !c.All { containerName = args[0] } port := "" - if len(args) > 1 && !c.Bool("latest") { + if len(args) > 1 && !c.Latest { port = args[1] } - if len(args) == 1 && c.Bool("latest") { + if len(args) == 1 && c.Latest { port = args[0] } if port != "" { @@ -90,19 +99,19 @@ func portCmd(c *cli.Context) error { } } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if !c.Bool("latest") && !c.Bool("all") { + if !c.Latest && !c.All { container, err = runtime.LookupContainer(containerName) if err != nil { return errors.Wrapf(err, "unable to find container %s", containerName) } containers = append(containers, container) - } else if c.Bool("latest") { + } else if c.Latest { container, err = runtime.GetLatestContainer() if err != nil { return errors.Wrapf(err, "unable to get last created container") @@ -119,11 +128,16 @@ func portCmd(c *cli.Context) error { if state, _ := con.State(); state != libpod.ContainerStateRunning { continue } - if c.Bool("all") { + if c.All { fmt.Println(con.ID()) } + + portmappings, err := con.PortMappings() + if err != nil { + return err + } // Iterate mappings - for _, v := range con.Config().PortMappings { + for _, v := range portmappings { hostIP := v.HostIP // Set host IP to 0.0.0.0 if blank if hostIP == "" { diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go index 1708c671c..9c165b836 100644 --- a/cmd/podman/ps.go +++ b/cmd/podman/ps.go @@ -12,6 +12,7 @@ import ( "text/tabwriter" "time" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" @@ -19,9 +20,10 @@ import ( "github.com/containers/libpod/pkg/util" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-units" + opentracing "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/fields" ) @@ -153,112 +155,86 @@ func (a psSortedSize) Less(i, j int) bool { } var ( - psFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Show all the containers, default is only running containers", - }, - cli.StringSliceFlag{ - Name: "filter, f", - Usage: "Filter output based on conditions given", - }, - cli.StringFlag{ - Name: "format", - Usage: "Pretty-print containers to JSON or using a Go template", - }, - cli.IntFlag{ - Name: "last, n", - Usage: "Print the n last created containers (all states)", - Value: -1, - }, - cli.BoolFlag{ - Name: "latest, l", - Usage: "Show the latest container created (all states)", - }, - cli.BoolFlag{ - Name: "namespace, ns", - Usage: "Display namespace information", - }, - cli.BoolFlag{ - Name: "no-trunc", - Usage: "Display the extended information", - }, - cli.BoolFlag{ - Name: "pod, p", - Usage: "Print the ID and name of the pod the containers are associated with", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Print the numeric IDs of the containers only", - }, - cli.BoolFlag{ - Name: "size, s", - Usage: "Display the total file sizes", - }, - cli.StringFlag{ - Name: "sort", - Usage: "Sort output by command, created, id, image, names, runningfor, size, or status", - Value: "created", - }, - cli.BoolFlag{ - Name: "sync", - Usage: "Sync container state with OCI runtime", - }, - } + psCommand cliconfig.PsValues psDescription = "Prints out information about the containers" - psCommand = cli.Command{ - Name: "list", - Aliases: []string{"ls", "ps"}, - Usage: "List containers", - Description: psDescription, - Flags: sortFlags(psFlags), - Action: psCmd, - ArgsUsage: "", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _psCommand = &cobra.Command{ + Use: "list", + Aliases: []string{"ls", "ps"}, + Short: "List containers", + Long: psDescription, + RunE: func(cmd *cobra.Command, args []string) error { + psCommand.InputArgs = args + psCommand.GlobalFlags = MainGlobalOpts + return psCmd(&psCommand) + }, + Example: `podman ps -a + podman ps -a --format "{{.ID}} {{.Image}} {{.Labels}} {{.Mounts}}" + podman ps --size --sort names`, } ) -func psCmd(c *cli.Context) error { +func init() { + psCommand.Command = _psCommand + psCommand.SetUsageTemplate(UsageTemplate()) + flags := psCommand.Flags() + flags.BoolVarP(&psCommand.All, "all", "a", false, "Show all the containers, default is only running containers") + flags.StringSliceVarP(&psCommand.Filter, "filter", "f", []string{}, "Filter output based on conditions given") + flags.StringVar(&psCommand.Format, "format", "", "Pretty-print containers to JSON or using a Go template") + flags.IntVarP(&psCommand.Last, "last", "n", -1, "Print the n last created containers (all states)") + flags.BoolVarP(&psCommand.Latest, "latest", "l", false, "Show the latest container created (all states)") + flags.BoolVar(&psCommand.Namespace, "namespace", false, "Display namespace information") + flags.BoolVar(&psCommand.Namespace, "ns", false, "Display namespace information") + flags.BoolVar(&psCommand.NoTrunct, "no-trunc", false, "Display the extended information") + flags.BoolVarP(&psCommand.Pod, "pod", "p", false, "Print the ID and name of the pod the containers are associated with") + flags.BoolVarP(&psCommand.Quiet, "quiet", "q", false, "Print the numeric IDs of the containers only") + flags.BoolVarP(&psCommand.Size, "size", "s", false, "Display the total file sizes") + flags.StringVar(&psCommand.Sort, "sort", "created", "Sort output by command, created, id, image, names, runningfor, size, or status") + flags.BoolVar(&psCommand.Sync, "sync", false, "Sync container state with OCI runtime") + + markFlagHiddenForRemoteClient("latest", flags) +} + +func psCmd(c *cliconfig.PsValues) error { + if c.Bool("trace") { + span, _ := opentracing.StartSpanFromContext(Ctx, "psCmd") + defer span.Finish() + } + var ( filterFuncs []libpod.ContainerFilter outputContainers []*libpod.Container ) - if err := validateFlags(c, psFlags); err != nil { - return err - } - if err := checkFlagsPassed(c); err != nil { return errors.Wrapf(err, "error with flags passed") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - if len(c.Args()) > 0 { + if len(c.InputArgs) > 0 { return errors.Errorf("too many arguments, ps takes no arguments") } opts := shared.PsOptions{ - All: c.Bool("all"), - Format: c.String("format"), - Last: c.Int("last"), - Latest: c.Bool("latest"), - NoTrunc: c.Bool("no-trunc"), - Pod: c.Bool("pod"), - Quiet: c.Bool("quiet"), - Size: c.Bool("size"), - Namespace: c.Bool("namespace"), - Sort: c.String("sort"), - Sync: c.Bool("sync"), - } - - filters := c.StringSlice("filter") + All: c.All, + Format: c.Format, + Last: c.Last, + Latest: c.Latest, + NoTrunc: c.NoTrunct, + Pod: c.Pod, + Quiet: c.Quiet, + Size: c.Size, + Namespace: c.Namespace, + Sort: c.Sort, + Sync: c.Sync, + } + + filters := c.Filter if len(filters) > 0 { for _, f := range filters { filterSplit := strings.SplitN(f, "=", 2) @@ -299,7 +275,7 @@ func psCmd(c *cli.Context) error { maxWorkers := shared.Parallelize("ps") if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalInt("max-workers") + maxWorkers = c.GlobalFlags.MaxWorks } logrus.Debugf("Setting maximum workers to %d", maxWorkers) @@ -384,20 +360,20 @@ func printQuiet(containers []shared.PsContainerOutput) error { } // checkFlagsPassed checks if mutually exclusive flags are passed together -func checkFlagsPassed(c *cli.Context) error { +func checkFlagsPassed(c *cliconfig.PsValues) error { // latest, and last are mutually exclusive. - if c.Int("last") >= 0 && c.Bool("latest") { + if c.Last >= 0 && c.Latest { return errors.Errorf("last and latest are mutually exclusive") } // Quiet conflicts with size, namespace, and format with a Go template - if c.Bool("quiet") { - if c.Bool("size") || c.Bool("namespace") || (c.IsSet("format") && - c.String("format") != formats.JSONString) { + if c.Quiet { + if c.Size || c.Namespace || (c.Flag("format").Changed && + c.Format != formats.JSONString) { return errors.Errorf("quiet conflicts with size, namespace, and format with go template") } } // Size and namespace conflict with each other - if c.Bool("size") && c.Bool("namespace") { + if c.Size && c.Namespace { return errors.Errorf("size and namespace options conflict") } return nil diff --git a/cmd/podman/pull.go b/cmd/podman/pull.go index 2349265d0..71f555162 100644 --- a/cmd/podman/pull.go +++ b/cmd/podman/pull.go @@ -6,88 +6,93 @@ import ( "os" "strings" + "github.com/containers/image/docker" dockerarchive "github.com/containers/image/docker/archive" "github.com/containers/image/transports/alltransports" "github.com/containers/image/types" - "github.com/containers/libpod/libpod/adapter" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/libpod/common" image2 "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/adapter" "github.com/containers/libpod/pkg/util" + opentracing "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - pullFlags = []cli.Flag{ - cli.StringFlag{ - Name: "authfile", - Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override. ", - }, - cli.StringFlag{ - Name: "cert-dir", - Usage: "`pathname` of a directory containing TLS certificates and keys", - }, - cli.StringFlag{ - Name: "creds", - Usage: "`credentials` (USERNAME:PASSWORD) to use for authenticating to a registry", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Suppress output information when pulling images", - }, - cli.StringFlag{ - Name: "signature-policy", - Usage: "`pathname` of signature policy file (not usually used)", - }, - cli.BoolTFlag{ - Name: "tls-verify", - Usage: "Require HTTPS and verify certificates when contacting registries (default: true)", - }, - } - + pullCommand cliconfig.PullValues pullDescription = ` Pulls an image from a registry and stores it locally. An image can be pulled using its tag or digest. If a tag is not specified, the image with the 'latest' tag (if it exists) is pulled ` - pullCommand = cli.Command{ - Name: "pull", - Usage: "Pull an image from a registry", - Description: pullDescription, - Flags: sortFlags(pullFlags), - Action: pullCmd, - ArgsUsage: "", - OnUsageError: usageErrorHandler, + _pullCommand = &cobra.Command{ + Use: "pull", + Short: "Pull an image from a registry", + Long: pullDescription, + RunE: func(cmd *cobra.Command, args []string) error { + pullCommand.InputArgs = args + pullCommand.GlobalFlags = MainGlobalOpts + return pullCmd(&pullCommand) + }, + Example: `podman pull imageName + podman pull --cert-dir image/certs --authfile temp-auths/myauths.json docker://docker.io/myrepo/finaltest + podman pull fedora:latest`, } ) +func init() { + pullCommand.Command = _pullCommand + pullCommand.SetUsageTemplate(UsageTemplate()) + flags := pullCommand.Flags() + flags.BoolVar(&pullCommand.AllTags, "all-tags", false, "All tagged images inthe repository will be pulled") + flags.StringVar(&pullCommand.Authfile, "authfile", "", "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&pullCommand.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") + flags.StringVar(&pullCommand.Creds, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") + flags.BoolVarP(&pullCommand.Quiet, "quiet", "q", false, "Suppress output information when pulling images") + flags.StringVar(&pullCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") + flags.BoolVar(&pullCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") + +} + // pullCmd gets the data from the command line and calls pullImage // to copy an image from a registry to a local machine -func pullCmd(c *cli.Context) error { - runtime, err := adapter.GetRuntime(c) +func pullCmd(c *cliconfig.PullValues) error { + if c.Bool("trace") { + span, _ := opentracing.StartSpanFromContext(Ctx, "pullCmd") + defer span.Finish() + } + + runtime, err := adapter.GetRuntime(&c.PodmanCommand) + if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - args := c.Args() + args := c.InputArgs if len(args) == 0 { - logrus.Errorf("an image name must be specified") - return nil + return errors.Errorf("an image name must be specified") } if len(args) > 1 { - logrus.Errorf("too many arguments. Requires exactly 1") - return nil + return errors.Errorf("too many arguments. Requires exactly 1") } - if err := validateFlags(c, pullFlags); err != nil { - return err + + arr := strings.SplitN(args[0], ":", 2) + if len(arr) == 2 { + if c.Bool("all-tags") { + return errors.Errorf("tag can't be used with --all-tags") + } } + ctx := getContext() image := args[0] var registryCreds *types.DockerAuthConfig - if c.IsSet("creds") { - creds, err := util.ParseRegistryCreds(c.String("creds")) + if c.Flag("creds").Changed { + creds, err := util.ParseRegistryCreds(c.Creds) if err != nil { return err } @@ -96,18 +101,17 @@ func pullCmd(c *cli.Context) error { var ( writer io.Writer - imgID string ) - if !c.Bool("quiet") { + if !c.Quiet { writer = os.Stderr } dockerRegistryOptions := image2.DockerRegistryOptions{ DockerRegistryCreds: registryCreds, - DockerCertPath: c.String("cert-dir"), + DockerCertPath: c.CertDir, } - if c.IsSet("tls-verify") { - dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify")) + if c.Flag("tls-verify").Changed { + dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) } // Possible for docker-archive to have multiple tags, so use LoadFromArchiveReference instead @@ -116,22 +120,62 @@ func pullCmd(c *cli.Context) error { if err != nil { return errors.Wrapf(err, "error parsing %q", image) } - newImage, err := runtime.LoadFromArchiveReference(getContext(), srcRef, c.String("signature-policy"), writer) + newImage, err := runtime.LoadFromArchiveReference(getContext(), srcRef, c.SignaturePolicy, writer) if err != nil { return errors.Wrapf(err, "error pulling image from %q", image) } - imgID = newImage[0].ID() + fmt.Println(newImage[0].ID()) } else { authfile := getAuthFile(c.String("authfile")) - newImage, err := runtime.New(getContext(), image, c.String("signature-policy"), authfile, writer, &dockerRegistryOptions, image2.SigningOptions{}, true, nil) + spec := image + systemContext := common.GetSystemContext("", authfile, false) + srcRef, err := alltransports.ParseImageName(spec) if err != nil { + dockerTransport := "docker://" + logrus.Debugf("error parsing image name %q, trying with transport %q: %v", spec, dockerTransport, err) + spec = dockerTransport + spec + srcRef2, err2 := alltransports.ParseImageName(spec) + if err2 != nil { + return errors.Wrapf(err2, "error parsing image name %q", image) + } + srcRef = srcRef2 + } + var names []string + if c.Bool("all-tags") { + if srcRef.DockerReference() == nil { + return errors.New("Non-docker transport is currently not supported") + } + tags, err := docker.GetRepositoryTags(ctx, systemContext, srcRef) + if err != nil { + return errors.Wrapf(err, "error getting repository tags") + } + for _, tag := range tags { + name := spec + ":" + tag + names = append(names, name) + } + } else { + names = append(names, spec) + } + var foundIDs []string + foundImage := true + for _, name := range names { + newImage, err := runtime.New(getContext(), name, c.String("signature-policy"), authfile, writer, &dockerRegistryOptions, image2.SigningOptions{}, true, nil) + if err != nil { + println(errors.Wrapf(err, "error pulling image %q", name)) + foundImage = false + continue + } + foundIDs = append(foundIDs, newImage.ID()) + } + if len(names) == 1 && !foundImage { return errors.Wrapf(err, "error pulling image %q", image) } - imgID = newImage.ID() - } - - // Intentionally choosing to ignore if there is an error because - // outputting the image ID is a NTH and not integral to the pull - fmt.Println(imgID) + if len(names) > 1 { + fmt.Println("Pulled Images:") + } + for _, id := range foundIDs { + fmt.Println(id) + } + } // end else if strings.HasPrefix(image, dockerarchive.Transport.Name()+":") return nil } diff --git a/cmd/podman/push.go b/cmd/podman/push.go index 361a25e35..56261a8d3 100644 --- a/cmd/podman/push.go +++ b/cmd/podman/push.go @@ -9,81 +9,61 @@ import ( "github.com/containers/image/directory" "github.com/containers/image/manifest" "github.com/containers/image/types" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/adapter" "github.com/containers/libpod/pkg/util" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - pushFlags = []cli.Flag{ - cli.StringFlag{ - Name: "signature-policy", - Usage: "`Pathname` of signature policy file (not usually used)", - Hidden: true, - }, - cli.StringFlag{ - Name: "creds", - Usage: "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry", - }, - cli.StringFlag{ - Name: "cert-dir", - Usage: "`Pathname` of a directory containing TLS certificates and keys", - }, - cli.BoolFlag{ - Name: "compress", - Usage: "Compress tarball image layers when pushing to a directory using the 'dir' transport. (default is same compression type as source)", - }, - cli.StringFlag{ - Name: "format, f", - Usage: "Manifest type (oci, v2s1, or v2s2) to use when pushing an image using the 'dir:' transport (default is manifest type of source)", - }, - cli.BoolTFlag{ - Name: "tls-verify", - Usage: "Require HTTPS and verify certificates when contacting registries (default: true)", - }, - cli.BoolFlag{ - Name: "remove-signatures", - Usage: "Discard any pre-existing signatures in the image", - }, - cli.StringFlag{ - Name: "sign-by", - Usage: "Add a signature at the destination using the specified key", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Don't output progress information when pushing images", - }, - cli.StringFlag{ - Name: "authfile", - Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override. ", - }, - } + pushCommand cliconfig.PushValues pushDescription = fmt.Sprintf(` Pushes an image to a specified location. The Image "DESTINATION" uses a "transport":"details" format. See podman-push(1) section "DESTINATION" for the expected format`) - pushCommand = cli.Command{ - Name: "push", - Usage: "Push an image to a specified destination", - Description: pushDescription, - Flags: sortFlags(pushFlags), - Action: pushCmd, - ArgsUsage: "IMAGE DESTINATION", - OnUsageError: usageErrorHandler, + _pushCommand = &cobra.Command{ + Use: "push", + Short: "Push an image to a specified destination", + Long: pushDescription, + RunE: func(cmd *cobra.Command, args []string) error { + pushCommand.InputArgs = args + pushCommand.GlobalFlags = MainGlobalOpts + return pushCmd(&pushCommand) + }, + Example: `podman push imageID docker://registry.example.com/repository:tag + podman push imageID oci-archive:/path/to/layout:image:tag + podman push --authfile temp-auths/myauths.json alpine docker://docker.io/myrepo/alpine`, } ) -func pushCmd(c *cli.Context) error { +func init() { + pushCommand.Command = _pushCommand + pushCommand.SetUsageTemplate(UsageTemplate()) + flags := pushCommand.Flags() + flags.MarkHidden("signature-policy") + flags.StringVar(&pushCommand.Authfile, "authfile", "", "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&pushCommand.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") + flags.BoolVar(&pushCommand.Compress, "compress", false, "Compress tarball image layers when pushing to a directory using the 'dir' transport. (default is same compression type as source)") + flags.StringVar(&pushCommand.Creds, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") + flags.StringVarP(&pushCommand.Format, "format", "f", "", "Manifest type (oci, v2s1, or v2s2) to use when pushing an image using the 'dir:' transport (default is manifest type of source)") + flags.BoolVarP(&pushCommand.Quiet, "quiet", "q", false, "Don't output progress information when pushing images") + flags.BoolVar(&pushCommand.RemoveSignatures, "remove-signatures", false, "Discard any pre-existing signatures in the image") + flags.StringVar(&pushCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") + flags.StringVar(&pushCommand.SignBy, "sign-by", "", "Add a signature at the destination using the specified key") + flags.BoolVar(&pushCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") +} + +func pushCmd(c *cliconfig.PushValues) error { var ( registryCreds *types.DockerAuthConfig destName string ) - args := c.Args() + args := c.InputArgs if len(args) == 0 || len(args) > 2 { return errors.New("podman push requires at least one image name, and optionally a second to specify a different destination name") } @@ -94,43 +74,40 @@ func pushCmd(c *cli.Context) error { case 2: destName = args[1] } - if err := validateFlags(c, pushFlags); err != nil { - return err - } // --compress and --format can only be used for the "dir" transport splitArg := strings.SplitN(destName, ":", 2) - if c.IsSet("compress") || c.IsSet("format") { + if c.Flag("compress").Changed || c.Flag("format").Changed { if splitArg[0] != directory.Transport.Name() { return errors.Errorf("--compress and --format can be set only when pushing to a directory using the 'dir' transport") } } - certPath := c.String("cert-dir") - removeSignatures := c.Bool("remove-signatures") - signBy := c.String("sign-by") + certPath := c.CertDir + removeSignatures := c.RemoveSignatures + signBy := c.SignBy - if c.IsSet("creds") { - creds, err := util.ParseRegistryCreds(c.String("creds")) + if c.Flag("creds").Changed { + creds, err := util.ParseRegistryCreds(c.Creds) if err != nil { return err } registryCreds = creds } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not create runtime") } defer runtime.Shutdown(false) var writer io.Writer - if !c.Bool("quiet") { + if !c.Quiet { writer = os.Stderr } var manifestType string - if c.IsSet("format") { + if c.Flag("format").Changed { switch c.String("format") { case "oci": manifestType = imgspecv1.MediaTypeImageManifest @@ -147,8 +124,8 @@ func pushCmd(c *cli.Context) error { DockerRegistryCreds: registryCreds, DockerCertPath: certPath, } - if c.IsSet("tls-verify") { - dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify")) + if c.Flag("tls-verify").Changed { + dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) } so := image.SigningOptions{ @@ -156,12 +133,7 @@ func pushCmd(c *cli.Context) error { SignBy: signBy, } - newImage, err := runtime.ImageRuntime().NewFromLocal(srcName) - if err != nil { - return err - } - - authfile := getAuthFile(c.String("authfile")) + authfile := getAuthFile(c.Authfile) - return newImage.PushImageToHeuristicDestination(getContext(), destName, manifestType, authfile, c.String("signature-policy"), writer, c.Bool("compress"), so, &dockerRegistryOptions, nil) + return runtime.Push(getContext(), srcName, destName, manifestType, authfile, c.SignaturePolicy, writer, c.Compress, so, &dockerRegistryOptions, nil) } diff --git a/cmd/podman/refresh.go b/cmd/podman/refresh.go index b07376170..641748452 100644 --- a/cmd/podman/refresh.go +++ b/cmd/podman/refresh.go @@ -4,37 +4,38 @@ import ( "fmt" "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - refreshFlags = []cli.Flag{} - + refreshCommand cliconfig.RefreshValues refreshDescription = "The refresh command resets the state of all containers to handle database changes after a Podman upgrade. All running containers will be restarted." - - refreshCommand = cli.Command{ - Name: "refresh", - Usage: "Refresh container state", - Description: refreshDescription, - Flags: sortFlags(refreshFlags), - Action: refreshCmd, - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _refreshCommand = &cobra.Command{ + Use: "refresh", + Short: "Refresh container state", + Long: refreshDescription, + RunE: func(cmd *cobra.Command, args []string) error { + refreshCommand.InputArgs = args + refreshCommand.GlobalFlags = MainGlobalOpts + return refreshCmd(&refreshCommand) + }, } ) -func refreshCmd(c *cli.Context) error { - if len(c.Args()) > 0 { - return errors.Errorf("refresh does not accept any arguments") - } +func init() { + refreshCommand.Command = _refreshCommand + refreshCommand.SetUsageTemplate(UsageTemplate()) +} - if err := validateFlags(c, refreshFlags); err != nil { - return err +func refreshCmd(c *cliconfig.RefreshValues) error { + if len(c.InputArgs) > 0 { + return errors.Errorf("refresh does not accept any arguments") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/restart.go b/cmd/podman/restart.go index 2e52ce5e4..58fb38874 100644 --- a/cmd/podman/restart.go +++ b/cmd/podman/restart.go @@ -4,47 +4,51 @@ import ( "fmt" "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - restartFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Restart all non-running containers", - }, - cli.BoolFlag{ - Name: "running", - Usage: "Restart only running containers when --all is used", + restartCommand cliconfig.RestartValues + restartDescription = `Restarts one or more running containers. The container ID or name can be used. A timeout before forcibly stopping can be set, but defaults to 10 seconds` + _restartCommand = &cobra.Command{ + Use: "restart", + Short: "Restart one or more containers", + Long: restartDescription, + RunE: func(cmd *cobra.Command, args []string) error { + restartCommand.InputArgs = args + restartCommand.GlobalFlags = MainGlobalOpts + return restartCmd(&restartCommand) }, - cli.UintFlag{ - Name: "timeout, time, t", - Usage: "Seconds to wait for stop before killing the container", - Value: libpod.CtrRemoveTimeout, + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, false) }, - LatestFlag, - } - restartDescription = `Restarts one or more running containers. The container ID or name can be used. A timeout before forcibly stopping can be set, but defaults to 10 seconds` - - restartCommand = cli.Command{ - Name: "restart", - Usage: "Restart one or more containers", - Description: restartDescription, - Flags: sortFlags(restartFlags), - Action: restartCmd, - ArgsUsage: "CONTAINER [CONTAINER ...]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + Example: `podman restart ctrID + podman restart --latest + podman restart ctrID1 ctrID2`, } ) -func restartCmd(c *cli.Context) error { +func init() { + restartCommand.Command = _restartCommand + restartCommand.SetUsageTemplate(UsageTemplate()) + flags := restartCommand.Flags() + flags.BoolVarP(&restartCommand.All, "all", "a", false, "Restart all non-running containers") + flags.BoolVarP(&restartCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&restartCommand.Running, "running", false, "Restart only running containers when --all is used") + flags.UintVarP(&restartCommand.Timeout, "timeout", "t", libpod.CtrRemoveTimeout, "Seconds to wait for stop before killing the container") + flags.UintVar(&restartCommand.Timeout, "time", libpod.CtrRemoveTimeout, "Seconds to wait for stop before killing the container") + + markFlagHiddenForRemoteClient("latest", flags) +} + +func restartCmd(c *cliconfig.RestartValues) error { var ( restartFuncs []shared.ParallelWorkerInput containers []*libpod.Container @@ -55,34 +59,31 @@ func restartCmd(c *cli.Context) error { rootless.SetSkipStorageSetup(true) } - args := c.Args() - runOnly := c.Bool("running") - all := c.Bool("all") - if len(args) < 1 && !c.Bool("latest") && !all { + args := c.InputArgs + runOnly := c.Running + all := c.All + if len(args) < 1 && !c.Latest && !all { return errors.Wrapf(libpod.ErrInvalidArg, "you must provide at least one container name or ID") } - if err := validateFlags(c, restartFlags); err != nil { - return err - } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - timeout := c.Uint("timeout") - useTimeout := c.IsSet("timeout") + timeout := c.Timeout + useTimeout := c.Flag("timeout").Changed // Handle --latest - if c.Bool("latest") { + if c.Latest { lastCtr, err := runtime.GetLatestContainer() if err != nil { return errors.Wrapf(err, "unable to get latest container") } restartContainers = append(restartContainers, lastCtr) } else if runOnly { - containers, err = getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + containers, err = getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStateRunning, "running") if err != nil { return err } @@ -105,7 +106,7 @@ func restartCmd(c *cli.Context) error { maxWorkers := shared.Parallelize("restart") if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalInt("max-workers") + maxWorkers = c.GlobalFlags.MaxWorks } logrus.Debugf("Setting maximum workers to %d", maxWorkers) diff --git a/cmd/podman/restore.go b/cmd/podman/restore.go index 664475e22..5f6e7b892 100644 --- a/cmd/podman/restore.go +++ b/cmd/podman/restore.go @@ -5,68 +5,69 @@ import ( "fmt" "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + restoreCommand cliconfig.RestoreValues restoreDescription = ` podman container restore Restores a container from a checkpoint. The container name or ID can be used. ` - restoreFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "keep, k", - Usage: "Keep all temporary checkpoint files", + _restoreCommand = &cobra.Command{ + Use: "restore", + Short: "Restores one or more containers from a checkpoint", + Long: restoreDescription, + RunE: func(cmd *cobra.Command, args []string) error { + restoreCommand.InputArgs = args + restoreCommand.GlobalFlags = MainGlobalOpts + return restoreCmd(&restoreCommand) }, - // restore --all would make more sense if there would be - // dedicated state for container which are checkpointed. - // TODO: add ContainerStateCheckpointed - cli.BoolFlag{ - Name: "tcp-established", - Usage: "Checkpoint a container with established TCP connections", + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, false) }, - cli.BoolFlag{ - Name: "all, a", - Usage: "Restore all checkpointed containers", - }, - LatestFlag, - } - restoreCommand = cli.Command{ - Name: "restore", - Usage: "Restores one or more containers from a checkpoint", - Description: restoreDescription, - Flags: sortFlags(restoreFlags), - Action: restoreCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", + Example: `podman container restore ctrID + podman container restore --latest + podman container restore --all`, } ) -func restoreCmd(c *cli.Context) error { +func init() { + restoreCommand.Command = _restoreCommand + restoreCommand.SetUsageTemplate(UsageTemplate()) + flags := restoreCommand.Flags() + flags.BoolVarP(&restoreCommand.All, "all", "a", false, "Restore all checkpointed containers") + flags.BoolVarP(&restoreCommand.Keep, "keep", "k", false, "Keep all temporary checkpoint files") + flags.BoolVarP(&restoreCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + // TODO: add ContainerStateCheckpointed + flags.BoolVar(&restoreCommand.TcpEstablished, "tcp-established", false, "Checkpoint a container with established TCP connections") + + markFlagHiddenForRemoteClient("latest", flags) +} + +func restoreCmd(c *cliconfig.RestoreValues) error { if rootless.IsRootless() { return errors.New("restoring a container requires root") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) options := libpod.ContainerCheckpointOptions{ - Keep: c.Bool("keep"), - TCPEstablished: c.Bool("tcp-established"), - } - - if err := checkAllAndLatest(c); err != nil { - return err + Keep: c.Keep, + TCPEstablished: c.TcpEstablished, } - containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateExited, "checkpointed") + containers, lastError := getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStateExited, "checkpointed") for _, ctr := range containers { if err = ctr.Restore(context.TODO(), options); err != nil { diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go index 7c0569b78..2dcb491d7 100644 --- a/cmd/podman/rm.go +++ b/cmd/podman/rm.go @@ -2,78 +2,92 @@ package main import ( "fmt" + + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/image" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - rmFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Remove all containers", - }, - cli.BoolFlag{ - Name: "force, f", - Usage: "Force removal of a running container. The default is false", - }, - LatestFlag, - cli.BoolFlag{ - Name: "volumes, v", - Usage: "Remove the volumes associated with the container (Not implemented yet)", - }, - } + rmCommand cliconfig.RmValues rmDescription = fmt.Sprintf(` Podman rm will remove one or more containers from the host. The container name or ID can be used. This does not remove images. Running containers will not be removed without the -f option. `) - rmCommand = cli.Command{ - Name: "rm", - Usage: "Remove one or more containers", - Description: rmDescription, - Flags: sortFlags(rmFlags), - Action: rmCmd, - ArgsUsage: "", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _rmCommand = &cobra.Command{ + Use: "rm", + Short: "Remove one or more containers", + Long: rmDescription, + RunE: func(cmd *cobra.Command, args []string) error { + rmCommand.InputArgs = args + rmCommand.GlobalFlags = MainGlobalOpts + return rmCmd(&rmCommand) + }, + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, false) + }, + Example: `podman rm imageID + podman rm mywebserver myflaskserver 860a4b23 + podman rm --force --all`, } ) +func init() { + rmCommand.Command = _rmCommand + rmCommand.SetUsageTemplate(UsageTemplate()) + flags := rmCommand.Flags() + flags.BoolVarP(&rmCommand.All, "all", "a", false, "Remove all containers") + flags.BoolVarP(&rmCommand.Force, "force", "f", false, "Force removal of a running container. The default is false") + flags.BoolVarP(&rmCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVarP(&rmCommand.Volumes, "volumes", "v", false, "Remove the volumes associated with the container") + markFlagHiddenForRemoteClient("latest", flags) +} + // saveCmd saves the image to either docker-archive or oci -func rmCmd(c *cli.Context) error { +func rmCmd(c *cliconfig.RmValues) error { var ( deleteFuncs []shared.ParallelWorkerInput ) ctx := getContext() - if err := validateFlags(c, rmFlags); err != nil { - return err - } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if err := checkAllAndLatest(c); err != nil { - return err - } - - delContainers, err := getAllOrLatestContainers(c, runtime, -1, "all") + failureCnt := 0 + delContainers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, -1, "all") if err != nil { + if c.Force && len(c.InputArgs) > 0 { + if errors.Cause(err) == libpod.ErrNoSuchCtr { + err = nil + } else { + failureCnt++ + } + runtime.RemoveContainersFromStorage(c.InputArgs) + } if len(delContainers) == 0 { + if err != nil && failureCnt == 0 { + exitCode = 1 + } return err } - fmt.Println(err.Error()) + if err != nil { + fmt.Println(err.Error()) + } } for _, container := range delContainers { con := container f := func() error { - return runtime.RemoveContainer(ctx, con, c.Bool("force")) + return runtime.RemoveContainer(ctx, con, c.Force, c.Volumes) } deleteFuncs = append(deleteFuncs, shared.ParallelWorkerInput{ @@ -83,11 +97,22 @@ func rmCmd(c *cli.Context) error { } maxWorkers := shared.Parallelize("rm") if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalInt("max-workers") + maxWorkers = c.GlobalFlags.MaxWorks } logrus.Debugf("Setting maximum workers to %d", maxWorkers) // Run the parallel funcs deleteErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, deleteFuncs) - return printParallelOutput(deleteErrors, errCount) + err = printParallelOutput(deleteErrors, errCount) + if err != nil { + for _, result := range deleteErrors { + if result != nil && errors.Cause(result) != image.ErrNoSuchCtr { + failureCnt++ + } + } + if failureCnt == 0 { + exitCode = 1 + } + } + return err } diff --git a/cmd/podman/rmi.go b/cmd/podman/rmi.go index 39757272e..709ed14e0 100644 --- a/cmd/podman/rmi.go +++ b/cmd/podman/rmi.go @@ -4,66 +4,67 @@ import ( "fmt" "os" - "github.com/containers/libpod/libpod/adapter" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/adapter" "github.com/containers/storage" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + rmiCommand cliconfig.RmiValues rmiDescription = "Removes one or more locally stored images." - rmiFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Remove all images", - }, - cli.BoolFlag{ - Name: "force, f", - Usage: "Force removal of the image", + _rmiCommand = &cobra.Command{ + Use: "rmi", + Short: "Removes one or more images from local storage", + Long: rmiDescription, + RunE: func(cmd *cobra.Command, args []string) error { + rmiCommand.InputArgs = args + rmiCommand.GlobalFlags = MainGlobalOpts + return rmiCmd(&rmiCommand) }, + Example: `podman rmi imageID + podman rmi --force alpine + podman rmi c4dfb1609ee2 93fd78260bd1 c0ed59d05ff7`, } - rmiCommand = cli.Command{ - Name: "rmi", - Usage: "Remove one or more images from local storage", - Description: rmiDescription, - Action: rmiCmd, - ArgsUsage: "IMAGE-NAME-OR-ID [...]", - Flags: sortFlags(rmiFlags), - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, +) + +func imageNotFound(err error) bool { + if errors.Cause(err) == image.ErrNoSuchImage { + return true } - rmImageCommand = cli.Command{ - Name: "rm", - Usage: "Removes one or more images from local storage", - Description: rmiDescription, - Action: rmiCmd, - ArgsUsage: "IMAGE-NAME-OR-ID [...]", - Flags: rmiFlags, - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + switch err.(type) { + case *iopodman.ImageNotFound: + return true } -) + return false +} + +func init() { + rmiCommand.Command = _rmiCommand + rmiCommand.SetUsageTemplate(UsageTemplate()) + flags := rmiCommand.Flags() + flags.BoolVarP(&rmiCommand.All, "all", "a", false, "Remove all images") + flags.BoolVarP(&rmiCommand.Force, "force", "f", false, "Force Removal of the image") +} -func rmiCmd(c *cli.Context) error { +func rmiCmd(c *cliconfig.RmiValues) error { var ( - lastError error - deleted bool - deleteErr error - msg string + lastError error + failureCnt int ) ctx := getContext() - if err := validateFlags(c, rmiFlags); err != nil { - return err - } - removeAll := c.Bool("all") - runtime, err := adapter.GetRuntime(c) + removeAll := c.All + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - args := c.Args() + args := c.InputArgs if len(args) == 0 && !removeAll { return errors.Errorf("image name or ID must be specified") } @@ -74,19 +75,21 @@ func rmiCmd(c *cli.Context) error { images := args[:] removeImage := func(img *adapter.ContainerImage) { - deleted = true - msg, deleteErr = runtime.RemoveImage(ctx, img, c.Bool("force")) - if deleteErr != nil { - if errors.Cause(deleteErr) == storage.ErrImageUsedByContainer { + msg, err := runtime.RemoveImage(ctx, img, c.Force) + if err != nil { + if errors.Cause(err) == storage.ErrImageUsedByContainer { fmt.Printf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID()) } + if !imageNotFound(err) { + failureCnt++ + } if lastError != nil { fmt.Fprintln(os.Stderr, lastError) } - lastError = deleteErr - } else { - fmt.Println(msg) + lastError = err + return } + fmt.Println(msg) } if removeAll { @@ -131,22 +134,21 @@ func rmiCmd(c *cli.Context) error { for _, i := range images { newImage, err := runtime.NewImageFromLocal(i) if err != nil { - fmt.Fprintln(os.Stderr, err) + if lastError != nil { + if !imageNotFound(lastError) { + failureCnt++ + } + fmt.Fprintln(os.Stderr, lastError) + } + lastError = err continue } removeImage(newImage) } } - // If the user calls remove all and there are none, it should not be a - // non-zero exit - if !deleted && removeAll { - return nil - } - // the user tries to remove images that do not exist, that should be a - // non-zero exit - if !deleted { - return errors.Errorf("no valid images to delete") + if imageNotFound(lastError) && failureCnt == 0 { + exitCode = 1 } return lastError diff --git a/cmd/podman/run.go b/cmd/podman/run.go index 3ef546940..bea9b1743 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -8,49 +8,64 @@ import ( "strconv" "strings" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" + opentracing "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) -var runDescription = "Runs a command in a new container from the given image" - -var runFlags []cli.Flag = append(createFlags, cli.BoolTFlag{ - Name: "sig-proxy", - Usage: "Proxy received signals to the process (default true)", -}) - -var runCommand = cli.Command{ - Name: "run", - Usage: "Run a command in a new container", - Description: runDescription, - Flags: sortFlags(runFlags), - Action: runCmd, - ArgsUsage: "IMAGE [COMMAND [ARG...]]", - HideHelp: true, - SkipArgReorder: true, - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, +var ( + runCommand cliconfig.RunValues + + runDescription = "Runs a command in a new container from the given image" + _runCommand = &cobra.Command{ + Use: "run", + Short: "Run a command in a new container", + Long: runDescription, + RunE: func(cmd *cobra.Command, args []string) error { + runCommand.InputArgs = args + runCommand.GlobalFlags = MainGlobalOpts + return runCmd(&runCommand) + }, + Example: `podman run imageID ls -alF /etc + podman run --net=host imageID dnf -y install java + podman run --volume /var/hostdir:/var/ctrdir -i -t fedora /bin/bash`, + } +) + +func init() { + runCommand.Command = _runCommand + runCommand.SetUsageTemplate(UsageTemplate()) + flags := runCommand.Flags() + flags.SetInterspersed(false) + flags.Bool("sig-proxy", true, "Proxy received signals to the process (default true)") + getCreateFlags(&runCommand.PodmanCommand) } -func runCmd(c *cli.Context) error { - if err := createInit(c); err != nil { +func runCmd(c *cliconfig.RunValues) error { + if c.Bool("trace") { + span, _ := opentracing.StartSpanFromContext(Ctx, "runCmd") + defer span.Finish() + } + + if err := createInit(&c.PodmanCommand); err != nil { return err } if os.Geteuid() != 0 { rootless.SetSkipStorageSetup(true) } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - ctr, createConfig, err := createContainer(c, runtime) + ctr, createConfig, err := createContainer(&c.PodmanCommand, runtime) if err != nil { return err } @@ -65,7 +80,8 @@ func runCmd(c *cli.Context) error { ctx := getContext() // Handle detached start if createConfig.Detach { - if err := ctr.Start(ctx); err != nil { + // if the container was created as part of a pod, also start its dependencies, if any. + if err := ctr.Start(ctx, c.IsSet("pod")); err != nil { // This means the command did not exist exitCode = 127 if strings.Index(err.Error(), "permission denied") > -1 { @@ -110,14 +126,22 @@ func runCmd(c *cli.Context) error { } } } - if err := startAttachCtr(ctr, outputStream, errorStream, inputStream, c.String("detach-keys"), c.BoolT("sig-proxy"), true); err != nil { + // if the container was created as part of a pod, also start its dependencies, if any. + if err := startAttachCtr(ctr, outputStream, errorStream, inputStream, c.String("detach-keys"), c.Bool("sig-proxy"), true, c.IsSet("pod")); err != nil { + // We've manually detached from the container + // Do not perform cleanup, or wait for container exit code + // Just exit immediately + if errors.Cause(err) == libpod.ErrDetach { + exitCode = 0 + return nil + } // This means the command did not exist exitCode = 127 if strings.Index(err.Error(), "permission denied") > -1 { exitCode = 126 } if c.IsSet("rm") { - if deleteError := runtime.RemoveContainer(ctx, ctr, true); deleteError != nil { + if deleteError := runtime.RemoveContainer(ctx, ctr, true, false); deleteError != nil { logrus.Errorf("unable to remove container %s after failing to start and attach to it", ctr.ID()) } } @@ -140,28 +164,12 @@ func runCmd(c *cli.Context) error { exitCode = int(ecode) } - if createConfig.Rm { - return runtime.RemoveContainer(ctx, ctr, true) - } - - if err := ctr.Cleanup(ctx); err != nil { - // If the container has been removed already, no need to error on cleanup - // Also, if it was restarted, don't error either - if errors.Cause(err) == libpod.ErrNoSuchCtr || - errors.Cause(err) == libpod.ErrCtrRemoved || - errors.Cause(err) == libpod.ErrCtrStateInvalid { - return nil - } - - return err - } - return nil } // Read a container's exit file func readExitFile(runtimeTmp, ctrID string) (int, error) { - exitFile := filepath.Join(runtimeTmp, "exits", ctrID) + exitFile := filepath.Join(runtimeTmp, "exits", fmt.Sprintf("%s-old", ctrID)) logrus.Debugf("Attempting to read container %s exit code from file %s", ctrID, exitFile) diff --git a/cmd/podman/run_test.go b/cmd/podman/run_test.go index 33c0a4bfe..5ea39e457 100644 --- a/cmd/podman/run_test.go +++ b/cmd/podman/run_test.go @@ -4,24 +4,19 @@ import ( "runtime" "testing" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/pkg/inspect" cc "github.com/containers/libpod/pkg/spec" - units "github.com/docker/go-units" + "github.com/docker/go-units" ociv1 "github.com/opencontainers/image-spec/specs-go/v1" spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/spf13/cobra" "github.com/stretchr/testify/assert" - "github.com/urfave/cli" ) var ( - cmd = []string{"podman", "test", "alpine"} - CLI *cli.Context - testCommand = cli.Command{ - Name: "test", - Flags: sortFlags(createFlags), - Action: testCmd, - HideHelp: true, - } + cmd = []string{"podman", "test", "alpine"} + CLI *cliconfig.PodmanCommand ) // generates a mocked ImageData structure based on alpine @@ -53,23 +48,29 @@ func generateAlpineImageData() *inspect.ImageData { } // sets a global CLI -func testCmd(c *cli.Context) error { - CLI = c +func testCmd(c *cobra.Command) error { + CLI = &cliconfig.PodmanCommand{Command: c} return nil } // creates the mocked cli pointing to our create flags // global flags like log-level are not implemented -func createCLI() cli.App { - a := cli.App{ - Commands: []cli.Command{ - testCommand, +func createCLI(args []string) *cliconfig.PodmanCommand { + var testCommand = &cliconfig.PodmanCommand{ + Command: &cobra.Command{ + Use: "test", + RunE: func(cmd *cobra.Command, args []string) error { + return testCmd(cmd) + }, }, } - return a + rootCmd := testCommand + getCreateFlags(rootCmd) + rootCmd.ParseFlags(args) + return rootCmd } -func getRuntimeSpec(c *cli.Context) (*spec.Spec, error) { +func getRuntimeSpec(c *cliconfig.PodmanCommand) (*spec.Spec, error) { /* TODO: This test has never worked. Need to install content runtime, err := getRuntime(c) @@ -98,10 +99,11 @@ func TestPIDsLimit(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("seccomp, which is enabled by default, is only supported on Linux") } - a := createCLI() args := []string{"--pids-limit", "22"} - a.Run(append(cmd, args...)) - runtimeSpec, err := getRuntimeSpec(CLI) + a := createCLI(args) + a.InputArgs = args + //a.Run(append(cmd, args...)) + runtimeSpec, err := getRuntimeSpec(a) if err != nil { t.Fatalf(err.Error()) } @@ -116,10 +118,10 @@ func TestBLKIOWeightDevice(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("seccomp, which is enabled by default, is only supported on Linux") } - a := createCLI() args := []string{"--blkio-weight-device", "/dev/zero:100"} - a.Run(append(cmd, args...)) - runtimeSpec, err := getRuntimeSpec(CLI) + a := createCLI(args) + a.InputArgs = args + runtimeSpec, err := getRuntimeSpec(a) if err != nil { t.Fatalf(err.Error()) } @@ -134,10 +136,11 @@ func TestMemorySwap(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("seccomp, which is enabled by default, is only supported on Linux") } - a := createCLI() args := []string{"--memory-swap", "45m", "--memory", "40m"} - a.Run(append(cmd, args...)) - runtimeSpec, err := getRuntimeSpec(CLI) + a := createCLI(args) + a.InputArgs = args + //a.Run(append(cmd, args...)) + runtimeSpec, err := getRuntimeSpec(a) if err != nil { t.Fatalf(err.Error()) } diff --git a/cmd/podman/runlabel.go b/cmd/podman/runlabel.go index 38905b5ca..d466651f3 100644 --- a/cmd/podman/runlabel.go +++ b/cmd/podman/runlabel.go @@ -7,88 +7,61 @@ import ( "strings" "github.com/containers/image/types" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/utils" "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - runlabelFlags = []cli.Flag{ - cli.StringFlag{ - Name: "authfile", - Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override. ", - }, - cli.BoolFlag{ - Name: "display", - Usage: "Preview the command that the label would run", - }, - cli.StringFlag{ - Name: "cert-dir", - Usage: "`Pathname` of a directory containing TLS certificates and keys", - }, - cli.StringFlag{ - Name: "creds", - Usage: "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry", - }, - cli.StringFlag{ - Name: "name", - Usage: "Assign a name to the container", - }, - cli.StringFlag{ - Name: "opt1", - Usage: "Optional parameter to pass for install", - Hidden: true, - }, - cli.StringFlag{ - Name: "opt2", - Usage: "Optional parameter to pass for install", - Hidden: true, - }, - cli.StringFlag{ - Name: "opt3", - Usage: "Optional parameter to pass for install", - Hidden: true, - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Suppress output information when installing images", - }, - cli.BoolFlag{ - Name: "pull, p", - Usage: "Pull the image if it does not exist locally prior to executing the label contents", - }, - cli.StringFlag{ - Name: "signature-policy", - Usage: "`Pathname` of signature policy file (not usually used)", - }, - cli.BoolTFlag{ - Name: "tls-verify", - Usage: "Require HTTPS and verify certificates when contacting registries (default: true)", - }, - } - + runlabelCommand cliconfig.RunlabelValues runlabelDescription = ` Executes a command as described by a container image label. ` - runlabelCommand = cli.Command{ - Name: "runlabel", - Usage: "Execute the command described by an image label", - Description: runlabelDescription, - Flags: sortFlags(runlabelFlags), - Action: runlabelCmd, - ArgsUsage: "", - SkipArgReorder: true, - OnUsageError: usageErrorHandler, + _runlabelCommand = &cobra.Command{ + Use: "runlabel", + Short: "Execute the command described by an image label", + Long: runlabelDescription, + RunE: func(cmd *cobra.Command, args []string) error { + runlabelCommand.InputArgs = args + runlabelCommand.GlobalFlags = MainGlobalOpts + return runlabelCmd(&runlabelCommand) + }, + Example: `podman container runlabel run imageID + podman container runlabel --pull install imageID arg1 arg2 + podman container runlabel --display run myImage`, } ) +func init() { + runlabelCommand.Command = _runlabelCommand + runlabelCommand.SetUsageTemplate(UsageTemplate()) + flags := runlabelCommand.Flags() + flags.StringVar(&runlabelCommand.Authfile, "authfile", "", "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&runlabelCommand.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") + flags.StringVar(&runlabelCommand.Creds, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") + flags.BoolVar(&runlabelCommand.Display, "display", false, "Preview the command that the label would run") + flags.StringVar(&runlabelCommand.Name, "name", "", "Assign a name to the container") + + flags.StringVar(&runlabelCommand.Opt1, "opt1", "", "Optional parameter to pass for install") + flags.StringVar(&runlabelCommand.Opt2, "opt2", "", "Optional parameter to pass for install") + flags.StringVar(&runlabelCommand.Opt3, "opt3", "", "Optional parameter to pass for install") + flags.MarkHidden("opt1") + flags.MarkHidden("opt2") + flags.MarkHidden("opt3") + + flags.BoolVarP(&runlabelCommand.Pull, "pull", "p", false, "Pull the image if it does not exist locally prior to executing the label contents") + flags.BoolVarP(&runlabelCommand.Quiet, "quiet", "q", false, "Suppress output information when installing images") + flags.StringVar(&runlabelCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") + flags.BoolVar(&runlabelCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") +} + // installCmd gets the data from the command line and calls installImage // to copy an image from a registry to a local machine -func runlabelCmd(c *cli.Context) error { +func runlabelCmd(c *cliconfig.RunlabelValues) error { var ( imageName string stdErr, stdOut io.Writer @@ -105,40 +78,37 @@ func runlabelCmd(c *cli.Context) error { } opts := make(map[string]string) - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - args := c.Args() + args := c.InputArgs if len(args) < 2 { - logrus.Errorf("the runlabel command requires at least 2 arguments: LABEL IMAGE") - return nil + return errors.Errorf("the runlabel command requires at least 2 arguments: LABEL IMAGE") } - if err := validateFlags(c, runlabelFlags); err != nil { - return err - } - if c.Bool("display") && c.Bool("quiet") { + if c.Display && c.Quiet { return errors.Errorf("the display and quiet flags cannot be used together.") } if len(args) > 2 { extraArgs = args[2:] } - pull := c.Bool("pull") + pull := c.Pull label := args[0] runlabelImage := args[1] - if c.IsSet("opt1") { - opts["opt1"] = c.String("opt1") + if c.Flag("opt1").Changed { + opts["opt1"] = c.Opt1 } - if c.IsSet("opt2") { - opts["opt2"] = c.String("opt2") + + if c.Flag("opt2").Changed { + opts["opt2"] = c.Opt2 } - if c.IsSet("opt3") { - opts["opt3"] = c.String("opt3") + if c.Flag("opt3").Changed { + opts["opt3"] = c.Opt3 } ctx := getContext() @@ -147,21 +117,21 @@ func runlabelCmd(c *cli.Context) error { stdOut = os.Stdout stdIn = os.Stdin - if c.Bool("quiet") { + if c.Quiet { stdErr = nil stdOut = nil stdIn = nil } dockerRegistryOptions := image.DockerRegistryOptions{ - DockerCertPath: c.String("cert-dir"), + DockerCertPath: c.CertDir, } - if c.IsSet("tls-verify") { - dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify")) + if c.Flag("tls-verify").Changed { + dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) } - authfile := getAuthFile(c.String("authfile")) - runLabel, imageName, err := shared.GetRunlabel(label, runlabelImage, ctx, runtime, pull, c.String("creds"), dockerRegistryOptions, authfile, c.String("signature-policy"), stdOut) + authfile := getAuthFile(c.Authfile) + runLabel, imageName, err := shared.GetRunlabel(label, runlabelImage, ctx, runtime, pull, c.Creds, dockerRegistryOptions, authfile, c.SignaturePolicy, stdOut) if err != nil { return err } @@ -169,13 +139,13 @@ func runlabelCmd(c *cli.Context) error { return errors.Errorf("%s does not have a label of %s", runlabelImage, label) } - cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, c.String("name"), opts, extraArgs) + cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, c.Name, opts, extraArgs) if err != nil { return err } - if !c.Bool("quiet") { + if !c.Quiet { fmt.Printf("Command: %s\n", strings.Join(cmd, " ")) - if c.Bool("display") { + if c.Display { return nil } } diff --git a/cmd/podman/save.go b/cmd/podman/save.go index 325140b76..161540deb 100644 --- a/cmd/podman/save.go +++ b/cmd/podman/save.go @@ -1,92 +1,85 @@ package main import ( - "fmt" - "io" "os" "strings" - "github.com/containers/image/directory" - dockerarchive "github.com/containers/image/docker/archive" - "github.com/containers/image/docker/reference" - "github.com/containers/image/manifest" - ociarchive "github.com/containers/image/oci/archive" - "github.com/containers/image/types" - "github.com/containers/libpod/cmd/podman/libpodruntime" - libpodImage "github.com/containers/libpod/libpod/image" - imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" + "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) const ( ociManifestDir = "oci-dir" + ociArchive = "oci-archive" v2s2ManifestDir = "docker-dir" + v2s2Archive = "docker-archive" ) +var validFormats = []string{ociManifestDir, ociArchive, v2s2ManifestDir, v2s2Archive} + var ( - saveFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "compress", - Usage: "Compress tarball image layers when saving to a directory using the 'dir' transport. (default is same compression type as source)", - }, - cli.StringFlag{ - Name: "output, o", - Usage: "Write to a file, default is STDOUT", - Value: "/dev/stdout", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Suppress the output", - }, - cli.StringFlag{ - Name: "format", - Usage: "Save image to oci-archive, oci-dir (directory with oci manifest type), docker-dir (directory with v2s2 manifest type)", - }, - } + saveCommand cliconfig.SaveValues saveDescription = ` Save an image to docker-archive or oci-archive on the local machine. Default is docker-archive` - saveCommand = cli.Command{ - Name: "save", - Usage: "Save image to an archive", - Description: saveDescription, - Flags: sortFlags(saveFlags), - Action: saveCmd, - ArgsUsage: "", - SkipArgReorder: true, - OnUsageError: usageErrorHandler, + _saveCommand = &cobra.Command{ + Use: "save", + Short: "Save image to an archive", + Long: saveDescription, + RunE: func(cmd *cobra.Command, args []string) error { + saveCommand.InputArgs = args + saveCommand.GlobalFlags = MainGlobalOpts + return saveCmd(&saveCommand) + }, + Args: func(cmd *cobra.Command, args []string) error { + format, err := cmd.Flags().GetString("format") + if err != nil { + return err + } + if !util.StringInSlice(format, validFormats) { + return errors.Errorf("format value must be one of %s", strings.Join(validFormats, " ")) + } + return nil + }, + Example: `podman save --quiet -o myimage.tar imageID + podman save --format docker-dir -o ubuntu-dir ubuntu + podman save > alpine-all.tar alpine:latest`, } ) +func init() { + saveCommand.Command = _saveCommand + saveCommand.SetUsageTemplate(UsageTemplate()) + flags := saveCommand.Flags() + flags.BoolVar(&saveCommand.Compress, "compress", false, "Compress tarball image layers when saving to a directory using the 'dir' transport. (default is same compression type as source)") + flags.StringVar(&saveCommand.Format, "format", v2s2Archive, "Save image to oci-archive, oci-dir (directory with oci manifest type), docker-archive, docker-dir (directory with v2s2 manifest type)") + flags.StringVarP(&saveCommand.Output, "output", "o", "/dev/stdout", "Write to a file, default is STDOUT") + flags.BoolVarP(&saveCommand.Quiet, "quiet", "q", false, "Suppress the output") +} + // saveCmd saves the image to either docker-archive or oci -func saveCmd(c *cli.Context) error { - args := c.Args() +func saveCmd(c *cliconfig.SaveValues) error { + args := c.InputArgs if len(args) == 0 { return errors.Errorf("need at least 1 argument") } - if err := validateFlags(c, saveFlags); err != nil { - return err - } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not create runtime") } defer runtime.Shutdown(false) - if c.IsSet("compress") && (c.String("format") != ociManifestDir && c.String("format") != v2s2ManifestDir && c.String("format") == "") { + if c.Flag("compress").Changed && (c.Format != ociManifestDir && c.Format != v2s2ManifestDir && c.Format == "") { return errors.Errorf("--compress can only be set when --format is either 'oci-dir' or 'docker-dir'") } - var writer io.Writer - if !c.Bool("quiet") { - writer = os.Stderr - } - - output := c.String("output") + output := c.Output if output == "/dev/stdout" { fi := os.Stdout if logrus.IsTerminal(fi) { @@ -96,87 +89,5 @@ func saveCmd(c *cli.Context) error { if err := validateFileName(output); err != nil { return err } - - source := args[0] - newImage, err := runtime.ImageRuntime().NewFromLocal(source) - if err != nil { - return err - } - - var destRef types.ImageReference - var manifestType string - switch c.String("format") { - case "oci-archive": - destImageName := imageNameForSaveDestination(newImage, source) - destRef, err = ociarchive.NewReference(output, destImageName) // destImageName may be "" - if err != nil { - return errors.Wrapf(err, "error getting OCI archive ImageReference for (%q, %q)", output, destImageName) - } - case "oci-dir": - destRef, err = directory.NewReference(output) - if err != nil { - return errors.Wrapf(err, "error getting directory ImageReference for %q", output) - } - manifestType = imgspecv1.MediaTypeImageManifest - case "docker-dir": - destRef, err = directory.NewReference(output) - if err != nil { - return errors.Wrapf(err, "error getting directory ImageReference for %q", output) - } - manifestType = manifest.DockerV2Schema2MediaType - case "docker-archive", "": - dst := output - destImageName := imageNameForSaveDestination(newImage, source) - if destImageName != "" { - dst = fmt.Sprintf("%s:%s", dst, destImageName) - } - destRef, err = dockerarchive.ParseReference(dst) // FIXME? Add dockerarchive.NewReference - if err != nil { - return errors.Wrapf(err, "error getting Docker archive ImageReference for %q", dst) - } - default: - return errors.Errorf("unknown format option %q", c.String("format")) - } - - // supports saving multiple tags to the same tar archive - var additionaltags []reference.NamedTagged - if len(args) > 1 { - additionaltags, err = libpodImage.GetAdditionalTags(args[1:]) - if err != nil { - return err - } - } - if err := newImage.PushImageToReference(getContext(), destRef, manifestType, "", "", writer, c.Bool("compress"), libpodImage.SigningOptions{}, &libpodImage.DockerRegistryOptions{}, additionaltags); err != nil { - if err2 := os.Remove(output); err2 != nil { - logrus.Errorf("error deleting %q: %v", output, err) - } - return errors.Wrapf(err, "unable to save %q", args) - } - - return nil -} - -// imageNameForSaveDestination returns a Docker-like reference appropriate for saving img, -// which the user referred to as imgUserInput; or an empty string, if there is no appropriate -// reference. -func imageNameForSaveDestination(img *libpodImage.Image, imgUserInput string) string { - if strings.Contains(img.ID(), imgUserInput) { - return "" - } - - prepend := "" - localRegistryPrefix := fmt.Sprintf("%s/", libpodImage.DefaultLocalRegistry) - if !strings.HasPrefix(imgUserInput, localRegistryPrefix) { - // we need to check if localhost was added to the image name in NewFromLocal - for _, name := range img.Names() { - // If the user is saving an image in the localhost registry, getLocalImage need - // a name that matches the format localhost/<tag1>:<tag2> or localhost/<tag>:latest to correctly - // set up the manifest and save. - if strings.HasPrefix(name, localRegistryPrefix) && (strings.HasSuffix(name, imgUserInput) || strings.HasSuffix(name, fmt.Sprintf("%s:latest", imgUserInput))) { - prepend = localRegistryPrefix - break - } - } - } - return fmt.Sprintf("%s%s", prepend, imgUserInput) + return runtime.SaveImage(getContext(), c) } diff --git a/cmd/podman/search.go b/cmd/podman/search.go index 81469a0f8..f63131c84 100644 --- a/cmd/podman/search.go +++ b/cmd/podman/search.go @@ -1,20 +1,14 @@ package main import ( - "context" - "reflect" - "strconv" "strings" - "github.com/containers/image/docker" "github.com/containers/image/types" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" - "github.com/containers/libpod/libpod/common" - sysreg "github.com/containers/libpod/pkg/registries" - "github.com/docker/distribution/reference" + "github.com/containers/libpod/libpod/image" "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) const ( @@ -23,72 +17,39 @@ const ( ) var ( - searchFlags = []cli.Flag{ - cli.StringFlag{ - Name: "authfile", - Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override. ", - }, - cli.StringSliceFlag{ - Name: "filter, f", - Usage: "Filter output based on conditions provided (default [])", - }, - cli.StringFlag{ - Name: "format", - Usage: "Change the output format to a Go template", - }, - cli.IntFlag{ - Name: "limit", - Usage: "Limit the number of results", - }, - cli.BoolFlag{ - Name: "no-trunc", - Usage: "Do not truncate the output", - }, - cli.BoolTFlag{ - Name: "tls-verify", - Usage: "Require HTTPS and verify certificates when contacting registries (default: true)", - }, - } + searchCommand cliconfig.SearchValues searchDescription = ` Search registries for a given image. Can search all the default registries or a specific registry. Can limit the number of results, and filter the output based on certain conditions.` - searchCommand = cli.Command{ - Name: "search", - Usage: "Search registry for image", - Description: searchDescription, - Flags: sortFlags(searchFlags), - Action: searchCmd, - ArgsUsage: "TERM", - OnUsageError: usageErrorHandler, + _searchCommand = &cobra.Command{ + Use: "search", + Short: "Search registry for image", + Long: searchDescription, + RunE: func(cmd *cobra.Command, args []string) error { + searchCommand.InputArgs = args + searchCommand.GlobalFlags = MainGlobalOpts + return searchCmd(&searchCommand) + }, + Example: `podman search --filter=is-official --limit 3 alpine + podman search registry.fedoraproject.org/ # only works with v2 registries + podman search --format "table {{.Index}} {{.Name}}" registry.fedoraproject.org/fedora`, } ) -type searchParams struct { - Index string - Name string - Description string - Stars int - Official string - Automated string -} - -type searchOpts struct { - filter []string - limit int - noTrunc bool - format string - authfile string - insecureSkipTLSVerify types.OptionalBool +func init() { + searchCommand.Command = _searchCommand + searchCommand.SetUsageTemplate(UsageTemplate()) + flags := searchCommand.Flags() + flags.StringVar(&searchCommand.Authfile, "authfile", "", "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringSliceVarP(&searchCommand.Filter, "filter", "f", []string{}, "Filter output based on conditions provided (default [])") + flags.StringVar(&searchCommand.Format, "format", "", "Change the output format to a Go template") + flags.IntVar(&searchCommand.Limit, "limit", 0, "Limit the number of results") + flags.BoolVar(&searchCommand.NoTrunc, "no-trunc", false, "Do not truncate the output") + flags.BoolVar(&searchCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") } -type searchFilterParams struct { - stars int - isAutomated *bool - isOfficial *bool -} - -func searchCmd(c *cli.Context) error { - args := c.Args() +func searchCmd(c *cliconfig.SearchValues) error { + args := c.InputArgs if len(args) > 1 { return errors.Errorf("too many arguments. Requires exactly 1") } @@ -97,41 +58,29 @@ func searchCmd(c *cli.Context) error { } term := args[0] - // Check if search term has a registry in it - registry, err := getRegistry(term) + filter, err := image.ParseSearchFilter(c.Filter) if err != nil { - return errors.Wrapf(err, "error getting registry from %q", term) - } - if registry != "" { - term = term[len(registry)+1:] - } - - if err := validateFlags(c, searchFlags); err != nil { return err } - format := genSearchFormat(c.String("format")) - opts := searchOpts{ - format: format, - noTrunc: c.Bool("no-trunc"), - limit: c.Int("limit"), - filter: c.StringSlice("filter"), - authfile: getAuthFile(c.String("authfile")), - } - if c.IsSet("tls-verify") { - opts.insecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify")) + searchOptions := image.SearchOptions{ + NoTrunc: c.NoTrunc, + Limit: c.Limit, + Filter: *filter, + Authfile: getAuthFile(c.Authfile), } - registries, err := getRegistries(registry) - if err != nil { - return err + if c.Flag("tls-verify").Changed { + searchOptions.InsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) } - filter, err := parseSearchFilter(&opts) + results, err := image.SearchImages(term, searchOptions) if err != nil { return err } - - return generateSearchOutput(term, registries, opts, *filter) + format := genSearchFormat(c.Format) + out := formats.StdoutTemplateArray{Output: searchToGeneric(results), Template: format, Fields: results[0].HeaderMap()} + formats.Writer(out).Out() + return nil } func genSearchFormat(format string) string { @@ -143,188 +92,9 @@ func genSearchFormat(format string) string { return "table {{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\t" } -func searchToGeneric(params []searchParams) (genericParams []interface{}) { +func searchToGeneric(params []image.SearchResult) (genericParams []interface{}) { for _, v := range params { genericParams = append(genericParams, interface{}(v)) } return genericParams } - -func (s *searchParams) headerMap() map[string]string { - v := reflect.Indirect(reflect.ValueOf(s)) - values := make(map[string]string, v.NumField()) - - for i := 0; i < v.NumField(); i++ { - key := v.Type().Field(i).Name - value := key - values[key] = strings.ToUpper(splitCamelCase(value)) - } - return values -} - -// getRegistries returns the list of registries to search, depending on an optional registry specification -func getRegistries(registry string) ([]string, error) { - var registries []string - if registry != "" { - registries = append(registries, registry) - } else { - var err error - registries, err = sysreg.GetRegistries() - if err != nil { - return nil, errors.Wrapf(err, "error getting registries to search") - } - } - return registries, nil -} - -func getSearchOutput(term string, registries []string, opts searchOpts, filter searchFilterParams) ([]searchParams, error) { - // Max number of queries by default is 25 - limit := maxQueries - if opts.limit != 0 { - limit = opts.limit - } - - sc := common.GetSystemContext("", opts.authfile, false) - sc.DockerInsecureSkipTLSVerify = opts.insecureSkipTLSVerify - sc.SystemRegistriesConfPath = sysreg.SystemRegistriesConfPath() // FIXME: Set this more globally. Probably no reason not to have it in every types.SystemContext, and to compute the value just once in one place. - var paramsArr []searchParams - for _, reg := range registries { - results, err := docker.SearchRegistry(context.TODO(), sc, reg, term, limit) - if err != nil { - logrus.Errorf("error searching registry %q: %v", reg, err) - continue - } - index := reg - arr := strings.Split(reg, ".") - if len(arr) > 2 { - index = strings.Join(arr[len(arr)-2:], ".") - } - - // limit is the number of results to output - // if the total number of results is less than the limit, output all - // if the limit has been set by the user, output those number of queries - limit := maxQueries - if len(results) < limit { - limit = len(results) - } - if opts.limit != 0 && opts.limit < len(results) { - limit = opts.limit - } - - for i := 0; i < limit; i++ { - if len(opts.filter) > 0 { - // Check whether query matches filters - if !(matchesAutomatedFilter(filter, results[i]) && matchesOfficialFilter(filter, results[i]) && matchesStarFilter(filter, results[i])) { - continue - } - } - official := "" - if results[i].IsOfficial { - official = "[OK]" - } - automated := "" - if results[i].IsAutomated { - automated = "[OK]" - } - description := strings.Replace(results[i].Description, "\n", " ", -1) - if len(description) > 44 && !opts.noTrunc { - description = description[:descriptionTruncLength] + "..." - } - name := reg + "/" + results[i].Name - if index == "docker.io" && !strings.Contains(results[i].Name, "/") { - name = index + "/library/" + results[i].Name - } - params := searchParams{ - Index: index, - Name: name, - Description: description, - Official: official, - Automated: automated, - Stars: results[i].StarCount, - } - paramsArr = append(paramsArr, params) - } - } - return paramsArr, nil -} - -func generateSearchOutput(term string, registries []string, opts searchOpts, filter searchFilterParams) error { - searchOutput, err := getSearchOutput(term, registries, opts, filter) - if err != nil { - return err - } - if len(searchOutput) == 0 { - return nil - } - out := formats.StdoutTemplateArray{Output: searchToGeneric(searchOutput), Template: opts.format, Fields: searchOutput[0].headerMap()} - return formats.Writer(out).Out() -} - -func parseSearchFilter(opts *searchOpts) (*searchFilterParams, error) { - filterParams := &searchFilterParams{} - ptrTrue := true - ptrFalse := false - for _, filter := range opts.filter { - arr := strings.Split(filter, "=") - switch arr[0] { - case "stars": - if len(arr) < 2 { - return nil, errors.Errorf("invalid `stars` filter %q, should be stars=<value>", filter) - } - stars, err := strconv.Atoi(arr[1]) - if err != nil { - return nil, errors.Wrapf(err, "incorrect value type for stars filter") - } - filterParams.stars = stars - break - case "is-automated": - if len(arr) == 2 && arr[1] == "false" { - filterParams.isAutomated = &ptrFalse - } else { - filterParams.isAutomated = &ptrTrue - } - break - case "is-official": - if len(arr) == 2 && arr[1] == "false" { - filterParams.isOfficial = &ptrFalse - } else { - filterParams.isOfficial = &ptrTrue - } - break - default: - return nil, errors.Errorf("invalid filter type %q", filter) - } - } - return filterParams, nil -} - -func matchesStarFilter(filter searchFilterParams, result docker.SearchResult) bool { - return result.StarCount >= filter.stars -} - -func matchesAutomatedFilter(filter searchFilterParams, result docker.SearchResult) bool { - if filter.isAutomated != nil { - return result.IsAutomated == *filter.isAutomated - } - return true -} - -func matchesOfficialFilter(filter searchFilterParams, result docker.SearchResult) bool { - if filter.isOfficial != nil { - return result.IsOfficial == *filter.isOfficial - } - return true -} - -func getRegistry(image string) (string, error) { - // It is possible to only have the registry name in the format "myregistry/" - // if so, just trim the "/" from the end and return the registry name - if strings.HasSuffix(image, "/") { - return strings.TrimSuffix(image, "/"), nil - } - imgRef, err := reference.Parse(image) - if err != nil { - return "", err - } - return reference.Domain(imgRef.(reference.Named)), nil -} diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index f84fb8261..81811e0f2 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -88,6 +88,7 @@ type PsContainerOutput struct { PIDNS string User string UTS string + Mounts string } // Namespace describes output for ps namespace @@ -212,11 +213,16 @@ func NewBatchContainer(ctr *libpod.Container, opts PsOptions) (PsContainerOutput } } + ports, err := ctr.PortMappings() + if err != nil { + logrus.Errorf("unable to lookup namespace container for %s", ctr.ID()) + } + pso.ID = cid pso.Image = imageName pso.Command = command pso.Created = created - pso.Ports = portsToString(ctr.PortMappings()) + pso.Ports = portsToString(ports) pso.Names = ctr.Name() pso.IsInfra = ctr.IsInfra() pso.Status = status @@ -228,6 +234,7 @@ func NewBatchContainer(ctr *libpod.Container, opts PsOptions) (PsContainerOutput pso.CreatedAt = ctr.CreatedTime() pso.StartedAt = startedAt pso.Labels = ctr.Labels() + pso.Mounts = strings.Join(ctr.UserVolumes(), " ") if opts.Namespace { pso.Cgroup = ns.Cgroup diff --git a/cmd/podman/shared/pod.go b/cmd/podman/shared/pod.go index 30dd14845..5f65c40ac 100644 --- a/cmd/podman/shared/pod.go +++ b/cmd/podman/shared/pod.go @@ -26,6 +26,10 @@ func GetPodStatus(pod *libpod.Pod) (string, error) { if err != nil { return errored, err } + return CreatePodStatusResults(ctrStatuses) +} + +func CreatePodStatusResults(ctrStatuses map[string]libpod.ContainerStatus) (string, error) { ctrNum := len(ctrStatuses) if ctrNum == 0 { return created, nil diff --git a/cmd/podman/sign.go b/cmd/podman/sign.go index 22aa07230..6e8f9ee95 100644 --- a/cmd/podman/sign.go +++ b/cmd/podman/sign.go @@ -11,60 +11,63 @@ import ( "github.com/containers/image/signature" "github.com/containers/image/transports" "github.com/containers/image/transports/alltransports" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/trust" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - signFlags = []cli.Flag{ - cli.StringFlag{ - Name: "sign-by", - Usage: "Name of the signing key", - }, - cli.StringFlag{ - Name: "directory, d", - Usage: "Define an alternate directory to store signatures", - }, - } - + signCommand cliconfig.SignValues signDescription = "Create a signature file that can be used later to verify the image" - signCommand = cli.Command{ - Name: "sign", - Usage: "Sign an image", - Description: signDescription, - Flags: sortFlags(signFlags), - Action: signCmd, - ArgsUsage: "IMAGE-NAME [IMAGE-NAME ...]", - OnUsageError: usageErrorHandler, + _signCommand = &cobra.Command{ + Use: "sign", + Short: "Sign an image", + Long: signDescription, + RunE: func(cmd *cobra.Command, args []string) error { + signCommand.InputArgs = args + signCommand.GlobalFlags = MainGlobalOpts + return signCmd(&signCommand) + }, + Example: `podman sign --sign-by mykey imageID + podman sign --sign-by mykey --directory ./mykeydir imageID`, } ) +func init() { + signCommand.Command = _signCommand + signCommand.SetUsageTemplate(UsageTemplate()) + flags := signCommand.Flags() + flags.StringVarP(&signCommand.Directory, "directory", "d", "", "Define an alternate directory to store signatures") + flags.StringVar(&signCommand.SignBy, "sign-by", "", "Name of the signing key") + +} + // SignatureStoreDir defines default directory to store signatures const SignatureStoreDir = "/var/lib/containers/sigstore" -func signCmd(c *cli.Context) error { - args := c.Args() +func signCmd(c *cliconfig.SignValues) error { + args := c.InputArgs if len(args) < 1 { return errors.Errorf("at least one image name must be specified") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not create runtime") } defer runtime.Shutdown(false) - signby := c.String("sign-by") + signby := c.SignBy if signby == "" { return errors.Errorf("please provide an identity") } var sigStoreDir string - if c.IsSet("directory") { - sigStoreDir = c.String("directory") + if c.Flag("directory").Changed { + sigStoreDir = c.Directory if _, err := os.Stat(sigStoreDir); err != nil { return errors.Wrapf(err, "invalid directory %s", sigStoreDir) } diff --git a/cmd/podman/start.go b/cmd/podman/start.go index f6e1d9882..c645a35c4 100644 --- a/cmd/podman/start.go +++ b/cmd/podman/start.go @@ -1,88 +1,85 @@ package main import ( - "encoding/json" "fmt" "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" - cc "github.com/containers/libpod/pkg/spec" + opentracing "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - startFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "attach, a", - Usage: "Attach container's STDOUT and STDERR", - }, - cli.StringFlag{ - Name: "detach-keys", - Usage: "Override the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _.", - }, - cli.BoolFlag{ - Name: "interactive, i", - Usage: "Keep STDIN open even if not attached", - }, - cli.BoolTFlag{ - Name: "sig-proxy", - Usage: "Proxy received signals to the process (default true if attaching, false otherwise)", - }, - LatestFlag, - } + startCommand cliconfig.StartValues startDescription = ` podman start Starts one or more containers. The container name or ID can be used. ` - - startCommand = cli.Command{ - Name: "start", - Usage: "Start one or more containers", - Description: startDescription, - Flags: sortFlags(startFlags), - Action: startCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _startCommand = &cobra.Command{ + Use: "start", + Short: "Start one or more containers", + Long: startDescription, + RunE: func(cmd *cobra.Command, args []string) error { + startCommand.InputArgs = args + startCommand.GlobalFlags = MainGlobalOpts + return startCmd(&startCommand) + }, + Example: `podman start --latest + podman start 860a4b231279 5421ab43b45 + podman start --interactive --attach imageID`, } ) -func startCmd(c *cli.Context) error { - args := c.Args() - if len(args) < 1 && !c.Bool("latest") { +func init() { + startCommand.Command = _startCommand + startCommand.SetUsageTemplate(UsageTemplate()) + flags := startCommand.Flags() + flags.BoolVarP(&startCommand.Attach, "attach", "a", false, "Attach container's STDOUT and STDERR") + flags.StringVar(&startCommand.DetachKeys, "detach-keys", "", "Override the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _") + flags.BoolVarP(&startCommand.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached") + flags.BoolVarP(&startCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&startCommand.SigProxy, "sig-proxy", true, "Proxy received signals to the process (default true if attaching, false otherwise)") + markFlagHiddenForRemoteClient("latest", flags) +} + +func startCmd(c *cliconfig.StartValues) error { + if c.Bool("trace") { + span, _ := opentracing.StartSpanFromContext(Ctx, "startCmd") + defer span.Finish() + } + + args := c.InputArgs + if len(args) < 1 && !c.Latest { return errors.Errorf("you must provide at least one container name or id") } - attach := c.Bool("attach") + attach := c.Attach if len(args) > 1 && attach { return errors.Errorf("you cannot start and attach multiple containers at once") } - if err := validateFlags(c, startFlags); err != nil { - return err - } - - sigProxy := c.BoolT("sig-proxy") + sigProxy := c.SigProxy if sigProxy && !attach { - if c.IsSet("sig-proxy") { + if c.Flag("sig-proxy").Changed { return errors.Wrapf(libpod.ErrInvalidArg, "you cannot use sig-proxy without --attach") } else { sigProxy = false } } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - if c.Bool("latest") { + if c.Latest { lastCtr, err := runtime.GetLatestContainer() if err != nil { return errors.Wrapf(err, "unable to get latest container") @@ -112,12 +109,20 @@ func startCmd(c *cli.Context) error { if attach { inputStream := os.Stdin - if !c.Bool("interactive") { + if !c.Interactive { inputStream = nil } // attach to the container and also start it not already running - err = startAttachCtr(ctr, os.Stdout, os.Stderr, inputStream, c.String("detach-keys"), sigProxy, !ctrRunning) + // If the container is in a pod, also set to recursively start dependencies + err = startAttachCtr(ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, sigProxy, !ctrRunning, ctr.PodID() != "") + if errors.Cause(err) == libpod.ErrDetach { + // User manually detached + // Exit cleanly immediately + exitCode = 0 + return nil + } + if ctrRunning { return err } @@ -127,31 +132,30 @@ func startCmd(c *cli.Context) error { } if ecode, err := ctr.Wait(); err != nil { - logrus.Errorf("unable to get exit code of container %s: %q", ctr.ID(), err) + if errors.Cause(err) == libpod.ErrNoSuchCtr { + // The container may have been removed + // Go looking for an exit file + ctrExitCode, err := readExitFile(runtime.GetConfig().TmpDir, ctr.ID()) + if err != nil { + logrus.Errorf("Cannot get exit code: %v", err) + exitCode = 127 + } else { + exitCode = ctrExitCode + } + } } else { exitCode = int(ecode) } - return ctr.Cleanup(ctx) + return nil } if ctrRunning { fmt.Println(ctr.ID()) continue } // Handle non-attach start - if err := ctr.Start(ctx); err != nil { - var createArtifact cc.CreateConfig - artifact, artifactErr := ctr.GetArtifact("create-config") - if artifactErr == nil { - if jsonErr := json.Unmarshal(artifact, &createArtifact); jsonErr != nil { - logrus.Errorf("unable to detect if container %s should be deleted", ctr.ID()) - } - if createArtifact.Rm { - if rmErr := runtime.RemoveContainer(ctx, ctr, true); rmErr != nil { - logrus.Errorf("unable to remove container %s after it failed to start", ctr.ID()) - } - } - } + // If the container is in a pod, also set to recursively start dependencies + if err := ctr.Start(ctx, ctr.PodID() != ""); err != nil { if lastError != nil { fmt.Fprintln(os.Stderr, lastError) } diff --git a/cmd/podman/stats.go b/cmd/podman/stats.go index 769354b23..2bbcd0a17 100644 --- a/cmd/podman/stats.go +++ b/cmd/podman/stats.go @@ -8,12 +8,13 @@ import ( "time" tm "github.com/buger/goterm" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/docker/go-units" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) type statsOutputParams struct { @@ -28,48 +29,46 @@ type statsOutputParams struct { } var ( - statsFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Show all containers. Only running containers are shown by default. The default is false", - }, - cli.BoolFlag{ - Name: "no-stream", - Usage: "Disable streaming stats and only pull the first result, default setting is false", + statsCommand cliconfig.StatsValues + + statsDescription = "display a live stream of one or more containers' resource usage statistics" + _statsCommand = &cobra.Command{ + Use: "stats", + Short: "Display percentage of CPU, memory, network I/O, block I/O and PIDs for one or more containers", + Long: statsDescription, + RunE: func(cmd *cobra.Command, args []string) error { + statsCommand.InputArgs = args + statsCommand.GlobalFlags = MainGlobalOpts + return statsCmd(&statsCommand) }, - cli.StringFlag{ - Name: "format", - Usage: "Pretty-print container statistics to JSON or using a Go template", + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, false) }, - cli.BoolFlag{ - Name: "no-reset", - Usage: "Disable resetting the screen between intervals", - }, LatestFlag, - } - - statsDescription = "Display a live stream of one or more containers' resource usage statistics" - statsCommand = cli.Command{ - Name: "stats", - Usage: "Display percentage of CPU, memory, network I/O, block I/O and PIDs for one or more containers", - Description: statsDescription, - Flags: sortFlags(statsFlags), - Action: statsCmd, - ArgsUsage: "", - OnUsageError: usageErrorHandler, + Example: `podman stats --all --no-stream + podman stats ctrID + podman stats --no-stream --format "table {{.ID}} {{.Name}} {{.MemUsage}}" ctrID`, } ) -func statsCmd(c *cli.Context) error { - if err := validateFlags(c, statsFlags); err != nil { - return err - } +func init() { + statsCommand.Command = _statsCommand + statsCommand.SetUsageTemplate(UsageTemplate()) + flags := statsCommand.Flags() + flags.BoolVarP(&statsCommand.All, "all", "a", false, "Show all containers. Only running containers are shown by default. The default is false") + flags.StringVar(&statsCommand.Format, "format", "", "Pretty-print container statistics to JSON or using a Go template") + flags.BoolVarP(&statsCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&statsCommand.NoReset, "no-reset", false, "Disable resetting the screen between intervals") + flags.BoolVar(&statsCommand.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result, default setting is false") + markFlagHiddenForRemoteClient("latest", flags) +} +func statsCmd(c *cliconfig.StatsValues) error { if os.Geteuid() != 0 { return errors.New("stats is not supported for rootless containers") } - all := c.Bool("all") - latest := c.Bool("latest") + all := c.All + latest := c.Latest ctr := 0 if all { ctr += 1 @@ -77,7 +76,7 @@ func statsCmd(c *cli.Context) error { if latest { ctr += 1 } - if len(c.Args()) > 0 { + if len(c.InputArgs) > 0 { ctr += 1 } @@ -87,14 +86,14 @@ func statsCmd(c *cli.Context) error { return errors.Errorf("you must specify --all, --latest, or at least one container") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) times := -1 - if c.Bool("no-stream") { + if c.NoStream { times = 1 } @@ -102,8 +101,8 @@ func statsCmd(c *cli.Context) error { var containerFunc func() ([]*libpod.Container, error) containerFunc = runtime.GetRunningContainers - if len(c.Args()) > 0 { - containerFunc = func() ([]*libpod.Container, error) { return runtime.GetContainersByList(c.Args()) } + if len(c.InputArgs) > 0 { + containerFunc = func() ([]*libpod.Container, error) { return runtime.GetContainersByList(c.InputArgs) } } else if latest { containerFunc = func() ([]*libpod.Container, error) { lastCtr, err := runtime.GetLatestContainer() @@ -126,7 +125,7 @@ func statsCmd(c *cli.Context) error { initialStats, err := ctr.GetContainerStats(&libpod.ContainerStats{}) if err != nil { // when doing "all", dont worry about containers that are not running - if c.Bool("all") && errors.Cause(err) == libpod.ErrCtrRemoved || errors.Cause(err) == libpod.ErrNoSuchCtr || errors.Cause(err) == libpod.ErrCtrStateInvalid { + if c.All && errors.Cause(err) == libpod.ErrCtrRemoved || errors.Cause(err) == libpod.ErrNoSuchCtr || errors.Cause(err) == libpod.ErrCtrStateInvalid { continue } return err @@ -134,7 +133,7 @@ func statsCmd(c *cli.Context) error { containerStats[ctr.ID()] = initialStats } - format := genStatsFormat(c.String("format")) + format := genStatsFormat(c.Format) step := 1 if times == -1 { @@ -168,7 +167,7 @@ func statsCmd(c *cli.Context) error { if err != nil { return err } - if strings.ToLower(format) != formats.JSONString && !c.Bool("no-reset") { + if strings.ToLower(format) != formats.JSONString && !c.NoReset { tm.Clear() tm.MoveCursor(1, 1) tm.Flush() diff --git a/cmd/podman/stop.go b/cmd/podman/stop.go index 204515c70..67c15b2a8 100644 --- a/cmd/podman/stop.go +++ b/cmd/podman/stop.go @@ -3,27 +3,19 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" + opentracing "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - stopFlags = []cli.Flag{ - cli.UintFlag{ - Name: "timeout, time, t", - Usage: "Seconds to wait for stop before killing the container", - Value: libpod.CtrRemoveTimeout, - }, - cli.BoolFlag{ - Name: "all, a", - Usage: "Stop all running containers", - }, LatestFlag, - } + stopCommand cliconfig.StopValues stopDescription = ` podman stop @@ -31,36 +23,49 @@ var ( A timeout to forcibly stop the container can also be set but defaults to 10 seconds otherwise. ` - - stopCommand = cli.Command{ - Name: "stop", - Usage: "Stop one or more containers", - Description: stopDescription, - Flags: sortFlags(stopFlags), - Action: stopCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", - OnUsageError: usageErrorHandler, + _stopCommand = &cobra.Command{ + Use: "stop", + Short: "Stop one or more containers", + Long: stopDescription, + RunE: func(cmd *cobra.Command, args []string) error { + stopCommand.InputArgs = args + stopCommand.GlobalFlags = MainGlobalOpts + return stopCmd(&stopCommand) + }, + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, false) + }, + Example: `podman stop ctrID + podman stop --latest + podman stop --timeout 2 mywebserver 6e534f14da9d`, } ) -func stopCmd(c *cli.Context) error { - - if err := checkAllAndLatest(c); err != nil { - return err - } +func init() { + stopCommand.Command = _stopCommand + stopCommand.SetUsageTemplate(UsageTemplate()) + flags := stopCommand.Flags() + flags.BoolVarP(&stopCommand.All, "all", "a", false, "Stop all running containers") + flags.BoolVarP(&stopCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.UintVar(&stopCommand.Timeout, "time", libpod.CtrRemoveTimeout, "Seconds to wait for stop before killing the container") + flags.UintVarP(&stopCommand.Timeout, "timeout", "t", libpod.CtrRemoveTimeout, "Seconds to wait for stop before killing the container") + markFlagHiddenForRemoteClient("latest", flags) +} - if err := validateFlags(c, stopFlags); err != nil { - return err +func stopCmd(c *cliconfig.StopValues) error { + if c.Bool("trace") { + span, _ := opentracing.StartSpanFromContext(Ctx, "stopCmd") + defer span.Finish() } rootless.SetSkipStorageSetup(true) - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + containers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStateRunning, "running") if err != nil { if len(containers) == 0 { return err @@ -72,8 +77,8 @@ func stopCmd(c *cli.Context) error { for _, ctr := range containers { con := ctr var stopTimeout uint - if c.IsSet("timeout") { - stopTimeout = c.Uint("timeout") + if c.Flag("timeout").Changed { + stopTimeout = c.Timeout } else { stopTimeout = ctr.StopTimeout() } @@ -92,7 +97,7 @@ func stopCmd(c *cli.Context) error { maxWorkers := shared.Parallelize("stop") if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalInt("max-workers") + maxWorkers = c.GlobalFlags.MaxWorks } logrus.Debugf("Setting maximum workers to %d", maxWorkers) diff --git a/cmd/podman/system.go b/cmd/podman/system.go index 9596252ad..741b79da5 100644 --- a/cmd/podman/system.go +++ b/cmd/podman/system.go @@ -1,29 +1,28 @@ package main import ( - "sort" - - "github.com/urfave/cli" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" ) var ( - systemSubCommands = []cli.Command{ - pruneSystemCommand, - } systemDescription = "Manage podman" - systemCommand = cli.Command{ - Name: "system", - Usage: "Manage podman", - Description: systemDescription, - ArgsUsage: "", - Subcommands: getSystemSubCommandsSorted(), - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + + systemCommand = cliconfig.PodmanCommand{ + Command: &cobra.Command{ + Use: "system", + Short: "Manage podman", + Long: systemDescription, + }, } ) -func getSystemSubCommandsSorted() []cli.Command { - systemSubCommands = append(systemSubCommands, getSystemSubCommands()...) - sort.Sort(commandSortedAlpha{systemSubCommands}) - return systemSubCommands +var systemCommands = []*cobra.Command{ + _infoCommand, +} + +func init() { + systemCommand.AddCommand(systemCommands...) + systemCommand.AddCommand(getSystemSubCommands()...) + systemCommand.SetUsageTemplate(UsageTemplate()) } diff --git a/cmd/podman/system_prune.go b/cmd/podman/system_prune.go index 64d291560..a823dcad1 100644 --- a/cmd/podman/system_prune.go +++ b/cmd/podman/system_prune.go @@ -6,50 +6,50 @@ import ( "os" "strings" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/libpod/adapter" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + pruneSystemCommand cliconfig.SystemPruneValues pruneSystemDescription = ` podman system prune Remove unused data ` - pruneSystemFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Remove all unused data", + _pruneSystemCommand = &cobra.Command{ + Use: "prune", + Short: "Remove unused data", + Long: pruneSystemDescription, + RunE: func(cmd *cobra.Command, args []string) error { + pruneSystemCommand.InputArgs = args + pruneSystemCommand.GlobalFlags = MainGlobalOpts + return pruneSystemCmd(&pruneSystemCommand) }, - cli.BoolFlag{ - Name: "force, f", - Usage: "Do not prompt for confirmation", - }, - cli.BoolFlag{ - Name: "volumes", - Usage: "Prune volumes", - }, - } - pruneSystemCommand = cli.Command{ - Name: "prune", - Usage: "Remove unused data", - Description: pruneSystemDescription, - Action: pruneSystemCmd, - OnUsageError: usageErrorHandler, - Flags: pruneSystemFlags, } ) -func pruneSystemCmd(c *cli.Context) error { +func init() { + pruneSystemCommand.Command = _pruneSystemCommand + pruneSystemCommand.SetUsageTemplate(UsageTemplate()) + flags := pruneSystemCommand.Flags() + flags.BoolVarP(&pruneSystemCommand.All, "all", "a", false, "Remove all unused data") + flags.BoolVarP(&pruneSystemCommand.Force, "force", "f", false, "Do not prompt for confirmation") + flags.BoolVar(&pruneSystemCommand.Volume, "volumes", false, "Prune volumes") + +} + +func pruneSystemCmd(c *cliconfig.SystemPruneValues) error { // Prompt for confirmation if --force is not set - if !c.Bool("force") { + if !c.Force { reader := bufio.NewReader(os.Stdin) volumeString := "" - if c.Bool("volumes") { + if c.Volume { volumeString = ` - all volumes not used by at least one container` } @@ -68,7 +68,7 @@ Are you sure you want to continue? [y/N] `, volumeString) } } - runtime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -76,7 +76,7 @@ Are you sure you want to continue? [y/N] `, volumeString) ctx := getContext() fmt.Println("Deleted Containers") - lasterr := pruneContainers(runtime, ctx, shared.Parallelize("rm"), false) + lasterr := pruneContainers(runtime, ctx, shared.Parallelize("rm"), false, false) if c.Bool("volumes") { fmt.Println("Deleted Volumes") err := volumePrune(runtime, getContext()) @@ -90,7 +90,7 @@ Are you sure you want to continue? [y/N] `, volumeString) // Call prune; if any cids are returned, print them and then // return err in case an error also came up - pruneCids, err := runtime.PruneImages(c.Bool("all")) + pruneCids, err := runtime.PruneImages(c.All) if len(pruneCids) > 0 { fmt.Println("Deleted Images") for _, cid := range pruneCids { diff --git a/cmd/podman/system_renumber.go b/cmd/podman/system_renumber.go new file mode 100644 index 000000000..c8ce628b1 --- /dev/null +++ b/cmd/podman/system_renumber.go @@ -0,0 +1,49 @@ +package main + +import ( + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + renumberCommand cliconfig.SystemRenumberValues + renumberDescription = ` + podman system renumber + + Migrate lock numbers to handle a change in maximum number of locks. + Mandatory after the number of locks in libpod.conf is changed. +` + + _renumberCommand = &cobra.Command{ + Use: "renumber", + Short: "Migrate lock numbers", + Long: renumberDescription, + RunE: func(cmd *cobra.Command, args []string) error { + renumberCommand.InputArgs = args + renumberCommand.GlobalFlags = MainGlobalOpts + return renumberCmd(&renumberCommand) + }, + } +) + +func init() { + renumberCommand.Command = _renumberCommand + renumberCommand.SetUsageTemplate(UsageTemplate()) +} + +func renumberCmd(c *cliconfig.SystemRenumberValues) error { + // We need to pass one extra option to NewRuntime. + // This will inform the OCI runtime to start a renumber. + // That's controlled by the last argument to GetRuntime. + r, err := libpodruntime.GetRuntimeRenumber(&c.PodmanCommand) + if err != nil { + return errors.Wrapf(err, "error renumbering locks") + } + if err := r.Shutdown(false); err != nil { + return err + } + + return nil +} diff --git a/cmd/podman/tag.go b/cmd/podman/tag.go index d19cf69a2..2b9d67066 100644 --- a/cmd/podman/tag.go +++ b/cmd/podman/tag.go @@ -1,29 +1,42 @@ package main import ( - "github.com/containers/libpod/libpod/adapter" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + tagCommand cliconfig.TagValues + tagDescription = "Adds one or more additional names to locally-stored image" - tagCommand = cli.Command{ - Name: "tag", - Usage: "Add an additional name to a local image", - Description: tagDescription, - Action: tagCmd, - ArgsUsage: "IMAGE-NAME [IMAGE-NAME ...]", - OnUsageError: usageErrorHandler, + _tagCommand = &cobra.Command{ + Use: "tag", + Short: "Add an additional name to a local image", + Long: tagDescription, + RunE: func(cmd *cobra.Command, args []string) error { + tagCommand.InputArgs = args + tagCommand.GlobalFlags = MainGlobalOpts + return tagCmd(&tagCommand) + }, + Example: `podman tag 0e3bbc2 fedora:latest + podman tag imageID:latest myNewImage:newTag + podman tag httpd myregistryhost:5000/fedora/httpd:v2`, } ) -func tagCmd(c *cli.Context) error { - args := c.Args() +func init() { + tagCommand.Command = _tagCommand + tagCommand.SetUsageTemplate(UsageTemplate()) +} + +func tagCmd(c *cliconfig.TagValues) error { + args := c.InputArgs if len(args) < 2 { return errors.Errorf("image name and at least one new name must be specified") } - runtime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not create runtime") } diff --git a/cmd/podman/top.go b/cmd/podman/top.go index 3012265ea..36d6bb6b4 100644 --- a/cmd/podman/top.go +++ b/cmd/podman/top.go @@ -6,11 +6,12 @@ import ( "strings" "text/tabwriter" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) func getDescriptorString() string { @@ -24,13 +25,7 @@ Format Descriptors: } var ( - topFlags = []cli.Flag{ - LatestFlag, - cli.BoolFlag{ - Name: "list-descriptors", - Hidden: true, - }, - } + topCommand cliconfig.TopValues topDescription = fmt.Sprintf(`Display the running processes of the container. Specify format descriptors to alter the output. You may run "podman top -l pid pcpu seccomp" to print the process ID, the CPU percentage and the seccomp mode of each process of @@ -38,24 +33,37 @@ the latest container. %s `, getDescriptorString()) - topCommand = cli.Command{ - Name: "top", - Usage: "Display the running processes of a container", - Description: topDescription, - Flags: sortFlags(topFlags), - Action: topCmd, - ArgsUsage: "CONTAINER-NAME [format descriptors]", - SkipArgReorder: true, - OnUsageError: usageErrorHandler, + _topCommand = &cobra.Command{ + Use: "top", + Short: "Display the running processes of a container", + Long: topDescription, + RunE: func(cmd *cobra.Command, args []string) error { + topCommand.InputArgs = args + topCommand.GlobalFlags = MainGlobalOpts + return topCmd(&topCommand) + }, + Example: `podman top ctrID + podman top --latest + podman top ctrID pid seccomp args %C`, } ) -func topCmd(c *cli.Context) error { +func init() { + topCommand.Command = _topCommand + topCommand.SetUsageTemplate(UsageTemplate()) + flags := topCommand.Flags() + flags.BoolVar(&topCommand.ListDescriptors, "list-descriptors", false, "") + flags.MarkHidden("list-descriptors") + flags.BoolVarP(&topCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + markFlagHiddenForRemoteClient("latest", flags) +} + +func topCmd(c *cliconfig.TopValues) error { var container *libpod.Container var err error - args := c.Args() + args := c.InputArgs - if c.Bool("list-descriptors") { + if c.ListDescriptors { descriptors, err := libpod.GetContainerPidInformationDescriptors() if err != nil { return err @@ -64,22 +72,19 @@ func topCmd(c *cli.Context) error { return nil } - if len(args) < 1 && !c.Bool("latest") { + if len(args) < 1 && !c.Latest { return errors.Errorf("you must provide the name or id of a running container") } - if err := validateFlags(c, topFlags); err != nil { - return err - } rootless.SetSkipStorageSetup(true) - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) var descriptors []string - if c.Bool("latest") { + if c.Latest { descriptors = args container, err = runtime.GetLatestContainer() } else { diff --git a/cmd/podman/trust.go b/cmd/podman/trust.go index a99be6ba2..8b02dcdc6 100644 --- a/cmd/podman/trust.go +++ b/cmd/podman/trust.go @@ -1,369 +1,22 @@ package main import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "sort" - "strings" - - "github.com/containers/image/types" - "github.com/containers/libpod/cmd/podman/formats" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/trust" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" ) var ( - setTrustFlags = []cli.Flag{ - cli.StringFlag{ - Name: "type, t", - Usage: "Trust type, accept values: signedBy(default), accept, reject.", - Value: "signedBy", - }, - cli.StringSliceFlag{ - Name: "pubkeysfile, f", - Usage: `Path of installed public key(s) to trust for TARGET. - Absolute path to keys is added to policy.json. May - used multiple times to define multiple public keys. - File(s) must exist before using this command.`, - }, - cli.StringFlag{ - Name: "policypath", - Hidden: true, - }, - } - showTrustFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "raw", - Usage: "Output raw policy file", + trustCommand = cliconfig.PodmanCommand{ + Command: &cobra.Command{ + Use: "trust", + Short: "Manage container image trust policy", + Long: "podman image trust command", }, - cli.BoolFlag{ - Name: "json, j", - Usage: "Output as json", - }, - cli.StringFlag{ - Name: "policypath", - Hidden: true, - }, - cli.StringFlag{ - Name: "registrypath", - Hidden: true, - }, - } - - setTrustDescription = "Set default trust policy or add a new trust policy for a registry" - setTrustCommand = cli.Command{ - Name: "set", - Usage: "Set default trust policy or a new trust policy for a registry", - Description: setTrustDescription, - Flags: sortFlags(setTrustFlags), - ArgsUsage: "default | REGISTRY[/REPOSITORY]", - Action: setTrustCmd, - OnUsageError: usageErrorHandler, - } - - showTrustDescription = "Display trust policy for the system" - showTrustCommand = cli.Command{ - Name: "show", - Usage: "Display trust policy for the system", - Description: showTrustDescription, - Flags: sortFlags(showTrustFlags), - Action: showTrustCmd, - ArgsUsage: "", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, - } - - trustSubCommands = []cli.Command{ - setTrustCommand, - showTrustCommand, - } - - trustDescription = fmt.Sprintf(`Manages the trust policy of the host system. (%s) - Trust policy describes a registry scope that must be signed by public keys.`, getDefaultPolicyPath()) - trustCommand = cli.Command{ - Name: "trust", - Usage: "Manage container image trust policy", - Description: trustDescription, - ArgsUsage: "{set,show} ...", - Subcommands: trustSubCommands, - OnUsageError: usageErrorHandler, } ) -func showTrustCmd(c *cli.Context) error { - runtime, err := libpodruntime.GetRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not create runtime") - } - - var ( - policyPath string - systemRegistriesDirPath string - outjson interface{} - ) - if c.IsSet("policypath") { - policyPath = c.String("policypath") - } else { - policyPath = trust.DefaultPolicyPath(runtime.SystemContext()) - } - policyContent, err := ioutil.ReadFile(policyPath) - if err != nil { - return errors.Wrapf(err, "unable to read %s", policyPath) - } - if c.IsSet("registrypath") { - systemRegistriesDirPath = c.String("registrypath") - } else { - systemRegistriesDirPath = trust.RegistriesDirPath(runtime.SystemContext()) - } - - if c.Bool("raw") { - _, err := os.Stdout.Write(policyContent) - if err != nil { - return errors.Wrap(err, "could not read trust policies") - } - return nil - } - - policyContentStruct, err := trust.GetPolicy(policyPath) - if err != nil { - return errors.Wrapf(err, "could not read trust policies") - } - - if c.Bool("json") { - policyJSON, err := getPolicyJSON(policyContentStruct, systemRegistriesDirPath) - if err != nil { - return errors.Wrapf(err, "could not show trust policies in JSON format") - } - outjson = policyJSON - out := formats.JSONStruct{Output: outjson} - return formats.Writer(out).Out() - } - - showOutputMap, err := getPolicyShowOutput(policyContentStruct, systemRegistriesDirPath) - if err != nil { - return errors.Wrapf(err, "could not show trust policies") - } - out := formats.StdoutTemplateArray{Output: showOutputMap, Template: "{{.Repo}}\t{{.Trusttype}}\t{{.GPGid}}\t{{.Sigstore}}"} - return formats.Writer(out).Out() -} - -func setTrustCmd(c *cli.Context) error { - runtime, err := libpodruntime.GetRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not create runtime") - } - var ( - policyPath string - policyContentStruct trust.PolicyContent - newReposContent []trust.RepoContent - ) - args := c.Args() - if len(args) != 1 { - return errors.Errorf("default or a registry name must be specified") - } - valid, err := image.IsValidImageURI(args[0]) - if err != nil || !valid { - return errors.Wrapf(err, "invalid image uri %s", args[0]) - } - - trusttype := c.String("type") - if !isValidTrustType(trusttype) { - return errors.Errorf("invalid choice: %s (choose from 'accept', 'reject', 'signedBy')", trusttype) - } - if trusttype == "accept" { - trusttype = "insecureAcceptAnything" - } - - pubkeysfile := c.StringSlice("pubkeysfile") - if len(pubkeysfile) == 0 && trusttype == "signedBy" { - return errors.Errorf("At least one public key must be defined for type 'signedBy'") - } - - if c.IsSet("policypath") { - policyPath = c.String("policypath") - } else { - policyPath = trust.DefaultPolicyPath(runtime.SystemContext()) - } - _, err = os.Stat(policyPath) - if !os.IsNotExist(err) { - policyContent, err := ioutil.ReadFile(policyPath) - if err != nil { - return errors.Wrapf(err, "unable to read %s", policyPath) - } - if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil { - return errors.Errorf("could not read trust policies") - } - } - if len(pubkeysfile) != 0 { - for _, filepath := range pubkeysfile { - newReposContent = append(newReposContent, trust.RepoContent{Type: trusttype, KeyType: "GPGKeys", KeyPath: filepath}) - } - } else { - newReposContent = append(newReposContent, trust.RepoContent{Type: trusttype}) - } - if args[0] == "default" { - policyContentStruct.Default = newReposContent - } else { - if len(policyContentStruct.Default) == 0 { - return errors.Errorf("Default trust policy must be set.") - } - registryExists := false - for transport, transportval := range policyContentStruct.Transports { - _, registryExists = transportval[args[0]] - if registryExists { - policyContentStruct.Transports[transport][args[0]] = newReposContent - break - } - } - if !registryExists { - if policyContentStruct.Transports == nil { - policyContentStruct.Transports = make(map[string]trust.RepoMap) - } - if policyContentStruct.Transports["docker"] == nil { - policyContentStruct.Transports["docker"] = make(map[string][]trust.RepoContent) - } - policyContentStruct.Transports["docker"][args[0]] = append(policyContentStruct.Transports["docker"][args[0]], newReposContent...) - } - } - - data, err := json.MarshalIndent(policyContentStruct, "", " ") - if err != nil { - return errors.Wrapf(err, "error setting trust policy") - } - err = ioutil.WriteFile(policyPath, data, 0644) - if err != nil { - return errors.Wrapf(err, "error setting trust policy") - } - return nil -} - -func sortShowOutputMapKey(m map[string]trust.ShowOutput) []string { - keys := make([]string, len(m)) - i := 0 - for k := range m { - keys[i] = k - i++ - } - sort.Strings(keys) - return keys -} - -func isValidTrustType(t string) bool { - if t == "accept" || t == "insecureAcceptAnything" || t == "reject" || t == "signedBy" { - return true - } - return false -} - -func getDefaultPolicyPath() string { - return trust.DefaultPolicyPath(&types.SystemContext{}) -} - -func getPolicyJSON(policyContentStruct trust.PolicyContent, systemRegistriesDirPath string) (map[string]map[string]interface{}, error) { - registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath) - if err != nil { - return nil, err - } - - policyJSON := make(map[string]map[string]interface{}) - if len(policyContentStruct.Default) > 0 { - policyJSON["* (default)"] = make(map[string]interface{}) - policyJSON["* (default)"]["type"] = policyContentStruct.Default[0].Type - } - for transname, transval := range policyContentStruct.Transports { - for repo, repoval := range transval { - policyJSON[repo] = make(map[string]interface{}) - policyJSON[repo]["type"] = repoval[0].Type - policyJSON[repo]["transport"] = transname - keyarr := []string{} - uids := []string{} - for _, repoele := range repoval { - if len(repoele.KeyPath) > 0 { - keyarr = append(keyarr, repoele.KeyPath) - uids = append(uids, trust.GetGPGIdFromKeyPath(repoele.KeyPath)...) - } - if len(repoele.KeyData) > 0 { - keyarr = append(keyarr, string(repoele.KeyData)) - uids = append(uids, trust.GetGPGIdFromKeyData(string(repoele.KeyData))...) - } - } - policyJSON[repo]["keys"] = keyarr - policyJSON[repo]["sigstore"] = "" - registryNamespace := trust.HaveMatchRegistry(repo, registryConfigs) - if registryNamespace != nil { - policyJSON[repo]["sigstore"] = registryNamespace.SigStore - } - } - } - return policyJSON, nil -} - -var typeDescription = map[string]string{"insecureAcceptAnything": "accept", "signedBy": "signed", "reject": "reject"} - -func trustTypeDescription(trustType string) string { - trustDescription, exist := typeDescription[trustType] - if !exist { - logrus.Warnf("invalid trust type %s", trustType) - } - return trustDescription -} - -func getPolicyShowOutput(policyContentStruct trust.PolicyContent, systemRegistriesDirPath string) ([]interface{}, error) { - var output []interface{} - - registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath) - if err != nil { - return nil, err - } - - trustShowOutputMap := make(map[string]trust.ShowOutput) - if len(policyContentStruct.Default) > 0 { - defaultPolicyStruct := trust.ShowOutput{ - Repo: "default", - Trusttype: trustTypeDescription(policyContentStruct.Default[0].Type), - } - trustShowOutputMap["* (default)"] = defaultPolicyStruct - } - for _, transval := range policyContentStruct.Transports { - for repo, repoval := range transval { - tempTrustShowOutput := trust.ShowOutput{ - Repo: repo, - Trusttype: repoval[0].Type, - } - keyarr := []string{} - uids := []string{} - for _, repoele := range repoval { - if len(repoele.KeyPath) > 0 { - keyarr = append(keyarr, repoele.KeyPath) - uids = append(uids, trust.GetGPGIdFromKeyPath(repoele.KeyPath)...) - } - if len(repoele.KeyData) > 0 { - keyarr = append(keyarr, string(repoele.KeyData)) - uids = append(uids, trust.GetGPGIdFromKeyData(string(repoele.KeyData))...) - } - } - tempTrustShowOutput.GPGid = strings.Join(uids, ", ") - - registryNamespace := trust.HaveMatchRegistry(repo, registryConfigs) - if registryNamespace != nil { - tempTrustShowOutput.Sigstore = registryNamespace.SigStore - } - trustShowOutputMap[repo] = tempTrustShowOutput - } - } - - sortedRepos := sortShowOutputMapKey(trustShowOutputMap) - for _, reponame := range sortedRepos { - showOutput, exists := trustShowOutputMap[reponame] - if exists { - output = append(output, interface{}(showOutput)) - } - } - return output, nil +func init() { + trustCommand.SetUsageTemplate(UsageTemplate()) + trustCommand.AddCommand(getTrustSubCommands()...) + imageCommand.AddCommand(trustCommand.Command) } diff --git a/cmd/podman/trust_set_show.go b/cmd/podman/trust_set_show.go new file mode 100644 index 000000000..0a4783d0a --- /dev/null +++ b/cmd/podman/trust_set_show.go @@ -0,0 +1,344 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + "os" + "sort" + "strings" + + "github.com/containers/image/types" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/formats" + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/trust" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + setTrustCommand cliconfig.SetTrustValues + showTrustCommand cliconfig.ShowTrustValues + setTrustDescription = "Set default trust policy or add a new trust policy for a registry" + _setTrustCommand = &cobra.Command{ + Use: "set", + Short: "Set default trust policy or a new trust policy for a registry", + Long: setTrustDescription, + Example: "", + RunE: func(cmd *cobra.Command, args []string) error { + setTrustCommand.InputArgs = args + setTrustCommand.GlobalFlags = MainGlobalOpts + return setTrustCmd(&setTrustCommand) + }, + } + + showTrustDescription = "Display trust policy for the system" + _showTrustCommand = &cobra.Command{ + Use: "show", + Short: "Display trust policy for the system", + Long: showTrustDescription, + RunE: func(cmd *cobra.Command, args []string) error { + showTrustCommand.InputArgs = args + showTrustCommand.GlobalFlags = MainGlobalOpts + return showTrustCmd(&showTrustCommand) + }, + Example: "", + } +) + +func init() { + setTrustCommand.Command = _setTrustCommand + setTrustCommand.SetUsageTemplate(UsageTemplate()) + showTrustCommand.Command = _showTrustCommand + showTrustCommand.SetUsageTemplate(UsageTemplate()) + setFlags := setTrustCommand.Flags() + setFlags.StringVar(&setTrustCommand.PolicyPath, "policypath", "", "") + setFlags.MarkHidden("policypath") + setFlags.StringSliceVarP(&setTrustCommand.PubKeysFile, "pubkeysfile", "f", []string{}, `Path of installed public key(s) to trust for TARGET. +Absolute path to keys is added to policy.json. May +used multiple times to define multiple public keys. +File(s) must exist before using this command`) + setFlags.StringVarP(&setTrustCommand.TrustType, "type", "t", "signedBy", "Trust type, accept values: signedBy(default), accept, reject") + + showFlags := showTrustCommand.Flags() + showFlags.BoolVarP(&showTrustCommand.Json, "json", "j", false, "Output as json") + showFlags.StringVar(&showTrustCommand.PolicyPath, "policypath", "", "") + showFlags.BoolVar(&showTrustCommand.Raw, "raw", false, "Output raw policy file") + showFlags.MarkHidden("policypath") + showFlags.StringVar(&showTrustCommand.RegistryPath, "registrypath", "", "") + showFlags.MarkHidden("registrypath") +} + +func showTrustCmd(c *cliconfig.ShowTrustValues) error { + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + if err != nil { + return errors.Wrapf(err, "could not create runtime") + } + + var ( + policyPath string + systemRegistriesDirPath string + outjson interface{} + ) + if c.Flag("policypath").Changed { + policyPath = c.PolicyPath + } else { + policyPath = trust.DefaultPolicyPath(runtime.SystemContext()) + } + policyContent, err := ioutil.ReadFile(policyPath) + if err != nil { + return errors.Wrapf(err, "unable to read %s", policyPath) + } + if c.Flag("registrypath").Changed { + systemRegistriesDirPath = c.RegistryPath + } else { + systemRegistriesDirPath = trust.RegistriesDirPath(runtime.SystemContext()) + } + + if c.Raw { + _, err := os.Stdout.Write(policyContent) + if err != nil { + return errors.Wrap(err, "could not read raw trust policies") + } + return nil + } + + policyContentStruct, err := trust.GetPolicy(policyPath) + if err != nil { + return errors.Wrapf(err, "could not read trust policies") + } + + if c.Json { + policyJSON, err := getPolicyJSON(policyContentStruct, systemRegistriesDirPath) + if err != nil { + return errors.Wrapf(err, "could not show trust policies in JSON format") + } + outjson = policyJSON + out := formats.JSONStruct{Output: outjson} + return formats.Writer(out).Out() + } + + showOutputMap, err := getPolicyShowOutput(policyContentStruct, systemRegistriesDirPath) + if err != nil { + return errors.Wrapf(err, "could not show trust policies") + } + out := formats.StdoutTemplateArray{Output: showOutputMap, Template: "{{.Repo}}\t{{.Trusttype}}\t{{.GPGid}}\t{{.Sigstore}}"} + return formats.Writer(out).Out() +} + +func setTrustCmd(c *cliconfig.SetTrustValues) error { + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + if err != nil { + return errors.Wrapf(err, "could not create runtime") + } + var ( + policyPath string + policyContentStruct trust.PolicyContent + newReposContent []trust.RepoContent + ) + args := c.InputArgs + if len(args) != 1 { + return errors.Errorf("default or a registry name must be specified") + } + valid, err := image.IsValidImageURI(args[0]) + if err != nil || !valid { + return errors.Wrapf(err, "invalid image uri %s", args[0]) + } + + trusttype := c.TrustType + if !isValidTrustType(trusttype) { + return errors.Errorf("invalid choice: %s (choose from 'accept', 'reject', 'signedBy')", trusttype) + } + if trusttype == "accept" { + trusttype = "insecureAcceptAnything" + } + + pubkeysfile := c.PubKeysFile + if len(pubkeysfile) == 0 && trusttype == "signedBy" { + return errors.Errorf("At least one public key must be defined for type 'signedBy'") + } + + if c.Flag("policypath").Changed { + policyPath = c.PolicyPath + } else { + policyPath = trust.DefaultPolicyPath(runtime.SystemContext()) + } + _, err = os.Stat(policyPath) + if !os.IsNotExist(err) { + policyContent, err := ioutil.ReadFile(policyPath) + if err != nil { + return errors.Wrapf(err, "unable to read %s", policyPath) + } + if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil { + return errors.Errorf("could not read trust policies") + } + } + if len(pubkeysfile) != 0 { + for _, filepath := range pubkeysfile { + newReposContent = append(newReposContent, trust.RepoContent{Type: trusttype, KeyType: "GPGKeys", KeyPath: filepath}) + } + } else { + newReposContent = append(newReposContent, trust.RepoContent{Type: trusttype}) + } + if args[0] == "default" { + policyContentStruct.Default = newReposContent + } else { + if len(policyContentStruct.Default) == 0 { + return errors.Errorf("Default trust policy must be set.") + } + registryExists := false + for transport, transportval := range policyContentStruct.Transports { + _, registryExists = transportval[args[0]] + if registryExists { + policyContentStruct.Transports[transport][args[0]] = newReposContent + break + } + } + if !registryExists { + if policyContentStruct.Transports == nil { + policyContentStruct.Transports = make(map[string]trust.RepoMap) + } + if policyContentStruct.Transports["docker"] == nil { + policyContentStruct.Transports["docker"] = make(map[string][]trust.RepoContent) + } + policyContentStruct.Transports["docker"][args[0]] = append(policyContentStruct.Transports["docker"][args[0]], newReposContent...) + } + } + + data, err := json.MarshalIndent(policyContentStruct, "", " ") + if err != nil { + return errors.Wrapf(err, "error setting trust policy") + } + err = ioutil.WriteFile(policyPath, data, 0644) + if err != nil { + return errors.Wrapf(err, "error setting trust policy") + } + return nil +} + +func sortShowOutputMapKey(m map[string]trust.ShowOutput) []string { + keys := make([]string, len(m)) + i := 0 + for k := range m { + keys[i] = k + i++ + } + sort.Strings(keys) + return keys +} + +func isValidTrustType(t string) bool { + if t == "accept" || t == "insecureAcceptAnything" || t == "reject" || t == "signedBy" { + return true + } + return false +} + +func getDefaultPolicyPath() string { + return trust.DefaultPolicyPath(&types.SystemContext{}) +} + +func getPolicyJSON(policyContentStruct trust.PolicyContent, systemRegistriesDirPath string) (map[string]map[string]interface{}, error) { + registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath) + if err != nil { + return nil, err + } + + policyJSON := make(map[string]map[string]interface{}) + if len(policyContentStruct.Default) > 0 { + policyJSON["* (default)"] = make(map[string]interface{}) + policyJSON["* (default)"]["type"] = policyContentStruct.Default[0].Type + } + for transname, transval := range policyContentStruct.Transports { + for repo, repoval := range transval { + policyJSON[repo] = make(map[string]interface{}) + policyJSON[repo]["type"] = repoval[0].Type + policyJSON[repo]["transport"] = transname + keyarr := []string{} + uids := []string{} + for _, repoele := range repoval { + if len(repoele.KeyPath) > 0 { + keyarr = append(keyarr, repoele.KeyPath) + uids = append(uids, trust.GetGPGIdFromKeyPath(repoele.KeyPath)...) + } + if len(repoele.KeyData) > 0 { + keyarr = append(keyarr, string(repoele.KeyData)) + uids = append(uids, trust.GetGPGIdFromKeyData(string(repoele.KeyData))...) + } + } + policyJSON[repo]["keys"] = keyarr + policyJSON[repo]["sigstore"] = "" + registryNamespace := trust.HaveMatchRegistry(repo, registryConfigs) + if registryNamespace != nil { + policyJSON[repo]["sigstore"] = registryNamespace.SigStore + } + } + } + return policyJSON, nil +} + +var typeDescription = map[string]string{"insecureAcceptAnything": "accept", "signedBy": "signed", "reject": "reject"} + +func trustTypeDescription(trustType string) string { + trustDescription, exist := typeDescription[trustType] + if !exist { + logrus.Warnf("invalid trust type %s", trustType) + } + return trustDescription +} + +func getPolicyShowOutput(policyContentStruct trust.PolicyContent, systemRegistriesDirPath string) ([]interface{}, error) { + var output []interface{} + + registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath) + if err != nil { + return nil, err + } + + trustShowOutputMap := make(map[string]trust.ShowOutput) + if len(policyContentStruct.Default) > 0 { + defaultPolicyStruct := trust.ShowOutput{ + Repo: "default", + Trusttype: trustTypeDescription(policyContentStruct.Default[0].Type), + } + trustShowOutputMap["* (default)"] = defaultPolicyStruct + } + for _, transval := range policyContentStruct.Transports { + for repo, repoval := range transval { + tempTrustShowOutput := trust.ShowOutput{ + Repo: repo, + Trusttype: repoval[0].Type, + } + keyarr := []string{} + uids := []string{} + for _, repoele := range repoval { + if len(repoele.KeyPath) > 0 { + keyarr = append(keyarr, repoele.KeyPath) + uids = append(uids, trust.GetGPGIdFromKeyPath(repoele.KeyPath)...) + } + if len(repoele.KeyData) > 0 { + keyarr = append(keyarr, string(repoele.KeyData)) + uids = append(uids, trust.GetGPGIdFromKeyData(string(repoele.KeyData))...) + } + } + tempTrustShowOutput.GPGid = strings.Join(uids, ", ") + + registryNamespace := trust.HaveMatchRegistry(repo, registryConfigs) + if registryNamespace != nil { + tempTrustShowOutput.Sigstore = registryNamespace.SigStore + } + trustShowOutputMap[repo] = tempTrustShowOutput + } + } + + sortedRepos := sortShowOutputMapKey(trustShowOutputMap) + for _, reponame := range sortedRepos { + showOutput, exists := trustShowOutputMap[reponame] + if exists { + output = append(output, interface{}(showOutput)) + } + } + return output, nil +} diff --git a/cmd/podman/umount.go b/cmd/podman/umount.go index ab6925e65..6d9009388 100644 --- a/cmd/podman/umount.go +++ b/cmd/podman/umount.go @@ -3,60 +3,64 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/storage" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - umountFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Umount all of the currently mounted containers", - }, - cli.BoolFlag{ - Name: "force, f", - Usage: "Force the complete umount all of the currently mounted containers", - }, - LatestFlag, - } - - description = ` + umountCommand cliconfig.UmountValues + description = ` Container storage increments a mount counter each time a container is mounted. When a container is unmounted, the mount counter is decremented and the container's root filesystem is physically unmounted only when the mount counter reaches zero indicating no other processes are using the mount. An unmount can be forced with the --force flag. ` - umountCommand = cli.Command{ - Name: "umount", - Aliases: []string{"unmount"}, - Usage: "Unmount working container's root filesystem", - Description: description, - Flags: sortFlags(umountFlags), - Action: umountCmd, - ArgsUsage: "CONTAINER-NAME-OR-ID", - OnUsageError: usageErrorHandler, + _umountCommand = &cobra.Command{ + Use: "umount", + Aliases: []string{"unmount"}, + Short: "Unmounts working container's root filesystem", + Long: description, + RunE: func(cmd *cobra.Command, args []string) error { + umountCommand.InputArgs = args + umountCommand.GlobalFlags = MainGlobalOpts + return umountCmd(&umountCommand) + }, + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, true) + }, + Example: `podman umount ctrID + podman umount ctrID1 ctrID2 ctrID3 + podman umount --all`, } ) -func umountCmd(c *cli.Context) error { - runtime, err := libpodruntime.GetRuntime(c) +func init() { + umountCommand.Command = _umountCommand + umountCommand.SetUsageTemplate(UsageTemplate()) + flags := umountCommand.Flags() + flags.BoolVarP(&umountCommand.All, "all", "a", false, "Umount all of the currently mounted containers") + flags.BoolVarP(&umountCommand.Force, "force", "f", false, "Force the complete umount all of the currently mounted containers") + flags.BoolVarP(&umountCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + markFlagHiddenForRemoteClient("latest", flags) +} + +func umountCmd(c *cliconfig.UmountValues) error { + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - force := c.Bool("force") - umountAll := c.Bool("all") - if err := checkAllAndLatest(c); err != nil { - return err - } + force := c.Force + umountAll := c.All - containers, err := getAllOrLatestContainers(c, runtime, -1, "all") + containers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, -1, "all") if err != nil { if len(containers) == 0 { return err diff --git a/cmd/podman/unpause.go b/cmd/podman/unpause.go index 91b5fda33..efd9a20a3 100644 --- a/cmd/podman/unpause.go +++ b/cmd/podman/unpause.go @@ -3,38 +3,45 @@ package main import ( "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - unpauseFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Unpause all paused containers", - }, - } + unpauseCommand cliconfig.UnpauseValues + unpauseDescription = ` podman unpause Unpauses one or more running containers. The container name or ID can be used. ` - unpauseCommand = cli.Command{ - Name: "unpause", - Usage: "Unpause the processes in one or more containers", - Description: unpauseDescription, - Flags: unpauseFlags, - Action: unpauseCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", - OnUsageError: usageErrorHandler, + _unpauseCommand = &cobra.Command{ + Use: "unpause", + Short: "Unpause the processes in one or more containers", + Long: unpauseDescription, + RunE: func(cmd *cobra.Command, args []string) error { + unpauseCommand.InputArgs = args + unpauseCommand.GlobalFlags = MainGlobalOpts + return unpauseCmd(&unpauseCommand) + }, + Example: `podman unpause ctrID + podman unpause --all`, } ) -func unpauseCmd(c *cli.Context) error { +func init() { + unpauseCommand.Command = _unpauseCommand + unpauseCommand.SetUsageTemplate(UsageTemplate()) + flags := unpauseCommand.Flags() + flags.BoolVarP(&unpauseCommand.All, "all", "a", false, "Unpause all paused containers") +} + +func unpauseCmd(c *cliconfig.UnpauseValues) error { var ( unpauseContainers []*libpod.Container unpauseFuncs []shared.ParallelWorkerInput @@ -43,18 +50,18 @@ func unpauseCmd(c *cli.Context) error { return errors.New("unpause is not supported for rootless containers") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - args := c.Args() - if len(args) < 1 && !c.Bool("all") { + args := c.InputArgs + if len(args) < 1 && !c.All { return errors.Errorf("you must provide at least one container name or id") } - if c.Bool("all") { - cs, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStatePaused, "paused") + if c.All { + cs, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStatePaused, "paused") if err != nil { return err } @@ -84,7 +91,7 @@ func unpauseCmd(c *cli.Context) error { maxWorkers := shared.Parallelize("unpause") if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalInt("max-workers") + maxWorkers = c.GlobalFlags.MaxWorks } logrus.Debugf("Setting maximum workers to %d", maxWorkers) diff --git a/cmd/podman/utils.go b/cmd/podman/utils.go index a59535b43..4ec0f8a13 100644 --- a/cmd/podman/utils.go +++ b/cmd/podman/utils.go @@ -3,15 +3,16 @@ package main import ( "context" "fmt" + "github.com/spf13/pflag" "os" gosignal "os/signal" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/term" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" "golang.org/x/crypto/ssh/terminal" "k8s.io/client-go/tools/remotecommand" ) @@ -20,7 +21,7 @@ type RawTtyFormatter struct { } // Start (if required) and attach to a container -func startAttachCtr(ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool) error { +func startAttachCtr(ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool, recursive bool) error { ctx := context.Background() resize := make(chan remotecommand.TerminalSize) @@ -76,7 +77,7 @@ func startAttachCtr(ctr *libpod.Container, stdout, stderr, stdin *os.File, detac return ctr.Attach(streams, detachKeys, resize) } - attachChan, err := ctr.StartAndAttach(getContext(), streams, detachKeys, resize) + attachChan, err := ctr.StartAndAttach(getContext(), streams, detachKeys, resize, recursive) if err != nil { return err } @@ -158,30 +159,20 @@ func (f *RawTtyFormatter) Format(entry *logrus.Entry) ([]byte, error) { return bytes, err } -func checkMutuallyExclusiveFlags(c *cli.Context) error { - if err := checkAllAndLatest(c); err != nil { - return err - } - if err := validateFlags(c, startFlags); err != nil { - return err - } - return nil -} - // For pod commands that have a latest and all flag, getPodsFromContext gets // pods the user specifies. If there's an error before getting pods, the pods slice // will be empty and error will be not nil. If an error occured after, the pod slice // will hold all of the successful pods, and error will hold the last error. // The remaining errors will be logged. On success, pods will hold all pods and // error will be nil. -func getPodsFromContext(c *cli.Context, r *libpod.Runtime) ([]*libpod.Pod, error) { - args := c.Args() +func getPodsFromContext(c *cliconfig.PodmanCommand, r *libpod.Runtime) ([]*libpod.Pod, error) { + args := c.InputArgs var pods []*libpod.Pod var lastError error var err error if c.Bool("all") { - pods, err = r.Pods() + pods, err = r.GetAllPods() if err != nil { return nil, errors.Wrapf(err, "unable to get running pods") } @@ -209,8 +200,8 @@ func getPodsFromContext(c *cli.Context, r *libpod.Runtime) ([]*libpod.Pod, error return pods, lastError } -func getVolumesFromContext(c *cli.Context, r *libpod.Runtime) ([]*libpod.Volume, error) { - args := c.Args() +func getVolumesFromContext(c *cliconfig.PodmanCommand, r *libpod.Runtime) ([]*libpod.Volume, error) { + args := c.InputArgs var ( vols []*libpod.Volume lastError error @@ -254,3 +245,11 @@ func printParallelOutput(m map[string]error, errCount int) error { } return lastError } + +// markFlagHiddenForRemoteClient makes the flag not appear as part of the CLI +// on the remote-client +func markFlagHiddenForRemoteClient(flagName string, flags *pflag.FlagSet) { + if remoteclient { + flags.MarkHidden(flagName) + } +} diff --git a/cmd/podman/varlink.go b/cmd/podman/varlink.go index 38ce77415..d9c6cdb47 100644 --- a/cmd/podman/varlink.go +++ b/cmd/podman/varlink.go @@ -1,59 +1,64 @@ -// +build varlink +// +build varlink,!remoteclient package main import ( "time" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/pkg/varlinkapi" "github.com/containers/libpod/version" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" "github.com/varlink/go/varlink" ) var ( + varlinkCommand cliconfig.VarlinkValues varlinkDescription = ` podman varlink run varlink interface ` - varlinkFlags = []cli.Flag{ - cli.IntFlag{ - Name: "timeout, t", - Usage: "Time until the varlink session expires in milliseconds. Use 0 to disable the timeout.", - Value: 1000, + _varlinkCommand = &cobra.Command{ + Use: "varlink", + Short: "Run varlink interface", + Long: varlinkDescription, + RunE: func(cmd *cobra.Command, args []string) error { + varlinkCommand.InputArgs = args + varlinkCommand.GlobalFlags = MainGlobalOpts + return varlinkCmd(&varlinkCommand) }, - } - varlinkCommand = &cli.Command{ - Name: "varlink", - Usage: "Run varlink interface", - Description: varlinkDescription, - Flags: sortFlags(varlinkFlags), - Action: varlinkCmd, - ArgsUsage: "VARLINK_URI", - OnUsageError: usageErrorHandler, + Example: `podman varlink unix:/run/podman/io.podman + podman varlink --timeout 5000 unix:/run/podman/io.podman`, } ) -func varlinkCmd(c *cli.Context) error { - args := c.Args() +func init() { + varlinkCommand.Command = _varlinkCommand + varlinkCommand.SetUsageTemplate(UsageTemplate()) + flags := varlinkCommand.Flags() + flags.Int64VarP(&varlinkCommand.Timeout, "timeout", "t", 1000, "Time until the varlink session expires in milliseconds. Use 0 to disable the timeout") +} + +func varlinkCmd(c *cliconfig.VarlinkValues) error { + args := c.InputArgs if len(args) < 1 { return errors.Errorf("you must provide a varlink URI") } - timeout := time.Duration(c.Int64("timeout")) * time.Millisecond + timeout := time.Duration(c.Timeout) * time.Millisecond // Create a single runtime for varlink - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - var varlinkInterfaces = []*iopodman.VarlinkInterface{varlinkapi.New(c, runtime)} + var varlinkInterfaces = []*iopodman.VarlinkInterface{varlinkapi.New(&c.PodmanCommand, runtime)} // Register varlink service. The metadata can be retrieved with: // $ varlink info [varlink address URI] service, err := varlink.NewService( @@ -79,7 +84,7 @@ func varlinkCmd(c *cli.Context) error { logrus.Infof("varlink service expired (use --timeout to increase session time beyond %d ms, 0 means never timeout)", c.Int64("timeout")) return nil default: - return errors.Errorf("unable to start varlink service") + return errors.Wrapf(err, "unable to start varlink service") } } diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 101232b0c..618af3481 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -2,15 +2,13 @@ # in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in the upstream libpod repository. interface io.podman - -# Version is the structure returned by GetVersion -type Version ( - version: string, - go_version: string, - git_commit: string, - built: int, - os_arch: string, - remote_api_version: int +type Volume ( + name: string, + labels: [string]string, + mountPoint: string, + driver: string, + options: [string]string, + scope: string ) type NotImplemented ( @@ -20,6 +18,7 @@ type NotImplemented ( type StringResponse ( message: string ) + # ContainerChanges describes the return struct for ListContainerChanges type ContainerChanges ( changed: []string, @@ -27,14 +26,35 @@ type ContainerChanges ( deleted: []string ) -# ImageInList describes the structure that is returned in -# ListImages. -type ImageInList ( +type ImageSaveOptions ( + name: string, + format: string, + output: string, + outputType: string, + moreTags: []string, + quiet: bool, + compress: bool +) + +type VolumeCreateOpts ( + volumeName: string, + driver: string, + labels: [string]string, + options: [string]string +) + +type VolumeRemoveOpts ( + volumes: []string, + all: bool, + force: bool +) + +type Image ( id: string, parentId: string, repoTags: []string, repoDigests: []string, - created: string, + created: string, # as RFC3339 size: int, virtualSize: int, containers: int, @@ -45,30 +65,35 @@ type ImageInList ( # ImageHistory describes the returned structure from ImageHistory. type ImageHistory ( id: string, - created: string, + created: string, # as RFC3339 createdBy: string, tags: []string, size: int, comment: string ) -# ImageSearch is the returned structure for SearchImage. It is returned -# in array form. -type ImageSearch ( +# Represents a single search result from SearchImages +type ImageSearchResult ( description: string, is_official: bool, is_automated: bool, + registry: string, name: string, star_count: int ) -# ListContainerData is the returned struct for an individual container -type ListContainerData ( +type ImageSearchFilter ( + is_official: ?bool, + is_automated: ?bool, + star_count: int +) + +type Container ( id: string, image: string, imageid: string, command: []string, - createdat: string, + createdat: string, # as RFC3339 runningfor: string, status: string, ports: []ContainerPortMappings, @@ -303,37 +328,54 @@ type IDMap ( size: int ) +# BuildOptions are are used to describe describe physical attributes of the build +type BuildOptions ( + addHosts: []string, + cgroupParent: string, + cpuPeriod: int, + cpuQuota: int, + cpuShares: int, + cpusetCpus: string, + cpusetMems: string, + memory: int, + memorySwap: int, + shmSize: string, + ulimit: []string, + volume: []string +) + # BuildInfo is used to describe user input for building images type BuildInfo ( - # paths to one or more dockerfiles - dockerfile: []string, - tags: []string, - add_hosts: []string, - cgroup_parent: string, - cpu_period: int, - cpu_quota: int, - cpu_shares: int, - cpuset_cpus: string, - cpuset_mems: string, - memory: string, - memory_swap: string, - security_opts: []string, - shm_size: string, - ulimit: []string, - volume: []string, - squash: bool, - pull: bool, - pull_always: bool, - force_rm: bool, - rm: bool, - label: []string, + additionalTags: []string, annotations: []string, - build_args: [string]string, - image_format: string + buildArgs: [string]string, + buildOptions: BuildOptions, + cniConfigDir: string, + cniPluginDir: string, + compression: string, + contextDir: string, + defaultsMountFilePath: string, + dockerfiles: []string, + err: string, + forceRmIntermediateCtrs: bool, + iidfile: string, + label: []string, + layers: bool, + nocache: bool, + out: string, + output: string, + outputFormat: string, + pullPolicy: string, + quiet: bool, + remoteIntermediateCtrs: bool, + reportWriter: string, + runtimeArgs: []string, + signaturePolicyPath: string, + squash: bool ) -# BuildResponse is used to describe the responses for building images -type BuildResponse ( +# MoreResponse is a struct for when responses from varlink requires longer output +type MoreResponse ( logs: []string, id: string ) @@ -387,40 +429,35 @@ type Runlabel( name: string, pull: bool, signaturePolicyPath: string, - tlsVerify: bool, + tlsVerify: ?bool, label: string, extraArgs: []string, opts: [string]string ) -# Ping provides a response for developers to ensure their varlink setup is working. -# #### Example -# ~~~ -# $ varlink call -m unix:/run/podman/io.podman/io.podman.Ping -# { -# "ping": { -# "message": "OK" -# } -# } -# ~~~ -method Ping() -> (ping: StringResponse) - -# GetVersion returns a Version structure describing the libpod setup on their -# system. -method GetVersion() -> (version: Version) +# GetVersion returns version and build information of the podman service +method GetVersion() -> ( + version: string, + go_version: string, + git_commit: string, + built: string, # as RFC3339 + os_arch: string, + remote_api_version: int +) # GetInfo returns a [PodmanInfo](#PodmanInfo) struct that describes podman and its host such as storage stats, # build information of Podman, and system-wide registries. method GetInfo() -> (info: PodmanInfo) -# ListContainers returns a list of containers in no particular order. There are -# returned as an array of ListContainerData structs. See also [GetContainer](#GetContainer). -method ListContainers() -> (containers: []ListContainerData) +# ListContainers returns information about all containers. +# See also [GetContainer](#GetContainer). +method ListContainers() -> (containers: []Container) -# GetContainer takes a name or ID of a container and returns single ListContainerData -# structure. A [ContainerNotFound](#ContainerNotFound) error will be returned if the container cannot be found. -# See also [ListContainers](ListContainers) and [InspectContainer](#InspectContainer). -method GetContainer(name: string) -> (container: ListContainerData) +# GetContainer returns information about a single container. If a container +# with the given id doesn't exist, a [ContainerNotFound](#ContainerNotFound) +# error will be returned. See also [ListContainers](ListContainers) and +# [InspectContainer](#InspectContainer). +method GetContainer(id: string) -> (container: Container) # CreateContainer creates a new container from an image. It uses a [Create](#Create) type for input. The minimum # input required for CreateContainer is an image name. If the image name is not found, an [ImageNotFound](#ImageNotFound) @@ -508,7 +545,7 @@ method ExportContainer(name: string, path: string) -> (tarfile: string) method GetContainerStats(name: string) -> (container: ContainerStats) # This method has not be implemented yet. -method ResizeContainerTty() -> (notimplemented: NotImplemented) +# method ResizeContainerTty() -> (notimplemented: NotImplemented) # StartContainer starts a created or stopped container. It takes the name or ID of container. It returns # the container ID once started. If the container cannot be found, a [ContainerNotFound](#ContainerNotFound) @@ -540,10 +577,10 @@ method RestartContainer(name: string, timeout: int) -> (container: string) method KillContainer(name: string, signal: int) -> (container: string) # This method has not be implemented yet. -method UpdateContainer() -> (notimplemented: NotImplemented) +# method UpdateContainer() -> (notimplemented: NotImplemented) # This method has not be implemented yet. -method RenameContainer() -> (notimplemented: NotImplemented) +# method RenameContainer() -> (notimplemented: NotImplemented) # PauseContainer takes the name or ID of container and pauses it. If the container cannot be found, # a [ContainerNotFound](#ContainerNotFound) error will be returned; otherwise the ID of the container is returned. @@ -556,7 +593,7 @@ method PauseContainer(name: string) -> (container: string) method UnpauseContainer(name: string) -> (container: string) # This method has not be implemented yet. -method AttachToContainer() -> (notimplemented: NotImplemented) +# method AttachToContainer() -> (notimplemented: NotImplemented) # GetAttachSockets takes the name or ID of an existing container. It returns file paths for two sockets needed # to properly communicate with a container. The first is the actual I/O socket that the container uses. The @@ -580,8 +617,9 @@ method GetAttachSockets(name: string) -> (sockets: Sockets) # a [ContainerNotFound](#ContainerNotFound) error is returned. method WaitContainer(name: string) -> (exitcode: int) -# RemoveContainer takes requires the name or ID of container as well a boolean representing whether a running -# container can be stopped and removed. Upon successful removal of the container, its ID is returned. If the +# RemoveContainer requires the name or ID of container as well a boolean representing whether a running container can be stopped and removed, and a boolean +# indicating whether to remove builtin volumes. Upon successful removal of the +# container, its ID is returned. If the # container cannot be found by name or ID, a [ContainerNotFound](#ContainerNotFound) error will be returned. # #### Example # ~~~ @@ -590,7 +628,7 @@ method WaitContainer(name: string) -> (exitcode: int) # "container": "62f4fd98cb57f529831e8f90610e54bba74bd6f02920ffb485e15376ed365c20" # } # ~~~ -method RemoveContainer(name: string, force: bool) -> (container: string) +method RemoveContainer(name: string, force: bool, removeVolumes: bool) -> (container: string) # DeleteStoppedContainers will delete all containers that are not running. It will return a list the deleted # container IDs. See also [RemoveContainer](RemoveContainer). @@ -608,21 +646,21 @@ method RemoveContainer(name: string, force: bool) -> (container: string) # ~~~ method DeleteStoppedContainers() -> (containers: []string) -# ListImages returns an array of ImageInList structures which provide basic information about -# an image currently in storage. See also [InspectImage](InspectImage). -method ListImages() -> (images: []ImageInList) +# ListImages returns information about the images that are currently in storage. +# See also [InspectImage](InspectImage). +method ListImages() -> (images: []Image) -# GetImage returns a single image in an [ImageInList](#ImageInList) struct. You must supply an image name as a string. -# If the image cannot be found, an [ImageNotFound](#ImageNotFound) error will be returned. -method GetImage(name: string) -> (image: ImageInList) +# GetImage returns information about a single image in storage. +# If the image caGetImage returns be found, [ImageNotFound](#ImageNotFound) will be returned. +method GetImage(id: string) -> (image: Image) # BuildImage takes a [BuildInfo](#BuildInfo) structure and builds an image. At a minimum, you must provide the -# 'dockerfile' and 'tags' options in the BuildInfo structure. It will return a [BuildResponse](#BuildResponse) structure +# 'dockerfile' and 'tags' options in the BuildInfo structure. It will return a [MoreResponse](#MoreResponse) structure # that contains the build logs and resulting image ID. -method BuildImage(build: BuildInfo) -> (image: BuildResponse) +method BuildImage(build: BuildInfo) -> (image: MoreResponse) # This function is not implemented yet. -method CreateImage() -> (notimplemented: NotImplemented) +# method CreateImage() -> (notimplemented: NotImplemented) # InspectImage takes the name or ID of an image and returns a string respresentation of data associated with the #image. You must serialize the string into JSON to use it further. An [ImageNotFound](#ImageNotFound) error will @@ -637,8 +675,8 @@ method HistoryImage(name: string) -> (history: []ImageHistory) # PushImage takes three input arguments: the name or ID of an image, the fully-qualified destination name of the image, # and a boolean as to whether tls-verify should be used (with false disabling TLS, not affecting the default behavior). # It will return an [ImageNotFound](#ImageNotFound) error if -# the image cannot be found in local storage; otherwise the ID of the image will be returned on success. -method PushImage(name: string, tag: string, tlsverify: bool, signaturePolicy: string, creds: string, certDir: string, compress: bool, format: string, removeSignatures: bool, signBy: string) -> (image: string) +# the image cannot be found in local storage; otherwise it will return a [MoreResponse](#MoreResponse) +method PushImage(name: string, tag: string, tlsverify: ?bool, signaturePolicy: string, creds: string, certDir: string, compress: bool, format: string, removeSignatures: bool, signBy: string) -> (reply: MoreResponse) # TagImage takes the name or ID of an image in local storage as well as the desired tag name. If the image cannot # be found, an [ImageNotFound](#ImageNotFound) error will be returned; otherwise, the ID of the image is returned on success. @@ -656,10 +694,10 @@ method TagImage(name: string, tagged: string) -> (image: string) # ~~~ method RemoveImage(name: string, force: bool) -> (image: string) -# SearchImage takes the string of an image name and a limit of searches from each registries to be returned. SearchImage -# will then use a glob-like match to find the image you are searching for. The images are returned in an array of -# ImageSearch structures which contain information about the image as well as its fully-qualified name. -method SearchImage(name: string, limit: int) -> (images: []ImageSearch) +# SearchImages searches available registries for images that contain the +# contents of "query" in their name. If "limit" is given, limits the amount of +# search results per registry. +method SearchImages(query: string, limit: ?int, tlsVerify: ?bool, filter: ImageSearchFilter) -> (results: []ImageSearchResult) # DeleteUnusedImages deletes any images not associated with a container. The IDs of the deleted images are returned # in a string array. @@ -697,16 +735,10 @@ method ImportImage(source: string, reference: string, message: string, changes: # error will be returned. See also [ImportImage](ImportImage). method ExportImage(name: string, destination: string, compress: bool, tags: []string) -> (image: string) -# PullImage pulls an image from a repository to local storage. After the pull is successful, the ID of the image -# is returned. -# #### Example -# ~~~ -# $ varlink call -m unix:/run/podman/io.podman/io.podman.PullImage '{"name": "registry.fedoraproject.org/fedora"}' -# { -# "id": "426866d6fa419873f97e5cbd320eeb22778244c1dfffa01c944db3114f55772e" -# } -# ~~~ -method PullImage(name: string, certDir: string, creds: string, signaturePolicy: string, tlsVerify: bool) -> (id: string) +# PullImage pulls an image from a repository to local storage. After a successful pull, the image id and logs +# are returned as a [MoreResponse](#MoreResponse). This connection also will handle a WantsMores request to send +# status as it occurs. +method PullImage(name: string, certDir: string, creds: string, signaturePolicy: string, tlsVerify: ?bool) -> (reply: MoreResponse) # CreatePod creates a new empty pod. It uses a [PodCreate](#PodCreate) type for input. # On success, the ID of the newly created pod will be returned. @@ -913,10 +945,10 @@ method UnpausePod(name: string) -> (pod: string) method RemovePod(name: string, force: bool) -> (pod: string) # This method has not be implemented yet. -method WaitPod() -> (notimplemented: NotImplemented) +# method WaitPod() -> (notimplemented: NotImplemented) # This method has not been implemented yet. -method TopPod() -> (notimplemented: NotImplemented) +# method TopPod() -> (notimplemented: NotImplemented) # GetPodStats takes the name or ID of a pod and returns a pod name and slice of ContainerStats structure which # contains attributes like memory and cpu usage. If the pod cannot be found, a [PodNotFound](#PodNotFound) @@ -1020,19 +1052,19 @@ method UnmountContainer(name: string, force: bool) -> () method ImagesPrune(all: bool) -> (pruned: []string) # This function is not implemented yet. -method ListContainerPorts(name: string) -> (notimplemented: NotImplemented) +# method ListContainerPorts(name: string) -> (notimplemented: NotImplemented) # GenerateKube generates a Kubernetes v1 Pod description of a Podman container or pod # and its containers. The description is in YAML. See also [ReplayKube](ReplayKube). -method GenerateKube() -> (notimplemented: NotImplemented) +# method GenerateKube() -> (notimplemented: NotImplemented) # GenerateKubeService generates a Kubernetes v1 Service description of a Podman container or pod # and its containers. The description is in YAML. See also [GenerateKube](GenerateKube). -method GenerateKubeService() -> (notimplemented: NotImplemented) +# method GenerateKubeService() -> (notimplemented: NotImplemented) # ReplayKube recreates a pod and its containers based on a Kubernetes v1 Pod description (in YAML) # like that created by GenerateKube. See also [GenerateKube](GenerateKube). -method ReplayKube() -> (notimplemented: NotImplemented) +# method ReplayKube() -> (notimplemented: NotImplemented) # ContainerConfig returns a container's config in string form. This call is for # development of Podman only and generally should not be used. @@ -1050,20 +1082,53 @@ method ContainerInspectData(name: string) -> (config: string) # development of Podman only and generally should not be used. method ContainerStateData(name: string) -> (config: string) +# PodStateData returns inspectr level information of a given pod in string form. This call is for +# development of Podman only and generally should not be used. +method PodStateData(name: string) -> (config: string) + +# Sendfile allows a remote client to send a file to the host method SendFile(type: string, length: int) -> (file_handle: string) + +# ReceiveFile allows the host to send a remote client a file method ReceiveFile(path: string, delete: bool) -> (len: int) +# VolumeCreate creates a volume on a remote host +method VolumeCreate(options: VolumeCreateOpts) -> (volumeName: string) + +# VolumeRemove removes a volume on a remote host +method VolumeRemove(options: VolumeRemoveOpts) -> (volumeNames: []string) + +# GetVolumes gets slice of the volumes on a remote host +method GetVolumes(args: []string, all: bool) -> (volumes: []Volume) + +# VolumesPrune removes unused volumes on the host +method VolumesPrune() -> (prunedNames: []string, prunedErrors: []string) + +# ImageSave allows you to save an image from the local image storage to a tarball +method ImageSave(options: ImageSaveOptions) -> (reply: MoreResponse) + +# GetPodsByContext allows you to get a list pod ids depending on all, latest, or a list of +# pod names. The definition of latest pod means the latest by creation date. In a multi- +# user environment, results might differ from what you expect. +method GetPodsByContext(all: bool, latest: bool, args: []string) -> (pods: []string) + +# LoadImage allows you to load an image into local storage from a tarball. +method LoadImage(name: string, inputFile: string, quiet: bool, deleteFile: bool) -> (reply: MoreResponse) + # ImageNotFound means the image could not be found by the provided name or ID in local storage. -error ImageNotFound (name: string) +error ImageNotFound (id: string, reason: string) # ContainerNotFound means the container could not be found by the provided name or ID in local storage. -error ContainerNotFound (name: string) +error ContainerNotFound (id: string, reason: string) # NoContainerRunning means none of the containers requested are running in a command that requires a running container. error NoContainerRunning () # PodNotFound means the pod could not be found by the provided name or ID in local storage. -error PodNotFound (name: string) +error PodNotFound (name: string, reason: string) + +# VolumeNotFound means the volume could not be found by the name or ID in local storage. +error VolumeNotFound (id: string, reason: string) # PodContainerError means a container associated with a pod failed to preform an operation. It contains # a container ID of the container that failed. diff --git a/cmd/podman/varlink_dummy.go b/cmd/podman/varlink_dummy.go index ec4bbb208..430511d72 100644 --- a/cmd/podman/varlink_dummy.go +++ b/cmd/podman/varlink_dummy.go @@ -2,8 +2,10 @@ package main -import ( - "github.com/urfave/cli" -) +import "github.com/spf13/cobra" -var varlinkCommand *cli.Command +var ( + _varlinkCommand = &cobra.Command{ + Use: "", + } +) diff --git a/cmd/podman/version.go b/cmd/podman/version.go index ce773ee2e..c65ba94f9 100644 --- a/cmd/podman/version.go +++ b/cmd/podman/version.go @@ -6,20 +6,41 @@ import ( "text/tabwriter" "time" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/libpod" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) +var ( + versionCommand cliconfig.VersionValues + _versionCommand = &cobra.Command{ + Use: "version", + Short: "Display the Podman Version Information", + RunE: func(cmd *cobra.Command, args []string) error { + versionCommand.InputArgs = args + versionCommand.GlobalFlags = MainGlobalOpts + return versionCmd(&versionCommand) + }, + } +) + +func init() { + versionCommand.Command = _versionCommand + versionCommand.SetUsageTemplate(UsageTemplate()) + flags := versionCommand.Flags() + flags.StringVarP(&versionCommand.Format, "format", "f", "", "Change the output format to JSON or a Go template") +} + // versionCmd gets and prints version info for version command -func versionCmd(c *cli.Context) error { +func versionCmd(c *cliconfig.VersionValues) error { output, err := libpod.GetVersion() if err != nil { errors.Wrapf(err, "unable to determine version") } - versionOutputFormat := c.String("format") + versionOutputFormat := c.Format if versionOutputFormat != "" { var out formats.Writer switch versionOutputFormat { @@ -46,19 +67,3 @@ func versionCmd(c *cli.Context) error { fmt.Fprintf(w, "OS/Arch:\t%s\n", output.OsArch) return nil } - -// Cli command to print out the full version of podman -var ( - versionCommand = cli.Command{ - Name: "version", - Usage: "Display the Podman Version Information", - Action: versionCmd, - Flags: versionFlags, - } - versionFlags = []cli.Flag{ - cli.StringFlag{ - Name: "format, f", - Usage: "Change the output format to JSON or a Go template", - }, - } -) diff --git a/cmd/podman/volume.go b/cmd/podman/volume.go index 913592e74..8a8664151 100644 --- a/cmd/podman/volume.go +++ b/cmd/podman/volume.go @@ -1,26 +1,31 @@ package main import ( - "github.com/urfave/cli" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" ) -var ( - volumeDescription = `Manage volumes. +var volumeDescription = `Manage volumes. Volumes are created in and can be shared between containers.` - volumeSubCommands = []cli.Command{ - volumeCreateCommand, - volumeLsCommand, - volumeRmCommand, - volumeInspectCommand, - volumePruneCommand, - } - volumeCommand = cli.Command{ - Name: "volume", - Usage: "Manage volumes", - Description: volumeDescription, - UseShortOptionHandling: true, - Subcommands: volumeSubCommands, - } -) +var volumeCommand = cliconfig.PodmanCommand{ + Command: &cobra.Command{ + Use: "volume", + Short: "Manage volumes", + Long: volumeDescription, + }, +} +var volumeSubcommands = []*cobra.Command{ + _volumeCreateCommand, + _volumeLsCommand, + _volumeRmCommand, + _volumeInspectCommand, + _volumePruneCommand, +} + +func init() { + volumeCommand.SetUsageTemplate(UsageTemplate()) + volumeCommand.AddCommand(volumeSubcommands...) + rootCmd.AddCommand(volumeCommand.Command) +} diff --git a/cmd/podman/volume_create.go b/cmd/podman/volume_create.go index 0b5f8d1e3..833191082 100644 --- a/cmd/podman/volume_create.go +++ b/cmd/podman/volume_create.go @@ -3,95 +3,69 @@ package main import ( "fmt" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) -var volumeCreateDescription = ` +var ( + volumeCreateCommand cliconfig.VolumeCreateValues + volumeCreateDescription = ` podman volume create Creates a new volume. If using the default driver, "local", the volume will be created at.` -var volumeCreateFlags = []cli.Flag{ - cli.StringFlag{ - Name: "driver", - Usage: "Specify volume driver name (default local)", - }, - cli.StringSliceFlag{ - Name: "label, l", - Usage: "Set metadata for a volume (default [])", - }, - cli.StringSliceFlag{ - Name: "opt, o", - Usage: "Set driver specific options (default [])", - }, -} - -var volumeCreateCommand = cli.Command{ - Name: "create", - Usage: "Create a new volume", - Description: volumeCreateDescription, - Flags: volumeCreateFlags, - Action: volumeCreateCmd, - SkipArgReorder: true, - ArgsUsage: "[VOLUME-NAME]", - UseShortOptionHandling: true, -} + _volumeCreateCommand = &cobra.Command{ + Use: "create", + Short: "Create a new volume", + Long: volumeCreateDescription, + RunE: func(cmd *cobra.Command, args []string) error { + volumeCreateCommand.InputArgs = args + volumeCreateCommand.GlobalFlags = MainGlobalOpts + return volumeCreateCmd(&volumeCreateCommand) + }, + Example: `podman volume create myvol + podman volume create + podman volume create --label foo=bar myvol`, + } +) -func volumeCreateCmd(c *cli.Context) error { - var ( - options []libpod.VolumeCreateOption - err error - volName string - ) +func init() { + volumeCreateCommand.Command = _volumeCreateCommand + volumeCreateCommand.SetUsageTemplate(UsageTemplate()) + flags := volumeCreateCommand.Flags() + flags.StringVar(&volumeCreateCommand.Driver, "driver", "", "Specify volume driver name (default local)") + flags.StringSliceVarP(&volumeCreateCommand.Label, "label", "l", []string{}, "Set metadata for a volume (default [])") + flags.StringSliceVarP(&volumeCreateCommand.Opt, "opt", "o", []string{}, "Set driver specific options (default [])") - if err = validateFlags(c, volumeCreateFlags); err != nil { - return err - } +} - runtime, err := libpodruntime.GetRuntime(c) +func volumeCreateCmd(c *cliconfig.VolumeCreateValues) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - if len(c.Args()) > 1 { + if len(c.InputArgs) > 1 { return errors.Errorf("too many arguments, create takes at most 1 argument") } - if len(c.Args()) > 0 { - volName = c.Args()[0] - options = append(options, libpod.WithVolumeName(volName)) - } - - if c.IsSet("driver") { - options = append(options, libpod.WithVolumeDriver(c.String("driver"))) - } - - labels, err := getAllLabels([]string{}, c.StringSlice("label")) + labels, err := getAllLabels([]string{}, c.Label) if err != nil { return errors.Wrapf(err, "unable to process labels") } - if len(labels) != 0 { - options = append(options, libpod.WithVolumeLabels(labels)) - } - opts, err := getAllLabels([]string{}, c.StringSlice("opt")) + opts, err := getAllLabels([]string{}, c.Opt) if err != nil { return errors.Wrapf(err, "unable to process options") } - if len(options) != 0 { - options = append(options, libpod.WithVolumeOptions(opts)) - } - vol, err := runtime.NewVolume(getContext(), options...) - if err != nil { - return err + volumeName, err := runtime.CreateVolume(getContext(), c, labels, opts) + if err == nil { + fmt.Println(volumeName) } - fmt.Printf("%s\n", vol.Name()) - - return nil + return err } diff --git a/cmd/podman/volume_inspect.go b/cmd/podman/volume_inspect.go index 152f1d098..dc6afbc36 100644 --- a/cmd/podman/volume_inspect.go +++ b/cmd/podman/volume_inspect.go @@ -1,63 +1,58 @@ package main import ( - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) -var volumeInspectDescription = ` +var ( + volumeInspectCommand cliconfig.VolumeInspectValues + volumeInspectDescription = ` podman volume inspect Display detailed information on one or more volumes. Can change the format from JSON to a Go template. ` + _volumeInspectCommand = &cobra.Command{ + Use: "inspect", + Short: "Display detailed information on one or more volumes", + Long: volumeInspectDescription, + RunE: func(cmd *cobra.Command, args []string) error { + volumeInspectCommand.InputArgs = args + volumeInspectCommand.GlobalFlags = MainGlobalOpts + return volumeInspectCmd(&volumeInspectCommand) + }, + Example: `podman volume inspect myvol + podman volume inspect --all + podman volume inspect --format "{{.Driver}} {{.Scope}}" myvol`, + } +) -var volumeInspectFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Inspect all volumes", - }, - cli.StringFlag{ - Name: "format, f", - Usage: "Format volume output using Go template", - Value: "json", - }, -} +func init() { + volumeInspectCommand.Command = _volumeInspectCommand + volumeInspectCommand.SetUsageTemplate(UsageTemplate()) + flags := volumeInspectCommand.Flags() + flags.BoolVarP(&volumeInspectCommand.All, "all", "a", false, "Inspect all volumes") + flags.StringVarP(&volumeInspectCommand.Format, "format", "f", "json", "Format volume output using Go template") -var volumeInspectCommand = cli.Command{ - Name: "inspect", - Usage: "Display detailed information on one or more volumes", - Description: volumeInspectDescription, - Flags: volumeInspectFlags, - Action: volumeInspectCmd, - SkipArgReorder: true, - ArgsUsage: "[VOLUME-NAME ...]", - UseShortOptionHandling: true, } -func volumeInspectCmd(c *cli.Context) error { - var err error - - if err = validateFlags(c, volumeInspectFlags); err != nil { - return err +func volumeInspectCmd(c *cliconfig.VolumeInspectValues) error { + if (c.All && len(c.InputArgs) > 0) || (!c.All && len(c.InputArgs) < 1) { + return errors.New("provide one or more volume names or use --all") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - opts := volumeLsOptions{ - Format: c.String("format"), - } - - vols, lastError := getVolumesFromContext(c, runtime) - if lastError != nil { - logrus.Errorf("%q", lastError) + vols, err := runtime.InspectVolumes(getContext(), c) + if err != nil { + return err } - - return generateVolLsOutput(vols, opts, runtime) + return generateVolLsOutput(vols, volumeLsOptions{Format: c.Format}) } diff --git a/cmd/podman/volume_ls.go b/cmd/podman/volume_ls.go index 0f94549ee..5adfc1e91 100644 --- a/cmd/podman/volume_ls.go +++ b/cmd/podman/volume_ls.go @@ -4,11 +4,11 @@ import ( "reflect" "strings" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) // volumeOptions is the "ls" command options @@ -37,70 +37,64 @@ type volumeLsJSONParams struct { Scope string `json:"scope"` } -var volumeLsDescription = ` +var ( + volumeLsCommand cliconfig.VolumeLsValues + + volumeLsDescription = ` podman volume ls List all available volumes. The output of the volumes can be filtered and the output format can be changed to JSON or a user specified Go template. ` + _volumeLsCommand = &cobra.Command{ + Use: "ls", + Aliases: []string{"list"}, + Short: "List volumes", + Long: volumeLsDescription, + RunE: func(cmd *cobra.Command, args []string) error { + volumeLsCommand.InputArgs = args + volumeLsCommand.GlobalFlags = MainGlobalOpts + return volumeLsCmd(&volumeLsCommand) + }, + } +) -var volumeLsFlags = []cli.Flag{ - cli.StringFlag{ - Name: "filter, f", - Usage: "Filter volume output", - }, - cli.StringFlag{ - Name: "format", - Usage: "Format volume output using Go template", - Value: "table {{.Driver}}\t{{.Name}}", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Print volume output in quiet mode", - }, -} +func init() { + volumeLsCommand.Command = _volumeLsCommand + volumeLsCommand.SetUsageTemplate(UsageTemplate()) + flags := volumeLsCommand.Flags() -var volumeLsCommand = cli.Command{ - Name: "ls", - Aliases: []string{"list"}, - Usage: "List volumes", - Description: volumeLsDescription, - Flags: volumeLsFlags, - Action: volumeLsCmd, - SkipArgReorder: true, - UseShortOptionHandling: true, + flags.StringVarP(&volumeLsCommand.Filter, "filter", "f", "", "Filter volume output") + flags.StringVar(&volumeLsCommand.Format, "format", "table {{.Driver}}\t{{.Name}}", "Format volume output using Go template") + flags.BoolVarP(&volumeLsCommand.Quiet, "quiet", "q", false, "Print volume output in quiet mode") } -func volumeLsCmd(c *cli.Context) error { - if err := validateFlags(c, volumeLsFlags); err != nil { - return err - } - - runtime, err := libpodruntime.GetRuntime(c) +func volumeLsCmd(c *cliconfig.VolumeLsValues) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - if len(c.Args()) > 0 { + if len(c.InputArgs) > 0 { return errors.Errorf("too many arguments, ls takes no arguments") } opts := volumeLsOptions{ - Quiet: c.Bool("quiet"), + Quiet: c.Quiet, } opts.Format = genVolLsFormat(c) // Get the filter functions based on any filters set - var filterFuncs []libpod.VolumeFilter - if c.String("filter") != "" { - filters := strings.Split(c.String("filter"), ",") + var filterFuncs []adapter.VolumeFilter + if c.Filter != "" { + filters := strings.Split(c.Filter, ",") for _, f := range filters { filterSplit := strings.Split(f, "=") if len(filterSplit) < 2 { return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) } - generatedFunc, err := generateVolumeFilterFuncs(filterSplit[0], filterSplit[1], runtime) + generatedFunc, err := generateVolumeFilterFuncs(filterSplit[0], filterSplit[1]) if err != nil { return errors.Wrapf(err, "invalid filter") } @@ -108,13 +102,12 @@ func volumeLsCmd(c *cli.Context) error { } } - volumes, err := runtime.GetAllVolumes() + volumes, err := runtime.Volumes(getContext()) if err != nil { return err } - // Get the volumes that match the filter - volsFiltered := make([]*libpod.Volume, 0, len(volumes)) + volsFiltered := make([]*adapter.Volume, 0, len(volumes)) for _, vol := range volumes { include := true for _, filter := range filterFuncs { @@ -125,18 +118,18 @@ func volumeLsCmd(c *cli.Context) error { volsFiltered = append(volsFiltered, vol) } } - return generateVolLsOutput(volsFiltered, opts, runtime) + return generateVolLsOutput(volsFiltered, opts) } // generate the template based on conditions given -func genVolLsFormat(c *cli.Context) string { +func genVolLsFormat(c *cliconfig.VolumeLsValues) string { var format string - if c.String("format") != "" { + if c.Format != "" { // "\t" from the command line is not being recognized as a tab // replacing the string "\t" to a tab character if the user passes in "\t" - format = strings.Replace(c.String("format"), `\t`, "\t", -1) + format = strings.Replace(c.Format, `\t`, "\t", -1) } - if c.Bool("quiet") { + if c.Quiet { format = "{{.Name}}" } return format @@ -211,7 +204,7 @@ func getVolTemplateOutput(lsParams []volumeLsJSONParams, opts volumeLsOptions) ( } // getVolJSONParams returns the volumes in JSON format -func getVolJSONParams(volumes []*libpod.Volume, opts volumeLsOptions, runtime *libpod.Runtime) ([]volumeLsJSONParams, error) { +func getVolJSONParams(volumes []*adapter.Volume) []volumeLsJSONParams { var lsOutput []volumeLsJSONParams for _, volume := range volumes { @@ -226,25 +219,19 @@ func getVolJSONParams(volumes []*libpod.Volume, opts volumeLsOptions, runtime *l lsOutput = append(lsOutput, params) } - return lsOutput, nil + return lsOutput } // generateVolLsOutput generates the output based on the format, JSON or Go Template, and prints it out -func generateVolLsOutput(volumes []*libpod.Volume, opts volumeLsOptions, runtime *libpod.Runtime) error { +func generateVolLsOutput(volumes []*adapter.Volume, opts volumeLsOptions) error { if len(volumes) == 0 && opts.Format != formats.JSONString { return nil } - lsOutput, err := getVolJSONParams(volumes, opts, runtime) - if err != nil { - return err - } + lsOutput := getVolJSONParams(volumes) var out formats.Writer switch opts.Format { case formats.JSONString: - if err != nil { - return errors.Wrapf(err, "unable to create JSON for volume output") - } out = formats.JSONStructArray{Output: volLsToGeneric([]volumeLsTemplateParams{}, lsOutput)} default: lsOutput, err := getVolTemplateOutput(lsOutput, opts) @@ -257,18 +244,18 @@ func generateVolLsOutput(volumes []*libpod.Volume, opts volumeLsOptions, runtime } // generateVolumeFilterFuncs returns the true if the volume matches the filter set, otherwise it returns false. -func generateVolumeFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) (func(volume *libpod.Volume) bool, error) { +func generateVolumeFilterFuncs(filter, filterValue string) (func(volume *adapter.Volume) bool, error) { switch filter { case "name": - return func(v *libpod.Volume) bool { + return func(v *adapter.Volume) bool { return strings.Contains(v.Name(), filterValue) }, nil case "driver": - return func(v *libpod.Volume) bool { + return func(v *adapter.Volume) bool { return v.Driver() == filterValue }, nil case "scope": - return func(v *libpod.Volume) bool { + return func(v *adapter.Volume) bool { return v.Scope() == filterValue }, nil case "label": @@ -279,7 +266,7 @@ func generateVolumeFilterFuncs(filter, filterValue string, runtime *libpod.Runti } else { filterValue = "" } - return func(v *libpod.Volume) bool { + return func(v *adapter.Volume) bool { for labelKey, labelValue := range v.Labels() { if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) { return true @@ -295,7 +282,7 @@ func generateVolumeFilterFuncs(filter, filterValue string, runtime *libpod.Runti } else { filterValue = "" } - return func(v *libpod.Volume) bool { + return func(v *adapter.Volume) bool { for labelKey, labelValue := range v.Options() { if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) { return true diff --git a/cmd/podman/volume_prune.go b/cmd/podman/volume_prune.go index 41d95f9c7..1f7931aa4 100644 --- a/cmd/podman/volume_prune.go +++ b/cmd/podman/volume_prune.go @@ -7,73 +7,69 @@ import ( "os" "strings" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/adapter" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) -var volumePruneDescription = ` +var ( + volumePruneCommand cliconfig.VolumePruneValues + volumePruneDescription = ` podman volume prune Remove all unused volumes. Will prompt for confirmation if not using force. ` + _volumePruneCommand = &cobra.Command{ + Use: "prune", + Short: "Remove all unused volumes", + Long: volumePruneDescription, + RunE: func(cmd *cobra.Command, args []string) error { + volumePruneCommand.InputArgs = args + volumePruneCommand.GlobalFlags = MainGlobalOpts + return volumePruneCmd(&volumePruneCommand) + }, + } +) -var volumePruneFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "force, f", - Usage: "Do not prompt for confirmation", - }, -} +func init() { + volumePruneCommand.Command = _volumePruneCommand + volumePruneCommand.SetUsageTemplate(UsageTemplate()) + flags := volumePruneCommand.Flags() -var volumePruneCommand = cli.Command{ - Name: "prune", - Usage: "Remove all unused volumes", - Description: volumePruneDescription, - Flags: volumePruneFlags, - Action: volumePruneCmd, - SkipArgReorder: true, - UseShortOptionHandling: true, + flags.BoolVarP(&volumePruneCommand.Force, "force", "f", false, "Do not prompt for confirmation") } func volumePrune(runtime *adapter.LocalRuntime, ctx context.Context) error { - var lastError error - - volumes, err := runtime.GetAllVolumes() - if err != nil { - return err + prunedNames, prunedErrors := runtime.PruneVolumes(ctx) + for _, name := range prunedNames { + fmt.Println(name) + } + if len(prunedErrors) == 0 { + return nil } + // Grab the last error + lastError := prunedErrors[len(prunedErrors)-1] + // Remove the last error from the error slice + prunedErrors = prunedErrors[:len(prunedErrors)-1] - for _, vol := range volumes { - err = runtime.RemoveVolume(ctx, vol, false, true) - if err == nil { - fmt.Println(vol.Name()) - } else if err != libpod.ErrVolumeBeingUsed { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "failed to remove volume %q", vol.Name()) - } + for _, err := range prunedErrors { + logrus.Errorf("%q", err) } return lastError } -func volumePruneCmd(c *cli.Context) error { - - if err := validateFlags(c, volumePruneFlags); err != nil { - return err - } - - runtime, err := adapter.GetRuntime(c) +func volumePruneCmd(c *cliconfig.VolumePruneValues) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) // Prompt for confirmation if --force is not set - if !c.Bool("force") { + if !c.Force { reader := bufio.NewReader(os.Stdin) fmt.Println("WARNING! This will remove all volumes not used by at least one container.") fmt.Print("Are you sure you want to continue? [y/N] ") @@ -85,6 +81,5 @@ func volumePruneCmd(c *cli.Context) error { return nil } } - return volumePrune(runtime, getContext()) } diff --git a/cmd/podman/volume_rm.go b/cmd/podman/volume_rm.go index 3fb623624..03b6ccae1 100644 --- a/cmd/podman/volume_rm.go +++ b/cmd/podman/volume_rm.go @@ -3,69 +3,70 @@ package main import ( "fmt" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) -var volumeRmDescription = ` +var ( + volumeRmCommand cliconfig.VolumeRmValues + volumeRmDescription = ` podman volume rm Remove one or more existing volumes. Will only remove volumes that are not being used by any containers. To remove the volumes anyways, use the --force flag. ` + _volumeRmCommand = &cobra.Command{ + Use: "rm", + Aliases: []string{"remove"}, + Short: "Remove one or more volumes", + Long: volumeRmDescription, + RunE: func(cmd *cobra.Command, args []string) error { + volumeRmCommand.InputArgs = args + volumeRmCommand.GlobalFlags = MainGlobalOpts + return volumeRmCmd(&volumeRmCommand) + }, + Example: `podman volume rm myvol1 myvol2 + podman volume rm --all + podman volume rm --force myvol`, + } +) -var volumeRmFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Remove all volumes", - }, - cli.BoolFlag{ - Name: "force, f", - Usage: "Remove a volume by force, even if it is being used by a container", - }, -} - -var volumeRmCommand = cli.Command{ - Name: "rm", - Aliases: []string{"remove"}, - Usage: "Remove one or more volumes", - Description: volumeRmDescription, - Flags: volumeRmFlags, - Action: volumeRmCmd, - ArgsUsage: "[VOLUME-NAME ...]", - SkipArgReorder: true, - UseShortOptionHandling: true, +func init() { + volumeRmCommand.Command = _volumeRmCommand + volumeRmCommand.SetUsageTemplate(UsageTemplate()) + flags := volumeRmCommand.Flags() + flags.BoolVarP(&volumeRmCommand.All, "all", "a", false, "Remove all volumes") + flags.BoolVarP(&volumeRmCommand.Force, "force", "f", false, "Remove a volume by force, even if it is being used by a container") } -func volumeRmCmd(c *cli.Context) error { +func volumeRmCmd(c *cliconfig.VolumeRmValues) error { var err error - if err = validateFlags(c, volumeRmFlags); err != nil { - return err + if (len(c.InputArgs) > 0 && c.All) || (len(c.InputArgs) < 1 && !c.All) { + return errors.New("choose either one or more volumes or all") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - - ctx := getContext() - - vols, lastError := getVolumesFromContext(c, runtime) - for _, vol := range vols { - err = runtime.RemoveVolume(ctx, vol, c.Bool("force"), false) - if err != nil { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "failed to remove volume %q", vol.Name()) - } else { - fmt.Println(vol.Name()) + deletedVolumeNames, err := runtime.RemoveVolumes(getContext(), c) + if err != nil { + if len(deletedVolumeNames) > 0 { + printDeleteVolumes(deletedVolumeNames) + return err } } - return lastError + printDeleteVolumes(deletedVolumeNames) + return err +} + +func printDeleteVolumes(volumes []string) { + for _, v := range volumes { + fmt.Println(v) + } } diff --git a/cmd/podman/wait.go b/cmd/podman/wait.go index 35ad7a662..9df7cdbae 100644 --- a/cmd/podman/wait.go +++ b/cmd/podman/wait.go @@ -5,43 +5,51 @@ import ( "os" "time" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + waitCommand cliconfig.WaitValues + waitDescription = ` podman wait Block until one or more containers stop and then print their exit codes ` - waitFlags = []cli.Flag{ - cli.UintFlag{ - Name: "interval, i", - Usage: "Milliseconds to wait before polling for completion", - Value: 250, + _waitCommand = &cobra.Command{ + Use: "wait", + Short: "Block on one or more containers", + Long: waitDescription, + RunE: func(cmd *cobra.Command, args []string) error { + waitCommand.InputArgs = args + waitCommand.GlobalFlags = MainGlobalOpts + return waitCmd(&waitCommand) }, - LatestFlag, - } - waitCommand = cli.Command{ - Name: "wait", - Usage: "Block on one or more containers", - Description: waitDescription, - Flags: sortFlags(waitFlags), - Action: waitCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", - OnUsageError: usageErrorHandler, + Example: `podman wait --latest + podman wait --interval 5000 ctrID + podman wait ctrID1 ctrID2`, } ) -func waitCmd(c *cli.Context) error { - args := c.Args() - if len(args) < 1 && !c.Bool("latest") { +func init() { + waitCommand.Command = _waitCommand + waitCommand.SetUsageTemplate(UsageTemplate()) + flags := waitCommand.Flags() + flags.UintVarP(&waitCommand.Interval, "interval", "i", 250, "Milliseconds to wait before polling for completion") + flags.BoolVarP(&waitCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + markFlagHiddenForRemoteClient("latest", flags) +} + +func waitCmd(c *cliconfig.WaitValues) error { + args := c.InputArgs + if len(args) < 1 && !c.Latest { return errors.Errorf("you must provide at least one container name or id") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } @@ -52,7 +60,7 @@ func waitCmd(c *cli.Context) error { } var lastError error - if c.Bool("latest") { + if c.Latest { latestCtr, err := runtime.GetLatestContainer() if err != nil { return errors.Wrapf(err, "unable to wait on latest container") @@ -65,10 +73,10 @@ func waitCmd(c *cli.Context) error { if err != nil { return errors.Wrapf(err, "unable to find container %s", container) } - if c.Uint("interval") == 0 { + if c.Interval == 0 { return errors.Errorf("interval must be greater then 0") } - returnCode, err := ctr.WaitWithInterval(time.Duration(c.Uint("interval")) * time.Millisecond) + returnCode, err := ctr.WaitWithInterval(time.Duration(c.Interval) * time.Millisecond) if err != nil { if lastError != nil { fmt.Fprintln(os.Stderr, lastError) |