diff options
Diffstat (limited to 'cmd/podman')
40 files changed, 916 insertions, 149 deletions
diff --git a/cmd/podman/README.md b/cmd/podman/README.md index c1b8f48a7..da92d0216 100644 --- a/cmd/podman/README.md +++ b/cmd/podman/README.md @@ -1,23 +1,16 @@ -# Adding a podman V2 commands +# Podman CLI -## Build podman V2 +The following is an example of how to add a new primary command (`manifest`) and a sub-command (`inspect`) to the Podman CLI. +This is example code, the production code has additional error checking and the business logic provided. -```shell script -$ cd $GOPATH/src/github.com/containers/libpod/cmd/podmanV2 -``` -If you wish to include the libpod library in your program, -```shell script -$ go build -tags 'ABISupport' . -``` -The `--remote` flag may be used to connect to the Podman service using the API. -Otherwise, direct calls will be made to the Libpod library. -```shell script -$ go build -tags '!ABISupport' . -``` -The Libpod library is not linked into the executable. -All calls are made via the API and `--remote=False` is an error condition. +See items below for details on building, installing, contributing to Podman: + - [Readme](README.md) + - [Contributing](CONTRIBUTING.md) + - [Podman Usage](transfer.md) + - [Trouble Shooting](troubleshooting.md) + - [Code Of Conduct](CODE-OF-CONDUCT.md) -## Adding a new command `podman manifests` +## Adding a new command `podman manifest` ```shell script $ mkdir -p $GOPATH/src/github.com/containers/libpod/cmd/podmanV2/manifests ``` @@ -27,6 +20,7 @@ package manifests import ( "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -36,11 +30,11 @@ var ( manifestCmd = &cobra.Command{ Use: "manifest", Short: "Manage manifests", + Args: cobra.ExactArgs(1), Long: "Manage manifests", - Example: "podman manifests IMAGE", + Example: "podman manifest IMAGE", TraverseChildren: true, - PersistentPreRunE: preRunE, - RunE: registry.SubCommandExists, // Report error if there is no sub command given + RunE: validate.SubCommandExists, // Report error if there is no sub command given } ) func init() { @@ -51,15 +45,6 @@ func init() { // The definition for this command Command: manifestCmd, }) - // Setup cobra templates, sub commands will inherit - manifestCmd.SetHelpTemplate(registry.HelpTemplate()) - manifestCmd.SetUsageTemplate(registry.UsageTemplate()) -} - -// preRunE populates the image engine for sub commands -func preRunE(cmd *cobra.Command, args []string) error { - _, err := registry.NewImageEngine(cmd, args) - return err } ``` To "wire" in the `manifest` command, edit the file ```$GOPATH/src/github.com/containers/libpod/cmd/podmanV2/main.go``` to add: @@ -87,7 +72,11 @@ var ( Short: "Display manifest from image", Long: "Displays the low-level information on a manifest identified by image name or ID", RunE: inspect, - Example: "podman manifest DEADBEEF", + Annotations: map[string]string{ + // Add this annotation if this command cannot be run rootless + // registry.ParentNSRequired: "", + }, + Example: "podman manifest inspect DEADBEEF", } ) @@ -98,6 +87,7 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, // The definition for this command Command: inspectCmd, + // The parent command to proceed this command on the CLI Parent: manifestCmd, }) @@ -106,8 +96,22 @@ func init() { // Business logic: cmd is inspectCmd, args is the positional arguments from os.Args func inspect(cmd *cobra.Command, args []string) error { - // Business logic using registry.ImageEngine + // Business logic using registry.ImageEngine() // Do not pull from libpod directly use the domain objects and types return nil } ``` + +## Helper functions + +The complete set can be found in the `validate` package, here are some examples: + + - `cobra.Command{ Args: validate.NoArgs }` used when the command does not accept errors + - `cobra.Command{ Args: validate.IdOrLatestArgs }` used to ensure either a list of ids given or the --latest flag + - `cobra.Command{ RunE: validate.SubCommandExists }` used to validate a subcommand given to a command + - `validate.ChoiceValue` used to create a `pflag.Value` that validate user input against a provided slice of values. For example: + ```go + flags := cobraCommand.Flags() + created := validate.ChoiceValue(&opts.Sort, "command", "created", "id", "image", "names", "runningfor", "size", "status") + flags.Var(created, "sort", "Sort output by: "+created.Choices()) + ``` diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index 0f9476754..53f4a8fa2 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -3,7 +3,7 @@ package common import ( "fmt" - buildahcli "github.com/containers/buildah/pkg/cli" + "github.com/containers/common/pkg/auth" "github.com/containers/libpod/cmd/podman/registry" "github.com/spf13/pflag" ) @@ -26,7 +26,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { ) createFlags.StringVar( &cf.Authfile, - "authfile", buildahcli.GetDefaultAuthFile(), + "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override", ) createFlags.StringVar( diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 7250f88bb..7467c184a 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -192,7 +192,6 @@ func getMemoryLimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []strin func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) error { var ( err error - // namespaces map[string]string ) // validate flags as needed @@ -234,9 +233,15 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string // We are not handling the Expose flag yet. // s.PortsExpose = c.Expose s.PortMappings = c.Net.PublishPorts - s.PublishImagePorts = c.PublishAll + s.PublishExposedPorts = c.PublishAll s.Pod = c.Pod + expose, err := createExpose(c.Expose) + if err != nil { + return err + } + s.Expose = expose + for k, v := range map[string]*specgen.Namespace{ c.IPC: &s.IpcNS, c.PID: &s.PidNS, @@ -403,6 +408,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string } s.ShmSize = &shmSize } + s.CNINetworks = c.Net.CNINetworks s.HostAdd = c.Net.AddHosts s.UseImageResolvConf = c.Net.UseImageResolvConf s.DNSServers = c.Net.DNSServers diff --git a/cmd/podman/common/util.go b/cmd/podman/common/util.go index 47bbe12fa..a3626b4e4 100644 --- a/cmd/podman/common/util.go +++ b/cmd/podman/common/util.go @@ -1,43 +1,201 @@ package common import ( + "net" "strconv" + "strings" - "github.com/cri-o/ocicni/pkg/ocicni" - "github.com/docker/go-connections/nat" + "github.com/containers/libpod/pkg/specgen" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) -// createPortBindings iterates ports mappings and exposed ports into a format CNI understands -func createPortBindings(ports []string) ([]ocicni.PortMapping, error) { - // TODO wants someone to rewrite this code in the future - var portBindings []ocicni.PortMapping - // The conversion from []string to natBindings is temporary while mheon reworks the port - // deduplication code. Eventually that step will not be required. - _, natBindings, err := nat.ParsePortSpecs(ports) - if err != nil { - return nil, err - } - for containerPb, hostPb := range natBindings { - var pm ocicni.PortMapping - pm.ContainerPort = int32(containerPb.Int()) - for _, i := range hostPb { - var hostPort int - var err error - pm.HostIP = i.HostIP - if i.HostPort == "" { - hostPort = containerPb.Int() +// createExpose parses user-provided exposed port definitions and converts them +// into SpecGen format. +// TODO: The SpecGen format should really handle ranges more sanely - we could +// be massively inflating what is sent over the wire with a large range. +func createExpose(expose []string) (map[uint16]string, error) { + toReturn := make(map[uint16]string) + + for _, e := range expose { + // Check for protocol + proto := "tcp" + splitProto := strings.Split(e, "/") + if len(splitProto) > 2 { + return nil, errors.Errorf("invalid expose format - protocol can only be specified once") + } else if len(splitProto) == 2 { + proto = splitProto[1] + } + + // Check for a range + start, len, err := parseAndValidateRange(splitProto[0]) + if err != nil { + return nil, err + } + + var index uint16 + for index = 0; index < len; index++ { + portNum := start + index + protocols, ok := toReturn[portNum] + if !ok { + toReturn[portNum] = proto } else { - hostPort, err = strconv.Atoi(i.HostPort) - if err != nil { - return nil, errors.Wrapf(err, "unable to convert host port to integer") - } + newProto := strings.Join(append(strings.Split(protocols, ","), strings.Split(proto, ",")...), ",") + toReturn[portNum] = newProto } + } + } + + return toReturn, nil +} + +// createPortBindings iterates ports mappings into SpecGen format. +func createPortBindings(ports []string) ([]specgen.PortMapping, error) { + // --publish is formatted as follows: + // [[hostip:]hostport[-endPort]:]containerport[-endPort][/protocol] + toReturn := make([]specgen.PortMapping, 0, len(ports)) + + for _, p := range ports { + var ( + ctrPort string + proto, hostIP, hostPort *string + ) + + splitProto := strings.Split(p, "/") + switch len(splitProto) { + case 1: + // No protocol was provided + case 2: + proto = &(splitProto[1]) + default: + return nil, errors.Errorf("invalid port format - protocol can only be specified once") + } - pm.HostPort = int32(hostPort) - pm.Protocol = containerPb.Proto() - portBindings = append(portBindings, pm) + splitPort := strings.Split(splitProto[0], ":") + switch len(splitPort) { + case 1: + ctrPort = splitPort[0] + case 2: + hostPort = &(splitPort[0]) + ctrPort = splitPort[1] + case 3: + hostIP = &(splitPort[0]) + hostPort = &(splitPort[1]) + ctrPort = splitPort[2] + default: + return nil, errors.Errorf("invalid port format - format is [[hostIP:]hostPort:]containerPort") + } + + newPort, err := parseSplitPort(hostIP, hostPort, ctrPort, proto) + if err != nil { + return nil, err + } + + toReturn = append(toReturn, newPort) + } + + return toReturn, nil +} + +// parseSplitPort parses individual components of the --publish flag to produce +// a single port mapping in SpecGen format. +func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string) (specgen.PortMapping, error) { + newPort := specgen.PortMapping{} + if ctrPort == "" { + return newPort, errors.Errorf("must provide a non-empty container port to publish") + } + ctrStart, ctrLen, err := parseAndValidateRange(ctrPort) + if err != nil { + return newPort, errors.Wrapf(err, "error parsing container port") + } + newPort.ContainerPort = ctrStart + newPort.Range = ctrLen + + if protocol != nil { + if *protocol == "" { + return newPort, errors.Errorf("must provide a non-empty protocol to publish") + } + newPort.Protocol = *protocol + } + if hostIP != nil { + if *hostIP == "" { + return newPort, errors.Errorf("must provide a non-empty container host IP to publish") } + testIP := net.ParseIP(*hostIP) + if testIP == nil { + return newPort, errors.Errorf("cannot parse %q as an IP address", *hostIP) + } + newPort.HostIP = testIP.String() + } + if hostPort != nil { + if *hostPort == "" { + return newPort, errors.Errorf("must provide a non-empty container host port to publish") + } + hostStart, hostLen, err := parseAndValidateRange(*hostPort) + if err != nil { + return newPort, errors.Wrapf(err, "error parsing host port") + } + if hostLen != ctrLen { + return newPort, errors.Errorf("host and container port ranges have different lengths: %d vs %d", hostLen, ctrLen) + } + newPort.HostPort = hostStart + } + + hport := newPort.HostPort + if hport == 0 { + hport = newPort.ContainerPort + } + logrus.Debugf("Adding port mapping from %d to %d length %d protocol %q", hport, newPort.ContainerPort, newPort.Range, newPort.Protocol) + + return newPort, nil +} + +// Parse and validate a port range. +// Returns start port, length of range, error. +func parseAndValidateRange(portRange string) (uint16, uint16, error) { + splitRange := strings.Split(portRange, "-") + if len(splitRange) > 2 { + return 0, 0, errors.Errorf("invalid port format - port ranges are formatted as startPort-stopPort") + } + + if splitRange[0] == "" { + return 0, 0, errors.Errorf("port numbers cannot be negative") + } + + startPort, err := parseAndValidatePort(splitRange[0]) + if err != nil { + return 0, 0, err + } + + var rangeLen uint16 = 1 + if len(splitRange) == 2 { + if splitRange[1] == "" { + return 0, 0, errors.Errorf("must provide ending number for port range") + } + endPort, err := parseAndValidatePort(splitRange[1]) + if err != nil { + return 0, 0, err + } + if endPort <= startPort { + return 0, 0, errors.Errorf("the end port of a range must be higher than the start port - %d is not higher than %d", endPort, startPort) + } + // Our range is the total number of ports + // involved, so we need to add 1 (8080:8081 is + // 2 ports, for example, not 1) + rangeLen = endPort - startPort + 1 + } + + return startPort, rangeLen, nil +} + +// Turn a single string into a valid U16 port. +func parseAndValidatePort(port string) (uint16, error) { + num, err := strconv.Atoi(port) + if err != nil { + return 0, errors.Wrapf(err, "cannot parse %q as a port number", port) + } + if num < 1 || num > 65535 { + return 0, errors.Errorf("port numbers must be between 1 and 65535 (inclusive), got %d", num) } - return portBindings, nil + return uint16(num), nil } diff --git a/cmd/podman/containers/logs.go b/cmd/podman/containers/logs.go index 5dec71fdd..2b8c3ed5f 100644 --- a/cmd/podman/containers/logs.go +++ b/cmd/podman/containers/logs.go @@ -27,7 +27,7 @@ var ( ` logsCommand = &cobra.Command{ Use: "logs [flags] CONTAINER [CONTAINER...]", - Short: "Fetch the logs of one or more container", + Short: "Fetch the logs of one or more containers", Long: logsDescription, RunE: logs, Example: `podman logs ctrID diff --git a/cmd/podman/containers/prune.go b/cmd/podman/containers/prune.go index df627259c..38168a6e4 100644 --- a/cmd/podman/containers/prune.go +++ b/cmd/podman/containers/prune.go @@ -18,10 +18,10 @@ import ( var ( pruneDescription = fmt.Sprintf(`podman container prune - Removes all stopped | exited containers`) + Removes all non running containers`) pruneCommand = &cobra.Command{ Use: "prune [flags]", - Short: "Remove all stopped | exited containers", + Short: "Remove all non running containers", Long: pruneDescription, RunE: prune, Example: `podman container prune`, @@ -43,7 +43,6 @@ func init() { func prune(cmd *cobra.Command, args []string) error { var ( - errs utils.OutputErrors pruneOptions = entities.ContainerPruneOptions{} ) if len(args) > 0 { @@ -51,7 +50,7 @@ func prune(cmd *cobra.Command, args []string) error { } if !force { reader := bufio.NewReader(os.Stdin) - fmt.Println("WARNING! This will remove all stopped containers.") + fmt.Println("WARNING! This will remove all non running containers.") fmt.Print("Are you sure you want to continue? [y/N] ") answer, err := reader.ReadString('\n') if err != nil { @@ -76,11 +75,5 @@ func prune(cmd *cobra.Command, args []string) error { if err != nil { return err } - for k := range responses.ID { - fmt.Println(k) - } - for _, v := range responses.Err { - errs = append(errs, v) - } - return errs.PrintErrors() + return utils.PrintContainerPruneResults(responses) } diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go index fd0b68195..4d12d2534 100644 --- a/cmd/podman/containers/ps.go +++ b/cmd/podman/containers/ps.go @@ -67,8 +67,8 @@ func listFlagSet(flags *pflag.FlagSet) { flags.BoolVar(&listOpts.Sync, "sync", false, "Sync container state with OCI runtime") flags.UintVarP(&listOpts.Watch, "watch", "w", 0, "Watch the ps output on an interval in seconds") - created := validate.ChoiceValue(&listOpts.Sort, "command", "created", "id", "image", "names", "runningfor", "size", "status") - flags.Var(created, "sort", "Sort output by: "+created.Choices()) + sort := validate.ChoiceValue(&listOpts.Sort, "command", "created", "id", "image", "names", "runningfor", "size", "status") + flags.Var(sort, "sort", "Sort output by: "+sort.Choices()) if registry.IsRemote() { _ = flags.MarkHidden("latest") diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go index 96549cead..2a0f9cc6a 100644 --- a/cmd/podman/containers/rm.go +++ b/cmd/podman/containers/rm.go @@ -35,7 +35,7 @@ var ( containerRmCommand = &cobra.Command{ Use: rmCommand.Use, - Short: rmCommand.Use, + Short: rmCommand.Short, Long: rmCommand.Long, RunE: rmCommand.RunE, Args: func(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index b13983e37..f72446cb6 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -170,9 +170,9 @@ func run(cmd *cobra.Command, args []string) error { return nil } if runRmi { - _, err := registry.ImageEngine().Remove(registry.GetContext(), []string{args[0]}, entities.ImageRemoveOptions{}) - if err != nil { - logrus.Errorf("%s", errors.Wrapf(err, "failed removing image")) + _, rmErrors := registry.ImageEngine().Remove(registry.GetContext(), []string{args[0]}, entities.ImageRemoveOptions{}) + if len(rmErrors) > 0 { + logrus.Errorf("%s", errors.Wrapf(errorhandling.JoinErrors(rmErrors), "failed removing image")) } } return nil diff --git a/cmd/podman/healthcheck/healthcheck.go b/cmd/podman/healthcheck/healthcheck.go index ce90dba31..f48701624 100644 --- a/cmd/podman/healthcheck/healthcheck.go +++ b/cmd/podman/healthcheck/healthcheck.go @@ -11,8 +11,8 @@ var ( // Command: healthcheck healthCmd = &cobra.Command{ Use: "healthcheck", - Short: "Manage Healthcheck", - Long: "Manage Healthcheck", + Short: "Manage health checks on containers", + Long: "Run health checks on containers", TraverseChildren: true, RunE: validate.SubCommandExists, } diff --git a/cmd/podman/build.go b/cmd/podman/images/build.go index 43a2f7ab5..06a7efd25 100644 --- a/cmd/podman/build.go +++ b/cmd/podman/images/build.go @@ -1,4 +1,4 @@ -package main +package images import ( "os" @@ -17,6 +17,7 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) // buildFlagsWrapper are local to cmd/ as the build code is using Buildah-internal @@ -48,6 +49,17 @@ var ( podman build --layers --force-rm --tag imageName .`, } + imageBuildCmd = &cobra.Command{ + Args: buildCmd.Args, + Use: buildCmd.Use, + Short: buildCmd.Short, + Long: buildCmd.Long, + RunE: buildCmd.RunE, + Example: `podman image build . + podman image build --creds=username:password -t imageName -f Containerfile.simple . + podman image build --layers --force-rm --tag imageName .`, + } + buildOpts = buildFlagsWrapper{} ) @@ -66,8 +78,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: buildCmd, }) - flags := buildCmd.Flags() + buildFlags(buildCmd.Flags()) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageBuildCmd, + Parent: imageCmd, + }) + buildFlags(imageBuildCmd.Flags()) +} +func buildFlags(flags *pflag.FlagSet) { // Podman flags flags.BoolVarP(&buildOpts.SquashAll, "squash-all", "", false, "Squash all layers into a single layer") diff --git a/cmd/podman/images/diff.go b/cmd/podman/images/diff.go index 7cfacfc6c..c24f98369 100644 --- a/cmd/podman/images/diff.go +++ b/cmd/podman/images/diff.go @@ -6,6 +6,7 @@ import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -28,9 +29,11 @@ func init() { Command: diffCmd, Parent: imageCmd, }) + diffFlags(diffCmd.Flags()) +} +func diffFlags(flags *pflag.FlagSet) { diffOpts = &entities.DiffOptions{} - flags := diffCmd.Flags() flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive") _ = flags.MarkDeprecated("archive", "Provided for backwards compatibility, has no impact on output.") flags.StringVar(&diffOpts.Format, "format", "", "Change the output format") diff --git a/cmd/podman/images/history.go b/cmd/podman/images/history.go index ce153aa46..17a80557e 100644 --- a/cmd/podman/images/history.go +++ b/cmd/podman/images/history.go @@ -15,6 +15,7 @@ import ( "github.com/docker/go-units" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -32,6 +33,15 @@ var ( RunE: history, } + imageHistoryCmd = &cobra.Command{ + Args: historyCmd.Args, + Use: historyCmd.Use, + Short: historyCmd.Short, + Long: historyCmd.Long, + RunE: historyCmd.RunE, + Example: `podman image history imageID`, + } + opts = struct { human bool noTrunc bool @@ -45,8 +55,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: historyCmd, }) + historyFlags(historyCmd.Flags()) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageHistoryCmd, + Parent: imageCmd, + }) + historyFlags(imageHistoryCmd.Flags()) +} - flags := historyCmd.Flags() +func historyFlags(flags *pflag.FlagSet) { flags.StringVar(&opts.format, "format", "", "Change the output to JSON or a Go template") flags.BoolVarP(&opts.human, "human", "H", true, "Display sizes and dates in human readable format") flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate the output") diff --git a/cmd/podman/images/import.go b/cmd/podman/images/import.go index 1c0568762..0e16128ce 100644 --- a/cmd/podman/images/import.go +++ b/cmd/podman/images/import.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -26,6 +27,17 @@ var ( cat ctr.tar | podman -q import --message "importing the ctr.tar tarball" - image-imported cat ctr.tar | podman import -`, } + + imageImportCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), + Use: importCommand.Use, + Short: importCommand.Short, + Long: importCommand.Long, + RunE: importCommand.RunE, + Example: `podman image import http://example.com/ctr.tar url-image + cat ctr.tar | podman -q image import --message "importing the ctr.tar tarball" - image-imported + cat ctr.tar | podman image import -`, + } ) var ( @@ -37,8 +49,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: importCommand, }) + importFlags(importCommand.Flags()) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageImportCommand, + Parent: imageCmd, + }) + importFlags(imageImportCommand.Flags()) +} - flags := importCommand.Flags() +func importFlags(flags *pflag.FlagSet) { flags.StringArrayVarP(&importOpts.Changes, "change", "c", []string{}, "Apply the following possible instructions to the created image (default []): CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR") flags.StringVarP(&importOpts.Message, "message", "m", "", "Set commit message for imported image") flags.BoolVarP(&importOpts.Quiet, "quiet", "q", false, "Suppress output") diff --git a/cmd/podman/images/load.go b/cmd/podman/images/load.go index f49f95002..d34c794c6 100644 --- a/cmd/podman/images/load.go +++ b/cmd/podman/images/load.go @@ -15,6 +15,7 @@ import ( "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" "golang.org/x/crypto/ssh/terminal" ) @@ -27,6 +28,14 @@ var ( RunE: load, Args: cobra.MaximumNArgs(1), } + + imageLoadCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), + Use: loadCommand.Use, + Short: loadCommand.Short, + Long: loadCommand.Long, + RunE: loadCommand.RunE, + } ) var ( @@ -38,8 +47,16 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: loadCommand, }) + loadFlags(loadCommand.Flags()) + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageLoadCommand, + Parent: imageCmd, + }) + loadFlags(imageLoadCommand.Flags()) +} - flags := loadCommand.Flags() +func loadFlags(flags *pflag.FlagSet) { flags.StringVarP(&loadOpts.Input, "input", "i", "", "Read from specified archive file (default: stdin)") flags.BoolVarP(&loadOpts.Quiet, "quiet", "q", false, "Suppress the output") flags.StringVar(&loadOpts.SignaturePolicy, "signature-policy", "", "Pathname of signature policy file") diff --git a/cmd/podman/images/prune.go b/cmd/podman/images/prune.go index 53a1966c1..7c9e3eb61 100644 --- a/cmd/podman/images/prune.go +++ b/cmd/podman/images/prune.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" @@ -71,17 +72,5 @@ Are you sure you want to continue? [y/N] `) return err } - for _, i := range results.Report.Id { - fmt.Println(i) - } - - for _, e := range results.Report.Err { - fmt.Fprint(os.Stderr, e.Error()+"\n") - } - - if results.Size > 0 { - fmt.Fprintf(os.Stdout, "Size: %d\n", results.Size) - } - - return nil + return utils.PrintImagePruneResults(results) } diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go index 9f4cbc50e..9e883703f 100644 --- a/cmd/podman/images/pull.go +++ b/cmd/podman/images/pull.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - buildahcli "github.com/containers/buildah/pkg/cli" + "github.com/containers/common/pkg/auth" "github.com/containers/image/v5/types" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" @@ -75,7 +75,7 @@ func init() { // pullFlags set the flags for the pull command. func pullFlags(flags *pflag.FlagSet) { flags.BoolVar(&pullOptions.AllTags, "all-tags", false, "All tagged images in the repository will be pulled") - flags.StringVar(&pullOptions.Authfile, "authfile", buildahcli.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&pullOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") flags.StringVar(&pullOptions.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") flags.StringVar(&pullOptions.Credentials, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") flags.StringVar(&pullOptions.OverrideArch, "override-arch", "", "Use `ARCH` instead of the architecture of the machine for choosing images") diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go index 0b3502d61..dd536213f 100644 --- a/cmd/podman/images/push.go +++ b/cmd/podman/images/push.go @@ -3,7 +3,7 @@ package images import ( "os" - buildahcli "github.com/containers/buildah/pkg/cli" + "github.com/containers/common/pkg/auth" "github.com/containers/image/v5/types" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" @@ -70,7 +70,7 @@ func init() { // pushFlags set the flags for the push command. func pushFlags(flags *pflag.FlagSet) { - flags.StringVar(&pushOptions.Authfile, "authfile", buildahcli.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&pushOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") flags.StringVar(&pushOptions.CertDir, "cert-dir", "", "Path to a directory containing TLS certificates and keys") flags.BoolVar(&pushOptions.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(&pushOptions.Credentials, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") diff --git a/cmd/podman/images/rm.go b/cmd/podman/images/rm.go index 1cf5fa365..4b9920532 100644 --- a/cmd/podman/images/rm.go +++ b/cmd/podman/images/rm.go @@ -5,6 +5,7 @@ import ( "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/errorhandling" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -48,7 +49,9 @@ func rm(cmd *cobra.Command, args []string) error { return errors.Errorf("when using the --all switch, you may not pass any images names or IDs") } - report, err := registry.ImageEngine().Remove(registry.GetContext(), args, imageOpts) + // Note: certain image-removal errors are non-fatal. Hence, the report + // might be set even if err != nil. + report, rmErrors := registry.ImageEngine().Remove(registry.GetContext(), args, imageOpts) if report != nil { for _, u := range report.Untagged { fmt.Println("Untagged: " + u) @@ -62,5 +65,5 @@ func rm(cmd *cobra.Command, args []string) error { registry.SetExitCode(report.ExitCode) } - return err + return errorhandling.JoinErrors(rmErrors) } diff --git a/cmd/podman/images/save.go b/cmd/podman/images/save.go index 8f7832074..56953e41c 100644 --- a/cmd/podman/images/save.go +++ b/cmd/podman/images/save.go @@ -13,6 +13,7 @@ import ( "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" "golang.org/x/crypto/ssh/terminal" ) @@ -43,6 +44,16 @@ var ( podman save --format docker-dir -o ubuntu-dir ubuntu podman save > alpine-all.tar alpine:latest`, } + imageSaveCommand = &cobra.Command{ + Args: saveCommand.Args, + Use: saveCommand.Use, + Short: saveCommand.Short, + Long: saveCommand.Long, + RunE: saveCommand.RunE, + Example: `podman image save --quiet -o myimage.tar imageID + podman image save --format docker-dir -o ubuntu-dir ubuntu + podman image save > alpine-all.tar alpine:latest`, + } ) var ( @@ -54,7 +65,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: saveCommand, }) - flags := saveCommand.Flags() + saveFlags(saveCommand.Flags()) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageSaveCommand, + Parent: imageCmd, + }) + saveFlags(imageSaveCommand.Flags()) +} + +func saveFlags(flags *pflag.FlagSet) { flags.BoolVar(&saveOpts.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(&saveOpts.Format, "format", define.V2s2Archive, "Save image to oci-archive, oci-dir (directory with oci manifest type), docker-archive, docker-dir (directory with v2s2 manifest type)") flags.StringVarP(&saveOpts.Output, "output", "o", "", "Write to a specified file (default: stdout, which must be redirected)") diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go index a8abfb339..640d497c3 100644 --- a/cmd/podman/images/search.go +++ b/cmd/podman/images/search.go @@ -5,8 +5,8 @@ import ( "reflect" "strings" - buildahcli "github.com/containers/buildah/pkg/cli" "github.com/containers/buildah/pkg/formats" + "github.com/containers/common/pkg/auth" "github.com/containers/image/v5/types" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" @@ -87,7 +87,7 @@ func searchFlags(flags *pflag.FlagSet) { flags.StringVar(&searchOptions.Format, "format", "", "Change the output format to a Go template") flags.IntVar(&searchOptions.Limit, "limit", 0, "Limit the number of results") flags.BoolVar(&searchOptions.NoTrunc, "no-trunc", false, "Do not truncate the output") - flags.StringVar(&searchOptions.Authfile, "authfile", buildahcli.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&searchOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") flags.BoolVar(&searchOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") if registry.IsRemote() { _ = flags.MarkHidden("authfile") @@ -105,6 +105,10 @@ func imageSearch(cmd *cobra.Command, args []string) error { return errors.Errorf("search requires exactly one argument") } + if searchOptions.Limit > 100 { + return errors.Errorf("Limit %d is outside the range of [1, 100]", searchOptions.Limit) + } + // TLS verification in c/image is controlled via a `types.OptionalBool` // which allows for distinguishing among set-true, set-false, unspecified // which is important to implement a sane way of dealing with defaults of diff --git a/cmd/podman/images/tag.go b/cmd/podman/images/tag.go index 411313a9b..dae3416c4 100644 --- a/cmd/podman/images/tag.go +++ b/cmd/podman/images/tag.go @@ -18,6 +18,17 @@ var ( podman tag imageID:latest myNewImage:newTag podman tag httpd myregistryhost:5000/fedora/httpd:v2`, } + + imageTagCommand = &cobra.Command{ + Args: tagCommand.Args, + Use: tagCommand.Use, + Short: tagCommand.Short, + Long: tagCommand.Long, + RunE: tagCommand.RunE, + Example: `podman image tag 0e3bbc2 fedora:latest + podman image tag imageID:latest myNewImage:newTag + podman image tag httpd myregistryhost:5000/fedora/httpd:v2`, + } ) func init() { @@ -25,6 +36,11 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: tagCommand, }) + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageTagCommand, + Parent: imageCmd, + }) } func tag(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/images/untag.go b/cmd/podman/images/untag.go index 3218844b7..266a3f115 100644 --- a/cmd/podman/images/untag.go +++ b/cmd/podman/images/untag.go @@ -17,6 +17,17 @@ var ( podman untag imageID:latest otherImageName:latest podman untag httpd myregistryhost:5000/fedora/httpd:v2`, } + + imageUntagCommand = &cobra.Command{ + Args: untagCommand.Args, + Use: untagCommand.Use, + Short: untagCommand.Short, + Long: untagCommand.Long, + RunE: untagCommand.RunE, + Example: `podman image untag 0e3bbc2 + podman image untag imageID:latest otherImageName:latest + podman image untag httpd myregistryhost:5000/fedora/httpd:v2`, + } ) func init() { @@ -24,6 +35,11 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: untagCommand, }) + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageUntagCommand, + Parent: imageCmd, + }) } func untag(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/login.go b/cmd/podman/login.go index 9de805d15..dc57758ab 100644 --- a/cmd/podman/login.go +++ b/cmd/podman/login.go @@ -63,6 +63,6 @@ func login(cmd *cobra.Command, args []string) error { DockerCertPath: loginOptions.CertDir, DockerInsecureSkipTLSVerify: skipTLS, } - + loginOptions.GetLoginSet = cmd.Flag("get-login").Changed return auth.Login(context.Background(), &sysCtx, &loginOptions.LoginOptions, args[0]) } diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 3a8958b6d..422dee90b 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -9,6 +9,7 @@ import ( _ "github.com/containers/libpod/cmd/podman/healthcheck" _ "github.com/containers/libpod/cmd/podman/images" _ "github.com/containers/libpod/cmd/podman/manifest" + _ "github.com/containers/libpod/cmd/podman/networks" _ "github.com/containers/libpod/cmd/podman/pods" "github.com/containers/libpod/cmd/podman/registry" _ "github.com/containers/libpod/cmd/podman/system" diff --git a/cmd/podman/networks/create.go b/cmd/podman/networks/create.go new file mode 100644 index 000000000..2bb75ea9e --- /dev/null +++ b/cmd/podman/networks/create.go @@ -0,0 +1,81 @@ +package network + +import ( + "fmt" + "net" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/network" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var ( + networkCreateDescription = `create CNI networks for containers and pods` + networkCreateCommand = &cobra.Command{ + Use: "create [flags] [NETWORK]", + Short: "network create", + Long: networkCreateDescription, + RunE: networkCreate, + Example: `podman network create podman1`, + Annotations: map[string]string{ + registry.ParentNSRequired: "", + }, + } +) + +var ( + networkCreateOptions entities.NetworkCreateOptions +) + +func networkCreateFlags(flags *pflag.FlagSet) { + flags.StringVarP(&networkCreateOptions.Driver, "driver", "d", "bridge", "driver to manage the network") + flags.IPVar(&networkCreateOptions.Gateway, "gateway", nil, "IPv4 or IPv6 gateway for the subnet") + flags.BoolVar(&networkCreateOptions.Internal, "internal", false, "restrict external access from this network") + flags.IPNetVar(&networkCreateOptions.Range, "ip-range", net.IPNet{}, "allocate container IP from range") + flags.StringVar(&networkCreateOptions.MacVLAN, "macvlan", "", "create a Macvlan connection based on this device") + // TODO not supported yet + //flags.StringVar(&networkCreateOptions.IPamDriver, "ipam-driver", "", "IP Address Management Driver") + // TODO enable when IPv6 is working + //flags.BoolVar(&networkCreateOptions.IPV6, "IPv6", false, "enable IPv6 networking") + flags.IPNetVar(&networkCreateOptions.Subnet, "subnet", net.IPNet{}, "subnet in CIDR format") + flags.BoolVar(&networkCreateOptions.DisableDNS, "disable-dns", false, "disable dns plugin") +} +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: networkCreateCommand, + Parent: networkCmd, + }) + flags := networkCreateCommand.Flags() + networkCreateFlags(flags) + +} + +func networkCreate(cmd *cobra.Command, args []string) error { + var ( + name string + ) + if err := network.IsSupportedDriver(networkCreateOptions.Driver); err != nil { + return err + } + if len(args) > 1 { + return errors.Errorf("only one network can be created at a time") + } + if len(args) > 0 && !libpod.NameRegex.MatchString(args[0]) { + return libpod.RegexError + } + + if len(args) > 0 { + name = args[0] + } + response, err := registry.ContainerEngine().NetworkCreate(registry.Context(), name, networkCreateOptions) + if err != nil { + return err + } + fmt.Println(response.Filename) + return nil +} diff --git a/cmd/podman/networks/inspect.go b/cmd/podman/networks/inspect.go new file mode 100644 index 000000000..0bc73579a --- /dev/null +++ b/cmd/podman/networks/inspect.go @@ -0,0 +1,46 @@ +package network + +import ( + "encoding/json" + "fmt" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + networkinspectDescription = `Inspect network` + networkinspectCommand = &cobra.Command{ + Use: "inspect NETWORK [NETWORK...] [flags] ", + Short: "network inspect", + Long: networkinspectDescription, + RunE: networkInspect, + Example: `podman network inspect podman`, + Args: cobra.MinimumNArgs(1), + Annotations: map[string]string{ + registry.ParentNSRequired: "", + }, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: networkinspectCommand, + Parent: networkCmd, + }) +} + +func networkInspect(cmd *cobra.Command, args []string) error { + responses, err := registry.ContainerEngine().NetworkInspect(registry.Context(), args, entities.NetworkInspectOptions{}) + if err != nil { + return err + } + b, err := json.MarshalIndent(responses, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil +} diff --git a/cmd/podman/networks/list.go b/cmd/podman/networks/list.go new file mode 100644 index 000000000..e27062255 --- /dev/null +++ b/cmd/podman/networks/list.go @@ -0,0 +1,133 @@ +package network + +import ( + "encoding/json" + "fmt" + "html/template" + "io" + "os" + "strings" + + "github.com/containers/libpod/cmd/podman/validate" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/network" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var ( + networklistDescription = `List networks` + networklistCommand = &cobra.Command{ + Use: "ls", + Args: validate.NoArgs, + Short: "network list", + Long: networklistDescription, + RunE: networkList, + Example: `podman network list`, + Annotations: map[string]string{ + registry.ParentNSRequired: "", + }, + } +) + +var ( + networkListOptions entities.NetworkListOptions + headers string = "NAME\tVERSION\tPLUGINS\n" + defaultListRow string = "{{.Name}}\t{{.Version}}\t{{.Plugins}}\n" +) + +func networkListFlags(flags *pflag.FlagSet) { + // TODO enable filters based on something + //flags.StringSliceVarP(&networklistCommand.Filter, "filter", "f", []string{}, "Pause all running containers") + flags.StringVarP(&networkListOptions.Format, "format", "f", "", "Pretty-print containers to JSON or using a Go template") + flags.BoolVarP(&networkListOptions.Quiet, "quiet", "q", false, "display only names") +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: networklistCommand, + Parent: networkCmd, + }) + flags := networklistCommand.Flags() + networkListFlags(flags) +} + +func networkList(cmd *cobra.Command, args []string) error { + var ( + w io.Writer = os.Stdout + nlprs []NetworkListPrintReports + ) + + responses, err := registry.ContainerEngine().NetworkList(registry.Context(), networkListOptions) + if err != nil { + return err + } + + // quiet means we only print the network names + if networkListOptions.Quiet { + return quietOut(responses) + } + + if networkListOptions.Format == "json" { + return jsonOut(responses) + } + + for _, r := range responses { + nlprs = append(nlprs, NetworkListPrintReports{r}) + } + + row := networkListOptions.Format + if len(row) < 1 { + row = defaultListRow + } + if !strings.HasSuffix(row, "\n") { + row += "\n" + } + + format := "{{range . }}" + row + "{{end}}" + if !cmd.Flag("format").Changed { + format = headers + format + } + tmpl, err := template.New("listNetworks").Parse(format) + if err != nil { + return err + } + if err := tmpl.Execute(w, nlprs); err != nil { + return err + } + if flusher, ok := w.(interface{ Flush() error }); ok { + return flusher.Flush() + } + return nil +} + +func quietOut(responses []*entities.NetworkListReport) error { + for _, r := range responses { + fmt.Println(r.Name) + } + return nil +} + +func jsonOut(responses []*entities.NetworkListReport) error { + b, err := json.MarshalIndent(responses, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil +} + +type NetworkListPrintReports struct { + *entities.NetworkListReport +} + +func (n NetworkListPrintReports) Version() string { + return n.CNIVersion +} + +func (n NetworkListPrintReports) Plugins() string { + return network.GetCNIPlugins(n.NetworkConfigList) +} diff --git a/cmd/podman/networks/network.go b/cmd/podman/networks/network.go index e2a928312..56dd390ea 100644 --- a/cmd/podman/networks/network.go +++ b/cmd/podman/networks/network.go @@ -1,4 +1,4 @@ -package images +package network import ( "github.com/containers/libpod/cmd/podman/registry" @@ -9,7 +9,7 @@ import ( var ( // Command: podman _network_ - cmd = &cobra.Command{ + networkCmd = &cobra.Command{ Use: "network", Short: "Manage networks", Long: "Manage networks", @@ -18,12 +18,9 @@ var ( } ) -// TODO add the following to main.go to get networks back onto the -// command list. -// _ "github.com/containers/libpod/cmd/podman/networks" func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode}, - Command: cmd, + Command: networkCmd, }) } diff --git a/cmd/podman/networks/rm.go b/cmd/podman/networks/rm.go new file mode 100644 index 000000000..dc1eb9909 --- /dev/null +++ b/cmd/podman/networks/rm.go @@ -0,0 +1,63 @@ +package network + +import ( + "fmt" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var ( + networkrmDescription = `Remove networks` + networkrmCommand = &cobra.Command{ + Use: "rm [flags] NETWORK [NETWORK...]", + Short: "network rm", + Long: networkrmDescription, + RunE: networkRm, + Example: `podman network rm podman`, + Args: cobra.MinimumNArgs(1), + Annotations: map[string]string{ + registry.ParentNSRequired: "", + }, + } +) + +var ( + networkRmOptions entities.NetworkRmOptions +) + +func networkRmFlags(flags *pflag.FlagSet) { + flags.BoolVarP(&networkRmOptions.Force, "force", "f", false, "remove any containers using network") +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: networkrmCommand, + Parent: networkCmd, + }) + flags := networkrmCommand.Flags() + networkRmFlags(flags) +} + +func networkRm(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + ) + + responses, err := registry.ContainerEngine().NetworkRm(registry.Context(), args, networkRmOptions) + if err != nil { + return err + } + for _, r := range responses { + if r.Err == nil { + fmt.Println(r.Name) + } else { + errs = append(errs, r.Err) + } + } + return errs.PrintErrors() +} diff --git a/cmd/podman/pods/pod.go b/cmd/podman/pods/pod.go index edca08202..ed265ef90 100644 --- a/cmd/podman/pods/pod.go +++ b/cmd/podman/pods/pod.go @@ -16,7 +16,7 @@ var ( podCmd = &cobra.Command{ Use: "pod", Short: "Manage pods", - Long: "Manage pods", + Long: "Pods are a group of one or more containers sharing the same network, pid and ipc namespaces.", TraverseChildren: true, RunE: validate.SubCommandExists, } diff --git a/cmd/podman/pods/prune.go b/cmd/podman/pods/prune.go index 091e3375b..bc15d8035 100644 --- a/cmd/podman/pods/prune.go +++ b/cmd/podman/pods/prune.go @@ -41,9 +41,6 @@ func init() { } func prune(cmd *cobra.Command, args []string) error { - var ( - errs utils.OutputErrors - ) if len(args) > 0 { return errors.Errorf("`%s` takes no arguments", cmd.CommandPath()) } @@ -60,16 +57,8 @@ func prune(cmd *cobra.Command, args []string) error { } } responses, err := registry.ContainerEngine().PodPrune(context.Background(), pruneOptions) - if err != nil { return err } - for _, r := range responses { - if r.Err == nil { - fmt.Println(r.Id) - } else { - errs = append(errs, r.Err) - } - } - return errs.PrintErrors() + return utils.PrintPodPruneResults(responses) } diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go index b97dfeb66..5703bd172 100644 --- a/cmd/podman/pods/ps.go +++ b/cmd/podman/pods/ps.go @@ -26,7 +26,7 @@ var ( psCmd = &cobra.Command{ Use: "ps", Aliases: []string{"ls", "list"}, - Short: "list pods", + Short: "List pods", Long: psDescription, RunE: pods, Args: validate.NoArgs, diff --git a/cmd/podman/pods/stats.go b/cmd/podman/pods/stats.go index 7c3597d9a..d3950fdbc 100644 --- a/cmd/podman/pods/stats.go +++ b/cmd/podman/pods/stats.go @@ -35,7 +35,7 @@ var ( // Command: podman pod _pod_ statsCmd = &cobra.Command{ Use: "stats [flags] [POD...]", - Short: "Display resource-usage statistics of pods", + Short: "Display a live stream of resource usage statistics for the containers in one or more pods", Long: statsDescription, RunE: stats, Example: `podman pod stats diff --git a/cmd/podman/pods/top.go b/cmd/podman/pods/top.go index ad602f4ea..9cf2bd525 100644 --- a/cmd/podman/pods/top.go +++ b/cmd/podman/pods/top.go @@ -25,7 +25,7 @@ var ( topCommand = &cobra.Command{ Use: "top [flags] POD [FORMAT-DESCRIPTORS|ARGS]", - Short: "Display the running processes in a pod", + Short: "Display the running processes of containers in a pod", Long: topDescription, RunE: top, Args: cobra.ArbitraryArgs, diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 375faf8b1..502b6c03c 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -212,7 +212,7 @@ func rootFlags(opts *entities.PodmanConfig, flags *pflag.FlagSet) { flags.StringSliceVar(&opts.Identities, "identity", []string{}, "path to SSH identity file") cfg := opts.Config - flags.StringVar(&cfg.Engine.CgroupManager, "cgroup-manager", cfg.Engine.CgroupManager, opts.CGroupUsage) + flags.StringVar(&cfg.Engine.CgroupManager, "cgroup-manager", cfg.Engine.CgroupManager, "Cgroup manager to use (\"cgroupfs\"|\"systemd\")") flags.StringVar(&opts.CpuProfile, "cpu-profile", "", "Path for the cpu profiling results") flags.StringVar(&opts.ConmonPath, "conmon", "", "Path of the conmon binary") flags.StringVar(&cfg.Engine.NetworkCmdPath, "network-cmd-path", cfg.Engine.NetworkCmdPath, "Path to the command for configuring the network") diff --git a/cmd/podman/system/info.go b/cmd/podman/system/info.go index 26be794c5..dad63bcd4 100644 --- a/cmd/podman/system/info.go +++ b/cmd/podman/system/info.go @@ -10,6 +10,7 @@ import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/ghodss/yaml" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -25,6 +26,15 @@ var ( RunE: info, Example: `podman info`, } + + systemInfoCommand = &cobra.Command{ + Args: infoCommand.Args, + Use: infoCommand.Use, + Short: infoCommand.Short, + Long: infoCommand.Long, + RunE: infoCommand.RunE, + Example: `podman system info`, + } ) var ( @@ -37,7 +47,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: infoCommand, }) - flags := infoCommand.Flags() + infoFlags(infoCommand.Flags()) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: systemInfoCommand, + Parent: systemCmd, + }) + infoFlags(systemInfoCommand.Flags()) +} + +func infoFlags(flags *pflag.FlagSet) { flags.BoolVarP(&debug, "debug", "D", false, "Display additional debug information") flags.StringVarP(&inFormat, "format", "f", "", "Change the output format to JSON or a Go template") } diff --git a/cmd/podman/system/prune.go b/cmd/podman/system/prune.go new file mode 100644 index 000000000..cc9b473f1 --- /dev/null +++ b/cmd/podman/system/prune.go @@ -0,0 +1,103 @@ +package system + +import ( + "bufio" + "context" + "fmt" + "os" + "strings" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + pruneOptions = entities.SystemPruneOptions{} + + pruneDescription = fmt.Sprintf(` + podman system prune + + Remove unused data +`) + + pruneCommand = &cobra.Command{ + Use: "prune [flags]", + Short: "Remove unused data", + Args: validate.NoArgs, + Long: pruneDescription, + RunE: prune, + Example: `podman system prune`, + } + force bool +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: pruneCommand, + Parent: systemCmd, + }) + flags := pruneCommand.Flags() + flags.BoolVarP(&force, "force", "f", false, "Do not prompt for confirmation. The default is false") + flags.BoolVarP(&pruneOptions.All, "all", "a", false, "Remove all unused data") + flags.BoolVar(&pruneOptions.Volume, "volumes", false, "Prune volumes") + +} + +func prune(cmd *cobra.Command, args []string) error { + // Prompt for confirmation if --force is not set + if !force { + reader := bufio.NewReader(os.Stdin) + volumeString := "" + if pruneOptions.Volume { + volumeString = ` + - all volumes not used by at least one container` + } + fmt.Printf(` +WARNING! This will remove: + - all stopped containers%s + - all stopped pods + - all dangling images + - all build cache +Are you sure you want to continue? [y/N] `, volumeString) + answer, err := reader.ReadString('\n') + if err != nil { + return errors.Wrapf(err, "error reading input") + } + if strings.ToLower(answer)[0] != 'y' { + return nil + } + } + // TODO: support for filters in system prune + response, err := registry.ContainerEngine().SystemPrune(context.Background(), pruneOptions) + if err != nil { + return err + } + // Print pod prune results + fmt.Println("Deleted Pods") + err = utils.PrintPodPruneResults(response.PodPruneReport) + if err != nil { + return err + } + // Print container prune results + fmt.Println("Deleted Containers") + err = utils.PrintContainerPruneResults(response.ContainerPruneReport) + if err != nil { + return err + } + // Print Volume prune results + if pruneOptions.Volume { + fmt.Println("Deleted Volumes") + err = utils.PrintVolumePruneResults(response.VolumePruneReport) + if err != nil { + return err + } + } + // Print Images prune results + fmt.Println("Deleted Images") + return utils.PrintImagePruneResults(response.ImagePruneReport) +} diff --git a/cmd/podman/utils/utils.go b/cmd/podman/utils/utils.go index c7d105ba4..f4c704628 100644 --- a/cmd/podman/utils/utils.go +++ b/cmd/podman/utils/utils.go @@ -1,6 +1,11 @@ package utils -import "os" +import ( + "fmt" + "os" + + "github.com/containers/libpod/pkg/domain/entities" +) // IsDir returns true if the specified path refers to a directory. func IsDir(path string) bool { @@ -20,3 +25,51 @@ func FileExists(path string) bool { } return !file.IsDir() } + +func PrintPodPruneResults(podPruneReports []*entities.PodPruneReport) error { + var errs OutputErrors + for _, r := range podPruneReports { + if r.Err == nil { + fmt.Println(r.Id) + } else { + errs = append(errs, r.Err) + } + } + return errs.PrintErrors() +} + +func PrintContainerPruneResults(containerPruneReport *entities.ContainerPruneReport) error { + var errs OutputErrors + for k := range containerPruneReport.ID { + fmt.Println(k) + } + for _, v := range containerPruneReport.Err { + errs = append(errs, v) + } + return errs.PrintErrors() +} + +func PrintVolumePruneResults(volumePruneReport []*entities.VolumePruneReport) error { + var errs OutputErrors + for _, r := range volumePruneReport { + if r.Err == nil { + fmt.Println(r.Id) + } else { + errs = append(errs, r.Err) + } + } + return errs.PrintErrors() +} + +func PrintImagePruneResults(imagePruneReport *entities.ImagePruneReport) error { + for _, i := range imagePruneReport.Report.Id { + fmt.Println(i) + } + for _, e := range imagePruneReport.Report.Err { + fmt.Fprint(os.Stderr, e.Error()+"\n") + } + if imagePruneReport.Size > 0 { + fmt.Fprintf(os.Stdout, "Size: %d\n", imagePruneReport.Size) + } + return nil +} diff --git a/cmd/podman/volumes/prune.go b/cmd/podman/volumes/prune.go index 2c3ed88f3..57344385b 100644 --- a/cmd/podman/volumes/prune.go +++ b/cmd/podman/volumes/prune.go @@ -44,9 +44,6 @@ func init() { } func prune(cmd *cobra.Command, args []string) error { - var ( - errs utils.OutputErrors - ) // Prompt for confirmation if --force is not set if !pruneOptions.Force { reader := bufio.NewReader(os.Stdin) @@ -64,12 +61,5 @@ func prune(cmd *cobra.Command, args []string) error { if err != nil { return err } - for _, r := range responses { - if r.Err == nil { - fmt.Println(r.Id) - } else { - errs = append(errs, r.Err) - } - } - return errs.PrintErrors() + return utils.PrintVolumePruneResults(responses) } |