diff options
Diffstat (limited to 'cmd/podman')
37 files changed, 900 insertions, 278 deletions
diff --git a/cmd/podman/build.go b/cmd/podman/build.go index 14bf226f9..880cb892f 100644 --- a/cmd/podman/build.go +++ b/cmd/podman/build.go @@ -1,6 +1,11 @@ package main import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + "github.com/containers/buildah" "github.com/containers/buildah/imagebuildah" buildahcli "github.com/containers/buildah/pkg/cli" @@ -10,15 +15,15 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/urfave/cli" - "io/ioutil" - "os" - "path/filepath" - "strings" ) 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. ", }, @@ -230,7 +235,7 @@ func buildCmd(c *cli.Context) error { Layers: layers, NoCache: c.Bool("no-cache"), RemoveIntermediateCtrs: c.BoolT("rm"), - ForceRmIntermediateCtrs: c.Bool("force-rm"), + ForceRmIntermediateCtrs: c.BoolT("force-rm"), } if c.Bool("quiet") { diff --git a/cmd/podman/checkpoint.go b/cmd/podman/checkpoint.go index bf280920d..824c97662 100644 --- a/cmd/podman/checkpoint.go +++ b/cmd/podman/checkpoint.go @@ -24,6 +24,14 @@ var ( Usage: "keep all temporary checkpoint files", }, cli.BoolFlag{ + Name: "leave-running, R", + Usage: "leave the container running after writing checkpoint to disk", + }, + cli.BoolFlag{ + Name: "tcp-established", + Usage: "checkpoint a container with established TCP connections", + }, + cli.BoolFlag{ Name: "all, a", Usage: "checkpoint all running containers", }, @@ -50,7 +58,11 @@ func checkpointCmd(c *cli.Context) error { } defer runtime.Shutdown(false) - keep := c.Bool("keep") + 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 @@ -59,7 +71,7 @@ func checkpointCmd(c *cli.Context) error { containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") for _, ctr := range containers { - if err = ctr.Checkpoint(context.TODO(), keep); err != nil { + if err = ctr.Checkpoint(context.TODO(), options); err != nil { if lastError != nil { fmt.Fprintln(os.Stderr, lastError) } diff --git a/cmd/podman/commit.go b/cmd/podman/commit.go index b09c6b0d9..02ede4f73 100644 --- a/cmd/podman/commit.go +++ b/cmd/podman/commit.go @@ -95,7 +95,7 @@ func commitCmd(c *cli.Context) error { for _, change := range c.StringSlice("change") { splitChange := strings.Split(strings.ToUpper(change), "=") if !util.StringInSlice(splitChange[0], libpod.ChangeCmds) { - return errors.Errorf("invalid syntax for --change ", change) + return errors.Errorf("invalid syntax for --change: %s", change) } } } diff --git a/cmd/podman/common.go b/cmd/podman/common.go index f9e746b28..c4016698a 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -11,6 +11,7 @@ import ( "github.com/containers/buildah" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/fatih/camelcase" "github.com/pkg/errors" @@ -161,6 +162,13 @@ func getContext() context.Context { return context.TODO() } +func getDefaultNetwork() string { + if rootless.IsRootless() { + return "slirp4netns" + } + return "bridge" +} + // Common flags shared between commands var createFlags = []cli.Flag{ cli.StringSliceFlag{ @@ -372,7 +380,7 @@ var createFlags = []cli.Flag{ cli.StringFlag{ Name: "net, network", Usage: "Connect a container to a network", - Value: "bridge", + Value: getDefaultNetwork(), }, cli.BoolFlag{ Name: "oom-kill-disable", diff --git a/cmd/podman/container.go b/cmd/podman/container.go index ff634278f..b6262f890 100644 --- a/cmd/podman/container.go +++ b/cmd/podman/container.go @@ -9,6 +9,7 @@ var ( attachCommand, checkpointCommand, cleanupCommand, + containerExistsCommand, commitCommand, createCommand, diffCommand, diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 9f6825c95..228438d75 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -11,6 +11,7 @@ import ( "syscall" "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" ann "github.com/containers/libpod/pkg/annotations" @@ -66,7 +67,7 @@ func createCmd(c *cli.Context) error { rootless.SetSkipStorageSetup(true) } - runtime, err := libpodruntime.GetContainerRuntime(c) + runtime, err := libpodruntime.GetRuntime(c) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } @@ -375,8 +376,8 @@ func configureEntrypoint(c *cli.Context, data *inspect.ImageData) []string { return entrypoint } -func configurePod(c *cli.Context, runtime *libpod.Runtime, namespaces map[string]string) (map[string]string, error) { - pod, err := runtime.LookupPod(c.String("pod")) +func configurePod(c *cli.Context, runtime *libpod.Runtime, namespaces map[string]string, podName string) (map[string]string, error) { + pod, err := runtime.LookupPod(podName) if err != nil { return namespaces, err } @@ -409,6 +410,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim inputCommand, command []string memoryLimit, memoryReservation, memorySwap, memoryKernel int64 blkioWeight uint16 + namespaces map[string]string ) idmappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidname"), c.String("subgidname")) if err != nil { @@ -492,12 +494,21 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim return nil, errors.Errorf("--cpu-quota and --cpus cannot be set together") } + // EXPOSED PORTS + var portBindings map[nat.Port][]nat.PortBinding + if data != nil { + portBindings, err = cc.ExposedPorts(c.StringSlice("expose"), c.StringSlice("publish"), c.Bool("publish-all"), data.ContainerConfig.ExposedPorts) + if err != nil { + return nil, err + } + } + // Kernel Namespaces // TODO Fix handling of namespace from pod // Instead of integrating here, should be done in libpod // However, that also involves setting up security opts // when the pod's namespace is integrated - namespaces := map[string]string{ + namespaces = map[string]string{ "pid": c.String("pid"), "net": c.String("net"), "ipc": c.String("ipc"), @@ -505,8 +516,41 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim "uts": c.String("uts"), } + originalPodName := c.String("pod") + podName := strings.Replace(originalPodName, "new:", "", 1) + // after we strip out :new, make sure there is something left for a pod name + if len(podName) < 1 && c.IsSet("pod") { + return nil, errors.Errorf("new pod name must be at least one character") + } if c.IsSet("pod") { - namespaces, err = configurePod(c, runtime, namespaces) + if strings.HasPrefix(originalPodName, "new:") { + // pod does not exist; lets make it + var podOptions []libpod.PodCreateOption + podOptions = append(podOptions, libpod.WithPodName(podName), libpod.WithInfraContainer(), libpod.WithPodCgroups()) + if len(portBindings) > 0 { + ociPortBindings, err := cc.NatToOCIPortBindings(portBindings) + if err != nil { + return nil, err + } + podOptions = append(podOptions, libpod.WithInfraContainerPorts(ociPortBindings)) + } + + podNsOptions, err := shared.GetNamespaceOptions(strings.Split(DefaultKernelNamespaces, ",")) + if err != nil { + return nil, err + } + podOptions = append(podOptions, podNsOptions...) + // make pod + pod, err := runtime.NewPod(ctx, podOptions...) + if err != nil { + return nil, err + } + logrus.Debugf("pod %s created by new container request", pod.ID()) + + // The container now cannot have port bindings; so we reset the map + portBindings = make(map[nat.Port][]nat.PortBinding) + } + namespaces, err = configurePod(c, runtime, namespaces, podName) if err != nil { return nil, err } @@ -535,7 +579,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim // Make sure if network is set to container namespace, port binding is not also being asked for netMode := ns.NetworkMode(namespaces["net"]) if netMode.IsContainer() { - if len(c.StringSlice("publish")) > 0 || c.Bool("publish-all") { + if len(portBindings) > 0 { return nil, errors.Errorf("cannot set port bindings on an existing container network namespace") } } @@ -644,15 +688,6 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim return nil, errors.Errorf("No command specified on command line or as CMD or ENTRYPOINT in this image") } - // EXPOSED PORTS - var portBindings map[nat.Port][]nat.PortBinding - if data != nil { - portBindings, err = cc.ExposedPorts(c.StringSlice("expose"), c.StringSlice("publish"), c.Bool("publish-all"), data.ContainerConfig.ExposedPorts) - if err != nil { - return nil, err - } - } - // SHM Size shmSize, err := units.FromHumanSize(c.String("shm-size")) if err != nil { @@ -670,6 +705,11 @@ 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") { @@ -741,7 +781,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim NetMode: netMode, UtsMode: utsMode, PidMode: pidMode, - Pod: c.String("pod"), + Pod: podName, Privileged: c.Bool("privileged"), Publish: c.StringSlice("publish"), PublishAll: c.Bool("publish-all"), diff --git a/cmd/podman/exists.go b/cmd/podman/exists.go new file mode 100644 index 000000000..2e2559ec7 --- /dev/null +++ b/cmd/podman/exists.go @@ -0,0 +1,120 @@ +package main + +import ( + "os" + + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/image" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +var ( + 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 +` + + podExistsCommand = cli.Command{ + Name: "exists", + Usage: "Check if a pod exists in local storage", + Description: podExistsDescription, + Action: podExistsCmd, + ArgsUsage: "POD-NAME", + OnUsageError: usageErrorHandler, + } +) + +func imageExistsCmd(c *cli.Context) error { + args := c.Args() + 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 := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + if _, err := runtime.ImageRuntime().NewFromLocal(args[0]); err != nil { + if errors.Cause(err) == image.ErrNoSuchImage { + os.Exit(1) + } + return err + } + return nil +} + +func containerExistsCmd(c *cli.Context) error { + args := c.Args() + 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 := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + if _, err := runtime.LookupContainer(args[0]); err != nil { + if errors.Cause(err) == libpod.ErrNoSuchCtr { + os.Exit(1) + } + return err + } + return nil +} + +func podExistsCmd(c *cli.Context) error { + args := c.Args() + 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) + 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 { + os.Exit(1) + } + return err + } + return nil +} diff --git a/cmd/podman/image.go b/cmd/podman/image.go index e67f61799..418b442e3 100644 --- a/cmd/podman/image.go +++ b/cmd/podman/image.go @@ -9,6 +9,7 @@ var ( buildCommand, historyCommand, importCommand, + imageExistsCommand, inspectCommand, loadCommand, lsImagesCommand, diff --git a/cmd/podman/images.go b/cmd/podman/images.go index a8955e49e..3351123ed 100644 --- a/cmd/podman/images.go +++ b/cmd/podman/images.go @@ -6,8 +6,7 @@ import ( "sort" "strings" "time" - - "github.com/sirupsen/logrus" + "unicode" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/libpodruntime" @@ -16,6 +15,7 @@ import ( "github.com/docker/go-units" digest "github.com/opencontainers/go-digest" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -289,6 +289,8 @@ func getImagesTemplateOutput(ctx context.Context, runtime *libpod.Runtime, image sizeStr = err.Error() } else { sizeStr = units.HumanSizeWithPrecision(float64(*size), 3) + lastNumIdx := strings.LastIndexFunc(sizeStr, unicode.IsNumber) + sizeStr = sizeStr[:lastNumIdx+1] + " " + sizeStr[lastNumIdx+1:] } params := imagesTemplateParams{ Repository: repo, @@ -374,13 +376,13 @@ func CreateFilterFuncs(ctx context.Context, r *libpod.Runtime, c *cli.Context, i case "before": before, err := r.ImageRuntime().NewFromLocal(splitFilter[1]) if err != nil { - return nil, errors.Wrapf(err, "unable to find image % in local stores", splitFilter[1]) + return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1]) } filterFuncs = append(filterFuncs, image.CreatedBeforeFilter(before.Created())) case "after": after, err := r.ImageRuntime().NewFromLocal(splitFilter[1]) if err != nil { - return nil, errors.Wrapf(err, "unable to find image % in local stores", splitFilter[1]) + return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1]) } filterFuncs = append(filterFuncs, image.CreatedAfterFilter(after.Created())) case "dangling": diff --git a/cmd/podman/import.go b/cmd/podman/import.go index be516e4fa..144354fa6 100644 --- a/cmd/podman/import.go +++ b/cmd/podman/import.go @@ -139,7 +139,7 @@ func downloadFromURL(source string) (string, error) { _, err = io.Copy(outFile, response.Body) if err != nil { - return "", errors.Wrapf(err, "error saving %q to %q", source, outFile) + return "", errors.Wrapf(err, "error saving %s to %s", source, outFile.Name()) } return outFile.Name(), nil diff --git a/cmd/podman/info.go b/cmd/podman/info.go index 563e63ba3..c0639725e 100644 --- a/cmd/podman/info.go +++ b/cmd/podman/info.go @@ -81,6 +81,7 @@ func debugInfo(c *cli.Context) map[string]interface{} { info["compiler"] = runtime.Compiler info["go version"] = runtime.Version() info["podman version"] = c.App.Version - info["git commit"] = libpod.GitCommit + version, _ := libpod.GetVersion() + info["git commit"] = version.GitCommit return info } diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index bd9e8c13c..6ffcde55f 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -119,7 +119,7 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *l } libpodInspectData, err := ctr.Inspect(c.Bool("size")) if err != nil { - inspectError = errors.Wrapf(err, "error getting libpod container inspect data %q", ctr.ID) + inspectError = errors.Wrapf(err, "error getting libpod container inspect data %s", ctr.ID()) break } data, err = shared.GetCtrInspectInfo(ctr, libpodInspectData) @@ -154,12 +154,12 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *l } else { libpodInspectData, err := ctr.Inspect(c.Bool("size")) if err != nil { - inspectError = errors.Wrapf(err, "error getting libpod container inspect data %q", ctr.ID) + inspectError = errors.Wrapf(err, "error getting libpod container inspect data %s", ctr.ID()) break } data, err = shared.GetCtrInspectInfo(ctr, libpodInspectData) if err != nil { - inspectError = errors.Wrapf(err, "error parsing container data %q", ctr.ID) + inspectError = errors.Wrapf(err, "error parsing container data %s", ctr.ID()) break } } diff --git a/cmd/podman/kill.go b/cmd/podman/kill.go index 7ca5bd7c5..cfe4b4218 100644 --- a/cmd/podman/kill.go +++ b/cmd/podman/kill.go @@ -1,15 +1,16 @@ package main import ( - "os" + "fmt" "syscall" - "fmt" "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/docker/docker/pkg/signal" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -41,6 +42,11 @@ var ( // killCmd kills one or more containers with a signal func killCmd(c *cli.Context) error { + var ( + killFuncs []shared.ParallelWorkerInput + killSignal uint = uint(syscall.SIGTERM) + ) + if err := checkAllAndLatest(c); err != nil { return err } @@ -56,7 +62,6 @@ func killCmd(c *cli.Context) error { } defer runtime.Shutdown(false) - var killSignal uint = uint(syscall.SIGTERM) if c.String("signal") != "" { // Check if the signalString provided by the user is valid // Invalid signals will return err @@ -67,17 +72,32 @@ func killCmd(c *cli.Context) error { killSignal = uint(sysSignal) } - containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + if err != nil { + if len(containers) == 0 { + return err + } + fmt.Println(err.Error()) + } for _, ctr := range containers { - if err := ctr.Kill(killSignal); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "unable to find container %v", ctr.ID()) - } else { - fmt.Println(ctr.ID()) + con := ctr + f := func() error { + return con.Kill(killSignal) } + + killFuncs = append(killFuncs, shared.ParallelWorkerInput{ + ContainerID: con.ID(), + ParallelFunc: f, + }) } - return lastError + + maxWorkers := shared.Parallelize("kill") + if c.GlobalIsSet("max-workers") { + maxWorkers = c.GlobalInt("max-workers") + } + logrus.Debugf("Setting maximum workers to %d", maxWorkers) + + killErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, killFuncs) + return printParallelOutput(killErrors, errCount) } diff --git a/cmd/podman/kube.go b/cmd/podman/kube.go new file mode 100644 index 000000000..2cb407c09 --- /dev/null +++ b/cmd/podman/kube.go @@ -0,0 +1,23 @@ +package main + +import ( + "github.com/urfave/cli" +) + +var ( + kubeSubCommands = []cli.Command{ + containerKubeCommand, + } + + kubeDescription = "Work with Kubernetes objects" + kubeCommand = cli.Command{ + Name: "kube", + Usage: "Import and export Kubernetes objections from and to Podman", + Description: containerDescription, + ArgsUsage: "", + Subcommands: kubeSubCommands, + UseShortOptionHandling: true, + OnUsageError: usageErrorHandler, + Hidden: true, + } +) diff --git a/cmd/podman/kube_generate.go b/cmd/podman/kube_generate.go new file mode 100644 index 000000000..a18912668 --- /dev/null +++ b/cmd/podman/kube_generate.go @@ -0,0 +1,93 @@ +package main + +import ( + "fmt" + + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/rootless" + "github.com/ghodss/yaml" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +var ( + containerKubeFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "service, s", + Usage: "only generate YAML for kubernetes service object", + }, + LatestFlag, + } + containerKubeDescription = "Generate Kubernetes Pod YAML" + containerKubeCommand = cli.Command{ + Name: "generate", + Usage: "Generate Kubernetes pod YAML for a container", + Description: containerKubeDescription, + Flags: sortFlags(containerKubeFlags), + Action: generateKubeYAMLCmd, + ArgsUsage: "CONTAINER-NAME", + UseShortOptionHandling: true, + OnUsageError: usageErrorHandler, + } +) + +// generateKubeYAMLCmdgenerates or replays kube +func generateKubeYAMLCmd(c *cli.Context) error { + var ( + container *libpod.Container + err error + output []byte + ) + + if rootless.IsRootless() { + return errors.Wrapf(libpod.ErrNotImplemented, "rootless users") + } + args := c.Args() + if len(args) > 1 || (len(args) < 1 && !c.Bool("latest")) { + return errors.Errorf("you must provide one container ID or name or --latest") + } + if c.Bool("service") { + return errors.Wrapf(libpod.ErrNotImplemented, "service generation") + } + + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + // Get the container in question + if c.Bool("latest") { + container, err = runtime.GetLatestContainer() + } else { + container, err = runtime.LookupContainer(args[0]) + } + if err != nil { + return err + } + + if len(container.Dependencies()) > 0 { + return errors.Wrapf(libpod.ErrNotImplemented, "containers with dependencies") + } + + podYAML, err := container.InspectForKube() + if err != nil { + return err + } + + developmentComment := []byte("# Generation of Kubenetes YAML is still under development!\n") + logrus.Warn("This function is still under heavy development.") + // Marshall the results + b, err := yaml.Marshal(podYAML) + if err != nil { + return err + } + output = append(output, developmentComment...) + output = append(output, b...) + // Output the v1.Pod with the v1.Container + fmt.Println(string(output)) + + return nil +} diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index df422eb81..0dc6bcf18 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -5,43 +5,33 @@ import ( "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" "github.com/containers/storage" + "github.com/pkg/errors" "github.com/urfave/cli" ) // GetRuntime generates a new libpod runtime configured by command line options func GetRuntime(c *cli.Context) (*libpod.Runtime, error) { - storageOpts, err := util.GetDefaultStoreOptions() - if err != nil { - return nil, err - } - return GetRuntimeWithStorageOpts(c, &storageOpts) -} - -// GetContainerRuntime generates a new libpod runtime configured by command line options for containers -func GetContainerRuntime(c *cli.Context) (*libpod.Runtime, error) { - mappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidmap"), c.String("subgidmap")) - if err != nil { - return nil, err - } - storageOpts, err := util.GetDefaultStoreOptions() - if err != nil { - return nil, err - } - storageOpts.UIDMap = mappings.UIDMap - storageOpts.GIDMap = mappings.GIDMap - return GetRuntimeWithStorageOpts(c, &storageOpts) -} - -// GetRuntime generates a new libpod runtime configured by command line options -func GetRuntimeWithStorageOpts(c *cli.Context, storageOpts *storage.StoreOptions) (*libpod.Runtime, error) { + storageOpts := new(storage.StoreOptions) options := []libpod.RuntimeOption{} + 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")) + 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.GlobalIsSet("runroot") { storageOpts.RunRoot = c.GlobalString("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") } @@ -86,8 +76,8 @@ func GetRuntimeWithStorageOpts(c *cli.Context, storageOpts *storage.StoreOptions if c.GlobalIsSet("default-mounts-file") { options = append(options, libpod.WithDefaultMountsFile(c.GlobalString("default-mounts-file"))) } - if c.GlobalIsSet("hooks-dir-path") { - options = append(options, libpod.WithHooksDir(c.GlobalString("hooks-dir-path"))) + if c.GlobalIsSet("hooks-dir") { + options = append(options, libpod.WithHooksDir(c.GlobalStringSlice("hooks-dir")...)) } // TODO flag to set CNI plugins dir? diff --git a/cmd/podman/logs.go b/cmd/podman/logs.go index 84aca5e61..75947c34e 100644 --- a/cmd/podman/logs.go +++ b/cmd/podman/logs.go @@ -40,14 +40,15 @@ var ( 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, + Name: "logs", + Usage: "Fetch the logs of a container", + Description: logsDescription, + Flags: sortFlags(logsFlags), + Action: logsCmd, + ArgsUsage: "CONTAINER", + SkipArgReorder: true, + OnUsageError: usageErrorHandler, + UseShortOptionHandling: true, } ) diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 38eac4504..bcae04575 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -8,7 +8,6 @@ import ( "syscall" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/hooks" _ "github.com/containers/libpod/pkg/hooks/0.1.0" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/version" @@ -77,6 +76,7 @@ func main() { infoCommand, inspectCommand, killCommand, + kubeCommand, loadCommand, loginCommand, logoutCommand, @@ -205,11 +205,9 @@ func main() { Usage: "path to default mounts file", Hidden: true, }, - cli.StringFlag{ - Name: "hooks-dir-path", - Usage: "set the OCI hooks directory path", - Value: hooks.DefaultDir, - Hidden: true, + cli.StringSliceFlag{ + Name: "hooks-dir", + Usage: "set the OCI hooks directory path (may be set multiple times)", }, cli.IntFlag{ Name: "max-workers", diff --git a/cmd/podman/pause.go b/cmd/podman/pause.go index 203fa6070..fcb2f3cb8 100644 --- a/cmd/podman/pause.go +++ b/cmd/podman/pause.go @@ -1,15 +1,23 @@ package main import ( - "fmt" "os" "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" ) var ( + pauseFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "pause all running containers", + }, + } pauseDescription = ` podman pause @@ -19,6 +27,7 @@ var ( Name: "pause", Usage: "Pauses all the processes in one or more containers", Description: pauseDescription, + Flags: pauseFlags, Action: pauseCmd, ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", OnUsageError: usageErrorHandler, @@ -26,6 +35,10 @@ var ( ) func pauseCmd(c *cli.Context) error { + var ( + pauseContainers []*libpod.Container + pauseFuncs []shared.ParallelWorkerInput + ) if os.Geteuid() != 0 { return errors.New("pause is not supported for rootless containers") } @@ -37,28 +50,44 @@ func pauseCmd(c *cli.Context) error { defer runtime.Shutdown(false) args := c.Args() - if len(args) < 1 { + if len(args) < 1 && !c.Bool("all") { return errors.Errorf("you must provide at least one container name or id") } - - var lastError error - for _, arg := range args { - ctr, err := runtime.LookupContainer(arg) + if c.Bool("all") { + containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "error looking up container %q", arg) - continue + return err } - if err = ctr.Pause(); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) + pauseContainers = append(pauseContainers, containers...) + } else { + for _, arg := range args { + ctr, err := runtime.LookupContainer(arg) + if err != nil { + return err } - lastError = errors.Wrapf(err, "failed to pause container %v", ctr.ID()) - } else { - fmt.Println(ctr.ID()) + pauseContainers = append(pauseContainers, ctr) + } + } + + // Now assemble the slice of pauseFuncs + for _, ctr := range pauseContainers { + con := ctr + + f := func() error { + return con.Pause() } + pauseFuncs = append(pauseFuncs, shared.ParallelWorkerInput{ + ContainerID: con.ID(), + ParallelFunc: f, + }) } - return lastError + + maxWorkers := shared.Parallelize("pause") + if c.GlobalIsSet("max-workers") { + maxWorkers = c.GlobalInt("max-workers") + } + logrus.Debugf("Setting maximum workers to %d", maxWorkers) + + pauseErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, pauseFuncs) + return printParallelOutput(pauseErrors, errCount) } diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go index 0c6ec5e8c..a30361134 100644 --- a/cmd/podman/pod.go +++ b/cmd/podman/pod.go @@ -11,6 +11,7 @@ Pods are a group of one or more containers sharing the same network, pid and ipc ` podSubCommands = []cli.Command{ podCreateCommand, + podExistsCommand, podInspectCommand, podKillCommand, podPauseCommand, diff --git a/cmd/podman/pod_create.go b/cmd/podman/pod_create.go index 63fa6b294..a3364ac4b 100644 --- a/cmd/podman/pod_create.go +++ b/cmd/podman/pod_create.go @@ -3,11 +3,15 @@ package main import ( "fmt" "os" + "strconv" "strings" "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/cri-o/ocicni/pkg/ocicni" + "github.com/docker/go-connections/nat" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -58,6 +62,10 @@ var podCreateFlags = []cli.Flag{ 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", @@ -102,6 +110,16 @@ func podCreateCmd(c *cli.Context) error { defer podIdFile.Close() defer podIdFile.Sync() } + + if len(c.StringSlice("publish")) > 0 { + if !c.BoolT("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") != "" { return errors.Errorf("You cannot share kernel namespaces on the pod level without an infra container") } @@ -131,6 +149,14 @@ func podCreateCmd(c *cli.Context) error { options = append(options, nsOptions...) } + if len(c.StringSlice("publish")) > 0 { + portBindings, err := CreatePortBindings(c.StringSlice("publish")) + if err != nil { + return err + } + options = append(options, libpod.WithInfraContainerPorts(portBindings)) + + } // always have containers use pod cgroups // User Opt out is not yet supported options = append(options, libpod.WithPodCgroups()) @@ -152,3 +178,36 @@ func podCreateCmd(c *cli.Context) error { return nil } + +// CreatePortBindings iterates ports mappings and exposed ports into a format CNI understands +func CreatePortBindings(ports []string) ([]ocicni.PortMapping, error) { + 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() + } else { + hostPort, err = strconv.Atoi(i.HostPort) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert host port to integer") + } + } + + pm.HostPort = int32(hostPort) + pm.Protocol = containerPb.Proto() + portBindings = append(portBindings, pm) + } + } + return portBindings, nil +} diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go index 83274c9a8..0b03388a2 100644 --- a/cmd/podman/ps.go +++ b/cmd/podman/ps.go @@ -184,7 +184,7 @@ var ( Usage: "Display the extended information", }, cli.BoolFlag{ - Name: "pod", + Name: "pod, p", Usage: "Print the ID and name of the pod the containers are associated with", }, cli.BoolFlag{ diff --git a/cmd/podman/restart.go b/cmd/podman/restart.go index 7b48ef24e..630493ef4 100644 --- a/cmd/podman/restart.go +++ b/cmd/podman/restart.go @@ -1,18 +1,24 @@ package main import ( - "context" - "fmt" - "os" - "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" ) 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", + }, cli.UintFlag{ Name: "timeout, time, t", Usage: "Seconds to wait for stop before killing the container", @@ -35,11 +41,18 @@ var ( ) func restartCmd(c *cli.Context) error { + var ( + restartFuncs []shared.ParallelWorkerInput + containers []*libpod.Container + restartContainers []*libpod.Container + ) + args := c.Args() - if len(args) < 1 && !c.Bool("latest") { + runOnly := c.Bool("running") + all := c.Bool("all") + if len(args) < 1 && !c.Bool("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 } @@ -50,8 +63,6 @@ func restartCmd(c *cli.Context) error { } defer runtime.Shutdown(false) - var lastError error - timeout := c.Uint("timeout") useTimeout := c.IsSet("timeout") @@ -59,39 +70,57 @@ func restartCmd(c *cli.Context) error { if c.Bool("latest") { lastCtr, err := runtime.GetLatestContainer() if err != nil { - lastError = errors.Wrapf(err, "unable to get latest container") - } else { - ctrTimeout := lastCtr.StopTimeout() - if useTimeout { - ctrTimeout = timeout - } - - lastError = lastCtr.RestartWithTimeout(context.TODO(), ctrTimeout) + return errors.Wrapf(err, "unable to get latest container") } - } - - for _, id := range args { - ctr, err := runtime.LookupContainer(id) + restartContainers = append(restartContainers, lastCtr) + } else if runOnly { + containers, err = getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) + return err + } + restartContainers = append(restartContainers, containers...) + } else if all { + containers, err = runtime.GetAllContainers() + if err != nil { + return err + } + restartContainers = append(restartContainers, containers...) + } else { + for _, id := range args { + ctr, err := runtime.LookupContainer(id) + if err != nil { + return err } - lastError = errors.Wrapf(err, "unable to find container %s", id) - continue + restartContainers = append(restartContainers, ctr) } + } + // We now have a slice of all the containers to be restarted. Iterate them to + // create restart Funcs with a timeout as needed + for _, ctr := range restartContainers { + con := ctr ctrTimeout := ctr.StopTimeout() if useTimeout { ctrTimeout = timeout } - if err := ctr.RestartWithTimeout(context.TODO(), ctrTimeout); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "error restarting container %s", ctr.ID()) + f := func() error { + return con.RestartWithTimeout(getContext(), ctrTimeout) } + + restartFuncs = append(restartFuncs, shared.ParallelWorkerInput{ + ContainerID: con.ID(), + ParallelFunc: f, + }) + } + + maxWorkers := shared.Parallelize("restart") + if c.GlobalIsSet("max-workers") { + maxWorkers = c.GlobalInt("max-workers") } - return lastError + logrus.Debugf("Setting maximum workers to %d", maxWorkers) + + restartErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, restartFuncs) + return printParallelOutput(restartErrors, errCount) } diff --git a/cmd/podman/restore.go b/cmd/podman/restore.go index 067a2b5d4..bc2a71ba0 100644 --- a/cmd/podman/restore.go +++ b/cmd/podman/restore.go @@ -27,6 +27,10 @@ var ( // dedicated state for container which are checkpointed. // TODO: add ContainerStateCheckpointed cli.BoolFlag{ + Name: "tcp-established", + Usage: "checkpoint a container with established TCP connections", + }, + cli.BoolFlag{ Name: "all, a", Usage: "restore all checkpointed containers", }, @@ -53,16 +57,19 @@ func restoreCmd(c *cli.Context) error { } defer runtime.Shutdown(false) - keep := c.Bool("keep") + options := libpod.ContainerCheckpointOptions{ + Keep: c.Bool("keep"), + TCPEstablished: c.Bool("tcp-established"), + } if err := checkAllAndLatest(c); err != nil { return err } - containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "checkpointed") + containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateExited, "checkpointed") for _, ctr := range containers { - if err = ctr.Restore(context.TODO(), keep); err != nil { + if err = ctr.Restore(context.TODO(), options); err != nil { if lastError != nil { fmt.Fprintln(os.Stderr, lastError) } diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go index 0fb5345ee..7c0569b78 100644 --- a/cmd/podman/rm.go +++ b/cmd/podman/rm.go @@ -4,7 +4,6 @@ import ( "fmt" "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" @@ -46,9 +45,7 @@ Running containers will not be removed without the -f option. // saveCmd saves the image to either docker-archive or oci func rmCmd(c *cli.Context) error { var ( - delContainers []*libpod.Container - lastError error - deleteFuncs []shared.ParallelWorkerInput + deleteFuncs []shared.ParallelWorkerInput ) ctx := getContext() @@ -65,7 +62,13 @@ func rmCmd(c *cli.Context) error { return err } - delContainers, lastError = getAllOrLatestContainers(c, runtime, -1, "all") + delContainers, err := getAllOrLatestContainers(c, runtime, -1, "all") + if err != nil { + if len(delContainers) == 0 { + return err + } + fmt.Println(err.Error()) + } for _, container := range delContainers { con := container @@ -84,14 +87,7 @@ func rmCmd(c *cli.Context) error { } logrus.Debugf("Setting maximum workers to %d", maxWorkers) - deleteErrors := shared.ParallelExecuteWorkerPool(maxWorkers, deleteFuncs) - for cid, result := range deleteErrors { - if result != nil { - fmt.Println(result.Error()) - lastError = result - continue - } - fmt.Println(cid) - } - return lastError + // Run the parallel funcs + deleteErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, deleteFuncs) + return printParallelOutput(deleteErrors, errCount) } diff --git a/cmd/podman/rmi.go b/cmd/podman/rmi.go index c0a0d69df..0f4f8765b 100644 --- a/cmd/podman/rmi.go +++ b/cmd/podman/rmi.go @@ -91,8 +91,23 @@ func rmiCmd(c *cli.Context) error { if err != nil { return errors.Wrapf(err, "unable to query local images") } - for _, i := range imagesToDelete { - removeImage(i) + lastNumberofImages := 0 + for len(imagesToDelete) > 0 { + if lastNumberofImages == len(imagesToDelete) { + return errors.New("unable to delete all images; re-run the rmi command again.") + } + for _, i := range imagesToDelete { + isParent, err := i.IsParent() + if err != nil { + return err + } + if isParent { + continue + } + removeImage(i) + } + lastNumberofImages = len(imagesToDelete) + imagesToDelete, err = runtime.ImageRuntime().GetImages() } } else { // Create image.image objects for deletion from user input. diff --git a/cmd/podman/run.go b/cmd/podman/run.go index e4b25eaf4..a4b5c918e 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -44,7 +44,7 @@ func runCmd(c *cli.Context) error { rootless.SetSkipStorageSetup(true) } - runtime, err := libpodruntime.GetContainerRuntime(c) + runtime, err := libpodruntime.GetRuntime(c) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } @@ -96,8 +96,6 @@ func runCmd(c *cli.Context) error { inputStream = nil } - inputStream = nil - attachTo := c.StringSlice("attach") for _, stream := range attachTo { switch strings.ToLower(stream) { diff --git a/cmd/podman/runlabel.go b/cmd/podman/runlabel.go index e1dee1fb2..b0d87d0d9 100644 --- a/cmd/podman/runlabel.go +++ b/cmd/podman/runlabel.go @@ -6,11 +6,9 @@ import ( "os" "strings" - "github.com/containers/image/types" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/util" "github.com/containers/libpod/utils" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -94,7 +92,7 @@ func runlabelCmd(c *cli.Context) error { imageName string stdErr, stdOut io.Writer stdIn io.Reader - newImage *image.Image + extraArgs []string ) // Evil images could trick into recursively executing the runlabel @@ -124,6 +122,9 @@ func runlabelCmd(c *cli.Context) error { return errors.Errorf("the display and quiet flags cannot be used together.") } + if len(args) > 2 { + extraArgs = args[2:] + } pull := c.Bool("pull") label := args[0] @@ -151,75 +152,24 @@ func runlabelCmd(c *cli.Context) error { stdIn = nil } - if pull { - var registryCreds *types.DockerAuthConfig - if c.IsSet("creds") { - creds, err := util.ParseRegistryCreds(c.String("creds")) - if err != nil { - return err - } - registryCreds = creds - } - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, - DockerCertPath: c.String("cert-dir"), - DockerInsecureSkipTLSVerify: !c.BoolT("tls-verify"), - } - authfile := getAuthFile(c.String("authfile")) - - newImage, err = runtime.ImageRuntime().New(ctx, runlabelImage, c.String("signature-policy"), authfile, stdOut, &dockerRegistryOptions, image.SigningOptions{}, false, false) - } else { - newImage, err = runtime.ImageRuntime().NewFromLocal(runlabelImage) - } - if err != nil { - return errors.Wrapf(err, "unable to find image") - } - - if len(newImage.Names()) < 1 { - imageName = newImage.ID() - } else { - imageName = newImage.Names()[0] + dockerRegistryOptions := image.DockerRegistryOptions{ + DockerCertPath: c.String("cert-dir"), + DockerInsecureSkipTLSVerify: !c.BoolT("tls-verify"), } - runLabel, err := newImage.GetLabel(ctx, label) + 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) if err != nil { return err } - - // If no label to execute, we return if runLabel == "" { return nil } - // The user provided extra arguments that need to be tacked onto the label's command - if len(args) > 2 { - runLabel = fmt.Sprintf("%s %s", runLabel, strings.Join(args[2:], " ")) - } - - cmd, err := shared.GenerateCommand(runLabel, imageName, c.String("name")) + cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, c.String("name"), opts, extraArgs) if err != nil { - return errors.Wrapf(err, "unable to generate command") - } - env := shared.GenerateRunEnvironment(c.String("name"), imageName, opts) - env = append(env, "PODMAN_RUNLABEL_NESTED=1") - - envmap := envSliceToMap(env) - - envmapper := func(k string) string { - switch k { - case "OPT1": - return envmap["OPT1"] - case "OPT2": - return envmap["OPT2"] - case "OPT3": - return envmap["OPT3"] - } - return "" + return err } - - newS := os.Expand(strings.Join(cmd, " "), envmapper) - cmd = strings.Split(newS, " ") - if !c.Bool("quiet") { fmt.Printf("Command: %s\n", strings.Join(cmd, " ")) if c.Bool("display") { @@ -228,12 +178,3 @@ func runlabelCmd(c *cli.Context) error { } return utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...) } - -func envSliceToMap(env []string) map[string]string { - m := make(map[string]string) - for _, i := range env { - split := strings.Split(i, "=") - m[split[0]] = strings.Join(split[1:], " ") - } - return m -} diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index 4404268d4..d0e892961 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -1,10 +1,15 @@ package shared import ( + "context" "encoding/json" "fmt" + "github.com/containers/image/types" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/util" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-units" + "io" "os" "path/filepath" "regexp" @@ -589,3 +594,79 @@ func portsToString(ports []ocicni.PortMapping) string { } return strings.Join(portDisplay, ", ") } + +// GetRunlabel is a helper function for runlabel; it gets the image if needed and begins the +// contruction of the runlabel output and environment variables +func GetRunlabel(label string, runlabelImage string, ctx context.Context, runtime *libpod.Runtime, pull bool, inputCreds string, dockerRegistryOptions image.DockerRegistryOptions, authfile string, signaturePolicyPath string, output io.Writer) (string, string, error) { + var ( + newImage *image.Image + err error + imageName string + ) + if pull { + var registryCreds *types.DockerAuthConfig + if inputCreds != "" { + creds, err := util.ParseRegistryCreds(inputCreds) + if err != nil { + return "", "", err + } + registryCreds = creds + } + dockerRegistryOptions.DockerRegistryCreds = registryCreds + newImage, err = runtime.ImageRuntime().New(ctx, runlabelImage, signaturePolicyPath, authfile, output, &dockerRegistryOptions, image.SigningOptions{}, false, false) + } else { + newImage, err = runtime.ImageRuntime().NewFromLocal(runlabelImage) + } + if err != nil { + return "", "", errors.Wrapf(err, "unable to find image") + } + + if len(newImage.Names()) < 1 { + imageName = newImage.ID() + } else { + imageName = newImage.Names()[0] + } + + runLabel, err := newImage.GetLabel(ctx, label) + return runLabel, imageName, err +} + +// GenerateRunlabelCommand generates the command that will eventually be execucted by podman +func GenerateRunlabelCommand(runLabel, imageName, name string, opts map[string]string, extraArgs []string) ([]string, []string, error) { + // The user provided extra arguments that need to be tacked onto the label's command + if len(extraArgs) > 0 { + runLabel = fmt.Sprintf("%s %s", runLabel, strings.Join(extraArgs, " ")) + } + cmd, err := GenerateCommand(runLabel, imageName, name) + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to generate command") + } + env := GenerateRunEnvironment(name, imageName, opts) + env = append(env, "PODMAN_RUNLABEL_NESTED=1") + + envmap := envSliceToMap(env) + + envmapper := func(k string) string { + switch k { + case "OPT1": + return envmap["OPT1"] + case "OPT2": + return envmap["OPT2"] + case "OPT3": + return envmap["OPT3"] + } + return "" + } + newS := os.Expand(strings.Join(cmd, " "), envmapper) + cmd = strings.Split(newS, " ") + return cmd, env, nil +} + +func envSliceToMap(env []string) map[string]string { + m := make(map[string]string) + for _, i := range env { + split := strings.Split(i, "=") + m[split[0]] = strings.Join(split[1:], " ") + } + return m +} diff --git a/cmd/podman/shared/funcs.go b/cmd/podman/shared/funcs.go index a92e0d547..8520c0616 100644 --- a/cmd/podman/shared/funcs.go +++ b/cmd/podman/shared/funcs.go @@ -5,6 +5,8 @@ import ( "os" "path/filepath" "strings" + + "github.com/google/shlex" ) func substituteCommand(cmd string) (string, error) { @@ -42,7 +44,11 @@ func GenerateCommand(command, imageName, name string) ([]string, error) { if name == "" { name = imageName } - cmd := strings.Split(command, " ") + + cmd, err := shlex.Split(command) + if err != nil { + return nil, err + } prog, err := substituteCommand(cmd[0]) if err != nil { diff --git a/cmd/podman/shared/funcs_test.go b/cmd/podman/shared/funcs_test.go index 596df84e8..7506b9d9c 100644 --- a/cmd/podman/shared/funcs_test.go +++ b/cmd/podman/shared/funcs_test.go @@ -18,10 +18,11 @@ var ( ) func TestGenerateCommand(t *testing.T) { - inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install" - correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install" + inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo \"hello world\"" + correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo hello world" newCommand, err := GenerateCommand(inputCommand, "foo", "bar") assert.Nil(t, err) + assert.Equal(t, "hello world", newCommand[11]) assert.Equal(t, correctCommand, strings.Join(newCommand, " ")) } @@ -108,8 +109,8 @@ func TestGenerateCommandNoSetName(t *testing.T) { } func TestGenerateCommandNoName(t *testing.T) { - inputCommand := "docker run -it -e IMAGE=IMAGE IMAGE echo install" - correctCommand := "/proc/self/exe run -it -e IMAGE=foo foo echo install" + inputCommand := "docker run -it -e IMAGE=IMAGE IMAGE echo install" + correctCommand := "/proc/self/exe run -it -e IMAGE=foo foo echo install" newCommand, err := GenerateCommand(inputCommand, "foo", "") assert.Nil(t, err) assert.Equal(t, correctCommand, strings.Join(newCommand, " ")) diff --git a/cmd/podman/shared/parallel.go b/cmd/podman/shared/parallel.go index 03eba2f0b..e6ce50f95 100644 --- a/cmd/podman/shared/parallel.go +++ b/cmd/podman/shared/parallel.go @@ -30,9 +30,10 @@ func ParallelWorker(wg *sync.WaitGroup, jobs <-chan ParallelWorkerInput, results // ParallelExecuteWorkerPool takes container jobs and performs them in parallel. The worker // int determines how many workers/threads should be premade. -func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) map[string]error { +func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) (map[string]error, int) { var ( - wg sync.WaitGroup + wg sync.WaitGroup + errorCount int ) resultChan := make(chan containerError, len(functions)) @@ -62,9 +63,12 @@ func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) map close(resultChan) for ctrError := range resultChan { results[ctrError.ContainerID] = ctrError.Err + if ctrError.Err != nil { + errorCount += 1 + } } - return results + return results, errorCount } // Parallelize provides the maximum number of parallel workers (int) as calculated by a basic @@ -72,20 +76,37 @@ func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) map func Parallelize(job string) int { numCpus := runtime.NumCPU() switch job { + case "kill": + if numCpus <= 3 { + return numCpus * 3 + } + return numCpus * 4 + case "pause": + if numCpus <= 3 { + return numCpus * 3 + } + return numCpus * 4 + case "ps": + return 8 + case "restart": + return numCpus * 2 + case "rm": + if numCpus <= 3 { + return numCpus * 3 + } else { + return numCpus * 4 + } case "stop": if numCpus <= 2 { return 4 } else { return numCpus * 3 } - case "rm": + case "unpause": if numCpus <= 3 { return numCpus * 3 - } else { - return numCpus * 4 } - case "ps": - return 8 + return numCpus * 4 } return 3 } diff --git a/cmd/podman/stop.go b/cmd/podman/stop.go index afeb49f76..ade51705e 100644 --- a/cmd/podman/stop.go +++ b/cmd/podman/stop.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" @@ -59,7 +60,13 @@ func stopCmd(c *cli.Context) error { } defer runtime.Shutdown(false) - containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + if err != nil { + if len(containers) == 0 { + return err + } + fmt.Println(err.Error()) + } var stopFuncs []shared.ParallelWorkerInput for _, ctr := range containers { @@ -71,7 +78,11 @@ func stopCmd(c *cli.Context) error { stopTimeout = ctr.StopTimeout() } f := func() error { - return con.StopWithTimeout(stopTimeout) + if err := con.StopWithTimeout(stopTimeout); err != nil && errors.Cause(err) != libpod.ErrCtrStopped { + return err + } + return nil + } stopFuncs = append(stopFuncs, shared.ParallelWorkerInput{ ContainerID: con.ID(), @@ -85,15 +96,6 @@ func stopCmd(c *cli.Context) error { } logrus.Debugf("Setting maximum workers to %d", maxWorkers) - stopErrors := shared.ParallelExecuteWorkerPool(maxWorkers, stopFuncs) - - for cid, result := range stopErrors { - if result != nil && result != libpod.ErrCtrStopped { - fmt.Println(result.Error()) - lastError = result - continue - } - fmt.Println(cid) - } - return lastError + stopErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, stopFuncs) + return printParallelOutput(stopErrors, errCount) } diff --git a/cmd/podman/unpause.go b/cmd/podman/unpause.go index a792aaf6d..d77e056f8 100644 --- a/cmd/podman/unpause.go +++ b/cmd/podman/unpause.go @@ -1,15 +1,23 @@ package main import ( - "fmt" "os" "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" ) var ( + unpauseFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "unpause all paused containers", + }, + } unpauseDescription = ` podman unpause @@ -19,6 +27,7 @@ var ( Name: "unpause", Usage: "Unpause the processes in one or more containers", Description: unpauseDescription, + Flags: unpauseFlags, Action: unpauseCmd, ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", OnUsageError: usageErrorHandler, @@ -26,6 +35,10 @@ var ( ) func unpauseCmd(c *cli.Context) error { + var ( + unpauseContainers []*libpod.Container + unpauseFuncs []shared.ParallelWorkerInput + ) if os.Geteuid() != 0 { return errors.New("unpause is not supported for rootless containers") } @@ -37,28 +50,44 @@ func unpauseCmd(c *cli.Context) error { defer runtime.Shutdown(false) args := c.Args() - if len(args) < 1 { + if len(args) < 1 && !c.Bool("all") { return errors.Errorf("you must provide at least one container name or id") } - - var lastError error - for _, arg := range args { - ctr, err := runtime.LookupContainer(arg) + if c.Bool("all") { + cs, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStatePaused, "paused") if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "error looking up container %q", arg) - continue + return err } - if err = ctr.Unpause(); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) + unpauseContainers = append(unpauseContainers, cs...) + } else { + for _, arg := range args { + ctr, err := runtime.LookupContainer(arg) + if err != nil { + return err } - lastError = errors.Wrapf(err, "failed to unpause container %v", ctr.ID()) - } else { - fmt.Println(ctr.ID()) + unpauseContainers = append(unpauseContainers, ctr) + } + } + + // Assemble the unpause funcs + for _, ctr := range unpauseContainers { + con := ctr + f := func() error { + return con.Unpause() } + + unpauseFuncs = append(unpauseFuncs, shared.ParallelWorkerInput{ + ContainerID: con.ID(), + ParallelFunc: f, + }) } - return lastError + + maxWorkers := shared.Parallelize("unpause") + if c.GlobalIsSet("max-workers") { + maxWorkers = c.GlobalInt("max-workers") + } + logrus.Debugf("Setting maximum workers to %d", maxWorkers) + + unpauseErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, unpauseFuncs) + return printParallelOutput(unpauseErrors, errCount) } diff --git a/cmd/podman/utils.go b/cmd/podman/utils.go index afeccb668..5735156c2 100644 --- a/cmd/podman/utils.go +++ b/cmd/podman/utils.go @@ -207,3 +207,20 @@ func getPodsFromContext(c *cli.Context, r *libpod.Runtime) ([]*libpod.Pod, error } return pods, lastError } + +//printParallelOutput takes the map of parallel worker results and outputs them +// to stdout +func printParallelOutput(m map[string]error, errCount int) error { + var lastError error + for cid, result := range m { + if result != nil { + if errCount > 1 { + fmt.Println(result.Error()) + } + lastError = result + continue + } + fmt.Println(cid) + } + return lastError +} diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 4a4a1854c..b081b60a3 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -371,6 +371,22 @@ type PodContainerErrorData ( reason: string ) +# Runlabel describes the required input for container runlabel +type Runlabel( + image: string, + authfile: string, + certDir: string, + creds: string, + display: bool, + name: string, + pull: bool, + signaturePolicyPath: string, + tlsVerify: bool, + label: string, + extraArgs: []string, + opts: [string]string +) + # Ping provides a response for developers to ensure their varlink setup is working. # #### Example # ~~~ @@ -804,6 +820,42 @@ method TopPod() -> (notimplemented: NotImplemented) # ~~~ method GetPodStats(name: string) -> (pod: string, containers: []ContainerStats) +# ImageExists talks a full or partial image ID or name and returns an int as to whether +# the image exists in local storage. An int result of 0 means the image does exist in +# local storage; whereas 1 indicates the image does not exists in local storage. +method ImageExists(name: string) -> (exists: int) + +# ContainerExists takes a full or partial container ID or name and returns an int as to +# whether the container exists in local storage. A result of 0 means the container does +# exists; whereas a result of 1 means it could not be found. +method ContainerExists(name: string) -> (exists: int) + +# ContainerCheckPoint performs a checkpopint on a container by its name or full/partial container +# ID. On successful checkpoint, the id of the checkpointed container is returned. +method ContainerCheckpoint(name: string, keep: bool, leaveRunning: bool, tcpEstablished: bool) -> (id: string) + +# ContainerRestore restores a container that has been checkpointed. The container to be restored can +# be identified by its name or full/partial container ID. A successful restore will result in the return +# of the container's ID. +method ContainerRestore(name: string, keep: bool, tcpEstablished: bool) -> (id: string) + +# ContainerRunlabel runs executes a command as described by a given container image label. +method ContainerRunlabel(runlabel: Runlabel) -> () + +# ListContainerMounts gathers all the mounted container mount points and returns them as an array +# of strings +method ListContainerMounts() -> (mounts: []string) + +# MountContainer mounts a container by name or full/partial ID. Upon a successful mount, the destination +# mount is returned as a string. +method MountContainer(name: string) -> (path: string) + +# UnmountContainer umounts a container by its name or full/partial container ID. +method UnmountContainer(name: string, force: bool) -> () + +# This function is not implemented yet. +method ListContainerPorts(name: string) -> (notimplemented: NotImplemented) + # ImageNotFound means the image could not be found by the provided name or ID in local storage. error ImageNotFound (name: string) diff --git a/cmd/podman/version.go b/cmd/podman/version.go index f896229c4..d81deb696 100644 --- a/cmd/podman/version.go +++ b/cmd/podman/version.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/libpod" "github.com/pkg/errors" "github.com/urfave/cli" @@ -15,13 +16,26 @@ func versionCmd(c *cli.Context) error { if err != nil { errors.Wrapf(err, "unable to determine version") } + + versionOutputFormat := c.String("format") + if versionOutputFormat != "" { + var out formats.Writer + switch versionOutputFormat { + case formats.JSONString: + out = formats.JSONStruct{Output: output} + default: + out = formats.StdoutTemplate{Output: output, Template: versionOutputFormat} + } + formats.Writer(out).Out() + return nil + } fmt.Println("Version: ", output.Version) fmt.Println("Go Version: ", output.GoVersion) if output.GitCommit != "" { fmt.Println("Git Commit: ", output.GitCommit) } // Prints out the build time in readable format - if libpod.BuildInfo != "" { + if output.Built != 0 { fmt.Println("Built: ", time.Unix(output.Built, 0).Format(time.ANSIC)) } @@ -30,8 +44,17 @@ func versionCmd(c *cli.Context) error { } // Cli command to print out the full version of podman -var versionCommand = cli.Command{ - Name: "version", - Usage: "Display the PODMAN Version Information", - Action: versionCmd, -} +var ( + versionCommand = cli.Command{ + Name: "version", + Usage: "Display the Podman Version Information", + Action: versionCmd, + Flags: versionFlags, + } + versionFlags = []cli.Flag{ + cli.StringFlag{ + Name: "format", + Usage: "Change the output format to JSON or a Go template", + }, + } +) |