diff options
Diffstat (limited to 'cmd/podman')
237 files changed, 8758 insertions, 19159 deletions
diff --git a/cmd/podman/README.md b/cmd/podman/README.md index 937eef510..c1b8f48a7 100644 --- a/cmd/podman/README.md +++ b/cmd/podman/README.md @@ -1,15 +1,113 @@ -# Podman - Simple debugging tool for pods and images -Podman is a daemonless container runtime for managing containers, pods, and container images. -It is intended as a counterpart to CRI-O, to provide low-level debugging not available through the CRI interface used by Kubernetes. -It can also act as a container runtime independent of CRI-O, creating and managing its own set of containers. - -## Use cases -1. Create containers -2. Start, stop, signal, attach to, and inspect existing containers -3. Run new commands in existing containers -4. Push and pull images -5. List and inspect existing images -6. Create new images by committing changes within a container -7. Create pods -8. Start, stop, signal, and inspect existing pods -9. Populate pods with containers +# Adding a podman V2 commands + +## Build podman V2 + +```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. + +## Adding a new command `podman manifests` +```shell script +$ mkdir -p $GOPATH/src/github.com/containers/libpod/cmd/podmanV2/manifests +``` +Create the file ```$GOPATH/src/github.com/containers/libpod/cmd/podmanV2/manifests/manifest.go``` +```go +package manifests + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + // podman _manifests_ + manifestCmd = &cobra.Command{ + Use: "manifest", + Short: "Manage manifests", + Long: "Manage manifests", + Example: "podman manifests IMAGE", + TraverseChildren: true, + PersistentPreRunE: preRunE, + RunE: registry.SubCommandExists, // Report error if there is no sub command given + } +) +func init() { + // Subscribe command to podman + registry.Commands = append(registry.Commands, registry.CliCommand{ + // _podman manifest_ will support both ABIMode and TunnelMode + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + // 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: +```go +package main + +import _ "github.com/containers/libpod/cmd/podman/manifests" +``` + +## Adding a new sub command `podman manifests list` +Create the file ```$GOPATH/src/github.com/containers/libpod/cmd/podmanV2/manifests/inspect.go``` +```go +package manifests + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + // podman manifests _inspect_ + inspectCmd = &cobra.Command{ + Use: "inspect IMAGE", + 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", + } +) + +func init() { + // Subscribe inspect sub command to manifest command + registry.Commands = append(registry.Commands, registry.CliCommand{ + // _podman manifest inspect_ will support both ABIMode and TunnelMode + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + // The definition for this command + Command: inspectCmd, + Parent: manifestCmd, + }) + + // This is where you would configure the cobra flags using inspectCmd.Flags() +} + +// 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 + // Do not pull from libpod directly use the domain objects and types + return nil +} +``` diff --git a/cmd/podman/attach.go b/cmd/podman/attach.go deleted file mode 100644 index 6f08cc396..000000000 --- a/cmd/podman/attach.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - attachCommand cliconfig.AttachValues - attachDescription = "The podman attach command allows you to attach to a running container using the container's ID or name, either to view its ongoing output or to control it interactively." - _attachCommand = &cobra.Command{ - Use: "attach [flags] CONTAINER", - Short: "Attach to a running container", - Long: attachDescription, - RunE: func(cmd *cobra.Command, args []string) error { - attachCommand.InputArgs = args - attachCommand.GlobalFlags = MainGlobalOpts - attachCommand.Remote = remoteclient - return attachCmd(&attachCommand) - }, - Example: `podman attach ctrID - podman attach 1234 - podman attach --no-stdin foobar`, - } -) - -func init() { - attachCommand.Command = _attachCommand - attachCommand.SetHelpTemplate(HelpTemplate()) - attachCommand.SetUsageTemplate(UsageTemplate()) - flags := attachCommand.Flags() - flags.StringVar(&attachCommand.DetachKeys, "detach-keys", getDefaultDetachKeys(), "Select the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`") - flags.BoolVar(&attachCommand.NoStdin, "no-stdin", false, "Do not attach STDIN. The default is false") - flags.BoolVar(&attachCommand.SigProxy, "sig-proxy", true, "Proxy received signals to the process") - flags.BoolVarP(&attachCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - markFlagHiddenForRemoteClient("latest", flags) - // TODO allow for passing of a new detach keys - markFlagHiddenForRemoteClient("detach-keys", flags) -} - -func attachCmd(c *cliconfig.AttachValues) error { - if len(c.InputArgs) > 1 || (len(c.InputArgs) == 0 && !c.Latest) { - return errors.Errorf("attach requires the name or id of one running container or the latest flag") - } - if remoteclient && len(c.InputArgs) != 1 { - return errors.Errorf("attach requires the name or id of one running container") - } - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating runtime") - } - defer runtime.DeferredShutdown(false) - return runtime.Attach(getContext(), c) -} diff --git a/cmd/podman/autoupdate.go b/cmd/podman/autoupdate.go deleted file mode 100644 index 2cc1ae72e..000000000 --- a/cmd/podman/autoupdate.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - autoUpdateCommand cliconfig.AutoUpdateValues - autoUpdateDescription = `Auto update containers according to their auto-update policy. - -Auto-update policies are specified with the "io.containers.autoupdate" label.` - _autoUpdateCommand = &cobra.Command{ - Use: "auto-update [flags]", - Short: "Auto update containers according to their auto-update policy", - Args: noSubArgs, - Long: autoUpdateDescription, - RunE: func(cmd *cobra.Command, args []string) error { - restartCommand.InputArgs = args - restartCommand.GlobalFlags = MainGlobalOpts - return autoUpdateCmd(&restartCommand) - }, - Example: `podman auto-update`, - } -) - -func init() { - autoUpdateCommand.Command = _autoUpdateCommand - autoUpdateCommand.SetHelpTemplate(HelpTemplate()) - autoUpdateCommand.SetUsageTemplate(UsageTemplate()) -} - -func autoUpdateCmd(c *cliconfig.RestartValues) error { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - units, failures := runtime.AutoUpdate() - for _, unit := range units { - fmt.Println(unit) - } - var finalErr error - if len(failures) > 0 { - finalErr = failures[0] - for _, e := range failures[1:] { - finalErr = errors.Errorf("%v\n%v", finalErr, e) - } - } - return finalErr -} diff --git a/cmd/podman/build.go b/cmd/podman/build.go deleted file mode 100644 index 04bc56ab0..000000000 --- a/cmd/podman/build.go +++ /dev/null @@ -1,432 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/containers/buildah" - "github.com/containers/buildah/imagebuildah" - buildahcli "github.com/containers/buildah/pkg/cli" - "github.com/containers/buildah/pkg/parse" - "github.com/containers/common/pkg/config" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/docker/go-units" - "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/spf13/pflag" -) - -var ( - buildCommand cliconfig.BuildValues - buildDescription = "Builds an OCI or Docker image using instructions from one or more Containerfiles and a specified build context directory." - layerValues buildahcli.LayerResults - budFlagsValues buildahcli.BudResults - fromAndBudValues buildahcli.FromAndBudResults - userNSValues buildahcli.UserNSResults - namespaceValues buildahcli.NameSpaceResults - podBuildValues cliconfig.PodmanBuildResults - - _buildCommand = &cobra.Command{ - Use: "build [flags] CONTEXT", - Short: "Build an image using instructions from Containerfiles", - Long: buildDescription, - RunE: func(cmd *cobra.Command, args []string) error { - buildCommand.InputArgs = args - buildCommand.GlobalFlags = MainGlobalOpts - buildCommand.BudResults = &budFlagsValues - buildCommand.UserNSResults = &userNSValues - buildCommand.FromAndBudResults = &fromAndBudValues - buildCommand.LayerResults = &layerValues - buildCommand.NameSpaceResults = &namespaceValues - buildCommand.PodmanBuildResults = &podBuildValues - buildCommand.Remote = remoteclient - return buildCmd(&buildCommand) - }, - Example: `podman build . - podman build --creds=username:password -t imageName -f Containerfile.simple . - podman build --layers --force-rm --tag imageName .`, - } -) - -func initBuild() { - buildCommand.Command = _buildCommand - buildCommand.SetHelpTemplate(HelpTemplate()) - buildCommand.SetUsageTemplate(UsageTemplate()) - flags := buildCommand.Flags() - flags.SetInterspersed(true) - budFlags := buildahcli.GetBudFlags(&budFlagsValues) - flag := budFlags.Lookup("pull") - if err := flag.Value.Set("true"); err != nil { - logrus.Error("unable to set pull flag to true") - } - flag.DefValue = "true" - layerFlags := buildahcli.GetLayerFlags(&layerValues) - flag = layerFlags.Lookup("layers") - if err := flag.Value.Set(useLayers()); err != nil { - logrus.Error("unable to set uselayers") - } - flag.DefValue = useLayers() - flag = layerFlags.Lookup("force-rm") - if err := flag.Value.Set("true"); err != nil { - logrus.Error("unable to set force-rm flag to true") - } - flag.DefValue = "true" - podmanBuildFlags := GetPodmanBuildFlags(&podBuildValues) - flag = podmanBuildFlags.Lookup("squash-all") - if err := flag.Value.Set("false"); err != nil { - logrus.Error("unable to set squash-all flag to false") - } - - flag.DefValue = "true" - fromAndBugFlags, err := buildahcli.GetFromAndBudFlags(&fromAndBudValues, &userNSValues, &namespaceValues) - if err != nil { - logrus.Errorf("failed to setup podman build flags: %v", err) - os.Exit(1) - } - - flags.AddFlagSet(&budFlags) - flags.AddFlagSet(&fromAndBugFlags) - flags.AddFlagSet(&layerFlags) - flags.AddFlagSet(&podmanBuildFlags) - markFlagHidden(flags, "signature-policy") -} - -// GetPodmanBuildFlags flags used only by `podman build` and not by -// `buildah bud`. -func GetPodmanBuildFlags(flags *cliconfig.PodmanBuildResults) pflag.FlagSet { - fs := pflag.FlagSet{} - fs.BoolVar(&flags.SquashAll, "squash-all", false, "Squash all layers into a single layer.") - return fs -} - -func getContainerfiles(files []string) []string { - var containerfiles []string - for _, f := range files { - if f == "-" { - containerfiles = append(containerfiles, "/dev/stdin") - } else { - containerfiles = append(containerfiles, f) - } - } - return containerfiles -} - -func getNsValues(c *cliconfig.BuildValues) ([]buildah.NamespaceOption, error) { - var ret []buildah.NamespaceOption - if c.Network != "" { - switch { - case c.Network == "host": - ret = append(ret, buildah.NamespaceOption{ - Name: string(specs.NetworkNamespace), - Host: true, - }) - case c.Network == "container": - ret = append(ret, buildah.NamespaceOption{ - Name: string(specs.NetworkNamespace), - }) - case c.Network[0] == '/': - ret = append(ret, buildah.NamespaceOption{ - Name: string(specs.NetworkNamespace), - Path: c.Network, - }) - default: - return nil, fmt.Errorf("unsupported configuration network=%s", c.Network) - } - } - return ret, nil -} - -func buildCmd(c *cliconfig.BuildValues) error { - if (c.Flags().Changed("squash") && c.Flags().Changed("layers")) || - (c.Flags().Changed("squash-all") && c.Flags().Changed("layers")) || - (c.Flags().Changed("squash-all") && c.Flags().Changed("squash")) { - return fmt.Errorf("cannot specify squash, squash-all and layers options together") - } - - // The following was taken directly from containers/buildah/cmd/bud.go - // TODO Find a away to vendor more of this in rather than copy from bud - output := "" - tags := []string{} - if c.Flag("tag").Changed { - tags = c.Tag - if len(tags) > 0 { - output = tags[0] - tags = tags[1:] - } - } - if c.BudResults.Authfile != "" { - if _, err := os.Stat(c.BudResults.Authfile); err != nil { - return errors.Wrapf(err, "error getting authfile %s", c.BudResults.Authfile) - } - } - - pullPolicy := imagebuildah.PullNever - if c.Pull { - pullPolicy = imagebuildah.PullIfMissing - } - if c.PullAlways { - pullPolicy = imagebuildah.PullAlways - } - - args := make(map[string]string) - if c.Flag("build-arg").Changed { - for _, arg := range c.BuildArg { - av := strings.SplitN(arg, "=", 2) - if len(av) > 1 { - args[av[0]] = av[1] - } else { - delete(args, av[0]) - } - } - } - - containerfiles := getContainerfiles(c.File) - format, err := getFormat(&c.PodmanCommand) - if err != nil { - return nil - } - contextDir := "" - cliArgs := c.InputArgs - - layers := c.Layers // layers for podman defaults to true - // Check to see if the BUILDAH_LAYERS environment variable is set and override command-line - if _, ok := os.LookupEnv("BUILDAH_LAYERS"); ok { - layers = buildahcli.UseLayers() - } - - if len(cliArgs) > 0 { - // The context directory could be a URL. Try to handle that. - tempDir, subDir, err := imagebuildah.TempDirForURL("", "buildah", cliArgs[0]) - if err != nil { - return errors.Wrapf(err, "error prepping temporary context directory") - } - if tempDir != "" { - // We had to download it to a temporary directory. - // Delete it later. - defer func() { - if err = os.RemoveAll(tempDir); err != nil { - logrus.Errorf("error removing temporary directory %q: %v", contextDir, err) - } - }() - contextDir = filepath.Join(tempDir, subDir) - } else { - // Nope, it was local. Use it as is. - absDir, err := filepath.Abs(cliArgs[0]) - if err != nil { - return errors.Wrapf(err, "error determining path to directory %q", cliArgs[0]) - } - contextDir = absDir - } - } else { - // No context directory or URL was specified. Try to use the - // home of the first locally-available Containerfile. - for i := range containerfiles { - if strings.HasPrefix(containerfiles[i], "http://") || - strings.HasPrefix(containerfiles[i], "https://") || - strings.HasPrefix(containerfiles[i], "git://") || - strings.HasPrefix(containerfiles[i], "github.com/") { - continue - } - absFile, err := filepath.Abs(containerfiles[i]) - if err != nil { - return errors.Wrapf(err, "error determining path to file %q", containerfiles[i]) - } - contextDir = filepath.Dir(absFile) - break - } - } - if contextDir == "" { - return errors.Errorf("no context directory specified, and no containerfile specified") - } - if !fileIsDir(contextDir) { - return errors.Errorf("context must be a directory: %v", contextDir) - } - if len(containerfiles) == 0 { - if checkIfFileExists(filepath.Join(contextDir, "Containerfile")) { - containerfiles = append(containerfiles, filepath.Join(contextDir, "Containerfile")) - } else { - containerfiles = append(containerfiles, filepath.Join(contextDir, "Dockerfile")) - } - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - - runtimeFlags := []string{} - for _, arg := range c.RuntimeFlags { - runtimeFlags = append(runtimeFlags, "--"+arg) - } - - conf, err := runtime.GetConfig() - if err != nil { - return err - } - if conf != nil && conf.Engine.CgroupManager == config.SystemdCgroupsManager { - runtimeFlags = append(runtimeFlags, "--systemd-cgroup") - } - // end from buildah - - defer runtime.DeferredShutdown(false) - - var stdin, stdout, stderr, reporter *os.File - stdin = os.Stdin - stdout = os.Stdout - stderr = os.Stderr - reporter = os.Stderr - if c.Flag("logfile").Changed { - f, err := os.OpenFile(c.Logfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) - if err != nil { - return errors.Errorf("error opening logfile %q: %v", c.Logfile, err) - } - defer f.Close() - logrus.SetOutput(f) - stdout = f - stderr = f - reporter = f - } - - var memoryLimit, memorySwap int64 - if c.Flags().Changed("memory") { - memoryLimit, err = units.RAMInBytes(c.Memory) - if err != nil { - return err - } - } - - if c.Flags().Changed("memory-swap") { - memorySwap, err = units.RAMInBytes(c.MemorySwap) - if err != nil { - return err - } - } - - nsValues, err := getNsValues(c) - if err != nil { - return err - } - - networkPolicy := buildah.NetworkDefault - for _, ns := range nsValues { - if ns.Name == "none" { - networkPolicy = buildah.NetworkDisabled - break - } else if !filepath.IsAbs(ns.Path) { - networkPolicy = buildah.NetworkEnabled - break - } - } - - buildOpts := buildah.CommonBuildOptions{ - AddHost: c.AddHost, - CgroupParent: c.CgroupParent, - CPUPeriod: c.CPUPeriod, - CPUQuota: c.CPUQuota, - CPUShares: c.CPUShares, - CPUSetCPUs: c.CPUSetCPUs, - CPUSetMems: c.CPUSetMems, - Memory: memoryLimit, - MemorySwap: memorySwap, - ShmSize: c.ShmSize, - Ulimit: c.Ulimit, - Volumes: c.Volumes, - } - - // `buildah bud --layers=false` acts like `docker build --squash` does. - // That is all of the new layers created during the build process are - // condensed into one, any layers present prior to this build are retained - // without condensing. `buildah bud --squash` squashes both new and old - // layers down into one. Translate Podman commands into Buildah. - // Squash invoked, retain old layers, squash new layers into one. - if c.Flags().Changed("squash") && c.Squash { - c.Squash = false - layers = false - } - // Squash-all invoked, squash both new and old layers into one. - if c.Flags().Changed("squash-all") { - c.Squash = true - layers = false - } - - compression := imagebuildah.Gzip - if c.DisableCompression { - compression = imagebuildah.Uncompressed - } - - isolation, err := parse.IsolationOption(c.Isolation) - if err != nil { - return errors.Wrapf(err, "error parsing ID mapping options") - } - - usernsOption, idmappingOptions, err := parse.IDMappingOptions(c.PodmanCommand.Command, isolation) - if err != nil { - return errors.Wrapf(err, "error parsing ID mapping options") - } - nsValues = append(nsValues, usernsOption...) - - systemContext, err := parse.SystemContextFromOptions(c.PodmanCommand.Command) - if err != nil { - return errors.Wrapf(err, "error building system context") - } - - options := imagebuildah.BuildOptions{ - AddCapabilities: c.CapAdd, - AdditionalTags: tags, - Annotations: c.Annotation, - Architecture: c.Arch, - Args: args, - BlobDirectory: c.BlobCache, - CNIConfigDir: c.CNIConfigDir, - CNIPluginPath: c.CNIPlugInPath, - CommonBuildOpts: &buildOpts, - Compression: compression, - ConfigureNetwork: networkPolicy, - ContextDirectory: contextDir, - DefaultMountsFilePath: c.GlobalFlags.DefaultMountsFile, - Devices: c.Devices, - DropCapabilities: c.CapDrop, - Err: stderr, - ForceRmIntermediateCtrs: c.ForceRm, - IDMappingOptions: idmappingOptions, - IIDFile: c.Iidfile, - In: stdin, - Isolation: isolation, - Labels: c.Label, - Layers: layers, - NamespaceOptions: nsValues, - NoCache: c.NoCache, - OS: c.OS, - Out: stdout, - Output: output, - OutputFormat: format, - PullPolicy: pullPolicy, - Quiet: c.Quiet, - RemoveIntermediateCtrs: c.Rm, - ReportWriter: reporter, - RuntimeArgs: runtimeFlags, - SignBy: c.SignBy, - SignaturePolicyPath: c.SignaturePolicy, - Squash: c.Squash, - SystemContext: systemContext, - Target: c.Target, - TransientMounts: c.Volumes, - } - _, _, err = runtime.Build(getContext(), c, options, containerfiles) - return err -} - -// useLayers returns false if BUILDAH_LAYERS is set to "0" or "false" -// otherwise it returns true -func useLayers() string { - layers := os.Getenv("BUILDAH_LAYERS") - if strings.ToLower(layers) == "false" || layers == "0" { - return "false" - } - return "true" -} diff --git a/cmd/podman/checkpoint.go b/cmd/podman/checkpoint.go deleted file mode 100644 index 07db519f8..000000000 --- a/cmd/podman/checkpoint.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - checkpointCommand cliconfig.CheckpointValues - checkpointDescription = ` - podman container checkpoint - - Checkpoints one or more running containers. The container name or ID can be used. -` - _checkpointCommand = &cobra.Command{ - Use: "checkpoint [flags] CONTAINER [CONTAINER...]", - Short: "Checkpoints one or more containers", - Long: checkpointDescription, - RunE: func(cmd *cobra.Command, args []string) error { - checkpointCommand.InputArgs = args - checkpointCommand.GlobalFlags = MainGlobalOpts - checkpointCommand.Remote = remoteclient - return checkpointCmd(&checkpointCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllLatestAndCIDFile(cmd, args, false, false) - }, - Example: `podman container checkpoint --keep ctrID - podman container checkpoint --all - podman container checkpoint --leave-running --latest`, - } -) - -func init() { - checkpointCommand.Command = _checkpointCommand - checkpointCommand.SetHelpTemplate(HelpTemplate()) - checkpointCommand.SetUsageTemplate(UsageTemplate()) - - flags := checkpointCommand.Flags() - flags.BoolVarP(&checkpointCommand.Keep, "keep", "k", false, "Keep all temporary checkpoint files") - flags.BoolVarP(&checkpointCommand.LeaveRunning, "leave-running", "R", false, "Leave the container running after writing checkpoint to disk") - flags.BoolVar(&checkpointCommand.TcpEstablished, "tcp-established", false, "Checkpoint a container with established TCP connections") - flags.BoolVarP(&checkpointCommand.All, "all", "a", false, "Checkpoint all running containers") - flags.BoolVarP(&checkpointCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - flags.StringVarP(&checkpointCommand.Export, "export", "e", "", "Export the checkpoint image to a tar.gz") - flags.BoolVar(&checkpointCommand.IgnoreRootfs, "ignore-rootfs", false, "Do not include root file-system changes when exporting") - markFlagHiddenForRemoteClient("latest", flags) -} - -func checkpointCmd(c *cliconfig.CheckpointValues) error { - if rootless.IsRootless() { - return errors.New("checkpointing a container requires root") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - - defer runtime.DeferredShutdown(false) - return runtime.Checkpoint(c) -} diff --git a/cmd/podman/cleanup.go b/cmd/podman/cleanup.go deleted file mode 100644 index 80a19b000..000000000 --- a/cmd/podman/cleanup.go +++ /dev/null @@ -1,64 +0,0 @@ -//+build !remoteclient - -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - cleanupCommand cliconfig.CleanupValues - cleanupDescription = ` - podman container cleanup - - Cleans up mount points and network stacks on one or more containers from the host. The container name or ID can be used. This command is used internally when running containers, but can also be used if container cleanup has failed when a container exits. -` - _cleanupCommand = &cobra.Command{ - Use: "cleanup [flags] CONTAINER [CONTAINER...]", - Short: "Cleanup network and mountpoints of one or more containers", - Long: cleanupDescription, - RunE: func(cmd *cobra.Command, args []string) error { - cleanupCommand.InputArgs = args - cleanupCommand.GlobalFlags = MainGlobalOpts - cleanupCommand.Remote = remoteclient - return cleanupCmd(&cleanupCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllLatestAndCIDFile(cmd, args, false, false) - }, - Example: `podman container cleanup --latest - podman container cleanup ctrID1 ctrID2 ctrID3 - podman container cleanup --all`, - } -) - -func init() { - cleanupCommand.Command = _cleanupCommand - cleanupCommand.SetHelpTemplate(HelpTemplate()) - cleanupCommand.SetUsageTemplate(UsageTemplate()) - flags := cleanupCommand.Flags() - - flags.BoolVarP(&cleanupCommand.All, "all", "a", false, "Cleans up all containers") - flags.BoolVarP(&cleanupCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - flags.BoolVar(&cleanupCommand.Remove, "rm", false, "After cleanup, remove the container entirely") - flags.BoolVar(&cleanupCommand.RemoveImage, "rmi", false, "After cleanup, remove the image entirely") - markFlagHiddenForRemoteClient("latest", flags) -} - -func cleanupCmd(c *cliconfig.CleanupValues) error { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - ok, failures, err := runtime.CleanupContainers(getContext(), c) - if err != nil { - return err - } - - return printCmdResults(ok, failures) -} diff --git a/cmd/podman/cliconfig/commands.go b/cmd/podman/cliconfig/commands.go deleted file mode 100644 index f6d92611f..000000000 --- a/cmd/podman/cliconfig/commands.go +++ /dev/null @@ -1,167 +0,0 @@ -package cliconfig - -import ( - "github.com/sirupsen/logrus" -) - -// GlobalIsSet is a compatibility method for urfave -func (p *PodmanCommand) GlobalIsSet(opt string) bool { - flag := p.PersistentFlags().Lookup(opt) - if flag == nil { - return false - } - return flag.Changed -} - -// IsSet is a compatibility method for urfave -func (p *PodmanCommand) IsSet(opt string) bool { - flag := p.Flags().Lookup(opt) - if flag == nil { - return false - } - return flag.Changed -} - -// Bool is a compatibility method for urfave -func (p *PodmanCommand) Bool(opt string) bool { - flag := p.Flags().Lookup(opt) - if flag == nil { - if !p.Remote { - logrus.Errorf("Could not find flag %s", opt) - } - return false - } - val, err := p.Flags().GetBool(opt) - if err != nil { - logrus.Errorf("Error getting flag %s: %v", opt, err) - } - return val -} - -// String is a compatibility method for urfave -func (p *PodmanCommand) String(opt string) string { - flag := p.Flags().Lookup(opt) - if flag == nil { - if !p.Remote { - logrus.Errorf("Could not find flag %s", opt) - } - return "" - } - val, err := p.Flags().GetString(opt) - if err != nil { - logrus.Errorf("Error getting flag %s: %v", opt, err) - } - return val -} - -// StringArray is a compatibility method for urfave -func (p *PodmanCommand) StringArray(opt string) []string { - flag := p.Flags().Lookup(opt) - if flag == nil { - if !p.Remote { - logrus.Errorf("Could not find flag %s", opt) - } - return []string{} - } - val, err := p.Flags().GetStringArray(opt) - if err != nil { - logrus.Errorf("Error getting flag %s: %v", opt, err) - } - return val -} - -// StringSlice is a compatibility method for urfave -func (p *PodmanCommand) StringSlice(opt string) []string { - flag := p.Flags().Lookup(opt) - if flag == nil { - if !p.Remote { - logrus.Errorf("Could not find flag %s", opt) - } - return []string{} - } - val, err := p.Flags().GetStringSlice(opt) - if err != nil { - logrus.Errorf("Error getting flag %s: %v", opt, err) - } - return val -} - -// Int is a compatibility method for urfave -func (p *PodmanCommand) Int(opt string) int { - flag := p.Flags().Lookup(opt) - if flag == nil { - if !p.Remote { - logrus.Errorf("Could not find flag %s", opt) - } - return 0 - } - val, err := p.Flags().GetInt(opt) - if err != nil { - logrus.Errorf("Error getting flag %s: %v", opt, err) - } - return val -} - -// Unt is a compatibility method for urfave -func (p *PodmanCommand) Uint(opt string) uint { - flag := p.Flags().Lookup(opt) - if flag == nil { - if !p.Remote { - logrus.Errorf("Could not find flag %s", opt) - } - return 0 - } - val, err := p.Flags().GetUint(opt) - if err != nil { - logrus.Errorf("Error getting flag %s: %v", opt, err) - } - return val -} - -// Int64 is a compatibility method for urfave -func (p *PodmanCommand) Int64(opt string) int64 { - flag := p.Flags().Lookup(opt) - if flag == nil { - if !p.Remote { - logrus.Errorf("Could not find flag %s", opt) - } - return 0 - } - val, err := p.Flags().GetInt64(opt) - if err != nil { - logrus.Errorf("Error getting flag %s: %v", opt, err) - } - return val -} - -// Unt64 is a compatibility method for urfave -func (p *PodmanCommand) Uint64(opt string) uint64 { - flag := p.Flags().Lookup(opt) - if flag == nil { - if !p.Remote { - logrus.Errorf("Could not find flag %s", opt) - } - return 0 - } - val, err := p.Flags().GetUint64(opt) - if err != nil { - logrus.Errorf("Error getting flag %s: %v", opt, err) - } - return val -} - -// Float64 is a compatibility method for urfave -func (p *PodmanCommand) Float64(opt string) float64 { - flag := p.Flags().Lookup(opt) - if flag == nil { - if !p.Remote { - logrus.Errorf("Could not find flag %s", opt) - } - return 0 - } - val, err := p.Flags().GetFloat64(opt) - if err != nil { - logrus.Errorf("Error getting flag %s: %v", opt, err) - } - return val -} diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go deleted file mode 100644 index 99f389799..000000000 --- a/cmd/podman/cliconfig/config.go +++ /dev/null @@ -1,716 +0,0 @@ -package cliconfig - -import ( - "net" - "os" - - "github.com/containers/common/pkg/config" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -type PodmanCommand struct { - *cobra.Command - InputArgs []string - GlobalFlags MainFlags - Remote bool -} - -type MainFlags struct { - CGroupManager string - CniConfigDir string - ConmonPath string - DefaultMountsFile string - EventsBackend string - HooksDir []string - MaxWorks int - Namespace string - Root string - Runroot string - Runtime string - StorageDriver string - StorageOpts []string - Syslog bool - Trace bool - NetworkCmdPath string - - Config string - CpuProfile string - LogLevel string - TmpDir string - - RemoteUserName string - RemoteHost string - VarlinkAddress string - ConnectionName string - RemoteConfigFilePath string - Port int - IdentityFile string - IgnoreHosts bool -} - -type AttachValues struct { - PodmanCommand - DetachKeys string - Latest bool - NoStdin bool - SigProxy bool -} - -type AutoUpdateValues struct { - PodmanCommand -} - -type ImagesValues struct { - PodmanCommand - All bool - Digests bool - Filter []string - Format string - Noheading bool - NoTrunc bool - Quiet bool - Sort string - History bool -} - -type EventValues struct { - PodmanCommand - Filter []string - Format string - Since string - Stream bool - Until string -} - -type TagValues struct { - PodmanCommand -} - -type TreeValues struct { - PodmanCommand - WhatRequires bool -} - -type WaitValues struct { - PodmanCommand - Interval uint - Latest bool -} - -type CheckpointValues struct { - PodmanCommand - Keep bool - LeaveRunning bool - TcpEstablished bool - All bool - Latest bool - Export string - IgnoreRootfs bool -} - -type CommitValues struct { - PodmanCommand - Change []string - Format string - Message string - Author string - Pause bool - Quiet bool - IncludeVolumes bool - ImageIDFile string -} - -type ContainersPrune struct { - PodmanCommand -} - -type DiffValues struct { - PodmanCommand - Archive bool - Format string - Latest bool -} - -type ExecValues struct { - PodmanCommand - DetachKeys string - Env []string - EnvFile []string - Privileged bool - Interactive bool - Tty bool - User string - Latest bool - Workdir string - PreserveFDs int -} - -type ImageExistsValues struct { - PodmanCommand -} - -type ContainerExistsValues struct { - PodmanCommand -} - -type PodExistsValues struct { - PodmanCommand -} - -type ExportValues struct { - PodmanCommand - Output string -} -type GenerateKubeValues struct { - PodmanCommand - Service bool - Filename string -} - -type GenerateSystemdValues struct { - PodmanCommand - Name bool - New bool - Files bool - RestartPolicy string - StopTimeout uint -} - -type HistoryValues struct { - PodmanCommand - Human bool - NoTrunc bool - Quiet bool - Format string -} -type PruneImagesValues struct { - PodmanCommand - All bool - Force bool - Filter []string -} - -type PruneContainersValues struct { - PodmanCommand - Force bool - Filter []string -} - -type PodPruneValues struct { - PodmanCommand - Force bool -} - -type ImportValues struct { - PodmanCommand - Change []string - Message string - Quiet bool -} - -type InfoValues struct { - PodmanCommand - Debug bool - Format string -} - -type InitValues struct { - PodmanCommand - All bool - Latest bool -} - -type InspectValues struct { - PodmanCommand - TypeObject string - Format string - Size bool - Latest bool -} - -type KillValues struct { - PodmanCommand - All bool - Signal string - Latest bool -} - -type LoadValues struct { - PodmanCommand - Input string - Quiet bool - SignaturePolicy string -} - -type LoginValues struct { - PodmanCommand - Password string - StdinPassword bool - Username string - Authfile string - CertDir string - GetLogin bool - TlsVerify bool -} - -type LogoutValues struct { - PodmanCommand - Authfile string - All bool -} - -type LogsValues struct { - PodmanCommand - Details bool - Follow bool - Since string - Tail int64 - Timestamps bool - Latest bool - UseName bool -} - -type MountValues struct { - PodmanCommand - All bool - Format string - NoTrunc bool - Latest bool -} - -type NetworkCreateValues struct { - PodmanCommand - Driver string - DisableDNS bool - Gateway net.IP - Internal bool - IPamDriver string - IPRange net.IPNet - IPV6 bool - Network net.IPNet - MacVLAN string -} - -type NetworkListValues struct { - PodmanCommand - Filter []string - Quiet bool -} - -type NetworkRmValues struct { - PodmanCommand - Force bool -} - -type NetworkInspectValues struct { - PodmanCommand -} - -type PauseValues struct { - PodmanCommand - All bool -} - -type HealthCheckValues struct { - PodmanCommand -} - -type KubePlayValues struct { - PodmanCommand - Authfile string - CertDir string - Creds string - Quiet bool - SignaturePolicy string - TlsVerify bool - SeccompProfileRoot string -} - -type PodCreateValues struct { - PodmanCommand - CgroupParent string - Infra bool - InfraImage string - InfraCommand string - LabelFile []string - Labels []string - Name string - Hostname string - PodIDFile string - Publish []string - Share string -} - -type PodInspectValues struct { - PodmanCommand - Latest bool -} - -type PodKillValues struct { - PodmanCommand - All bool - Signal string - Latest bool -} - -type PodPauseValues struct { - PodmanCommand - All bool - Latest bool -} - -type PodPsValues struct { - PodmanCommand - CtrNames bool - CtrIDs bool - CtrStatus bool - Filter string - Format string - Latest bool - Namespace bool - NoTrunc bool - Quiet bool - Sort string -} - -type PodRestartValues struct { - PodmanCommand - All bool - Latest bool -} - -type PodRmValues struct { - PodmanCommand - All bool - Ignore bool - Force bool - Latest bool -} - -type PodStartValues struct { - PodmanCommand - All bool - Latest bool -} -type PodStatsValues struct { - PodmanCommand - All bool - NoStream bool - NoReset bool - Format string - Latest bool -} - -type PodStopValues struct { - PodmanCommand - All bool - Ignore bool - Latest bool - Timeout uint -} - -type PodTopValues struct { - PodmanCommand - Latest bool - ListDescriptors bool -} -type PodUnpauseValues struct { - PodmanCommand - All bool - Latest bool -} - -type PortValues struct { - PodmanCommand - All bool - Latest bool -} - -type PsValues struct { - PodmanCommand - All bool - Filter []string - Format string - Last int - Latest bool - Namespace bool - NoTrunct bool - Pod bool - Quiet bool - Size bool - Sort string - Sync bool - Watch uint -} - -type PullValues struct { - PodmanCommand - AllTags bool - Authfile string - CertDir string - Creds string - OverrideArch string - OverrideOS string - Quiet bool - SignaturePolicy string - TlsVerify bool -} - -type PushValues struct { - PodmanCommand - Authfile string - CertDir string - Compress bool - Creds string - Digestfile string - Format string - Quiet bool - RemoveSignatures bool - SignBy string - SignaturePolicy string - TlsVerify bool -} - -type RefreshValues struct { - PodmanCommand -} - -type RestartValues struct { - PodmanCommand - All bool - AutoUpdate bool - Latest bool - Running bool - Timeout uint -} - -type RestoreValues struct { - PodmanCommand - All bool - Keep bool - Latest bool - TcpEstablished bool - Import string - Name string - IgnoreRootfs bool - IgnoreStaticIP bool - IgnoreStaticMAC bool -} - -type RmValues struct { - PodmanCommand - All bool - Force bool - Ignore bool - Latest bool - Storage bool - Volumes bool - CIDFiles []string -} - -type RmiValues struct { - PodmanCommand - All bool - Force bool -} - -type RunlabelValues struct { - PodmanCommand - Authfile string - CertDir string - Creds string - Display bool - Name string - Opt1 string - Opt2 string - Opt3 string - Quiet bool - Replace bool - SignaturePolicy string - TlsVerify bool -} -type SaveValues struct { - PodmanCommand - Compress bool - Format string - Output string - Quiet bool -} - -type SearchValues struct { - PodmanCommand - Authfile string - Filter []string - Format string - Limit int - NoTrunc bool - TlsVerify bool -} - -type TrustValues struct { - PodmanCommand -} - -type SignValues struct { - PodmanCommand - Directory string - SignBy string - CertDir string -} - -type StartValues struct { - PodmanCommand - Attach bool - DetachKeys string - Interactive bool - Latest bool - SigProxy bool -} - -type StatsValues struct { - PodmanCommand - All bool - Format string - Latest bool - NoReset bool - NoStream bool -} - -type StopValues struct { - PodmanCommand - All bool - Ignore bool - Latest bool - Timeout uint - CIDFiles []string -} - -type TopValues struct { - PodmanCommand - Latest bool - ListDescriptors bool -} - -type UmountValues struct { - PodmanCommand - All bool - Force bool - Latest bool -} - -type UnpauseValues struct { - PodmanCommand - All bool -} - -type VarlinkValues struct { - PodmanCommand - Timeout int64 -} - -type ServiceValues struct { - PodmanCommand - Varlink bool - Timeout int64 -} - -type SetTrustValues struct { - PodmanCommand - PolicyPath string - PubKeysFile []string - TrustType string -} - -type ShowTrustValues struct { - PodmanCommand - Json bool - PolicyPath string - Raw bool - RegistryPath string -} - -type VersionValues struct { - PodmanCommand - Format string -} - -type VolumeCreateValues struct { - PodmanCommand - Driver string - Label []string - Opt []string -} -type VolumeInspectValues struct { - PodmanCommand - All bool - Format string -} - -type VolumeLsValues struct { - PodmanCommand - Filter string - Format string - Quiet bool -} - -type VolumePruneValues struct { - PodmanCommand - Force bool -} - -type VolumeRmValues struct { - PodmanCommand - All bool - Force bool -} - -type CleanupValues struct { - PodmanCommand - All bool - Latest bool - Remove bool - RemoveImage bool -} - -type SystemPruneValues struct { - PodmanCommand - All bool - Force bool - Volume bool -} - -type SystemResetValues struct { - PodmanCommand - Force bool -} - -type SystemRenumberValues struct { - PodmanCommand -} - -type SystemMigrateValues struct { - PodmanCommand - NewRuntime string -} - -type SystemDfValues struct { - PodmanCommand - Verbose bool - Format string -} - -type UntagValues struct { - PodmanCommand -} - -func GetDefaultConfig() *config.Config { - var err error - conf, err := config.NewConfig("") - conf.CheckCgroupsAndAdjustConfig() - if err != nil { - logrus.Errorf("Error loading container config %v\n", err) - os.Exit(1) - } - return conf -} diff --git a/cmd/podman/cliconfig/create.go b/cmd/podman/cliconfig/create.go deleted file mode 100644 index c27dfbbee..000000000 --- a/cmd/podman/cliconfig/create.go +++ /dev/null @@ -1,35 +0,0 @@ -package cliconfig - -import ( - buildahcli "github.com/containers/buildah/pkg/cli" -) - -type CreateValues struct { - PodmanCommand -} - -type RunValues struct { - PodmanCommand -} - -// PodmanBuildResults represents the results for Podman Build flags -// that are unique to Podman. -type PodmanBuildResults struct { - SquashAll bool -} - -type BuildValues struct { - PodmanCommand - *buildahcli.BudResults - *buildahcli.UserNSResults - *buildahcli.FromAndBudResults - *buildahcli.LayerResults - *buildahcli.NameSpaceResults - *PodmanBuildResults -} - -type CpValues struct { - PodmanCommand - Extract bool - Pause bool -} diff --git a/cmd/podman/cliconfig/defaults.go b/cmd/podman/cliconfig/defaults.go deleted file mode 100644 index 3082207e0..000000000 --- a/cmd/podman/cliconfig/defaults.go +++ /dev/null @@ -1,14 +0,0 @@ -package cliconfig - -var ( - // DefaultHealthCheckInterval default value - DefaultHealthCheckInterval = "30s" - // DefaultHealthCheckRetries default value - DefaultHealthCheckRetries uint = 3 - // DefaultHealthCheckStartPeriod default value - DefaultHealthCheckStartPeriod = "0s" - // DefaultHealthCheckTimeout default value - DefaultHealthCheckTimeout = "30s" - // DefaultImageVolume default value - DefaultImageVolume = "bind" -) diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go deleted file mode 100644 index 2ee31b643..000000000 --- a/cmd/podman/commands.go +++ /dev/null @@ -1,196 +0,0 @@ -// +build !remoteclient - -package main - -import ( - "fmt" - "os" - - "github.com/containers/buildah/pkg/parse" - "github.com/containers/libpod/pkg/apparmor" - "github.com/containers/libpod/pkg/cgroups" - "github.com/containers/libpod/pkg/rootless" - "github.com/containers/libpod/pkg/sysinfo" - "github.com/opencontainers/selinux/go-selinux" - "github.com/spf13/cobra" -) - -const remoteclient = false - -// Commands that the local client implements -func getMainCommands() []*cobra.Command { - rootCommands := []*cobra.Command{ - _autoUpdateCommand, - _cpCommand, - _playCommand, - _loginCommand, - _logoutCommand, - _mountCommand, - _refreshCommand, - _searchCommand, - _statsCommand, - _umountCommand, - _unshareCommand, - } - - if len(_varlinkCommand.Use) > 0 { - rootCommands = append(rootCommands, _varlinkCommand) - } - return rootCommands -} - -// Commands that the local client implements -func getImageSubCommands() []*cobra.Command { - return []*cobra.Command{ - _signCommand, - _trustCommand, - } -} - -// Commands that the local client implements -func getContainerSubCommands() []*cobra.Command { - - return []*cobra.Command{ - _cpCommand, - _cleanupCommand, - _mountCommand, - _refreshCommand, - _runlabelCommand, - _statsCommand, - _umountCommand, - } -} - -// Commands that the local client implements -func getPlaySubCommands() []*cobra.Command { - return []*cobra.Command{ - _playKubeCommand, - } -} - -// Commands that the local client implements -func getTrustSubCommands() []*cobra.Command { - return []*cobra.Command{ - _setTrustCommand, - _showTrustCommand, - } -} - -// Commands that the local client implements -func getSystemSubCommands() []*cobra.Command { - systemCommands := []*cobra.Command{ - _renumberCommand, - _dfSystemCommand, - _migrateCommand, - } - - if len(_serviceCommand.Use) > 0 { - systemCommands = append(systemCommands, _serviceCommand) - } - - return systemCommands -} - -func getDefaultSecurityOptions() []string { - securityOpts := []string{} - if defaultContainerConfig.Containers.SeccompProfile != "" && defaultContainerConfig.Containers.SeccompProfile != parse.SeccompDefaultPath { - securityOpts = append(securityOpts, fmt.Sprintf("seccomp=%s", defaultContainerConfig.Containers.SeccompProfile)) - } - if apparmor.IsEnabled() && defaultContainerConfig.Containers.ApparmorProfile != "" { - securityOpts = append(securityOpts, fmt.Sprintf("apparmor=%s", defaultContainerConfig.Containers.ApparmorProfile)) - } - if selinux.GetEnabled() && !defaultContainerConfig.Containers.EnableLabeling { - securityOpts = append(securityOpts, fmt.Sprintf("label=%s", selinux.DisableSecOpt()[0])) - } - return securityOpts -} - -// getDefaultSysctls -func getDefaultSysctls() []string { - return defaultContainerConfig.Containers.DefaultSysctls -} - -func getDefaultVolumes() []string { - return defaultContainerConfig.Containers.Volumes -} - -func getDefaultDevices() []string { - return defaultContainerConfig.Containers.Devices -} - -func getDefaultDNSServers() []string { - return defaultContainerConfig.Containers.DNSServers -} - -func getDefaultDNSSearches() []string { - return defaultContainerConfig.Containers.DNSSearches -} - -func getDefaultDNSOptions() []string { - return defaultContainerConfig.Containers.DNSOptions -} - -func getDefaultEnv() []string { - return defaultContainerConfig.Containers.Env -} - -func getDefaultInitPath() string { - return defaultContainerConfig.Containers.InitPath -} - -func getDefaultIPCNS() string { - return defaultContainerConfig.Containers.IPCNS -} - -func getDefaultPidNS() string { - return defaultContainerConfig.Containers.PidNS -} - -func getDefaultNetNS() string { - if defaultContainerConfig.Containers.NetNS == "private" && rootless.IsRootless() { - return "slirp4netns" - } - return defaultContainerConfig.Containers.NetNS -} - -func getDefaultCgroupNS() string { - return defaultContainerConfig.Containers.CgroupNS -} - -func getDefaultUTSNS() string { - return defaultContainerConfig.Containers.UTSNS -} - -func getDefaultShmSize() string { - return defaultContainerConfig.Containers.ShmSize -} - -func getDefaultUlimits() []string { - return defaultContainerConfig.Containers.DefaultUlimits -} - -func getDefaultUserNS() string { - userns := os.Getenv("PODMAN_USERNS") - if userns != "" { - return userns - } - return defaultContainerConfig.Containers.UserNS -} - -func getDefaultPidsLimit() int64 { - if rootless.IsRootless() { - cgroup2, _ := cgroups.IsCgroup2UnifiedMode() - if cgroup2 { - return defaultContainerConfig.Containers.PidsLimit - } - } - return sysinfo.GetDefaultPidsLimit() -} - -func getDefaultPidsDescription() string { - return "Tune container pids limit (set 0 for unlimited)" -} - -func getDefaultDetachKeys() string { - return defaultContainerConfig.Engine.DetachKeys -} diff --git a/cmd/podman/commands_remoteclient.go b/cmd/podman/commands_remoteclient.go deleted file mode 100644 index ef523ffb1..000000000 --- a/cmd/podman/commands_remoteclient.go +++ /dev/null @@ -1,135 +0,0 @@ -// +build remoteclient - -package main - -import ( - "github.com/spf13/cobra" -) - -const remoteclient = true - -// commands that only the remoteclient implements -func getMainCommands() []*cobra.Command { - return []*cobra.Command{} -} - -// commands that only the remoteclient implements -func getAppCommands() []*cobra.Command { - return []*cobra.Command{} -} - -// commands that only the remoteclient implements -func getImageSubCommands() []*cobra.Command { - return []*cobra.Command{} -} - -// commands that only the remoteclient implements -func getContainerSubCommands() []*cobra.Command { - return []*cobra.Command{} -} - -// commands that only the remoteclient implements -func getGenerateSubCommands() []*cobra.Command { - return []*cobra.Command{} -} - -// commands that only the remoteclient implements -func getPlaySubCommands() []*cobra.Command { - return []*cobra.Command{} -} - -// commands that only the remoteclient implements -func getTrustSubCommands() []*cobra.Command { - return []*cobra.Command{} -} - -// commands that only the remoteclient implements -func getSystemSubCommands() []*cobra.Command { - return []*cobra.Command{} -} - -func getDefaultSecurityOptions() []string { - return []string{} -} - -// getDefaultSysctls -func getDefaultSysctls() []string { - return []string{} -} - -// getDefaultDevices -func getDefaultDevices() []string { - return []string{} -} - -func getDefaultVolumes() []string { - return []string{} -} - -func getDefaultDNSServers() []string { - return []string{} -} - -func getDefaultDNSSearches() []string { - return []string{} -} - -func getDefaultDNSOptions() []string { - return []string{} -} - -func getDefaultEnv() []string { - return []string{} -} - -func getDefaultInitPath() string { - return "" -} - -func getDefaultIPCNS() string { - return "" -} - -func getDefaultPidNS() string { - return "" -} - -func getDefaultNetNS() string { - return "" -} - -func getDefaultCgroupNS() string { - return "" -} - -func getDefaultUTSNS() string { - return "" -} - -func getDefaultShmSize() string { - return "" -} - -func getDefaultUlimits() []string { - return []string{} -} - -func getDefaultUserNS() string { - return "" -} - -func getDefaultPidsLimit() int64 { - return -1 -} - -func getDefaultPidsDescription() string { - return "Tune container pids limit (set 0 for unlimited, -1 for server defaults)" -} - -func getDefaultShareNetwork() string { - return "" -} - -func getDefaultDetachKeys() string { - return "" -} diff --git a/cmd/podman/commit.go b/cmd/podman/commit.go deleted file mode 100644 index 3ad3bd275..000000000 --- a/cmd/podman/commit.go +++ /dev/null @@ -1,82 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - "strings" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - commitCommand cliconfig.CommitValues - commitDescription = `Create an image from a container's changes. Optionally tag the image created, set the author with the --author flag, set the commit message with the --message flag, and make changes to the instructions with the --change flag.` - - _commitCommand = &cobra.Command{ - Use: "commit [flags] CONTAINER [IMAGE]", - Short: "Create new image based on the changed container", - Long: commitDescription, - RunE: func(cmd *cobra.Command, args []string) error { - commitCommand.InputArgs = args - commitCommand.GlobalFlags = MainGlobalOpts - commitCommand.Remote = remoteclient - return commitCmd(&commitCommand) - }, - Example: `podman commit -q --message "committing container to image" reverent_golick image-committed - podman commit -q --author "firstName lastName" reverent_golick image-committed - podman commit -q --pause=false containerID image-committed - podman commit containerID`, - } - - // ChangeCmds is the list of valid Changes commands to passed to the Commit call - ChangeCmds = []string{"CMD", "ENTRYPOINT", "ENV", "EXPOSE", "LABEL", "ONBUILD", "STOPSIGNAL", "USER", "VOLUME", "WORKDIR"} -) - -func init() { - commitCommand.Command = _commitCommand - commitCommand.SetHelpTemplate(HelpTemplate()) - commitCommand.SetUsageTemplate(UsageTemplate()) - flags := commitCommand.Flags() - flags.StringArrayVarP(&commitCommand.Change, "change", "c", []string{}, fmt.Sprintf("Apply the following possible instructions to the created image (default []): %s", strings.Join(ChangeCmds, " | "))) - flags.StringVarP(&commitCommand.Format, "format", "f", "oci", "`Format` of the image manifest and metadata") - flags.StringVarP(&commitCommand.ImageIDFile, "iidfile", "", "", "`file` to write the image ID to") - flags.StringVarP(&commitCommand.Message, "message", "m", "", "Set commit message for imported image") - flags.StringVarP(&commitCommand.Author, "author", "a", "", "Set the author for the image committed") - flags.BoolVarP(&commitCommand.Pause, "pause", "p", false, "Pause container during commit") - flags.BoolVarP(&commitCommand.Quiet, "quiet", "q", false, "Suppress output") - flags.BoolVar(&commitCommand.IncludeVolumes, "include-volumes", false, "Include container volumes as image volumes") -} - -func commitCmd(c *cliconfig.CommitValues) error { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - args := c.InputArgs - if len(args) < 1 { - return errors.Errorf("you must provide a container name or ID and optionally a target image name") - } - - container := args[0] - reference := "" - if len(args) > 1 { - reference = args[1] - } - - iid, err := runtime.Commit(getContext(), c, container, reference) - if err != nil { - return err - } - if c.ImageIDFile != "" { - if err = ioutil.WriteFile(c.ImageIDFile, []byte(iid), 0644); err != nil { - return errors.Wrapf(err, "failed to write image ID to file %q", c.ImageIDFile) - } - } - fmt.Println(iid) - return nil -} diff --git a/cmd/podman/common.go b/cmd/podman/common.go deleted file mode 100644 index 9aa9a63fe..000000000 --- a/cmd/podman/common.go +++ /dev/null @@ -1,593 +0,0 @@ -package main - -import ( - "context" - "fmt" - "strings" - - "github.com/containers/buildah" - buildahcli "github.com/containers/buildah/pkg/cli" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/util/camelcase" - jsoniter "github.com/json-iterator/go" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "github.com/spf13/pflag" -) - -var ( - json = jsoniter.ConfigCompatibleWithStandardLibrary -) - -const ( - idTruncLength = 12 - sizeWithUnitFormat = "(format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))" -) - -func splitCamelCase(src string) string { - entries := camelcase.Split(src) - return strings.Join(entries, " ") -} - -func shortID(id string) string { - if len(id) > idTruncLength { - return id[:idTruncLength] - } - return id -} - -// checkAllLatestAndCIDFile checks that --all and --latest are used correctly. -// If cidfile is set, also check for the --cidfile flag. -func checkAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool, cidfile bool) error { - argLen := len(args) - if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { - if !cidfile { - return errors.New("unable to lookup values for 'latest' or 'all'") - } else if c.Flags().Lookup("cidfile") == nil { - return errors.New("unable to lookup values for 'latest', 'all' or 'cidfile'") - } - } - - specifiedAll, _ := c.Flags().GetBool("all") - specifiedLatest, _ := c.Flags().GetBool("latest") - specifiedCIDFile := false - if cid, _ := c.Flags().GetStringArray("cidfile"); len(cid) > 0 { - specifiedCIDFile = true - } - - if specifiedCIDFile && (specifiedAll || specifiedLatest) { - return errors.Errorf("--all, --latest and --cidfile cannot be used together") - } else if specifiedAll && specifiedLatest { - return errors.Errorf("--all and --latest cannot be used together") - } - - if ignoreArgLen { - return nil - } - if (argLen > 0) && (specifiedAll || specifiedLatest) { - return errors.Errorf("no arguments are needed with --all or --latest") - } else if cidfile && (argLen > 0) && (specifiedAll || specifiedLatest || specifiedCIDFile) { - return errors.Errorf("no arguments are needed with --all, --latest or --cidfile") - } - - if specifiedCIDFile { - return nil - } - - if argLen < 1 && !specifiedAll && !specifiedLatest && !specifiedCIDFile { - return errors.Errorf("you must provide at least one name or id") - } - return nil -} - -// noSubArgs checks that there are no further positional parameters -func noSubArgs(c *cobra.Command, args []string) error { - if len(args) > 0 { - return errors.Errorf("`%s` takes no arguments", c.CommandPath()) - } - return nil -} - -func commandRunE() func(*cobra.Command, []string) error { - return func(cmd *cobra.Command, args []string) error { - if len(args) > 0 { - return errors.Errorf("unrecognized command `%s %s`\nTry '%s --help' for more information.", cmd.CommandPath(), args[0], cmd.CommandPath()) - } else { - return errors.Errorf("missing command '%s COMMAND'\nTry '%s --help' for more information.", cmd.CommandPath(), cmd.CommandPath()) - } - } -} - -// getContext returns a non-nil, empty context -func getContext() context.Context { - if Ctx != nil { - return Ctx - } - return context.TODO() -} - -func getNetFlags() *pflag.FlagSet { - netFlags := pflag.FlagSet{} - netFlags.StringSlice( - "add-host", []string{}, - "Add a custom host-to-IP mapping (host:ip)", - ) - netFlags.StringSlice( - "dns", getDefaultDNSServers(), - "Set custom DNS servers", - ) - netFlags.StringSlice( - "dns-opt", getDefaultDNSOptions(), - "Set custom DNS options", - ) - netFlags.StringSlice( - "dns-search", getDefaultDNSSearches(), - "Set custom DNS search domains", - ) - netFlags.String( - "ip", "", - "Specify a static IPv4 address for the container", - ) - netFlags.String( - "mac-address", "", - "Container MAC address (e.g. 92:d0:c6:0a:29:33)", - ) - netFlags.String( - "network", getDefaultNetNS(), - "Connect a container to a network", - ) - netFlags.StringSliceP( - "publish", "p", []string{}, - "Publish a container's port, or a range of ports, to the host (default [])", - ) - netFlags.Bool( - "no-hosts", false, - "Do not create /etc/hosts within the container, instead use the version from the image", - ) - return &netFlags -} - -func getCreateFlags(c *cliconfig.PodmanCommand) { - createFlags := c.Flags() - createFlags.StringSlice( - "annotation", []string{}, - "Add annotations to container (key:value)", - ) - createFlags.StringSliceP( - "attach", "a", []string{}, - "Attach to STDIN, STDOUT or STDERR", - ) - createFlags.String( - "authfile", buildahcli.GetDefaultAuthFile(), - "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override", - ) - createFlags.String( - "blkio-weight", "", - "Block IO weight (relative weight) accepts a weight value between 10 and 1000.", - ) - createFlags.StringSlice( - "blkio-weight-device", []string{}, - "Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`)", - ) - createFlags.StringSlice( - "cap-add", []string{}, - "Add capabilities to the container", - ) - createFlags.StringSlice( - "cap-drop", []string{}, - "Drop capabilities from the container", - ) - createFlags.String( - "cgroupns", getDefaultCgroupNS(), - "cgroup namespace to use", - ) - createFlags.String( - "cgroups", "enabled", - `control container cgroup configuration ("enabled"|"disabled"|"no-conmon")`, - ) - createFlags.String( - "cgroup-parent", "", - "Optional parent cgroup for the container", - ) - createFlags.String( - "cidfile", "", - "Write the container ID to the file", - ) - createFlags.String( - "conmon-pidfile", "", - "Path to the file that will receive the PID of conmon", - ) - createFlags.Uint64( - "cpu-period", 0, - "Limit the CPU CFS (Completely Fair Scheduler) period", - ) - createFlags.Int64( - "cpu-quota", 0, - "Limit the CPU CFS (Completely Fair Scheduler) quota", - ) - createFlags.Uint64( - "cpu-rt-period", 0, - "Limit the CPU real-time period in microseconds", - ) - createFlags.Int64( - "cpu-rt-runtime", 0, - "Limit the CPU real-time runtime in microseconds", - ) - createFlags.Uint64( - "cpu-shares", 0, - "CPU shares (relative weight)", - ) - createFlags.Float64( - "cpus", 0, - "Number of CPUs. The default is 0.000 which means no limit", - ) - createFlags.String( - "cpuset-cpus", "", - "CPUs in which to allow execution (0-3, 0,1)", - ) - createFlags.String( - "cpuset-mems", "", - "Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.", - ) - createFlags.BoolP( - "detach", "d", false, - "Run container in background and print container ID", - ) - createFlags.String( - "detach-keys", getDefaultDetachKeys(), - "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`", - ) - createFlags.StringSlice( - "device", getDefaultDevices(), - fmt.Sprintf("Add a host device to the container"), - ) - createFlags.StringSlice( - "device-cgroup-rule", []string{}, - "Add a rule to the cgroup allowed devices list", - ) - createFlags.StringSlice( - "device-read-bps", []string{}, - "Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)", - ) - createFlags.StringSlice( - "device-read-iops", []string{}, - "Limit read rate (IO per second) from a device (e.g. --device-read-iops=/dev/sda:1000)", - ) - createFlags.StringSlice( - "device-write-bps", []string{}, - "Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)", - ) - createFlags.StringSlice( - "device-write-iops", []string{}, - "Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)", - ) - createFlags.String( - "entrypoint", "", - "Overwrite the default ENTRYPOINT of the image", - ) - createFlags.StringArrayP( - "env", "e", getDefaultEnv(), - "Set environment variables in container", - ) - createFlags.Bool( - "env-host", false, "Use all current host environment variables in container", - ) - createFlags.StringSlice( - "env-file", []string{}, - "Read in a file of environment variables", - ) - createFlags.StringSlice( - "expose", []string{}, - "Expose a port or a range of ports", - ) - createFlags.StringSlice( - "gidmap", []string{}, - "GID map to use for the user namespace", - ) - createFlags.StringSlice( - "group-add", []string{}, - "Add additional groups to join", - ) - createFlags.Bool( - "help", false, "", - ) - createFlags.String( - "health-cmd", "", - "set a healthcheck command for the container ('none' disables the existing healthcheck)", - ) - createFlags.String( - "health-interval", cliconfig.DefaultHealthCheckInterval, - "set an interval for the healthchecks (a value of disable results in no automatic timer setup)", - ) - createFlags.Uint( - "health-retries", cliconfig.DefaultHealthCheckRetries, - "the number of retries allowed before a healthcheck is considered to be unhealthy", - ) - createFlags.String( - "health-start-period", cliconfig.DefaultHealthCheckStartPeriod, - "the initialization time needed for a container to bootstrap", - ) - createFlags.String( - "health-timeout", cliconfig.DefaultHealthCheckTimeout, - "the maximum time allowed to complete the healthcheck before an interval is considered failed", - ) - createFlags.StringP( - "hostname", "h", "", - "Set container hostname", - ) - createFlags.Bool( - "http-proxy", true, - "Set proxy environment variables in the container based on the host proxy vars", - ) - createFlags.String( - "image-volume", cliconfig.DefaultImageVolume, - `Tells podman how to handle the builtin image volumes ("bind"|"tmpfs"|"ignore")`, - ) - createFlags.Bool( - "init", false, - "Run an init binary inside the container that forwards signals and reaps processes", - ) - createFlags.String( - "init-path", getDefaultInitPath(), - // Do not use the Value field for setting the default value to determine user input (i.e., non-empty string) - fmt.Sprintf("Path to the container-init binary"), - ) - createFlags.BoolP( - "interactive", "i", false, - "Keep STDIN open even if not attached", - ) - createFlags.String( - "ipc", getDefaultIPCNS(), - "IPC namespace to use", - ) - createFlags.String( - "kernel-memory", "", - "Kernel memory limit "+sizeWithUnitFormat, - ) - createFlags.StringArrayP( - "label", "l", []string{}, - "Set metadata on container", - ) - createFlags.StringSlice( - "label-file", []string{}, - "Read in a line delimited file of labels", - ) - createFlags.String( - "log-driver", "", - "Logging driver for the container", - ) - createFlags.StringSlice( - "log-opt", []string{}, - "Logging driver options", - ) - createFlags.StringP( - "memory", "m", "", - "Memory limit "+sizeWithUnitFormat, - ) - createFlags.String( - "memory-reservation", "", - "Memory soft limit "+sizeWithUnitFormat, - ) - createFlags.String( - "memory-swap", "", - "Swap limit equal to memory plus swap: '-1' to enable unlimited swap", - ) - createFlags.Int64( - "memory-swappiness", -1, - "Tune container memory swappiness (0 to 100, or -1 for system default)", - ) - createFlags.String( - "name", "", - "Assign a name to the container", - ) - createFlags.Bool( - "no-healthcheck", false, - "Disable healthchecks on container", - ) - createFlags.Bool( - "oom-kill-disable", false, - "Disable OOM Killer", - ) - createFlags.Int( - "oom-score-adj", 0, - "Tune the host's OOM preferences (-1000 to 1000)", - ) - createFlags.String( - "override-arch", "", - "use `ARCH` instead of the architecture of the machine for choosing images", - ) - markFlagHidden(createFlags, "override-arch") - createFlags.String( - "override-os", "", - "use `OS` instead of the running OS for choosing images", - ) - markFlagHidden(createFlags, "override-os") - createFlags.String( - "pid", getDefaultPidNS(), - "PID namespace to use", - ) - createFlags.Int64( - "pids-limit", getDefaultPidsLimit(), - getDefaultPidsDescription(), - ) - createFlags.String( - "pod", "", - "Run container in an existing pod", - ) - createFlags.Bool( - "privileged", false, - "Give extended privileges to container", - ) - createFlags.BoolP( - "publish-all", "P", false, - "Publish all exposed ports to random ports on the host interface", - ) - createFlags.String( - "pull", "missing", - `Pull image before creating ("always"|"missing"|"never")`, - ) - createFlags.BoolP( - "quiet", "q", false, - "Suppress output information when pulling images", - ) - createFlags.Bool( - "read-only", false, - "Make containers root filesystem read-only", - ) - createFlags.Bool( - "read-only-tmpfs", true, - "When running containers in read-only mode mount a read-write tmpfs on /run, /tmp and /var/tmp", - ) - createFlags.String( - "restart", "", - `Restart policy to apply when a container exits ("always"|"no"|"on-failure")`, - ) - createFlags.Bool( - "rm", false, - "Remove container (and pod if created) after exit", - ) - createFlags.Bool( - "rootfs", false, - "The first argument is not an image but the rootfs to the exploded container", - ) - createFlags.StringArray( - "security-opt", getDefaultSecurityOptions(), - fmt.Sprintf("Security Options"), - ) - createFlags.String( - "shm-size", getDefaultShmSize(), - "Size of /dev/shm "+sizeWithUnitFormat, - ) - createFlags.String( - "stop-signal", "", - "Signal to stop a container. Default is SIGTERM", - ) - createFlags.Uint( - "stop-timeout", defaultContainerConfig.Engine.StopTimeout, - "Timeout (in seconds) to stop a container. Default is 10", - ) - createFlags.StringSlice( - "storage-opt", []string{}, - "Storage driver options per container", - ) - createFlags.String( - "subgidname", "", - "Name of range listed in /etc/subgid for use in user namespace", - ) - createFlags.String( - "subuidname", "", - "Name of range listed in /etc/subuid for use in user namespace", - ) - - createFlags.StringSlice( - "sysctl", getDefaultSysctls(), - "Sysctl options", - ) - createFlags.String( - "systemd", "true", - `Run container in systemd mode ("true"|"false"|"always")`, - ) - createFlags.StringArray( - "tmpfs", []string{}, - "Mount a temporary filesystem (`tmpfs`) into a container", - ) - createFlags.BoolP( - "tty", "t", false, - "Allocate a pseudo-TTY for container", - ) - createFlags.StringSlice( - "uidmap", []string{}, - "UID map to use for the user namespace", - ) - createFlags.StringSlice( - "ulimit", getDefaultUlimits(), - "Ulimit options", - ) - createFlags.StringP( - "user", "u", "", - "Username or UID (format: <name|uid>[:<group|gid>])", - ) - createFlags.String( - "userns", getDefaultUserNS(), - "User namespace to use", - ) - createFlags.String( - "uts", getDefaultUTSNS(), - "UTS namespace to use", - ) - createFlags.StringArray( - "mount", []string{}, - "Attach a filesystem mount to the container", - ) - createFlags.StringArrayP( - "volume", "v", getDefaultVolumes(), - "Bind mount a volume into the container", - ) - createFlags.StringSlice( - "volumes-from", []string{}, - "Mount volumes from the specified container(s)", - ) - createFlags.StringP( - "workdir", "w", "", - "Working directory inside the container", - ) - createFlags.String( - "seccomp-policy", "default", - "Policy for selecting a seccomp profile (experimental)", - ) -} - -func getFormat(c *cliconfig.PodmanCommand) (string, error) { - format := strings.ToLower(c.String("format")) - if strings.HasPrefix(format, buildah.OCI) { - return buildah.OCIv1ImageManifest, nil - } - - if strings.HasPrefix(format, buildah.DOCKER) { - return buildah.Dockerv2ImageManifest, nil - } - return "", errors.Errorf("unrecognized image type %q", format) -} - -// scrubServer removes 'http://' or 'https://' from the front of the -// server/registry string if either is there. This will be mostly used -// for user input from 'podman login' and 'podman logout'. -func scrubServer(server string) string { - server = strings.TrimPrefix(server, "https://") - return strings.TrimPrefix(server, "http://") -} - -// HelpTemplate returns the help template for podman commands -// This uses the short and long options. -// command should not use this. -func HelpTemplate() string { - return `{{.Short}} - -Description: - {{.Long}} - -{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` -} - -// UsageTemplate returns the usage template for podman commands -// This blocks the desplaying of the global options. The main podman -// command should not use this. -func UsageTemplate() string { - return `Usage:{{if (and .Runnable (not .HasAvailableSubCommands))}} - {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} - {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} - -Aliases: - {{.NameAndAliases}}{{end}}{{if .HasExample}} - -Examples: - {{.Example}}{{end}}{{if .HasAvailableSubCommands}} - -Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} - {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} - -Flags: -{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} -{{end}} -` -} diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go new file mode 100644 index 000000000..49a40dfa0 --- /dev/null +++ b/cmd/podman/common/create.go @@ -0,0 +1,517 @@ +package common + +import ( + "fmt" + + buildahcli "github.com/containers/buildah/pkg/cli" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/spf13/pflag" +) + +const sizeWithUnitFormat = "(format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))" + +var containerConfig = registry.PodmanConfig() + +func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { + createFlags := pflag.FlagSet{} + createFlags.StringSliceVar( + &cf.Annotation, + "annotation", []string{}, + "Add annotations to container (key:value)", + ) + createFlags.StringSliceVarP( + &cf.Attach, + "attach", "a", []string{}, + "Attach to STDIN, STDOUT or STDERR", + ) + createFlags.StringVar( + &cf.Authfile, + "authfile", buildahcli.GetDefaultAuthFile(), + "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override", + ) + createFlags.StringVar( + &cf.BlkIOWeight, + "blkio-weight", "", + "Block IO weight (relative weight) accepts a weight value between 10 and 1000.", + ) + createFlags.StringSliceVar( + &cf.BlkIOWeightDevice, + "blkio-weight-device", []string{}, + "Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`)", + ) + createFlags.StringSliceVar( + &cf.CapAdd, + "cap-add", []string{}, + "Add capabilities to the container", + ) + createFlags.StringSliceVar( + &cf.CapDrop, + "cap-drop", []string{}, + "Drop capabilities from the container", + ) + createFlags.StringVar( + &cf.CGroupsNS, + "cgroupns", getDefaultCgroupNS(), + "cgroup namespace to use", + ) + createFlags.StringVar( + &cf.CGroups, + "cgroups", "enabled", + `control container cgroup configuration ("enabled"|"disabled"|"no-conmon")`, + ) + createFlags.StringVar( + &cf.CGroupParent, + "cgroup-parent", "", + "Optional parent cgroup for the container", + ) + createFlags.StringVar( + &cf.CIDFile, + "cidfile", "", + "Write the container ID to the file", + ) + createFlags.StringVar( + &cf.ConmonPIDFile, + "conmon-pidfile", "", + "Path to the file that will receive the PID of conmon", + ) + createFlags.Uint64Var( + &cf.CPUPeriod, + "cpu-period", 0, + "Limit the CPU CFS (Completely Fair Scheduler) period", + ) + createFlags.Int64Var( + &cf.CPUQuota, + "cpu-quota", 0, + "Limit the CPU CFS (Completely Fair Scheduler) quota", + ) + createFlags.Uint64Var( + &cf.CPURTPeriod, + "cpu-rt-period", 0, + "Limit the CPU real-time period in microseconds", + ) + createFlags.Int64Var( + &cf.CPURTRuntime, + "cpu-rt-runtime", 0, + "Limit the CPU real-time runtime in microseconds", + ) + createFlags.Uint64Var( + &cf.CPUShares, + "cpu-shares", 0, + "CPU shares (relative weight)", + ) + createFlags.Float64Var( + &cf.CPUS, + "cpus", 0, + "Number of CPUs. The default is 0.000 which means no limit", + ) + createFlags.StringVar( + &cf.CPUSetCPUs, + "cpuset-cpus", "", + "CPUs in which to allow execution (0-3, 0,1)", + ) + createFlags.StringVar( + &cf.CPUSetMems, + "cpuset-mems", "", + "Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.", + ) + createFlags.BoolVarP( + &cf.Detach, + "detach", "d", false, + "Run container in background and print container ID", + ) + createFlags.StringVar( + &cf.DetachKeys, + "detach-keys", GetDefaultDetachKeys(), + "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-cf`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`", + ) + createFlags.StringSliceVar( + &cf.Device, + "device", getDefaultDevices(), + fmt.Sprintf("Add a host device to the container"), + ) + createFlags.StringSliceVar( + &cf.DeviceCGroupRule, + "device-cgroup-rule", []string{}, + "Add a rule to the cgroup allowed devices list", + ) + createFlags.StringSliceVar( + &cf.DeviceReadBPs, + "device-read-bps", []string{}, + "Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)", + ) + createFlags.StringSliceVar( + &cf.DeviceReadIOPs, + "device-read-iops", []string{}, + "Limit read rate (IO per second) from a device (e.g. --device-read-iops=/dev/sda:1000)", + ) + createFlags.StringSliceVar( + &cf.DeviceWriteBPs, + "device-write-bps", []string{}, + "Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)", + ) + createFlags.StringSliceVar( + &cf.DeviceWriteIOPs, + "device-write-iops", []string{}, + "Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)", + ) + createFlags.StringVar( + &cf.Entrypoint, + "entrypoint", "", + "Overwrite the default ENTRYPOINT of the image", + ) + createFlags.StringArrayVarP( + &cf.env, + "env", "e", getDefaultEnv(), + "Set environment variables in container", + ) + createFlags.BoolVar( + &cf.EnvHost, + "env-host", false, "Use all current host environment variables in container", + ) + createFlags.StringSliceVar( + &cf.EnvFile, + "env-file", []string{}, + "Read in a file of environment variables", + ) + createFlags.StringSliceVar( + &cf.Expose, + "expose", []string{}, + "Expose a port or a range of ports", + ) + createFlags.StringSliceVar( + &cf.GIDMap, + "gidmap", []string{}, + "GID map to use for the user namespace", + ) + createFlags.StringSliceVar( + &cf.GroupAdd, + "group-add", []string{}, + "Add additional groups to join", + ) + createFlags.Bool( + "help", false, "", + ) + createFlags.StringVar( + &cf.HealthCmd, + "health-cmd", "", + "set a healthcheck command for the container ('none' disables the existing healthcheck)", + ) + createFlags.StringVar( + &cf.HealthInterval, + "health-interval", DefaultHealthCheckInterval, + "set an interval for the healthchecks (a value of disable results in no automatic timer setup)", + ) + createFlags.UintVar( + &cf.HealthRetries, + "health-retries", DefaultHealthCheckRetries, + "the number of retries allowed before a healthcheck is considered to be unhealthy", + ) + createFlags.StringVar( + &cf.HealthStartPeriod, + "health-start-period", DefaultHealthCheckStartPeriod, + "the initialization time needed for a container to bootstrap", + ) + createFlags.StringVar( + &cf.HealthTimeout, + "health-timeout", DefaultHealthCheckTimeout, + "the maximum time allowed to complete the healthcheck before an interval is considered failed", + ) + createFlags.StringVarP( + &cf.Hostname, + "hostname", "h", "", + "Set container hostname", + ) + createFlags.BoolVar( + &cf.HTTPProxy, + "http-proxy", true, + "Set proxy environment variables in the container based on the host proxy vars", + ) + createFlags.StringVar( + &cf.ImageVolume, + "image-volume", DefaultImageVolume, + `Tells podman how to handle the builtin image volumes ("bind"|"tmpfs"|"ignore")`, + ) + createFlags.BoolVar( + &cf.Init, + "init", false, + "Run an init binary inside the container that forwards signals and reaps processes", + ) + createFlags.StringVar( + &cf.InitPath, + "init-path", getDefaultInitPath(), + // Do not use the Value field for setting the default value to determine user input (i.e., non-empty string) + fmt.Sprintf("Path to the container-init binary"), + ) + createFlags.BoolVarP( + &cf.Interactive, + "interactive", "i", false, + "Keep STDIN open even if not attached", + ) + createFlags.StringVar( + &cf.IPC, + "ipc", getDefaultIPCNS(), + "IPC namespace to use", + ) + createFlags.StringVar( + &cf.KernelMemory, + "kernel-memory", "", + "Kernel memory limit "+sizeWithUnitFormat, + ) + createFlags.StringArrayVarP( + &cf.Label, + "label", "l", []string{}, + "Set metadata on container", + ) + createFlags.StringSliceVar( + &cf.LabelFile, + "label-file", []string{}, + "Read in a line delimited file of labels", + ) + createFlags.StringVar( + &cf.LogDriver, + "log-driver", "", + "Logging driver for the container", + ) + createFlags.StringSliceVar( + &cf.LogOptions, + "log-opt", []string{}, + "Logging driver options", + ) + createFlags.StringVarP( + &cf.Memory, + "memory", "m", "", + "Memory limit "+sizeWithUnitFormat, + ) + createFlags.StringVar( + &cf.MemoryReservation, + "memory-reservation", "", + "Memory soft limit "+sizeWithUnitFormat, + ) + createFlags.StringVar( + &cf.MemorySwap, + "memory-swap", "", + "Swap limit equal to memory plus swap: '-1' to enable unlimited swap", + ) + createFlags.Int64Var( + &cf.MemorySwappiness, + "memory-swappiness", -1, + "Tune container memory swappiness (0 to 100, or -1 for system default)", + ) + createFlags.StringVar( + &cf.Name, + "name", "", + "Assign a name to the container", + ) + createFlags.BoolVar( + &cf.NoHealthCheck, + "no-healthcheck", false, + "Disable healthchecks on container", + ) + createFlags.BoolVar( + &cf.OOMKillDisable, + "oom-kill-disable", false, + "Disable OOM Killer", + ) + createFlags.IntVar( + &cf.OOMScoreAdj, + "oom-score-adj", 0, + "Tune the host's OOM preferences (-1000 to 1000)", + ) + createFlags.StringVar( + &cf.OverrideArch, + "override-arch", "", + "use `ARCH` instead of the architecture of the machine for choosing images", + ) + // markFlagHidden(createFlags, "override-arch") + createFlags.StringVar( + &cf.OverrideOS, + "override-os", "", + "use `OS` instead of the running OS for choosing images", + ) + // markFlagHidden(createFlags, "override-os") + createFlags.StringVar( + &cf.PID, + "pid", getDefaultPidNS(), + "PID namespace to use", + ) + createFlags.Int64Var( + &cf.PIDsLimit, + "pids-limit", getDefaultPidsLimit(), + getDefaultPidsDescription(), + ) + createFlags.StringVar( + &cf.Pod, + "pod", "", + "Run container in an existing pod", + ) + createFlags.BoolVar( + &cf.Privileged, + "privileged", false, + "Give extended privileges to container", + ) + createFlags.BoolVarP( + &cf.PublishAll, + "publish-all", "P", false, + "Publish all exposed ports to random ports on the host interface", + ) + createFlags.StringVar( + &cf.Pull, + "pull", "missing", + `Pull image before creating ("always"|"missing"|"never")`, + ) + createFlags.BoolVarP( + &cf.Quiet, + "quiet", "q", false, + "Suppress output information when pulling images", + ) + createFlags.BoolVar( + &cf.ReadOnly, + "read-only", false, + "Make containers root filesystem read-only", + ) + createFlags.BoolVar( + &cf.ReadOnlyTmpFS, + "read-only-tmpfs", true, + "When running containers in read-only mode mount a read-write tmpfs on /run, /tmp and /var/tmp", + ) + createFlags.StringVar( + &cf.Restart, + "restart", "", + `Restart policy to apply when a container exits ("always"|"no"|"on-failure")`, + ) + createFlags.BoolVar( + &cf.Rm, + "rm", false, + "Remove container (and pod if created) after exit", + ) + createFlags.BoolVar( + &cf.RootFS, + "rootfs", false, + "The first argument is not an image but the rootfs to the exploded container", + ) + createFlags.StringArrayVar( + &cf.SecurityOpt, + "security-opt", getDefaultSecurityOptions(), + "Security Options", + ) + createFlags.StringVar( + &cf.ShmSize, + "shm-size", getDefaultShmSize(), + "Size of /dev/shm "+sizeWithUnitFormat, + ) + createFlags.StringVar( + &cf.StopSignal, + "stop-signal", "", + "Signal to stop a container. Default is SIGTERM", + ) + createFlags.UintVar( + &cf.StopTimeout, + "stop-timeout", containerConfig.Engine.StopTimeout, + "Timeout (in seconds) to stop a container. Default is 10", + ) + createFlags.StringSliceVar( + &cf.StoreageOpt, + "storage-opt", []string{}, + "Storage driver options per container", + ) + createFlags.StringVar( + &cf.SubUIDName, + "subgidname", "", + "Name of range listed in /etc/subgid for use in user namespace", + ) + createFlags.StringVar( + &cf.SubGIDName, + "subuidname", "", + "Name of range listed in /etc/subuid for use in user namespace", + ) + + createFlags.StringSliceVar( + &cf.Sysctl, + "sysctl", getDefaultSysctls(), + "Sysctl options", + ) + createFlags.StringVar( + &cf.SystemdD, + "systemd", "true", + `Run container in systemd mode ("true"|"false"|"always")`, + ) + createFlags.StringArrayVar( + &cf.TmpFS, + "tmpfs", []string{}, + "Mount a temporary filesystem (`tmpfs`) into a container", + ) + createFlags.BoolVarP( + &cf.TTY, + "tty", "t", false, + "Allocate a pseudo-TTY for container", + ) + createFlags.StringSliceVar( + &cf.UIDMap, + "uidmap", []string{}, + "UID map to use for the user namespace", + ) + createFlags.StringSliceVar( + &cf.Ulimit, + "ulimit", getDefaultUlimits(), + "Ulimit options", + ) + createFlags.StringVarP( + &cf.User, + "user", "u", "", + "Username or UID (format: <name|uid>[:<group|gid>])", + ) + createFlags.StringVar( + &cf.UserNS, + "userns", getDefaultUserNS(), + "User namespace to use", + ) + createFlags.StringVar( + &cf.UTS, + "uts", getDefaultUTSNS(), + "UTS namespace to use", + ) + createFlags.StringArrayVar( + &cf.Mount, + "mount", []string{}, + "Attach a filesystem mount to the container", + ) + createFlags.StringArrayVarP( + &cf.Volume, + "volume", "v", getDefaultVolumes(), + "Bind mount a volume into the container", + ) + createFlags.StringSliceVar( + &cf.VolumesFrom, + "volumes-from", []string{}, + "Mount volumes from the specified container(s)", + ) + createFlags.StringVarP( + &cf.Workdir, + "workdir", "w", "", + "Working directory inside the container", + ) + createFlags.StringVar( + &cf.SeccompPolicy, + "seccomp-policy", "default", + "Policy for selecting a seccomp profile (experimental)", + ) + return &createFlags +} + +func AliasFlags(_ *pflag.FlagSet, name string) pflag.NormalizedName { + switch name { + case "healthcheck-command": + name = "health-cmd" + case "healthcheck-interval": + name = "health-interval" + case "healthcheck-retries": + name = "health-retries" + case "healthcheck-start-period": + name = "health-start-period" + case "healthcheck-timeout": + name = "health-timeout" + case "net": + name = "network" + } + return pflag.NormalizedName(name) +} diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go new file mode 100644 index 000000000..9d12e4b26 --- /dev/null +++ b/cmd/podman/common/create_opts.go @@ -0,0 +1,103 @@ +package common + +import "github.com/containers/libpod/pkg/domain/entities" + +type ContainerCLIOpts struct { + Annotation []string + Attach []string + Authfile string + BlkIOWeight string + BlkIOWeightDevice []string + CapAdd []string + CapDrop []string + CGroupsNS string + CGroups string + CGroupParent string + CIDFile string + ConmonPIDFile string + CPUPeriod uint64 + CPUQuota int64 + CPURTPeriod uint64 + CPURTRuntime int64 + CPUShares uint64 + CPUS float64 + CPUSetCPUs string + CPUSetMems string + Detach bool + DetachKeys string + Device []string + DeviceCGroupRule []string + DeviceReadBPs []string + DeviceReadIOPs []string + DeviceWriteBPs []string + DeviceWriteIOPs []string + Entrypoint string + env []string + EnvHost bool + EnvFile []string + Expose []string + GIDMap []string + GroupAdd []string + HealthCmd string + HealthInterval string + HealthRetries uint + HealthStartPeriod string + HealthTimeout string + Hostname string + HTTPProxy bool + ImageVolume string + Init bool + InitPath string + Interactive bool + IPC string + KernelMemory string + Label []string + LabelFile []string + LogDriver string + LogOptions []string + Memory string + MemoryReservation string + MemorySwap string + MemorySwappiness int64 + Name string + NoHealthCheck bool + OOMKillDisable bool + OOMScoreAdj int + OverrideArch string + OverrideOS string + PID string + PIDsLimit int64 + Pod string + Privileged bool + PublishAll bool + Pull string + Quiet bool + ReadOnly bool + ReadOnlyTmpFS bool + Restart string + Rm bool + RootFS bool + SecurityOpt []string + ShmSize string + StopSignal string + StopTimeout uint + StoreageOpt []string + SubUIDName string + SubGIDName string + Sysctl []string + SystemdD string + TmpFS []string + TTY bool + UIDMap []string + Ulimit []string + User string + UserNS string + UTS string + Mount []string + Volume []string + VolumesFrom []string + Workdir string + SeccompPolicy string + + Net *entities.NetOptions +} diff --git a/cmd/podman/common/createparse.go b/cmd/podman/common/createparse.go new file mode 100644 index 000000000..aca6f752e --- /dev/null +++ b/cmd/podman/common/createparse.go @@ -0,0 +1,51 @@ +package common + +import ( + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" +) + +// validate determines if the flags and values given by the user are valid. things checked +// by validate must not need any state information on the flag (i.e. changed) +func (c *ContainerCLIOpts) validate() error { + var () + if c.Rm && c.Restart != "" && c.Restart != "no" { + return errors.Errorf("the --rm option conflicts with --restart") + } + + if _, err := util.ValidatePullType(c.Pull); err != nil { + return err + } + // Verify the additional hosts are in correct format + for _, host := range c.Net.AddHosts { + if _, err := parse.ValidateExtraHost(host); err != nil { + return err + } + } + + if dnsSearches := c.Net.DNSSearch; len(dnsSearches) > 0 { + // Validate domains are good + for _, dom := range dnsSearches { + if dom == "." { + if len(dnsSearches) > 1 { + return errors.Errorf("cannot pass additional search domains when also specifying '.'") + } + continue + } + if _, err := parse.ValidateDomain(dom); err != nil { + return err + } + } + } + var imageVolType = map[string]string{ + "bind": "", + "tmpfs": "", + "ignore": "", + } + if _, ok := imageVolType[c.ImageVolume]; !ok { + return errors.Errorf("invalid image-volume type %q. Pick one of bind, tmpfs, or ignore", c.ImageVolume) + } + return nil + +} diff --git a/cmd/podman/common/default.go b/cmd/podman/common/default.go new file mode 100644 index 000000000..853f87ab6 --- /dev/null +++ b/cmd/podman/common/default.go @@ -0,0 +1,135 @@ +package common + +import ( + "fmt" + "os" + + "github.com/containers/buildah/pkg/parse" + "github.com/containers/libpod/pkg/apparmor" + "github.com/containers/libpod/pkg/cgroups" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/specgen" + "github.com/containers/libpod/pkg/sysinfo" + "github.com/opencontainers/selinux/go-selinux" +) + +var ( + // DefaultHealthCheckInterval default value + DefaultHealthCheckInterval = "30s" + // DefaultHealthCheckRetries default value + DefaultHealthCheckRetries uint = 3 + // DefaultHealthCheckStartPeriod default value + DefaultHealthCheckStartPeriod = "0s" + // DefaultHealthCheckTimeout default value + DefaultHealthCheckTimeout = "30s" + // DefaultImageVolume default value + DefaultImageVolume = "bind" +) + +// TODO these options are directly embedded into many of the CLI cobra values, as such +// this approach will not work in a remote client. so we will need to likely do something like a +// supported and unsupported approach here and backload these options into the specgen +// once we are "on" the host system. +func getDefaultSecurityOptions() []string { + securityOpts := []string{} + if containerConfig.Containers.SeccompProfile != "" && containerConfig.Containers.SeccompProfile != parse.SeccompDefaultPath { + securityOpts = append(securityOpts, fmt.Sprintf("seccomp=%s", containerConfig.Containers.SeccompProfile)) + } + if apparmor.IsEnabled() && containerConfig.Containers.ApparmorProfile != "" { + securityOpts = append(securityOpts, fmt.Sprintf("apparmor=%s", containerConfig.Containers.ApparmorProfile)) + } + if selinux.GetEnabled() && !containerConfig.Containers.EnableLabeling { + securityOpts = append(securityOpts, fmt.Sprintf("label=%s", selinux.DisableSecOpt()[0])) + } + return securityOpts +} + +// getDefaultSysctls +func getDefaultSysctls() []string { + return containerConfig.Containers.DefaultSysctls +} + +func getDefaultVolumes() []string { + return containerConfig.Containers.Volumes +} + +func getDefaultDevices() []string { + return containerConfig.Containers.Devices +} + +func getDefaultDNSServers() []string { //nolint + return containerConfig.Containers.DNSServers +} + +func getDefaultDNSSearches() []string { //nolint + return containerConfig.Containers.DNSSearches +} + +func getDefaultDNSOptions() []string { //nolint + return containerConfig.Containers.DNSOptions +} + +func getDefaultEnv() []string { + return containerConfig.Containers.Env +} + +func getDefaultInitPath() string { + return containerConfig.Containers.InitPath +} + +func getDefaultIPCNS() string { + return containerConfig.Containers.IPCNS +} + +func getDefaultPidNS() string { + return containerConfig.Containers.PidNS +} + +func getDefaultNetNS() string { //nolint + if containerConfig.Containers.NetNS == string(specgen.Private) && rootless.IsRootless() { + return string(specgen.Slirp) + } + return containerConfig.Containers.NetNS +} + +func getDefaultCgroupNS() string { + return containerConfig.Containers.CgroupNS +} + +func getDefaultUTSNS() string { + return containerConfig.Containers.UTSNS +} + +func getDefaultShmSize() string { + return containerConfig.Containers.ShmSize +} + +func getDefaultUlimits() []string { + return containerConfig.Containers.DefaultUlimits +} + +func getDefaultUserNS() string { + userns := os.Getenv("PODMAN_USERNS") + if userns != "" { + return userns + } + return containerConfig.Containers.UserNS +} + +func getDefaultPidsLimit() int64 { + if rootless.IsRootless() { + cgroup2, _ := cgroups.IsCgroup2UnifiedMode() + if cgroup2 { + return containerConfig.Containers.PidsLimit + } + } + return sysinfo.GetDefaultPidsLimit() +} + +func getDefaultPidsDescription() string { + return "Tune container pids limit (set 0 for unlimited)" +} + +func GetDefaultDetachKeys() string { + return containerConfig.Engine.DetachKeys +} diff --git a/cmd/podman/common/inspect.go b/cmd/podman/common/inspect.go new file mode 100644 index 000000000..dfc6fe679 --- /dev/null +++ b/cmd/podman/common/inspect.go @@ -0,0 +1,18 @@ +package common + +import ( + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +// AddInspectFlagSet takes a command and adds the inspect flags and returns an InspectOptions object +// Since this cannot live in `package main` it lives here until a better home is found +func AddInspectFlagSet(cmd *cobra.Command) *entities.InspectOptions { + opts := entities.InspectOptions{} + + flags := cmd.Flags() + flags.BoolVarP(&opts.Size, "size", "s", false, "Display total file size") + flags.StringVarP(&opts.Format, "format", "f", "", "Change the output format to a Go template") + + return &opts +} diff --git a/cmd/podman/common/netflags.go b/cmd/podman/common/netflags.go new file mode 100644 index 000000000..41eed2988 --- /dev/null +++ b/cmd/podman/common/netflags.go @@ -0,0 +1,100 @@ +package common + +import ( + "net" + + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +func GetNetFlags() *pflag.FlagSet { + netFlags := pflag.FlagSet{} + netFlags.StringSlice( + "add-host", []string{}, + "Add a custom host-to-IP mapping (host:ip) (default [])", + ) + netFlags.StringSlice( + "dns", getDefaultDNSServers(), + "Set custom DNS servers", + ) + netFlags.StringSlice( + "dns-opt", getDefaultDNSOptions(), + "Set custom DNS options", + ) + netFlags.StringSlice( + "dns-search", getDefaultDNSSearches(), + "Set custom DNS search domains", + ) + netFlags.String( + "ip", "", + "Specify a static IPv4 address for the container", + ) + netFlags.String( + "mac-address", "", + "Container MAC address (e.g. 92:d0:c6:0a:29:33)", + ) + netFlags.String( + "network", getDefaultNetNS(), + "Connect a container to a network", + ) + netFlags.StringSliceP( + "publish", "p", []string{}, + "Publish a container's port, or a range of ports, to the host (default [])", + ) + netFlags.Bool( + "no-hosts", false, + "Do not create /etc/hosts within the container, instead use the version from the image", + ) + return &netFlags +} + +func NetFlagsToNetOptions(cmd *cobra.Command) (*entities.NetOptions, error) { + var ( + err error + ) + opts := entities.NetOptions{} + opts.AddHosts, err = cmd.Flags().GetStringSlice("add-host") + if err != nil { + return nil, err + } + servers, err := cmd.Flags().GetStringSlice("dns") + if err != nil { + return nil, err + } + for _, d := range servers { + if d == "none" { + opts.DNSHost = true + break + } + opts.DNSServers = append(opts.DNSServers, net.ParseIP(d)) + } + opts.DNSSearch, err = cmd.Flags().GetStringSlice("dns-search") + if err != nil { + return nil, err + } + + m, err := cmd.Flags().GetString("mac-address") + if err != nil { + return nil, err + } + if len(m) > 0 { + mac, err := net.ParseMAC(m) + if err != nil { + return nil, err + } + opts.StaticMAC = &mac + } + inputPorts, err := cmd.Flags().GetStringSlice("publish") + if err != nil { + return nil, err + } + if len(inputPorts) > 0 { + opts.PublishPorts, err = createPortBindings(inputPorts) + if err != nil { + return nil, err + } + } + opts.NoHosts, err = cmd.Flags().GetBool("no-hosts") + return &opts, err +} diff --git a/cmd/podman/common/ports.go b/cmd/podman/common/ports.go new file mode 100644 index 000000000..7e2b1e79d --- /dev/null +++ b/cmd/podman/common/ports.go @@ -0,0 +1,126 @@ +package common + +import ( + "fmt" + "net" + "strconv" + + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/docker/go-connections/nat" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// ExposedPorts parses user and image ports and returns binding information +func ExposedPorts(expose []string, publish []ocicni.PortMapping, publishAll bool, imageExposedPorts map[string]struct{}) ([]ocicni.PortMapping, error) { + containerPorts := make(map[string]string) + + // TODO this needs to be added into a something that + // has access to an imageengine + // add expose ports from the image itself + //for expose := range imageExposedPorts { + // _, port := nat.SplitProtoPort(expose) + // containerPorts[port] = "" + //} + + // add the expose ports from the user (--expose) + // can be single or a range + for _, expose := range expose { + //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>] + _, port := nat.SplitProtoPort(expose) + //parse the start and end port and create a sequence of ports to expose + //if expose a port, the start and end port are the same + start, end, err := nat.ParsePortRange(port) + if err != nil { + return nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", expose, err) + } + for i := start; i <= end; i++ { + containerPorts[strconv.Itoa(int(i))] = "" + } + } + + // TODO/FIXME this is hell reencarnated + // parse user inputted port bindings + pbPorts, portBindings, err := nat.ParsePortSpecs([]string{}) + if err != nil { + return nil, err + } + + // delete exposed container ports if being used by -p + for i := range pbPorts { + delete(containerPorts, i.Port()) + } + + // iterate container ports and make port bindings from them + if publishAll { + for e := range containerPorts { + //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>] + //proto, port := nat.SplitProtoPort(e) + p, err := nat.NewPort("tcp", e) + if err != nil { + return nil, err + } + rp, err := getRandomPort() + if err != nil { + return nil, err + } + logrus.Debug(fmt.Sprintf("Using random host port %d with container port %d", rp, p.Int())) + portBindings[p] = CreatePortBinding(rp, "") + } + } + + // We need to see if any host ports are not populated and if so, we need to assign a + // random port to them. + for k, pb := range portBindings { + if pb[0].HostPort == "" { + hostPort, err := getRandomPort() + if err != nil { + return nil, err + } + logrus.Debug(fmt.Sprintf("Using random host port %d with container port %s", hostPort, k.Port())) + pb[0].HostPort = strconv.Itoa(hostPort) + } + } + var pms []ocicni.PortMapping + for k, v := range portBindings { + for _, pb := range v { + hp, err := strconv.Atoi(pb.HostPort) + if err != nil { + return nil, err + } + pms = append(pms, ocicni.PortMapping{ + HostPort: int32(hp), + ContainerPort: int32(k.Int()), + //Protocol: "", + HostIP: pb.HostIP, + }) + } + } + return pms, nil +} + +func getRandomPort() (int, error) { + l, err := net.Listen("tcp", ":0") + if err != nil { + return 0, errors.Wrapf(err, "unable to get free port") + } + defer l.Close() + _, randomPort, err := net.SplitHostPort(l.Addr().String()) + if err != nil { + return 0, errors.Wrapf(err, "unable to determine free port") + } + rp, err := strconv.Atoi(randomPort) + if err != nil { + return 0, errors.Wrapf(err, "unable to convert random port to int") + } + return rp, nil +} + +//CreatePortBinding takes port (int) and IP (string) and creates an array of portbinding structs +func CreatePortBinding(hostPort int, hostIP string) []nat.PortBinding { + pb := nat.PortBinding{ + HostPort: strconv.Itoa(hostPort), + } + pb.HostIP = hostIP + return []nat.PortBinding{pb} +} diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go new file mode 100644 index 000000000..87194a9fb --- /dev/null +++ b/cmd/podman/common/specgen.go @@ -0,0 +1,647 @@ +package common + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/containers/image/v5/manifest" + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/libpod/define" + ann "github.com/containers/libpod/pkg/annotations" + envLib "github.com/containers/libpod/pkg/env" + ns "github.com/containers/libpod/pkg/namespaces" + "github.com/containers/libpod/pkg/specgen" + systemdGen "github.com/containers/libpod/pkg/systemd/generate" + "github.com/containers/libpod/pkg/util" + "github.com/docker/go-units" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" +) + +func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) error { + var ( + err error + //namespaces map[string]string + ) + + // validate flags as needed + if err := c.validate(); err != nil { + return nil + } + + inputCommand := args[1:] + if len(c.HealthCmd) > 0 { + s.HealthConfig, err = makeHealthCheckFromCli(c.HealthCmd, c.HealthInterval, c.HealthRetries, c.HealthTimeout, c.HealthStartPeriod) + if err != nil { + return err + } + } + + s.IDMappings, err = util.ParseIDMapping(ns.UsernsMode(c.UserNS), c.UIDMap, c.GIDMap, c.SubUIDName, c.SubGIDName) + if err != nil { + return err + } + if m := c.Memory; len(m) > 0 { + ml, err := units.RAMInBytes(m) + if err != nil { + return errors.Wrapf(err, "invalid value for memory") + } + s.ResourceLimits.Memory.Limit = &ml + } + if m := c.MemoryReservation; len(m) > 0 { + mr, err := units.RAMInBytes(m) + if err != nil { + return errors.Wrapf(err, "invalid value for memory") + } + s.ResourceLimits.Memory.Reservation = &mr + } + if m := c.MemorySwap; len(m) > 0 { + var ms int64 + if m == "-1" { + ms = int64(-1) + s.ResourceLimits.Memory.Swap = &ms + } else { + ms, err = units.RAMInBytes(m) + if err != nil { + return errors.Wrapf(err, "invalid value for memory") + } + } + s.ResourceLimits.Memory.Swap = &ms + } + if m := c.KernelMemory; len(m) > 0 { + mk, err := units.RAMInBytes(m) + if err != nil { + return errors.Wrapf(err, "invalid value for kernel-memory") + } + s.ResourceLimits.Memory.Kernel = &mk + } + if b := c.BlkIOWeight; len(b) > 0 { + u, err := strconv.ParseUint(b, 10, 16) + if err != nil { + return errors.Wrapf(err, "invalid value for blkio-weight") + } + nu := uint16(u) + s.ResourceLimits.BlockIO.Weight = &nu + } + + s.Terminal = c.TTY + ep, err := ExposedPorts(c.Expose, c.Net.PublishPorts, c.PublishAll, nil) + if err != nil { + return err + } + s.PortMappings = ep + s.Pod = c.Pod + + //s.CgroupNS = specgen.Namespace{ + // NSMode: , + // Value: "", + //} + + //s.UserNS = specgen.Namespace{} + + // 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{ + // "cgroup": c.CGroupsNS, + // "pid": c.PID, + // //"net": c.Net.Network.Value, // TODO need help here + // "ipc": c.IPC, + // "user": c.User, + // "uts": c.UTS, + //} + // + //if len(c.PID) > 0 { + // split := strings.SplitN(c.PID, ":", 2) + // // need a way to do thsi + // specgen.Namespace{ + // NSMode: split[0], + // } + // //Value: split1 if len allows + //} + // TODO this is going to have be done after things like pod creation are done because + // pod creation changes these values. + //pidMode := ns.PidMode(namespaces["pid"]) + //usernsMode := ns.UsernsMode(namespaces["user"]) + //utsMode := ns.UTSMode(namespaces["uts"]) + //cgroupMode := ns.CgroupMode(namespaces["cgroup"]) + //ipcMode := ns.IpcMode(namespaces["ipc"]) + //// 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(portBindings) > 0 { + // return nil, errors.Errorf("cannot set port bindings on an existing container network namespace") + // } + //} + + // TODO Remove when done with namespaces for realz + // Setting a default for IPC to get this working + s.IpcNS = specgen.Namespace{ + NSMode: specgen.Private, + Value: "", + } + + // TODO this is going to have to be done the libpod/server end of things + // USER + //user := c.String("user") + //if user == "" { + // switch { + // case usernsMode.IsKeepID(): + // user = fmt.Sprintf("%d:%d", rootless.GetRootlessUID(), rootless.GetRootlessGID()) + // case data == nil: + // user = "0" + // default: + // user = data.Config.User + // } + //} + + // STOP SIGNAL + signalString := "TERM" + if sig := c.StopSignal; len(sig) > 0 { + signalString = sig + } + stopSignal, err := util.ParseSignal(signalString) + if err != nil { + return err + } + s.StopSignal = &stopSignal + + // ENVIRONMENT VARIABLES + // + // Precedence order (higher index wins): + // 1) env-host, 2) image data, 3) env-file, 4) env + env := map[string]string{ + "container": "podman", + } + + // First transform the os env into a map. We need it for the labels later in + // any case. + osEnv, err := envLib.ParseSlice(os.Environ()) + if err != nil { + return errors.Wrap(err, "error parsing host environment variables") + } + + if c.EnvHost { + env = envLib.Join(env, osEnv) + } + // env-file overrides any previous variables + for _, f := range c.EnvFile { + fileEnv, err := envLib.ParseFile(f) + if err != nil { + return err + } + // File env is overridden by env. + env = envLib.Join(env, fileEnv) + } + + // env overrides any previous variables + if cmdLineEnv := c.env; len(cmdLineEnv) > 0 { + parsedEnv, err := envLib.ParseSlice(cmdLineEnv) + if err != nil { + return err + } + env = envLib.Join(env, parsedEnv) + } + s.Env = env + + // LABEL VARIABLES + labels, err := parse.GetAllLabels(c.LabelFile, c.Label) + if err != nil { + return errors.Wrapf(err, "unable to process labels") + } + + if systemdUnit, exists := osEnv[systemdGen.EnvVariable]; exists { + labels[systemdGen.EnvVariable] = systemdUnit + } + + s.Labels = labels + + // ANNOTATIONS + annotations := make(map[string]string) + + // First, add our default annotations + annotations[ann.TTY] = "false" + if c.TTY { + annotations[ann.TTY] = "true" + } + + // Last, add user annotations + for _, annotation := range c.Annotation { + splitAnnotation := strings.SplitN(annotation, "=", 2) + if len(splitAnnotation) < 2 { + return errors.Errorf("Annotations must be formatted KEY=VALUE") + } + annotations[splitAnnotation[0]] = splitAnnotation[1] + } + s.Annotations = annotations + + workDir := "/" + if wd := c.Workdir; len(wd) > 0 { + workDir = wd + } + s.WorkDir = workDir + entrypoint := []string{} + userCommand := []string{} + if ep := c.Entrypoint; len(ep) > 0 { + // Check if entrypoint specified is json + if err := json.Unmarshal([]byte(c.Entrypoint), &entrypoint); err != nil { + entrypoint = append(entrypoint, ep) + } + } + + var command []string + + // Build the command + // If we have an entry point, it goes first + if len(entrypoint) > 0 { + command = entrypoint + } + if len(inputCommand) > 0 { + // User command overrides data CMD + command = append(command, inputCommand...) + userCommand = append(userCommand, inputCommand...) + } + + if len(inputCommand) > 0 { + s.Command = userCommand + } else { + s.Command = command + } + + // SHM Size + shmSize, err := units.FromHumanSize(c.ShmSize) + if err != nil { + return errors.Wrapf(err, "unable to translate --shm-size") + } + s.ShmSize = &shmSize + s.HostAdd = c.Net.AddHosts + s.DNSServer = c.Net.DNSServers + s.DNSSearch = c.Net.DNSSearch + s.DNSOption = c.Net.DNSOptions + + // deferred, must be added on libpod side + //var ImageVolumes map[string]struct{} + //if data != nil && c.String("image-volume") != "ignore" { + // ImageVolumes = data.Config.Volumes + //} + + s.ImageVolumeMode = c.ImageVolume + systemd := c.SystemdD == "always" + if !systemd && command != nil { + x, err := strconv.ParseBool(c.SystemdD) + if err != nil { + return errors.Wrapf(err, "cannot parse bool %s", c.SystemdD) + } + if x && (command[0] == "/usr/sbin/init" || command[0] == "/sbin/init" || (filepath.Base(command[0]) == "systemd")) { + systemd = true + } + } + if systemd { + if s.StopSignal == nil { + stopSignal, err = util.ParseSignal("RTMIN+3") + if err != nil { + return errors.Wrapf(err, "error parsing systemd signal") + } + s.StopSignal = &stopSignal + } + } + swappiness := uint64(c.MemorySwappiness) + if s.ResourceLimits == nil { + s.ResourceLimits = &specs.LinuxResources{} + } + if s.ResourceLimits.Memory == nil { + s.ResourceLimits.Memory = &specs.LinuxMemory{} + } + s.ResourceLimits.Memory.Swappiness = &swappiness + + if s.LogConfiguration == nil { + s.LogConfiguration = &specgen.LogConfig{} + } + s.LogConfiguration.Driver = define.KubernetesLogging + if ld := c.LogDriver; len(ld) > 0 { + s.LogConfiguration.Driver = ld + } + if s.ResourceLimits.Pids == nil { + s.ResourceLimits.Pids = &specs.LinuxPids{} + } + s.ResourceLimits.Pids.Limit = c.PIDsLimit + if c.CGroups == "disabled" && c.PIDsLimit > 0 { + s.ResourceLimits.Pids.Limit = -1 + } + // TODO WTF + //cgroup := &cc.CgroupConfig{ + // Cgroups: c.String("cgroups"), + // Cgroupns: c.String("cgroupns"), + // CgroupParent: c.String("cgroup-parent"), + // CgroupMode: cgroupMode, + //} + // + //userns := &cc.UserConfig{ + // GroupAdd: c.StringSlice("group-add"), + // IDMappings: idmappings, + // UsernsMode: usernsMode, + // User: user, + //} + // + //uts := &cc.UtsConfig{ + // UtsMode: utsMode, + // NoHosts: c.Bool("no-hosts"), + // HostAdd: c.StringSlice("add-host"), + // Hostname: c.String("hostname"), + //} + + sysctl := map[string]string{} + if ctl := c.Sysctl; len(ctl) > 0 { + sysctl, err = util.ValidateSysctls(ctl) + if err != nil { + return err + } + } + s.Sysctl = sysctl + + s.CapAdd = c.CapAdd + s.CapDrop = c.CapDrop + s.Privileged = c.Privileged + s.ReadOnlyFilesystem = c.ReadOnly + + // TODO + // ouitside of specgen and oci though + // defaults to true, check spec/storage + //s.readon = c.ReadOnlyTmpFS + // TODO convert to map? + // check if key=value and convert + sysmap := make(map[string]string) + for _, ctl := range c.Sysctl { + splitCtl := strings.SplitN(ctl, "=", 2) + if len(splitCtl) < 2 { + return errors.Errorf("invalid sysctl value %q", ctl) + } + sysmap[splitCtl[0]] = splitCtl[1] + } + s.Sysctl = sysmap + + for _, opt := range c.SecurityOpt { + if opt == "no-new-privileges" { + s.ContainerSecurityConfig.NoNewPrivileges = true + } else { + con := strings.SplitN(opt, "=", 2) + if len(con) != 2 { + return fmt.Errorf("invalid --security-opt 1: %q", opt) + } + + switch con[0] { + case "label": + // TODO selinux opts and label opts are the same thing + s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, con[1]) + case "apparmor": + s.ContainerSecurityConfig.ApparmorProfile = con[1] + case "seccomp": + s.SeccompProfilePath = con[1] + default: + return fmt.Errorf("invalid --security-opt 2: %q", opt) + } + } + } + + // TODO any idea why this was done + // storage.go from spec/ + // grab it + //volumes := rtc.Containers.Volumes + // TODO conflict on populate? + //if v := c.Volume; len(v)> 0 { + // s.Volumes = append(volumes, c.StringSlice("volume")...) + //} + //s.volu + + //s.Mounts = c.Mount + s.VolumesFrom = c.VolumesFrom + + // TODO any idea why this was done + //devices := rtc.Containers.Devices + // TODO conflict on populate? + // + //if c.Changed("device") { + // devices = append(devices, c.StringSlice("device")...) + //} + + // TODO things i cannot find in spec + // we dont think these are in the spec + // init - initbinary + // initpath + s.Stdin = c.Interactive + // quiet + //DeviceCgroupRules: c.StringSlice("device-cgroup-rule"), + + if bps := c.DeviceReadBPs; len(bps) > 0 { + if s.ThrottleReadBpsDevice, err = parseThrottleBPSDevices(bps); err != nil { + return err + } + } + + if bps := c.DeviceWriteBPs; len(bps) > 0 { + if s.ThrottleWriteBpsDevice, err = parseThrottleBPSDevices(bps); err != nil { + return err + } + } + + if iops := c.DeviceReadIOPs; len(iops) > 0 { + if s.ThrottleReadIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil { + return err + } + } + + if iops := c.DeviceWriteIOPs; len(iops) > 0 { + if s.ThrottleWriteIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil { + return err + } + } + + s.ResourceLimits.Memory.DisableOOMKiller = &c.OOMKillDisable + + // Rlimits/Ulimits + for _, u := range c.Ulimit { + if u == "host" { + s.Rlimits = nil + break + } + ul, err := units.ParseUlimit(u) + if err != nil { + return errors.Wrapf(err, "ulimit option %q requires name=SOFT:HARD, failed to be parsed", u) + } + rl := specs.POSIXRlimit{ + Type: ul.Name, + Hard: uint64(ul.Hard), + Soft: uint64(ul.Soft), + } + s.Rlimits = append(s.Rlimits, rl) + } + + //Tmpfs: c.StringArray("tmpfs"), + + // TODO how to handle this? + //Syslog: c.Bool("syslog"), + + logOpts := make(map[string]string) + for _, o := range c.LogOptions { + split := strings.SplitN(o, "=", 2) + if len(split) < 2 { + return errors.Errorf("invalid log option %q", o) + } + logOpts[split[0]] = split[1] + } + s.LogConfiguration.Options = logOpts + s.Name = c.Name + + if err := parseWeightDevices(c.BlkIOWeightDevice, s); err != nil { + return err + } + + if s.ResourceLimits.CPU == nil { + s.ResourceLimits.CPU = &specs.LinuxCPU{} + } + s.ResourceLimits.CPU.Shares = &c.CPUShares + s.ResourceLimits.CPU.Period = &c.CPUPeriod + + // TODO research these + //s.ResourceLimits.CPU.Cpus = c.CPUS + //s.ResourceLimits.CPU.Cpus = c.CPUSetCPUs + + //s.ResourceLimits.CPU. = c.CPUSetCPUs + s.ResourceLimits.CPU.Mems = c.CPUSetMems + s.ResourceLimits.CPU.Quota = &c.CPUQuota + s.ResourceLimits.CPU.RealtimePeriod = &c.CPURTPeriod + s.ResourceLimits.CPU.RealtimeRuntime = &c.CPURTRuntime + s.OOMScoreAdj = &c.OOMScoreAdj + s.RestartPolicy = c.Restart + s.Remove = c.Rm + s.StopTimeout = &c.StopTimeout + + // TODO where should we do this? + //func verifyContainerResources(config *cc.CreateConfig, update bool) ([]string, error) { + return nil +} + +func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, startPeriod string) (*manifest.Schema2HealthConfig, error) { + // Every healthcheck requires a command + if len(inCmd) == 0 { + return nil, errors.New("Must define a healthcheck command for all healthchecks") + } + + // first try to parse option value as JSON array of strings... + cmd := []string{} + err := json.Unmarshal([]byte(inCmd), &cmd) + if err != nil { + // ...otherwise pass it to "/bin/sh -c" inside the container + cmd = []string{"CMD-SHELL", inCmd} + } + hc := manifest.Schema2HealthConfig{ + Test: cmd, + } + + if interval == "disable" { + interval = "0" + } + intervalDuration, err := time.ParseDuration(interval) + if err != nil { + return nil, errors.Wrapf(err, "invalid healthcheck-interval %s ", interval) + } + + hc.Interval = intervalDuration + + if retries < 1 { + return nil, errors.New("healthcheck-retries must be greater than 0.") + } + hc.Retries = int(retries) + timeoutDuration, err := time.ParseDuration(timeout) + if err != nil { + return nil, errors.Wrapf(err, "invalid healthcheck-timeout %s", timeout) + } + if timeoutDuration < time.Duration(1) { + return nil, errors.New("healthcheck-timeout must be at least 1 second") + } + hc.Timeout = timeoutDuration + + startPeriodDuration, err := time.ParseDuration(startPeriod) + if err != nil { + return nil, errors.Wrapf(err, "invalid healthcheck-start-period %s", startPeriod) + } + if startPeriodDuration < time.Duration(0) { + return nil, errors.New("healthcheck-start-period must be 0 seconds or greater") + } + hc.StartPeriod = startPeriodDuration + + return &hc, nil +} + +func parseWeightDevices(weightDevs []string, s *specgen.SpecGenerator) error { + for _, val := range weightDevs { + split := strings.SplitN(val, ":", 2) + if len(split) != 2 { + return fmt.Errorf("bad format: %s", val) + } + if !strings.HasPrefix(split[0], "/dev/") { + return fmt.Errorf("bad format for device path: %s", val) + } + weight, err := strconv.ParseUint(split[1], 10, 0) + if err != nil { + return fmt.Errorf("invalid weight for device: %s", val) + } + if weight > 0 && (weight < 10 || weight > 1000) { + return fmt.Errorf("invalid weight for device: %s", val) + } + w := uint16(weight) + s.WeightDevice[split[0]] = specs.LinuxWeightDevice{ + Weight: &w, + LeafWeight: nil, + } + } + return nil +} + +func parseThrottleBPSDevices(bpsDevices []string) (map[string]specs.LinuxThrottleDevice, error) { + td := make(map[string]specs.LinuxThrottleDevice) + for _, val := range bpsDevices { + split := strings.SplitN(val, ":", 2) + if len(split) != 2 { + return nil, fmt.Errorf("bad format: %s", val) + } + if !strings.HasPrefix(split[0], "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", val) + } + rate, err := units.RAMInBytes(split[1]) + if err != nil { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) + } + if rate < 0 { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) + } + td[split[0]] = specs.LinuxThrottleDevice{Rate: uint64(rate)} + } + return td, nil +} + +func parseThrottleIOPsDevices(iopsDevices []string) (map[string]specs.LinuxThrottleDevice, error) { + td := make(map[string]specs.LinuxThrottleDevice) + for _, val := range iopsDevices { + split := strings.SplitN(val, ":", 2) + if len(split) != 2 { + return nil, fmt.Errorf("bad format: %s", val) + } + if !strings.HasPrefix(split[0], "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", val) + } + rate, err := strconv.ParseUint(split[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val) + } + td[split[0]] = specs.LinuxThrottleDevice{Rate: rate} + } + return td, nil +} diff --git a/cmd/podman/common/types.go b/cmd/podman/common/types.go new file mode 100644 index 000000000..2427ae975 --- /dev/null +++ b/cmd/podman/common/types.go @@ -0,0 +1,3 @@ +package common + +var DefaultKernelNamespaces = "cgroup,ipc,net,uts" diff --git a/cmd/podman/common/util.go b/cmd/podman/common/util.go new file mode 100644 index 000000000..47bbe12fa --- /dev/null +++ b/cmd/podman/common/util.go @@ -0,0 +1,43 @@ +package common + +import ( + "strconv" + + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/docker/go-connections/nat" + "github.com/pkg/errors" +) + +// 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() + } 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/common_libpod.go b/cmd/podman/common_libpod.go deleted file mode 100644 index b97ff5986..000000000 --- a/cmd/podman/common_libpod.go +++ /dev/null @@ -1,66 +0,0 @@ -//build !remoteclient - -package main - -import ( - "fmt" - "os" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" - "github.com/pkg/errors" -) - -// getAllOrLatestContainers tries to return the correct list of containers -// depending if --all, --latest or <container-id> is used. -// It requires the Context (c) and the Runtime (runtime). As different -// commands are using different container state for the --all option -// the desired state has to be specified in filterState. If no filter -// is desired a -1 can be used to get all containers. For a better -// error message, if the filter fails, a corresponding verb can be -// specified which will then appear in the error message. -func getAllOrLatestContainers(c *cliconfig.PodmanCommand, runtime *libpod.Runtime, filterState define.ContainerStatus, verb string) ([]*libpod.Container, error) { - var containers []*libpod.Container - var lastError error - var err error - switch { - case c.Bool("all"): - if filterState != -1 { - var filterFuncs []libpod.ContainerFilter - filterFuncs = append(filterFuncs, func(c *libpod.Container) bool { - state, _ := c.State() - return state == filterState - }) - containers, err = runtime.GetContainers(filterFuncs...) - } else { - containers, err = runtime.GetContainers() - } - if err != nil { - return nil, errors.Wrapf(err, "unable to get %s containers", verb) - } - case c.Bool("latest"): - lastCtr, err := runtime.GetLatestContainer() - if err != nil { - return nil, errors.Wrapf(err, "unable to get latest container") - } - containers = append(containers, lastCtr) - default: - args := c.InputArgs - for _, i := range args { - container, err := runtime.LookupContainer(i) - if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "unable to find container %s", i) - } - if container != nil { - // This is here to make sure this does not return [<nil>] but only nil - containers = append(containers, container) - } - } - } - - return containers, lastError -} diff --git a/cmd/podman/container.go b/cmd/podman/container.go deleted file mode 100644 index 66b58f06e..000000000 --- a/cmd/podman/container.go +++ /dev/null @@ -1,93 +0,0 @@ -package main - -import ( - "strings" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/spf13/cobra" -) - -var ( - containerDescription = "Manage containers" - containerCommand = cliconfig.PodmanCommand{ - Command: &cobra.Command{ - Use: "container", - Short: "Manage Containers", - Long: containerDescription, - TraverseChildren: true, - RunE: commandRunE(), - }, - } - - contInspectSubCommand cliconfig.InspectValues - _contInspectSubCommand = &cobra.Command{ - Use: strings.Replace(_inspectCommand.Use, "| IMAGE", "", 1), - Short: "Display the configuration of a container", - Long: `Displays the low-level information on a container identified by name or ID.`, - RunE: func(cmd *cobra.Command, args []string) error { - contInspectSubCommand.InputArgs = args - contInspectSubCommand.GlobalFlags = MainGlobalOpts - return inspectCmd(&contInspectSubCommand) - }, - Example: `podman container inspect myCtr - podman container inspect -l --format '{{.Id}} {{.Config.Labels}}'`, - } - - listSubCommand cliconfig.PsValues - _listSubCommand = &cobra.Command{ - Use: strings.Replace(_psCommand.Use, "ps", "list", 1), - Args: noSubArgs, - Short: _psCommand.Short, - Long: _psCommand.Long, - Aliases: []string{"ls"}, - RunE: func(cmd *cobra.Command, args []string) error { - listSubCommand.InputArgs = args - listSubCommand.GlobalFlags = MainGlobalOpts - return psCmd(&listSubCommand) - }, - Example: strings.Replace(_psCommand.Example, "podman ps", "podman container list", -1), - } - - // Commands that are universally implemented. - containerCommands = []*cobra.Command{ - _attachCommand, - _checkpointCommand, - _commitCommand, - _containerExistsCommand, - _contInspectSubCommand, - _diffCommand, - _execCommand, - _exportCommand, - _createCommand, - _initCommand, - _killCommand, - _listSubCommand, - _logsCommand, - _pauseCommand, - _portCommand, - _pruneContainersCommand, - _restartCommand, - _restoreCommand, - _runCommand, - _rmCommand, - _startCommand, - _stopCommand, - _topCommand, - _unpauseCommand, - _waitCommand, - } -) - -func init() { - contInspectSubCommand.Command = _contInspectSubCommand - inspectInit(&contInspectSubCommand) - - listSubCommand.Command = _listSubCommand - psInit(&listSubCommand) - - containerCommand.AddCommand(containerCommands...) - containerCommand.AddCommand(getContainerSubCommands()...) - containerCommand.SetUsageTemplate(UsageTemplate()) - - rootCmd.AddCommand(containerCommand.Command) -} diff --git a/cmd/podman/containers/attach.go b/cmd/podman/containers/attach.go new file mode 100644 index 000000000..700be1f84 --- /dev/null +++ b/cmd/podman/containers/attach.go @@ -0,0 +1,59 @@ +package containers + +import ( + "os" + + "github.com/containers/libpod/cmd/podman/common" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + attachDescription = "The podman attach command allows you to attach to a running container using the container's ID or name, either to view its ongoing output or to control it interactively." + attachCommand = &cobra.Command{ + Use: "attach [flags] CONTAINER", + Short: "Attach to a running container", + Long: attachDescription, + RunE: attach, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) > 1 || (len(args) == 0 && !cmd.Flag("latest").Changed) { + return errors.Errorf("attach requires the name or id of one running container or the latest flag") + } + return nil + }, + Example: `podman attach ctrID + podman attach 1234 + podman attach --no-stdin foobar`, + } +) + +var ( + attachOpts entities.AttachOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: attachCommand, + }) + flags := attachCommand.Flags() + flags.StringVar(&attachOpts.DetachKeys, "detach-keys", common.GetDefaultDetachKeys(), "Select the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`") + flags.BoolVar(&attachOpts.NoStdin, "no-stdin", false, "Do not attach STDIN. The default is false") + flags.BoolVar(&attachOpts.SigProxy, "sig-proxy", true, "Proxy received signals to the process") + flags.BoolVarP(&attachOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} + +func attach(cmd *cobra.Command, args []string) error { + attachOpts.Stdin = os.Stdin + if attachOpts.NoStdin { + attachOpts.Stdin = nil + } + attachOpts.Stdout = os.Stdout + attachOpts.Stderr = os.Stderr + return registry.ContainerEngine().ContainerAttach(registry.GetContext(), args[0], attachOpts) +} diff --git a/cmd/podman/containers/checkpoint.go b/cmd/podman/containers/checkpoint.go new file mode 100644 index 000000000..7259ed38b --- /dev/null +++ b/cmd/podman/containers/checkpoint.go @@ -0,0 +1,79 @@ +package containers + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/rootless" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + checkpointDescription = ` + podman container checkpoint + + Checkpoints one or more running containers. The container name or ID can be used. +` + checkpointCommand = &cobra.Command{ + Use: "checkpoint [flags] CONTAINER [CONTAINER...]", + Short: "Checkpoints one or more containers", + Long: checkpointDescription, + RunE: checkpoint, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, + Example: `podman container checkpoint --keep ctrID + podman container checkpoint --all + podman container checkpoint --leave-running --latest`, + } +) + +var ( + checkpointOptions entities.CheckpointOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: checkpointCommand, + Parent: containerCmd, + }) + flags := checkpointCommand.Flags() + flags.BoolVarP(&checkpointOptions.Keep, "keep", "k", false, "Keep all temporary checkpoint files") + flags.BoolVarP(&checkpointOptions.LeaveRuninng, "leave-running", "R", false, "Leave the container running after writing checkpoint to disk") + flags.BoolVar(&checkpointOptions.TCPEstablished, "tcp-established", false, "Checkpoint a container with established TCP connections") + flags.BoolVarP(&checkpointOptions.All, "all", "a", false, "Checkpoint all running containers") + flags.BoolVarP(&checkpointOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.StringVarP(&checkpointOptions.Export, "export", "e", "", "Export the checkpoint image to a tar.gz") + flags.BoolVar(&checkpointOptions.IgnoreRootFS, "ignore-rootfs", false, "Do not include root file-system changes when exporting") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} + +func checkpoint(cmd *cobra.Command, args []string) error { + var errs utils.OutputErrors + if rootless.IsRootless() { + return errors.New("checkpointing a container requires root") + } + if checkpointOptions.Export == "" && checkpointOptions.IgnoreRootFS { + return errors.Errorf("--ignore-rootfs can only be used with --export") + } + responses, err := registry.ContainerEngine().ContainerCheckpoint(context.Background(), args, checkpointOptions) + 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() +} diff --git a/cmd/podman/containers/cleanup.go b/cmd/podman/containers/cleanup.go new file mode 100644 index 000000000..2bcd1c1e9 --- /dev/null +++ b/cmd/podman/containers/cleanup.go @@ -0,0 +1,75 @@ +package containers + +import ( + "fmt" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + cleanupDescription = ` + podman container cleanup + + Cleans up mount points and network stacks on one or more containers from the host. The container name or ID can be used. This command is used internally when running containers, but can also be used if container cleanup has failed when a container exits. +` + cleanupCommand = &cobra.Command{ + Use: "cleanup [flags] CONTAINER [CONTAINER...]", + Short: "Cleanup network and mountpoints of one or more containers", + Long: cleanupDescription, + RunE: cleanup, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, + Example: `podman container cleanup --latest + podman container cleanup ctrID1 ctrID2 ctrID3 + podman container cleanup --all`, + } +) + +var ( + cleanupOptions entities.ContainerCleanupOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Parent: containerCmd, + Command: cleanupCommand, + }) + flags := cleanupCommand.Flags() + flags.BoolVarP(&cleanupOptions.All, "all", "a", false, "Cleans up all containers") + flags.BoolVarP(&cleanupOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&cleanupOptions.Remove, "rm", false, "After cleanup, remove the container entirely") + flags.BoolVar(&cleanupOptions.RemoveImage, "rmi", false, "After cleanup, remove the image entirely") + +} + +func cleanup(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + ) + responses, err := registry.ContainerEngine().ContainerCleanup(registry.GetContext(), args, cleanupOptions) + if err != nil { + return err + } + for _, r := range responses { + if r.CleanErr == nil && r.RmErr == nil && r.RmiErr == nil { + fmt.Println(r.Id) + continue + } + if r.RmErr != nil { + errs = append(errs, r.RmErr) + } + if r.RmiErr != nil { + errs = append(errs, r.RmiErr) + } + if r.CleanErr != nil { + errs = append(errs, r.CleanErr) + } + } + return errs.PrintErrors() +} diff --git a/cmd/podman/containers/commit.go b/cmd/podman/containers/commit.go new file mode 100644 index 000000000..eaba07981 --- /dev/null +++ b/cmd/podman/containers/commit.go @@ -0,0 +1,78 @@ +package containers + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "strings" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + commitDescription = `Create an image from a container's changes. Optionally tag the image created, set the author with the --author flag, set the commit message with the --message flag, and make changes to the instructions with the --change flag.` + + commitCommand = &cobra.Command{ + Use: "commit [flags] CONTAINER [IMAGE]", + Short: "Create new image based on the changed container", + Long: commitDescription, + RunE: commit, + Args: cobra.MinimumNArgs(1), + Example: `podman commit -q --message "committing container to image" reverent_golick image-committed + podman commit -q --author "firstName lastName" reverent_golick image-committed + podman commit -q --pause=false containerID image-committed + podman commit containerID`, + } + + // ChangeCmds is the list of valid Changes commands to passed to the Commit call + ChangeCmds = []string{"CMD", "ENTRYPOINT", "ENV", "EXPOSE", "LABEL", "ONBUILD", "STOPSIGNAL", "USER", "VOLUME", "WORKDIR"} +) + +var ( + commitOptions = entities.CommitOptions{ + ImageName: "", + } + iidFile string +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: commitCommand, + }) + flags := commitCommand.Flags() + flags.StringArrayVarP(&commitOptions.Changes, "change", "c", []string{}, "Apply the following possible instructions to the created image (default []): "+strings.Join(ChangeCmds, " | ")) + flags.StringVarP(&commitOptions.Format, "format", "f", "oci", "`Format` of the image manifest and metadata") + flags.StringVarP(&iidFile, "iidfile", "", "", "`file` to write the image ID to") + flags.StringVarP(&commitOptions.Message, "message", "m", "", "Set commit message for imported image") + flags.StringVarP(&commitOptions.Author, "author", "a", "", "Set the author for the image committed") + flags.BoolVarP(&commitOptions.Pause, "pause", "p", false, "Pause container during commit") + flags.BoolVarP(&commitOptions.Quiet, "quiet", "q", false, "Suppress output") + flags.BoolVar(&commitOptions.IncludeVolumes, "include-volumes", false, "Include container volumes as image volumes") + +} +func commit(cmd *cobra.Command, args []string) error { + container := args[0] + if len(args) > 1 { + commitOptions.ImageName = args[1] + } + if !commitOptions.Quiet { + commitOptions.Writer = os.Stderr + } + + response, err := registry.ContainerEngine().ContainerCommit(context.Background(), container, commitOptions) + if err != nil { + return err + } + if len(iidFile) > 0 { + if err = ioutil.WriteFile(iidFile, []byte(response.Id), 0644); err != nil { + return errors.Wrapf(err, "failed to write image ID to file %q", iidFile) + } + } + fmt.Println(response.Id) + return nil +} diff --git a/cmd/podman/containers/container.go b/cmd/podman/containers/container.go new file mode 100644 index 000000000..8564b23f4 --- /dev/null +++ b/cmd/podman/containers/container.go @@ -0,0 +1,40 @@ +package containers + +import ( + "os" + + "github.com/containers/common/pkg/config" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + // Command: podman _container_ + containerCmd = &cobra.Command{ + Use: "container", + Short: "Manage containers", + Long: "Manage containers", + TraverseChildren: true, + RunE: registry.SubCommandExists, + } + + defaultContainerConfig = getDefaultContainerConfig() +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: containerCmd, + }) +} + +func getDefaultContainerConfig() *config.Config { + defaultContainerConfig, err := config.Default() + if err != nil { + logrus.Error(err) + os.Exit(1) + } + return defaultContainerConfig +} diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go new file mode 100644 index 000000000..292d5c1ad --- /dev/null +++ b/cmd/podman/containers/create.go @@ -0,0 +1,102 @@ +package containers + +import ( + "fmt" + + "github.com/containers/libpod/cmd/podman/common" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/specgen" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + createDescription = `Creates a new container from the given image or storage and prepares it for running the specified command. + + The container ID is then printed to stdout. You can then start it at any time with the podman start <container_id> command. The container will be created with the initial state 'created'.` + createCommand = &cobra.Command{ + Use: "create [flags] IMAGE [COMMAND [ARG...]]", + Short: "Create but do not start a container", + Long: createDescription, + RunE: create, + Args: cobra.MinimumNArgs(1), + Example: `podman create alpine ls + podman create --annotation HELLO=WORLD alpine ls + podman create -t -i --name myctr alpine ls`, + } +) + +var ( + cliVals common.ContainerCLIOpts +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: createCommand, + }) + //common.GetCreateFlags(createCommand) + flags := createCommand.Flags() + flags.SetInterspersed(false) + flags.AddFlagSet(common.GetCreateFlags(&cliVals)) + flags.AddFlagSet(common.GetNetFlags()) + flags.SetNormalizeFunc(common.AliasFlags) +} + +func create(cmd *cobra.Command, args []string) error { + var ( + err error + rawImageInput string + ) + cliVals.Net, err = common.NetFlagsToNetOptions(cmd) + if err != nil { + return err + } + if rfs := cliVals.RootFS; !rfs { + rawImageInput = args[0] + } + + if err := createInit(cmd); err != nil { + return err + } + //TODO rootfs still + s := specgen.NewSpecGenerator(rawImageInput) + if err := common.FillOutSpecGen(s, &cliVals, args); err != nil { + return err + } + + report, err := registry.ContainerEngine().ContainerCreate(registry.GetContext(), s) + if err != nil { + return err + } + fmt.Println(report.Id) + return nil +} + +func createInit(c *cobra.Command) error { + if c.Flag("privileged").Changed && c.Flag("security-opt").Changed { + logrus.Warn("setting security options with --privileged has no effect") + } + + if (c.Flag("dns").Changed || c.Flag("dns-opt").Changed || c.Flag("dns-search").Changed) && (cliVals.Net.Network.NSMode == specgen.NoNetwork || cliVals.Net.Network.IsContainer()) { + return errors.Errorf("conflicting options: dns and the network mode.") + } + + if c.Flag("cpu-period").Changed && c.Flag("cpus").Changed { + return errors.Errorf("--cpu-period and --cpus cannot be set together") + } + if c.Flag("cpu-quota").Changed && c.Flag("cpus").Changed { + return errors.Errorf("--cpu-quota and --cpus cannot be set together") + } + + if c.Flag("no-hosts").Changed && c.Flag("add-host").Changed { + return errors.Errorf("--no-hosts and --add-host cannot be set together") + } + + // Docker-compatibility: the "-h" flag for run/create is reserved for + // the hostname (see https://github.com/containers/libpod/issues/1367). + + return nil +} diff --git a/cmd/podman/containers/diff.go b/cmd/podman/containers/diff.go new file mode 100644 index 000000000..ebc0d8ea1 --- /dev/null +++ b/cmd/podman/containers/diff.go @@ -0,0 +1,66 @@ +package containers + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/report" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + // podman container _diff_ + diffCmd = &cobra.Command{ + Use: "diff [flags] CONTAINER", + Args: registry.IdOrLatestArgs, + Short: "Inspect changes on container's file systems", + Long: `Displays changes on a container filesystem. The container will be compared to its parent layer.`, + RunE: diff, + Example: `podman container diff myCtr + podman container diff -l --format json myCtr`, + } + diffOpts *entities.DiffOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: diffCmd, + Parent: containerCmd, + }) + + diffOpts = &entities.DiffOptions{} + flags := diffCmd.Flags() + flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive") + _ = flags.MarkHidden("archive") + flags.StringVar(&diffOpts.Format, "format", "", "Change the output format") + + if !registry.IsRemote() { + flags.BoolVarP(&diffOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + } +} + +func diff(cmd *cobra.Command, args []string) error { + if len(args) == 0 && !diffOpts.Latest { + return errors.New("container must be specified: podman container diff [options [...]] ID-NAME") + } + + results, err := registry.ContainerEngine().ContainerDiff(registry.GetContext(), args[0], entities.DiffOptions{}) + if err != nil { + return err + } + + switch diffOpts.Format { + case "": + return report.ChangesToTable(results) + case "json": + return report.ChangesToJSON(results) + default: + return errors.New("only supported value for '--format' is 'json'") + } +} + +func Diff(cmd *cobra.Command, args []string, options entities.DiffOptions) error { + diffOpts = &options + return diff(cmd, args) +} diff --git a/cmd/podman/containers/exec.go b/cmd/podman/containers/exec.go new file mode 100644 index 000000000..68ecb2196 --- /dev/null +++ b/cmd/podman/containers/exec.go @@ -0,0 +1,92 @@ +package containers + +import ( + "bufio" + "os" + + "github.com/containers/libpod/cmd/podman/common" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + envLib "github.com/containers/libpod/pkg/env" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + execDescription = `Execute the specified command inside a running container. +` + execCommand = &cobra.Command{ + Use: "exec [flags] CONTAINER [COMMAND [ARG...]]", + Short: "Run a process in a running container", + Long: execDescription, + RunE: exec, + Example: `podman exec -it ctrID ls + podman exec -it -w /tmp myCtr pwd + podman exec --user root ctrID ls`, + } +) + +var ( + envInput, envFile []string + execOpts entities.ExecOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: execCommand, + }) + flags := execCommand.Flags() + flags.SetInterspersed(false) + flags.StringVar(&execOpts.DetachKeys, "detach-keys", common.GetDefaultDetachKeys(), "Select the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _") + flags.StringArrayVarP(&envInput, "env", "e", []string{}, "Set environment variables") + flags.StringSliceVar(&envFile, "env-file", []string{}, "Read in a file of environment variables") + flags.BoolVarP(&execOpts.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached") + flags.BoolVarP(&execOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&execOpts.Privileged, "privileged", false, "Give the process extended Linux capabilities inside the container. The default is false") + flags.BoolVarP(&execOpts.Tty, "tty", "t", false, "Allocate a pseudo-TTY. The default is false") + flags.StringVarP(&execOpts.User, "user", "u", "", "Sets the username or UID used and optionally the groupname or GID for the specified command") + flags.UintVar(&execOpts.PreserveFDs, "preserve-fds", 0, "Pass N additional file descriptors to the container") + flags.StringVarP(&execOpts.WorkDir, "workdir", "w", "", "Working directory inside the container") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + _ = flags.MarkHidden("preserve-fds") + } + +} +func exec(cmd *cobra.Command, args []string) error { + var nameOrId string + execOpts.Cmd = args + if !execOpts.Latest { + execOpts.Cmd = args[1:] + nameOrId = args[0] + } + // Validate given environment variables + execOpts.Envs = make(map[string]string) + for _, f := range envFile { + fileEnv, err := envLib.ParseFile(f) + if err != nil { + return err + } + execOpts.Envs = envLib.Join(execOpts.Envs, fileEnv) + } + + cliEnv, err := envLib.ParseSlice(envInput) + if err != nil { + return errors.Wrap(err, "error parsing environment variables") + } + + execOpts.Envs = envLib.Join(execOpts.Envs, cliEnv) + execOpts.Streams.OutputStream = os.Stdout + execOpts.Streams.ErrorStream = os.Stderr + if execOpts.Interactive { + execOpts.Streams.InputStream = bufio.NewReader(os.Stdin) + execOpts.Streams.AttachInput = true + } + execOpts.Streams.AttachOutput = true + execOpts.Streams.AttachError = true + + exitCode, err := registry.ContainerEngine().ContainerExec(registry.GetContext(), nameOrId, execOpts) + registry.SetExitCode(exitCode) + return err +} diff --git a/cmd/podman/containers/exists.go b/cmd/podman/containers/exists.go new file mode 100644 index 000000000..f1bc09f78 --- /dev/null +++ b/cmd/podman/containers/exists.go @@ -0,0 +1,43 @@ +package containers + +import ( + "context" + "os" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + containerExistsDescription = `If the named container exists in local storage, podman container exists exits with 0, otherwise the exit code will be 1.` + + existsCommand = &cobra.Command{ + Use: "exists CONTAINER", + Short: "Check if a container exists in local storage", + Long: containerExistsDescription, + Example: `podman container exists containerID + podman container exists myctr || podman run --name myctr [etc...]`, + RunE: exists, + Args: cobra.ExactArgs(1), + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: existsCommand, + Parent: containerCmd, + }) +} + +func exists(cmd *cobra.Command, args []string) error { + response, err := registry.ContainerEngine().ContainerExists(context.Background(), args[0]) + if err != nil { + return err + } + if !response.Value { + os.Exit(1) + } + return nil +} diff --git a/cmd/podman/containers/export.go b/cmd/podman/containers/export.go new file mode 100644 index 000000000..5110812d1 --- /dev/null +++ b/cmd/podman/containers/export.go @@ -0,0 +1,54 @@ +package containers + +import ( + "context" + "os" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "golang.org/x/crypto/ssh/terminal" +) + +var ( + exportDescription = "Exports container's filesystem contents as a tar archive" + + " and saves it on the local machine." + + exportCommand = &cobra.Command{ + Use: "export [flags] CONTAINER", + Short: "Export container's filesystem contents as a tar archive", + Long: exportDescription, + RunE: export, + Args: cobra.ExactArgs(1), + Example: `podman export ctrID > myCtr.tar + podman export --output="myCtr.tar" ctrID`, + } +) + +var ( + exportOpts entities.ContainerExportOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: exportCommand, + }) + flags := exportCommand.Flags() + flags.StringVarP(&exportOpts.Output, "output", "o", "", "Write to a specified file (default: stdout, which must be redirected)") +} + +func export(cmd *cobra.Command, args []string) error { + if len(exportOpts.Output) == 0 { + file := os.Stdout + if terminal.IsTerminal(int(file.Fd())) { + return errors.Errorf("refusing to export to terminal. Use -o flag or redirect") + } + exportOpts.Output = "/dev/stdout" + } else if err := parse.ValidateFileName(exportOpts.Output); err != nil { + return err + } + return registry.ContainerEngine().ContainerExport(context.Background(), args[0], exportOpts) +} diff --git a/cmd/podman/containers/init.go b/cmd/podman/containers/init.go new file mode 100644 index 000000000..bb02f22fd --- /dev/null +++ b/cmd/podman/containers/init.go @@ -0,0 +1,59 @@ +package containers + +import ( + "fmt" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + initDescription = `Initialize one or more containers, creating the OCI spec and mounts for inspection. Container names or IDs can be used.` + + initCommand = &cobra.Command{ + Use: "init [flags] CONTAINER [CONTAINER...]", + Short: "Initialize one or more containers", + Long: initDescription, + RunE: initContainer, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, + Example: `podman init --latest + podman init 3c45ef19d893 + podman init test1`, + } +) + +var ( + initOptions entities.ContainerInitOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: initCommand, + }) + flags := initCommand.Flags() + flags.BoolVarP(&initOptions.All, "all", "a", false, "Initialize all containers") + flags.BoolVarP(&initOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + _ = flags.MarkHidden("latest") +} + +func initContainer(cmd *cobra.Command, args []string) error { + var errs utils.OutputErrors + report, err := registry.ContainerEngine().ContainerInit(registry.GetContext(), args, initOptions) + if err != nil { + return err + } + for _, r := range report { + if r.Err == nil { + fmt.Println(r.Id) + } else { + errs = append(errs, r.Err) + } + } + return errs.PrintErrors() +} diff --git a/cmd/podman/containers/inspect.go b/cmd/podman/containers/inspect.go new file mode 100644 index 000000000..8d591832b --- /dev/null +++ b/cmd/podman/containers/inspect.go @@ -0,0 +1,78 @@ +package containers + +import ( + "context" + "fmt" + "os" + "strings" + "text/template" + + "github.com/containers/libpod/cmd/podman/common" + "github.com/containers/libpod/cmd/podman/registry" + + "github.com/containers/libpod/pkg/domain/entities" + json "github.com/json-iterator/go" + "github.com/spf13/cobra" +) + +var ( + // podman container _inspect_ + inspectCmd = &cobra.Command{ + Use: "inspect [flags] CONTAINER", + Short: "Display the configuration of a container", + Long: `Displays the low-level information on a container identified by name or ID.`, + RunE: inspect, + Example: `podman container inspect myCtr + podman container inspect -l --format '{{.Id}} {{.Config.Labels}}'`, + } + inspectOpts *entities.InspectOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: inspectCmd, + Parent: containerCmd, + }) + inspectOpts = common.AddInspectFlagSet(inspectCmd) + flags := inspectCmd.Flags() + + if !registry.IsRemote() { + flags.BoolVarP(&inspectOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + } + +} + +func inspect(cmd *cobra.Command, args []string) error { + responses, err := registry.ContainerEngine().ContainerInspect(context.Background(), args, *inspectOpts) + if err != nil { + return err + } + if inspectOpts.Format == "" { + b, err := json.MarshalIndent(responses, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil + } + format := inspectOpts.Format + if !strings.HasSuffix(format, "\n") { + format += "\n" + } + tmpl, err := template.New("inspect").Parse(format) + if err != nil { + return err + } + for _, i := range responses { + if err := tmpl.Execute(os.Stdout, i); err != nil { + return err + } + } + return nil +} + +func Inspect(cmd *cobra.Command, args []string, options *entities.InspectOptions) error { + inspectOpts = options + return inspect(cmd, args) +} diff --git a/cmd/podman/containers/kill.go b/cmd/podman/containers/kill.go new file mode 100644 index 000000000..5341457fb --- /dev/null +++ b/cmd/podman/containers/kill.go @@ -0,0 +1,76 @@ +package containers + +import ( + "context" + "errors" + "fmt" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/signal" + "github.com/spf13/cobra" +) + +var ( + killDescription = "The main process inside each container specified will be sent SIGKILL, or any signal specified with option --signal." + killCommand = &cobra.Command{ + Use: "kill [flags] CONTAINER [CONTAINER...]", + Short: "Kill one or more running containers with a specific signal", + Long: killDescription, + RunE: kill, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, + Example: `podman kill mywebserver + podman kill 860a4b23 + podman kill --signal TERM ctrID`, + } +) + +var ( + killOptions = entities.KillOptions{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: killCommand, + }) + flags := killCommand.Flags() + flags.BoolVarP(&killOptions.All, "all", "a", false, "Signal all running containers") + flags.StringVarP(&killOptions.Signal, "signal", "s", "KILL", "Signal to send to the container") + flags.BoolVarP(&killOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} + +func kill(cmd *cobra.Command, args []string) error { + var ( + err error + errs utils.OutputErrors + ) + // Check if the signalString provided by the user is valid + // Invalid signals will return err + sig, err := signal.ParseSignalNameOrNumber(killOptions.Signal) + if err != nil { + return err + } + if sig < 1 || sig > 64 { + return errors.New("valid signals are 1 through 64") + } + responses, err := registry.ContainerEngine().ContainerKill(context.Background(), args, killOptions) + 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() +} diff --git a/cmd/podman/containers/list.go b/cmd/podman/containers/list.go new file mode 100644 index 000000000..938fb63d3 --- /dev/null +++ b/cmd/podman/containers/list.go @@ -0,0 +1,34 @@ +package containers + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + // podman container _list_ + listCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Args: cobra.NoArgs, + Short: "List containers", + Long: "Prints out information about the containers", + RunE: containers, + Example: `podman container list -a + podman container list -a --format "{{.ID}} {{.Image}} {{.Labels}} {{.Mounts}}" + podman container list --size --sort names`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: listCmd, + Parent: containerCmd, + }) +} + +func containers(cmd *cobra.Command, args []string) error { + return nil +} diff --git a/cmd/podman/containers/logs.go b/cmd/podman/containers/logs.go new file mode 100644 index 000000000..5dec71fdd --- /dev/null +++ b/cmd/podman/containers/logs.go @@ -0,0 +1,103 @@ +package containers + +import ( + "os" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// logsOptionsWrapper wraps entities.LogsOptions and prevents leaking +// CLI-only fields into the API types. +type logsOptionsWrapper struct { + entities.ContainerLogsOptions + + SinceRaw string +} + +var ( + logsOptions logsOptionsWrapper + logsDescription = `Retrieves logs for one or more containers. + + 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 = &cobra.Command{ + Use: "logs [flags] CONTAINER [CONTAINER...]", + Short: "Fetch the logs of one or more container", + Long: logsDescription, + RunE: logs, + Example: `podman logs ctrID + podman logs --names ctrID1 ctrID2 + podman logs --tail 2 mywebserver + podman logs --follow=true --since 10m ctrID + podman logs mywebserver mydbserver`, + } + + containerLogsCommand = &cobra.Command{ + Use: logsCommand.Use, + Short: logsCommand.Short, + Long: logsCommand.Long, + RunE: logsCommand.RunE, + Example: `podman container logs ctrID + podman container logs --names ctrID1 ctrID2 + podman container logs --tail 2 mywebserver + podman container logs --follow=true --since 10m ctrID + podman container logs mywebserver mydbserver`, + } +) + +func init() { + // logs + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: logsCommand, + }) + + flags := logsCommand.Flags() + logsFlags(flags) + + // container logs + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: containerLogsCommand, + Parent: containerCmd, + }) + + containerLogsFlags := containerLogsCommand.Flags() + logsFlags(containerLogsFlags) +} + +func logsFlags(flags *pflag.FlagSet) { + flags.BoolVar(&logsOptions.Details, "details", false, "Show extra details provided to the logs") + flags.BoolVarP(&logsOptions.Follow, "follow", "f", false, "Follow log output. The default is false") + flags.BoolVarP(&logsOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.StringVar(&logsOptions.SinceRaw, "since", "", "Show logs since TIMESTAMP") + flags.Int64Var(&logsOptions.Tail, "tail", -1, "Output the specified number of LINES at the end of the logs. Defaults to -1, which prints all lines") + flags.BoolVarP(&logsOptions.Timestamps, "timestamps", "t", false, "Output the timestamps in the log") + flags.BoolVarP(&logsOptions.Names, "names", "n", false, "Output the container name in the log") + flags.SetInterspersed(false) + _ = flags.MarkHidden("details") +} + +func logs(cmd *cobra.Command, args []string) error { + if len(args) > 0 && logsOptions.Latest { + return errors.New("no containers can be specified when using 'latest'") + } + if !logsOptions.Latest && len(args) < 1 { + return errors.New("specify at least one container name or ID to log") + } + if logsOptions.SinceRaw != "" { + // parse time, error out if something is wrong + since, err := util.ParseInputTime(logsOptions.SinceRaw) + if err != nil { + return errors.Wrapf(err, "error parsing --since %q", logsOptions.SinceRaw) + } + logsOptions.Since = since + } + logsOptions.Writer = os.Stdout + return registry.ContainerEngine().ContainerLogs(registry.GetContext(), args, logsOptions.ContainerLogsOptions) +} diff --git a/cmd/podman/containers/mount.go b/cmd/podman/containers/mount.go new file mode 100644 index 000000000..25eec46ca --- /dev/null +++ b/cmd/podman/containers/mount.go @@ -0,0 +1,123 @@ +package containers + +import ( + "encoding/json" + "fmt" + "os" + "text/tabwriter" + "text/template" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + mountDescription = `podman mount + Lists all mounted containers mount points if no container is specified + + podman mount CONTAINER-NAME-OR-ID + Mounts the specified container and outputs the mountpoint +` + + mountCommand = &cobra.Command{ + Use: "mount [flags] [CONTAINER]", + Short: "Mount a working container's root filesystem", + Long: mountDescription, + RunE: mount, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, true, false) + }, + Annotations: map[string]string{ + registry.ParentNSRequired: "", + }, + } +) + +var ( + mountOpts entities.ContainerMountOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: mountCommand, + }) + flags := mountCommand.Flags() + flags.BoolVarP(&mountOpts.All, "all", "a", false, "Mount all containers") + flags.StringVar(&mountOpts.Format, "format", "", "Change the output format to Go template") + flags.BoolVarP(&mountOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&mountOpts.NoTruncate, "notruncate", false, "Do not truncate output") +} + +func mount(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + mrs []mountReporter + ) + reports, err := registry.ContainerEngine().ContainerMount(registry.GetContext(), args, mountOpts) + if err != nil { + return err + } + if len(args) > 0 || mountOpts.Latest || mountOpts.All { + for _, r := range reports { + if r.Err == nil { + fmt.Println(r.Path) + continue + } + errs = append(errs, r.Err) + } + return errs.PrintErrors() + } + if mountOpts.Format == "json" { + return printJSON(reports) + } + for _, r := range reports { + mrs = append(mrs, mountReporter{r}) + } + row := "{{.ID}} {{.Path}}" + format := "{{range . }}" + row + "{{end}}" + tmpl, err := template.New("mounts").Parse(format) + if err != nil { + return err + } + w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + defer w.Flush() + return tmpl.Execute(w, mrs) +} + +func printJSON(reports []*entities.ContainerMountReport) error { + type jreport struct { + ID string `json:"id"` + Names []string + Mountpoint string `json:"mountpoint"` + } + var jreports []jreport + + for _, r := range reports { + jreports = append(jreports, jreport{ + ID: r.Id, + Names: []string{r.Name}, + Mountpoint: r.Path, + }) + } + b, err := json.MarshalIndent(jreports, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil +} + +type mountReporter struct { + *entities.ContainerMountReport +} + +func (m mountReporter) ID() string { + if mountOpts.NoTruncate { + return m.Id + } + return m.Id[0:12] +} diff --git a/cmd/podman/containers/pause.go b/cmd/podman/containers/pause.go new file mode 100644 index 000000000..f3654b5c1 --- /dev/null +++ b/cmd/podman/containers/pause.go @@ -0,0 +1,61 @@ +package containers + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/rootless" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + pauseDescription = `Pauses one or more running containers. The container name or ID can be used.` + pauseCommand = &cobra.Command{ + Use: "pause [flags] CONTAINER [CONTAINER...]", + Short: "Pause all the processes in one or more containers", + Long: pauseDescription, + RunE: pause, + Example: `podman pause mywebserver + podman pause 860a4b23 + podman pause -a`, + } + + pauseOpts = entities.PauseUnPauseOptions{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: pauseCommand, + }) + flags := pauseCommand.Flags() + flags.BoolVarP(&pauseOpts.All, "all", "a", false, "Pause all running containers") +} + +func pause(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + ) + if rootless.IsRootless() && !registry.IsRemote() { + return errors.New("pause is not supported for rootless containers") + } + if len(args) < 1 && !pauseOpts.All { + return errors.Errorf("you must provide at least one container name or id") + } + responses, err := registry.ContainerEngine().ContainerPause(context.Background(), args, pauseOpts) + 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() +} diff --git a/cmd/podman/containers/prune.go b/cmd/podman/containers/prune.go new file mode 100644 index 000000000..df627259c --- /dev/null +++ b/cmd/podman/containers/prune.go @@ -0,0 +1,86 @@ +package containers + +import ( + "bufio" + "context" + "fmt" + "net/url" + "os" + "strings" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + pruneDescription = fmt.Sprintf(`podman container prune + + Removes all stopped | exited containers`) + pruneCommand = &cobra.Command{ + Use: "prune [flags]", + Short: "Remove all stopped | exited containers", + Long: pruneDescription, + RunE: prune, + Example: `podman container prune`, + } + force bool + filter = []string{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: pruneCommand, + Parent: containerCmd, + }) + flags := pruneCommand.Flags() + flags.BoolVarP(&force, "force", "f", false, "Do not prompt for confirmation. The default is false") + flags.StringArrayVar(&filter, "filter", []string{}, "Provide filter values (e.g. 'label=<key>=<value>')") +} + +func prune(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + pruneOptions = entities.ContainerPruneOptions{} + ) + if len(args) > 0 { + return errors.Errorf("`%s` takes no arguments", cmd.CommandPath()) + } + if !force { + reader := bufio.NewReader(os.Stdin) + fmt.Println("WARNING! This will remove all stopped containers.") + fmt.Print("Are you sure you want to continue? [y/N] ") + answer, err := reader.ReadString('\n') + if err != nil { + return errors.Wrapf(err, "error reading input") + } + if strings.ToLower(answer)[0] != 'y' { + return nil + } + } + + // TODO Remove once filter refactor is finished and url.Values done. + for _, f := range filter { + t := strings.SplitN(f, "=", 2) + pruneOptions.Filters = make(url.Values) + if len(t) < 2 { + return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) + } + pruneOptions.Filters.Add(t[0], t[1]) + } + responses, err := registry.ContainerEngine().ContainerPrune(context.Background(), pruneOptions) + + 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() +} diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go new file mode 100644 index 000000000..57b81a609 --- /dev/null +++ b/cmd/podman/containers/ps.go @@ -0,0 +1,413 @@ +package containers + +import ( + "encoding/json" + "fmt" + "os" + "sort" + "strconv" + "strings" + "text/tabwriter" + "text/template" + "time" + + tm "github.com/buger/goterm" + "github.com/containers/buildah/pkg/formats" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/docker/go-units" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + psDescription = "Prints out information about the containers" + psCommand = &cobra.Command{ + Use: "ps", + Args: checkFlags, + Short: "List containers", + Long: psDescription, + RunE: ps, + Example: `podman ps -a + podman ps -a --format "{{.ID}} {{.Image}} {{.Labels}} {{.Mounts}}" + podman ps --size --sort names`, + } +) +var ( + listOpts = entities.ContainerListOptions{ + Filters: make(map[string][]string), + } + filters []string + noTrunc bool + defaultHeaders string = "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES" +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: psCommand, + }) + flags := psCommand.Flags() + flags.BoolVarP(&listOpts.All, "all", "a", false, "Show all the containers, default is only running containers") + flags.StringSliceVarP(&filters, "filter", "f", []string{}, "Filter output based on conditions given") + flags.StringVar(&listOpts.Format, "format", "", "Pretty-print containers to JSON or using a Go template") + flags.IntVarP(&listOpts.Last, "last", "n", -1, "Print the n last created containers (all states)") + flags.BoolVarP(&listOpts.Latest, "latest", "l", false, "Show the latest container created (all states)") + flags.BoolVar(&listOpts.Namespace, "namespace", false, "Display namespace information") + flags.BoolVar(&listOpts.Namespace, "ns", false, "Display namespace information") + flags.BoolVar(&noTrunc, "no-trunc", false, "Display the extended information") + flags.BoolVarP(&listOpts.Pod, "pod", "p", false, "Print the ID and name of the pod the containers are associated with") + flags.BoolVarP(&listOpts.Quiet, "quiet", "q", false, "Print the numeric IDs of the containers only") + flags.BoolVarP(&listOpts.Size, "size", "s", false, "Display the total file sizes") + flags.StringVar(&listOpts.Sort, "sort", "created", "Sort output by command, created, id, image, names, runningfor, size, or status") + flags.BoolVar(&listOpts.Sync, "sync", false, "Sync container state with OCI runtime") + flags.UintVarP(&listOpts.Watch, "watch", "w", 0, "Watch the ps output on an interval in seconds") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} +func checkFlags(c *cobra.Command, args []string) error { + // latest, and last are mutually exclusive. + if listOpts.Last >= 0 && listOpts.Latest { + return errors.Errorf("last and latest are mutually exclusive") + } + // Filter on status forces all + for _, filter := range filters { + splitFilter := strings.SplitN(filter, "=", 2) + if strings.ToLower(splitFilter[0]) == "status" { + listOpts.All = true + break + } + } + // Quiet conflicts with size and namespace and is overridden by a Go + // template. + if listOpts.Quiet { + if listOpts.Size || listOpts.Namespace { + return errors.Errorf("quiet conflicts with size and namespace") + } + if c.Flag("format").Changed && listOpts.Format != formats.JSONString { + // Quiet is overridden by Go template output. + listOpts.Quiet = false + } + } + // Size and namespace conflict with each other + if listOpts.Size && listOpts.Namespace { + return errors.Errorf("size and namespace options conflict") + } + + if listOpts.Watch > 0 && listOpts.Latest { + return errors.New("the watch and latest flags cannot be used together") + } + return nil +} + +func jsonOut(responses []entities.ListContainer) error { + b, err := json.MarshalIndent(responses, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil +} + +func quietOut(responses []entities.ListContainer) error { + for _, r := range responses { + id := r.ID + if !noTrunc { + id = id[0:12] + } + fmt.Println(id) + } + return nil +} + +func getResponses() ([]entities.ListContainer, error) { + responses, err := registry.ContainerEngine().ContainerList(registry.GetContext(), listOpts) + if err != nil { + return nil, err + } + if len(listOpts.Sort) > 0 { + responses, err = entities.SortPsOutput(listOpts.Sort, responses) + if err != nil { + return nil, err + } + } + return responses, nil +} + +func ps(cmd *cobra.Command, args []string) error { + var responses []psReporter + for _, f := range filters { + split := strings.SplitN(f, "=", 2) + if len(split) == 1 { + return errors.Errorf("invalid filter %q", f) + } + listOpts.Filters[split[0]] = append(listOpts.Filters[split[0]], split[1]) + } + listContainers, err := getResponses() + if err != nil { + return err + } + if len(listOpts.Sort) > 0 { + listContainers, err = entities.SortPsOutput(listOpts.Sort, listContainers) + if err != nil { + return err + } + } + if listOpts.Format == "json" { + return jsonOut(listContainers) + } + if listOpts.Quiet { + return quietOut(listContainers) + } + + for _, r := range listContainers { + responses = append(responses, psReporter{r}) + } + + headers, row := createPsOut() + if cmd.Flag("format").Changed { + row = listOpts.Format + if !strings.HasPrefix(row, "\n") { + row += "\n" + } + } + format := "{{range . }}" + row + "{{end}}" + if !listOpts.Quiet && !cmd.Flag("format").Changed { + format = headers + format + } + tmpl, err := template.New("listContainers").Parse(format) + if err != nil { + return err + } + w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + if listOpts.Watch > 0 { + for { + var responses []psReporter + tm.Clear() + tm.MoveCursor(1, 1) + tm.Flush() + listContainers, err := getResponses() + for _, r := range listContainers { + responses = append(responses, psReporter{r}) + } + if err != nil { + return err + } + if err := tmpl.Execute(w, responses); err != nil { + return nil + } + if err := w.Flush(); err != nil { + return err + } + time.Sleep(time.Duration(listOpts.Watch) * time.Second) + tm.Clear() + tm.MoveCursor(1, 1) + tm.Flush() + } + } else if listOpts.Watch < 1 { + if err := tmpl.Execute(w, responses); err != nil { + return err + } + return w.Flush() + } + return nil +} + +func createPsOut() (string, string) { + var row string + if listOpts.Namespace { + headers := "CONTAINER ID\tNAMES\tPID\tCGROUPNS\tIPC\tMNT\tNET\tPIDN\tUSERNS\tUTS\n" + row := "{{.ID}}\t{{.Names}}\t{{.Pid}}\t{{.Namespaces.Cgroup}}\t{{.Namespaces.IPC}}\t{{.Namespaces.MNT}}\t{{.Namespaces.NET}}\t{{.Namespaces.PIDNS}}\t{{.Namespaces.User}}\t{{.Namespaces.UTS}}\n" + return headers, row + } + headers := defaultHeaders + row += "{{.ID}}" + row += "\t{{.Image}}\t{{.Command}}\t{{.CreatedHuman}}\t{{.State}}\t{{.Ports}}\t{{.Names}}" + + if listOpts.Pod { + headers += "\tPOD ID\tPODNAME" + row += "\t{{.Pod}}\t{{.PodName}}" + } + + if listOpts.Size { + headers += "\tSIZE" + row += "\t{{.Size}}" + } + if !strings.HasSuffix(headers, "\n") { + headers += "\n" + } + if !strings.HasSuffix(row, "\n") { + row += "\n" + } + return headers, row +} + +type psReporter struct { + entities.ListContainer +} + +// ID returns the ID of the container +func (l psReporter) ID() string { + if !noTrunc { + return l.ListContainer.ID[0:12] + } + return l.ListContainer.ID +} + +// Pod returns the ID of the pod the container +// belongs to and appropriately truncates the ID +func (l psReporter) Pod() string { + if !noTrunc && len(l.ListContainer.Pod) > 0 { + return l.ListContainer.Pod[0:12] + } + return l.ListContainer.Pod +} + +// State returns the container state in human duration +func (l psReporter) State() string { + var state string + switch l.ListContainer.State { + case "running": + t := units.HumanDuration(time.Since(time.Unix(l.StartedAt, 0))) + state = "Up " + t + " ago" + case "configured": + state = "Created" + case "exited", "stopped": + t := units.HumanDuration(time.Since(time.Unix(l.ExitedAt, 0))) + state = fmt.Sprintf("Exited (%d) %s ago", l.ExitCode, t) + default: + state = l.ListContainer.State + } + return state +} + +// Command returns the container command in string format +func (l psReporter) Command() string { + return strings.Join(l.ListContainer.Command, " ") +} + +// Size returns the rootfs and virtual sizes in human duration in +// and output form (string) suitable for ps +func (l psReporter) Size() string { + virt := units.HumanSizeWithPrecision(float64(l.ListContainer.Size.RootFsSize), 3) + s := units.HumanSizeWithPrecision(float64(l.ListContainer.Size.RwSize), 3) + return fmt.Sprintf("%s (virtual %s)", s, virt) +} + +// Names returns the container name in string format +func (l psReporter) Names() string { + return l.ListContainer.Names[0] +} + +// Ports converts from Portmappings to the string form +// required by ps +func (l psReporter) Ports() string { + if len(l.ListContainer.Ports) < 1 { + return "" + } + return portsToString(l.ListContainer.Ports) +} + +// CreatedAt returns the container creation time in string format. podman +// and docker both return a timestamped value for createdat +func (l psReporter) CreatedAt() string { + return time.Unix(l.Created, 0).String() +} + +// CreateHuman allows us to output the created time in human readable format +func (l psReporter) CreatedHuman() string { + return units.HumanDuration(time.Since(time.Unix(l.Created, 0))) + " ago" +} + +// portsToString converts the ports used to a string of the from "port1, port2" +// and also groups a continuous list of ports into a readable format. +func portsToString(ports []ocicni.PortMapping) string { + type portGroup struct { + first int32 + last int32 + } + var portDisplay []string + if len(ports) == 0 { + return "" + } + //Sort the ports, so grouping continuous ports become easy. + sort.Slice(ports, func(i, j int) bool { + return comparePorts(ports[i], ports[j]) + }) + + // portGroupMap is used for grouping continuous ports. + portGroupMap := make(map[string]*portGroup) + var groupKeyList []string + + for _, v := range ports { + + hostIP := v.HostIP + if hostIP == "" { + hostIP = "0.0.0.0" + } + // If hostPort and containerPort are not same, consider as individual port. + if v.ContainerPort != v.HostPort { + portDisplay = append(portDisplay, fmt.Sprintf("%s:%d->%d/%s", hostIP, v.HostPort, v.ContainerPort, v.Protocol)) + continue + } + + portMapKey := fmt.Sprintf("%s/%s", hostIP, v.Protocol) + + portgroup, ok := portGroupMap[portMapKey] + if !ok { + portGroupMap[portMapKey] = &portGroup{first: v.ContainerPort, last: v.ContainerPort} + // This list is required to traverse portGroupMap. + groupKeyList = append(groupKeyList, portMapKey) + continue + } + + if portgroup.last == (v.ContainerPort - 1) { + portgroup.last = v.ContainerPort + continue + } + } + // For each portMapKey, format group list and appned to output string. + for _, portKey := range groupKeyList { + group := portGroupMap[portKey] + portDisplay = append(portDisplay, formatGroup(portKey, group.first, group.last)) + } + return strings.Join(portDisplay, ", ") +} + +func comparePorts(i, j ocicni.PortMapping) bool { + if i.ContainerPort != j.ContainerPort { + return i.ContainerPort < j.ContainerPort + } + + if i.HostIP != j.HostIP { + return i.HostIP < j.HostIP + } + + if i.HostPort != j.HostPort { + return i.HostPort < j.HostPort + } + + return i.Protocol < j.Protocol +} + +// formatGroup returns the group as <IP:startPort:lastPort->startPort:lastPort/Proto> +// e.g 0.0.0.0:1000-1006->1000-1006/tcp. +func formatGroup(key string, start, last int32) string { + parts := strings.Split(key, "/") + groupType := parts[0] + var ip string + if len(parts) > 1 { + ip = parts[0] + groupType = parts[1] + } + group := strconv.Itoa(int(start)) + if start != last { + group = fmt.Sprintf("%s-%d", group, last) + } + if ip != "" { + group = fmt.Sprintf("%s:%s->%s", ip, group, group) + } + return fmt.Sprintf("%s/%s", group, groupType) +} diff --git a/cmd/podman/containers/restart.go b/cmd/podman/containers/restart.go new file mode 100644 index 000000000..68b6de4ca --- /dev/null +++ b/cmd/podman/containers/restart.go @@ -0,0 +1,79 @@ +package containers + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + restartDescription = fmt.Sprintf(`Restarts one or more running containers. The container ID or name can be used. + + A timeout before forcibly stopping can be set, but defaults to %d seconds.`, defaultContainerConfig.Engine.StopTimeout) + + restartCommand = &cobra.Command{ + Use: "restart [flags] CONTAINER [CONTAINER...]", + Short: "Restart one or more containers", + Long: restartDescription, + RunE: restart, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, + Example: `podman restart ctrID + podman restart --latest + podman restart ctrID1 ctrID2`, + } +) + +var ( + restartOptions = entities.RestartOptions{} + restartTimeout uint +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: restartCommand, + }) + flags := restartCommand.Flags() + flags.BoolVarP(&restartOptions.All, "all", "a", false, "Restart all non-running containers") + flags.BoolVarP(&restartOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&restartOptions.Running, "running", false, "Restart only running containers when --all is used") + flags.UintVarP(&restartTimeout, "time", "t", defaultContainerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } + flags.SetNormalizeFunc(utils.AliasFlags) +} + +func restart(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + ) + if len(args) < 1 && !restartOptions.Latest && !restartOptions.All { + return errors.Wrapf(define.ErrInvalidArg, "you must provide at least one container name or ID") + } + + if cmd.Flag("time").Changed { + restartOptions.Timeout = &restartTimeout + } + responses, err := registry.ContainerEngine().ContainerRestart(context.Background(), args, restartOptions) + 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() +} diff --git a/cmd/podman/containers/restore.go b/cmd/podman/containers/restore.go new file mode 100644 index 000000000..3bc17206a --- /dev/null +++ b/cmd/podman/containers/restore.go @@ -0,0 +1,104 @@ +package containers + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/rootless" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + restoreDescription = ` + podman container restore + + Restores a container from a checkpoint. The container name or ID can be used. +` + restoreCommand = &cobra.Command{ + Use: "restore [flags] CONTAINER [CONTAINER...]", + Short: "Restores one or more containers from a checkpoint", + Long: restoreDescription, + RunE: restore, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, true, false) + }, + Example: `podman container restore ctrID + podman container restore --latest + podman container restore --all`, + } +) + +var ( + restoreOptions entities.RestoreOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: restoreCommand, + Parent: containerCmd, + }) + flags := restoreCommand.Flags() + flags.BoolVarP(&restoreOptions.All, "all", "a", false, "Restore all checkpointed containers") + flags.BoolVarP(&restoreOptions.Keep, "keep", "k", false, "Keep all temporary checkpoint files") + flags.BoolVarP(&restoreOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&restoreOptions.TCPEstablished, "tcp-established", false, "Restore a container with established TCP connections") + flags.StringVarP(&restoreOptions.Import, "import", "i", "", "Restore from exported checkpoint archive (tar.gz)") + flags.StringVarP(&restoreOptions.Name, "name", "n", "", "Specify new name for container restored from exported checkpoint (only works with --import)") + flags.BoolVar(&restoreOptions.IgnoreRootFS, "ignore-rootfs", false, "Do not apply root file-system changes when importing from exported checkpoint") + flags.BoolVar(&restoreOptions.IgnoreStaticIP, "ignore-static-ip", false, "Ignore IP address set via --static-ip") + flags.BoolVar(&restoreOptions.IgnoreStaticMAC, "ignore-static-mac", false, "Ignore MAC address set via --mac-address") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} + +func restore(cmd *cobra.Command, args []string) error { + var errs utils.OutputErrors + if rootless.IsRootless() { + return errors.New("restoring a container requires root") + } + if restoreOptions.Import == "" && restoreOptions.IgnoreRootFS { + return errors.Errorf("--ignore-rootfs can only be used with --import") + } + if restoreOptions.Import == "" && restoreOptions.Name != "" { + return errors.Errorf("--name can only be used with --import") + } + if restoreOptions.Name != "" && restoreOptions.TCPEstablished { + return errors.Errorf("--tcp-established cannot be used with --name") + } + + argLen := len(args) + if restoreOptions.Import != "" { + if restoreOptions.All || restoreOptions.Latest { + return errors.Errorf("Cannot use --import with --all or --latest") + } + if argLen > 0 { + return errors.Errorf("Cannot use --import with positional arguments") + } + } + if (restoreOptions.All || restoreOptions.Latest) && argLen > 0 { + return errors.Errorf("no arguments are needed with --all or --latest") + } + if argLen < 1 && !restoreOptions.All && !restoreOptions.Latest && restoreOptions.Import == "" { + return errors.Errorf("you must provide at least one name or id") + } + responses, err := registry.ContainerEngine().ContainerRestore(context.Background(), args, restoreOptions) + 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() + +} diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go new file mode 100644 index 000000000..a22880d93 --- /dev/null +++ b/cmd/podman/containers/rm.go @@ -0,0 +1,93 @@ +package containers + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + rmDescription = `Removes one or more containers from the host. The container name or ID can be used. + + Command does not remove images. Running or unusable containers will not be removed without the -f option.` + rmCommand = &cobra.Command{ + Use: "rm [flags] CONTAINER [CONTAINER...]", + Short: "Remove one or more containers", + Long: rmDescription, + RunE: rm, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, true) + }, + Example: `podman rm imageID + podman rm mywebserver myflaskserver 860a4b23 + podman rm --force --all + podman rm -f c684f0d469f2`, + } +) + +var ( + rmOptions = entities.RmOptions{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: rmCommand, + }) + flags := rmCommand.Flags() + flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all containers") + flags.BoolVarP(&rmOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing") + flags.BoolVarP(&rmOptions.Force, "force", "f", false, "Force removal of a running or unusable container. The default is false") + flags.BoolVarP(&rmOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&rmOptions.Storage, "storage", false, "Remove container from storage library") + flags.BoolVarP(&rmOptions.Volumes, "volumes", "v", false, "Remove anonymous volumes associated with the container") + flags.StringArrayVarP(&rmOptions.CIDFiles, "cidfile", "", nil, "Read the container ID from the file") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + _ = flags.MarkHidden("ignore") + _ = flags.MarkHidden("cidfile") + _ = flags.MarkHidden("storage") + } + +} + +func rm(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + ) + // Storage conflicts with --all/--latest/--volumes/--cidfile/--ignore + if rmOptions.Storage { + if rmOptions.All || rmOptions.Ignore || rmOptions.Latest || rmOptions.Volumes || rmOptions.CIDFiles != nil { + return errors.Errorf("--storage conflicts with --volumes, --all, --latest, --ignore and --cidfile") + } + } + responses, err := registry.ContainerEngine().ContainerRm(context.Background(), args, rmOptions) + if err != nil { + // TODO exitcode is a global main variable to track exit codes. + // we need this enabled + //if len(c.InputArgs) < 2 { + // exitCode = setExitCode(err) + //} + return err + } + for _, r := range responses { + if r.Err != nil { + // TODO this will not work with the remote client + if errors.Cause(err) == define.ErrWillDeadlock { + logrus.Errorf("Potential deadlock detected - please run 'podman system renumber' to resolve") + } + errs = append(errs, r.Err) + } else { + fmt.Println(r.Id) + } + } + return errs.PrintErrors() +} diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go new file mode 100644 index 000000000..91edb6bda --- /dev/null +++ b/cmd/podman/containers/run.go @@ -0,0 +1,147 @@ +package containers + +import ( + "fmt" + "os" + "strings" + + "github.com/containers/common/pkg/config" + "github.com/containers/libpod/cmd/podman/common" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/specgen" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + runDescription = "Runs a command in a new container from the given image" + runCommand = &cobra.Command{ + Use: "run [flags] IMAGE [COMMAND [ARG...]]", + Short: "Run a command in a new container", + Long: runDescription, + RunE: run, + Example: `podman run imageID ls -alF /etc + podman run --network=host imageID dnf -y install java + podman run --volume /var/hostdir:/var/ctrdir -i -t fedora /bin/bash`, + } +) + +var ( + runOpts = entities.ContainerRunOptions{ + OutputStream: os.Stdout, + InputStream: os.Stdin, + ErrorStream: os.Stderr, + } + runRmi bool +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: runCommand, + }) + flags := runCommand.Flags() + flags.SetInterspersed(false) + flags.AddFlagSet(common.GetCreateFlags(&cliVals)) + flags.AddFlagSet(common.GetNetFlags()) + flags.SetNormalizeFunc(common.AliasFlags) + flags.BoolVar(&runOpts.SigProxy, "sig-proxy", true, "Proxy received signals to the process") + flags.BoolVar(&runRmi, "rmi", false, "Remove container image unless used by other containers") + if registry.IsRemote() { + _ = flags.MarkHidden("authfile") + } +} + +func run(cmd *cobra.Command, args []string) error { + var err error + cliVals.Net, err = common.NetFlagsToNetOptions(cmd) + if err != nil { + return err + } + + if af := cliVals.Authfile; len(af) > 0 { + if _, err := os.Stat(af); err != nil { + return errors.Wrapf(err, "error checking authfile path %s", af) + } + } + runOpts.Rm = cliVals.Rm + if err := createInit(cmd); err != nil { + return err + } + + br, err := registry.ImageEngine().Exists(registry.GetContext(), args[0]) + if err != nil { + return err + } + pullPolicy, err := config.ValidatePullPolicy(cliVals.Pull) + if err != nil { + return err + } + if !br.Value || pullPolicy == config.PullImageAlways { + if pullPolicy == config.PullImageNever { + return errors.New("unable to find a name and tag match for busybox in repotags: no such image") + } + _, pullErr := registry.ImageEngine().Pull(registry.GetContext(), args[0], entities.ImagePullOptions{ + Authfile: cliVals.Authfile, + Quiet: cliVals.Quiet, + }) + if pullErr != nil { + return pullErr + } + } + // If -i is not set, clear stdin + if !cliVals.Interactive { + runOpts.InputStream = nil + } + + // If attach is set, clear stdin/stdout/stderr and only attach requested + if cmd.Flag("attach").Changed { + runOpts.OutputStream = nil + runOpts.ErrorStream = nil + if !cliVals.Interactive { + runOpts.InputStream = nil + } + + for _, stream := range cliVals.Attach { + switch strings.ToLower(stream) { + case "stdout": + runOpts.OutputStream = os.Stdout + case "stderr": + runOpts.ErrorStream = os.Stderr + case "stdin": + runOpts.InputStream = os.Stdin + default: + return errors.Wrapf(define.ErrInvalidArg, "invalid stream %q for --attach - must be one of stdin, stdout, or stderr", stream) + } + } + } + runOpts.Detach = cliVals.Detach + runOpts.DetachKeys = cliVals.DetachKeys + s := specgen.NewSpecGenerator(args[0]) + if err := common.FillOutSpecGen(s, &cliVals, args); err != nil { + return err + } + runOpts.Spec = s + + report, err := registry.ContainerEngine().ContainerRun(registry.GetContext(), runOpts) + // report.ExitCode is set by ContainerRun even it it returns an error + if report != nil { + registry.SetExitCode(report.ExitCode) + } + if err != nil { + return err + } + if cliVals.Detach { + fmt.Println(report.Id) + } + if runRmi { + _, err := registry.ImageEngine().Delete(registry.GetContext(), []string{args[0]}, entities.ImageDeleteOptions{}) + if err != nil { + logrus.Errorf("%s", errors.Wrapf(err, "failed removing image")) + } + } + return nil +} diff --git a/cmd/podman/containers/start.go b/cmd/podman/containers/start.go new file mode 100644 index 000000000..33e5a3094 --- /dev/null +++ b/cmd/podman/containers/start.go @@ -0,0 +1,86 @@ +package containers + +import ( + "fmt" + "os" + + "github.com/containers/libpod/cmd/podman/common" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + startDescription = `Starts one or more containers. The container name or ID can be used.` + startCommand = &cobra.Command{ + Use: "start [flags] CONTAINER [CONTAINER...]", + Short: "Start one or more containers", + Long: startDescription, + RunE: start, + Args: cobra.MinimumNArgs(1), + Example: `podman start --latest + podman start 860a4b231279 5421ab43b45 + podman start --interactive --attach imageID`, + } +) + +var ( + startOptions entities.ContainerStartOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: startCommand, + }) + flags := startCommand.Flags() + flags.BoolVarP(&startOptions.Attach, "attach", "a", false, "Attach container's STDOUT and STDERR") + flags.StringVar(&startOptions.DetachKeys, "detach-keys", common.GetDefaultDetachKeys(), "Select the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`") + flags.BoolVarP(&startOptions.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached") + flags.BoolVarP(&startOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&startOptions.SigProxy, "sig-proxy", false, "Proxy received signals to the process (default true if attaching, false otherwise)") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + _ = flags.MarkHidden("sig-proxy") + } + +} + +func start(cmd *cobra.Command, args []string) error { + var errs utils.OutputErrors + if len(args) > 1 && startOptions.Attach { + return errors.Errorf("you cannot start and attach multiple containers at once") + } + + sigProxy := startOptions.SigProxy || startOptions.Attach + if cmd.Flag("sig-proxy").Changed { + sigProxy = startOptions.SigProxy + } + + if sigProxy && !startOptions.Attach { + return errors.Wrapf(define.ErrInvalidArg, "you cannot use sig-proxy without --attach") + } + if startOptions.Attach { + startOptions.Stdin = os.Stdin + startOptions.Stderr = os.Stderr + startOptions.Stdout = os.Stdout + } + + responses, err := registry.ContainerEngine().ContainerStart(registry.GetContext(), args, startOptions) + if err != nil { + return err + } + + for _, r := range responses { + if r.Err == nil { + fmt.Println(r.Id) + } else { + errs = append(errs, r.Err) + } + } + // TODO need to understand an implement exitcodes + return errs.PrintErrors() +} diff --git a/cmd/podman/containers/stop.go b/cmd/podman/containers/stop.go new file mode 100644 index 000000000..c1560be08 --- /dev/null +++ b/cmd/podman/containers/stop.go @@ -0,0 +1,78 @@ +package containers + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + stopDescription = fmt.Sprintf(`Stops one or more running containers. The container name or ID can be used. + + A timeout to forcibly stop the container can also be set but defaults to %d seconds otherwise.`, defaultContainerConfig.Engine.StopTimeout) + stopCommand = &cobra.Command{ + Use: "stop [flags] CONTAINER [CONTAINER...]", + Short: "Stop one or more containers", + Long: stopDescription, + RunE: stop, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, true) + }, + Example: `podman stop ctrID + podman stop --latest + podman stop --time 2 mywebserver 6e534f14da9d`, + } +) + +var ( + stopOptions = entities.StopOptions{} + stopTimeout uint +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: stopCommand, + }) + flags := stopCommand.Flags() + flags.BoolVarP(&stopOptions.All, "all", "a", false, "Stop all running containers") + flags.BoolVarP(&stopOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing") + flags.StringArrayVarP(&stopOptions.CIDFiles, "cidfile", "", nil, "Read the container ID from the file") + flags.BoolVarP(&stopOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.UintVarP(&stopTimeout, "time", "t", defaultContainerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container") + + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + _ = flags.MarkHidden("cidfile") + _ = flags.MarkHidden("ignore") + } + flags.SetNormalizeFunc(utils.AliasFlags) +} + +func stop(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + ) + stopOptions.Timeout = defaultContainerConfig.Engine.StopTimeout + if cmd.Flag("time").Changed { + stopOptions.Timeout = stopTimeout + } + + responses, err := registry.ContainerEngine().ContainerStop(context.Background(), args, stopOptions) + 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() +} diff --git a/cmd/podman/containers/top.go b/cmd/podman/containers/top.go new file mode 100644 index 000000000..db5213863 --- /dev/null +++ b/cmd/podman/containers/top.go @@ -0,0 +1,87 @@ +package containers + +import ( + "context" + "fmt" + "os" + "strings" + "text/tabwriter" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/psgo" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + topDescription = fmt.Sprintf(`Similar to system "top" command. + + Specify format descriptors to alter the output. + + Running "podman top -l pid pcpu seccomp" will print the process ID, the CPU percentage and the seccomp mode of each process of the latest container. + Format Descriptors: + %s`, strings.Join(psgo.ListDescriptors(), ",")) + + topOptions = entities.TopOptions{} + + topCommand = &cobra.Command{ + Use: "top [flags] CONTAINER [FORMAT-DESCRIPTORS|ARGS]", + Short: "Display the running processes of a container", + Long: topDescription, + RunE: top, + Args: cobra.ArbitraryArgs, + Example: `podman top ctrID +podman top --latest +podman top ctrID pid seccomp args %C +podman top ctrID -eo user,pid,comm`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: topCommand, + }) + + flags := topCommand.Flags() + flags.SetInterspersed(false) + flags.BoolVar(&topOptions.ListDescriptors, "list-descriptors", false, "") + flags.BoolVarP(&topOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + + _ = flags.MarkHidden("list-descriptors") // meant only for bash completion + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} + +func top(cmd *cobra.Command, args []string) error { + if topOptions.ListDescriptors { + fmt.Println(strings.Join(psgo.ListDescriptors(), "\n")) + return nil + } + + if len(args) < 1 && !topOptions.Latest { + return errors.Errorf("you must provide the name or id of a running container") + } + + if topOptions.Latest { + topOptions.Descriptors = args + } else { + topOptions.NameOrID = args[0] + topOptions.Descriptors = args[1:] + } + + topResponse, err := registry.ContainerEngine().ContainerTop(context.Background(), topOptions) + if err != nil { + return err + } + + w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) + for _, proc := range topResponse.Value { + if _, err := fmt.Fprintln(w, proc); err != nil { + return err + } + } + return w.Flush() +} diff --git a/cmd/podman/containers/unmount.go b/cmd/podman/containers/unmount.go new file mode 100644 index 000000000..3dbfc1eae --- /dev/null +++ b/cmd/podman/containers/unmount.go @@ -0,0 +1,64 @@ +package containers + +import ( + "fmt" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + description = `Container storage increments a mount counter each time a container is mounted. + + When a container is unmounted, the mount counter is decremented. The container's root filesystem is physically unmounted only when the mount counter reaches zero indicating no other processes are using the mount. + + An unmount can be forced with the --force flag. +` + umountCommand = &cobra.Command{ + Use: "umount [flags] CONTAINER [CONTAINER...]", + Aliases: []string{"unmount"}, + Short: "Unmounts working container's root filesystem", + Long: description, + RunE: unmount, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, + Example: `podman umount ctrID + podman umount ctrID1 ctrID2 ctrID3 + podman umount --all`, + } +) + +var ( + unmountOpts entities.ContainerUnmountOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: umountCommand, + }) + flags := umountCommand.Flags() + flags.BoolVarP(&unmountOpts.All, "all", "a", false, "Umount all of the currently mounted containers") + flags.BoolVarP(&unmountOpts.Force, "force", "f", false, "Force the complete umount all of the currently mounted containers") + flags.BoolVarP(&unmountOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") +} + +func unmount(cmd *cobra.Command, args []string) error { + var errs utils.OutputErrors + reports, err := registry.ContainerEngine().ContainerUnmount(registry.GetContext(), args, unmountOpts) + if err != nil { + return err + } + for _, r := range reports { + if r.Err == nil { + fmt.Println(r.Id) + } else { + errs = append(errs, r.Err) + } + } + return errs.PrintErrors() +} diff --git a/cmd/podman/containers/unpause.go b/cmd/podman/containers/unpause.go new file mode 100644 index 000000000..ef874b042 --- /dev/null +++ b/cmd/podman/containers/unpause.go @@ -0,0 +1,60 @@ +package containers + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/rootless" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + unpauseDescription = `Unpauses one or more previously paused containers. The container name or ID can be used.` + unpauseCommand = &cobra.Command{ + Use: "unpause [flags] CONTAINER [CONTAINER...]", + Short: "Unpause the processes in one or more containers", + Long: unpauseDescription, + RunE: unpause, + Example: `podman unpause ctrID + podman unpause --all`, + } + unPauseOptions = entities.PauseUnPauseOptions{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: unpauseCommand, + Parent: containerCmd, + }) + flags := unpauseCommand.Flags() + flags.BoolVarP(&unPauseOptions.All, "all", "a", false, "Pause all running containers") +} + +func unpause(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + ) + if rootless.IsRootless() && !registry.IsRemote() { + return errors.New("unpause is not supported for rootless containers") + } + if len(args) < 1 && !unPauseOptions.All { + return errors.Errorf("you must provide at least one container name or id") + } + responses, err := registry.ContainerEngine().ContainerUnpause(context.Background(), args, unPauseOptions) + 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() +} diff --git a/cmd/podman/containers/utils.go b/cmd/podman/containers/utils.go new file mode 100644 index 000000000..0c09d3e40 --- /dev/null +++ b/cmd/podman/containers/utils.go @@ -0,0 +1 @@ +package containers diff --git a/cmd/podman/containers/wait.go b/cmd/podman/containers/wait.go new file mode 100644 index 000000000..47f28f4c6 --- /dev/null +++ b/cmd/podman/containers/wait.go @@ -0,0 +1,78 @@ +package containers + +import ( + "context" + "fmt" + "time" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + waitDescription = `Block until one or more containers stop and then print their exit codes. +` + waitCommand = &cobra.Command{ + Use: "wait [flags] CONTAINER [CONTAINER...]", + Short: "Block on one or more containers", + Long: waitDescription, + RunE: wait, + Args: registry.IdOrLatestArgs, + Example: `podman wait --latest + podman wait --interval 5000 ctrID + podman wait ctrID1 ctrID2`, + } +) + +var ( + waitOptions = entities.WaitOptions{} + waitCondition string +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: waitCommand, + }) + + flags := waitCommand.Flags() + flags.DurationVarP(&waitOptions.Interval, "interval", "i", time.Duration(250), "Milliseconds to wait before polling for completion") + flags.BoolVarP(&waitOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.StringVar(&waitCondition, "condition", "stopped", "Condition to wait on") + if registry.IsRemote() { + // TODO: This is the same as V1. We could skip creating the flag altogether in V2... + _ = flags.MarkHidden("latest") + } +} + +func wait(cmd *cobra.Command, args []string) error { + var ( + err error + errs utils.OutputErrors + ) + if waitOptions.Interval == 0 { + return errors.New("interval must be greater then 0") + } + + waitOptions.Condition, err = define.StringToContainerStatus(waitCondition) + if err != nil { + return err + } + + responses, err := registry.ContainerEngine().ContainerWait(context.Background(), args, waitOptions) + if err != nil { + return err + } + for _, r := range responses { + if r.Error == nil { + fmt.Println(r.ExitCode) + } else { + errs = append(errs, r.Error) + } + } + return errs.PrintErrors() +} diff --git a/cmd/podman/containers_prune.go b/cmd/podman/containers_prune.go deleted file mode 100644 index cd9817e7e..000000000 --- a/cmd/podman/containers_prune.go +++ /dev/null @@ -1,86 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "os" - "strings" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - pruneContainersCommand cliconfig.PruneContainersValues - pruneContainersDescription = ` - podman container prune - - Removes all exited containers -` - _pruneContainersCommand = &cobra.Command{ - Use: "prune", - Args: noSubArgs, - Short: "Remove all stopped containers", - Long: pruneContainersDescription, - RunE: func(cmd *cobra.Command, args []string) error { - pruneContainersCommand.InputArgs = args - pruneContainersCommand.GlobalFlags = MainGlobalOpts - pruneContainersCommand.Remote = remoteclient - return pruneContainersCmd(&pruneContainersCommand) - }, - } -) - -func init() { - pruneContainersCommand.Command = _pruneContainersCommand - pruneContainersCommand.SetHelpTemplate(HelpTemplate()) - pruneContainersCommand.SetUsageTemplate(UsageTemplate()) - flags := pruneContainersCommand.Flags() - flags.BoolVarP(&pruneContainersCommand.Force, "force", "f", false, "Skip interactive prompt for container removal") - flags.StringArrayVar(&pruneContainersCommand.Filter, "filter", []string{}, "Provide filter values (e.g. 'until=<timestamp>')") -} - -func pruneContainersCmd(c *cliconfig.PruneContainersValues) error { - if !c.Force { - reader := bufio.NewReader(os.Stdin) - fmt.Printf(`WARNING! This will remove all stopped containers. -Are you sure you want to continue? [y/N] `) - answer, err := reader.ReadString('\n') - if err != nil { - return errors.Wrapf(err, "error reading input") - } - if strings.ToLower(answer)[0] != 'y' { - return nil - } - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - maxWorkers := shared.DefaultPoolSize("prune") - if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalFlags.MaxWorks - } - ok, failures, err := runtime.Prune(getContext(), maxWorkers, c.Filter) - if err != nil { - if errors.Cause(err) == define.ErrNoSuchCtr { - if len(c.InputArgs) > 1 { - exitCode = define.ExecErrorCodeGeneric - } else { - exitCode = 1 - } - } - return err - } - if len(failures) > 0 { - exitCode = define.ExecErrorCodeGeneric - } - return printCmdResults(ok, failures) -} diff --git a/cmd/podman/cp.go b/cmd/podman/cp.go deleted file mode 100644 index 205103381..000000000 --- a/cmd/podman/cp.go +++ /dev/null @@ -1,490 +0,0 @@ -package main - -import ( - "archive/tar" - "fmt" - "io" - "os" - "path/filepath" - "strings" - - "github.com/containers/buildah/pkg/chrootuser" - "github.com/containers/buildah/util" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/cgroups" - "github.com/containers/libpod/pkg/rootless" - "github.com/containers/storage" - "github.com/containers/storage/pkg/archive" - "github.com/containers/storage/pkg/chrootarchive" - "github.com/containers/storage/pkg/idtools" - securejoin "github.com/cyphar/filepath-securejoin" - "github.com/opencontainers/go-digest" - "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - cpCommand cliconfig.CpValues - - cpDescription = `Command copies the contents of SRC_PATH to the DEST_PATH. - - You can copy from the container's file system to the local machine or the reverse, from the local filesystem to the container. If "-" is specified for either the SRC_PATH or DEST_PATH, you can also stream a tar archive from STDIN or to STDOUT. The CONTAINER can be a running or stopped container. The SRC_PATH or DEST_PATH can be a file or directory. -` - _cpCommand = &cobra.Command{ - Use: "cp [flags] SRC_PATH DEST_PATH", - Short: "Copy files/folders between a container and the local filesystem", - Long: cpDescription, - RunE: func(cmd *cobra.Command, args []string) error { - cpCommand.InputArgs = args - cpCommand.GlobalFlags = MainGlobalOpts - cpCommand.Remote = remoteclient - return cpCmd(&cpCommand) - }, - Example: "[CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH", - } -) - -func init() { - cpCommand.Command = _cpCommand - flags := cpCommand.Flags() - flags.BoolVar(&cpCommand.Extract, "extract", false, "Extract the tar file into the destination directory.") - flags.BoolVar(&cpCommand.Pause, "pause", copyPause(), "Pause the container while copying") - cpCommand.SetHelpTemplate(HelpTemplate()) - cpCommand.SetUsageTemplate(UsageTemplate()) -} - -func cpCmd(c *cliconfig.CpValues) error { - args := c.InputArgs - if len(args) != 2 { - return errors.Errorf("you must provide a source path and a destination path") - } - - runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - return copyBetweenHostAndContainer(runtime, args[0], args[1], c.Extract, c.Pause) -} - -func copyBetweenHostAndContainer(runtime *libpod.Runtime, src string, dest string, extract bool, pause bool) error { - - srcCtr, srcPath := parsePath(runtime, src) - destCtr, destPath := parsePath(runtime, dest) - - if (srcCtr == nil && destCtr == nil) || (srcCtr != nil && destCtr != nil) { - return errors.Errorf("invalid arguments %s, %s you must use just one container", src, dest) - } - - if len(srcPath) == 0 || len(destPath) == 0 { - return errors.Errorf("invalid arguments %s, %s you must specify paths", src, dest) - } - ctr := srcCtr - isFromHostToCtr := ctr == nil - if isFromHostToCtr { - ctr = destCtr - } - - mountPoint, err := ctr.Mount() - if err != nil { - return err - } - defer func() { - if err := ctr.Unmount(false); err != nil { - logrus.Errorf("unable to umount container '%s': %q", ctr.ID(), err) - } - }() - - if pause { - if err := ctr.Pause(); err != nil { - // An invalid state error is fine. - // The container isn't running or is already paused. - // TODO: We can potentially start the container while - // the copy is running, which still allows a race where - // malicious code could mess with the symlink. - if errors.Cause(err) != define.ErrCtrStateInvalid { - return err - } - } else { - // Only add the defer if we actually paused - defer func() { - if err := ctr.Unpause(); err != nil { - logrus.Errorf("Error unpausing container after copying: %v", err) - } - }() - } - } - - user, err := getUser(mountPoint, ctr.User()) - if err != nil { - return err - } - idMappingOpts, err := ctr.IDMappings() - if err != nil { - return errors.Wrapf(err, "error getting IDMappingOptions") - } - destOwner := idtools.IDPair{UID: int(user.UID), GID: int(user.GID)} - hostUID, hostGID, err := util.GetHostIDs(convertIDMap(idMappingOpts.UIDMap), convertIDMap(idMappingOpts.GIDMap), user.UID, user.GID) - if err != nil { - return err - } - - hostOwner := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)} - - if isFromHostToCtr { - if isVol, volDestName, volName := isVolumeDestName(destPath, ctr); isVol { //nolint(gocritic) - path, err := pathWithVolumeMount(ctr, runtime, volDestName, volName, destPath) - if err != nil { - return errors.Wrapf(err, "error getting destination path from volume %s", volDestName) - } - destPath = path - } else if isBindMount, mount := isBindMountDestName(destPath, ctr); isBindMount { //nolint(gocritic) - path, err := pathWithBindMountSource(mount, destPath) - if err != nil { - return errors.Wrapf(err, "error getting destination path from bind mount %s", mount.Destination) - } - destPath = path - } else if filepath.IsAbs(destPath) { //nolint(gocritic) - cleanedPath, err := securejoin.SecureJoin(mountPoint, destPath) - if err != nil { - return err - } - destPath = cleanedPath - } else { //nolint(gocritic) - ctrWorkDir, err := securejoin.SecureJoin(mountPoint, ctr.WorkingDir()) - if err != nil { - return err - } - if err = idtools.MkdirAllAndChownNew(ctrWorkDir, 0755, hostOwner); err != nil { - return errors.Wrapf(err, "error creating directory %q", destPath) - } - cleanedPath, err := securejoin.SecureJoin(mountPoint, filepath.Join(ctr.WorkingDir(), destPath)) - if err != nil { - return err - } - destPath = cleanedPath - } - } else { - destOwner = idtools.IDPair{UID: os.Getuid(), GID: os.Getgid()} - if isVol, volDestName, volName := isVolumeDestName(srcPath, ctr); isVol { //nolint(gocritic) - path, err := pathWithVolumeMount(ctr, runtime, volDestName, volName, srcPath) - if err != nil { - return errors.Wrapf(err, "error getting source path from volume %s", volDestName) - } - srcPath = path - } else if isBindMount, mount := isBindMountDestName(srcPath, ctr); isBindMount { //nolint(gocritic) - path, err := pathWithBindMountSource(mount, srcPath) - if err != nil { - return errors.Wrapf(err, "error getting source path from bind mount %s", mount.Destination) - } - srcPath = path - } else if filepath.IsAbs(srcPath) { //nolint(gocritic) - cleanedPath, err := securejoin.SecureJoin(mountPoint, srcPath) - if err != nil { - return err - } - srcPath = cleanedPath - } else { //nolint(gocritic) - cleanedPath, err := securejoin.SecureJoin(mountPoint, filepath.Join(ctr.WorkingDir(), srcPath)) - if err != nil { - return err - } - srcPath = cleanedPath - } - } - - if !filepath.IsAbs(destPath) { - dir, err := os.Getwd() - if err != nil { - return errors.Wrapf(err, "err getting current working directory") - } - destPath = filepath.Join(dir, destPath) - } - - if src == "-" { - srcPath = os.Stdin.Name() - extract = true - } - return copy(srcPath, destPath, src, dest, idMappingOpts, &destOwner, extract, isFromHostToCtr) -} - -func getUser(mountPoint string, userspec string) (specs.User, error) { - uid, gid, _, err := chrootuser.GetUser(mountPoint, userspec) - u := specs.User{ - UID: uid, - GID: gid, - Username: userspec, - } - if !strings.Contains(userspec, ":") { - groups, err2 := chrootuser.GetAdditionalGroupsForUser(mountPoint, uint64(u.UID)) - if err2 != nil { - if errors.Cause(err2) != chrootuser.ErrNoSuchUser && err == nil { - err = err2 - } - } else { - u.AdditionalGids = groups - } - - } - return u, err -} - -func parsePath(runtime *libpod.Runtime, path string) (*libpod.Container, string) { - pathArr := strings.SplitN(path, ":", 2) - if len(pathArr) == 2 { - ctr, err := runtime.LookupContainer(pathArr[0]) - if err == nil { - return ctr, pathArr[1] - } - } - return nil, path -} - -func evalSymlinks(path string) (string, error) { - if path == os.Stdin.Name() { - return path, nil - } - return filepath.EvalSymlinks(path) -} - -func getPathInfo(path string) (string, os.FileInfo, error) { - path, err := evalSymlinks(path) - if err != nil { - return "", nil, errors.Wrapf(err, "error evaluating symlinks %q", path) - } - srcfi, err := os.Stat(path) - if err != nil { - return "", nil, errors.Wrapf(err, "error reading path %q", path) - } - return path, srcfi, nil -} - -func copy(srcPath, destPath, src, dest string, idMappingOpts storage.IDMappingOptions, chownOpts *idtools.IDPair, extract, isFromHostToCtr bool) error { - srcPath, err := evalSymlinks(srcPath) - if err != nil { - return errors.Wrapf(err, "error evaluating symlinks %q", srcPath) - } - - srcPath, srcfi, err := getPathInfo(srcPath) - if err != nil { - return err - } - - filename := filepath.Base(destPath) - if filename == "-" && !isFromHostToCtr { - err := streamFileToStdout(srcPath, srcfi) - if err != nil { - return errors.Wrapf(err, "error streaming source file %s to Stdout", srcPath) - } - return nil - } - - destdir := destPath - if !srcfi.IsDir() { - destdir = filepath.Dir(destPath) - } - _, err = os.Stat(destdir) - if err != nil && !os.IsNotExist(err) { - return errors.Wrapf(err, "error checking directory %q", destdir) - } - destDirIsExist := err == nil - if err = os.MkdirAll(destdir, 0755); err != nil { - return errors.Wrapf(err, "error creating directory %q", destdir) - } - - // return functions for copying items - copyFileWithTar := chrootarchive.CopyFileWithTarAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap) - copyWithTar := chrootarchive.CopyWithTarAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap) - untarPath := chrootarchive.UntarPathAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap) - - if srcfi.IsDir() { - logrus.Debugf("copying %q to %q", srcPath+string(os.PathSeparator)+"*", dest+string(os.PathSeparator)+"*") - if destDirIsExist && !strings.HasSuffix(src, fmt.Sprintf("%s.", string(os.PathSeparator))) { - destPath = filepath.Join(destPath, filepath.Base(srcPath)) - } - if err = copyWithTar(srcPath, destPath); err != nil { - return errors.Wrapf(err, "error copying %q to %q", srcPath, dest) - } - return nil - } - - if extract { - // We're extracting an archive into the destination directory. - logrus.Debugf("extracting contents of %q into %q", srcPath, destPath) - if err = untarPath(srcPath, destPath); err != nil { - return errors.Wrapf(err, "error extracting %q into %q", srcPath, destPath) - } - return nil - } - - destfi, err := os.Stat(destPath) - if err != nil { - if !os.IsNotExist(err) || strings.HasSuffix(dest, string(os.PathSeparator)) { - return errors.Wrapf(err, "failed to get stat of dest path %s", destPath) - } - } - if destfi != nil && destfi.IsDir() { - destPath = filepath.Join(destPath, filepath.Base(srcPath)) - } - - // Copy the file, preserving attributes. - logrus.Debugf("copying %q to %q", srcPath, destPath) - if err = copyFileWithTar(srcPath, destPath); err != nil { - return errors.Wrapf(err, "error copying %q to %q", srcPath, destPath) - } - return nil -} - -func convertIDMap(idMaps []idtools.IDMap) (convertedIDMap []specs.LinuxIDMapping) { - for _, idmap := range idMaps { - tempIDMap := specs.LinuxIDMapping{ - ContainerID: uint32(idmap.ContainerID), - HostID: uint32(idmap.HostID), - Size: uint32(idmap.Size), - } - convertedIDMap = append(convertedIDMap, tempIDMap) - } - return convertedIDMap -} - -func streamFileToStdout(srcPath string, srcfi os.FileInfo) error { - if srcfi.IsDir() { - tw := tar.NewWriter(os.Stdout) - err := filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error { - if err != nil || !info.Mode().IsRegular() || path == srcPath { - return err - } - hdr, err := tar.FileInfoHeader(info, "") - if err != nil { - return err - } - - if err = tw.WriteHeader(hdr); err != nil { - return err - } - fh, err := os.Open(path) - if err != nil { - return err - } - defer fh.Close() - - _, err = io.Copy(tw, fh) - return err - }) - if err != nil { - return errors.Wrapf(err, "error streaming directory %s to Stdout", srcPath) - } - return nil - } - - file, err := os.Open(srcPath) - if err != nil { - return errors.Wrapf(err, "error opening file %s", srcPath) - } - defer file.Close() - if !archive.IsArchivePath(srcPath) { - tw := tar.NewWriter(os.Stdout) - hdr, err := tar.FileInfoHeader(srcfi, "") - if err != nil { - return err - } - err = tw.WriteHeader(hdr) - if err != nil { - return err - } - _, err = io.Copy(tw, file) - if err != nil { - return errors.Wrapf(err, "error streaming archive %s to Stdout", srcPath) - } - return nil - } - - _, err = io.Copy(os.Stdout, file) - if err != nil { - return errors.Wrapf(err, "error streaming file to Stdout") - } - return nil -} - -func isVolumeDestName(path string, ctr *libpod.Container) (bool, string, string) { - separator := string(os.PathSeparator) - if filepath.IsAbs(path) { - path = strings.TrimPrefix(path, separator) - } - if path == "" { - return false, "", "" - } - for _, vol := range ctr.Config().NamedVolumes { - volNamePath := strings.TrimPrefix(vol.Dest, separator) - if matchVolumePath(path, volNamePath) { - return true, vol.Dest, vol.Name - } - } - return false, "", "" -} - -// if SRCPATH or DESTPATH is from volume mount's destination -v or --mount type=volume, generates the path with volume mount point -func pathWithVolumeMount(ctr *libpod.Container, runtime *libpod.Runtime, volDestName, volName, path string) (string, error) { - destVolume, err := runtime.GetVolume(volName) - if err != nil { - return "", errors.Wrapf(err, "error getting volume destination %s", volName) - } - if !filepath.IsAbs(path) { - path = filepath.Join(string(os.PathSeparator), path) - } - path, err = securejoin.SecureJoin(destVolume.MountPoint(), strings.TrimPrefix(path, volDestName)) - return path, err -} - -func isBindMountDestName(path string, ctr *libpod.Container) (bool, specs.Mount) { - separator := string(os.PathSeparator) - if filepath.IsAbs(path) { - path = strings.TrimPrefix(path, string(os.PathSeparator)) - } - if path == "" { - return false, specs.Mount{} - } - for _, m := range ctr.Config().Spec.Mounts { - if m.Type != "bind" { - continue - } - mDest := strings.TrimPrefix(m.Destination, separator) - if matchVolumePath(path, mDest) { - return true, m - } - } - return false, specs.Mount{} -} - -func matchVolumePath(path, target string) bool { - pathStr := filepath.Clean(path) - target = filepath.Clean(target) - for len(pathStr) > len(target) && strings.Contains(pathStr, string(os.PathSeparator)) { - pathStr = pathStr[:strings.LastIndex(pathStr, string(os.PathSeparator))] - } - return pathStr == target -} - -func pathWithBindMountSource(m specs.Mount, path string) (string, error) { - if !filepath.IsAbs(path) { - path = filepath.Join(string(os.PathSeparator), path) - } - return securejoin.SecureJoin(m.Source, strings.TrimPrefix(path, m.Destination)) -} - -func copyPause() bool { - if !remoteclient && rootless.IsRootless() { - cgroupv2, _ := cgroups.IsCgroup2UnifiedMode() - if !cgroupv2 { - logrus.Debugf("defaulting to pause==false on rootless cp in cgroupv1 systems") - return false - } - } - return true -} diff --git a/cmd/podman/create.go b/cmd/podman/create.go deleted file mode 100644 index 03eb1b09f..000000000 --- a/cmd/podman/create.go +++ /dev/null @@ -1,100 +0,0 @@ -package main - -import ( - "fmt" - "os" - "strings" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/opentracing/opentracing-go" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - createCommand cliconfig.CreateValues - createDescription = `Creates a new container from the given image or storage and prepares it for running the specified command. - - The container ID is then printed to stdout. You can then start it at any time with the podman start <container_id> command. The container will be created with the initial state 'created'.` - _createCommand = &cobra.Command{ - Use: "create [flags] IMAGE [COMMAND [ARG...]]", - Short: "Create but do not start a container", - Long: createDescription, - RunE: func(cmd *cobra.Command, args []string) error { - createCommand.InputArgs = args - createCommand.GlobalFlags = MainGlobalOpts - createCommand.Remote = remoteclient - return createCmd(&createCommand) - }, - Example: `podman create alpine ls - podman create --annotation HELLO=WORLD alpine ls - podman create -t -i --name myctr alpine ls`, - } -) - -func init() { - createCommand.PodmanCommand.Command = _createCommand - createCommand.SetHelpTemplate(HelpTemplate()) - createCommand.SetUsageTemplate(UsageTemplate()) - getCreateFlags(&createCommand.PodmanCommand) - flags := createCommand.Flags() - flags.AddFlagSet(getNetFlags()) - flags.SetInterspersed(false) - flags.SetNormalizeFunc(aliasFlags) -} - -func createCmd(c *cliconfig.CreateValues) error { - if c.Bool("trace") { - span, _ := opentracing.StartSpanFromContext(Ctx, "createCmd") - defer span.Finish() - } - - if c.String("authfile") != "" { - if _, err := os.Stat(c.String("authfile")); err != nil { - return errors.Wrapf(err, "error getting authfile %s", c.String("authfile")) - } - } - - if err := createInit(&c.PodmanCommand); err != nil { - return err - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - cid, err := runtime.CreateContainer(getContext(), c) - if err != nil { - return err - } - fmt.Printf("%s\n", cid) - return nil -} - -func createInit(c *cliconfig.PodmanCommand) error { - if !remote && c.Bool("trace") { - span, _ := opentracing.StartSpanFromContext(Ctx, "createInit") - defer span.Finish() - } - - if c.IsSet("privileged") && c.IsSet("security-opt") { - logrus.Warn("setting security options with --privileged has no effect") - } - - if (c.IsSet("dns") || c.IsSet("dns-opt") || c.IsSet("dns-search")) && (c.String("network") == "none" || strings.HasPrefix(c.String("network"), "container:")) { - return errors.Errorf("conflicting options: dns and the network mode.") - } - - // Docker-compatibility: the "-h" flag for run/create is reserved for - // the hostname (see https://github.com/containers/libpod/issues/1367). - - if len(c.InputArgs) < 1 { - return errors.Errorf("image name or ID is required") - } - - return nil -} diff --git a/cmd/podman/diff.go b/cmd/podman/diff.go index c15512360..8db76e8af 100644 --- a/cmd/podman/diff.go +++ b/cmd/podman/diff.go @@ -3,139 +3,59 @@ package main import ( "fmt" - "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/storage/pkg/archive" - "github.com/pkg/errors" + "github.com/containers/libpod/cmd/podman/containers" + "github.com/containers/libpod/cmd/podman/images" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) -type diffJSONOutput struct { - Changed []string `json:"changed,omitempty"` - Added []string `json:"added,omitempty"` - Deleted []string `json:"deleted,omitempty"` -} - -type diffOutputParams struct { - Change archive.ChangeType - Path string -} - -type stdoutStruct struct { - output []diffOutputParams -} - -func (so stdoutStruct) Out() error { - for _, d := range so.output { - fmt.Printf("%s %s\n", d.Change, d.Path) - } - return nil -} +// Inspect is one of the outlier commands in that it operates on images/containers/... var ( - diffCommand cliconfig.DiffValues - diffDescription = fmt.Sprint(`Displays changes on a container or image's filesystem. The container or image will be compared to its parent layer.`) - - _diffCommand = &cobra.Command{ - Use: "diff [flags] CONTAINER | IMAGE", - Short: "Inspect changes on container's file systems", - Long: diffDescription, - RunE: func(cmd *cobra.Command, args []string) error { - diffCommand.InputArgs = args - diffCommand.GlobalFlags = MainGlobalOpts - diffCommand.Remote = remoteclient - return diffCmd(&diffCommand) - }, + // Command: podman _diff_ Object_ID + diffDescription = `Displays changes on a container or image's filesystem. The container or image will be compared to its parent layer.` + diffCmd = &cobra.Command{ + Use: "diff [flags] {CONTAINER_ID | IMAGE_ID}", + Args: registry.IdOrLatestArgs, + Short: "Display the changes of object's file system", + Long: diffDescription, + TraverseChildren: true, + RunE: diff, Example: `podman diff imageID podman diff ctrID podman diff --format json redis:alpine`, } + + diffOpts = entities.DiffOptions{} ) func init() { - diffCommand.Command = _diffCommand - diffCommand.SetHelpTemplate(HelpTemplate()) - diffCommand.SetUsageTemplate(UsageTemplate()) - flags := diffCommand.Flags() - - flags.BoolVar(&diffCommand.Archive, "archive", true, "Save the diff as a tar archive") - flags.StringVar(&diffCommand.Format, "format", "", "Change the output format") - flags.BoolVarP(&diffCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - markFlagHidden(flags, "archive") - markFlagHiddenForRemoteClient("latest", flags) - -} - -func formatJSON(output []diffOutputParams) (diffJSONOutput, error) { - jsonStruct := diffJSONOutput{} - for _, output := range output { - switch output.Change { - case archive.ChangeModify: - jsonStruct.Changed = append(jsonStruct.Changed, output.Path) - case archive.ChangeAdd: - jsonStruct.Added = append(jsonStruct.Added, output.Path) - case archive.ChangeDelete: - jsonStruct.Deleted = append(jsonStruct.Deleted, output.Path) - default: - return jsonStruct, errors.Errorf("output kind %q not recognized", output.Change.String()) - } + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: diffCmd, + }) + flags := diffCmd.Flags() + flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive") + _ = flags.MarkHidden("archive") + flags.StringVar(&diffOpts.Format, "format", "", "Change the output format") + + if !registry.IsRemote() { + flags.BoolVarP(&diffOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") } - return jsonStruct, nil } -func diffCmd(c *cliconfig.DiffValues) error { - if len(c.InputArgs) != 1 && !c.Latest { - return errors.Errorf("container, image, or layer name must be specified: podman diff [options [...]] ID-NAME") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - var to string - if c.Latest { - ctr, err := runtime.GetLatestContainer() - if err != nil { - return errors.Wrapf(err, "unable to get latest container") - } - to = ctr.ID() - } else { - to = c.InputArgs[0] +func diff(cmd *cobra.Command, args []string) error { + if found, err := registry.ImageEngine().Exists(registry.GetContext(), args[0]); err != nil { + return err + } else if found.Value { + return images.Diff(cmd, args, diffOpts) } - changes, err := runtime.Diff(c, to) - if err != nil { - return errors.Wrapf(err, "could not get changes for %q", to) - } - diffOutput := []diffOutputParams{} - outputFormat := c.Format - - for _, change := range changes { - - params := diffOutputParams{ - Change: change.Kind, - Path: change.Path, - } - diffOutput = append(diffOutput, params) - } - - var out formats.Writer - if outputFormat != "" { - switch outputFormat { - case formats.JSONString: - data, err := formatJSON(diffOutput) - if err != nil { - return err - } - out = formats.JSONStruct{Output: data} - default: - return errors.New("only valid format for diff is 'json'") - } - } else { - out = stdoutStruct{output: diffOutput} + if found, err := registry.ContainerEngine().ContainerExists(registry.GetContext(), args[0]); err != nil { + return err + } else if found.Value { + return containers.Diff(cmd, args, diffOpts) } - return out.Out() + return fmt.Errorf("%s not found on system", args[0]) } diff --git a/cmd/podman/errors.go b/cmd/podman/errors.go deleted file mode 100644 index ae9e73e62..000000000 --- a/cmd/podman/errors.go +++ /dev/null @@ -1,39 +0,0 @@ -// +build !remoteclient - -package main - -import ( - "fmt" - "os" - "os/exec" - "syscall" - - "github.com/containers/libpod/libpod/define" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -func outputError(err error) { - if MainGlobalOpts.LogLevel == "debug" { - logrus.Errorf(err.Error()) - } else { - ee, ok := err.(*exec.ExitError) - if ok { - if status, ok := ee.Sys().(syscall.WaitStatus); ok { - exitCode = status.ExitStatus() - } - } - fmt.Fprintln(os.Stderr, "Error:", err.Error()) - } -} - -func setExitCode(err error) int { - cause := errors.Cause(err) - switch cause { - case define.ErrNoSuchCtr: - return 1 - case define.ErrCtrStateInvalid: - return 2 - } - return exitCode -} diff --git a/cmd/podman/errors_remote.go b/cmd/podman/errors_remote.go deleted file mode 100644 index 4b543ccd5..000000000 --- a/cmd/podman/errors_remote.go +++ /dev/null @@ -1,65 +0,0 @@ -// +build remoteclient - -package main - -import ( - "fmt" - "os" - "os/exec" - "syscall" - - "github.com/containers/libpod/libpod/define" - iopodman "github.com/containers/libpod/pkg/varlink" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -func outputError(err error) { - if MainGlobalOpts.LogLevel == "debug" { - logrus.Errorf(err.Error()) - } else { - if ee, ok := err.(*exec.ExitError); ok { - if status, ok := ee.Sys().(syscall.WaitStatus); ok { - exitCode = status.ExitStatus() - } - } - var ne error - switch e := err.(type) { - // For some reason golang won't let me list them with commas so listing them all. - case *iopodman.ImageNotFound: - ne = errors.New(e.Reason) - case *iopodman.ContainerNotFound: - ne = errors.New(e.Reason) - case *iopodman.PodNotFound: - ne = errors.New(e.Reason) - case *iopodman.VolumeNotFound: - ne = errors.New(e.Reason) - case *iopodman.InvalidState: - ne = errors.New(e.Reason) - case *iopodman.ErrorOccurred: - ne = errors.New(e.Reason) - default: - ne = err - } - fmt.Fprintln(os.Stderr, "Error:", ne.Error()) - } -} - -func setExitCode(err error) int { - cause := errors.Cause(err) - switch e := cause.(type) { - // For some reason golang won't let me list them with commas so listing them all. - case *iopodman.ContainerNotFound: - return 1 - case *iopodman.InvalidState: - return 2 - default: - switch e { - case define.ErrNoSuchCtr: - return 1 - case define.ErrCtrStateInvalid: - return 2 - } - } - return exitCode -} diff --git a/cmd/podman/events.go b/cmd/podman/events.go deleted file mode 100644 index 18126e626..000000000 --- a/cmd/podman/events.go +++ /dev/null @@ -1,50 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - eventsCommand cliconfig.EventValues - eventsDescription = "Monitor podman events" - _eventsCommand = &cobra.Command{ - Use: "events", - Args: noSubArgs, - Short: "Show podman events", - Long: eventsDescription, - RunE: func(cmd *cobra.Command, args []string) error { - eventsCommand.InputArgs = args - eventsCommand.GlobalFlags = MainGlobalOpts - eventsCommand.Remote = remoteclient - return eventsCmd(&eventsCommand) - }, - Example: `podman events - podman events --filter event=create - podman events --since 1h30s`, - } -) - -func init() { - eventsCommand.Command = _eventsCommand - eventsCommand.SetUsageTemplate(UsageTemplate()) - flags := eventsCommand.Flags() - flags.StringArrayVar(&eventsCommand.Filter, "filter", []string{}, "filter output") - flags.StringVar(&eventsCommand.Format, "format", "", "format the output using a Go template") - flags.BoolVar(&eventsCommand.Stream, "stream", true, "stream new events; for testing only") - flags.StringVar(&eventsCommand.Since, "since", "", "show all events created since timestamp") - flags.StringVar(&eventsCommand.Until, "until", "", "show all events until timestamp") - markFlagHidden(flags, "stream") -} - -func eventsCmd(c *cliconfig.EventValues) error { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - return runtime.Events(c) -} diff --git a/cmd/podman/exec.go b/cmd/podman/exec.go deleted file mode 100644 index b341ab496..000000000 --- a/cmd/podman/exec.go +++ /dev/null @@ -1,75 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - execCommand cliconfig.ExecValues - - execDescription = `Execute the specified command inside a running container. -` - _execCommand = &cobra.Command{ - Use: "exec [flags] CONTAINER [COMMAND [ARG...]]", - Short: "Run a process in a running container", - Long: execDescription, - RunE: func(cmd *cobra.Command, args []string) error { - execCommand.InputArgs = args - execCommand.GlobalFlags = MainGlobalOpts - execCommand.Remote = remoteclient - return execCmd(&execCommand) - }, - Example: `podman exec -it ctrID ls - podman exec -it -w /tmp myCtr pwd - podman exec --user root ctrID ls`, - } -) - -func init() { - execCommand.Command = _execCommand - execCommand.SetHelpTemplate(HelpTemplate()) - execCommand.SetUsageTemplate(UsageTemplate()) - flags := execCommand.Flags() - flags.SetInterspersed(false) - flags.StringVar(&execCommand.DetachKeys, "detach-keys", getDefaultDetachKeys(), "Select the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _") - flags.StringArrayVarP(&execCommand.Env, "env", "e", []string{}, "Set environment variables") - flags.StringSliceVar(&execCommand.EnvFile, "env-file", []string{}, "Read in a file of environment variables") - flags.BoolVarP(&execCommand.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached") - flags.BoolVarP(&execCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - flags.BoolVar(&execCommand.Privileged, "privileged", false, "Give the process extended Linux capabilities inside the container. The default is false") - flags.BoolVarP(&execCommand.Tty, "tty", "t", false, "Allocate a pseudo-TTY. The default is false") - flags.StringVarP(&execCommand.User, "user", "u", "", "Sets the username or UID used and optionally the groupname or GID for the specified command") - - flags.IntVar(&execCommand.PreserveFDs, "preserve-fds", 0, "Pass N additional file descriptors to the container") - flags.StringVarP(&execCommand.Workdir, "workdir", "w", "", "Working directory inside the container") - markFlagHiddenForRemoteClient("env-file", flags) - markFlagHiddenForRemoteClient("latest", flags) - markFlagHiddenForRemoteClient("preserve-fds", flags) -} - -func execCmd(c *cliconfig.ExecValues) error { - argLen := len(c.InputArgs) - if c.Latest { - if argLen < 1 { - return errors.Errorf("you must provide a command to exec") - } - } else { - if argLen < 1 { - return errors.Errorf("you must provide one container name or id") - } - if argLen < 2 { - return errors.Errorf("you must provide a command to exec") - } - } - runtime, err := adapter.GetRuntimeNoStore(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - exitCode, err = runtime.ExecContainer(getContext(), c) - return err -} diff --git a/cmd/podman/exists.go b/cmd/podman/exists.go deleted file mode 100644 index f8b1f8e59..000000000 --- a/cmd/podman/exists.go +++ /dev/null @@ -1,142 +0,0 @@ -package main - -import ( - "os" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - imageExistsCommand cliconfig.ImageExistsValues - containerExistsCommand cliconfig.ContainerExistsValues - podExistsCommand cliconfig.PodExistsValues - - imageExistsDescription = `If the named image exists in local storage, podman image exists exits with 0, otherwise the exit code will be 1.` - - containerExistsDescription = `If the named container exists in local storage, podman container exists exits with 0, otherwise the exit code will be 1.` - - podExistsDescription = `If the named pod exists in local storage, podman pod exists exits with 0, otherwise the exit code will be 1.` - - _imageExistsCommand = &cobra.Command{ - Use: "exists IMAGE", - Short: "Check if an image exists in local storage", - Long: imageExistsDescription, - RunE: func(cmd *cobra.Command, args []string) error { - imageExistsCommand.InputArgs = args - imageExistsCommand.GlobalFlags = MainGlobalOpts - imageExistsCommand.Remote = remoteclient - return imageExistsCmd(&imageExistsCommand) - }, - Example: `podman image exists imageID - podman image exists alpine || podman pull alpine`, - } - - _containerExistsCommand = &cobra.Command{ - Use: "exists CONTAINER", - Short: "Check if a container exists in local storage", - Long: containerExistsDescription, - RunE: func(cmd *cobra.Command, args []string) error { - containerExistsCommand.InputArgs = args - containerExistsCommand.GlobalFlags = MainGlobalOpts - containerExistsCommand.Remote = remoteclient - return containerExistsCmd(&containerExistsCommand) - - }, - Example: `podman container exists containerID - podman container exists myctr || podman run --name myctr [etc...]`, - } - - _podExistsCommand = &cobra.Command{ - Use: "exists POD", - Short: "Check if a pod exists in local storage", - Long: podExistsDescription, - RunE: func(cmd *cobra.Command, args []string) error { - podExistsCommand.InputArgs = args - podExistsCommand.GlobalFlags = MainGlobalOpts - podExistsCommand.Remote = remoteclient - return podExistsCmd(&podExistsCommand) - }, - Example: `podman pod exists podID - podman pod exists mypod || podman pod create --name mypod`, - } -) - -func init() { - imageExistsCommand.Command = _imageExistsCommand - imageExistsCommand.DisableFlagsInUseLine = true - imageExistsCommand.SetHelpTemplate(HelpTemplate()) - imageExistsCommand.SetUsageTemplate(UsageTemplate()) - containerExistsCommand.Command = _containerExistsCommand - containerExistsCommand.DisableFlagsInUseLine = true - containerExistsCommand.SetHelpTemplate(HelpTemplate()) - containerExistsCommand.SetUsageTemplate(UsageTemplate()) - podExistsCommand.Command = _podExistsCommand - podExistsCommand.DisableFlagsInUseLine = true - podExistsCommand.SetHelpTemplate(HelpTemplate()) - podExistsCommand.SetUsageTemplate(UsageTemplate()) -} - -func imageExistsCmd(c *cliconfig.ImageExistsValues) error { - args := c.InputArgs - if len(args) > 1 || len(args) < 1 { - return errors.New("you may only check for the existence of one image at a time") - } - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - if _, err := runtime.NewImageFromLocal(args[0]); err != nil { - //TODO we need to ask about having varlink defined errors exposed - //so we can reuse them - if errors.Cause(err) == image.ErrNoSuchImage || err.Error() == "io.podman.ImageNotFound" { - os.Exit(1) - } - return err - } - return nil -} - -func containerExistsCmd(c *cliconfig.ContainerExistsValues) error { - args := c.InputArgs - if len(args) > 1 || len(args) < 1 { - return errors.New("you may only check for the existence of one container at a time") - } - runtime, err := adapter.GetRuntimeNoStore(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - if _, err := runtime.LookupContainer(args[0]); err != nil { - if errors.Cause(err) == define.ErrNoSuchCtr || err.Error() == "io.podman.ContainerNotFound" { - os.Exit(1) - } - return err - } - return nil -} - -func podExistsCmd(c *cliconfig.PodExistsValues) error { - args := c.InputArgs - if len(args) > 1 || len(args) < 1 { - return errors.New("you may only check for the existence of one pod at a time") - } - runtime, err := adapter.GetRuntimeNoStore(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - if _, err := runtime.LookupPod(args[0]); err != nil { - if errors.Cause(err) == define.ErrNoSuchPod || err.Error() == "io.podman.PodNotFound" { - os.Exit(1) - } - return err - } - return nil -} diff --git a/cmd/podman/export.go b/cmd/podman/export.go deleted file mode 100644 index 27948004c..000000000 --- a/cmd/podman/export.go +++ /dev/null @@ -1,75 +0,0 @@ -package main - -import ( - "os" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/shared/parse" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "golang.org/x/crypto/ssh/terminal" -) - -var ( - exportCommand cliconfig.ExportValues - exportDescription = "Exports container's filesystem contents as a tar archive" + - " and saves it on the local machine." - - _exportCommand = &cobra.Command{ - Use: "export [flags] CONTAINER", - Short: "Export container's filesystem contents as a tar archive", - Long: exportDescription, - RunE: func(cmd *cobra.Command, args []string) error { - exportCommand.InputArgs = args - exportCommand.GlobalFlags = MainGlobalOpts - exportCommand.Remote = remoteclient - return exportCmd(&exportCommand) - }, - Example: `podman export ctrID > myCtr.tar - podman export --output="myCtr.tar" ctrID`, - } -) - -func init() { - exportCommand.Command = _exportCommand - exportCommand.SetHelpTemplate(HelpTemplate()) - exportCommand.SetUsageTemplate(UsageTemplate()) - flags := exportCommand.Flags() - flags.StringVarP(&exportCommand.Output, "output", "o", "", "Write to a specified file (default: stdout, which must be redirected)") -} - -// exportCmd saves a container to a tarball on disk -func exportCmd(c *cliconfig.ExportValues) error { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - args := c.InputArgs - if len(args) == 0 { - return errors.Errorf("container id must be specified") - } - if len(args) > 1 { - return errors.Errorf("too many arguments given, need 1 at most.") - } - - output := c.Output - if runtime.Remote && len(output) == 0 { - return errors.New("remote client usage must specify an output file (-o)") - } - - if len(output) == 0 { - file := os.Stdout - if terminal.IsTerminal(int(file.Fd())) { - return errors.Errorf("refusing to export to terminal. Use -o flag or redirect") - } - output = "/dev/stdout" - } - - if err := parse.ValidateFileName(output); err != nil { - return err - } - return runtime.Export(args[0], output) -} diff --git a/cmd/podman/generate.go b/cmd/podman/generate.go deleted file mode 100644 index 196556bc5..000000000 --- a/cmd/podman/generate.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/spf13/cobra" -) - -var ( - generateCommand cliconfig.PodmanCommand - generateDescription = "Generate structured data based for a containers and pods" - _generateCommand = &cobra.Command{ - Use: "generate", - Short: "Generated structured data", - Long: generateDescription, - RunE: commandRunE(), - } - - // Commands that are universally implemented - generateCommands = []*cobra.Command{ - _containerKubeCommand, - } -) - -func init() { - // Systemd-service generation is not supported for remote-clients. - if !remoteclient { - generateCommands = append(generateCommands, _containerSystemdCommand) - } - generateCommand.Command = _generateCommand - generateCommand.AddCommand(generateCommands...) - generateCommand.SetUsageTemplate(UsageTemplate()) -} diff --git a/cmd/podman/generate_kube.go b/cmd/podman/generate_kube.go deleted file mode 100644 index 6f04d6517..000000000 --- a/cmd/podman/generate_kube.go +++ /dev/null @@ -1,110 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - "os" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - podmanVersion "github.com/containers/libpod/version" - "github.com/ghodss/yaml" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - containerKubeCommand cliconfig.GenerateKubeValues - containerKubeDescription = `Command generates Kubernetes Pod YAML (v1 specification) from a podman container or pod. - - Whether the input is for a container or pod, Podman will always generate the specification as a Pod. The input may be in the form of a pod or container name or ID.` - _containerKubeCommand = &cobra.Command{ - Use: "kube [flags] CONTAINER | POD", - Short: "Generate Kubernetes pod YAML from a container or pod", - Long: containerKubeDescription, - RunE: func(cmd *cobra.Command, args []string) error { - containerKubeCommand.InputArgs = args - containerKubeCommand.GlobalFlags = MainGlobalOpts - containerKubeCommand.Remote = remoteclient - return generateKubeYAMLCmd(&containerKubeCommand) - }, - Example: `podman generate kube ctrID - podman generate kube podID - podman generate kube --service podID`, - } -) - -func init() { - containerKubeCommand.Command = _containerKubeCommand - containerKubeCommand.SetHelpTemplate(HelpTemplate()) - containerKubeCommand.SetUsageTemplate(UsageTemplate()) - flags := containerKubeCommand.Flags() - flags.BoolVarP(&containerKubeCommand.Service, "service", "s", false, "Generate YAML for kubernetes service object") - flags.StringVarP(&containerKubeCommand.Filename, "filename", "f", "", "Filename to output to") -} - -func generateKubeYAMLCmd(c *cliconfig.GenerateKubeValues) error { - var ( - //podYAML *v1.Pod - err error - output []byte - //pod *libpod.Pod - marshalledPod []byte - marshalledService []byte - ) - - args := c.InputArgs - if len(args) != 1 { - return errors.Errorf("you must provide exactly one container|pod ID or name") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - podYAML, serviceYAML, err := runtime.GenerateKube(c) - if err != nil { - return err - } - // Marshall the results - marshalledPod, err = yaml.Marshal(podYAML) - if err != nil { - return err - } - if c.Service { - marshalledService, err = yaml.Marshal(serviceYAML) - if err != nil { - return err - } - } - header := `# Generation of Kubernetes YAML is still under development! -# -# Save the output of this file and use kubectl create -f to import -# it into Kubernetes. -# -# Created with podman-%s -` - output = append(output, []byte(fmt.Sprintf(header, podmanVersion.Version))...) - output = append(output, marshalledPod...) - if c.Bool("service") { - output = append(output, []byte("---\n")...) - output = append(output, marshalledService...) - } - - if c.Filename != "" { - if _, err := os.Stat(c.Filename); err == nil { - return errors.Errorf("cannot write to %q - file exists", c.Filename) - } - - if err := ioutil.WriteFile(c.Filename, output, 0644); err != nil { - return err - } - } else { - // Output the v1.Pod with the v1.Container - fmt.Println(string(output)) - } - - return nil -} diff --git a/cmd/podman/generate_systemd.go b/cmd/podman/generate_systemd.go deleted file mode 100644 index fd0d13d78..000000000 --- a/cmd/podman/generate_systemd.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - containerSystemdCommand cliconfig.GenerateSystemdValues - containerSystemdDescription = `Command generates a systemd unit file for a Podman container - ` - _containerSystemdCommand = &cobra.Command{ - Use: "systemd [flags] CONTAINER | POD", - Short: "Generate a systemd unit file for a Podman container", - Long: containerSystemdDescription, - RunE: func(cmd *cobra.Command, args []string) error { - containerSystemdCommand.InputArgs = args - containerSystemdCommand.GlobalFlags = MainGlobalOpts - containerSystemdCommand.Remote = remoteclient - return generateSystemdCmd(&containerSystemdCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) > 1 || len(args) < 1 { - return errors.New("provide only one container name or ID") - } - return nil - }, - Example: `podman generate systemd ctrID -`, - } -) - -func init() { - containerSystemdCommand.Command = _containerSystemdCommand - containerSystemdCommand.SetHelpTemplate(HelpTemplate()) - containerSystemdCommand.SetUsageTemplate(UsageTemplate()) - flags := containerSystemdCommand.Flags() - flags.BoolVarP(&containerSystemdCommand.Name, "name", "n", false, "use the container/pod name instead of ID") - if !remoteclient { - flags.BoolVarP(&containerSystemdCommand.Files, "files", "f", false, "generate files instead of printing to stdout") - } - flags.UintVarP(&containerSystemdCommand.StopTimeout, "time", "t", defaultContainerConfig.Engine.StopTimeout, "stop timeout override") - flags.StringVar(&containerSystemdCommand.RestartPolicy, "restart-policy", "on-failure", "applicable systemd restart-policy") - flags.BoolVarP(&containerSystemdCommand.New, "new", "", false, "create a new container instead of starting an existing one") - flags.SetNormalizeFunc(aliasFlags) -} - -func generateSystemdCmd(c *cliconfig.GenerateSystemdValues) error { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - unit, err := runtime.GenerateSystemd(c) - if err != nil { - return err - } - fmt.Println(unit) - return nil -} diff --git a/cmd/podman/healthcheck.go b/cmd/podman/healthcheck.go deleted file mode 100644 index 140206dbe..000000000 --- a/cmd/podman/healthcheck.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/spf13/cobra" -) - -var healthcheckDescription = "Manage health checks on containers" -var healthcheckCommand = cliconfig.PodmanCommand{ - Command: &cobra.Command{ - Use: "healthcheck", - Short: "Manage Healthcheck", - Long: healthcheckDescription, - RunE: commandRunE(), - }, -} - -// Commands that are universally implemented -var healthcheckCommands = []*cobra.Command{ - _healthcheckrunCommand, -} - -func init() { - healthcheckCommand.AddCommand(healthcheckCommands...) - healthcheckCommand.SetUsageTemplate(UsageTemplate()) - rootCmd.AddCommand(healthcheckCommand.Command) -} diff --git a/cmd/podman/healthcheck/healthcheck.go b/cmd/podman/healthcheck/healthcheck.go new file mode 100644 index 000000000..794a94615 --- /dev/null +++ b/cmd/podman/healthcheck/healthcheck.go @@ -0,0 +1,25 @@ +package healthcheck + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + // Command: healthcheck + healthCmd = &cobra.Command{ + Use: "healthcheck", + Short: "Manage Healthcheck", + Long: "Manage Healthcheck", + TraverseChildren: true, + RunE: registry.SubCommandExists, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: healthCmd, + }) +} diff --git a/cmd/podman/healthcheck/run.go b/cmd/podman/healthcheck/run.go new file mode 100644 index 000000000..5612910cb --- /dev/null +++ b/cmd/podman/healthcheck/run.go @@ -0,0 +1,42 @@ +package healthcheck + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + healthcheckRunDescription = "run the health check of a container" + healthcheckrunCommand = &cobra.Command{ + Use: "run [flags] CONTAINER", + Short: "run the health check of a container", + Long: healthcheckRunDescription, + Example: `podman healthcheck run mywebapp`, + RunE: run, + Args: cobra.ExactArgs(1), + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: healthcheckrunCommand, + Parent: healthCmd, + }) +} + +func run(cmd *cobra.Command, args []string) error { + response, err := registry.ContainerEngine().HealthCheckRun(context.Background(), args[0], entities.HealthCheckOptions{}) + if err != nil { + return err + } + if response.Status == "unhealthy" { + registry.SetExitCode(1) + } + fmt.Println(response.Status) + return err +} diff --git a/cmd/podman/healthcheck_run.go b/cmd/podman/healthcheck_run.go deleted file mode 100644 index 3a2a8f333..000000000 --- a/cmd/podman/healthcheck_run.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - healthcheckRunCommand cliconfig.HealthCheckValues - healthcheckRunDescription = "run the health check of a container" - _healthcheckrunCommand = &cobra.Command{ - Use: "run [flags] CONTAINER", - Short: "run the health check of a container", - Long: healthcheckRunDescription, - Example: `podman healthcheck run mywebapp`, - RunE: func(cmd *cobra.Command, args []string) error { - healthcheckRunCommand.InputArgs = args - healthcheckRunCommand.GlobalFlags = MainGlobalOpts - healthcheckRunCommand.Remote = remoteclient - return healthCheckCmd(&healthcheckRunCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 || len(args) > 1 { - return errors.New("must provide the name or ID of one container") - } - return nil - }, - } -) - -func init() { - healthcheckRunCommand.Command = _healthcheckrunCommand - healthcheckRunCommand.SetUsageTemplate(UsageTemplate()) -} - -func healthCheckCmd(c *cliconfig.HealthCheckValues) error { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrap(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - status, err := runtime.HealthCheck(c) - if err == nil && status == "unhealthy" { - exitCode = 1 - } - fmt.Println(status) - return err -} diff --git a/cmd/podman/history.go b/cmd/podman/history.go deleted file mode 100644 index da6a3f608..000000000 --- a/cmd/podman/history.go +++ /dev/null @@ -1,199 +0,0 @@ -package main - -import ( - "reflect" - "strconv" - "strings" - "time" - - "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/adapter" - "github.com/docker/go-units" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -const createdByTruncLength = 45 - -// historyTemplateParams stores info about each layer -type historyTemplateParams struct { - ID string - Created string - CreatedBy string - Size string - Comment string -} - -// historyOptions stores cli flag values -type historyOptions struct { - human bool - noTrunc bool - quiet bool - format string -} - -var ( - historyCommand cliconfig.HistoryValues - - historyDescription = `Displays the history of an image. - - The information can be printed out in an easy to read, or user specified format, and can be truncated.` - _historyCommand = &cobra.Command{ - Use: "history [flags] IMAGE", - Short: "Show history of a specified image", - Long: historyDescription, - RunE: func(cmd *cobra.Command, args []string) error { - historyCommand.InputArgs = args - historyCommand.GlobalFlags = MainGlobalOpts - historyCommand.Remote = remoteclient - return historyCmd(&historyCommand) - }, - } -) - -func init() { - historyCommand.Command = _historyCommand - historyCommand.SetHelpTemplate(HelpTemplate()) - historyCommand.SetUsageTemplate(UsageTemplate()) - flags := historyCommand.Flags() - flags.StringVar(&historyCommand.Format, "format", "", "Change the output to JSON or a Go template") - flags.BoolVarP(&historyCommand.Human, "human", "H", true, "Display sizes and dates in human readable format") - // notrucate needs to be added - flags.BoolVar(&historyCommand.NoTrunc, "no-trunc", false, "Do not truncate the output") - flags.BoolVarP(&historyCommand.Quiet, "quiet", "q", false, "Display the numeric IDs only") - -} - -func historyCmd(c *cliconfig.HistoryValues) error { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - format := genHistoryFormat(c.Format, c.Quiet) - - args := c.InputArgs - if len(args) == 0 { - return errors.Errorf("an image name must be specified") - } - if len(args) > 1 { - return errors.Errorf("podman history takes at most 1 argument") - } - - image, err := runtime.NewImageFromLocal(args[0]) - if err != nil { - return err - } - opts := historyOptions{ - human: c.Human, - noTrunc: c.NoTrunc, - quiet: c.Quiet, - format: format, - } - - history, err := image.History(getContext()) - if err != nil { - return errors.Wrapf(err, "error getting history of image %q", image.InputName) - } - - return generateHistoryOutput(history, opts) -} - -func genHistoryFormat(format string, quiet bool) string { - if format != "" { - // "\t" from the command line is not being recognized as a tab - // replacing the string "\t" to a tab character if the user passes in "\t" - return strings.Replace(format, `\t`, "\t", -1) - } - if quiet { - return formats.IDString - } - return "table {{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\t" -} - -// historyToGeneric makes an empty array of interfaces for output -func historyToGeneric(templParams []historyTemplateParams, jsonParams []*image.History) (genericParams []interface{}) { - if len(templParams) > 0 { - for _, v := range templParams { - genericParams = append(genericParams, interface{}(v)) - } - return - } - for _, v := range jsonParams { - genericParams = append(genericParams, interface{}(v)) - } - return -} - -// generate the header based on the template provided -func (h *historyTemplateParams) headerMap() map[string]string { - v := reflect.Indirect(reflect.ValueOf(h)) - values := make(map[string]string) - for h := 0; h < v.NumField(); h++ { - key := v.Type().Field(h).Name - value := key - values[key] = strings.ToUpper(splitCamelCase(value)) - } - return values -} - -// getHistorytemplateOutput gets the modified history information to be printed in human readable format -func getHistoryTemplateOutput(history []*image.History, opts historyOptions) []historyTemplateParams { - var ( - outputSize string - createdTime string - createdBy string - historyOutput []historyTemplateParams - ) - for _, hist := range history { - imageID := hist.ID - if !opts.noTrunc && imageID != "<missing>" { - imageID = shortID(imageID) - } - - if opts.human { - createdTime = units.HumanDuration(time.Since(*hist.Created)) + " ago" - outputSize = units.HumanSize(float64(hist.Size)) - } else { - createdTime = (hist.Created).Format(time.RFC3339) - outputSize = strconv.FormatInt(hist.Size, 10) - } - - createdBy = strings.Join(strings.Fields(hist.CreatedBy), " ") - if !opts.noTrunc && len(createdBy) > createdByTruncLength { - createdBy = createdBy[:createdByTruncLength-3] + "..." - } - - params := historyTemplateParams{ - ID: imageID, - Created: createdTime, - CreatedBy: createdBy, - Size: outputSize, - Comment: hist.Comment, - } - historyOutput = append(historyOutput, params) - } - return historyOutput -} - -// generateHistoryOutput generates the history based on the format given -func generateHistoryOutput(history []*image.History, opts historyOptions) error { - if len(history) == 0 { - return nil - } - - var out formats.Writer - - switch opts.format { - case formats.JSONString: - out = formats.JSONStructArray{Output: historyToGeneric([]historyTemplateParams{}, history)} - default: - historyOutput := getHistoryTemplateOutput(history, opts) - out = formats.StdoutTemplateArray{Output: historyToGeneric(historyOutput, []*image.History{}), Template: opts.format, Fields: historyOutput[0].headerMap()} - } - - return out.Out() -} diff --git a/cmd/podman/image.go b/cmd/podman/image.go deleted file mode 100644 index ce576ff4b..000000000 --- a/cmd/podman/image.go +++ /dev/null @@ -1,94 +0,0 @@ -package main - -import ( - "strings" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/spf13/cobra" -) - -var ( - imageDescription = "Manage images" - imageCommand = cliconfig.PodmanCommand{ - Command: &cobra.Command{ - Use: "image", - Short: "Manage images", - Long: imageDescription, - RunE: commandRunE(), - }, - } - imagesSubCommand cliconfig.ImagesValues - _imagesSubCommand = &cobra.Command{ - Use: strings.Replace(_imagesCommand.Use, "images", "list", 1), - Short: _imagesCommand.Short, - Long: _imagesCommand.Long, - Aliases: []string{"ls"}, - RunE: func(cmd *cobra.Command, args []string) error { - imagesSubCommand.InputArgs = args - imagesSubCommand.GlobalFlags = MainGlobalOpts - return imagesCmd(&imagesSubCommand) - }, - Example: strings.Replace(_imagesCommand.Example, "podman images", "podman image list", -1), - } - - inspectSubCommand cliconfig.InspectValues - _inspectSubCommand = &cobra.Command{ - Use: strings.Replace(_inspectCommand.Use, "CONTAINER | ", "", 1), - Short: "Display the configuration of an image", - Long: `Displays the low-level information on an image identified by name or ID.`, - RunE: func(cmd *cobra.Command, args []string) error { - inspectSubCommand.InputArgs = args - inspectSubCommand.GlobalFlags = MainGlobalOpts - return inspectCmd(&inspectSubCommand) - }, - Example: `podman image inspect alpine`, - } - - rmSubCommand cliconfig.RmiValues - _rmSubCommand = &cobra.Command{ - Use: strings.Replace(_rmiCommand.Use, "rmi", "rm", 1), - Short: _rmiCommand.Short, - Long: _rmiCommand.Long, - RunE: func(cmd *cobra.Command, args []string) error { - rmSubCommand.InputArgs = args - rmSubCommand.GlobalFlags = MainGlobalOpts - return rmiCmd(&rmSubCommand) - }, - Example: strings.Replace(_rmiCommand.Example, "podman rmi", "podman image rm", -1), - } -) - -//imageSubCommands are implemented both in local and remote clients -var imageSubCommands = []*cobra.Command{ - _buildCommand, - _historyCommand, - _imagesSubCommand, - _imageExistsCommand, - _importCommand, - _inspectSubCommand, - _loadCommand, - _pruneImagesCommand, - _pullCommand, - _pushCommand, - _rmSubCommand, - _saveCommand, - _tagCommand, - _treeCommand, - _untagCommand, -} - -func init() { - rmSubCommand.Command = _rmSubCommand - rmiInit(&rmSubCommand) - - imagesSubCommand.Command = _imagesSubCommand - imagesInit(&imagesSubCommand) - - inspectSubCommand.Command = _inspectSubCommand - inspectInit(&inspectSubCommand) - - imageCommand.SetUsageTemplate(UsageTemplate()) - imageCommand.AddCommand(imageSubCommands...) - imageCommand.AddCommand(getImageSubCommands()...) - -} diff --git a/cmd/podman/images.go b/cmd/podman/images.go deleted file mode 100644 index ed33402ab..000000000 --- a/cmd/podman/images.go +++ /dev/null @@ -1,405 +0,0 @@ -package main - -import ( - "context" - "fmt" - "reflect" - "sort" - "strings" - "time" - "unicode" - - "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/adapter" - units "github.com/docker/go-units" - digest "github.com/opencontainers/go-digest" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -type imagesTemplateParams struct { - Repository string - Tag string - ID string - Digest digest.Digest - Digests []digest.Digest - CreatedAt time.Time - CreatedSince string - Size string - ReadOnly bool - History string -} - -type imagesJSONParams struct { - ID string `json:"ID"` - Name []string `json:"Names"` - Created string `json:"Created"` - Digest digest.Digest `json:"Digest"` - Digests []digest.Digest `json:"Digests"` - CreatedAt time.Time `json:"CreatedAt"` - Size *uint64 `json:"Size"` - ReadOnly bool `json:"ReadOnly"` - History []string `json:"History"` -} - -type imagesOptions struct { - quiet bool - noHeading bool - noTrunc bool - digests bool - format string - outputformat string - sort string - all bool - history bool -} - -// Type declaration and functions for sorting the images output -type imagesSorted []imagesTemplateParams - -func (a imagesSorted) Len() int { return len(a) } -func (a imagesSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -type imagesSortedCreated struct{ imagesSorted } - -func (a imagesSortedCreated) Less(i, j int) bool { - return a.imagesSorted[i].CreatedAt.After(a.imagesSorted[j].CreatedAt) -} - -type imagesSortedID struct{ imagesSorted } - -func (a imagesSortedID) Less(i, j int) bool { return a.imagesSorted[i].ID < a.imagesSorted[j].ID } - -type imagesSortedTag struct{ imagesSorted } - -func (a imagesSortedTag) Less(i, j int) bool { return a.imagesSorted[i].Tag < a.imagesSorted[j].Tag } - -type imagesSortedRepository struct{ imagesSorted } - -func (a imagesSortedRepository) Less(i, j int) bool { - return a.imagesSorted[i].Repository < a.imagesSorted[j].Repository -} - -type imagesSortedSize struct{ imagesSorted } - -func (a imagesSortedSize) Less(i, j int) bool { - size1, _ := units.FromHumanSize(a.imagesSorted[i].Size) - size2, _ := units.FromHumanSize(a.imagesSorted[j].Size) - return size1 < size2 -} - -var ( - imagesCommand cliconfig.ImagesValues - imagesDescription = "Lists images previously pulled to the system or created on the system." - - _imagesCommand = cobra.Command{ - Use: "images [flags] [IMAGE]", - Short: "List images in local storage", - Long: imagesDescription, - RunE: func(cmd *cobra.Command, args []string) error { - imagesCommand.InputArgs = args - imagesCommand.GlobalFlags = MainGlobalOpts - imagesCommand.Remote = remoteclient - return imagesCmd(&imagesCommand) - }, - Example: `podman images --format json - podman images --sort repository --format "table {{.ID}} {{.Repository}} {{.Tag}}" - podman images --filter dangling=true`, - } -) - -func imagesInit(command *cliconfig.ImagesValues) { - command.SetHelpTemplate(HelpTemplate()) - command.SetUsageTemplate(UsageTemplate()) - - flags := command.Flags() - flags.BoolVarP(&command.All, "all", "a", false, "Show all images (default hides intermediate images)") - flags.BoolVar(&command.Digests, "digests", false, "Show digests") - flags.StringSliceVarP(&command.Filter, "filter", "f", []string{}, "Filter output based on conditions provided (default [])") - flags.StringVar(&command.Format, "format", "", "Change the output format to JSON or a Go template") - flags.BoolVarP(&command.Noheading, "noheading", "n", false, "Do not print column headings") - // TODO Need to learn how to deal with second name being a string instead of a char. - // This needs to be "no-trunc, notruncate" - flags.BoolVar(&command.NoTrunc, "no-trunc", false, "Do not truncate output") - flags.BoolVarP(&command.Quiet, "quiet", "q", false, "Display only image IDs") - flags.StringVar(&command.Sort, "sort", "created", "Sort by created, id, repository, size, or tag") - flags.BoolVarP(&command.History, "history", "", false, "Display the image name history") - -} - -func init() { - imagesCommand.Command = &_imagesCommand - imagesInit(&imagesCommand) -} - -func imagesCmd(c *cliconfig.ImagesValues) error { - var ( - image string - ) - - ctx := getContext() - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "Could not get runtime") - } - defer runtime.DeferredShutdown(false) - if len(c.InputArgs) == 1 { - image = c.InputArgs[0] - } - if len(c.InputArgs) > 1 { - return errors.New("'podman images' requires at most 1 argument") - } - if len(c.Filter) > 0 && image != "" { - return errors.New("can not specify an image and a filter") - } - filters := c.Filter - if len(filters) < 1 && len(image) > 0 { - filters = append(filters, fmt.Sprintf("reference=%s", image)) - } - - var sortValues = map[string]bool{ - "created": true, - "id": true, - "repository": true, - "size": true, - "tag": true, - } - if !sortValues[c.Sort] { - keys := make([]string, 0, len(sortValues)) - for k := range sortValues { - keys = append(keys, k) - } - return errors.Errorf("invalid sort value %q, required values: %s", c.Sort, strings.Join(keys, ", ")) - } - - opts := imagesOptions{ - quiet: c.Quiet, - noHeading: c.Noheading, - noTrunc: c.NoTrunc, - digests: c.Digests, - format: c.Format, - sort: c.Sort, - all: c.All, - history: c.History, - } - - outputformat := opts.setOutputFormat() - // These fields were renamed, so we need to provide backward compat for - // the old names. - if strings.Contains(outputformat, "{{.Created}}") { - outputformat = strings.Replace(outputformat, "{{.Created}}", "{{.CreatedSince}}", -1) - } - if strings.Contains(outputformat, "{{.CreatedTime}}") { - outputformat = strings.Replace(outputformat, "{{.CreatedTime}}", "{{.CreatedAt}}", -1) - } - opts.outputformat = outputformat - - filteredImages, err := runtime.GetFilteredImages(filters, false) - if err != nil { - return errors.Wrapf(err, "unable to get images") - } - - for _, image := range filteredImages { - if image.IsReadOnly() { - opts.outputformat += "{{.ReadOnly}}\t" - break - } - } - return generateImagesOutput(ctx, filteredImages, opts) -} - -func (i imagesOptions) setOutputFormat() string { - if i.format != "" { - // "\t" from the command line is not being recognized as a tab - // replacing the string "\t" to a tab character if the user passes in "\t" - return strings.Replace(i.format, `\t`, "\t", -1) - } - if i.quiet { - return formats.IDString - } - format := "table {{.Repository}}\t{{if .Tag}}{{.Tag}}{{else}}<none>{{end}}\t" - if i.noHeading { - format = "{{.Repository}}\t{{if .Tag}}{{.Tag}}{{else}}<none>{{end}}\t" - } - if i.digests { - format += "{{.Digest}}\t" - } - format += "{{.ID}}\t{{.CreatedSince}}\t{{.Size}}\t" - if i.history { - format += "{{if .History}}{{.History}}{{else}}<none>{{end}}\t" - } - return format -} - -// imagesToGeneric creates an empty array of interfaces for output -func imagesToGeneric(templParams []imagesTemplateParams, jsonParams []imagesJSONParams) []interface{} { - genericParams := []interface{}{} - if len(templParams) > 0 { - for _, v := range templParams { - genericParams = append(genericParams, interface{}(v)) - } - return genericParams - } - for _, v := range jsonParams { - genericParams = append(genericParams, interface{}(v)) - } - return genericParams -} - -func sortImagesOutput(sortBy string, imagesOutput imagesSorted) imagesSorted { - switch sortBy { - case "id": - sort.Sort(imagesSortedID{imagesOutput}) - case "size": - sort.Sort(imagesSortedSize{imagesOutput}) - case "tag": - sort.Sort(imagesSortedTag{imagesOutput}) - case "repository": - sort.Sort(imagesSortedRepository{imagesOutput}) - default: - // default is created time - sort.Sort(imagesSortedCreated{imagesOutput}) - } - return imagesOutput -} - -// getImagesTemplateOutput returns the images information to be printed in human readable format -func getImagesTemplateOutput(ctx context.Context, images []*adapter.ContainerImage, opts imagesOptions) imagesSorted { - var imagesOutput imagesSorted - for _, img := range images { - // If all is false and the image doesn't have a name, check to see if the top layer of the image is a parent - // to another image's top layer. If it is, then it is an intermediate image so don't print out if the --all flag - // is not set. - isParent, err := img.IsParent(ctx) - if err != nil { - logrus.Errorf("error checking if image is a parent %q: %v", img.ID(), err) - } - if !opts.all && len(img.Names()) == 0 && isParent { - continue - } - createdTime := img.Created() - - imageID := "sha256:" + img.ID() - if !opts.noTrunc { - imageID = shortID(img.ID()) - } - - // get all specified repo:tag and repo@digest pairs and print them separately - repopairs, err := image.ReposToMap(img.Names()) - if err != nil { - logrus.Errorf("error finding tag/digest for %s", img.ID()) - } - outer: - for repo, tags := range repopairs { - for _, tag := range tags { - size, err := img.Size(ctx) - var sizeStr string - if err != nil { - sizeStr = err.Error() - } else { - sizeStr = units.HumanSizeWithPrecision(float64(*size), 3) - lastNumIdx := strings.LastIndexFunc(sizeStr, unicode.IsNumber) - sizeStr = sizeStr[:lastNumIdx+1] + " " + sizeStr[lastNumIdx+1:] - } - var imageDigest digest.Digest - if len(tag) == 71 && strings.HasPrefix(tag, "sha256:") { - imageDigest = digest.Digest(tag) - tag = "" - } else if img.Digest() != "" { - imageDigest = img.Digest() - } - params := imagesTemplateParams{ - Repository: repo, - Tag: tag, - ID: imageID, - Digest: imageDigest, - Digests: img.Digests(), - CreatedAt: createdTime, - CreatedSince: units.HumanDuration(time.Since(createdTime)) + " ago", - Size: sizeStr, - ReadOnly: img.IsReadOnly(), - History: strings.Join(img.NamesHistory(), ", "), - } - imagesOutput = append(imagesOutput, params) - if opts.quiet { // Show only one image ID when quiet - break outer - } - } - } - } - - // Sort images by created time - sortImagesOutput(opts.sort, imagesOutput) - return imagesOutput -} - -// getImagesJSONOutput returns the images information in its raw form -func getImagesJSONOutput(ctx context.Context, images []*adapter.ContainerImage) []imagesJSONParams { - imagesOutput := []imagesJSONParams{} - for _, img := range images { - size, err := img.Size(ctx) - if err != nil { - size = nil - } - params := imagesJSONParams{ - ID: img.ID(), - Name: img.Names(), - Digest: img.Digest(), - Digests: img.Digests(), - Created: units.HumanDuration(time.Since(img.Created())) + " ago", - CreatedAt: img.Created(), - Size: size, - ReadOnly: img.IsReadOnly(), - History: img.NamesHistory(), - } - imagesOutput = append(imagesOutput, params) - } - return imagesOutput -} - -// generateImagesOutput generates the images based on the format provided - -func generateImagesOutput(ctx context.Context, images []*adapter.ContainerImage, opts imagesOptions) error { - templateMap := GenImageOutputMap() - var out formats.Writer - - switch opts.format { - case formats.JSONString: - imagesOutput := getImagesJSONOutput(ctx, images) - out = formats.JSONStructArray{Output: imagesToGeneric([]imagesTemplateParams{}, imagesOutput)} - default: - imagesOutput := getImagesTemplateOutput(ctx, images, opts) - out = formats.StdoutTemplateArray{Output: imagesToGeneric(imagesOutput, []imagesJSONParams{}), Template: opts.outputformat, Fields: templateMap} - } - return out.Out() -} - -// GenImageOutputMap generates the map used for outputting the images header -// without requiring a populated image. This replaces the previous HeaderMap -// call. -func GenImageOutputMap() map[string]string { - io := imagesTemplateParams{} - v := reflect.Indirect(reflect.ValueOf(io)) - values := make(map[string]string) - - for i := 0; i < v.NumField(); i++ { - key := v.Type().Field(i).Name - value := key - if value == "ID" { - value = "Image" + value - } - - if value == "ReadOnly" { - values[key] = "R/O" - continue - } - if value == "CreatedSince" { - value = "created" - } - values[key] = strings.ToUpper(splitCamelCase(value)) - } - return values -} diff --git a/cmd/podman/images/diff.go b/cmd/podman/images/diff.go new file mode 100644 index 000000000..dd98dc4d6 --- /dev/null +++ b/cmd/podman/images/diff.go @@ -0,0 +1,62 @@ +package images + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/report" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + // podman container _inspect_ + diffCmd = &cobra.Command{ + Use: "diff [flags] CONTAINER", + Args: registry.IdOrLatestArgs, + Short: "Inspect changes on image's file systems", + Long: `Displays changes on a image's filesystem. The image will be compared to its parent layer.`, + RunE: diff, + Example: `podman image diff myImage + podman image diff --format json redis:alpine`, + } + diffOpts *entities.DiffOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: diffCmd, + Parent: imageCmd, + }) + + diffOpts = &entities.DiffOptions{} + flags := diffCmd.Flags() + flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive") + _ = flags.MarkHidden("archive") + flags.StringVar(&diffOpts.Format, "format", "", "Change the output format") +} + +func diff(cmd *cobra.Command, args []string) error { + if len(args) == 0 && !diffOpts.Latest { + return errors.New("image must be specified: podman image diff [options [...]] ID-NAME") + } + + results, err := registry.ImageEngine().Diff(registry.GetContext(), args[0], entities.DiffOptions{}) + if err != nil { + return err + } + + switch diffOpts.Format { + case "": + return report.ChangesToTable(results) + case "json": + return report.ChangesToJSON(results) + default: + return errors.New("only supported value for '--format' is 'json'") + } +} + +func Diff(cmd *cobra.Command, args []string, options entities.DiffOptions) error { + diffOpts = &options + return diff(cmd, args) +} diff --git a/cmd/podman/images/exists.go b/cmd/podman/images/exists.go new file mode 100644 index 000000000..0bb288b96 --- /dev/null +++ b/cmd/podman/images/exists.go @@ -0,0 +1,40 @@ +package images + +import ( + "os" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + existsCmd = &cobra.Command{ + Use: "exists IMAGE", + Short: "Check if an image exists in local storage", + Long: `If the named image exists in local storage, podman image exists exits with 0, otherwise the exit code will be 1.`, + Args: cobra.ExactArgs(1), + RunE: exists, + Example: `podman image exists ID + podman image exists IMAGE && podman pull IMAGE`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: existsCmd, + Parent: imageCmd, + }) +} + +func exists(cmd *cobra.Command, args []string) error { + found, err := registry.ImageEngine().Exists(registry.GetContext(), args[0]) + if err != nil { + return err + } + if !found.Value { + os.Exit(1) + } + return nil +} diff --git a/cmd/podman/images/history.go b/cmd/podman/images/history.go new file mode 100644 index 000000000..c92072bff --- /dev/null +++ b/cmd/podman/images/history.go @@ -0,0 +1,152 @@ +package images + +import ( + "context" + "fmt" + "os" + "strings" + "text/tabwriter" + "text/template" + "time" + "unicode" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/docker/go-units" + jsoniter "github.com/json-iterator/go" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + long = `Displays the history of an image. + + The information can be printed out in an easy to read, or user specified format, and can be truncated.` + + // podman _history_ + historyCmd = &cobra.Command{ + Use: "history [flags] IMAGE", + Short: "Show history of a specified image", + Long: long, + Example: "podman history quay.io/fedora/fedora", + Args: cobra.ExactArgs(1), + RunE: history, + } + + opts = struct { + human bool + noTrunc bool + quiet bool + format string + }{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: historyCmd, + }) + + flags := historyCmd.Flags() + 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") + flags.BoolVar(&opts.noTrunc, "notruncate", false, "Do not truncate the output") + flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Display the numeric IDs only") +} + +func history(cmd *cobra.Command, args []string) error { + results, err := registry.ImageEngine().History(context.Background(), args[0], entities.ImageHistoryOptions{}) + if err != nil { + return err + } + + if opts.format == "json" { + var err error + if len(results.Layers) == 0 { + _, err = fmt.Fprintf(os.Stdout, "[]\n") + } else { + // ah-hoc change to "Created": type and format + type layer struct { + entities.ImageHistoryLayer + Created string `json:"Created"` + } + + layers := make([]layer, len(results.Layers)) + for i, l := range results.Layers { + layers[i].ImageHistoryLayer = l + layers[i].Created = l.Created.Format(time.RFC3339) + } + json := jsoniter.ConfigCompatibleWithStandardLibrary + enc := json.NewEncoder(os.Stdout) + err = enc.Encode(layers) + } + return err + } + var hr []historyreporter + for _, l := range results.Layers { + hr = append(hr, historyreporter{l}) + } + // Defaults + hdr := "ID\tCREATED\tCREATED BY\tSIZE\tCOMMENT\n" + row := "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" + + if len(opts.format) > 0 { + hdr = "" + row = opts.format + if !strings.HasSuffix(opts.format, "\n") { + row += "\n" + } + } else { + switch { + case opts.human: + row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" + case opts.noTrunc: + row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" + case opts.quiet: + hdr = "" + row = "{{.ID}}\n" + } + } + format := hdr + "{{range . }}" + row + "{{end}}" + + tmpl := template.Must(template.New("report").Parse(format)) + w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + err = tmpl.Execute(w, hr) + if err != nil { + fmt.Fprintln(os.Stderr, errors.Wrapf(err, "Failed to print report")) + } + w.Flush() + return nil +} + +type historyreporter struct { + entities.ImageHistoryLayer +} + +func (h historyreporter) Created() string { + if opts.human { + return units.HumanDuration(time.Since(h.ImageHistoryLayer.Created)) + " ago" + } + return h.ImageHistoryLayer.Created.Format(time.RFC3339) +} + +func (h historyreporter) Size() string { + s := units.HumanSizeWithPrecision(float64(h.ImageHistoryLayer.Size), 3) + i := strings.LastIndexFunc(s, unicode.IsNumber) + return s[:i+1] + " " + s[i+1:] +} + +func (h historyreporter) CreatedBy() string { + if len(h.ImageHistoryLayer.CreatedBy) > 45 { + return h.ImageHistoryLayer.CreatedBy[:45-3] + "..." + } + return h.ImageHistoryLayer.CreatedBy +} + +func (h historyreporter) ID() string { + if !opts.noTrunc && len(h.ImageHistoryLayer.ID) >= 12 { + return h.ImageHistoryLayer.ID[0:12] + } + return h.ImageHistoryLayer.ID +} diff --git a/cmd/podman/images/image.go b/cmd/podman/images/image.go new file mode 100644 index 000000000..37e46ab9e --- /dev/null +++ b/cmd/podman/images/image.go @@ -0,0 +1,25 @@ +package images + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + // Command: podman _image_ + imageCmd = &cobra.Command{ + Use: "image", + Short: "Manage images", + Long: "Manage images", + TraverseChildren: true, + RunE: registry.SubCommandExists, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageCmd, + }) +} diff --git a/cmd/podman/images/images.go b/cmd/podman/images/images.go new file mode 100644 index 000000000..fd3ede26a --- /dev/null +++ b/cmd/podman/images/images.go @@ -0,0 +1,30 @@ +package images + +import ( + "strings" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + // podman _images_ Alias for podman image _list_ + imagesCmd = &cobra.Command{ + Use: strings.Replace(listCmd.Use, "list", "images", 1), + Args: listCmd.Args, + Short: listCmd.Short, + Long: listCmd.Long, + RunE: listCmd.RunE, + Example: strings.Replace(listCmd.Example, "podman image list", "podman images", -1), + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imagesCmd, + }) + + imageListFlagSet(imagesCmd.Flags()) +} diff --git a/cmd/podman/images/import.go b/cmd/podman/images/import.go new file mode 100644 index 000000000..1c0568762 --- /dev/null +++ b/cmd/podman/images/import.go @@ -0,0 +1,84 @@ +package images + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/hashicorp/go-multierror" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + importDescription = `Create a container image from the contents of the specified tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz). + + Note remote tar balls can be specified, via web address. + Optionally tag the image. You can specify the instructions using the --change option.` + importCommand = &cobra.Command{ + Use: "import [flags] PATH [REFERENCE]", + Short: "Import a tarball to create a filesystem image", + Long: importDescription, + RunE: importCon, + Example: `podman import http://example.com/ctr.tar url-image + cat ctr.tar | podman -q import --message "importing the ctr.tar tarball" - image-imported + cat ctr.tar | podman import -`, + } +) + +var ( + importOpts entities.ImageImportOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: importCommand, + }) + + flags := importCommand.Flags() + 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") +} + +func importCon(cmd *cobra.Command, args []string) error { + var ( + source string + reference string + ) + switch len(args) { + case 0: + return errors.Errorf("need to give the path to the tarball, or must specify a tarball of '-' for stdin") + case 1: + source = args[0] + case 2: + source = args[0] + // TODO when save is merged, we need to process reference + // like it is done in there or we end up with docker.io prepends + // instead of the localhost ones + reference = args[1] + default: + return errors.Errorf("too many arguments. Usage TARBALL [REFERENCE]") + } + errFileName := parse.ValidateFileName(source) + errURL := parse.ValidURL(source) + if errURL == nil { + importOpts.SourceIsURL = true + } + if errFileName != nil && errURL != nil { + return multierror.Append(errFileName, errURL) + } + + importOpts.Source = source + importOpts.Reference = reference + + response, err := registry.ImageEngine().Import(context.Background(), importOpts) + if err != nil { + return err + } + fmt.Println(response.Id) + return nil +} diff --git a/cmd/podman/images/inspect.go b/cmd/podman/images/inspect.go new file mode 100644 index 000000000..4482ceee5 --- /dev/null +++ b/cmd/podman/images/inspect.go @@ -0,0 +1,108 @@ +package images + +import ( + "context" + "encoding/json" + "fmt" + "os" + "strings" + "text/tabwriter" + "text/template" + + "github.com/containers/buildah/pkg/formats" + "github.com/containers/libpod/cmd/podman/common" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + // Command: podman image _inspect_ + inspectCmd = &cobra.Command{ + Use: "inspect [flags] IMAGE", + Short: "Display the configuration of an image", + Long: `Displays the low-level information on an image identified by name or ID.`, + RunE: inspect, + Example: `podman image inspect alpine`, + } + inspectOpts *entities.InspectOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: inspectCmd, + Parent: imageCmd, + }) + inspectOpts = common.AddInspectFlagSet(inspectCmd) +} + +func inspect(cmd *cobra.Command, args []string) error { + latestContainer := inspectOpts.Latest + + if len(args) == 0 && !latestContainer { + return errors.Errorf("container or image name must be specified: podman inspect [options [...]] name") + } + + if len(args) > 0 && latestContainer { + return errors.Errorf("you cannot provide additional arguments with --latest") + } + + results, err := registry.ImageEngine().Inspect(context.Background(), args, *inspectOpts) + if err != nil { + return err + } + + if len(results.Images) > 0 { + if inspectOpts.Format == "" { + buf, err := json.MarshalIndent(results.Images, "", " ") + if err != nil { + return err + } + fmt.Println(string(buf)) + + for id, e := range results.Errors { + fmt.Fprintf(os.Stderr, "%s: %s\n", id, e.Error()) + } + return nil + } + row := inspectFormat(inspectOpts.Format) + format := "{{range . }}" + row + "{{end}}" + tmpl, err := template.New("inspect").Parse(format) + if err != nil { + return err + } + + w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + defer func() { _ = w.Flush() }() + err = tmpl.Execute(w, results.Images) + if err != nil { + return err + } + } + + for id, e := range results.Errors { + fmt.Fprintf(os.Stderr, "%s: %s\n", id, e.Error()) + } + return nil +} + +func inspectFormat(row string) string { + r := strings.NewReplacer("{{.Id}}", formats.IDString, + ".Src", ".Source", + ".Dst", ".Destination", + ".ImageID", ".Image", + ) + row = r.Replace(row) + + if !strings.HasSuffix(row, "\n") { + row += "\n" + } + return row +} + +func Inspect(cmd *cobra.Command, args []string, options *entities.InspectOptions) error { + inspectOpts = options + return inspect(cmd, args) +} diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go new file mode 100644 index 000000000..366dfc4ba --- /dev/null +++ b/cmd/podman/images/list.go @@ -0,0 +1,273 @@ +package images + +import ( + "errors" + "fmt" + "os" + "sort" + "strings" + "text/tabwriter" + "text/template" + "time" + "unicode" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/docker/go-units" + jsoniter "github.com/json-iterator/go" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +type listFlagType struct { + format string + history bool + noHeading bool + noTrunc bool + quiet bool + sort string + readOnly bool + digests bool +} + +var ( + // Command: podman image _list_ + listCmd = &cobra.Command{ + Use: "list [flag] [IMAGE]", + Aliases: []string{"ls"}, + Args: cobra.MaximumNArgs(1), + Short: "List images in local storage", + Long: "Lists images previously pulled to the system or created on the system.", + RunE: images, + Example: `podman image list --format json + podman image list --sort repository --format "table {{.ID}} {{.Repository}} {{.Tag}}" + podman image list --filter dangling=true`, + } + + // Options to pull data + listOptions = entities.ImageListOptions{} + + // Options for presenting data + listFlag = listFlagType{} + + sortFields = entities.NewStringSet( + "created", + "id", + "repository", + "size", + "tag") +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: listCmd, + Parent: imageCmd, + }) + imageListFlagSet(listCmd.Flags()) +} + +func imageListFlagSet(flags *pflag.FlagSet) { + flags.BoolVarP(&listOptions.All, "all", "a", false, "Show all images (default hides intermediate images)") + flags.StringSliceVarP(&listOptions.Filter, "filter", "f", []string{}, "Filter output based on conditions provided (default [])") + flags.StringVar(&listFlag.format, "format", "", "Change the output format to JSON or a Go template") + flags.BoolVar(&listFlag.digests, "digests", false, "Show digests") + flags.BoolVarP(&listFlag.noHeading, "noheading", "n", false, "Do not print column headings") + flags.BoolVar(&listFlag.noTrunc, "no-trunc", false, "Do not truncate output") + flags.BoolVar(&listFlag.noTrunc, "notruncate", false, "Do not truncate output") + flags.BoolVarP(&listFlag.quiet, "quiet", "q", false, "Display only image IDs") + flags.StringVar(&listFlag.sort, "sort", "created", "Sort by "+sortFields.String()) + flags.BoolVarP(&listFlag.history, "history", "", false, "Display the image name history") +} + +func images(cmd *cobra.Command, args []string) error { + if len(listOptions.Filter) > 0 && len(args) > 0 { + return errors.New("cannot specify an image and a filter(s)") + } + + if len(listOptions.Filter) < 1 && len(args) > 0 { + listOptions.Filter = append(listOptions.Filter, "reference="+args[0]) + } + + if cmd.Flag("sort").Changed && !sortFields.Contains(listFlag.sort) { + return fmt.Errorf("\"%s\" is not a valid field for sorting. Choose from: %s", + listFlag.sort, sortFields.String()) + } + + summaries, err := registry.ImageEngine().List(registry.GetContext(), listOptions) + if err != nil { + return err + } + + imageS := summaries + sort.Slice(imageS, sortFunc(listFlag.sort, imageS)) + + if cmd.Flag("format").Changed && listFlag.format == "json" { + return writeJSON(imageS) + } else { + return writeTemplate(imageS, err) + } +} + +func writeJSON(imageS []*entities.ImageSummary) error { + type image struct { + entities.ImageSummary + Created string + } + + imgs := make([]image, 0, len(imageS)) + for _, e := range imageS { + var h image + h.ImageSummary = *e + h.Created = time.Unix(e.Created, 0).Format(time.RFC3339) + h.RepoTags = nil + + imgs = append(imgs, h) + } + + json := jsoniter.ConfigCompatibleWithStandardLibrary + enc := json.NewEncoder(os.Stdout) + return enc.Encode(imgs) +} + +func writeTemplate(imageS []*entities.ImageSummary, err error) error { + var ( + hdr, row string + ) + imgs := make([]imageReporter, 0, len(imageS)) + for _, e := range imageS { + for _, tag := range e.RepoTags { + var h imageReporter + h.ImageSummary = *e + h.Repository, h.Tag = tokenRepoTag(tag) + imgs = append(imgs, h) + } + if e.IsReadOnly() { + listFlag.readOnly = true + } + } + if len(listFlag.format) < 1 { + hdr, row = imageListFormat(listFlag) + } else { + row = listFlag.format + if !strings.HasSuffix(row, "\n") { + row += "\n" + } + } + format := hdr + "{{range . }}" + row + "{{end}}" + tmpl := template.Must(template.New("list").Parse(format)) + w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + defer w.Flush() + return tmpl.Execute(w, imgs) +} + +func tokenRepoTag(tag string) (string, string) { + tokens := strings.SplitN(tag, ":", 2) + switch len(tokens) { + case 0: + return tag, "" + case 1: + return tokens[0], "" + case 2: + return tokens[0], tokens[1] + default: + return "<N/A>", "" + } +} + +func sortFunc(key string, data []*entities.ImageSummary) func(i, j int) bool { + switch key { + case "id": + return func(i, j int) bool { + return data[i].ID < data[j].ID + } + case "repository": + return func(i, j int) bool { + return data[i].RepoTags[0] < data[j].RepoTags[0] + } + case "size": + return func(i, j int) bool { + return data[i].Size < data[j].Size + } + case "tag": + return func(i, j int) bool { + return data[i].RepoTags[0] < data[j].RepoTags[0] + } + default: + // case "created": + return func(i, j int) bool { + return data[i].Created >= data[j].Created + } + } +} + +func imageListFormat(flags listFlagType) (string, string) { + if flags.quiet { + return "", "{{.ID}}\n" + } + + // Defaults + hdr := "REPOSITORY\tTAG" + row := "{{.Repository}}\t{{if .Tag}}{{.Tag}}{{else}}<none>{{end}}" + + if flags.digests { + hdr += "\tDIGEST" + row += "\t{{.Digest}}" + } + + hdr += "\tIMAGE ID" + if flags.noTrunc { + row += "\tsha256:{{.ID}}" + } else { + row += "\t{{.ID}}" + } + + hdr += "\tCREATED\tSIZE" + row += "\t{{.Created}}\t{{.Size}}" + + if flags.history { + hdr += "\tHISTORY" + row += "\t{{if .History}}{{.History}}{{else}}<none>{{end}}" + } + + if flags.readOnly { + hdr += "\tReadOnly" + row += "\t{{.ReadOnly}}" + } + + if flags.noHeading { + hdr = "" + } else { + hdr += "\n" + } + + row += "\n" + return hdr, row +} + +type imageReporter struct { + Repository string `json:"repository,omitempty"` + Tag string `json:"tag,omitempty"` + entities.ImageSummary +} + +func (i imageReporter) ID() string { + if !listFlag.noTrunc && len(i.ImageSummary.ID) >= 12 { + return i.ImageSummary.ID[0:12] + } + return i.ImageSummary.ID +} + +func (i imageReporter) Created() string { + return units.HumanDuration(time.Since(time.Unix(i.ImageSummary.Created, 0))) + " ago" +} + +func (i imageReporter) Size() string { + s := units.HumanSizeWithPrecision(float64(i.ImageSummary.Size), 3) + j := strings.LastIndexFunc(s, unicode.IsNumber) + return s[:j+1] + " " + s[j+1:] +} + +func (i imageReporter) History() string { + return strings.Join(i.ImageSummary.History, ", ") +} diff --git a/cmd/podman/images/load.go b/cmd/podman/images/load.go new file mode 100644 index 000000000..23c657b59 --- /dev/null +++ b/cmd/podman/images/load.go @@ -0,0 +1,94 @@ +package images + +import ( + "context" + "fmt" + "io" + "io/ioutil" + "os" + + "github.com/containers/image/v5/docker/reference" + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "golang.org/x/crypto/ssh/terminal" +) + +var ( + loadDescription = "Loads an image from a locally stored archive (tar file) into container storage." + loadCommand = &cobra.Command{ + Use: "load [flags] [NAME[:TAG]]", + Short: "Load an image from container archive", + Long: loadDescription, + RunE: load, + Args: cobra.MaximumNArgs(1), + } +) + +var ( + loadOpts entities.ImageLoadOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: loadCommand, + }) + + flags := loadCommand.Flags() + 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") + if registry.IsRemote() { + _ = flags.MarkHidden("signature-policy") + } + +} + +func load(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + ref, err := reference.Parse(args[0]) + if err != nil { + return err + } + if t, ok := ref.(reference.Tagged); ok { + loadOpts.Tag = t.Tag() + } else { + loadOpts.Tag = "latest" + } + if r, ok := ref.(reference.Named); ok { + fmt.Println(r.Name()) + loadOpts.Name = r.Name() + } + } + if len(loadOpts.Input) > 0 { + if err := parse.ValidateFileName(loadOpts.Input); err != nil { + return err + } + } else { + if terminal.IsTerminal(int(os.Stdin.Fd())) { + return errors.Errorf("cannot read from terminal. Use command-line redirection or the --input flag.") + } + outFile, err := ioutil.TempFile(util.Tmpdir(), "podman") + if err != nil { + return errors.Errorf("error creating file %v", err) + } + defer os.Remove(outFile.Name()) + defer outFile.Close() + + _, err = io.Copy(outFile, os.Stdin) + if err != nil { + return errors.Errorf("error copying file %v", err) + } + loadOpts.Input = outFile.Name() + } + response, err := registry.ImageEngine().Load(context.Background(), loadOpts) + if err != nil { + return err + } + fmt.Println("Loaded image: " + response.Name) + return nil +} diff --git a/cmd/podman/images/prune.go b/cmd/podman/images/prune.go new file mode 100644 index 000000000..b90d889be --- /dev/null +++ b/cmd/podman/images/prune.go @@ -0,0 +1,86 @@ +package images + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + pruneDescription = `Removes all unnamed images from local storage. + + If an image is not being used by a container, it will be removed from the system.` + pruneCmd = &cobra.Command{ + Use: "prune", + Args: cobra.NoArgs, + Short: "Remove unused images", + Long: pruneDescription, + RunE: prune, + Example: `podman image prune`, + } + + pruneOpts = entities.ImagePruneOptions{} + force bool + filter = []string{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: pruneCmd, + Parent: imageCmd, + }) + + flags := pruneCmd.Flags() + flags.BoolVarP(&pruneOpts.All, "all", "a", false, "Remove all unused images, not just dangling ones") + flags.BoolVarP(&force, "force", "f", false, "Do not prompt for confirmation") + flags.StringArrayVar(&filter, "filter", []string{}, "Provide filter values (e.g. 'label=<key>=<value>')") + +} + +func prune(cmd *cobra.Command, args []string) error { + if !force { + reader := bufio.NewReader(os.Stdin) + fmt.Printf(` +WARNING! This will remove all dangling images. +Are you sure you want to continue? [y/N] `) + answer, err := reader.ReadString('\n') + if err != nil { + return errors.Wrapf(err, "error reading input") + } + if strings.ToLower(answer)[0] != 'y' { + return nil + } + } + + // TODO Remove once filter refactor is finished and url.Values rules :) + for _, f := range filter { + t := strings.SplitN(f, "=", 2) + pruneOpts.Filters.Add(t[0], t[1]) + } + + results, err := registry.ImageEngine().Prune(registry.GetContext(), pruneOpts) + if err != nil { + 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 +} diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go new file mode 100644 index 000000000..fb107d00c --- /dev/null +++ b/cmd/podman/images/pull.go @@ -0,0 +1,118 @@ +package images + +import ( + "fmt" + + buildahcli "github.com/containers/buildah/pkg/cli" + "github.com/containers/image/v5/types" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// pullOptionsWrapper wraps entities.ImagePullOptions and prevents leaking +// CLI-only fields into the API types. +type pullOptionsWrapper struct { + entities.ImagePullOptions + TLSVerifyCLI bool // CLI only +} + +var ( + pullOptions = pullOptionsWrapper{} + pullDescription = `Pulls an image from a registry and stores it locally. + + An image can be pulled by tag or digest. If a tag is not specified, the image with the 'latest' tag is pulled.` + + // Command: podman pull + pullCmd = &cobra.Command{ + Use: "pull [flags] IMAGE", + Args: cobra.ExactArgs(1), + Short: "Pull an image from a registry", + Long: pullDescription, + RunE: imagePull, + Example: `podman pull imageName + podman pull fedora:latest`, + } + + // Command: podman image pull + // It's basically a clone of `pullCmd` with the exception of being a + // child of the images command. + imagesPullCmd = &cobra.Command{ + Use: pullCmd.Use, + Short: pullCmd.Short, + Long: pullCmd.Long, + RunE: pullCmd.RunE, + Example: `podman image pull imageName + podman image pull fedora:latest`, + } +) + +func init() { + // pull + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: pullCmd, + }) + + flags := pullCmd.Flags() + pullFlags(flags) + + // images pull + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imagesPullCmd, + Parent: imageCmd, + }) + + imagesPullFlags := imagesPullCmd.Flags() + pullFlags(imagesPullFlags) +} + +// 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.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") + flags.StringVar(&pullOptions.OverrideOS, "override-os", "", "Use `OS` instead of the running OS for choosing images") + flags.BoolVarP(&pullOptions.Quiet, "quiet", "q", false, "Suppress output information when pulling images") + flags.StringVar(&pullOptions.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") + flags.BoolVar(&pullOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") + + if registry.IsRemote() { + _ = flags.MarkHidden("authfile") + _ = flags.MarkHidden("cert-dir") + _ = flags.MarkHidden("signature-policy") + _ = flags.MarkHidden("tls-verify") + } +} + +// imagePull is implement the command for pulling images. +func imagePull(cmd *cobra.Command, args []string) error { + pullOptsAPI := pullOptions.ImagePullOptions + // TLS verification in c/image is controlled via a `types.OptionalBool` + // which allows for distinguishing among set-true, set-false, unspecified + // which is important to implement a sane way of dealing with defaults of + // boolean CLI flags. + if cmd.Flags().Changed("tls-verify") { + pullOptsAPI.TLSVerify = types.NewOptionalBool(pullOptions.TLSVerifyCLI) + } + + // Let's do all the remaining Yoga in the API to prevent us from + // scattering logic across (too) many parts of the code. + pullReport, err := registry.ImageEngine().Pull(registry.GetContext(), args[0], pullOptsAPI) + if err != nil { + return err + } + + if len(pullReport.Images) > 1 { + fmt.Println("Pulled Images:") + } + for _, img := range pullReport.Images { + fmt.Println(img) + } + + return nil +} diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go new file mode 100644 index 000000000..f12a5ac86 --- /dev/null +++ b/cmd/podman/images/push.go @@ -0,0 +1,120 @@ +package images + +import ( + buildahcli "github.com/containers/buildah/pkg/cli" + "github.com/containers/image/v5/types" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// pushOptionsWrapper wraps entities.ImagepushOptions and prevents leaking +// CLI-only fields into the API types. +type pushOptionsWrapper struct { + entities.ImagePushOptions + TLSVerifyCLI bool // CLI only +} + +var ( + pushOptions = pushOptionsWrapper{} + pushDescription = `Pushes a source image to a specified destination. + + The Image "DESTINATION" uses a "transport":"details" format. See podman-push(1) section "DESTINATION" for the expected format.` + + // Command: podman push + pushCmd = &cobra.Command{ + Use: "push [flags] SOURCE DESTINATION", + Short: "Push an image to a specified destination", + Long: pushDescription, + RunE: imagePush, + Example: `podman push imageID docker://registry.example.com/repository:tag + podman push imageID oci-archive:/path/to/layout:image:tag`, + } + + // Command: podman image push + // It's basically a clone of `pushCmd` with the exception of being a + // child of the images command. + imagePushCmd = &cobra.Command{ + Use: pushCmd.Use, + Short: pushCmd.Short, + Long: pushCmd.Long, + RunE: pushCmd.RunE, + Example: `podman image push imageID docker://registry.example.com/repository:tag + podman image push imageID oci-archive:/path/to/layout:image:tag`, + } +) + +func init() { + // push + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: pushCmd, + }) + + flags := pushCmd.Flags() + pushFlags(flags) + + // images push + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imagePushCmd, + Parent: imageCmd, + }) + + pushFlags(imagePushCmd.Flags()) +} + +// 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.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") + flags.StringVar(&pushOptions.DigestFile, "digestfile", "", "Write the digest of the pushed image to the specified file") + flags.StringVarP(&pushOptions.Format, "format", "f", "", "Manifest type (oci, v2s1, or v2s2) to use when pushing an image using the 'dir' transport (default is manifest type of source)") + flags.BoolVarP(&pushOptions.Quiet, "quiet", "q", false, "Suppress output information when pushing images") + flags.BoolVar(&pushOptions.RemoveSignatures, "remove-signatures", false, "Discard any pre-existing signatures in the image") + flags.StringVar(&pushOptions.SignaturePolicy, "signature-policy", "", "Path to a signature-policy file") + flags.StringVar(&pushOptions.SignBy, "sign-by", "", "Add a signature at the destination using the specified key") + flags.BoolVar(&pushOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") + + if registry.IsRemote() { + _ = flags.MarkHidden("authfile") + _ = flags.MarkHidden("cert-dir") + _ = flags.MarkHidden("compress") + _ = flags.MarkHidden("quiet") + _ = flags.MarkHidden("signature-policy") + _ = flags.MarkHidden("tls-verify") + } +} + +// imagePush is implement the command for pushing images. +func imagePush(cmd *cobra.Command, args []string) error { + var source, destination string + switch len(args) { + case 1: + source = args[0] + case 2: + source = args[0] + destination = args[1] + case 0: + fallthrough + default: + return errors.New("push requires at least one image name, or optionally a second to specify a different destination") + } + + pushOptsAPI := pushOptions.ImagePushOptions + // TLS verification in c/image is controlled via a `types.OptionalBool` + // which allows for distinguishing among set-true, set-false, unspecified + // which is important to implement a sane way of dealing with defaults of + // boolean CLI flags. + if cmd.Flags().Changed("tls-verify") { + pushOptsAPI.TLSVerify = types.NewOptionalBool(pushOptions.TLSVerifyCLI) + } + + // Let's do all the remaining Yoga in the API to prevent us from scattering + // logic across (too) many parts of the code. + return registry.ImageEngine().Push(registry.GetContext(), source, destination, pushOptsAPI) +} diff --git a/cmd/podman/images/rm.go b/cmd/podman/images/rm.go new file mode 100644 index 000000000..135fda387 --- /dev/null +++ b/cmd/podman/images/rm.go @@ -0,0 +1,71 @@ +package images + +import ( + "fmt" + "os" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var ( + rmDescription = "Removes one or more previously pulled or locally created images." + rmCmd = &cobra.Command{ + Use: "rm [flags] IMAGE [IMAGE...]", + Short: "Removes one or more images from local storage", + Long: rmDescription, + RunE: rm, + Example: `podman image rm imageID + podman image rm --force alpine + podman image rm c4dfb1609ee2 93fd78260bd1 c0ed59d05ff7`, + } + + imageOpts = entities.ImageDeleteOptions{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: rmCmd, + Parent: imageCmd, + }) + + imageRemoveFlagSet(rmCmd.Flags()) +} + +func imageRemoveFlagSet(flags *pflag.FlagSet) { + flags.BoolVarP(&imageOpts.All, "all", "a", false, "Remove all images") + flags.BoolVarP(&imageOpts.Force, "force", "f", false, "Force Removal of the image") +} +func rm(cmd *cobra.Command, args []string) error { + + if len(args) < 1 && !imageOpts.All { + return errors.Errorf("image name or ID must be specified") + } + if len(args) > 0 && imageOpts.All { + return errors.Errorf("when using the --all switch, you may not pass any images names or IDs") + } + report, err := registry.ImageEngine().Delete(registry.GetContext(), args, imageOpts) + if err != nil { + switch { + case report != nil && report.ImageNotFound != nil: + fmt.Fprintln(os.Stderr, err.Error()) + registry.SetExitCode(2) + case report != nil && report.ImageInUse != nil: + fmt.Fprintln(os.Stderr, err.Error()) + default: + return err + } + } + + for _, u := range report.Untagged { + fmt.Println("Untagged: " + u) + } + for _, d := range report.Deleted { + fmt.Println("Deleted: " + d) + } + return nil +} diff --git a/cmd/podman/images/rmi.go b/cmd/podman/images/rmi.go new file mode 100644 index 000000000..8e1759ef4 --- /dev/null +++ b/cmd/podman/images/rmi.go @@ -0,0 +1,28 @@ +package images + +import ( + "strings" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + rmiCmd = &cobra.Command{ + Use: strings.Replace(rmCmd.Use, "rm ", "rmi ", 1), + Args: rmCmd.Args, + Short: rmCmd.Short, + Long: rmCmd.Long, + RunE: rmCmd.RunE, + Example: strings.Replace(rmCmd.Example, "podman image rm", "podman rmi", -1), + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: rmiCmd, + }) + imageRemoveFlagSet(rmiCmd.Flags()) +} diff --git a/cmd/podman/images/save.go b/cmd/podman/images/save.go new file mode 100644 index 000000000..8f7832074 --- /dev/null +++ b/cmd/podman/images/save.go @@ -0,0 +1,86 @@ +package images + +import ( + "context" + "os" + "strings" + + "github.com/containers/libpod/libpod/define" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "golang.org/x/crypto/ssh/terminal" +) + +var validFormats = []string{define.OCIManifestDir, define.OCIArchive, define.V2s2ManifestDir, define.V2s2Archive} + +var ( + saveDescription = `Save an image to docker-archive or oci-archive on the local machine. Default is docker-archive.` + + saveCommand = &cobra.Command{ + Use: "save [flags] IMAGE", + Short: "Save image to an archive", + Long: saveDescription, + RunE: save, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.Errorf("need at least 1 argument") + } + format, err := cmd.Flags().GetString("format") + if err != nil { + return err + } + if !util.StringInSlice(format, validFormats) { + return errors.Errorf("format value must be one of %s", strings.Join(validFormats, " ")) + } + return nil + }, + Example: `podman save --quiet -o myimage.tar imageID + podman save --format docker-dir -o ubuntu-dir ubuntu + podman save > alpine-all.tar alpine:latest`, + } +) + +var ( + saveOpts entities.ImageSaveOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: saveCommand, + }) + flags := saveCommand.Flags() + 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)") + flags.BoolVarP(&saveOpts.Quiet, "quiet", "q", false, "Suppress the output") + +} + +func save(cmd *cobra.Command, args []string) error { + var ( + tags []string + ) + if cmd.Flag("compress").Changed && (saveOpts.Format != define.OCIManifestDir && saveOpts.Format != define.V2s2ManifestDir && saveOpts.Format == "") { + return errors.Errorf("--compress can only be set when --format is either 'oci-dir' or 'docker-dir'") + } + if len(saveOpts.Output) == 0 { + fi := os.Stdout + if terminal.IsTerminal(int(fi.Fd())) { + return errors.Errorf("refusing to save to terminal. Use -o flag or redirect") + } + saveOpts.Output = "/dev/stdout" + } + if err := parse.ValidateFileName(saveOpts.Output); err != nil { + return err + } + if len(args) > 1 { + tags = args[1:] + } + return registry.ImageEngine().Save(context.Background(), args[0], tags, saveOpts) +} diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go new file mode 100644 index 000000000..fdad94d45 --- /dev/null +++ b/cmd/podman/images/search.go @@ -0,0 +1,156 @@ +package images + +import ( + "reflect" + "strings" + + buildahcli "github.com/containers/buildah/pkg/cli" + "github.com/containers/buildah/pkg/formats" + "github.com/containers/image/v5/types" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/util/camelcase" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// searchOptionsWrapper wraps entities.ImagePullOptions and prevents leaking +// CLI-only fields into the API types. +type searchOptionsWrapper struct { + entities.ImageSearchOptions + // CLI only flags + TLSVerifyCLI bool // Used to convert to an optional bool later + Format string // For go templating +} + +var ( + searchOptions = searchOptionsWrapper{} + searchDescription = `Search registries for a given image. Can search all the default registries or a specific registry. + + Users can limit the number of results, and filter the output based on certain conditions.` + + // Command: podman search + searchCmd = &cobra.Command{ + Use: "search [flags] TERM", + Short: "Search registry for image", + Long: searchDescription, + RunE: imageSearch, + Args: cobra.ExactArgs(1), + Annotations: map[string]string{ + registry.ParentNSRequired: "", + }, + Example: `podman search --filter=is-official --limit 3 alpine + podman search registry.fedoraproject.org/ # only works with v2 registries + podman search --format "table {{.Index}} {{.Name}}" registry.fedoraproject.org/fedora`, + } + + // Command: podman image search + imageSearchCmd = &cobra.Command{ + Use: searchCmd.Use, + Short: searchCmd.Short, + Long: searchCmd.Long, + RunE: searchCmd.RunE, + Args: searchCmd.Args, + Example: `podman image search --filter=is-official --limit 3 alpine + podman image search registry.fedoraproject.org/ # only works with v2 registries + podman image search --format "table {{.Index}} {{.Name}}" registry.fedoraproject.org/fedora`, + } +) + +func init() { + // search + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: searchCmd, + }) + + flags := searchCmd.Flags() + searchFlags(flags) + + // images search + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageSearchCmd, + Parent: imageCmd, + }) + + imageSearchFlags := imageSearchCmd.Flags() + searchFlags(imageSearchFlags) +} + +// searchFlags set the flags for the pull command. +func searchFlags(flags *pflag.FlagSet) { + flags.StringSliceVarP(&searchOptions.Filters, "filter", "f", []string{}, "Filter output based on conditions provided (default [])") + 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.BoolVar(&searchOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") + if registry.IsRemote() { + _ = flags.MarkHidden("authfile") + _ = flags.MarkHidden("tls-verify") + } +} + +// imageSearch implements the command for searching images. +func imageSearch(cmd *cobra.Command, args []string) error { + searchTerm := "" + switch len(args) { + case 1: + searchTerm = args[0] + default: + return errors.Errorf("search requires exactly one argument") + } + + sarchOptsAPI := searchOptions.ImageSearchOptions + // TLS verification in c/image is controlled via a `types.OptionalBool` + // which allows for distinguishing among set-true, set-false, unspecified + // which is important to implement a sane way of dealing with defaults of + // boolean CLI flags. + if cmd.Flags().Changed("tls-verify") { + sarchOptsAPI.TLSVerify = types.NewOptionalBool(pullOptions.TLSVerifyCLI) + } + + searchReport, err := registry.ImageEngine().Search(registry.GetContext(), searchTerm, sarchOptsAPI) + if err != nil { + return err + } + + format := genSearchFormat(searchOptions.Format) + if len(searchReport) == 0 { + return nil + } + out := formats.StdoutTemplateArray{Output: searchToGeneric(searchReport), Template: format, Fields: searchHeaderMap()} + return out.Out() +} + +// searchHeaderMap returns the headers of a SearchResult. +func searchHeaderMap() map[string]string { + s := new(entities.ImageSearchReport) + v := reflect.Indirect(reflect.ValueOf(s)) + values := make(map[string]string, v.NumField()) + + for i := 0; i < v.NumField(); i++ { + key := v.Type().Field(i).Name + value := key + values[key] = strings.ToUpper(strings.Join(camelcase.Split(value), " ")) + } + return values +} + +func genSearchFormat(format string) string { + if format != "" { + // "\t" from the command line is not being recognized as a tab + // replacing the string "\t" to a tab character if the user passes in "\t" + return strings.Replace(format, `\t`, "\t", -1) + } + return "table {{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\t" +} + +func searchToGeneric(params []entities.ImageSearchReport) (genericParams []interface{}) { + for _, v := range params { + genericParams = append(genericParams, interface{}(v)) + } + return genericParams +} diff --git a/cmd/podman/images/tag.go b/cmd/podman/images/tag.go new file mode 100644 index 000000000..411313a9b --- /dev/null +++ b/cmd/podman/images/tag.go @@ -0,0 +1,32 @@ +package images + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + tagDescription = "Adds one or more additional names to locally-stored image." + tagCommand = &cobra.Command{ + Use: "tag [flags] IMAGE TARGET_NAME [TARGET_NAME...]", + Short: "Add an additional name to a local image", + Long: tagDescription, + RunE: tag, + Args: cobra.MinimumNArgs(2), + Example: `podman tag 0e3bbc2 fedora:latest + podman tag imageID:latest myNewImage:newTag + podman tag httpd myregistryhost:5000/fedora/httpd:v2`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: tagCommand, + }) +} + +func tag(cmd *cobra.Command, args []string) error { + return registry.ImageEngine().Tag(registry.GetContext(), args[0], args[1:], entities.ImageTagOptions{}) +} diff --git a/cmd/podman/images/untag.go b/cmd/podman/images/untag.go new file mode 100644 index 000000000..3218844b7 --- /dev/null +++ b/cmd/podman/images/untag.go @@ -0,0 +1,31 @@ +package images + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + untagCommand = &cobra.Command{ + Use: "untag [flags] IMAGE [NAME...]", + Short: "Remove a name from a local image", + Long: "Removes one or more names from a locally-stored image.", + RunE: untag, + Args: cobra.MinimumNArgs(1), + Example: `podman untag 0e3bbc2 + podman untag imageID:latest otherImageName:latest + podman untag httpd myregistryhost:5000/fedora/httpd:v2`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: untagCommand, + }) +} + +func untag(cmd *cobra.Command, args []string) error { + return registry.ImageEngine().Untag(registry.GetContext(), args[0], args[1:], entities.ImageUntagOptions{}) +} diff --git a/cmd/podman/images_prune.go b/cmd/podman/images_prune.go deleted file mode 100644 index 8f187cbd7..000000000 --- a/cmd/podman/images_prune.go +++ /dev/null @@ -1,73 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "os" - "strings" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - pruneImagesCommand cliconfig.PruneImagesValues - pruneImagesDescription = `Removes all unnamed images from local storage. - - If an image is not being used by a container, it will be removed from the system.` - _pruneImagesCommand = &cobra.Command{ - Use: "prune", - Args: noSubArgs, - Short: "Remove unused images", - Long: pruneImagesDescription, - RunE: func(cmd *cobra.Command, args []string) error { - pruneImagesCommand.InputArgs = args - pruneImagesCommand.GlobalFlags = MainGlobalOpts - pruneImagesCommand.Remote = remoteclient - return pruneImagesCmd(&pruneImagesCommand) - }, - } -) - -func init() { - pruneImagesCommand.Command = _pruneImagesCommand - pruneImagesCommand.SetHelpTemplate(HelpTemplate()) - pruneImagesCommand.SetUsageTemplate(UsageTemplate()) - flags := pruneImagesCommand.Flags() - flags.BoolVarP(&pruneImagesCommand.All, "all", "a", false, "Remove all unused images, not just dangling ones") - flags.BoolVarP(&pruneImagesCommand.Force, "force", "f", false, "Do not prompt for confirmation") - flags.StringArrayVar(&pruneImagesCommand.Filter, "filter", []string{}, "Provide filter values (e.g. 'label=<key>=<value>')") -} - -func pruneImagesCmd(c *cliconfig.PruneImagesValues) error { - if !c.Force { - reader := bufio.NewReader(os.Stdin) - fmt.Printf(` -WARNING! This will remove all dangling images. -Are you sure you want to continue? [y/N] `) - answer, err := reader.ReadString('\n') - if err != nil { - return errors.Wrapf(err, "error reading input") - } - if strings.ToLower(answer)[0] != 'y' { - return nil - } - } - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - // Call prune; if any cids are returned, print them and then - // return err in case an error also came up - pruneCids, err := runtime.PruneImages(getContext(), c.All, c.Filter) - if len(pruneCids) > 0 { - for _, cid := range pruneCids { - fmt.Println(cid) - } - } - return err -} diff --git a/cmd/podman/import.go b/cmd/podman/import.go deleted file mode 100644 index 5a21e5cc1..000000000 --- a/cmd/podman/import.go +++ /dev/null @@ -1,88 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/shared/parse" - "github.com/containers/libpod/pkg/adapter" - "github.com/hashicorp/go-multierror" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - importCommand cliconfig.ImportValues - - importDescription = `Create a container image from the contents of the specified tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz). - - Note remote tar balls can be specified, via web address. - Optionally tag the image. You can specify the instructions using the --change option.` - _importCommand = &cobra.Command{ - Use: "import [flags] PATH [REFERENCE]", - Short: "Import a tarball to create a filesystem image", - Long: importDescription, - RunE: func(cmd *cobra.Command, args []string) error { - importCommand.InputArgs = args - importCommand.GlobalFlags = MainGlobalOpts - importCommand.Remote = remoteclient - return importCmd(&importCommand) - }, - Example: `podman import http://example.com/ctr.tar url-image - cat ctr.tar | podman -q import --message "importing the ctr.tar tarball" - image-imported - cat ctr.tar | podman import -`, - } -) - -func init() { - importCommand.Command = _importCommand - importCommand.SetHelpTemplate(HelpTemplate()) - importCommand.SetUsageTemplate(UsageTemplate()) - flags := importCommand.Flags() - flags.StringArrayVarP(&importCommand.Change, "change", "c", []string{}, "Apply the following possible instructions to the created image (default []): CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR") - flags.StringVarP(&importCommand.Message, "message", "m", "", "Set commit message for imported image") - flags.BoolVarP(&importCommand.Quiet, "quiet", "q", false, "Suppress output") - -} - -func importCmd(c *cliconfig.ImportValues) error { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - var ( - source string - reference string - ) - args := c.InputArgs - switch len(args) { - case 0: - return errors.Errorf("need to give the path to the tarball, or must specify a tarball of '-' for stdin") - case 1: - source = args[0] - case 2: - source = args[0] - reference = args[1] - default: - return errors.Errorf("too many arguments. Usage TARBALL [REFERENCE]") - } - - errFileName := parse.ValidateFileName(source) - errURL := parse.ValidURL(source) - - if errFileName != nil && errURL != nil { - return multierror.Append(errFileName, errURL) - } - - quiet := c.Quiet - if runtime.Remote { - quiet = false - } - iid, err := runtime.Import(getContext(), source, reference, importCommand.Change, c.String("message"), quiet) - if err == nil { - fmt.Println(iid) - } - return err -} diff --git a/cmd/podman/info.go b/cmd/podman/info.go deleted file mode 100644 index 7361525ce..000000000 --- a/cmd/podman/info.go +++ /dev/null @@ -1,116 +0,0 @@ -package main - -import ( - "fmt" - rt "runtime" - "strings" - - "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/version" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - infoCommand cliconfig.InfoValues - - infoDescription = `Display information pertaining to the host, current storage stats, and build of podman. - - Useful for the user and when reporting issues. -` - _infoCommand = &cobra.Command{ - Use: "info", - Args: noSubArgs, - Long: infoDescription, - Short: "Display podman system information", - RunE: func(cmd *cobra.Command, args []string) error { - infoCommand.InputArgs = args - infoCommand.GlobalFlags = MainGlobalOpts - infoCommand.Remote = remoteclient - return infoCmd(&infoCommand) - }, - Example: `podman info`, - } -) - -func init() { - infoCommand.Command = _infoCommand - infoCommand.SetHelpTemplate(HelpTemplate()) - infoCommand.SetUsageTemplate(UsageTemplate()) - flags := infoCommand.Flags() - - flags.BoolVarP(&infoCommand.Debug, "debug", "D", false, "Display additional debug information") - flags.StringVarP(&infoCommand.Format, "format", "f", "", "Change the output format to JSON or a Go template") - -} - -func infoCmd(c *cliconfig.InfoValues) error { - info := map[string]interface{}{} - remoteClientInfo := map[string]interface{}{} - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - infoArr, err := runtime.Info() - if err != nil { - return errors.Wrapf(err, "error getting info") - } - - if runtime.Remote { - endpoint, err := runtime.RemoteEndpoint() - if err != nil { - logrus.Errorf("Failed to obtain server connection: %s", err.Error()) - } else { - remoteClientInfo["Connection"] = endpoint.Connection - remoteClientInfo["Connection Type"] = endpoint.Type.String() - } - - remoteClientInfo["RemoteAPI Version"] = version.RemoteAPIVersion - remoteClientInfo["Podman Version"] = version.Version - remoteClientInfo["OS Arch"] = fmt.Sprintf("%s/%s", rt.GOOS, rt.GOARCH) - infoArr = append(infoArr, define.InfoData{Type: "client", Data: remoteClientInfo}) - } - - if !runtime.Remote && c.Debug { - debugInfo := debugInfo(c) - infoArr = append(infoArr, define.InfoData{Type: "debug", Data: debugInfo}) - } - - for _, currInfo := range infoArr { - info[currInfo.Type] = currInfo.Data - } - - var out formats.Writer - infoOutputFormat := c.Format - if strings.Join(strings.Fields(infoOutputFormat), "") == "{{json.}}" { - infoOutputFormat = formats.JSONString - } - switch infoOutputFormat { - case formats.JSONString: - out = formats.JSONStruct{Output: info} - case "": - out = formats.YAMLStruct{Output: info} - default: - out = formats.StdoutTemplate{Output: info, Template: infoOutputFormat} - } - - return out.Out() -} - -// top-level "debug" info -func debugInfo(c *cliconfig.InfoValues) map[string]interface{} { - info := map[string]interface{}{} - info["compiler"] = rt.Compiler - info["go version"] = rt.Version() - info["podman version"] = version.Version - version, _ := define.GetVersion() - info["git commit"] = version.GitCommit - return info -} diff --git a/cmd/podman/init.go b/cmd/podman/init.go deleted file mode 100644 index 2e0b33828..000000000 --- a/cmd/podman/init.go +++ /dev/null @@ -1,64 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/opentracing/opentracing-go" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - initCommand cliconfig.InitValues - initDescription = `Initialize one or more containers, creating the OCI spec and mounts for inspection. Container names or IDs can be used.` - - _initCommand = &cobra.Command{ - Use: "init [flags] CONTAINER [CONTAINER...]", - Short: "Initialize one or more containers", - Long: initDescription, - RunE: func(cmd *cobra.Command, args []string) error { - initCommand.InputArgs = args - initCommand.GlobalFlags = MainGlobalOpts - initCommand.Remote = remoteclient - return initCmd(&initCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllLatestAndCIDFile(cmd, args, false, false) - }, - Example: `podman init --latest - podman init 3c45ef19d893 - podman init test1`, - } -) - -func init() { - initCommand.Command = _initCommand - initCommand.SetHelpTemplate(HelpTemplate()) - initCommand.SetUsageTemplate(UsageTemplate()) - flags := initCommand.Flags() - flags.BoolVarP(&initCommand.All, "all", "a", false, "Initialize all containers") - flags.BoolVarP(&initCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - markFlagHiddenForRemoteClient("latest", flags) -} - -// initCmd initializes a container -func initCmd(c *cliconfig.InitValues) error { - if c.Bool("trace") { - span, _ := opentracing.StartSpanFromContext(Ctx, "initCmd") - defer span.Finish() - } - - ctx := getContext() - - runtime, err := adapter.GetRuntime(ctx, &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - ok, failures, err := runtime.InitContainers(ctx, c) - if err != nil { - return err - } - return printCmdResults(ok, failures) -} diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index 872b59561..0393303e8 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -2,193 +2,51 @@ package main import ( "context" - "strings" + "fmt" - "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/util" - "github.com/pkg/errors" + "github.com/containers/libpod/cmd/podman/common" + "github.com/containers/libpod/cmd/podman/containers" + "github.com/containers/libpod/cmd/podman/images" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) -const ( - inspectTypeContainer = "container" - inspectTypeImage = "image" - inspectAll = "all" -) +// Inspect is one of the outlier commands in that it operates on images/containers/... var ( - inspectCommand cliconfig.InspectValues - - inspectDescription = `This displays the low-level information on containers and images identified by name or ID. + inspectOpts *entities.InspectOptions - If given a name that matches both a container and an image, this command inspects the container. By default, this will render all results in a JSON array.` - _inspectCommand = cobra.Command{ - Use: "inspect [flags] CONTAINER | IMAGE", - Short: "Display the configuration of a container or image", - Long: inspectDescription, - RunE: func(cmd *cobra.Command, args []string) error { - inspectCommand.InputArgs = args - inspectCommand.GlobalFlags = MainGlobalOpts - inspectCommand.Remote = remoteclient - return inspectCmd(&inspectCommand) - }, - Example: `podman inspect alpine - podman inspect --format "imageId: {{.Id}} size: {{.Size}}" alpine - podman inspect --format "image: {{.ImageName}} driver: {{.Driver}}" myctr`, + // Command: podman _inspect_ Object_ID + inspectCmd = &cobra.Command{ + Use: "inspect [flags] {CONTAINER_ID | IMAGE_ID}", + Args: cobra.ExactArgs(1), + Short: "Display the configuration of object denoted by ID", + Long: "Displays the low-level information on an object identified by name or ID", + TraverseChildren: true, + RunE: inspect, } ) -func inspectInit(command *cliconfig.InspectValues) { - command.SetHelpTemplate(HelpTemplate()) - command.SetUsageTemplate(UsageTemplate()) - flags := command.Flags() - flags.StringVarP(&command.Format, "format", "f", "", "Change the output format to a Go template") - - // -t flag applicable only to 'podman inspect', not 'image/container inspect' - ambiguous := strings.Contains(command.Use, "|") - if ambiguous { - flags.StringVarP(&command.TypeObject, "type", "t", inspectAll, "Return JSON for specified type, (image or container)") - } - - if strings.Contains(command.Use, "CONTAINER") { - containers_only := " (containers only)" - if !ambiguous { - containers_only = "" - command.TypeObject = inspectTypeContainer - } - flags.BoolVarP(&command.Latest, "latest", "l", false, "Act on the latest container podman is aware of"+containers_only) - flags.BoolVarP(&command.Size, "size", "s", false, "Display total file size"+containers_only) - markFlagHiddenForRemoteClient("latest", flags) - } else { - command.TypeObject = inspectTypeImage - } -} func init() { - inspectCommand.Command = &_inspectCommand - inspectInit(&inspectCommand) + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: inspectCmd, + }) + inspectOpts = common.AddInspectFlagSet(inspectCmd) } -func inspectCmd(c *cliconfig.InspectValues) error { - args := c.InputArgs - inspectType := c.TypeObject - latestContainer := c.Latest - if len(args) == 0 && !latestContainer { - return errors.Errorf("container or image name must be specified: podman inspect [options [...]] name") - } - - if len(args) > 0 && latestContainer { - return errors.Errorf("you cannot provide additional arguments with --latest") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - if !util.StringInSlice(inspectType, []string{inspectTypeContainer, inspectTypeImage, inspectAll}) { - return errors.Errorf("the only recognized types are %q, %q, and %q", inspectTypeContainer, inspectTypeImage, inspectAll) +func inspect(cmd *cobra.Command, args []string) error { + if found, err := registry.ImageEngine().Exists(context.Background(), args[0]); err != nil { + return err + } else if found.Value { + return images.Inspect(cmd, args, inspectOpts) } - outputFormat := c.Format - if strings.Contains(outputFormat, "{{.Id}}") { - outputFormat = strings.Replace(outputFormat, "{{.Id}}", formats.IDString, -1) - } - // These fields were renamed, so we need to provide backward compat for - // the old names. - if strings.Contains(outputFormat, ".Src") { - outputFormat = strings.Replace(outputFormat, ".Src", ".Source", -1) - } - if strings.Contains(outputFormat, ".Dst") { - outputFormat = strings.Replace(outputFormat, ".Dst", ".Destination", -1) - } - if strings.Contains(outputFormat, ".ImageID") { - outputFormat = strings.Replace(outputFormat, ".ImageID", ".Image", -1) - } - if latestContainer { - lc, err := runtime.GetLatestContainer() - if err != nil { - return err - } - args = append(args, lc.ID()) - inspectType = inspectTypeContainer - } - - inspectedObjects, iterateErr := iterateInput(getContext(), c.Size, args, runtime, inspectType) - if iterateErr != nil { - return iterateErr - } - - var out formats.Writer - if outputFormat != "" && outputFormat != formats.JSONString { - //template - out = formats.StdoutTemplateArray{Output: inspectedObjects, Template: outputFormat} - } else { - // default is json output - out = formats.JSONStructArray{Output: inspectedObjects} - } - - return out.Out() -} - -// func iterateInput iterates the images|containers the user has requested and returns the inspect data and error -func iterateInput(ctx context.Context, size bool, args []string, runtime *adapter.LocalRuntime, inspectType string) ([]interface{}, error) { - var ( - data interface{} - inspectedItems []interface{} - inspectError error - ) - - for _, input := range args { - switch inspectType { - case inspectTypeContainer: - ctr, err := runtime.LookupContainer(input) - if err != nil { - inspectError = errors.Wrapf(err, "error looking up container %q", input) - break - } - data, err = ctr.Inspect(size) - if err != nil { - inspectError = errors.Wrapf(err, "error inspecting container %s", ctr.ID()) - break - } - case inspectTypeImage: - image, err := runtime.NewImageFromLocal(input) - if err != nil { - inspectError = errors.Wrapf(err, "error getting image %q", input) - break - } - data, err = image.Inspect(ctx) - if err != nil { - inspectError = errors.Wrapf(err, "error parsing image data %q", image.ID()) - break - } - case inspectAll: - ctr, err := runtime.LookupContainer(input) - if err != nil { - image, err := runtime.NewImageFromLocal(input) - if err != nil { - inspectError = errors.Wrapf(err, "error getting image %q", input) - break - } - data, err = image.Inspect(ctx) - if err != nil { - inspectError = errors.Wrapf(err, "error parsing image data %q", image.ID()) - break - } - } else { - data, err = ctr.Inspect(size) - if err != nil { - inspectError = errors.Wrapf(err, "error inspecting container %s", ctr.ID()) - break - } - } - } - if inspectError == nil { - inspectedItems = append(inspectedItems, data) - } + if found, err := registry.ContainerEngine().ContainerExists(context.Background(), args[0]); err != nil { + return err + } else if found.Value { + return containers.Inspect(cmd, args, inspectOpts) } - return inspectedItems, inspectError + return fmt.Errorf("%s not found on system", args[0]) } diff --git a/cmd/podman/kill.go b/cmd/podman/kill.go deleted file mode 100644 index a10546ea9..000000000 --- a/cmd/podman/kill.go +++ /dev/null @@ -1,73 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/util" - "github.com/opentracing/opentracing-go" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - killCommand cliconfig.KillValues - - killDescription = "The main process inside each container specified will be sent SIGKILL, or any signal specified with option --signal." - _killCommand = &cobra.Command{ - Use: "kill [flags] CONTAINER [CONTAINER...]", - Short: "Kill one or more running containers with a specific signal", - Long: killDescription, - RunE: func(cmd *cobra.Command, args []string) error { - killCommand.InputArgs = args - killCommand.GlobalFlags = MainGlobalOpts - killCommand.Remote = remoteclient - return killCmd(&killCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllLatestAndCIDFile(cmd, args, false, false) - }, - Example: `podman kill mywebserver - podman kill 860a4b23 - podman kill --signal TERM ctrID`, - } -) - -func init() { - killCommand.Command = _killCommand - killCommand.SetHelpTemplate(HelpTemplate()) - killCommand.SetUsageTemplate(UsageTemplate()) - flags := killCommand.Flags() - - flags.BoolVarP(&killCommand.All, "all", "a", false, "Signal all running containers") - flags.StringVarP(&killCommand.Signal, "signal", "s", "KILL", "Signal to send to the container") - flags.BoolVarP(&killCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - - markFlagHiddenForRemoteClient("latest", flags) -} - -// killCmd kills one or more containers with a signal -func killCmd(c *cliconfig.KillValues) error { - if c.Bool("trace") { - span, _ := opentracing.StartSpanFromContext(Ctx, "killCmd") - defer span.Finish() - } - - // Check if the signalString provided by the user is valid - // Invalid signals will return err - killSignal, err := util.ParseSignal(c.Signal) - if err != nil { - return err - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - ok, failures, err := runtime.KillContainers(getContext(), c, killSignal) - if err != nil { - return err - } - return printCmdResults(ok, failures) -} diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go deleted file mode 100644 index 8dbc4009b..000000000 --- a/cmd/podman/libpodruntime/runtime.go +++ /dev/null @@ -1,216 +0,0 @@ -package libpodruntime - -import ( - "context" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/cgroups" - "github.com/containers/libpod/pkg/namespaces" - "github.com/containers/libpod/pkg/rootless" - "github.com/containers/libpod/pkg/util" - "github.com/containers/storage" - "github.com/pkg/errors" -) - -type runtimeOptions struct { - name string - renumber bool - migrate bool - noStore bool - withFDS bool -} - -// GetRuntimeMigrate gets a libpod runtime that will perform a migration of existing containers -func GetRuntimeMigrate(ctx context.Context, c *cliconfig.PodmanCommand, newRuntime string) (*libpod.Runtime, error) { - return getRuntime(ctx, c, &runtimeOptions{ - name: newRuntime, - renumber: false, - migrate: true, - noStore: false, - withFDS: true, - }) -} - -// GetRuntimeDisableFDs gets a libpod runtime that will disable sd notify -func GetRuntimeDisableFDs(ctx context.Context, c *cliconfig.PodmanCommand) (*libpod.Runtime, error) { - return getRuntime(ctx, c, &runtimeOptions{ - renumber: false, - migrate: false, - noStore: false, - withFDS: false, - }) -} - -// GetRuntimeRenumber gets a libpod runtime that will perform a lock renumber -func GetRuntimeRenumber(ctx context.Context, c *cliconfig.PodmanCommand) (*libpod.Runtime, error) { - return getRuntime(ctx, c, &runtimeOptions{ - renumber: true, - migrate: false, - noStore: false, - withFDS: true, - }) -} - -// GetRuntime generates a new libpod runtime configured by command line options -func GetRuntime(ctx context.Context, c *cliconfig.PodmanCommand) (*libpod.Runtime, error) { - return getRuntime(ctx, c, &runtimeOptions{ - renumber: false, - migrate: false, - noStore: false, - withFDS: true, - }) -} - -// GetRuntimeNoStore generates a new libpod runtime configured by command line options -func GetRuntimeNoStore(ctx context.Context, c *cliconfig.PodmanCommand) (*libpod.Runtime, error) { - return getRuntime(ctx, c, &runtimeOptions{ - renumber: false, - migrate: false, - noStore: true, - withFDS: true, - }) -} - -func getRuntime(ctx context.Context, c *cliconfig.PodmanCommand, opts *runtimeOptions) (*libpod.Runtime, error) { - options := []libpod.RuntimeOption{} - storageOpts := storage.StoreOptions{} - storageSet := false - - uidmapFlag := c.Flags().Lookup("uidmap") - gidmapFlag := c.Flags().Lookup("gidmap") - subuidname := c.Flags().Lookup("subuidname") - subgidname := c.Flags().Lookup("subgidname") - if (uidmapFlag != nil && gidmapFlag != nil && subuidname != nil && subgidname != nil) && - (uidmapFlag.Changed || gidmapFlag.Changed || subuidname.Changed || subgidname.Changed) { - userns, _ := c.Flags().GetString("userns") - uidmapVal, _ := c.Flags().GetStringSlice("uidmap") - gidmapVal, _ := c.Flags().GetStringSlice("gidmap") - subuidVal, _ := c.Flags().GetString("subuidname") - subgidVal, _ := c.Flags().GetString("subgidname") - mappings, err := util.ParseIDMapping(namespaces.UsernsMode(userns), uidmapVal, gidmapVal, subuidVal, subgidVal) - if err != nil { - return nil, err - } - storageOpts.UIDMap = mappings.UIDMap - storageOpts.GIDMap = mappings.GIDMap - - storageSet = true - } - - if c.Flags().Changed("root") { - storageSet = true - storageOpts.GraphRoot = c.GlobalFlags.Root - } - if c.Flags().Changed("runroot") { - storageSet = true - storageOpts.RunRoot = c.GlobalFlags.Runroot - } - if len(storageOpts.RunRoot) > 50 { - return nil, errors.New("the specified runroot is longer than 50 characters") - } - if c.Flags().Changed("storage-driver") { - storageSet = true - storageOpts.GraphDriverName = c.GlobalFlags.StorageDriver - // Overriding the default storage driver caused GraphDriverOptions from storage.conf to be ignored - storageOpts.GraphDriverOptions = []string{} - } - // This should always be checked after storage-driver is checked - if len(c.GlobalFlags.StorageOpts) > 0 { - storageSet = true - storageOpts.GraphDriverOptions = c.GlobalFlags.StorageOpts - } - if opts.migrate { - options = append(options, libpod.WithMigrate()) - if opts.name != "" { - options = append(options, libpod.WithMigrateRuntime(opts.name)) - } - } - - if opts.renumber { - options = append(options, libpod.WithRenumber()) - } - - // Only set this if the user changes storage config on the command line - if storageSet { - options = append(options, libpod.WithStorageConfig(storageOpts)) - } - - if !storageSet && opts.noStore { - options = append(options, libpod.WithNoStore()) - } - // TODO CLI flags for image config? - // TODO CLI flag for signature policy? - - if len(c.GlobalFlags.Namespace) > 0 { - options = append(options, libpod.WithNamespace(c.GlobalFlags.Namespace)) - } - - if c.Flags().Changed("runtime") { - options = append(options, libpod.WithOCIRuntime(c.GlobalFlags.Runtime)) - } - - if c.Flags().Changed("conmon") { - options = append(options, libpod.WithConmonPath(c.GlobalFlags.ConmonPath)) - } - if c.Flags().Changed("tmpdir") { - options = append(options, libpod.WithTmpDir(c.GlobalFlags.TmpDir)) - } - if c.Flags().Changed("network-cmd-path") { - options = append(options, libpod.WithNetworkCmdPath(c.GlobalFlags.NetworkCmdPath)) - } - - if c.Flags().Changed("events-backend") { - options = append(options, libpod.WithEventsLogger(c.GlobalFlags.EventsBackend)) - } - - if c.Flags().Changed("cgroup-manager") { - options = append(options, libpod.WithCgroupManager(c.GlobalFlags.CGroupManager)) - } else { - unified, err := cgroups.IsCgroup2UnifiedMode() - if err != nil { - return nil, err - } - if rootless.IsRootless() && !unified { - options = append(options, libpod.WithCgroupManager("cgroupfs")) - } - } - - // TODO flag to set libpod static dir? - // TODO flag to set libpod tmp dir? - - if c.Flags().Changed("cni-config-dir") { - options = append(options, libpod.WithCNIConfigDir(c.GlobalFlags.CniConfigDir)) - } - if c.Flags().Changed("default-mounts-file") { - options = append(options, libpod.WithDefaultMountsFile(c.GlobalFlags.DefaultMountsFile)) - } - if c.Flags().Changed("hooks-dir") { - options = append(options, libpod.WithHooksDir(c.GlobalFlags.HooksDir...)) - } - - // TODO flag to set CNI plugins dir? - - // TODO I don't think these belong here? - // Will follow up with a different PR to address - // - // Pod create options - - infraImageFlag := c.Flags().Lookup("infra-image") - if infraImageFlag != nil && infraImageFlag.Changed { - infraImage, _ := c.Flags().GetString("infra-image") - options = append(options, libpod.WithDefaultInfraImage(infraImage)) - } - - infraCommandFlag := c.Flags().Lookup("infra-command") - if infraCommandFlag != nil && infraImageFlag.Changed { - infraCommand, _ := c.Flags().GetString("infra-command") - options = append(options, libpod.WithDefaultInfraCommand(infraCommand)) - } - - if !opts.withFDS { - options = append(options, libpod.WithEnableSDNotify()) - } - - return libpod.NewRuntime(ctx, options...) -} diff --git a/cmd/podman/load.go b/cmd/podman/load.go deleted file mode 100644 index 318b5b5fb..000000000 --- a/cmd/podman/load.go +++ /dev/null @@ -1,110 +0,0 @@ -package main - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "strings" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/shared/parse" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/util" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "golang.org/x/crypto/ssh/terminal" -) - -var ( - loadCommand cliconfig.LoadValues - - loadDescription = "Loads an image from a locally stored archive (tar file) into container storage." - - _loadCommand = &cobra.Command{ - Use: "load [flags] [NAME[:TAG]]", - Short: "Load an image from container archive", - Long: loadDescription, - RunE: func(cmd *cobra.Command, args []string) error { - loadCommand.InputArgs = args - loadCommand.GlobalFlags = MainGlobalOpts - loadCommand.Remote = remoteclient - return loadCmd(&loadCommand) - }, - } -) - -func init() { - loadCommand.Command = _loadCommand - loadCommand.SetHelpTemplate(HelpTemplate()) - loadCommand.SetUsageTemplate(UsageTemplate()) - flags := loadCommand.Flags() - flags.StringVarP(&loadCommand.Input, "input", "i", "", "Read from specified archive file (default: stdin)") - flags.BoolVarP(&loadCommand.Quiet, "quiet", "q", false, "Suppress the output") - // Disabled flags for the remote client - if !remote { - flags.StringVar(&loadCommand.SignaturePolicy, "signature-policy", "", "Pathname of signature policy file (not usually used)") - markFlagHidden(flags, "signature-policy") - } -} - -// loadCmd gets the image/file to be loaded from the command line -// and calls loadImage to load the image to containers-storage -func loadCmd(c *cliconfig.LoadValues) error { - - args := c.InputArgs - var imageName string - - if len(args) == 1 { - imageName = args[0] - } - if len(args) > 1 { - return errors.New("too many arguments. Requires exactly 1") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - if len(c.Input) > 0 { - if err := parse.ValidateFileName(c.Input); err != nil { - return err - } - } else { - if terminal.IsTerminal(int(os.Stdin.Fd())) { - return errors.Errorf("cannot read from terminal. Use command-line redirection or the --input flag.") - } - outFile, err := ioutil.TempFile(util.Tmpdir(), "podman") - if err != nil { - return errors.Errorf("error creating file %v", err) - } - defer os.Remove(outFile.Name()) - defer outFile.Close() - - _, err = io.Copy(outFile, os.Stdin) - if err != nil { - return errors.Errorf("error copying file %v", err) - } - - c.Input = outFile.Name() - } - - names, err := runtime.LoadImage(getContext(), imageName, c) - if err != nil { - return err - } - if len(imageName) > 0 { - split := strings.Split(names, ",") - newImage, err := runtime.NewImageFromLocal(split[0]) - if err != nil { - return err - } - if err := newImage.TagImage(imageName); err != nil { - return errors.Wrapf(err, "error adding '%s' to image %q", imageName, newImage.InputName) - } - } - fmt.Println("Loaded image(s): " + names) - return nil -} diff --git a/cmd/podman/login.go b/cmd/podman/login.go deleted file mode 100644 index 1539e3a79..000000000 --- a/cmd/podman/login.go +++ /dev/null @@ -1,210 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "os" - "strings" - - buildahcli "github.com/containers/buildah/pkg/cli" - "github.com/containers/image/v5/docker" - "github.com/containers/image/v5/pkg/docker/config" - "github.com/containers/image/v5/types" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/registries" - "github.com/docker/docker-credential-helpers/credentials" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "golang.org/x/crypto/ssh/terminal" -) - -var ( - loginCommand cliconfig.LoginValues - - loginDescription = "Login to a container registry on a specified server." - _loginCommand = &cobra.Command{ - Use: "login [flags] REGISTRY", - Short: "Login to a container registry", - Long: loginDescription, - RunE: func(cmd *cobra.Command, args []string) error { - loginCommand.InputArgs = args - loginCommand.GlobalFlags = MainGlobalOpts - loginCommand.Remote = remoteclient - return loginCmd(&loginCommand) - }, - Example: `podman login -u testuser -p testpassword localhost:5000 - podman login -u testuser -p testpassword localhost:5000`, - } -) - -func init() { - if !remote { - _loginCommand.Example = fmt.Sprintf("%s\n podman login --authfile authdir/myauths.json quay.io", _loginCommand.Example) - - } - loginCommand.Command = _loginCommand - loginCommand.SetHelpTemplate(HelpTemplate()) - loginCommand.SetUsageTemplate(UsageTemplate()) - flags := loginCommand.Flags() - - flags.BoolVar(&loginCommand.GetLogin, "get-login", true, "Return the current login user for the registry") - flags.StringVarP(&loginCommand.Password, "password", "p", "", "Password for registry") - flags.StringVarP(&loginCommand.Username, "username", "u", "", "Username for registry") - flags.BoolVar(&loginCommand.StdinPassword, "password-stdin", false, "Take the password from stdin") - // Disabled flags for the remote client - if !remote { - flags.StringVar(&loginCommand.Authfile, "authfile", buildahcli.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") - flags.StringVar(&loginCommand.CertDir, "cert-dir", "", "Pathname of a directory containing TLS certificates and keys used to connect to the registry") - flags.BoolVar(&loginCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") - } -} - -// loginCmd uses the authentication package to store a user's authenticated credentials -// in an auth.json file for future use -func loginCmd(c *cliconfig.LoginValues) error { - args := c.InputArgs - if len(args) > 1 { - return errors.Errorf("too many arguments, login takes only 1 argument") - } - var server string - if len(args) == 0 { - registriesFromFile, err := registries.GetRegistries() - if err != nil || len(registriesFromFile) == 0 { - return errors.Errorf("please specify a registry to login to") - } - - server = registriesFromFile[0] - logrus.Debugf("registry not specified, default to the first registry %q from registries.conf", server) - - } else { - server = registryFromFullName(scrubServer(args[0])) - } - - if c.Flag("password").Changed { - fmt.Fprintf(os.Stderr, "WARNING! Using --password via the cli is insecure. Please consider using --password-stdin\n") - } - - sc := image.GetSystemContext("", c.Authfile, false) - if c.Flag("tls-verify").Changed { - sc.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) - } - if c.CertDir != "" { - sc.DockerCertPath = c.CertDir - } - - // username of user logged in to server (if one exists) - authConfig, err := config.GetCredentials(sc, server) - // Do not return error if no credentials found in credHelpers, new credentials will be stored by config.SetAuthentication - if err != nil && err != credentials.NewErrCredentialsNotFound() { - return errors.Wrapf(err, "error reading auth file") - } - if authConfig.IdentityToken != "" { - return errors.Errorf("currently logged in, auth file contains an Identity token") - } - if c.Flag("get-login").Changed { - if authConfig.Username == "" { - return errors.Errorf("not logged into %s", server) - } - fmt.Printf("%s\n", authConfig.Username) - return nil - } - - ctx := getContext() - - password := c.Password - - if c.Flag("password-stdin").Changed { - var stdinPasswordStrBuilder strings.Builder - if c.Password != "" { - return errors.Errorf("Can't specify both --password-stdin and --password") - } - if c.Username == "" { - return errors.Errorf("Must provide --username with --password-stdin") - } - scanner := bufio.NewScanner(os.Stdin) - for scanner.Scan() { - fmt.Fprint(&stdinPasswordStrBuilder, scanner.Text()) - } - password = stdinPasswordStrBuilder.String() - } - - // If no username and no password is specified, try to use existing ones. - if c.Username == "" && password == "" && authConfig.Username == "" && authConfig.Password != "" { - fmt.Println("Authenticating with existing credentials...") - if err := docker.CheckAuth(ctx, sc, authConfig.Username, authConfig.Password, server); err == nil { - fmt.Println("Existing credentials are valid. Already logged in to", server) - return nil - } - fmt.Println("Existing credentials are invalid, please enter valid username and password") - } - - username, password, err := getUserAndPass(c.Username, password, authConfig.Username) - if err != nil { - return errors.Wrapf(err, "error getting username and password") - } - - if err = docker.CheckAuth(ctx, sc, username, password, server); err == nil { - // Write the new credentials to the authfile - if err = config.SetAuthentication(sc, server, username, password); err != nil { - return err - } - } - if err == nil { - fmt.Println("Login Succeeded!") - return nil - } - if unauthorizedError, ok := err.(docker.ErrUnauthorizedForCredentials); ok { - logrus.Debugf("error logging into %q: %v", server, unauthorizedError) - return errors.Errorf("error logging into %q: invalid username/password", server) - } - return errors.Wrapf(err, "error authenticating creds for %q", server) -} - -// getUserAndPass gets the username and password from STDIN if not given -// using the -u and -p flags. If the username prompt is left empty, the -// displayed userFromAuthFile will be used instead. -func getUserAndPass(username, password, userFromAuthFile string) (string, string, error) { - var err error - reader := bufio.NewReader(os.Stdin) - if username == "" { - if userFromAuthFile != "" { - fmt.Printf("Username (%s): ", userFromAuthFile) - } else { - fmt.Print("Username: ") - } - username, err = reader.ReadString('\n') - if err != nil { - return "", "", errors.Wrapf(err, "error reading username") - } - // If the user just hit enter, use the displayed user from the - // the authentication file. This allows to do a lazy - // `$ podman login -p $NEW_PASSWORD` without specifying the - // user. - if strings.TrimSpace(username) == "" { - username = userFromAuthFile - } - } - if password == "" { - fmt.Print("Password: ") - pass, err := terminal.ReadPassword(0) - if err != nil { - return "", "", errors.Wrapf(err, "error reading password") - } - password = string(pass) - fmt.Println() - } - return strings.TrimSpace(username), password, err -} - -// registryFromFullName gets the registry from the input. If the input is of the form -// quay.io/myuser/myimage, it will parse it and just return quay.io -// It also returns true if a full image name was given -func registryFromFullName(input string) string { - split := strings.Split(input, "/") - if len(split) > 1 { - return split[0] - } - return split[0] -} diff --git a/cmd/podman/logout.go b/cmd/podman/logout.go deleted file mode 100644 index a541438c3..000000000 --- a/cmd/podman/logout.go +++ /dev/null @@ -1,104 +0,0 @@ -package main - -import ( - "fmt" - - buildahcli "github.com/containers/buildah/pkg/cli" - "github.com/containers/image/v5/docker" - "github.com/containers/image/v5/pkg/docker/config" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/pkg/registries" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - logoutCommand cliconfig.LogoutValues - logoutDescription = "Remove the cached username and password for the registry." - _logoutCommand = &cobra.Command{ - Use: "logout [flags] REGISTRY", - Short: "Logout of a container registry", - Long: logoutDescription, - RunE: func(cmd *cobra.Command, args []string) error { - logoutCommand.InputArgs = args - logoutCommand.GlobalFlags = MainGlobalOpts - logoutCommand.Remote = remoteclient - return logoutCmd(&logoutCommand) - }, - Example: `podman logout quay.io - podman logout --all`, - } -) - -func init() { - if !remote { - _logoutCommand.Example = fmt.Sprintf("%s\n podman logout --authfile authdir/myauths.json quay.io", _logoutCommand.Example) - - } - logoutCommand.Command = _logoutCommand - logoutCommand.SetHelpTemplate(HelpTemplate()) - logoutCommand.SetUsageTemplate(UsageTemplate()) - flags := logoutCommand.Flags() - flags.BoolVarP(&logoutCommand.All, "all", "a", false, "Remove the cached credentials for all registries in the auth file") - flags.StringVar(&logoutCommand.Authfile, "authfile", buildahcli.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") - markFlagHiddenForRemoteClient("authfile", flags) -} - -// logoutCmd uses the authentication package to remove the authenticated of a registry -// stored in the auth.json file -func logoutCmd(c *cliconfig.LogoutValues) error { - args := c.InputArgs - if len(args) > 1 { - return errors.Errorf("too many arguments, logout takes at most 1 argument") - } - var server string - if len(args) == 0 && !c.All { - registriesFromFile, err := registries.GetRegistries() - if err != nil || len(registriesFromFile) == 0 { - return errors.Errorf("no registries found in registries.conf, a registry must be provided") - } - - server = registriesFromFile[0] - logrus.Debugf("registry not specified, default to the first registry %q from registries.conf", server) - } - if len(args) == 1 { - server = scrubServer(args[0]) - } - - sc, err := shared.GetSystemContext(c.Authfile) - if err != nil { - return err - } - - if c.All { - if err := config.RemoveAllAuthentication(sc); err != nil { - return err - } - fmt.Println("Removed login credentials for all registries") - return nil - } - - err = config.RemoveAuthentication(sc, server) - switch errors.Cause(err) { - case nil: - fmt.Printf("Removed login credentials for %s\n", server) - return nil - case config.ErrNotLoggedIn: - // username of user logged in to server (if one exists) - authConfig, err := config.GetCredentials(sc, server) - if err != nil { - return errors.Wrapf(err, "error reading auth file") - } - islogin := docker.CheckAuth(getContext(), sc, authConfig.Username, authConfig.Password, server) - if authConfig.IdentityToken != "" && authConfig.Username != "" && authConfig.Password != "" && islogin == nil { - fmt.Printf("Not logged into %s with podman. Existing credentials were established via docker login. Please use docker logout instead.\n", server) - return nil - } - fmt.Printf("Not logged into %s\n", server) - return nil - default: - return errors.Wrapf(err, "error logging out of %q", server) - } -} diff --git a/cmd/podman/logs.go b/cmd/podman/logs.go deleted file mode 100644 index 0a86fa128..000000000 --- a/cmd/podman/logs.go +++ /dev/null @@ -1,93 +0,0 @@ -package main - -import ( - "time" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod/logs" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/util" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - logsCommand cliconfig.LogsValues - logsDescription = `Retrieves logs for one or more containers. - - 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 = &cobra.Command{ - Use: "logs [flags] CONTAINER [CONTAINER...]", - Short: "Fetch the logs of a container", - Long: logsDescription, - RunE: func(cmd *cobra.Command, args []string) error { - logsCommand.InputArgs = args - logsCommand.GlobalFlags = MainGlobalOpts - logsCommand.Remote = remoteclient - return logsCmd(&logsCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) > 0 && logsCommand.Latest { - return errors.New("no containers can be specified when using 'latest'") - } - if !logsCommand.Latest && len(args) < 1 { - return errors.New("specify at least one container name or ID to log") - } - return nil - }, - Example: `podman logs ctrID - podman logs --names ctrID1 ctrID2 - podman logs --tail 2 mywebserver - podman logs --follow=true --since 10m ctrID - podman logs mywebserver mydbserver`, - } -) - -func init() { - logsCommand.Command = _logsCommand - logsCommand.SetHelpTemplate(HelpTemplate()) - logsCommand.SetUsageTemplate(UsageTemplate()) - flags := logsCommand.Flags() - flags.BoolVar(&logsCommand.Details, "details", false, "Show extra details provided to the logs") - flags.BoolVarP(&logsCommand.Follow, "follow", "f", false, "Follow log output. The default is false") - flags.BoolVarP(&logsCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - flags.StringVar(&logsCommand.Since, "since", "", "Show logs since TIMESTAMP") - flags.Int64Var(&logsCommand.Tail, "tail", -1, "Output the specified number of LINES at the end of the logs. Defaults to -1, which prints all lines") - flags.BoolVarP(&logsCommand.Timestamps, "timestamps", "t", false, "Output the timestamps in the log") - flags.BoolVarP(&logsCommand.UseName, "names", "n", false, "Output the container name in the log") - markFlagHidden(flags, "details") - flags.SetInterspersed(false) - - markFlagHiddenForRemoteClient("latest", flags) -} - -func logsCmd(c *cliconfig.LogsValues) error { - var err error - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - sinceTime := time.Time{} - if c.Flag("since").Changed { - // parse time, error out if something is wrong - since, err := util.ParseInputTime(c.Since) - if err != nil { - return errors.Wrapf(err, "could not parse time: %q", c.Since) - } - sinceTime = since - } - - options := &logs.LogOptions{ - Details: c.Details, - Follow: c.Follow, - Since: sinceTime, - Tail: c.Tail, - Timestamps: c.Timestamps, - UseName: c.UseName, - } - return runtime.Log(c, options) -} diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 5134448da..2d9e45177 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -1,190 +1,45 @@ package main import ( - "context" - "io" "os" - "path" - "github.com/containers/common/pkg/config" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" - _ "github.com/containers/libpod/pkg/hooks/0.1.0" - "github.com/containers/libpod/pkg/rootless" - "github.com/containers/libpod/version" + _ "github.com/containers/libpod/cmd/podman/containers" + _ "github.com/containers/libpod/cmd/podman/healthcheck" + _ "github.com/containers/libpod/cmd/podman/images" + _ "github.com/containers/libpod/cmd/podman/networks" + _ "github.com/containers/libpod/cmd/podman/pods" + "github.com/containers/libpod/cmd/podman/registry" + _ "github.com/containers/libpod/cmd/podman/system" + _ "github.com/containers/libpod/cmd/podman/volumes" "github.com/containers/storage/pkg/reexec" - "github.com/opentracing/opentracing-go" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" ) -// This is populated by the Makefile from the VERSION file -// in the repository -var ( - exitCode = define.ExecErrorCodeGeneric - Ctx context.Context - span opentracing.Span - closer io.Closer -) - -// Commands that the remote and local client have -// implemented. -var mainCommands = []*cobra.Command{ - _attachCommand, - _buildCommand, - _commitCommand, - _diffCommand, - _createCommand, - _eventsCommand, - _execCommand, - _exportCommand, - _generateCommand, - _historyCommand, - &_imagesCommand, - _importCommand, - _infoCommand, - _initCommand, - &_inspectCommand, - _killCommand, - _loadCommand, - _logsCommand, - _pauseCommand, - podCommand.Command, - _portCommand, - &_psCommand, - _pullCommand, - _pushCommand, - _restartCommand, - _rmCommand, - &_rmiCommand, - _runCommand, - _saveCommand, - _stopCommand, - _tagCommand, - _topCommand, - _unpauseCommand, - _versionCommand, - _waitCommand, - imageCommand.Command, - _startCommand, - systemCommand.Command, - _untagCommand, -} - -var rootCmd = &cobra.Command{ - Use: path.Base(os.Args[0]), - Long: "manage pods and images", - RunE: commandRunE(), - PersistentPreRunE: before, - PersistentPostRunE: after, - SilenceUsage: true, - SilenceErrors: true, -} - -var ( - MainGlobalOpts cliconfig.MainFlags - defaultContainerConfig = getDefaultContainerConfig() -) - -func initCobra() { - cobra.OnInitialize(initConfig) - rootCmd.TraverseChildren = true - rootCmd.Version = version.Version - // Override default --help information of `--version` global flag - var dummyVersion bool - rootCmd.Flags().BoolVarP(&dummyVersion, "version", "v", false, "Version of podman") - rootCmd.AddCommand(mainCommands...) - rootCmd.AddCommand(getMainCommands()...) -} - -func init() { - if err := libpod.SetXdgDirs(); err != nil { - logrus.Errorf(err.Error()) - os.Exit(1) - } - initBuild() - initCobra() -} - -func initConfig() { - // we can do more stuff in here. -} - -func before(cmd *cobra.Command, args []string) error { - // Set log level; if not log-level is provided, default to error - logLevel := MainGlobalOpts.LogLevel - if logLevel == "" { - logLevel = "error" - } - level, err := logrus.ParseLevel(logLevel) - if err != nil { - return err - } - logrus.SetLevel(level) - if err := setSyslog(); err != nil { - return err - } - - if err := setupRootless(cmd, args); err != nil { - return err - } - - // check that global opts input is valid - if err := checkInput(); err != nil { - return err - } - - if err := setRLimits(); err != nil { - return err - } - if rootless.IsRootless() { - logrus.Info("running as rootless") - } - setUMask() - - return profileOn(cmd) -} - -func after(cmd *cobra.Command, args []string) error { - return profileOff(cmd) -} - func main() { - //debug := false - //cpuProfile := false - if reexec.Init() { // We were invoked with a different argv[0] indicating that we // had a specific job to do as a subprocess, and it's done. return } - // Hard code TMPDIR functions to use /var/tmp, if user did not override - if _, ok := os.LookupEnv("TMPDIR"); !ok { - os.Setenv("TMPDIR", "/var/tmp") - } - if err := rootCmd.Execute(); err != nil { - outputError(err) - } else if exitCode == define.ExecErrorCodeGeneric { - // The exitCode modified from define.ExecErrorCodeGeneric, - // indicates an application - // running inside of a container failed, as opposed to the - // podman command failed. Must exit with that exit code - // otherwise command exited correctly. - exitCode = 0 - } - // Check if /etc/containers/registries.conf exists when running in - // in a local environment. - CheckForRegistries() - os.Exit(exitCode) -} + cfg := registry.PodmanConfig() + for _, c := range registry.Commands { + for _, m := range c.Mode { + if cfg.EngineMode == m { + parent := rootCmd + if c.Parent != nil { + parent = c.Parent + } + parent.AddCommand(c.Command) -func getDefaultContainerConfig() *config.Config { - defaultContainerConfig, err := config.Default() - if err != nil { - logrus.Error(err) - os.Exit(1) + // - templates need to be set here, as PersistentPreRunE() is + // not called when --help is used. + // - rootCmd uses cobra default template not ours + c.Command.SetHelpTemplate(helpTemplate) + c.Command.SetUsageTemplate(usageTemplate) + } + } } - return defaultContainerConfig + + Execute() + os.Exit(0) } diff --git a/cmd/podman/main_local.go b/cmd/podman/main_local.go deleted file mode 100644 index 23b3f5ae7..000000000 --- a/cmd/podman/main_local.go +++ /dev/null @@ -1,277 +0,0 @@ -// +build !remoteclient -// +build linux - -package main - -import ( - "context" - "fmt" - "io/ioutil" - "log/syslog" - "os" - "runtime/pprof" - "strconv" - "strings" - "syscall" - - "github.com/containers/common/pkg/config" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/pkg/cgroups" - "github.com/containers/libpod/pkg/rootless" - "github.com/containers/libpod/pkg/tracing" - "github.com/containers/libpod/pkg/util" - "github.com/containers/libpod/utils" - "github.com/opentracing/opentracing-go" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - lsyslog "github.com/sirupsen/logrus/hooks/syslog" - "github.com/spf13/cobra" -) - -const remote = false - -func init() { - cgroupManager := defaultContainerConfig.Engine.CgroupManager - cgroupHelp := `Cgroup manager to use ("cgroupfs"|"systemd")` - cgroupv2, _ := cgroups.IsCgroup2UnifiedMode() - - defaultContainerConfig = cliconfig.GetDefaultConfig() - if rootless.IsRootless() && !cgroupv2 { - cgroupManager = "" - cgroupHelp = "Cgroup manager is not supported in rootless mode" - } - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.CGroupManager, "cgroup-manager", cgroupManager, cgroupHelp) - // -c is deprecated due to conflict with -c on subcommands - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.CpuProfile, "cpu-profile", "", "Path for the cpu profiling results") - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.ConmonPath, "conmon", "", "Path of the conmon binary") - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.NetworkCmdPath, "network-cmd-path", defaultContainerConfig.Engine.NetworkCmdPath, "Path to the command for configuring the network") - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.CniConfigDir, "cni-config-dir", getCNIPluginsDir(), "Path of the configuration directory for CNI networks") - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.DefaultMountsFile, "default-mounts-file", defaultContainerConfig.Containers.DefaultMountsFile, "Path to default mounts file") - if err := rootCmd.PersistentFlags().MarkHidden("cpu-profile"); err != nil { - logrus.Error("unable to mark default-mounts-file flag as hidden") - } - if err := rootCmd.PersistentFlags().MarkHidden("default-mounts-file"); err != nil { - logrus.Error("unable to mark default-mounts-file flag as hidden") - } - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.EventsBackend, "events-backend", defaultContainerConfig.Engine.EventsLogger, `Events backend to use ("file"|"journald"|"none")`) - // Override default --help information of `--help` global flag - var dummyHelp bool - rootCmd.PersistentFlags().BoolVar(&dummyHelp, "help", false, "Help for podman") - rootCmd.PersistentFlags().StringSliceVar(&MainGlobalOpts.HooksDir, "hooks-dir", defaultContainerConfig.Engine.HooksDir, "Set the OCI hooks directory path (may be set multiple times)") - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.LogLevel, "log-level", "error", `Log messages above specified level ("debug"|"info"|"warn"|"error"|"fatal"|"panic")`) - rootCmd.PersistentFlags().IntVar(&MainGlobalOpts.MaxWorks, "max-workers", 0, "The maximum number of workers for parallel operations") - if err := rootCmd.PersistentFlags().MarkHidden("max-workers"); err != nil { - logrus.Error("unable to mark max-workers flag as hidden") - } - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.Namespace, "namespace", defaultContainerConfig.Engine.Namespace, "Set the libpod namespace, used to create separate views of the containers and pods on the system") - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.Root, "root", "", "Path to the root directory in which data, including images, is stored") - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.Runroot, "runroot", "", "Path to the 'run directory' where all state information is stored") - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.Runtime, "runtime", "", "Path to the OCI-compatible binary used to run containers, default is /usr/bin/runc") - // -s is deprecated due to conflict with -s on subcommands - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.StorageDriver, "storage-driver", "", "Select which storage driver is used to manage storage of images and containers (default is overlay)") - rootCmd.PersistentFlags().StringArrayVar(&MainGlobalOpts.StorageOpts, "storage-opt", []string{}, "Used to pass an option to the storage driver") - rootCmd.PersistentFlags().BoolVar(&MainGlobalOpts.Syslog, "syslog", false, "Output logging information to syslog as well as the console (default false)") - - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.TmpDir, "tmpdir", "", "Path to the tmp directory for libpod state content.\n\nNote: use the environment variable 'TMPDIR' to change the temporary storage location for container images, '/var/tmp'.\n") - rootCmd.PersistentFlags().BoolVar(&MainGlobalOpts.Trace, "trace", false, "Enable opentracing output (default false)") - markFlagHidden(rootCmd.PersistentFlags(), "trace") -} - -func setSyslog() error { - if MainGlobalOpts.Syslog { - hook, err := lsyslog.NewSyslogHook("", "", syslog.LOG_INFO, "") - if err == nil { - logrus.AddHook(hook) - return nil - } - return err - } - return nil -} - -func profileOn(cmd *cobra.Command) error { - if cmd.Flag("cpu-profile").Changed { - f, err := os.Create(MainGlobalOpts.CpuProfile) - if err != nil { - return errors.Wrapf(err, "unable to create cpu profiling file %s", - MainGlobalOpts.CpuProfile) - } - if err := pprof.StartCPUProfile(f); err != nil { - return err - } - } - - if cmd.Flag("trace").Changed { - var tracer opentracing.Tracer - tracer, closer = tracing.Init("podman") - opentracing.SetGlobalTracer(tracer) - - span = tracer.StartSpan("before-context") - - Ctx = opentracing.ContextWithSpan(context.Background(), span) - } - return nil -} - -func profileOff(cmd *cobra.Command) error { - if cmd.Flag("cpu-profile").Changed { - pprof.StopCPUProfile() - } - if cmd.Flag("trace").Changed { - span.Finish() - closer.Close() - } - return nil -} - -func movePauseProcessToScope() error { - pausePidPath, err := util.GetRootlessPauseProcessPidPath() - if err != nil { - return errors.Wrapf(err, "could not get pause process pid file path") - } - - data, err := ioutil.ReadFile(pausePidPath) - if err != nil { - return errors.Wrapf(err, "cannot read pause pid file") - } - pid, err := strconv.ParseUint(string(data), 10, 0) - if err != nil { - return errors.Wrapf(err, "cannot parse pid file %s", pausePidPath) - } - - return utils.RunUnderSystemdScope(int(pid), "user.slice", "podman-pause.scope") -} - -func setupRootless(cmd *cobra.Command, args []string) error { - if !rootless.IsRootless() { - return nil - } - - matches, err := rootless.ConfigurationMatches() - if err != nil { - return err - } - if !matches { - logrus.Warningf("the current user namespace doesn't match the configuration in /etc/subuid or /etc/subgid") - logrus.Warningf("you can use `%s system migrate` to recreate the user namespace and restart the containers", os.Args[0]) - } - - podmanCmd := cliconfig.PodmanCommand{ - Command: cmd, - InputArgs: args, - GlobalFlags: MainGlobalOpts, - Remote: remoteclient, - } - - runtime, err := libpodruntime.GetRuntimeNoStore(getContext(), &podmanCmd) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - // do it only after podman has already re-execed and running with uid==0. - if os.Geteuid() == 0 { - ownsCgroup, err := cgroups.UserOwnsCurrentSystemdCgroup() - if err != nil { - logrus.Warnf("Failed to detect the owner for the current cgroup: %v", err) - } - if !ownsCgroup { - conf, err := runtime.GetConfig() - if err != nil { - return err - } - unitName := fmt.Sprintf("podman-%d.scope", os.Getpid()) - if err := utils.RunUnderSystemdScope(os.Getpid(), "user.slice", unitName); err != nil { - if conf.Engine.CgroupManager == config.SystemdCgroupsManager { - logrus.Warnf("Failed to add podman to systemd sandbox cgroup: %v", err) - } else { - logrus.Debugf("Failed to add podman to systemd sandbox cgroup: %v", err) - } - } - } - } - - if os.Geteuid() == 0 || cmd == _searchCommand || cmd == _versionCommand || cmd == _mountCommand || cmd == _migrateCommand || strings.HasPrefix(cmd.Use, "help") { - return nil - } - - pausePidPath, err := util.GetRootlessPauseProcessPidPath() - if err != nil { - return errors.Wrapf(err, "could not get pause process pid file path") - } - - became, ret, err := rootless.TryJoinPauseProcess(pausePidPath) - if err != nil { - return err - } - if became { - os.Exit(ret) - } - - // if there is no pid file, try to join existing containers, and create a pause process. - ctrs, err := runtime.GetRunningContainers() - if err != nil { - logrus.Errorf(err.Error()) - os.Exit(1) - } - - paths := []string{} - for _, ctr := range ctrs { - paths = append(paths, ctr.Config().ConmonPidFile) - } - - became, ret, err = rootless.TryJoinFromFilePaths(pausePidPath, true, paths) - if err := movePauseProcessToScope(); err != nil { - conf, err := runtime.GetConfig() - if err != nil { - return err - } - if conf.Engine.CgroupManager == config.SystemdCgroupsManager { - logrus.Warnf("Failed to add pause process to systemd sandbox cgroup: %v", err) - } else { - logrus.Debugf("Failed to add pause process to systemd sandbox cgroup: %v", err) - } - } - if err != nil { - logrus.Errorf(err.Error()) - os.Exit(1) - } - if became { - os.Exit(ret) - } - return nil -} - -func setRLimits() error { - rlimits := new(syscall.Rlimit) - rlimits.Cur = 1048576 - rlimits.Max = 1048576 - if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { - if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { - return errors.Wrapf(err, "error getting rlimits") - } - rlimits.Cur = rlimits.Max - if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { - return errors.Wrapf(err, "error setting new rlimits") - } - } - return nil -} - -func setUMask() { - // Be sure we can create directories with 0755 mode. - syscall.Umask(0022) -} - -// checkInput can be used to verify any of the globalopt values -func checkInput() error { - return nil -} -func getCNIPluginsDir() string { - if rootless.IsRootless() { - return "" - } - - return defaultContainerConfig.Network.CNIPluginDirs[0] -} diff --git a/cmd/podman/main_local_unsupported.go b/cmd/podman/main_local_unsupported.go deleted file mode 100644 index 75728627e..000000000 --- a/cmd/podman/main_local_unsupported.go +++ /dev/null @@ -1,44 +0,0 @@ -// +build !remoteclient,!linux - -package main - -// The ONLY purpose of this file is to allow the subpackage to compile. Don’t expect anything -// to work. - -import ( - "syscall" - - "github.com/spf13/cobra" -) - -const remote = false - -func setSyslog() error { - return nil -} - -func profileOn(cmd *cobra.Command) error { - return nil -} - -func profileOff(cmd *cobra.Command) error { - return nil -} - -func setupRootless(cmd *cobra.Command, args []string) error { - return nil -} - -func setRLimits() error { - return nil -} - -func setUMask() { - // Be sure we can create directories with 0755 mode. - syscall.Umask(0022) -} - -// checkInput can be used to verify any of the globalopt values -func checkInput() error { - return nil -} diff --git a/cmd/podman/main_remote.go b/cmd/podman/main_remote.go deleted file mode 100644 index 623f4098e..000000000 --- a/cmd/podman/main_remote.go +++ /dev/null @@ -1,74 +0,0 @@ -// +build remoteclient - -package main - -import ( - "os" - "os/user" - "strconv" - - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -const remote = true - -func init() { - var username string - if username = os.Getenv("PODMAN_USER"); username == "" { - if curruser, err := user.Current(); err == nil { - username = curruser.Username - } - } - host := os.Getenv("PODMAN_HOST") - port := 22 - if portstr := os.Getenv("PODMAN_PORT"); portstr != "" { - if p, err := strconv.Atoi(portstr); err == nil { - port = p - } - } - key := os.Getenv("PODMAN_IDENTITY_FILE") - ignore := false - if ignorestr := os.Getenv("PODMAN_IGNORE_HOSTS"); ignorestr != "" { - if b, err := strconv.ParseBool(ignorestr); err == nil { - ignore = b - } - } - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.ConnectionName, "connection", "", "remote connection name") - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.RemoteConfigFilePath, "remote-config-path", "", "alternate path for configuration file") - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.RemoteUserName, "username", username, "username on the remote host") - rootCmd.PersistentFlags().IntVar(&MainGlobalOpts.Port, "port", port, "port on remote host") - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.RemoteHost, "remote-host", host, "remote host") - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.IdentityFile, "identity-file", key, "identity-file") - rootCmd.PersistentFlags().BoolVar(&MainGlobalOpts.IgnoreHosts, "ignore-hosts", ignore, "ignore hosts") - // TODO maybe we allow the altering of this for bridge connections? - // rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.VarlinkAddress, "varlink-address", adapter.DefaultAddress, "address of the varlink socket") - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.LogLevel, "log-level", "error", "Log messages above specified level: debug, info, warn, error, fatal or panic. Logged to ~/.config/containers/podman.log") - rootCmd.PersistentFlags().BoolVar(&MainGlobalOpts.Syslog, "syslog", false, "Output logging information to syslog as well as the console") -} - -func profileOn(cmd *cobra.Command) error { - return nil -} - -func profileOff(cmd *cobra.Command) error { - return nil -} - -func setupRootless(cmd *cobra.Command, args []string) error { - return nil -} - -func setRLimits() error { - return nil -} - -func setUMask() {} - -// checkInput can be used to verify any of the globalopt values -func checkInput() error { - if MainGlobalOpts.Port < 0 || MainGlobalOpts.Port > 65536 { - return errors.Errorf("remote port must be between 0 and 65536") - } - return nil -} diff --git a/cmd/podman/main_remote_supported.go b/cmd/podman/main_remote_supported.go deleted file mode 100644 index bb567c273..000000000 --- a/cmd/podman/main_remote_supported.go +++ /dev/null @@ -1,57 +0,0 @@ -// +build remoteclient -// +build linux darwin - -package main - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/containers/libpod/pkg/util" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -func setSyslog() error { - var err error - cfgHomeDir := os.Getenv("XDG_CONFIG_HOME") - if cfgHomeDir == "" { - if cfgHomeDir, err = util.GetRootlessConfigHomeDir(); err != nil { - return err - } - if err = os.Setenv("XDG_CONFIG_HOME", cfgHomeDir); err != nil { - return errors.Wrapf(err, "cannot set XDG_CONFIG_HOME") - } - } - path := filepath.Join(cfgHomeDir, "containers") - - // Log to file if not using syslog - - if _, err := os.Stat(path); os.IsNotExist(err) { - if err := os.MkdirAll(path, 0750); err != nil { - fmt.Fprintf(os.Stderr, "%v", err) - return err - } - } - - // Update path to include file name - path = filepath.Join(path, "podman.log") - - // Create the log file if doesn't exist. And append to it if it already exists. - file, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0640) - if err != nil { - // Cannot open log file. Logging to stderr - fmt.Fprintf(os.Stderr, "%v", err) - return err - } else { - formatter := new(logrus.TextFormatter) - formatter.FullTimestamp = true - logrus.SetFormatter(formatter) - logrus.SetOutput(file) - } - - // Note this message is only logged if --log-level >= Info! - logrus.Infof("Logging level set to %s", logrus.GetLevel().String()) - return nil -} diff --git a/cmd/podman/main_remote_windows.go b/cmd/podman/main_remote_windows.go deleted file mode 100644 index 0ef1370ce..000000000 --- a/cmd/podman/main_remote_windows.go +++ /dev/null @@ -1,7 +0,0 @@ -// +build remoteclient,windows - -package main - -func setSyslog() error { - return nil -} diff --git a/cmd/podman/mount.go b/cmd/podman/mount.go deleted file mode 100644 index 99e185589..000000000 --- a/cmd/podman/mount.go +++ /dev/null @@ -1,165 +0,0 @@ -package main - -import ( - js "encoding/json" - "fmt" - "os" - - of "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/pkg/rootless" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - mountCommand cliconfig.MountValues - - mountDescription = `podman mount - Lists all mounted containers mount points if no container is specified - - podman mount CONTAINER-NAME-OR-ID - Mounts the specified container and outputs the mountpoint -` - - _mountCommand = &cobra.Command{ - Use: "mount [flags] [CONTAINER]", - Short: "Mount a working container's root filesystem", - Long: mountDescription, - RunE: func(cmd *cobra.Command, args []string) error { - mountCommand.InputArgs = args - mountCommand.GlobalFlags = MainGlobalOpts - mountCommand.Remote = remoteclient - return mountCmd(&mountCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllLatestAndCIDFile(cmd, args, true, false) - }, - } -) - -func init() { - mountCommand.Command = _mountCommand - mountCommand.SetHelpTemplate(HelpTemplate()) - mountCommand.SetUsageTemplate(UsageTemplate()) - flags := mountCommand.Flags() - flags.BoolVarP(&mountCommand.All, "all", "a", false, "Mount all containers") - flags.StringVar(&mountCommand.Format, "format", "", "Change the output format to Go template") - flags.BoolVarP(&mountCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - flags.BoolVar(&mountCommand.NoTrunc, "notruncate", false, "Do not truncate output") - - markFlagHiddenForRemoteClient("latest", flags) -} - -// jsonMountPoint stores info about each container -type jsonMountPoint struct { - ID string `json:"id"` - Names []string `json:"names"` - MountPoint string `json:"mountpoint"` -} - -func mountCmd(c *cliconfig.MountValues) error { - runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - if os.Geteuid() != 0 { - if driver := runtime.StorageConfig().GraphDriverName; driver != "vfs" { - // Do not allow to mount a graphdriver that is not vfs if we are creating the userns as part - // of the mount command. - return fmt.Errorf("cannot mount using driver %s in rootless mode", driver) - } - - became, ret, err := rootless.BecomeRootInUserNS("") - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } - - if c.All && c.Latest { - return errors.Errorf("--all and --latest cannot be used together") - } - - mountContainers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, -1, "all") - if err != nil { - if len(mountContainers) == 0 { - return err - } - fmt.Println(err.Error()) - } - - formats := map[string]bool{ - "": true, - of.JSONString: true, - } - - json := c.Format == of.JSONString - if !formats[c.Format] { - return errors.Errorf("%q is not a supported format", c.Format) - } - - var lastError error - if len(mountContainers) > 0 { - for _, ctr := range mountContainers { - if json { - if lastError != nil { - logrus.Error(lastError) - } - lastError = errors.Wrapf(err, "json option cannot be used with a container id") - continue - } - mountPoint, err := ctr.Mount() - if err != nil { - if lastError != nil { - logrus.Error(lastError) - } - lastError = errors.Wrapf(err, "error mounting container %q", ctr.ID()) - continue - } - fmt.Printf("%s\n", mountPoint) - } - return lastError - } else { - jsonMountPoints := []jsonMountPoint{} - containers, err2 := runtime.GetContainers() - if err2 != nil { - return errors.Wrapf(err2, "error reading list of all containers") - } - for _, container := range containers { - mounted, mountPoint, err := container.Mounted() - if err != nil { - return errors.Wrapf(err, "error getting mountpoint for %q", container.ID()) - } - - if !mounted { - continue - } - - if json { - jsonMountPoints = append(jsonMountPoints, jsonMountPoint{ID: container.ID(), Names: []string{container.Name()}, MountPoint: mountPoint}) - continue - } - - if c.NoTrunc { - fmt.Printf("%-64s %s\n", container.ID(), mountPoint) - } else { - fmt.Printf("%-12.12s %s\n", container.ID(), mountPoint) - } - } - if json { - data, err := js.MarshalIndent(jsonMountPoints, "", " ") - if err != nil { - return err - } - fmt.Printf("%s\n", data) - } - } - return nil -} diff --git a/cmd/podman/network.go b/cmd/podman/network.go deleted file mode 100644 index 702593e5c..000000000 --- a/cmd/podman/network.go +++ /dev/null @@ -1,31 +0,0 @@ -//+build !remoteclient - -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/spf13/cobra" -) - -var networkcheckDescription = "Manage networks" -var networkcheckCommand = cliconfig.PodmanCommand{ - Command: &cobra.Command{ - Use: "network", - Short: "Manage Networks", - Long: networkcheckDescription, - RunE: commandRunE(), - }, -} - -var networkcheckCommands = []*cobra.Command{ - _networkCreateCommand, - _networkinspectCommand, - _networklistCommand, - _networkrmCommand, -} - -func init() { - networkcheckCommand.AddCommand(networkcheckCommands...) - networkcheckCommand.SetUsageTemplate(UsageTemplate()) - rootCmd.AddCommand(networkcheckCommand.Command) -} diff --git a/cmd/podman/network_create.go b/cmd/podman/network_create.go deleted file mode 100644 index 886607885..000000000 --- a/cmd/podman/network_create.go +++ /dev/null @@ -1,83 +0,0 @@ -// +build !remoteclient - -package main - -import ( - "fmt" - "net" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/network" - "github.com/containers/libpod/pkg/rootless" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - networkCreateCommand cliconfig.NetworkCreateValues - networkCreateDescription = `create CNI networks for containers and pods` - _networkCreateCommand = &cobra.Command{ - Use: "create [flags] [NETWORK]", - Short: "network create", - Long: networkCreateDescription, - RunE: func(cmd *cobra.Command, args []string) error { - networkCreateCommand.InputArgs = args - networkCreateCommand.GlobalFlags = MainGlobalOpts - networkCreateCommand.Remote = remoteclient - return networkcreateCmd(&networkCreateCommand) - }, - Example: `podman network create podman1`, - } -) - -func init() { - networkCreateCommand.Command = _networkCreateCommand - networkCreateCommand.SetHelpTemplate(HelpTemplate()) - networkCreateCommand.SetUsageTemplate(UsageTemplate()) - flags := networkCreateCommand.Flags() - flags.StringVarP(&networkCreateCommand.Driver, "driver", "d", "bridge", "driver to manage the network") - flags.IPVar(&networkCreateCommand.Gateway, "gateway", nil, "IPv4 or IPv6 gateway for the subnet") - flags.BoolVar(&networkCreateCommand.Internal, "internal", false, "restrict external access from this network") - flags.IPNetVar(&networkCreateCommand.IPRange, "ip-range", net.IPNet{}, "allocate container IP from range") - flags.StringVar(&networkCreateCommand.MacVLAN, "macvlan", "", "create a Macvlan connection based on this device") - // TODO not supported yet - //flags.StringVar(&networkCreateCommand.IPamDriver, "ipam-driver", "", "IP Address Management Driver") - // TODO enable when IPv6 is working - //flags.BoolVar(&networkCreateCommand.IPV6, "IPv6", false, "enable IPv6 networking") - flags.IPNetVar(&networkCreateCommand.Network, "subnet", net.IPNet{}, "subnet in CIDR format") - flags.BoolVar(&networkCreateCommand.DisableDNS, "disable-dns", false, "disable dns plugin") -} - -func networkcreateCmd(c *cliconfig.NetworkCreateValues) error { - var ( - fileName string - err error - ) - if err := network.IsSupportedDriver(c.Driver); err != nil { - return err - } - if rootless.IsRootless() && !remoteclient { - return errors.New("network create is not supported for rootless mode") - } - if len(c.InputArgs) > 1 { - return errors.Errorf("only one network can be created at a time") - } - if len(c.InputArgs) > 0 && !libpod.NameRegex.MatchString(c.InputArgs[0]) { - return libpod.RegexError - } - runtime, err := adapter.GetRuntimeNoStore(getContext(), &c.PodmanCommand) - if err != nil { - return err - } - if len(c.MacVLAN) > 0 { - fileName, err = runtime.NetworkCreateMacVLAN(c) - } else { - fileName, err = runtime.NetworkCreateBridge(c) - } - if err == nil { - fmt.Println(fileName) - } - return err -} diff --git a/cmd/podman/network_inspect.go b/cmd/podman/network_inspect.go deleted file mode 100644 index 38aaf6ba4..000000000 --- a/cmd/podman/network_inspect.go +++ /dev/null @@ -1,48 +0,0 @@ -// +build !remoteclient - -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - networkinspectCommand cliconfig.NetworkInspectValues - networkinspectDescription = `Inspect network` - _networkinspectCommand = &cobra.Command{ - Use: "inspect NETWORK [NETWORK...] [flags] ", - Short: "network inspect", - Long: networkinspectDescription, - RunE: func(cmd *cobra.Command, args []string) error { - networkinspectCommand.InputArgs = args - networkinspectCommand.GlobalFlags = MainGlobalOpts - networkinspectCommand.Remote = remoteclient - return networkinspectCmd(&networkinspectCommand) - }, - Example: `podman network inspect podman`, - } -) - -func init() { - networkinspectCommand.Command = _networkinspectCommand - networkinspectCommand.SetHelpTemplate(HelpTemplate()) - networkinspectCommand.SetUsageTemplate(UsageTemplate()) -} - -func networkinspectCmd(c *cliconfig.NetworkInspectValues) error { - if rootless.IsRootless() && !remoteclient { - return errors.New("network inspect is not supported for rootless mode") - } - if len(c.InputArgs) < 1 { - return errors.Errorf("at least one network name is required") - } - runtime, err := adapter.GetRuntimeNoStore(getContext(), &c.PodmanCommand) - if err != nil { - return err - } - return runtime.NetworkInspect(c) -} diff --git a/cmd/podman/network_list.go b/cmd/podman/network_list.go deleted file mode 100644 index 4f2380067..000000000 --- a/cmd/podman/network_list.go +++ /dev/null @@ -1,54 +0,0 @@ -// +build !remoteclient - -package main - -import ( - "errors" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" - "github.com/spf13/cobra" -) - -var ( - networklistCommand cliconfig.NetworkListValues - networklistDescription = `List networks` - _networklistCommand = &cobra.Command{ - Use: "ls", - Args: noSubArgs, - Short: "network list", - Long: networklistDescription, - RunE: func(cmd *cobra.Command, args []string) error { - networklistCommand.InputArgs = args - networklistCommand.GlobalFlags = MainGlobalOpts - networklistCommand.Remote = remoteclient - return networklistCmd(&networklistCommand) - }, - Example: `podman network list`, - } -) - -func init() { - networklistCommand.Command = _networklistCommand - networklistCommand.SetHelpTemplate(HelpTemplate()) - networklistCommand.SetUsageTemplate(UsageTemplate()) - flags := networklistCommand.Flags() - // TODO enable filters based on something - //flags.StringSliceVarP(&networklistCommand.Filter, "filter", "f", []string{}, "Pause all running containers") - flags.BoolVarP(&networklistCommand.Quiet, "quiet", "q", false, "display only names") -} - -func networklistCmd(c *cliconfig.NetworkListValues) error { - if rootless.IsRootless() && !remoteclient { - return errors.New("network list is not supported for rootless mode") - } - if len(c.InputArgs) > 0 { - return errors.New("network list takes no arguments") - } - runtime, err := adapter.GetRuntimeNoStore(getContext(), &c.PodmanCommand) - if err != nil { - return err - } - return runtime.NetworkList(c) -} diff --git a/cmd/podman/network_rm.go b/cmd/podman/network_rm.go deleted file mode 100644 index 41e5dbdab..000000000 --- a/cmd/podman/network_rm.go +++ /dev/null @@ -1,62 +0,0 @@ -// +build !remoteclient - -package main - -import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - networkrmCommand cliconfig.NetworkRmValues - networkrmDescription = `Remove networks` - _networkrmCommand = &cobra.Command{ - Use: "rm [flags] NETWORK [NETWORK...]", - Short: "network rm", - Long: networkrmDescription, - RunE: func(cmd *cobra.Command, args []string) error { - networkrmCommand.InputArgs = args - networkrmCommand.GlobalFlags = MainGlobalOpts - networkrmCommand.Remote = remoteclient - return networkrmCmd(&networkrmCommand) - }, - Example: `podman network rm podman`, - } -) - -func init() { - networkrmCommand.Command = _networkrmCommand - networkrmCommand.SetHelpTemplate(HelpTemplate()) - networkrmCommand.SetUsageTemplate(UsageTemplate()) - flags := networkrmCommand.Flags() - flags.BoolVarP(&networkrmCommand.Force, "force", "f", false, "remove any containers using network") -} - -func networkrmCmd(c *cliconfig.NetworkRmValues) error { - if rootless.IsRootless() && !remoteclient { - return errors.New("network rm is not supported for rootless mode") - } - if len(c.InputArgs) < 1 { - return errors.Errorf("at least one network name is required") - } - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return err - } - deletes, rmErrors, lastErr := runtime.NetworkRemove(getContext(), c) - for _, d := range deletes { - fmt.Println(d) - } - // we only want to print errors if there is more - // than one - for network, removalErr := range rmErrors { - logrus.Errorf("unable to remove %q: %q", network, removalErr) - } - return lastErr -} diff --git a/cmd/podman/networks/network.go b/cmd/podman/networks/network.go new file mode 100644 index 000000000..3cee86bcc --- /dev/null +++ b/cmd/podman/networks/network.go @@ -0,0 +1,25 @@ +package images + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + // Command: podman _network_ + cmd = &cobra.Command{ + Use: "network", + Short: "Manage networks", + Long: "Manage networks", + TraverseChildren: true, + RunE: registry.SubCommandExists, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: cmd, + }) +} diff --git a/cmd/podman/parse/common.go b/cmd/podman/parse/common.go new file mode 100644 index 000000000..a5e9b4fc2 --- /dev/null +++ b/cmd/podman/parse/common.go @@ -0,0 +1,50 @@ +package parse + +import ( + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +// CheckAllLatestAndCIDFile checks that --all and --latest are used correctly. +// If cidfile is set, also check for the --cidfile flag. +func CheckAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool, cidfile bool) error { + argLen := len(args) + if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { + if !cidfile { + return errors.New("unable to lookup values for 'latest' or 'all'") + } else if c.Flags().Lookup("cidfile") == nil { + return errors.New("unable to lookup values for 'latest', 'all' or 'cidfile'") + } + } + + specifiedAll, _ := c.Flags().GetBool("all") + specifiedLatest, _ := c.Flags().GetBool("latest") + specifiedCIDFile := false + if cid, _ := c.Flags().GetStringArray("cidfile"); len(cid) > 0 { + specifiedCIDFile = true + } + + if specifiedCIDFile && (specifiedAll || specifiedLatest) { + return errors.Errorf("--all, --latest and --cidfile cannot be used together") + } else if specifiedAll && specifiedLatest { + return errors.Errorf("--all and --latest cannot be used together") + } + + if ignoreArgLen { + return nil + } + if (argLen > 0) && (specifiedAll || specifiedLatest) { + return errors.Errorf("no arguments are needed with --all or --latest") + } else if cidfile && (argLen > 0) && (specifiedAll || specifiedLatest || specifiedCIDFile) { + return errors.Errorf("no arguments are needed with --all, --latest or --cidfile") + } + + if specifiedCIDFile { + return nil + } + + if argLen < 1 && !specifiedAll && !specifiedLatest && !specifiedCIDFile { + return errors.Errorf("you must provide at least one name or id") + } + return nil +} diff --git a/cmd/podman/shared/parse/parse.go b/cmd/podman/parse/net.go index 03cda268c..03cda268c 100644 --- a/cmd/podman/shared/parse/parse.go +++ b/cmd/podman/parse/net.go diff --git a/cmd/podman/shared/parse/parse_test.go b/cmd/podman/parse/net_test.go index a6ddc2be9..a6ddc2be9 100644 --- a/cmd/podman/shared/parse/parse_test.go +++ b/cmd/podman/parse/net_test.go diff --git a/cmd/podman/pause.go b/cmd/podman/pause.go deleted file mode 100644 index 247a480e3..000000000 --- a/cmd/podman/pause.go +++ /dev/null @@ -1,70 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - pauseCommand cliconfig.PauseValues - pauseDescription = `Pauses one or more running containers. The container name or ID can be used.` - _pauseCommand = &cobra.Command{ - Use: "pause [flags] CONTAINER [CONTAINER...]", - Short: "Pause all the processes in one or more containers", - Long: pauseDescription, - RunE: func(cmd *cobra.Command, args []string) error { - pauseCommand.InputArgs = args - pauseCommand.GlobalFlags = MainGlobalOpts - pauseCommand.Remote = remoteclient - return pauseCmd(&pauseCommand) - }, - Example: `podman pause mywebserver - podman pause 860a4b23 - podman pause -a`, - } -) - -func init() { - pauseCommand.Command = _pauseCommand - pauseCommand.SetHelpTemplate(HelpTemplate()) - pauseCommand.SetUsageTemplate(UsageTemplate()) - flags := pauseCommand.Flags() - flags.BoolVarP(&pauseCommand.All, "all", "a", false, "Pause all running containers") - -} - -func pauseCmd(c *cliconfig.PauseValues) error { - if rootless.IsRootless() && !remoteclient { - return errors.New("pause is not supported for rootless containers") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - args := c.InputArgs - if len(args) < 1 && !c.All { - return errors.Errorf("you must provide at least one container name or id") - } - ok, failures, err := runtime.PauseContainers(getContext(), c) - if err != nil { - if errors.Cause(err) == define.ErrNoSuchCtr { - if len(c.InputArgs) > 1 { - exitCode = define.ExecErrorCodeGeneric - } else { - exitCode = 1 - } - } - return err - } - if len(failures) > 0 { - exitCode = define.ExecErrorCodeGeneric - } - return printCmdResults(ok, failures) -} diff --git a/cmd/podman/platform_linux.go b/cmd/podman/platform_linux.go deleted file mode 100644 index eb11867cc..000000000 --- a/cmd/podman/platform_linux.go +++ /dev/null @@ -1,29 +0,0 @@ -// +build linux - -package main - -import ( - "os" - "path/filepath" - - "github.com/containers/libpod/pkg/rootless" - "github.com/sirupsen/logrus" -) - -// userRegistriesFile is the path to the per user registry configuration file. -var userRegistriesFile = filepath.Join(os.Getenv("HOME"), ".config/containers/registries.conf") - -func CheckForRegistries() { - if _, err := os.Stat("/etc/containers/registries.conf"); err != nil { - if os.IsNotExist(err) { - // If it is running in rootless mode, also check the user configuration file - if rootless.IsRootless() { - if _, err := os.Stat(userRegistriesFile); err != nil { - logrus.Warnf("unable to find %s. some podman (image shortnames) commands may be limited", userRegistriesFile) - } - return - } - logrus.Warn("unable to find /etc/containers/registries.conf. some podman (image shortnames) commands may be limited") - } - } -} diff --git a/cmd/podman/platform_unsupported.go b/cmd/podman/platform_unsupported.go deleted file mode 100644 index f39eeaf63..000000000 --- a/cmd/podman/platform_unsupported.go +++ /dev/null @@ -1,6 +0,0 @@ -// +build !linux - -package main - -func CheckForRegistries() { -} diff --git a/cmd/podman/play.go b/cmd/podman/play.go deleted file mode 100644 index 95eae653e..000000000 --- a/cmd/podman/play.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/spf13/cobra" -) - -var ( - playCommand cliconfig.PodmanCommand - playDescription = "Play a pod and its containers from a structured file." - _playCommand = &cobra.Command{ - Use: "play", - Short: "Play a pod", - Long: playDescription, - RunE: commandRunE(), - } -) - -func init() { - playCommand.Command = _playCommand - playCommand.SetHelpTemplate(HelpTemplate()) - playCommand.SetUsageTemplate(UsageTemplate()) - playCommand.AddCommand(getPlaySubCommands()...) -} diff --git a/cmd/podman/play_kube.go b/cmd/podman/play_kube.go deleted file mode 100644 index 2028d2ef4..000000000 --- a/cmd/podman/play_kube.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "fmt" - "os" - - buildahcli "github.com/containers/buildah/pkg/cli" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - playKubeCommand cliconfig.KubePlayValues - playKubeDescription = `Command reads in a structured file of Kubernetes YAML. - - It creates the pod and containers described in the YAML. The containers within the pod are then started and the ID of the new Pod is output.` - _playKubeCommand = &cobra.Command{ - Use: "kube [flags] KUBEFILE", - Short: "Play a pod based on Kubernetes YAML", - Long: playKubeDescription, - RunE: func(cmd *cobra.Command, args []string) error { - playKubeCommand.InputArgs = args - playKubeCommand.GlobalFlags = MainGlobalOpts - playKubeCommand.Remote = remoteclient - return playKubeCmd(&playKubeCommand) - }, - Example: `podman play kube demo.yml`, - } - // https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/ - defaultSeccompRoot = "/var/lib/kubelet/seccomp" -) - -func init() { - if !remote { - _playKubeCommand.Example = fmt.Sprintf("%s\n podman play kube --cert-dir /mycertsdir --tls-verify=true --quiet myWebPod", _playKubeCommand.Example) - } - playKubeCommand.Command = _playKubeCommand - playKubeCommand.SetHelpTemplate(HelpTemplate()) - playKubeCommand.SetUsageTemplate(UsageTemplate()) - flags := playKubeCommand.Flags() - flags.StringVar(&playKubeCommand.Creds, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") - flags.BoolVarP(&playKubeCommand.Quiet, "quiet", "q", false, "Suppress output information when pulling images") - // Disabled flags for the remote client - if !remote { - flags.StringVar(&playKubeCommand.Authfile, "authfile", buildahcli.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") - flags.StringVar(&playKubeCommand.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") - flags.StringVar(&playKubeCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") - flags.BoolVar(&playKubeCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") - flags.StringVar(&playKubeCommand.SeccompProfileRoot, "seccomp-profile-root", defaultSeccompRoot, "Directory path for seccomp profiles") - markFlagHidden(flags, "signature-policy") - } -} - -func playKubeCmd(c *cliconfig.KubePlayValues) error { - args := c.InputArgs - if len(args) > 1 { - return errors.New("you can only play one kubernetes file at a time") - } - if len(args) < 1 { - return errors.New("you must supply at least one file") - } - - if c.Authfile != "" { - if _, err := os.Stat(c.Authfile); err != nil { - return errors.Wrapf(err, "error getting authfile %s", c.Authfile) - } - } - - ctx := getContext() - runtime, err := adapter.GetRuntime(ctx, &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - _, err = runtime.PlayKubeYAML(ctx, c, args[0]) - return err -} diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go deleted file mode 100644 index ed331965e..000000000 --- a/cmd/podman/pod.go +++ /dev/null @@ -1,42 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/spf13/cobra" -) - -var ( - podDescription = `Pods are a group of one or more containers sharing the same network, pid and ipc namespaces.` -) -var podCommand = cliconfig.PodmanCommand{ - Command: &cobra.Command{ - Use: "pod", - Short: "Manage pods", - Long: podDescription, - RunE: commandRunE(), - }, -} - -//podSubCommands are implemented both in local and remote clients -var podSubCommands = []*cobra.Command{ - _podCreateCommand, - _podExistsCommand, - _podInspectCommand, - _podKillCommand, - _podPauseCommand, - _prunePodsCommand, - _podPsCommand, - _podRestartCommand, - _podRmCommand, - _podStartCommand, - _podStatsCommand, - _podStopCommand, - _podTopCommand, - _podUnpauseCommand, -} - -func init() { - podCommand.AddCommand(podSubCommands...) - podCommand.SetHelpTemplate(HelpTemplate()) - podCommand.SetUsageTemplate(UsageTemplate()) -} diff --git a/cmd/podman/pod_create.go b/cmd/podman/pod_create.go deleted file mode 100644 index 810f62f02..000000000 --- a/cmd/podman/pod_create.go +++ /dev/null @@ -1,111 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/cmd/podman/shared/parse" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/errorhandling" - "github.com/containers/libpod/pkg/util" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - // Kernel namespaces shared by default within a pod - - podCreateCommand cliconfig.PodCreateValues - - podCreateDescription = `After creating the pod, the pod ID is printed to stdout. - - You can then start it at any time with the podman pod start <pod_id> command. The pod will be created with the initial state 'created'.` - - _podCreateCommand = &cobra.Command{ - Use: "create", - Args: noSubArgs, - Short: "Create a new empty pod", - Long: podCreateDescription, - RunE: func(cmd *cobra.Command, args []string) error { - podCreateCommand.InputArgs = args - podCreateCommand.GlobalFlags = MainGlobalOpts - podCreateCommand.Remote = remoteclient - return podCreateCmd(&podCreateCommand) - }, - } -) - -func init() { - podCreateCommand.Command = _podCreateCommand - podCreateCommand.SetHelpTemplate(HelpTemplate()) - podCreateCommand.SetUsageTemplate(UsageTemplate()) - flags := podCreateCommand.Flags() - flags.SetInterspersed(false) - flags.AddFlagSet(getNetFlags()) - flags.StringVar(&podCreateCommand.CgroupParent, "cgroup-parent", "", "Set parent cgroup for the pod") - flags.BoolVar(&podCreateCommand.Infra, "infra", true, "Create an infra container associated with the pod to share namespaces with") - flags.StringVar(&podCreateCommand.InfraImage, "infra-image", define.DefaultInfraImage, "The image of the infra container to associate with the pod") - flags.StringVar(&podCreateCommand.InfraCommand, "infra-command", define.DefaultInfraCommand, "The command to run on the infra container when the pod is started") - flags.StringSliceVar(&podCreateCommand.LabelFile, "label-file", []string{}, "Read in a line delimited file of labels") - flags.StringSliceVarP(&podCreateCommand.Labels, "label", "l", []string{}, "Set metadata on pod (default [])") - flags.StringVarP(&podCreateCommand.Name, "name", "n", "", "Assign a name to the pod") - flags.StringVarP(&podCreateCommand.Hostname, "hostname", "", "", "Set a hostname to the pod") - flags.StringVar(&podCreateCommand.PodIDFile, "pod-id-file", "", "Write the pod ID to the file") - flags.StringVar(&podCreateCommand.Share, "share", shared.DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share") - -} -func podCreateCmd(c *cliconfig.PodCreateValues) error { - var ( - err error - podIdFile *os.File - ) - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - if len(c.StringSlice("publish")) > 0 { - if !c.Infra { - return errors.Errorf("you must have an infra container to publish port bindings to the host") - } - } - - if !c.Infra && c.Flag("share").Changed && c.Share != "none" && c.Share != "" { - return errors.Errorf("You cannot share kernel namespaces on the pod level without an infra container") - } - if c.Flag("pod-id-file").Changed { - podIdFile, err = util.OpenExclusiveFile(c.PodIDFile) - if err != nil && os.IsExist(err) { - return errors.Errorf("pod id file exists. Ensure another pod is not using it or delete %s", c.PodIDFile) - } - if err != nil { - return errors.Errorf("error opening pod-id-file %s", c.PodIDFile) - } - defer errorhandling.CloseQuiet(podIdFile) - defer errorhandling.SyncQuiet(podIdFile) - } - - labels, err := parse.GetAllLabels(c.LabelFile, c.Labels) - if err != nil { - return errors.Wrapf(err, "unable to process labels") - } - - podID, err := runtime.CreatePod(getContext(), c, labels) - if err != nil { - return errors.Wrapf(err, "unable to create pod") - } - if podIdFile != nil { - _, err = podIdFile.WriteString(podID) - if err != nil { - logrus.Error(err) - } - } - fmt.Printf("%s\n", podID) - return nil -} diff --git a/cmd/podman/pod_inspect.go b/cmd/podman/pod_inspect.go deleted file mode 100644 index 03b5a8cc4..000000000 --- a/cmd/podman/pod_inspect.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - podInspectCommand cliconfig.PodInspectValues - podInspectDescription = `Display the configuration for a pod by name or id - - By default, this will render all results in a JSON array.` - - _podInspectCommand = &cobra.Command{ - Use: "inspect [flags] POD", - Short: "Displays a pod configuration", - Long: podInspectDescription, - RunE: func(cmd *cobra.Command, args []string) error { - podInspectCommand.InputArgs = args - podInspectCommand.GlobalFlags = MainGlobalOpts - podInspectCommand.Remote = remoteclient - return podInspectCmd(&podInspectCommand) - }, - Example: `podman pod inspect podID`, - } -) - -func init() { - podInspectCommand.Command = _podInspectCommand - podInspectCommand.SetHelpTemplate(HelpTemplate()) - podInspectCommand.SetUsageTemplate(UsageTemplate()) - flags := podInspectCommand.Flags() - flags.BoolVarP(&podInspectCommand.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") - - markFlagHiddenForRemoteClient("latest", flags) -} - -func podInspectCmd(c *cliconfig.PodInspectValues) error { - var ( - pod *adapter.Pod - ) - args := c.InputArgs - - if len(args) < 1 && !c.Latest { - return errors.Errorf("you must provide the name or id of a pod") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - if c.Latest { - pod, err = runtime.GetLatestPod() - if err != nil { - return errors.Wrapf(err, "unable to get latest pod") - } - } else { - pod, err = runtime.LookupPod(args[0]) - if err != nil { - return err - } - } - - podInspectData, err := pod.Inspect() - if err != nil { - return err - } - b, err := json.MarshalIndent(&podInspectData, "", " ") - if err != nil { - return err - } - fmt.Println(string(b)) - return nil -} diff --git a/cmd/podman/pod_kill.go b/cmd/podman/pod_kill.go deleted file mode 100644 index 9f696073d..000000000 --- a/cmd/podman/pod_kill.go +++ /dev/null @@ -1,86 +0,0 @@ -package main - -import ( - "fmt" - "syscall" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/util" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - podKillCommand cliconfig.PodKillValues - podKillDescription = `Signals are sent to the main process of each container inside the specified pod. - - The default signal is SIGKILL, or any signal specified with option --signal.` - _podKillCommand = &cobra.Command{ - Use: "kill [flags] POD [POD...]", - Short: "Send the specified signal or SIGKILL to containers in pod", - Long: podKillDescription, - RunE: func(cmd *cobra.Command, args []string) error { - podKillCommand.InputArgs = args - podKillCommand.GlobalFlags = MainGlobalOpts - podKillCommand.Remote = remoteclient - return podKillCmd(&podKillCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllLatestAndCIDFile(cmd, args, false, false) - }, - Example: `podman pod kill podID - podman pod kill --signal TERM mywebserver - podman pod kill --latest`, - } -) - -func init() { - podKillCommand.Command = _podKillCommand - podKillCommand.SetHelpTemplate(HelpTemplate()) - podKillCommand.SetUsageTemplate(UsageTemplate()) - flags := podKillCommand.Flags() - flags.BoolVarP(&podKillCommand.All, "all", "a", false, "Kill all containers in all pods") - flags.BoolVarP(&podKillCommand.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") - flags.StringVarP(&podKillCommand.Signal, "signal", "s", "KILL", "Signal to send to the containers in the pod") - markFlagHiddenForRemoteClient("latest", flags) -} - -// podKillCmd kills one or more pods with a signal -func podKillCmd(c *cliconfig.PodKillValues) error { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - killSignal := uint(syscall.SIGTERM) - - if c.Signal != "" { - // Check if the signalString provided by the user is valid - // Invalid signals will return err - sysSignal, err := util.ParseSignal(c.Signal) - if err != nil { - return err - } - killSignal = uint(sysSignal) - } - - podKillIds, podKillErrors := runtime.KillPods(getContext(), c, killSignal) - for _, p := range podKillIds { - fmt.Println(p) - } - if len(podKillErrors) == 0 { - return nil - } - // Grab the last error - lastError := podKillErrors[len(podKillErrors)-1] - // Remove the last error from the error slice - podKillErrors = podKillErrors[:len(podKillErrors)-1] - - for _, err := range podKillErrors { - logrus.Errorf("%q", err) - } - return lastError -} diff --git a/cmd/podman/pod_pause.go b/cmd/podman/pod_pause.go deleted file mode 100644 index 24fcee6b9..000000000 --- a/cmd/podman/pod_pause.go +++ /dev/null @@ -1,77 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - podPauseCommand cliconfig.PodPauseValues - podPauseDescription = `The pod name or ID can be used. - - All running containers within each specified pod will then be paused.` - _podPauseCommand = &cobra.Command{ - Use: "pause [flags] POD [POD...]", - Short: "Pause one or more pods", - Long: podPauseDescription, - RunE: func(cmd *cobra.Command, args []string) error { - podPauseCommand.InputArgs = args - podPauseCommand.GlobalFlags = MainGlobalOpts - podPauseCommand.Remote = remoteclient - return podPauseCmd(&podPauseCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllLatestAndCIDFile(cmd, args, false, false) - }, - Example: `podman pod pause podID1 podID2 - podman pod pause --latest - podman pod pause --all`, - } -) - -func init() { - podPauseCommand.Command = _podPauseCommand - podPauseCommand.SetHelpTemplate(HelpTemplate()) - podPauseCommand.SetUsageTemplate(UsageTemplate()) - flags := podPauseCommand.Flags() - flags.BoolVarP(&podPauseCommand.All, "all", "a", false, "Pause all running pods") - flags.BoolVarP(&podPauseCommand.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") - markFlagHiddenForRemoteClient("latest", flags) -} - -func podPauseCmd(c *cliconfig.PodPauseValues) error { - var lastError error - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - pauseIDs, conErrors, pauseErrors := runtime.PausePods(c) - - for _, p := range pauseIDs { - fmt.Println(p) - } - if len(conErrors) > 0 { - for ctr, err := range conErrors { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "unable to pause container %s", ctr) - } - } - if len(pauseErrors) > 0 { - lastError = pauseErrors[len(pauseErrors)-1] - // Remove the last error from the error slice - pauseErrors = pauseErrors[:len(pauseErrors)-1] - } - for _, err := range pauseErrors { - logrus.Errorf("%q", err) - } - return lastError -} diff --git a/cmd/podman/pod_ps.go b/cmd/podman/pod_ps.go deleted file mode 100644 index 7acbd6888..000000000 --- a/cmd/podman/pod_ps.go +++ /dev/null @@ -1,462 +0,0 @@ -package main - -import ( - "fmt" - "reflect" - "sort" - "strings" - "time" - - "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/adapter" - "github.com/docker/go-units" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -const ( - STOPPED = "Stopped" //nolint - RUNNING = "Running" - PAUSED = "Paused" - EXITED = "Exited" - ERROR = "Error" - CREATED = "Created" - NUM_CTR_INFO = 10 -) - -var ( - bc_opts shared.PsOptions -) - -type podPsCtrInfo struct { - Name string `json:"name,omitempty"` - Id string `json:"id,omitempty"` - Status string `json:"status,omitempty"` -} - -type podPsOptions struct { - NoTrunc bool - Format string - Sort string - Quiet bool - NumberOfContainers bool - Cgroup bool - NamesOfContainers bool - IdsOfContainers bool - StatusOfContainers bool -} - -type podPsTemplateParams struct { - Created string - ID string - Name string - NumberOfContainers int - Status string - Cgroup string - ContainerInfo string - InfraID string - Namespaces string -} - -// podPsJSONParams is used as a base structure for the psParams -// If template output is requested, podPsJSONParams will be converted to -// podPsTemplateParams. -// podPsJSONParams will be populated by data from libpod.Container, -// the members of the struct are the sama data types as their sources. -type podPsJSONParams struct { - CreatedAt time.Time `json:"createdAt"` - ID string `json:"id"` - Name string `json:"name"` - NumberOfContainers int `json:"numberOfContainers"` - Status string `json:"status"` - CtrsInfo []podPsCtrInfo `json:"containerInfo,omitempty"` - Cgroup string `json:"cgroup,omitempty"` - InfraID string `json:"infraContainerId,omitempty"` - Namespaces []string `json:"namespaces,omitempty"` -} - -// Type declaration and functions for sorting the pod PS output -type podPsSorted []podPsJSONParams - -func (a podPsSorted) Len() int { return len(a) } -func (a podPsSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -type podPsSortedCreated struct{ podPsSorted } - -func (a podPsSortedCreated) Less(i, j int) bool { - return a.podPsSorted[i].CreatedAt.After(a.podPsSorted[j].CreatedAt) -} - -type podPsSortedId struct{ podPsSorted } - -func (a podPsSortedId) Less(i, j int) bool { return a.podPsSorted[i].ID < a.podPsSorted[j].ID } - -type podPsSortedNumber struct{ podPsSorted } - -func (a podPsSortedNumber) Less(i, j int) bool { - return len(a.podPsSorted[i].CtrsInfo) < len(a.podPsSorted[j].CtrsInfo) -} - -type podPsSortedName struct{ podPsSorted } - -func (a podPsSortedName) Less(i, j int) bool { return a.podPsSorted[i].Name < a.podPsSorted[j].Name } - -type podPsSortedStatus struct{ podPsSorted } - -func (a podPsSortedStatus) Less(i, j int) bool { - return a.podPsSorted[i].Status < a.podPsSorted[j].Status -} - -var ( - podPsCommand cliconfig.PodPsValues - - podPsDescription = "List all pods on system including their names, ids and current state." - _podPsCommand = &cobra.Command{ - Use: "ps", - Aliases: []string{"ls", "list"}, - Args: noSubArgs, - Short: "List pods", - Long: podPsDescription, - RunE: func(cmd *cobra.Command, args []string) error { - podPsCommand.InputArgs = args - podPsCommand.GlobalFlags = MainGlobalOpts - podPsCommand.Remote = remoteclient - return podPsCmd(&podPsCommand) - }, - } -) - -func init() { - podPsCommand.Command = _podPsCommand - podPsCommand.SetHelpTemplate(HelpTemplate()) - podPsCommand.SetUsageTemplate(UsageTemplate()) - flags := podPsCommand.Flags() - flags.BoolVar(&podPsCommand.CtrNames, "ctr-names", false, "Display the container names") - flags.BoolVar(&podPsCommand.CtrIDs, "ctr-ids", false, "Display the container UUIDs. If no-trunc is not set they will be truncated") - flags.BoolVar(&podPsCommand.CtrStatus, "ctr-status", false, "Display the container status") - flags.StringVarP(&podPsCommand.Filter, "filter", "f", "", "Filter output based on conditions given") - flags.StringVar(&podPsCommand.Format, "format", "", "Pretty-print pods to JSON or using a Go template") - flags.BoolVarP(&podPsCommand.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") - flags.BoolVar(&podPsCommand.Namespace, "namespace", false, "Display namespace information of the pod") - flags.BoolVar(&podPsCommand.Namespace, "ns", false, "Display namespace information of the pod") - flags.BoolVar(&podPsCommand.NoTrunc, "no-trunc", false, "Do not truncate pod and container IDs") - flags.BoolVarP(&podPsCommand.Quiet, "quiet", "q", false, "Print the numeric IDs of the pods only") - flags.StringVar(&podPsCommand.Sort, "sort", "created", "Sort output by created, id, name, or number") - markFlagHiddenForRemoteClient("latest", flags) -} - -func podPsCmd(c *cliconfig.PodPsValues) error { - if err := podPsCheckFlagsPassed(c); err != nil { - return errors.Wrapf(err, "error with flags passed") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - opts := podPsOptions{ - NoTrunc: c.NoTrunc, - Quiet: c.Quiet, - Sort: c.Sort, - IdsOfContainers: c.CtrIDs, - NamesOfContainers: c.CtrNames, - StatusOfContainers: c.CtrStatus, - } - - opts.Format = genPodPsFormat(c) - - var pods []*adapter.Pod - - // If latest is set true filters are ignored. - if c.Latest { - pod, err := runtime.GetLatestPod() - if err != nil { - return err - } - pods = append(pods, pod) - return generatePodPsOutput(pods, opts) - } - - if c.Filter != "" { - pods, err = runtime.GetPodsWithFilters(c.Filter) - if err != nil { - return err - } - } else { - pods, err = runtime.GetAllPods() - if err != nil { - return err - } - } - - return generatePodPsOutput(pods, opts) -} - -// podPsCheckFlagsPassed checks if mutually exclusive flags are passed together -func podPsCheckFlagsPassed(c *cliconfig.PodPsValues) error { - // quiet, and format with Go template are mutually exclusive - flags := 0 - if c.Quiet { - flags++ - } - if c.Flag("format").Changed && c.Format != formats.JSONString { - flags++ - } - if flags > 1 { - return errors.Errorf("quiet and format with Go template are mutually exclusive") - } - return nil -} - -// generate the template based on conditions given -func genPodPsFormat(c *cliconfig.PodPsValues) string { - format := "" - switch { - case c.Format != "": - // "\t" from the command line is not being recognized as a tab - // replacing the string "\t" to a tab character if the user passes in "\t" - format = strings.Replace(c.Format, `\t`, "\t", -1) - case c.Quiet: - format = formats.IDString - default: - format = "table {{.ID}}\t{{.Name}}\t{{.Status}}\t{{.Created}}" - if c.Bool("namespace") { - format += "\t{{.Cgroup}}\t{{.Namespaces}}" - } - if c.CtrNames || c.CtrIDs || c.CtrStatus { - format += "\t{{.ContainerInfo}}" - } else { - format += "\t{{.NumberOfContainers}}" - } - format += "\t{{.InfraID}}" - } - return format -} - -func podPsToGeneric(templParams []podPsTemplateParams, jsonParams []podPsJSONParams) (genericParams []interface{}) { - if len(templParams) > 0 { - for _, v := range templParams { - genericParams = append(genericParams, interface{}(v)) - } - return - } - for _, v := range jsonParams { - genericParams = append(genericParams, interface{}(v)) - } - return -} - -// generate the accurate header based on template given -func (p *podPsTemplateParams) podHeaderMap() map[string]string { - v := reflect.Indirect(reflect.ValueOf(p)) - values := make(map[string]string) - - for i := 0; i < v.NumField(); i++ { - key := v.Type().Field(i).Name - value := key - if value == "ID" { - value = "Pod" + value - } - if value == "NumberOfContainers" { - value = "#OfContainers" - } - values[key] = strings.ToUpper(splitCamelCase(value)) - } - return values -} - -func sortPodPsOutput(sortBy string, psOutput podPsSorted) (podPsSorted, error) { - switch sortBy { - case "created": - sort.Sort(podPsSortedCreated{psOutput}) - case "id": - sort.Sort(podPsSortedId{psOutput}) - case "name": - sort.Sort(podPsSortedName{psOutput}) - case "number": - sort.Sort(podPsSortedNumber{psOutput}) - case "status": - sort.Sort(podPsSortedStatus{psOutput}) - default: - return nil, errors.Errorf("invalid option for --sort, options are: id, names, or number") - } - return psOutput, nil -} - -// getPodTemplateOutput returns the modified container information -func getPodTemplateOutput(psParams []podPsJSONParams, opts podPsOptions) ([]podPsTemplateParams, error) { - var ( - psOutput []podPsTemplateParams - ) - - for _, psParam := range psParams { - podID := psParam.ID - infraID := psParam.InfraID - var ctrStr string - - truncated := "" - if !opts.NoTrunc { - podID = shortID(podID) - if len(psParam.CtrsInfo) > NUM_CTR_INFO { - psParam.CtrsInfo = psParam.CtrsInfo[:NUM_CTR_INFO] - truncated = "..." - } - infraID = shortID(infraID) - } - for _, ctrInfo := range psParam.CtrsInfo { - infoSlice := make([]string, 0) - if opts.IdsOfContainers { - if opts.NoTrunc { - infoSlice = append(infoSlice, ctrInfo.Id) - } else { - infoSlice = append(infoSlice, shortID(ctrInfo.Id)) - } - } - if opts.NamesOfContainers { - infoSlice = append(infoSlice, ctrInfo.Name) - } - if opts.StatusOfContainers { - infoSlice = append(infoSlice, ctrInfo.Status) - } - if len(infoSlice) != 0 { - ctrStr += fmt.Sprintf("[%s] ", strings.Join(infoSlice, ",")) - } - } - ctrStr += truncated - params := podPsTemplateParams{ - Created: units.HumanDuration(time.Since(psParam.CreatedAt)) + " ago", - ID: podID, - Name: psParam.Name, - Status: psParam.Status, - NumberOfContainers: psParam.NumberOfContainers, - Cgroup: psParam.Cgroup, - ContainerInfo: ctrStr, - InfraID: infraID, - Namespaces: strings.Join(psParam.Namespaces, ","), - } - - psOutput = append(psOutput, params) - } - - return psOutput, nil -} - -func getNamespaces(pod *adapter.Pod) []string { - var shared []string - if pod.SharesPID() { - shared = append(shared, "pid") - } - if pod.SharesNet() { - shared = append(shared, "net") - } - if pod.SharesMount() { - shared = append(shared, "mnt") - } - if pod.SharesIPC() { - shared = append(shared, "ipc") - } - if pod.SharesUser() { - shared = append(shared, "user") - } - if pod.SharesCgroup() { - shared = append(shared, "cgroup") - } - if pod.SharesUTS() { - shared = append(shared, "uts") - } - return shared -} - -// getAndSortPodJSONOutput returns the container info in its raw, sorted form -func getAndSortPodJSONParams(pods []*adapter.Pod, opts podPsOptions) ([]podPsJSONParams, error) { - var ( - psOutput []podPsJSONParams - ) - - for _, pod := range pods { - ctrs, err := pod.AllContainers() - ctrsInfo := make([]podPsCtrInfo, 0) - if err != nil { - return nil, err - } - ctrNum := len(ctrs) - status, err := pod.GetPodStatus() - if err != nil { - return nil, err - } - - infraID, err := pod.InfraContainerID() - if err != nil { - return nil, err - } - for _, ctr := range ctrs { - batchInfo, err := adapter.BatchContainerOp(ctr, bc_opts) - if err != nil { - return nil, err - } - var status string - switch batchInfo.ConState { - case define.ContainerStateExited: - fallthrough - case define.ContainerStateStopped: - status = EXITED - case define.ContainerStateRunning: - status = RUNNING - case define.ContainerStatePaused: - status = PAUSED - case define.ContainerStateCreated, define.ContainerStateConfigured: - status = CREATED - default: - status = ERROR - } - ctrsInfo = append(ctrsInfo, podPsCtrInfo{ - Name: batchInfo.ConConfig.Name, - Id: ctr.ID(), - Status: status, - }) - } - params := podPsJSONParams{ - CreatedAt: pod.CreatedTime(), - ID: pod.ID(), - Name: pod.Name(), - Status: status, - Cgroup: pod.CgroupParent(), - NumberOfContainers: ctrNum, - CtrsInfo: ctrsInfo, - Namespaces: getNamespaces(pod), - InfraID: infraID, - } - - psOutput = append(psOutput, params) - } - return sortPodPsOutput(opts.Sort, psOutput) -} - -func generatePodPsOutput(pods []*adapter.Pod, opts podPsOptions) error { - if len(pods) == 0 && opts.Format != formats.JSONString { - return nil - } - psOutput, err := getAndSortPodJSONParams(pods, opts) - if err != nil { - return err - } - var out formats.Writer - - switch opts.Format { - case formats.JSONString: - out = formats.JSONStructArray{Output: podPsToGeneric([]podPsTemplateParams{}, psOutput)} - default: - psOutput, err := getPodTemplateOutput(psOutput, opts) - if err != nil { - return errors.Wrapf(err, "unable to create output") - } - out = formats.StdoutTemplateArray{Output: podPsToGeneric(psOutput, []podPsJSONParams{}), Template: opts.Format, Fields: psOutput[0].podHeaderMap()} - } - - return out.Out() -} diff --git a/cmd/podman/pod_restart.go b/cmd/podman/pod_restart.go deleted file mode 100644 index cb9f3770f..000000000 --- a/cmd/podman/pod_restart.go +++ /dev/null @@ -1,78 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - podRestartCommand cliconfig.PodRestartValues - podRestartDescription = `The pod ID or name can be used. - - All of the containers within each of the specified pods will be restarted. If a container in a pod is not currently running it will be started.` - _podRestartCommand = &cobra.Command{ - Use: "restart [flags] POD [POD...]", - Short: "Restart one or more pods", - Long: podRestartDescription, - RunE: func(cmd *cobra.Command, args []string) error { - podRestartCommand.InputArgs = args - podRestartCommand.GlobalFlags = MainGlobalOpts - podRestartCommand.Remote = remoteclient - return podRestartCmd(&podRestartCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllLatestAndCIDFile(cmd, args, false, false) - }, - Example: `podman pod restart podID1 podID2 - podman pod restart --latest - podman pod restart --all`, - } -) - -func init() { - podRestartCommand.Command = _podRestartCommand - podRestartCommand.SetHelpTemplate(HelpTemplate()) - podRestartCommand.SetUsageTemplate(UsageTemplate()) - flags := podRestartCommand.Flags() - flags.BoolVarP(&podRestartCommand.All, "all", "a", false, "Restart all running pods") - flags.BoolVarP(&podRestartCommand.Latest, "latest", "l", false, "Restart the latest pod podman is aware of") - - markFlagHiddenForRemoteClient("latest", flags) -} - -func podRestartCmd(c *cliconfig.PodRestartValues) error { - var lastError error - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - restartIDs, conErrors, restartErrors := runtime.RestartPods(getContext(), c) - - for _, p := range restartIDs { - fmt.Println(p) - } - if len(conErrors) > 0 { - for ctr, err := range conErrors { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "unable to pause container %s", ctr) - } - } - if len(restartErrors) > 0 { - lastError = restartErrors[len(restartErrors)-1] - // Remove the last error from the error slice - restartErrors = restartErrors[:len(restartErrors)-1] - } - for _, err := range restartErrors { - logrus.Errorf("%q", err) - } - return lastError -} diff --git a/cmd/podman/pod_rm.go b/cmd/podman/pod_rm.go deleted file mode 100644 index 02daf8764..000000000 --- a/cmd/podman/pod_rm.go +++ /dev/null @@ -1,74 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - podRmCommand cliconfig.PodRmValues - podRmDescription = fmt.Sprintf(`podman rm will remove one or more stopped pods and their containers from the host. - - The pod name or ID can be used. A pod with containers will not be removed without --force. If --force is specified, all containers will be stopped, then removed.`) - _podRmCommand = &cobra.Command{ - Use: "rm [flags] POD [POD...]", - Short: "Remove one or more pods", - Long: podRmDescription, - RunE: func(cmd *cobra.Command, args []string) error { - podRmCommand.InputArgs = args - podRmCommand.GlobalFlags = MainGlobalOpts - podRmCommand.Remote = remoteclient - return podRmCmd(&podRmCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllLatestAndCIDFile(cmd, args, false, false) - }, - Example: `podman pod rm mywebserverpod - podman pod rm -f 860a4b23 - podman pod rm -f -a`, - } -) - -func init() { - podRmCommand.Command = _podRmCommand - podRmCommand.SetHelpTemplate(HelpTemplate()) - podRmCommand.SetUsageTemplate(UsageTemplate()) - flags := podRmCommand.Flags() - flags.BoolVarP(&podRmCommand.All, "all", "a", false, "Remove all running pods") - flags.BoolVarP(&podRmCommand.Force, "force", "f", false, "Force removal of a running pod by first stopping all containers, then removing all containers in the pod. The default is false") - flags.BoolVarP(&podRmCommand.Ignore, "ignore", "i", false, "Ignore errors when a specified pod is missing") - flags.BoolVarP(&podRmCommand.Latest, "latest", "l", false, "Remove the latest pod podman is aware of") - markFlagHiddenForRemoteClient("ignore", flags) - markFlagHiddenForRemoteClient("latest", flags) -} - -// podRmCmd deletes pods -func podRmCmd(c *cliconfig.PodRmValues) error { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - podRmIds, podRmErrors := runtime.RemovePods(getContext(), c) - for _, p := range podRmIds { - fmt.Println(p) - } - if len(podRmErrors) == 0 { - return nil - } - // Grab the last error - lastError := podRmErrors[len(podRmErrors)-1] - // Remove the last error from the error slice - podRmErrors = podRmErrors[:len(podRmErrors)-1] - - for _, err := range podRmErrors { - logrus.Errorf("%q", err) - } - return lastError -} diff --git a/cmd/podman/pod_start.go b/cmd/podman/pod_start.go deleted file mode 100644 index aa2e09e98..000000000 --- a/cmd/podman/pod_start.go +++ /dev/null @@ -1,70 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - podStartCommand cliconfig.PodStartValues - podStartDescription = `The pod name or ID can be used. - - All containers defined in the pod will be started.` - _podStartCommand = &cobra.Command{ - Use: "start [flags] POD [POD...]", - Short: "Start one or more pods", - Long: podStartDescription, - RunE: func(cmd *cobra.Command, args []string) error { - podStartCommand.InputArgs = args - podStartCommand.GlobalFlags = MainGlobalOpts - podStartCommand.Remote = remoteclient - return podStartCmd(&podStartCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllLatestAndCIDFile(cmd, args, false, false) - }, - Example: `podman pod start podID - podman pod start --latest - podman pod start --all`, - } -) - -func init() { - podStartCommand.Command = _podStartCommand - podStartCommand.SetHelpTemplate(HelpTemplate()) - podStartCommand.SetUsageTemplate(UsageTemplate()) - flags := podStartCommand.Flags() - flags.BoolVarP(&podStartCommand.All, "all", "a", false, "Start all pods") - flags.BoolVarP(&podStartCommand.Latest, "latest", "l", false, "Start the latest pod podman is aware of") - markFlagHiddenForRemoteClient("latest", flags) -} - -func podStartCmd(c *cliconfig.PodStartValues) error { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - podStartIDs, podStartErrors := runtime.StartPods(getContext(), c) - for _, p := range podStartIDs { - fmt.Println(p) - } - if len(podStartErrors) == 0 { - return nil - } - // Grab the last error - lastError := podStartErrors[len(podStartErrors)-1] - // Remove the last error from the error slice - podStartErrors = podStartErrors[:len(podStartErrors)-1] - - for _, err := range podStartErrors { - logrus.Errorf("%q", err) - } - return lastError -} diff --git a/cmd/podman/pod_stats.go b/cmd/podman/pod_stats.go deleted file mode 100644 index 297603410..000000000 --- a/cmd/podman/pod_stats.go +++ /dev/null @@ -1,286 +0,0 @@ -package main - -import ( - "fmt" - "html/template" - "os" - "reflect" - "strings" - "text/tabwriter" - "time" - - tm "github.com/buger/goterm" - "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/cgroups" - "github.com/containers/libpod/pkg/rootless" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - podStatsCommand cliconfig.PodStatsValues - podStatsDescription = `For each specified pod this command will display percentage of CPU, memory, network I/O, block I/O and PIDs for containers in one the pods.` - - _podStatsCommand = &cobra.Command{ - Use: "stats [flags] [POD...]", - Short: "Display a live stream of resource usage statistics for the containers in one or more pods", - Long: podStatsDescription, - RunE: func(cmd *cobra.Command, args []string) error { - podStatsCommand.InputArgs = args - podStatsCommand.GlobalFlags = MainGlobalOpts - podStatsCommand.Remote = remoteclient - return podStatsCmd(&podStatsCommand) - }, - Example: `podman stats -a --no-stream - podman stats --no-reset ctrID - podman stats --no-stream --format "table {{.ID}} {{.Name}} {{.MemUsage}}" ctrID`, - } -) - -func init() { - podStatsCommand.Command = _podStatsCommand - podStatsCommand.SetHelpTemplate(HelpTemplate()) - podStatsCommand.SetUsageTemplate(UsageTemplate()) - flags := podStatsCommand.Flags() - flags.BoolVarP(&podStatsCommand.All, "all", "a", false, "Provide stats for all running pods") - flags.StringVar(&podStatsCommand.Format, "format", "", "Pretty-print container statistics to JSON or using a Go template") - flags.BoolVarP(&podStatsCommand.Latest, "latest", "l", false, "Provide stats on the latest pod podman is aware of") - flags.BoolVar(&podStatsCommand.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result, default setting is false") - flags.BoolVar(&podStatsCommand.NoReset, "no-reset", false, "Disable resetting the screen between intervals") - markFlagHiddenForRemoteClient("latest", flags) -} - -func podStatsCmd(c *cliconfig.PodStatsValues) error { - if rootless.IsRootless() { - unified, err := cgroups.IsCgroup2UnifiedMode() - if err != nil { - return err - } - if !unified { - return errors.New("stats is not supported in rootless mode without cgroups v2") - } - } - - format := c.Format - all := c.All - latest := c.Latest - ctr := 0 - if all { - ctr += 1 - } - if latest { - ctr += 1 - } - if len(c.InputArgs) > 0 { - ctr += 1 - } - - if ctr > 1 { - return errors.Errorf("--all, --latest and containers cannot be used together") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - times := -1 - if c.NoStream { - times = 1 - } - - pods, err := runtime.GetStatPods(c) - if err != nil { - return errors.Wrapf(err, "unable to get a list of pods") - } - - // Create empty container stat results for our first pass - var previousPodStats []*adapter.PodContainerStats - for _, p := range pods { - cs := make(map[string]*libpod.ContainerStats) - pcs := adapter.PodContainerStats{ - Pod: p, - ContainerStats: cs, - } - previousPodStats = append(previousPodStats, &pcs) - } - - step := 1 - if times == -1 { - times = 1 - step = 0 - } - - headerNames := make(map[string]string) - if c.Format != "" { - // Make a map of the field names for the headers - v := reflect.ValueOf(podStatOut{}) - t := v.Type() - for i := 0; i < t.NumField(); i++ { - value := strings.ToUpper(splitCamelCase(t.Field(i).Name)) - switch value { - case "CPU", "MEM": - value += " %" - case "MEM USAGE": - value = "MEM USAGE / LIMIT" - } - headerNames[t.Field(i).Name] = value - } - } - - for i := 0; i < times; i += step { - var newStats []*adapter.PodContainerStats - for _, p := range pods { - prevStat := getPreviousPodContainerStats(p.ID(), previousPodStats) - newPodStats, err := p.GetPodStats(prevStat) - if errors.Cause(err) == define.ErrNoSuchPod { - continue - } - if err != nil { - return err - } - newPod := adapter.PodContainerStats{ - Pod: p, - ContainerStats: newPodStats, - } - newStats = append(newStats, &newPod) - } - //Output - if strings.ToLower(format) != formats.JSONString && !c.NoReset { - tm.Clear() - tm.MoveCursor(1, 1) - tm.Flush() - } - if strings.ToLower(format) == formats.JSONString { - if err := outputJson(newStats); err != nil { - return err - } - - } else { - results := podContainerStatsToPodStatOut(newStats) - if len(format) == 0 { - outputToStdOut(results) - } else if err := printPSFormat(c.Format, results, headerNames); err != nil { - return err - } - } - time.Sleep(time.Second) - previousPodStats := new([]*libpod.PodContainerStats) - if err := libpod.JSONDeepCopy(newStats, previousPodStats); err != nil { - return err - } - pods, err = runtime.GetStatPods(c) - if err != nil { - return err - } - } - - return nil -} - -func podContainerStatsToPodStatOut(stats []*adapter.PodContainerStats) []*podStatOut { - var out []*podStatOut - for _, p := range stats { - for _, c := range p.ContainerStats { - o := podStatOut{ - CPU: floatToPercentString(c.CPU), - MemUsage: combineHumanValues(c.MemUsage, c.MemLimit), - Mem: floatToPercentString(c.MemPerc), - NetIO: combineHumanValues(c.NetInput, c.NetOutput), - BlockIO: combineHumanValues(c.BlockInput, c.BlockOutput), - PIDS: pidsToString(c.PIDs), - CID: c.ContainerID[:12], - Name: c.Name, - Pod: p.Pod.ID()[:12], - } - out = append(out, &o) - } - } - return out -} - -type podStatOut struct { - CPU string - MemUsage string - Mem string - NetIO string - BlockIO string - PIDS string - Pod string - CID string - Name string -} - -func printPSFormat(format string, stats []*podStatOut, headerNames map[string]string) error { - if len(stats) == 0 { - return nil - } - - // Use a tabwriter to align column format - w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) - // Spit out the header if "table" is present in the format - if strings.HasPrefix(format, "table") { - hformat := strings.Replace(strings.TrimSpace(format[5:]), " ", "\t", -1) - format = hformat - headerTmpl, err := template.New("header").Parse(hformat) - if err != nil { - return err - } - if err := headerTmpl.Execute(w, headerNames); err != nil { - return err - } - fmt.Fprintln(w, "") - } - - // Spit out the data rows now - dataTmpl, err := template.New("data").Parse(format) - if err != nil { - return err - } - for _, container := range stats { - if err := dataTmpl.Execute(w, container); err != nil { - return err - } - fmt.Fprintln(w, "") - } - // Flush the writer - return w.Flush() - -} - -func outputToStdOut(stats []*podStatOut) { - w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - outFormat := "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" - fmt.Fprintf(w, outFormat, "POD", "CID", "NAME", "CPU %", "MEM USAGE/ LIMIT", "MEM %", "NET IO", "BLOCK IO", "PIDS") - for _, i := range stats { - if len(stats) == 0 { - fmt.Fprintf(w, outFormat, i.Pod, "--", "--", "--", "--", "--", "--", "--", "--") - } else { - fmt.Fprintf(w, outFormat, i.Pod, i.CID, i.Name, i.CPU, i.MemUsage, i.Mem, i.NetIO, i.BlockIO, i.PIDS) - } - } - w.Flush() -} - -func getPreviousPodContainerStats(podID string, prev []*adapter.PodContainerStats) map[string]*libpod.ContainerStats { - for _, p := range prev { - if podID == p.Pod.ID() { - return p.ContainerStats - } - } - return map[string]*libpod.ContainerStats{} -} - -func outputJson(stats []*adapter.PodContainerStats) error { - b, err := json.MarshalIndent(&stats, "", " ") - if err != nil { - return err - } - fmt.Println(string(b)) - return nil -} diff --git a/cmd/podman/pod_stop.go b/cmd/podman/pod_stop.go deleted file mode 100644 index 395731551..000000000 --- a/cmd/podman/pod_stop.go +++ /dev/null @@ -1,75 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - podStopCommand cliconfig.PodStopValues - podStopDescription = `The pod name or ID can be used. - - This command will stop all running containers in each of the specified pods.` - - _podStopCommand = &cobra.Command{ - Use: "stop [flags] POD [POD...]", - Short: "Stop one or more pods", - Long: podStopDescription, - RunE: func(cmd *cobra.Command, args []string) error { - podStopCommand.InputArgs = args - podStopCommand.GlobalFlags = MainGlobalOpts - podStopCommand.Remote = remoteclient - return podStopCmd(&podStopCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllLatestAndCIDFile(cmd, args, false, false) - }, - Example: `podman pod stop mywebserverpod - podman pod stop --latest - podman pod stop --time 0 490eb 3557fb`, - } -) - -func init() { - podStopCommand.Command = _podStopCommand - podStopCommand.SetHelpTemplate(HelpTemplate()) - podStopCommand.SetUsageTemplate(UsageTemplate()) - flags := podStopCommand.Flags() - flags.BoolVarP(&podStopCommand.All, "all", "a", false, "Stop all running pods") - flags.BoolVarP(&podStopCommand.Ignore, "ignore", "i", false, "Ignore errors when a specified pod is missing") - flags.BoolVarP(&podStopCommand.Latest, "latest", "l", false, "Stop the latest pod podman is aware of") - flags.UintVarP(&podStopCommand.Timeout, "time", "t", defaultContainerConfig.Engine.StopTimeout, "Seconds to wait for pod stop before killing the container") - flags.SetNormalizeFunc(aliasFlags) - markFlagHiddenForRemoteClient("ignore", flags) - markFlagHiddenForRemoteClient("latest", flags) -} - -func podStopCmd(c *cliconfig.PodStopValues) error { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - podStopIds, podStopErrors := runtime.StopPods(getContext(), c) - for _, p := range podStopIds { - fmt.Println(p) - } - if len(podStopErrors) == 0 { - return nil - } - // Grab the last error - lastError := podStopErrors[len(podStopErrors)-1] - // Remove the last error from the error slice - podStopErrors = podStopErrors[:len(podStopErrors)-1] - - for _, err := range podStopErrors { - logrus.Errorf("%q", err) - } - return lastError -} diff --git a/cmd/podman/pod_top.go b/cmd/podman/pod_top.go deleted file mode 100644 index 734472817..000000000 --- a/cmd/podman/pod_top.go +++ /dev/null @@ -1,92 +0,0 @@ -package main - -import ( - "fmt" - "os" - "strings" - "text/tabwriter" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/util" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - podTopCommand cliconfig.PodTopValues - - podTopDescription = fmt.Sprintf(`Specify format descriptors to alter the output. - - You may run "podman pod top -l pid pcpu seccomp" to print the process ID, the CPU percentage and the seccomp mode of each process of the latest pod. -%s`, getDescriptorString()) - - _podTopCommand = &cobra.Command{ - Use: "top [flags] CONTAINER [FORMAT-DESCRIPTORS]", - Short: "Display the running processes of containers in a pod", - Long: podTopDescription, - RunE: func(cmd *cobra.Command, args []string) error { - podTopCommand.InputArgs = args - podTopCommand.GlobalFlags = MainGlobalOpts - podTopCommand.Remote = remoteclient - return podTopCmd(&podTopCommand) - }, - Example: `podman top ctrID - podman top --latest - podman top --latest pid seccomp args %C`, - } -) - -func init() { - podTopCommand.Command = _podTopCommand - podTopCommand.SetHelpTemplate(HelpTemplate()) - podTopCommand.SetUsageTemplate(UsageTemplate()) - flags := podTopCommand.Flags() - flags.BoolVarP(&podTopCommand.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") - flags.BoolVar(&podTopCommand.ListDescriptors, "list-descriptors", false, "") - markFlagHidden(flags, "list-descriptors") -} - -func podTopCmd(c *cliconfig.PodTopValues) error { - var ( - descriptors []string - ) - args := c.InputArgs - - if c.ListDescriptors { - descriptors, err := util.GetContainerPidInformationDescriptors() - if err != nil { - return err - } - fmt.Println(strings.Join(descriptors, "\n")) - return nil - } - - if len(args) < 1 && !c.Latest { - return errors.Errorf("you must provide the name or id of a running pod") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - if c.Latest { - descriptors = args - } else { - descriptors = args[1:] - } - - w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) - psOutput, err := runtime.PodTop(c, descriptors) - if err != nil { - return err - } - for _, proc := range psOutput { - if _, err := fmt.Fprintln(w, proc); err != nil { - return err - } - } - return w.Flush() -} diff --git a/cmd/podman/pod_unpause.go b/cmd/podman/pod_unpause.go deleted file mode 100644 index 1f80a7c79..000000000 --- a/cmd/podman/pod_unpause.go +++ /dev/null @@ -1,77 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - podUnpauseCommand cliconfig.PodUnpauseValues - podUnpauseDescription = `The podman unpause command will unpause all "paused" containers assigned to the pod. - - The pod name or ID can be used.` - _podUnpauseCommand = &cobra.Command{ - Use: "unpause [flags] POD [POD...]", - Short: "Unpause one or more pods", - Long: podUnpauseDescription, - RunE: func(cmd *cobra.Command, args []string) error { - podUnpauseCommand.InputArgs = args - podUnpauseCommand.GlobalFlags = MainGlobalOpts - podUnpauseCommand.Remote = remoteclient - return podUnpauseCmd(&podUnpauseCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllLatestAndCIDFile(cmd, args, false, false) - }, - Example: `podman pod unpause podID1 podID2 - podman pod unpause --all - podman pod unpause --latest`, - } -) - -func init() { - podUnpauseCommand.Command = _podUnpauseCommand - podUnpauseCommand.SetHelpTemplate(HelpTemplate()) - podUnpauseCommand.SetUsageTemplate(UsageTemplate()) - flags := podUnpauseCommand.Flags() - flags.BoolVarP(&podUnpauseCommand.All, "all", "a", false, "Unpause all running pods") - flags.BoolVarP(&podUnpauseCommand.Latest, "latest", "l", false, "Unpause the latest pod podman is aware of") - markFlagHiddenForRemoteClient("latest", flags) -} - -func podUnpauseCmd(c *cliconfig.PodUnpauseValues) error { - var lastError error - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - unpauseIDs, conErrors, unpauseErrors := runtime.UnpausePods(c) - - for _, p := range unpauseIDs { - fmt.Println(p) - } - if len(conErrors) > 0 { - for ctr, err := range conErrors { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "unable to unpause container %s", ctr) - } - } - if len(unpauseErrors) > 0 { - lastError = unpauseErrors[len(unpauseErrors)-1] - // Remove the last error from the error slice - unpauseErrors = unpauseErrors[:len(unpauseErrors)-1] - } - for _, err := range unpauseErrors { - logrus.Errorf("%q", err) - } - return lastError -} diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go new file mode 100644 index 000000000..63dab4707 --- /dev/null +++ b/cmd/podman/pods/create.go @@ -0,0 +1,147 @@ +package pods + +import ( + "context" + "fmt" + "os" + "strings" + + "github.com/containers/libpod/cmd/podman/common" + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/errorhandling" + "github.com/containers/libpod/pkg/specgen" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + podCreateDescription = `After creating the pod, the pod ID is printed to stdout. + + You can then start it at any time with the podman pod start <pod_id> command. The pod will be created with the initial state 'created'.` + + createCommand = &cobra.Command{ + Use: "create", + Args: cobra.NoArgs, + Short: "Create a new empty pod", + Long: podCreateDescription, + RunE: create, + } +) + +var ( + createOptions entities.PodCreateOptions + labels, labelFile []string + podIDFile string + share string +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: createCommand, + Parent: podCmd, + }) + flags := createCommand.Flags() + flags.SetInterspersed(false) + flags.AddFlagSet(common.GetNetFlags()) + flags.StringVar(&createOptions.CGroupParent, "cgroup-parent", "", "Set parent cgroup for the pod") + flags.BoolVar(&createOptions.Infra, "infra", true, "Create an infra container associated with the pod to share namespaces with") + flags.StringVar(&createOptions.InfraImage, "infra-image", define.DefaultInfraImage, "The image of the infra container to associate with the pod") + flags.StringVar(&createOptions.InfraCommand, "infra-command", define.DefaultInfraCommand, "The command to run on the infra container when the pod is started") + flags.StringSliceVar(&labelFile, "label-file", []string{}, "Read in a line delimited file of labels") + flags.StringSliceVarP(&labels, "label", "l", []string{}, "Set metadata on pod (default [])") + flags.StringVarP(&createOptions.Name, "name", "n", "", "Assign a name to the pod") + flags.StringVarP(&createOptions.Hostname, "hostname", "", "", "Set a hostname to the pod") + flags.StringVar(&podIDFile, "pod-id-file", "", "Write the pod ID to the file") + flags.StringVar(&share, "share", common.DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share") +} + +func create(cmd *cobra.Command, args []string) error { + var ( + err error + podIdFile *os.File + ) + createOptions.Labels, err = parse.GetAllLabels(labelFile, labels) + if err != nil { + return errors.Wrapf(err, "unable to process labels") + } + + if !createOptions.Infra && cmd.Flag("share").Changed && share != "none" && share != "" { + return errors.Errorf("You cannot share kernel namespaces on the pod level without an infra container") + } + createOptions.Share = strings.Split(share, ",") + if cmd.Flag("pod-id-file").Changed { + podIdFile, err = util.OpenExclusiveFile(podIDFile) + if err != nil && os.IsExist(err) { + return errors.Errorf("pod id file exists. Ensure another pod is not using it or delete %s", podIDFile) + } + if err != nil { + return errors.Errorf("error opening pod-id-file %s", podIDFile) + } + defer errorhandling.CloseQuiet(podIdFile) + defer errorhandling.SyncQuiet(podIdFile) + } + + createOptions.Net, err = common.NetFlagsToNetOptions(cmd) + if err != nil { + return err + } + netInput, err := cmd.Flags().GetString("network") + if err != nil { + return err + } + n := specgen.Namespace{} + switch netInput { + case "bridge": + n.NSMode = specgen.Bridge + case "host": + n.NSMode = specgen.Host + case "slip4netns": + n.NSMode = specgen.Slirp + default: + if strings.HasPrefix(netInput, "container:") { //nolint + split := strings.Split(netInput, ":") + if len(split) != 2 { + return errors.Errorf("invalid network paramater: %q", netInput) + } + n.NSMode = specgen.FromContainer + n.Value = split[1] + } else if strings.HasPrefix(netInput, "ns:") { + return errors.New("the ns: network option is not supported for pods") + } else { + n.NSMode = specgen.Bridge + createOptions.Net.CNINetworks = strings.Split(netInput, ",") + } + } + if len(createOptions.Net.PublishPorts) > 0 { + if !createOptions.Infra { + return errors.Errorf("you must have an infra container to publish port bindings to the host") + } + } + + if !createOptions.Infra { + if cmd.Flag("infra-command").Changed { + return errors.New("cannot set infra-command without an infra container") + } + createOptions.InfraCommand = "" + if cmd.Flag("infra-image").Changed { + return errors.New("cannot set infra-image without an infra container") + } + createOptions.InfraImage = "" + if cmd.Flag("share").Changed { + return errors.New("cannot set share namespaces without an infra container") + } + createOptions.Share = nil + } + + response, err := registry.ContainerEngine().PodCreate(context.Background(), createOptions) + if err != nil { + return err + } + fmt.Println(response.Id) + return nil +} diff --git a/cmd/podman/pods/exists.go b/cmd/podman/pods/exists.go new file mode 100644 index 000000000..ad0e28b90 --- /dev/null +++ b/cmd/podman/pods/exists.go @@ -0,0 +1,43 @@ +package pods + +import ( + "context" + "os" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + podExistsDescription = `If the named pod exists in local storage, podman pod exists exits with 0, otherwise the exit code will be 1.` + + existsCommand = &cobra.Command{ + Use: "exists POD", + Short: "Check if a pod exists in local storage", + Long: podExistsDescription, + RunE: exists, + Args: cobra.ExactArgs(1), + Example: `podman pod exists podID + podman pod exists mypod || podman pod create --name mypod`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: existsCommand, + Parent: podCmd, + }) +} + +func exists(cmd *cobra.Command, args []string) error { + response, err := registry.ContainerEngine().PodExists(context.Background(), args[0]) + if err != nil { + return err + } + if !response.Value { + os.Exit(1) + } + return nil +} diff --git a/cmd/podman/pods/inspect.go b/cmd/podman/pods/inspect.go new file mode 100644 index 000000000..901ae50b2 --- /dev/null +++ b/cmd/podman/pods/inspect.go @@ -0,0 +1,64 @@ +package pods + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + jsoniter "github.com/json-iterator/go" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + inspectOptions = entities.PodInspectOptions{} +) + +var ( + inspectDescription = fmt.Sprintf(`Display the configuration for a pod by name or id + + By default, this will render all results in a JSON array.`) + + inspectCmd = &cobra.Command{ + Use: "inspect [flags] POD [POD...]", + Short: "Displays a pod configuration", + Long: inspectDescription, + RunE: inspect, + Example: `podman pod inspect podID`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: inspectCmd, + Parent: podCmd, + }) + flags := inspectCmd.Flags() + flags.BoolVarP(&inspectOptions.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} + +func inspect(cmd *cobra.Command, args []string) error { + + if len(args) < 1 && !inspectOptions.Latest { + return errors.Errorf("you must provide the name or id of a running pod") + } + + if !inspectOptions.Latest { + inspectOptions.NameOrID = args[0] + } + responses, err := registry.ContainerEngine().PodInspect(context.Background(), inspectOptions) + if err != nil { + return err + } + b, err := jsoniter.MarshalIndent(responses, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil +} diff --git a/cmd/podman/pods/kill.go b/cmd/podman/pods/kill.go new file mode 100644 index 000000000..02089016e --- /dev/null +++ b/cmd/podman/pods/kill.go @@ -0,0 +1,68 @@ +package pods + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + podKillDescription = `Signals are sent to the main process of each container inside the specified pod. + + The default signal is SIGKILL, or any signal specified with option --signal.` + killCommand = &cobra.Command{ + Use: "kill [flags] POD [POD...]", + Short: "Send the specified signal or SIGKILL to containers in pod", + Long: podKillDescription, + RunE: kill, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, + Example: `podman pod kill podID + podman pod kill --signal TERM mywebserver + podman pod kill --latest`, + } +) + +var ( + killOpts entities.PodKillOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: killCommand, + Parent: podCmd, + }) + flags := killCommand.Flags() + flags.BoolVarP(&killOpts.All, "all", "a", false, "Kill all containers in all pods") + flags.BoolVarP(&killOpts.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") + flags.StringVarP(&killOpts.Signal, "signal", "s", "KILL", "Signal to send to the containers in the pod") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } + +} +func kill(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + ) + responses, err := registry.ContainerEngine().PodKill(context.Background(), args, killOpts) + if err != nil { + return err + } + // in the cli, first we print out all the successful attempts + for _, r := range responses { + if len(r.Errs) == 0 { + fmt.Println(r.Id) + } else { + errs = append(errs, r.Errs...) + } + } + return errs.PrintErrors() +} diff --git a/cmd/podman/pods/pause.go b/cmd/podman/pods/pause.go new file mode 100644 index 000000000..4ee182661 --- /dev/null +++ b/cmd/podman/pods/pause.go @@ -0,0 +1,66 @@ +package pods + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + podPauseDescription = `The pod name or ID can be used. + + All running containers within each specified pod will then be paused.` + pauseCommand = &cobra.Command{ + Use: "pause [flags] POD [POD...]", + Short: "Pause one or more pods", + Long: podPauseDescription, + RunE: pause, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, + Example: `podman pod pause podID1 podID2 + podman pod pause --latest + podman pod pause --all`, + } +) + +var ( + pauseOptions entities.PodPauseOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: pauseCommand, + Parent: podCmd, + }) + flags := pauseCommand.Flags() + flags.BoolVarP(&pauseOptions.All, "all", "a", false, "Pause all running pods") + flags.BoolVarP(&pauseOptions.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} +func pause(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + ) + responses, err := registry.ContainerEngine().PodPause(context.Background(), args, pauseOptions) + if err != nil { + return err + } + // in the cli, first we print out all the successful attempts + for _, r := range responses { + if len(r.Errs) == 0 { + fmt.Println(r.Id) + } else { + errs = append(errs, r.Errs...) + } + } + return errs.PrintErrors() +} diff --git a/cmd/podman/pods/pod.go b/cmd/podman/pods/pod.go new file mode 100644 index 000000000..1cac50e40 --- /dev/null +++ b/cmd/podman/pods/pod.go @@ -0,0 +1,25 @@ +package pods + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + // Command: podman _pod_ + podCmd = &cobra.Command{ + Use: "pod", + Short: "Manage pods", + Long: "Manage pods", + TraverseChildren: true, + RunE: registry.SubCommandExists, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: podCmd, + }) +} diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go new file mode 100644 index 000000000..8cb7b6266 --- /dev/null +++ b/cmd/podman/pods/ps.go @@ -0,0 +1,229 @@ +package pods + +import ( + "context" + "encoding/json" + "fmt" + "io" + "os" + "strings" + "text/tabwriter" + "text/template" + "time" + + "github.com/docker/go-units" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + psDescription = "List all pods on system including their names, ids and current state." + + // Command: podman pod _ps_ + psCmd = &cobra.Command{ + Use: "ps", + Aliases: []string{"ls", "list"}, + Short: "list pods", + Long: psDescription, + RunE: pods, + } +) + +var ( + defaultHeaders string = "POD ID\tNAME\tSTATUS\tCREATED" + inputFilters string + noTrunc bool + psInput entities.PodPSOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: psCmd, + Parent: podCmd, + }) + flags := psCmd.Flags() + flags.BoolVar(&psInput.CtrNames, "ctr-names", false, "Display the container names") + flags.BoolVar(&psInput.CtrIds, "ctr-ids", false, "Display the container UUIDs. If no-trunc is not set they will be truncated") + flags.BoolVar(&psInput.CtrStatus, "ctr-status", false, "Display the container status") + // TODO should we make this a [] ? + flags.StringVarP(&inputFilters, "filter", "f", "", "Filter output based on conditions given") + flags.StringVar(&psInput.Format, "format", "", "Pretty-print pods to JSON or using a Go template") + flags.BoolVarP(&psInput.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") + flags.BoolVar(&psInput.Namespace, "namespace", false, "Display namespace information of the pod") + flags.BoolVar(&psInput.Namespace, "ns", false, "Display namespace information of the pod") + flags.BoolVar(&noTrunc, "no-trunc", false, "Do not truncate pod and container IDs") + flags.BoolVarP(&psInput.Quiet, "quiet", "q", false, "Print the numeric IDs of the pods only") + flags.StringVar(&psInput.Sort, "sort", "created", "Sort output by created, id, name, or number") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} + +func pods(cmd *cobra.Command, args []string) error { + var ( + w io.Writer = os.Stdout + row string + lpr []ListPodReporter + ) + if cmd.Flag("filter").Changed { + for _, f := range strings.Split(inputFilters, ",") { + split := strings.Split(f, "=") + if len(split) < 2 { + return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) + } + psInput.Filters[split[0]] = append(psInput.Filters[split[0]], split[1]) + } + } + responses, err := registry.ContainerEngine().PodPs(context.Background(), psInput) + if err != nil { + return err + } + + if psInput.Format == "json" { + b, err := json.MarshalIndent(responses, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil + } + + for _, r := range responses { + lpr = append(lpr, ListPodReporter{r}) + } + headers, row := createPodPsOut() + if psInput.Quiet { + if noTrunc { + row = "{{.Id}}\n" + } else { + row = "{{slice .Id 0 12}}\n" + } + } + if cmd.Flag("format").Changed { + row = psInput.Format + if !strings.HasPrefix(row, "\n") { + row += "\n" + } + } + format := "{{range . }}" + row + "{{end}}" + if !psInput.Quiet && !cmd.Flag("format").Changed { + format = headers + format + } + tmpl, err := template.New("listPods").Parse(format) + if err != nil { + return err + } + if !psInput.Quiet { + w = tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + } + if err := tmpl.Execute(w, lpr); err != nil { + return err + } + if flusher, ok := w.(interface{ Flush() error }); ok { + return flusher.Flush() + } + return nil +} + +func createPodPsOut() (string, string) { + var row string + headers := defaultHeaders + if noTrunc { + row += "{{.Id}}" + } else { + row += "{{slice .Id 0 12}}" + } + + row += "\t{{.Name}}\t{{.Status}}\t{{.Created}}" + + if psInput.CtrIds { + headers += "\tIDS" + row += "\t{{.ContainerIds}}" + } + if psInput.CtrNames { + headers += "\tNAMES" + row += "\t{{.ContainerNames}}" + } + if psInput.CtrStatus { + headers += "\tSTATUS" + row += "\t{{.ContainerStatuses}}" + } + if psInput.Namespace { + headers += "\tCGROUP\tNAMESPACES" + row += "\t{{.Cgroup}}\t{{.Namespace}}" + } + if !psInput.CtrStatus && !psInput.CtrNames && !psInput.CtrIds { + headers += "\t# OF CONTAINERS" + row += "\t{{.NumberOfContainers}}" + + } + headers += "\tINFRA ID\n" + if noTrunc { + row += "\t{{.InfraId}}\n" + } else { + row += "\t{{slice .InfraId 0 12}}\n" + } + return headers, row +} + +// ListPodReporter is a struct for pod ps output +type ListPodReporter struct { + *entities.ListPodsReport +} + +// Created returns a human readable created time/date +func (l ListPodReporter) Created() string { + return units.HumanDuration(time.Since(l.ListPodsReport.Created)) + " ago" +} + +// NumberofContainers returns an int representation for +// the number of containers belonging to the pod +func (l ListPodReporter) NumberOfContainers() int { + return len(l.Containers) +} + +// Added for backwards compatibility with podmanv1 +func (l ListPodReporter) InfraID() string { + return l.InfraId() +} + +// InfraId returns the infra container id for the pod +// depending on trunc +func (l ListPodReporter) InfraId() string { + if noTrunc { + return l.ListPodsReport.InfraId + } + return l.ListPodsReport.InfraId[0:12] +} + +func (l ListPodReporter) ContainerIds() string { + var ctrids []string + for _, c := range l.Containers { + id := c.Id + if !noTrunc { + id = id[0:12] + } + ctrids = append(ctrids, id) + } + return strings.Join(ctrids, ",") +} + +func (l ListPodReporter) ContainerNames() string { + var ctrNames []string + for _, c := range l.Containers { + ctrNames = append(ctrNames, c.Names) + } + return strings.Join(ctrNames, ",") +} + +func (l ListPodReporter) ContainerStatuses() string { + var statuses []string + for _, c := range l.Containers { + statuses = append(statuses, c.Status) + } + return strings.Join(statuses, ",") +} diff --git a/cmd/podman/pods/restart.go b/cmd/podman/pods/restart.go new file mode 100644 index 000000000..1f617a277 --- /dev/null +++ b/cmd/podman/pods/restart.go @@ -0,0 +1,68 @@ +package pods + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + podRestartDescription = `The pod ID or name can be used. + + All of the containers within each of the specified pods will be restarted. If a container in a pod is not currently running it will be started.` + restartCommand = &cobra.Command{ + Use: "restart [flags] POD [POD...]", + Short: "Restart one or more pods", + Long: podRestartDescription, + RunE: restart, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, + Example: `podman pod restart podID1 podID2 + podman pod restart --latest + podman pod restart --all`, + } +) + +var ( + restartOptions = entities.PodRestartOptions{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: restartCommand, + Parent: podCmd, + }) + + flags := restartCommand.Flags() + flags.BoolVarP(&restartOptions.All, "all", "a", false, "Restart all running pods") + flags.BoolVarP(&restartOptions.Latest, "latest", "l", false, "Restart the latest pod podman is aware of") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} + +func restart(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + ) + responses, err := registry.ContainerEngine().PodRestart(context.Background(), args, restartOptions) + if err != nil { + return err + } + // in the cli, first we print out all the successful attempts + for _, r := range responses { + if len(r.Errs) == 0 { + fmt.Println(r.Id) + } else { + errs = append(errs, r.Errs...) + } + } + return errs.PrintErrors() +} diff --git a/cmd/podman/pods/rm.go b/cmd/podman/pods/rm.go new file mode 100644 index 000000000..ea3a6476a --- /dev/null +++ b/cmd/podman/pods/rm.go @@ -0,0 +1,71 @@ +package pods + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + podRmDescription = fmt.Sprintf(`podman rm will remove one or more stopped pods and their containers from the host. + + The pod name or ID can be used. A pod with containers will not be removed without --force. If --force is specified, all containers will be stopped, then removed.`) + rmCommand = &cobra.Command{ + Use: "rm [flags] POD [POD...]", + Short: "Remove one or more pods", + Long: podRmDescription, + RunE: rm, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, + Example: `podman pod rm mywebserverpod + podman pod rm -f 860a4b23 + podman pod rm -f -a`, + } +) + +var ( + rmOptions = entities.PodRmOptions{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: rmCommand, + Parent: podCmd, + }) + + flags := rmCommand.Flags() + flags.BoolVarP(&rmOptions.All, "all", "a", false, "Restart all running pods") + flags.BoolVarP(&rmOptions.Force, "force", "f", false, "Force removal of a running pod by first stopping all containers, then removing all containers in the pod. The default is false") + flags.BoolVarP(&rmOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified pod is missing") + flags.BoolVarP(&rmOptions.Latest, "latest", "l", false, "Restart the latest pod podman is aware of") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + _ = flags.MarkHidden("ignore") + } +} + +func rm(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + ) + responses, err := registry.ContainerEngine().PodRm(context.Background(), args, rmOptions) + if err != nil { + return err + } + // in the cli, first we print out all the successful attempts + for _, r := range responses { + if r.Err == nil { + fmt.Println(r.Id) + } else { + errs = append(errs, r.Err) + } + } + return errs.PrintErrors() +} diff --git a/cmd/podman/pods/start.go b/cmd/podman/pods/start.go new file mode 100644 index 000000000..d0150a3c2 --- /dev/null +++ b/cmd/podman/pods/start.go @@ -0,0 +1,68 @@ +package pods + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + podStartDescription = `The pod name or ID can be used. + + All containers defined in the pod will be started.` + startCommand = &cobra.Command{ + Use: "start [flags] POD [POD...]", + Short: "Start one or more pods", + Long: podStartDescription, + RunE: start, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, + Example: `podman pod start podID + podman pod start --latest + podman pod start --all`, + } +) + +var ( + startOptions = entities.PodStartOptions{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: startCommand, + Parent: podCmd, + }) + + flags := startCommand.Flags() + flags.BoolVarP(&startOptions.All, "all", "a", false, "Restart all running pods") + flags.BoolVarP(&startOptions.Latest, "latest", "l", false, "Restart the latest pod podman is aware of") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} + +func start(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + ) + responses, err := registry.ContainerEngine().PodStart(context.Background(), args, startOptions) + if err != nil { + return err + } + // in the cli, first we print out all the successful attempts + for _, r := range responses { + if len(r.Errs) == 0 { + fmt.Println(r.Id) + } else { + errs = append(errs, r.Errs...) + } + } + return errs.PrintErrors() +} diff --git a/cmd/podman/pods/stop.go b/cmd/podman/pods/stop.go new file mode 100644 index 000000000..683d9c00a --- /dev/null +++ b/cmd/podman/pods/stop.go @@ -0,0 +1,79 @@ +package pods + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + podStopDescription = `The pod name or ID can be used. + + This command will stop all running containers in each of the specified pods.` + + stopCommand = &cobra.Command{ + Use: "stop [flags] POD [POD...]", + Short: "Stop one or more pods", + Long: podStopDescription, + RunE: stop, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, + Example: `podman pod stop mywebserverpod + podman pod stop --latest + podman pod stop --time 0 490eb 3557fb`, + } +) + +var ( + stopOptions = entities.PodStopOptions{ + Timeout: -1, + } + timeout uint +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: stopCommand, + Parent: podCmd, + }) + flags := stopCommand.Flags() + flags.BoolVarP(&stopOptions.All, "all", "a", false, "Stop all running pods") + flags.BoolVarP(&stopOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified pod is missing") + flags.BoolVarP(&stopOptions.Latest, "latest", "l", false, "Stop the latest pod podman is aware of") + flags.UintVarP(&timeout, "time", "t", 0, "Seconds to wait for pod stop before killing the container") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + _ = flags.MarkHidden("ignore") + + } + flags.SetNormalizeFunc(utils.AliasFlags) +} + +func stop(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + ) + if cmd.Flag("time").Changed { + stopOptions.Timeout = int(timeout) + } + responses, err := registry.ContainerEngine().PodStop(context.Background(), args, stopOptions) + if err != nil { + return err + } + // in the cli, first we print out all the successful attempts + for _, r := range responses { + if len(r.Errs) == 0 { + fmt.Println(r.Id) + } else { + errs = append(errs, r.Errs...) + } + } + return errs.PrintErrors() +} diff --git a/cmd/podman/pods/top.go b/cmd/podman/pods/top.go new file mode 100644 index 000000000..ad602f4ea --- /dev/null +++ b/cmd/podman/pods/top.go @@ -0,0 +1,86 @@ +package pods + +import ( + "context" + "fmt" + "os" + "strings" + "text/tabwriter" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/psgo" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + topDescription = fmt.Sprintf(`Specify format descriptors to alter the output. + + You may run "podman pod top -l pid pcpu seccomp" to print the process ID, the CPU percentage and the seccomp mode of each process of the latest pod. + Format Descriptors: + %s`, strings.Join(psgo.ListDescriptors(), ",")) + + topOptions = entities.PodTopOptions{} + + topCommand = &cobra.Command{ + Use: "top [flags] POD [FORMAT-DESCRIPTORS|ARGS]", + Short: "Display the running processes in a pod", + Long: topDescription, + RunE: top, + Args: cobra.ArbitraryArgs, + Example: `podman pod top podID +podman pod top --latest +podman pod top podID pid seccomp args %C +podman pod top podID -eo user,pid,comm`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: topCommand, + Parent: podCmd, + }) + + flags := topCommand.Flags() + flags.SetInterspersed(false) + flags.BoolVar(&topOptions.ListDescriptors, "list-descriptors", false, "") + flags.BoolVarP(&topOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + + _ = flags.MarkHidden("list-descriptors") // meant only for bash completion + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} + +func top(cmd *cobra.Command, args []string) error { + if topOptions.ListDescriptors { + fmt.Println(strings.Join(psgo.ListDescriptors(), "\n")) + return nil + } + + if len(args) < 1 && !topOptions.Latest { + return errors.Errorf("you must provide the name or id of a running pod") + } + + if topOptions.Latest { + topOptions.Descriptors = args + } else { + topOptions.NameOrID = args[0] + topOptions.Descriptors = args[1:] + } + + topResponse, err := registry.ContainerEngine().PodTop(context.Background(), topOptions) + if err != nil { + return err + } + + w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) + for _, proc := range topResponse.Value { + if _, err := fmt.Fprintln(w, proc); err != nil { + return err + } + } + return w.Flush() +} diff --git a/cmd/podman/pods/unpause.go b/cmd/podman/pods/unpause.go new file mode 100644 index 000000000..b30bd930a --- /dev/null +++ b/cmd/podman/pods/unpause.go @@ -0,0 +1,66 @@ +package pods + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + podUnpauseDescription = `The podman unpause command will unpause all "paused" containers assigned to the pod. + + The pod name or ID can be used.` + unpauseCommand = &cobra.Command{ + Use: "unpause [flags] POD [POD...]", + Short: "Unpause one or more pods", + Long: podUnpauseDescription, + RunE: unpause, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, + Example: `podman pod unpause podID1 podID2 + podman pod unpause --all + podman pod unpause --latest`, + } +) + +var ( + unpauseOptions entities.PodunpauseOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: unpauseCommand, + Parent: podCmd, + }) + flags := unpauseCommand.Flags() + flags.BoolVarP(&unpauseOptions.All, "all", "a", false, "Pause all running pods") + flags.BoolVarP(&unpauseOptions.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} +func unpause(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + ) + responses, err := registry.ContainerEngine().PodUnpause(context.Background(), args, unpauseOptions) + if err != nil { + return err + } + // in the cli, first we print out all the successful attempts + for _, r := range responses { + if len(r.Errs) == 0 { + fmt.Println(r.Id) + } else { + errs = append(errs, r.Errs...) + } + } + return errs.PrintErrors() +} diff --git a/cmd/podman/pods_prune.go b/cmd/podman/pods_prune.go deleted file mode 100644 index 1c5c0bb58..000000000 --- a/cmd/podman/pods_prune.go +++ /dev/null @@ -1,50 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - podPruneCommand cliconfig.PodPruneValues - podPruneDescription = ` - podman pod prune - - Removes all exited pods -` - _prunePodsCommand = &cobra.Command{ - Use: "prune", - Args: noSubArgs, - Short: "Remove all stopped pods and their containers", - Long: podPruneDescription, - RunE: func(cmd *cobra.Command, args []string) error { - podPruneCommand.InputArgs = args - podPruneCommand.GlobalFlags = MainGlobalOpts - return podPruneCmd(&podPruneCommand) - }, - } -) - -func init() { - podPruneCommand.Command = _prunePodsCommand - podPruneCommand.SetHelpTemplate(HelpTemplate()) - podPruneCommand.SetUsageTemplate(UsageTemplate()) - flags := podPruneCommand.Flags() - flags.BoolVarP(&podPruneCommand.Force, "force", "f", false, "Force removal of all running pods. The default is false") -} - -func podPruneCmd(c *cliconfig.PodPruneValues) error { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - ok, failures, err := runtime.PrunePods(getContext(), c) - if err != nil { - return err - } - return printCmdResults(ok, failures) -} diff --git a/cmd/podman/port.go b/cmd/podman/port.go deleted file mode 100644 index 4bb79a3a2..000000000 --- a/cmd/podman/port.go +++ /dev/null @@ -1,135 +0,0 @@ -package main - -import ( - "fmt" - "strconv" - "strings" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/docker/go-connections/nat" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - portCommand cliconfig.PortValues - portDescription = `List port mappings for the CONTAINER, or lookup the public-facing port that is NAT-ed to the PRIVATE_PORT -` - _portCommand = &cobra.Command{ - Use: "port [flags] CONTAINER [PORT]", - Short: "List port mappings or a specific mapping for the container", - Long: portDescription, - RunE: func(cmd *cobra.Command, args []string) error { - portCommand.InputArgs = args - portCommand.GlobalFlags = MainGlobalOpts - portCommand.Remote = remoteclient - return portCmd(&portCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllLatestAndCIDFile(cmd, args, true, false) - }, - Example: `podman port --all - podman port ctrID 80/tcp - podman port --latest 80`, - } -) - -func init() { - portCommand.Command = _portCommand - portCommand.SetHelpTemplate(HelpTemplate()) - portCommand.SetUsageTemplate(UsageTemplate()) - flags := portCommand.Flags() - - flags.BoolVarP(&portCommand.All, "all", "a", false, "Display port information for all containers") - flags.BoolVarP(&portCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - - markFlagHiddenForRemoteClient("latest", flags) -} - -func portCmd(c *cliconfig.PortValues) error { - var ( - userPort nat.Port - err error - ) - args := c.InputArgs - - if c.Latest && c.All { - return errors.Errorf("the 'all' and 'latest' options cannot be used together") - } - if c.All && len(args) > 0 { - return errors.Errorf("no additional arguments can be used with 'all'") - } - if len(args) == 0 && !c.Latest && !c.All { - return errors.Errorf("you must supply a running container name or id") - } - - port := "" - if len(args) > 1 && !c.Latest { - port = args[1] - } - if len(args) == 1 && c.Latest { - port = args[0] - } - if len(port) > 0 { - fields := strings.Split(port, "/") - if len(fields) > 2 || len(fields) < 1 { - return errors.Errorf("port formats are port/protocol. '%s' is invalid", port) - } - if len(fields) == 1 { - fields = append(fields, "tcp") - } - - userPort, err = nat.NewPort(fields[1], fields[0]) - if err != nil { - return err - } - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - containers, err := runtime.Port(c) - if err != nil { - return err - } - for _, con := range containers { - portmappings, err := con.PortMappings() - if err != nil { - return err - } - var found bool - // Iterate mappings - for _, v := range portmappings { - hostIP := v.HostIP - // Set host IP to 0.0.0.0 if blank - if hostIP == "" { - hostIP = "0.0.0.0" - } - if c.All { - fmt.Printf("%s\t", con.ID()[:12]) - } - // If not searching by port or port/proto, then dump what we see - if port == "" { - fmt.Printf("%d/%s -> %s:%d\n", v.ContainerPort, v.Protocol, hostIP, v.HostPort) - continue - } - containerPort, err := nat.NewPort(v.Protocol, strconv.Itoa(int(v.ContainerPort))) - if err != nil { - return err - } - if containerPort == userPort { - fmt.Printf("%s:%d\n", hostIP, v.HostPort) - found = true - break - } - } - if !found && port != "" { - return errors.Errorf("failed to find published port %q", port) - } - } - - return nil -} diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go deleted file mode 100644 index accd5b51a..000000000 --- a/cmd/podman/ps.go +++ /dev/null @@ -1,410 +0,0 @@ -package main - -import ( - "fmt" - "html/template" - "os" - "reflect" - "sort" - "strings" - "text/tabwriter" - "time" - - tm "github.com/buger/goterm" - "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/pkg/adapter" - "github.com/docker/go-units" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -const ( - hid = "CONTAINER ID" - himage = "IMAGE" - hcommand = "COMMAND" - hcreated = "CREATED" - hstatus = "STATUS" - hports = "PORTS" - hnames = "NAMES" - hsize = "SIZE" - hinfra = "IS INFRA" //nolint - hpod = "POD" - hpodname = "POD NAME" - nspid = "PID" - nscgroup = "CGROUPNS" - nsipc = "IPC" - nsmnt = "MNT" - nsnet = "NET" - nspidns = "PIDNS" - nsuserns = "USERNS" - nsuts = "UTS" -) - -// Type declaration and functions for sorting the PS output -type psSorted []shared.PsContainerOutput - -func (a psSorted) Len() int { return len(a) } -func (a psSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -type psSortedCommand struct{ psSorted } - -func (a psSortedCommand) Less(i, j int) bool { - return a.psSorted[i].Command < a.psSorted[j].Command -} - -type psSortedCreated struct{ psSorted } - -func (a psSortedCreated) Less(i, j int) bool { - return a.psSorted[i].CreatedAt.After(a.psSorted[j].CreatedAt) -} - -type psSortedId struct{ psSorted } - -func (a psSortedId) Less(i, j int) bool { return a.psSorted[i].ID < a.psSorted[j].ID } - -type psSortedImage struct{ psSorted } - -func (a psSortedImage) Less(i, j int) bool { return a.psSorted[i].Image < a.psSorted[j].Image } - -type psSortedNames struct{ psSorted } - -func (a psSortedNames) Less(i, j int) bool { return a.psSorted[i].Names < a.psSorted[j].Names } - -type psSortedPod struct{ psSorted } - -func (a psSortedPod) Less(i, j int) bool { return a.psSorted[i].Pod < a.psSorted[j].Pod } - -type psSortedRunningFor struct{ psSorted } - -func (a psSortedRunningFor) Less(i, j int) bool { - return a.psSorted[j].StartedAt.After(a.psSorted[i].StartedAt) -} - -type psSortedStatus struct{ psSorted } - -func (a psSortedStatus) Less(i, j int) bool { return a.psSorted[i].Status < a.psSorted[j].Status } - -type psSortedSize struct{ psSorted } - -func (a psSortedSize) Less(i, j int) bool { - if a.psSorted[i].Size == nil || a.psSorted[j].Size == nil { - return false - } - return a.psSorted[i].Size.RootFsSize < a.psSorted[j].Size.RootFsSize -} - -var ( - psCommand cliconfig.PsValues - psDescription = "Prints out information about the containers" - _psCommand = cobra.Command{ - Use: "ps", - Args: noSubArgs, - Short: "List containers", - Long: psDescription, - RunE: func(cmd *cobra.Command, args []string) error { - psCommand.InputArgs = args - psCommand.GlobalFlags = MainGlobalOpts - psCommand.Remote = remoteclient - return psCmd(&psCommand) - }, - Example: `podman ps -a - podman ps -a --format "{{.ID}} {{.Image}} {{.Labels}} {{.Mounts}}" - podman ps --size --sort names`, - } -) - -func psInit(command *cliconfig.PsValues) { - command.SetHelpTemplate(HelpTemplate()) - command.SetUsageTemplate(UsageTemplate()) - flags := command.Flags() - flags.BoolVarP(&command.All, "all", "a", false, "Show all the containers, default is only running containers") - flags.StringSliceVarP(&command.Filter, "filter", "f", []string{}, "Filter output based on conditions given") - flags.StringVar(&command.Format, "format", "", "Pretty-print containers to JSON or using a Go template") - flags.IntVarP(&command.Last, "last", "n", -1, "Print the n last created containers (all states)") - flags.BoolVarP(&command.Latest, "latest", "l", false, "Show the latest container created (all states)") - flags.BoolVar(&command.Namespace, "namespace", false, "Display namespace information") - flags.BoolVar(&command.Namespace, "ns", false, "Display namespace information") - flags.BoolVar(&command.NoTrunct, "no-trunc", false, "Display the extended information") - flags.BoolVarP(&command.Pod, "pod", "p", false, "Print the ID and name of the pod the containers are associated with") - flags.BoolVarP(&command.Quiet, "quiet", "q", false, "Print the numeric IDs of the containers only") - flags.BoolVarP(&command.Size, "size", "s", false, "Display the total file sizes") - flags.StringVar(&command.Sort, "sort", "created", "Sort output by command, created, id, image, names, runningfor, size, or status") - flags.BoolVar(&command.Sync, "sync", false, "Sync container state with OCI runtime") - flags.UintVarP(&command.Watch, "watch", "w", 0, "Watch the ps output on an interval in seconds") - - markFlagHiddenForRemoteClient("latest", flags) -} - -func init() { - psCommand.Command = &_psCommand - psInit(&psCommand) -} - -func psCmd(c *cliconfig.PsValues) error { - var ( - watch bool - runtime *adapter.LocalRuntime - err error - ) - - if c.Watch > 0 { - watch = true - } - - if c.Watch > 0 && c.Latest { - return errors.New("the watch and latest flags cannot be used together") - } - - if err := checkFlagsPassed(c); err != nil { - return errors.Wrapf(err, "error with flags passed") - } - if !c.Size { - runtime, err = adapter.GetRuntimeNoStore(getContext(), &c.PodmanCommand) - } else { - runtime, err = adapter.GetRuntime(getContext(), &c.PodmanCommand) - } - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - - defer runtime.DeferredShutdown(false) - - if !watch { - if err := psDisplay(c, runtime); err != nil { - return err - } - } else { - for { - tm.Clear() - tm.MoveCursor(1, 1) - tm.Flush() - if err := psDisplay(c, runtime); err != nil { - return err - } - time.Sleep(time.Duration(c.Watch) * time.Second) - tm.Clear() - tm.MoveCursor(1, 1) - tm.Flush() - } - } - return nil -} - -func printQuiet(containers []shared.PsContainerOutput) error { - for _, c := range containers { - fmt.Println(c.ID) - } - return nil -} - -// checkFlagsPassed checks if mutually exclusive flags are passed together -func checkFlagsPassed(c *cliconfig.PsValues) error { - // latest, and last are mutually exclusive. - if c.Last >= 0 && c.Latest { - return errors.Errorf("last and latest are mutually exclusive") - } - // Filter on status forces all - if len(c.Filter) > 0 { - for _, filter := range c.Filter { - splitFilter := strings.SplitN(filter, "=", 2) - if strings.ToLower(splitFilter[0]) == "status" { - c.All = true - break - } - } - } - // Quiet conflicts with size and namespace and is overridden by a Go - // template. - if c.Quiet { - if c.Size || c.Namespace { - return errors.Errorf("quiet conflicts with size and namespace") - } - if c.Flag("format").Changed && c.Format != formats.JSONString { - // Quiet is overridden by Go template output. - c.Quiet = false - } - } - // Size and namespace conflict with each other - if c.Size && c.Namespace { - return errors.Errorf("size and namespace options conflict") - } - return nil -} - -func sortPsOutput(sortBy string, psOutput psSorted) (psSorted, error) { - switch sortBy { - case "id": - sort.Sort(psSortedId{psOutput}) - case "image": - sort.Sort(psSortedImage{psOutput}) - case "command": - sort.Sort(psSortedCommand{psOutput}) - case "runningfor": - sort.Sort(psSortedRunningFor{psOutput}) - case "status": - sort.Sort(psSortedStatus{psOutput}) - case "size": - sort.Sort(psSortedSize{psOutput}) - case "names": - sort.Sort(psSortedNames{psOutput}) - case "created": - sort.Sort(psSortedCreated{psOutput}) - case "pod": - sort.Sort(psSortedPod{psOutput}) - default: - return nil, errors.Errorf("invalid option for --sort, options are: command, created, id, image, names, runningfor, size, or status") - } - return psOutput, nil -} - -func printFormat(format string, containers []shared.PsContainerOutput) error { - // return immediately if no containers are present - if len(containers) == 0 { - return nil - } - - // Use a tabwriter to align column format - w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) - - // Make a map of the field names for the headers - headerNames := make(map[string]string) - v := reflect.ValueOf(containers[0]) - t := v.Type() - for i := 0; i < t.NumField(); i++ { - headerNames[t.Field(i).Name] = t.Field(i).Name - } - - // Spit out the header if "table" is present in the format - if strings.HasPrefix(format, "table") { - hformat := strings.Replace(strings.TrimSpace(format[5:]), " ", "\t", -1) - format = hformat - headerTmpl, err := template.New("header").Parse(hformat) - if err != nil { - return err - } - if err := headerTmpl.Execute(w, headerNames); err != nil { - return err - } - fmt.Fprintln(w, "") - } - - // Spit out the data rows now - dataTmpl, err := template.New("data").Parse(format) - if err != nil { - return err - } - - for _, container := range containers { - if err := dataTmpl.Execute(w, container); err != nil { - return err - } - fmt.Fprintln(w, "") - } - // Flush the writer - return w.Flush() -} - -func dumpJSON(containers []shared.PsContainerOutput) error { - b, err := json.MarshalIndent(containers, "", " ") - if err != nil { - return err - } - os.Stdout.Write(b) - return nil -} - -func psDisplay(c *cliconfig.PsValues, runtime *adapter.LocalRuntime) error { - var ( - err error - ) - opts := shared.PsOptions{ - All: c.All, - Format: c.Format, - Last: c.Last, - Latest: c.Latest, - NoTrunc: c.NoTrunct, - Pod: c.Pod, - Quiet: c.Quiet, - Size: c.Size, - Namespace: c.Namespace, - Sort: c.Sort, - Sync: c.Sync, - } - - pss, err := runtime.Ps(c, opts) - if err != nil { - return err - } - // Here and down - if opts.Sort != "" { - pss, err = sortPsOutput(opts.Sort, pss) - if err != nil { - return err - } - } - - // If quiet, print only cids and return - if opts.Quiet { - return printQuiet(pss) - } - - // If the user wants their own GO template format - if opts.Format != "" { - if opts.Format == "json" { - return dumpJSON(pss) - } - return printFormat(opts.Format, pss) - } - - // Define a tab writer with stdout as the output - w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - - // Output standard PS headers - if !opts.Namespace { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s", hid, himage, hcommand, hcreated, hstatus, hports, hnames) - // User wants pod info - if opts.Pod { - fmt.Fprintf(w, "\t%s\t%s", hpod, hpodname) - } - //User wants size info - if opts.Size { - fmt.Fprintf(w, "\t%s", hsize) - } - } else { - // Output Namespace headers - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s", hid, hnames, nspid, nscgroup, nsipc, nsmnt, nsnet, nspidns, nsuserns, nsuts) - } - - // Now iterate each container and output its information - for _, container := range pss { - - // Standard PS output - if !opts.Namespace { - fmt.Fprintf(w, "\n%s\t%s\t%s\t%s\t%s\t%s\t%s", container.ID, container.Image, container.Command, container.Created, container.Status, container.Ports, container.Names) - // User wants pod info - if opts.Pod { - fmt.Fprintf(w, "\t%s\t%s", container.Pod, container.PodName) - } - //User wants size info - if opts.Size { - var size string - if container.Size == nil { - size = units.HumanSizeWithPrecision(0, 0) - } else { - size = units.HumanSizeWithPrecision(float64(container.Size.RwSize), 3) + " (virtual " + units.HumanSizeWithPrecision(float64(container.Size.RootFsSize), 3) + ")" - } - fmt.Fprintf(w, "\t%s", size) - } - - } else { - // Print namespace information - ns := runtime.GetNamespaces(container) - fmt.Fprintf(w, "\n%s\t%s\t%d\t%s\t%s\t%s\t%s\t%s\t%s\t%s", container.ID, container.Names, container.Pid, ns.Cgroup, ns.IPC, ns.MNT, ns.NET, ns.PIDNS, ns.User, ns.UTS) - } - - } - fmt.Fprint(w, "\n") - return w.Flush() -} diff --git a/cmd/podman/pull.go b/cmd/podman/pull.go deleted file mode 100644 index f800d68fe..000000000 --- a/cmd/podman/pull.go +++ /dev/null @@ -1,200 +0,0 @@ -package main - -import ( - "fmt" - "io" - "os" - - buildahcli "github.com/containers/buildah/pkg/cli" - "github.com/containers/image/v5/docker" - dockerarchive "github.com/containers/image/v5/docker/archive" - "github.com/containers/image/v5/transports/alltransports" - "github.com/containers/image/v5/types" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/util" - "github.com/docker/distribution/reference" - "github.com/opentracing/opentracing-go" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - pullCommand cliconfig.PullValues - pullDescription = `Pulls an image from a registry and stores it locally. - - An image can be pulled using its tag or digest. If a tag is not specified, the image with the 'latest' tag (if it exists) is pulled.` - _pullCommand = &cobra.Command{ - Use: "pull [flags] IMAGE-PATH", - Short: "Pull an image from a registry", - Long: pullDescription, - RunE: func(cmd *cobra.Command, args []string) error { - pullCommand.InputArgs = args - pullCommand.GlobalFlags = MainGlobalOpts - pullCommand.Remote = remoteclient - return pullCmd(&pullCommand) - }, - Example: `podman pull imageName - podman pull fedora:latest`, - } -) - -func init() { - - if !remote { - _pullCommand.Example = fmt.Sprintf("%s\n podman pull --cert-dir image/certs --authfile temp-auths/myauths.json docker://docker.io/myrepo/finaltest", _pullCommand.Example) - - } - pullCommand.Command = _pullCommand - pullCommand.SetHelpTemplate(HelpTemplate()) - pullCommand.SetUsageTemplate(UsageTemplate()) - flags := pullCommand.Flags() - flags.BoolVar(&pullCommand.AllTags, "all-tags", false, "All tagged images in the repository will be pulled") - flags.StringVar(&pullCommand.Creds, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") - flags.BoolVarP(&pullCommand.Quiet, "quiet", "q", false, "Suppress output information when pulling images") - flags.StringVar(&pullCommand.OverrideArch, "override-arch", "", "use `ARCH` instead of the architecture of the machine for choosing images") - flags.StringVar(&pullCommand.OverrideOS, "override-os", "", "use `OS` instead of the running OS for choosing images") - markFlagHidden(flags, "override-os") - // Disabled flags for the remote client - if !remote { - flags.StringVar(&pullCommand.Authfile, "authfile", buildahcli.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") - flags.StringVar(&pullCommand.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") - flags.StringVar(&pullCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") - flags.BoolVar(&pullCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") - markFlagHidden(flags, "signature-policy") - } -} - -// pullCmd gets the data from the command line and calls pullImage -// to copy an image from a registry to a local machine -func pullCmd(c *cliconfig.PullValues) (retError error) { - defer func() { - if retError != nil && exitCode == 0 { - exitCode = 1 - } - }() - if c.Bool("trace") { - span, _ := opentracing.StartSpanFromContext(Ctx, "pullCmd") - defer span.Finish() - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - args := c.InputArgs - if len(args) == 0 { - return errors.Errorf("an image name must be specified") - } - if len(args) > 1 { - return errors.Errorf("too many arguments. Requires exactly 1") - } - - if c.Authfile != "" { - if _, err := os.Stat(c.Authfile); err != nil { - return errors.Wrapf(err, "error getting authfile %s", c.Authfile) - } - } - - ctx := getContext() - imageName := args[0] - - imageRef, err := alltransports.ParseImageName(imageName) - if err != nil { - imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s://%s", docker.Transport.Name(), imageName)) - if err != nil { - return errors.Errorf("invalid image reference %q", imageName) - } - } - - var writer io.Writer - if !c.Quiet { - writer = os.Stderr - } - // Special-case for docker-archive which allows multiple tags. - if imageRef.Transport().Name() == dockerarchive.Transport.Name() { - newImage, err := runtime.LoadFromArchiveReference(getContext(), imageRef, c.SignaturePolicy, writer) - if err != nil { - return errors.Wrapf(err, "error pulling image %q", imageName) - } - fmt.Println(newImage[0].ID()) - return nil - } - - var registryCreds *types.DockerAuthConfig - if c.Flag("creds").Changed { - creds, err := util.ParseRegistryCreds(c.Creds) - if err != nil { - return err - } - registryCreds = creds - } - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, - DockerCertPath: c.CertDir, - OSChoice: c.OverrideOS, - ArchitectureChoice: c.OverrideArch, - } - if c.IsSet("tls-verify") { - dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) - } - - if !c.Bool("all-tags") { - newImage, err := runtime.New(getContext(), imageName, c.SignaturePolicy, c.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways) - if err != nil { - return errors.Wrapf(err, "error pulling image %q", imageName) - } - fmt.Println(newImage.ID()) - return nil - } - - // --all-tags requires the docker transport - if imageRef.Transport().Name() != docker.Transport.Name() { - return errors.New("--all-tags requires docker transport") - } - - // all-tags doesn't work with a tagged reference, so let's check early - namedRef, err := reference.Parse(imageName) - if err != nil { - return errors.Wrapf(err, "error parsing %q", imageName) - } - if _, isTagged := namedRef.(reference.Tagged); isTagged { - return errors.New("--all-tags requires a reference without a tag") - - } - - systemContext := image.GetSystemContext("", c.Authfile, false) - tags, err := docker.GetRepositoryTags(ctx, systemContext, imageRef) - if err != nil { - return errors.Wrapf(err, "error getting repository tags") - } - - var foundIDs []string - for _, tag := range tags { - name := imageName + ":" + tag - newImage, err := runtime.New(getContext(), name, c.SignaturePolicy, c.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways) - if err != nil { - logrus.Errorf("error pulling image %q", name) - continue - } - foundIDs = append(foundIDs, newImage.ID()) - } - - if len(tags) != len(foundIDs) { - return errors.Errorf("error pulling image %q", imageName) - } - - if len(foundIDs) > 1 { - fmt.Println("Pulled Images:") - } - for _, id := range foundIDs { - fmt.Println(id) - } - - return nil -} diff --git a/cmd/podman/push.go b/cmd/podman/push.go deleted file mode 100644 index b078959ba..000000000 --- a/cmd/podman/push.go +++ /dev/null @@ -1,155 +0,0 @@ -package main - -import ( - "fmt" - "io" - "os" - "strings" - - buildahcli "github.com/containers/buildah/pkg/cli" - "github.com/containers/image/v5/directory" - "github.com/containers/image/v5/manifest" - "github.com/containers/image/v5/types" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/util" - imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - pushCommand cliconfig.PushValues - pushDescription = fmt.Sprintf(`Pushes an image to a specified location. - - The Image "DESTINATION" uses a "transport":"details" format. See podman-push(1) section "DESTINATION" for the expected format.`) - - _pushCommand = &cobra.Command{ - Use: "push [flags] IMAGE REGISTRY", - Short: "Push an image to a specified destination", - Long: pushDescription, - RunE: func(cmd *cobra.Command, args []string) error { - pushCommand.InputArgs = args - pushCommand.GlobalFlags = MainGlobalOpts - pushCommand.Remote = remoteclient - return pushCmd(&pushCommand) - }, - Example: `podman push imageID docker://registry.example.com/repository:tag - podman push imageID oci-archive:/path/to/layout:image:tag`, - } -) - -func init() { - if !remote { - _pushCommand.Example = fmt.Sprintf("%s\n podman push --authfile temp-auths/myauths.json alpine docker://docker.io/myrepo/alpine", _pushCommand.Example) - - } - - pushCommand.Command = _pushCommand - pushCommand.SetHelpTemplate(HelpTemplate()) - pushCommand.SetUsageTemplate(UsageTemplate()) - flags := pushCommand.Flags() - flags.StringVar(&pushCommand.Creds, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") - flags.StringVar(&pushCommand.Digestfile, "digestfile", "", "After copying the image, write the digest of the resulting image to the file") - flags.StringVarP(&pushCommand.Format, "format", "f", "", "Manifest type (oci, v2s1, or v2s2) to use when pushing an image using the 'dir:' transport (default is manifest type of source)") - flags.BoolVarP(&pushCommand.Quiet, "quiet", "q", false, "Don't output progress information when pushing images") - flags.BoolVar(&pushCommand.RemoveSignatures, "remove-signatures", false, "Discard any pre-existing signatures in the image") - flags.StringVar(&pushCommand.SignBy, "sign-by", "", "Add a signature at the destination using the specified key") - - // Disabled flags for the remote client - if !remote { - flags.StringVar(&pushCommand.Authfile, "authfile", buildahcli.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") - flags.StringVar(&pushCommand.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") - flags.BoolVar(&pushCommand.Compress, "compress", false, "Compress tarball image layers when pushing to a directory using the 'dir' transport. (default is same compression type as source)") - flags.StringVar(&pushCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") - flags.BoolVar(&pushCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") - markFlagHidden(flags, "signature-policy") - } -} - -func pushCmd(c *cliconfig.PushValues) error { - var ( - registryCreds *types.DockerAuthConfig - destName string - ) - - if c.Authfile != "" { - if _, err := os.Stat(c.Authfile); err != nil { - return errors.Wrapf(err, "error getting authfile %s", c.Authfile) - } - } - - args := c.InputArgs - if len(args) == 0 || len(args) > 2 { - return errors.New("podman push requires at least one image name, and optionally a second to specify a different destination name") - } - srcName := args[0] - switch len(args) { - case 1: - destName = args[0] - case 2: - destName = args[1] - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not create runtime") - } - defer runtime.DeferredShutdown(false) - - // --compress and --format can only be used for the "dir" transport - splitArg := strings.SplitN(destName, ":", 2) - - if c.IsSet("compress") || c.Flag("format").Changed { - if splitArg[0] != directory.Transport.Name() { - return errors.Errorf("--compress and --format can be set only when pushing to a directory using the 'dir' transport") - } - } - - certPath := c.CertDir - removeSignatures := c.RemoveSignatures - signBy := c.SignBy - - if c.Flag("creds").Changed { - creds, err := util.ParseRegistryCreds(c.Creds) - if err != nil { - return err - } - registryCreds = creds - } - - var writer io.Writer - if !c.Quiet { - writer = os.Stderr - } - - var manifestType string - if c.Flag("format").Changed { - switch c.String("format") { - case "oci": - manifestType = imgspecv1.MediaTypeImageManifest - case "v2s1": - manifestType = manifest.DockerV2Schema1SignedMediaType - case "v2s2", "docker": - manifestType = manifest.DockerV2Schema2MediaType - default: - return fmt.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", c.String("format")) - } - } - - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, - DockerCertPath: certPath, - } - if c.IsSet("tls-verify") { - dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) - } - - so := image.SigningOptions{ - RemoveSignatures: removeSignatures, - SignBy: signBy, - } - - return runtime.Push(getContext(), srcName, destName, manifestType, c.Authfile, c.String("digestfile"), c.SignaturePolicy, writer, c.Compress, so, &dockerRegistryOptions, nil) -} diff --git a/cmd/podman/refresh.go b/cmd/podman/refresh.go deleted file mode 100644 index b21a4ff79..000000000 --- a/cmd/podman/refresh.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - refreshCommand cliconfig.RefreshValues - refreshDescription = `Resets the state of all containers to handle database changes after a Podman upgrade. - - All running containers will be restarted. -` - _refreshCommand = &cobra.Command{ - Use: "refresh", - Args: noSubArgs, - Short: "Refresh container state", - Long: refreshDescription, - RunE: func(cmd *cobra.Command, args []string) error { - refreshCommand.InputArgs = args - refreshCommand.GlobalFlags = MainGlobalOpts - refreshCommand.Remote = remoteclient - return refreshCmd(&refreshCommand) - }, - } -) - -func init() { - _refreshCommand.Hidden = true - refreshCommand.Command = _refreshCommand - refreshCommand.SetHelpTemplate(HelpTemplate()) - refreshCommand.SetUsageTemplate(UsageTemplate()) -} - -func refreshCmd(c *cliconfig.RefreshValues) error { - runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - allCtrs, err := runtime.GetAllContainers() - if err != nil { - return err - } - - ctx := getContext() - - var lastError error - for _, ctr := range allCtrs { - if err := ctr.Refresh(ctx); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "error refreshing container %s state", ctr.ID()) - } - } - - return lastError -} diff --git a/cmd/podman/registry/config.go b/cmd/podman/registry/config.go new file mode 100644 index 000000000..fc6eb538e --- /dev/null +++ b/cmd/podman/registry/config.go @@ -0,0 +1,114 @@ +package registry + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "sync" + + "github.com/containers/common/pkg/config" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" +) + +const ( + ParentNSRequired = "ParentNSRequired" +) + +var ( + podmanOptions entities.PodmanConfig + podmanSync sync.Once +) + +// PodmanConfig returns an entities.PodmanConfig built up from +// environment and CLI +func PodmanConfig() *entities.PodmanConfig { + podmanSync.Do(newPodmanConfig) + return &podmanOptions +} + +func newPodmanConfig() { + if err := setXdgDirs(); err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + var mode entities.EngineMode + switch runtime.GOOS { + case "darwin": + fallthrough + case "windows": + mode = entities.TunnelMode + case "linux": + mode = entities.ABIMode + default: + fmt.Fprintf(os.Stderr, "%s is not a supported OS", runtime.GOOS) + os.Exit(1) + } + + // cobra.Execute() may not be called yet, so we peek at os.Args. + for _, v := range os.Args { + // Prefix checking works because of how default EngineMode's + // have been defined. + if strings.HasPrefix(v, "--remote") { + mode = entities.TunnelMode + } + } + + // FIXME: for rootless, add flag to get the path to override configuration + cfg, err := config.NewConfig("") + if err != nil { + fmt.Fprint(os.Stderr, "Failed to obtain podman configuration: "+err.Error()) + os.Exit(1) + } + + cfg.Network.NetworkConfigDir = cfg.Network.CNIPluginDirs[0] + if rootless.IsRootless() { + cfg.Network.NetworkConfigDir = "" + } + + podmanOptions = entities.PodmanConfig{Config: cfg, EngineMode: mode} +} + +// SetXdgDirs ensures the XDG_RUNTIME_DIR env and XDG_CONFIG_HOME variables are set. +// containers/image uses XDG_RUNTIME_DIR to locate the auth file, XDG_CONFIG_HOME is +// use for the libpod.conf configuration file. +func setXdgDirs() error { + if !rootless.IsRootless() { + return nil + } + + // Setup XDG_RUNTIME_DIR + if _, found := os.LookupEnv("XDG_RUNTIME_DIR"); !found { + dir, err := util.GetRuntimeDir() + if err != nil { + return err + } + if err := os.Setenv("XDG_RUNTIME_DIR", dir); err != nil { + return errors.Wrapf(err, "cannot set XDG_RUNTIME_DIR="+dir) + } + } + + if _, found := os.LookupEnv("DBUS_SESSION_BUS_ADDRESS"); !found { + sessionAddr := filepath.Join(os.Getenv("XDG_RUNTIME_DIR"), "bus") + if _, err := os.Stat(sessionAddr); err == nil { + os.Setenv("DBUS_SESSION_BUS_ADDRESS", "unix:path="+sessionAddr) + } + } + + // Setup XDG_CONFIG_HOME + if _, found := os.LookupEnv("XDG_CONFIG_HOME"); !found { + cfgHomeDir, err := util.GetRootlessConfigHomeDir() + if err != nil { + return err + } + if err := os.Setenv("XDG_CONFIG_HOME", cfgHomeDir); err != nil { + return errors.Wrapf(err, "cannot set XDG_CONFIG_HOME="+cfgHomeDir) + } + } + return nil +} diff --git a/cmd/podman/registry/registry.go b/cmd/podman/registry/registry.go new file mode 100644 index 000000000..2e9d59d10 --- /dev/null +++ b/cmd/podman/registry/registry.go @@ -0,0 +1,116 @@ +package registry + +import ( + "context" + + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/domain/infra" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +// DefaultAPIAddress is the default address of the REST socket +const DefaultAPIAddress = "unix:/run/podman/podman.sock" + +// DefaultVarlinkAddress is the default address of the varlink socket +const DefaultVarlinkAddress = "unix:/run/podman/io.podman" + +type CliCommand struct { + Mode []entities.EngineMode + Command *cobra.Command + Parent *cobra.Command +} + +const ExecErrorCodeGeneric = 125 + +var ( + cliCtx context.Context + containerEngine entities.ContainerEngine + exitCode = ExecErrorCodeGeneric + imageEngine entities.ImageEngine + + // Commands holds the cobra.Commands to present to the user, including + // parent if not a child of "root" + Commands []CliCommand +) + +func SetExitCode(code int) { + exitCode = code +} + +func GetExitCode() int { + return exitCode +} + +func ImageEngine() entities.ImageEngine { + return imageEngine +} + +// NewImageEngine is a wrapper for building an ImageEngine to be used for PreRunE functions +func NewImageEngine(cmd *cobra.Command, args []string) (entities.ImageEngine, error) { + if imageEngine == nil { + podmanOptions.FlagSet = cmd.Flags() + engine, err := infra.NewImageEngine(&podmanOptions) + if err != nil { + return nil, err + } + imageEngine = engine + } + return imageEngine, nil +} + +func ContainerEngine() entities.ContainerEngine { + return containerEngine +} + +// NewContainerEngine is a wrapper for building an ContainerEngine to be used for PreRunE functions +func NewContainerEngine(cmd *cobra.Command, args []string) (entities.ContainerEngine, error) { + if containerEngine == nil { + podmanOptions.FlagSet = cmd.Flags() + engine, err := infra.NewContainerEngine(&podmanOptions) + if err != nil { + return nil, err + } + containerEngine = engine + } + return containerEngine, nil +} + +func SubCommandExists(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + return errors.Errorf("unrecognized command `%[1]s %[2]s`\nTry '%[1]s --help' for more information.", cmd.CommandPath(), args[0]) + } + return errors.Errorf("missing command '%[1]s COMMAND'\nTry '%[1]s --help' for more information.", cmd.CommandPath()) +} + +// IdOrLatestArgs used to validate a nameOrId was provided or the "--latest" flag +func IdOrLatestArgs(cmd *cobra.Command, args []string) error { + if len(args) > 1 || (len(args) == 0 && !cmd.Flag("latest").Changed) { + return errors.New(`command requires a name, id or the "--latest" flag`) + } + return nil +} + +type PodmanOptionsKey struct{} + +func Context() context.Context { + if cliCtx == nil { + cliCtx = ContextWithOptions(context.Background()) + } + return cliCtx +} + +func ContextWithOptions(ctx context.Context) context.Context { + cliCtx = context.WithValue(ctx, PodmanOptionsKey{}, podmanOptions) + return cliCtx +} + +// GetContextWithOptions deprecated, use NewContextWithOptions() +func GetContextWithOptions() context.Context { + return ContextWithOptions(context.Background()) +} + +// GetContext deprecated, use Context() +func GetContext() context.Context { + return Context() +} diff --git a/cmd/podman/registry/remote.go b/cmd/podman/registry/remote.go new file mode 100644 index 000000000..95870750e --- /dev/null +++ b/cmd/podman/registry/remote.go @@ -0,0 +1,9 @@ +package registry + +import ( + "github.com/containers/libpod/pkg/domain/entities" +) + +func IsRemote() bool { + return podmanOptions.EngineMode == entities.TunnelMode +} diff --git a/cmd/podman/remoteclientconfig/config.go b/cmd/podman/remoteclientconfig/config.go deleted file mode 100644 index 3faa7954a..000000000 --- a/cmd/podman/remoteclientconfig/config.go +++ /dev/null @@ -1,24 +0,0 @@ -package remoteclientconfig - -const remoteConfigFileName string = "podman-remote.conf" - -// RemoteConfig describes the podman remote configuration file -type RemoteConfig struct { - Connections map[string]RemoteConnection -} - -// RemoteConnection describes the attributes of a podman-remote endpoint -type RemoteConnection struct { - Destination string `toml:"destination"` - Username string `toml:"username"` - IsDefault bool `toml:"default"` - Port int `toml:"port"` - IdentityFile string `toml:"identity_file"` - IgnoreHosts bool `toml:"ignore_hosts"` -} - -// GetConfigFilePath is a simple helper to export the configuration file's -// path based on arch, etc -func GetConfigFilePath() string { - return getConfigFilePath() -} diff --git a/cmd/podman/remoteclientconfig/config_darwin.go b/cmd/podman/remoteclientconfig/config_darwin.go deleted file mode 100644 index dddb217ac..000000000 --- a/cmd/podman/remoteclientconfig/config_darwin.go +++ /dev/null @@ -1,12 +0,0 @@ -package remoteclientconfig - -import ( - "path/filepath" - - "github.com/containers/storage/pkg/homedir" -) - -func getConfigFilePath() string { - homeDir := homedir.Get() - return filepath.Join(homeDir, ".config", "containers", remoteConfigFileName) -} diff --git a/cmd/podman/remoteclientconfig/config_linux.go b/cmd/podman/remoteclientconfig/config_linux.go deleted file mode 100644 index afcf73e6d..000000000 --- a/cmd/podman/remoteclientconfig/config_linux.go +++ /dev/null @@ -1,17 +0,0 @@ -package remoteclientconfig - -import ( - "os" - "path/filepath" - - "github.com/containers/storage/pkg/homedir" -) - -func getConfigFilePath() string { - path := os.Getenv("XDG_CONFIG_HOME") - if path == "" { - homeDir := homedir.Get() - path = filepath.Join(homeDir, ".config") - } - return filepath.Join(path, "containers", remoteConfigFileName) -} diff --git a/cmd/podman/remoteclientconfig/config_windows.go b/cmd/podman/remoteclientconfig/config_windows.go deleted file mode 100644 index 3a8f3bc7a..000000000 --- a/cmd/podman/remoteclientconfig/config_windows.go +++ /dev/null @@ -1,12 +0,0 @@ -package remoteclientconfig - -import ( - "path/filepath" - - "github.com/containers/storage/pkg/homedir" -) - -func getConfigFilePath() string { - homeDir := homedir.Get() - return filepath.Join(homeDir, "AppData", "podman", remoteConfigFileName) -} diff --git a/cmd/podman/remoteclientconfig/configfile.go b/cmd/podman/remoteclientconfig/configfile.go deleted file mode 100644 index 56a868733..000000000 --- a/cmd/podman/remoteclientconfig/configfile.go +++ /dev/null @@ -1,64 +0,0 @@ -package remoteclientconfig - -import ( - "io" - - "github.com/BurntSushi/toml" - "github.com/pkg/errors" -) - -// ReadRemoteConfig takes an io.Reader representing the remote configuration -// file and returns a remoteconfig -func ReadRemoteConfig(reader io.Reader) (*RemoteConfig, error) { - var remoteConfig RemoteConfig - // the configuration file does not exist - if reader == nil { - return &remoteConfig, ErrNoConfigationFile - } - _, err := toml.DecodeReader(reader, &remoteConfig) - if err != nil { - return nil, err - } - // We need to validate each remote connection has fields filled out - for name, conn := range remoteConfig.Connections { - if len(conn.Destination) < 1 { - return nil, errors.Errorf("connection %q has no destination defined", name) - } - } - return &remoteConfig, err -} - -// GetDefault returns the default RemoteConnection. If there is only one -// connection, we assume it is the default as well -func (r *RemoteConfig) GetDefault() (*RemoteConnection, error) { - if len(r.Connections) == 0 { - return nil, ErrNoDefinedConnections - } - for _, v := range r.Connections { - v := v - if len(r.Connections) == 1 { - // if there is only one defined connection, we assume it is - // the default whether tagged as such or not - return &v, nil - } - if v.IsDefault { - return &v, nil - } - } - return nil, ErrNoDefaultConnection -} - -// GetRemoteConnection "looks up" a remote connection by name and returns it in the -// form of a RemoteConnection -func (r *RemoteConfig) GetRemoteConnection(name string) (*RemoteConnection, error) { - if len(r.Connections) == 0 { - return nil, ErrNoDefinedConnections - } - for k, v := range r.Connections { - v := v - if k == name { - return &v, nil - } - } - return nil, errors.Wrap(ErrConnectionNotFound, name) -} diff --git a/cmd/podman/remoteclientconfig/configfile_test.go b/cmd/podman/remoteclientconfig/configfile_test.go deleted file mode 100644 index 4ad2c2100..000000000 --- a/cmd/podman/remoteclientconfig/configfile_test.go +++ /dev/null @@ -1,212 +0,0 @@ -package remoteclientconfig - -import ( - "io" - "reflect" - "strings" - "testing" -) - -var goodConfig = ` -[connections] - -[connections.homer] -destination = "192.168.1.1" -username = "myuser" -port = 22 -default = true - -[connections.bart] -destination = "foobar.com" -username = "root" -port = 22 -` -var noDest = ` -[connections] - -[connections.homer] -destination = "192.168.1.1" -username = "myuser" -default = true -port = 22 - -[connections.bart] -username = "root" -port = 22 -` - -var noUser = ` -[connections] - -[connections.homer] -destination = "192.168.1.1" -port = 22 -` - -func makeGoodResult() *RemoteConfig { - var goodConnections = make(map[string]RemoteConnection) - goodConnections["homer"] = RemoteConnection{ - Destination: "192.168.1.1", - Username: "myuser", - IsDefault: true, - Port: 22, - } - goodConnections["bart"] = RemoteConnection{ - Destination: "foobar.com", - Username: "root", - Port: 22, - } - var goodResult = RemoteConfig{ - Connections: goodConnections, - } - return &goodResult -} - -func makeNoUserResult() *RemoteConfig { - var goodConnections = make(map[string]RemoteConnection) - goodConnections["homer"] = RemoteConnection{ - Destination: "192.168.1.1", - Port: 22, - } - var goodResult = RemoteConfig{ - Connections: goodConnections, - } - return &goodResult -} - -func TestReadRemoteConfig(t *testing.T) { - type args struct { - reader io.Reader - } - tests := []struct { - name string - args args - want *RemoteConfig - wantErr bool - }{ - // good test should pass - {"good", args{reader: strings.NewReader(goodConfig)}, makeGoodResult(), false}, - // a connection with no destination is an error - {"nodest", args{reader: strings.NewReader(noDest)}, nil, true}, - // a connection with no user is OK - {"nouser", args{reader: strings.NewReader(noUser)}, makeNoUserResult(), false}, - } - for _, tt := range tests { - test := tt - t.Run(tt.name, func(t *testing.T) { - got, err := ReadRemoteConfig(test.args.reader) - if (err != nil) != test.wantErr { - t.Errorf("ReadRemoteConfig() error = %v, wantErr %v", err, test.wantErr) - return - } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("ReadRemoteConfig() = %v, want %v", got, test.want) - } - }) - } -} - -func TestRemoteConfig_GetDefault(t *testing.T) { - good := make(map[string]RemoteConnection) - good["homer"] = RemoteConnection{ - Username: "myuser", - Destination: "192.168.1.1", - IsDefault: true, - } - good["bart"] = RemoteConnection{ - Username: "root", - Destination: "foobar.com", - } - noDefault := make(map[string]RemoteConnection) - noDefault["homer"] = RemoteConnection{ - Username: "myuser", - Destination: "192.168.1.1", - } - noDefault["bart"] = RemoteConnection{ - Username: "root", - Destination: "foobar.com", - } - single := make(map[string]RemoteConnection) - single["homer"] = RemoteConnection{ - Username: "myuser", - Destination: "192.168.1.1", - } - - none := make(map[string]RemoteConnection) - - type fields struct { - Connections map[string]RemoteConnection - } - tests := []struct { - name string - fields fields - want *RemoteConnection - wantErr bool - }{ - // A good toml should return the connection that is marked isDefault - {"good", fields{Connections: makeGoodResult().Connections}, &RemoteConnection{"192.168.1.1", "myuser", true, 22, "", false}, false}, - // If nothing is marked as isDefault and there is more than one connection, error should occur - {"nodefault", fields{Connections: noDefault}, nil, true}, - // if nothing is marked as isDefault but there is only one connection, the one connection is considered the default - {"single", fields{Connections: none}, nil, true}, - } - for _, tt := range tests { - test := tt - t.Run(test.name, func(t *testing.T) { - r := &RemoteConfig{ - Connections: test.fields.Connections, - } - got, err := r.GetDefault() - if (err != nil) != test.wantErr { - t.Errorf("RemoteConfig.GetDefault() error = %v, wantErr %v", err, test.wantErr) - return - } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("RemoteConfig.GetDefault() = %v, want %v", got, test.want) - } - }) - } -} - -func TestRemoteConfig_GetRemoteConnection(t *testing.T) { - type fields struct { - Connections map[string]RemoteConnection - } - type args struct { - name string - } - - blank := make(map[string]RemoteConnection) - tests := []struct { - name string - fields fields - args args - want *RemoteConnection - wantErr bool - }{ - // Good connection - {"goodhomer", fields{Connections: makeGoodResult().Connections}, args{name: "homer"}, &RemoteConnection{"192.168.1.1", "myuser", true, 22, "", false}, false}, - // Good connection - {"goodbart", fields{Connections: makeGoodResult().Connections}, args{name: "bart"}, &RemoteConnection{"foobar.com", "root", false, 22, "", false}, false}, - // Getting an unknown connection should result in error - {"noexist", fields{Connections: makeGoodResult().Connections}, args{name: "foobar"}, nil, true}, - // Getting a connection when there are none should result in an error - {"none", fields{Connections: blank}, args{name: "foobar"}, nil, true}, - } - for _, tt := range tests { - test := tt - t.Run(test.name, func(t *testing.T) { - r := &RemoteConfig{ - Connections: test.fields.Connections, - } - got, err := r.GetRemoteConnection(test.args.name) - if (err != nil) != test.wantErr { - t.Errorf("RemoteConfig.GetRemoteConnection() error = %v, wantErr %v", err, test.wantErr) - return - } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("RemoteConfig.GetRemoteConnection() = %v, want %v", got, test.want) - } - }) - } -} diff --git a/cmd/podman/remoteclientconfig/errors.go b/cmd/podman/remoteclientconfig/errors.go deleted file mode 100644 index 2689d3b49..000000000 --- a/cmd/podman/remoteclientconfig/errors.go +++ /dev/null @@ -1,14 +0,0 @@ -package remoteclientconfig - -import "errors" - -var ( - // ErrNoDefaultConnection no default connection is defined in the podman-remote.conf file - ErrNoDefaultConnection = errors.New("no default connection is defined") - // ErrNoDefinedConnections no connections are defined in the podman-remote.conf file - ErrNoDefinedConnections = errors.New("no remote connections have been defined") - // ErrConnectionNotFound unable to lookup connection by name - ErrConnectionNotFound = errors.New("remote connection not found by name") - // ErrNoConfigationFile no config file found - ErrNoConfigationFile = errors.New("no configuration file found") -) diff --git a/cmd/podman/report/diff.go b/cmd/podman/report/diff.go new file mode 100644 index 000000000..b36189d75 --- /dev/null +++ b/cmd/podman/report/diff.go @@ -0,0 +1,44 @@ +package report + +import ( + "fmt" + "os" + + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/storage/pkg/archive" + jsoniter "github.com/json-iterator/go" + "github.com/pkg/errors" +) + +type ChangesReportJSON struct { + Changed []string `json:"changed,omitempty"` + Added []string `json:"added,omitempty"` + Deleted []string `json:"deleted,omitempty"` +} + +func ChangesToJSON(diffs *entities.DiffReport) error { + body := ChangesReportJSON{} + for _, row := range diffs.Changes { + switch row.Kind { + case archive.ChangeAdd: + body.Added = append(body.Added, row.Path) + case archive.ChangeDelete: + body.Deleted = append(body.Deleted, row.Path) + case archive.ChangeModify: + body.Changed = append(body.Changed, row.Path) + default: + return errors.Errorf("output kind %q not recognized", row.Kind) + } + } + + json := jsoniter.ConfigCompatibleWithStandardLibrary + enc := json.NewEncoder(os.Stdout) + return enc.Encode(body) +} + +func ChangesToTable(diffs *entities.DiffReport) error { + for _, row := range diffs.Changes { + fmt.Fprintln(os.Stdout, row.String()) + } + return nil +} diff --git a/cmd/podman/reset.go b/cmd/podman/reset.go deleted file mode 100644 index 203399047..000000000 --- a/cmd/podman/reset.go +++ /dev/null @@ -1,71 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "os" - "strings" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - systemResetCommand cliconfig.SystemResetValues - systemResetDescription = `Reset podman storage back to default state" - - All containers will be stopped and removed, and all images, volumes and container content will be removed. -` - _systemResetCommand = &cobra.Command{ - Use: "reset", - Args: noSubArgs, - Short: "Reset podman storage", - Long: systemResetDescription, - RunE: func(cmd *cobra.Command, args []string) error { - systemResetCommand.InputArgs = args - systemResetCommand.GlobalFlags = MainGlobalOpts - systemResetCommand.Remote = remoteclient - return systemResetCmd(&systemResetCommand) - }, - } -) - -func init() { - systemResetCommand.Command = _systemResetCommand - flags := systemResetCommand.Flags() - flags.BoolVarP(&systemResetCommand.Force, "force", "f", false, "Do not prompt for confirmation") - - systemResetCommand.SetHelpTemplate(HelpTemplate()) - systemResetCommand.SetUsageTemplate(UsageTemplate()) -} - -func systemResetCmd(c *cliconfig.SystemResetValues) error { - // Prompt for confirmation if --force is not set - if !c.Force { - reader := bufio.NewReader(os.Stdin) - fmt.Print(` -WARNING! This will remove: - - all containers - - all pods - - all images - - all build cache -Are you sure you want to continue? [y/N] `) - answer, err := reader.ReadString('\n') - if err != nil { - return errors.Wrapf(err, "error reading input") - } - if strings.ToLower(answer)[0] != 'y' { - return nil - } - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - // No shutdown, since storage will be destroyed when command completes - - return runtime.Reset() -} diff --git a/cmd/podman/restart.go b/cmd/podman/restart.go deleted file mode 100644 index 4ee043442..000000000 --- a/cmd/podman/restart.go +++ /dev/null @@ -1,77 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - restartCommand cliconfig.RestartValues - restartDescription = fmt.Sprintf(`Restarts one or more running containers. The container ID or name can be used. - - A timeout before forcibly stopping can be set, but defaults to %d seconds.`, defaultContainerConfig.Engine.StopTimeout) - _restartCommand = &cobra.Command{ - Use: "restart [flags] CONTAINER [CONTAINER...]", - Short: "Restart one or more containers", - Long: restartDescription, - RunE: func(cmd *cobra.Command, args []string) error { - restartCommand.InputArgs = args - restartCommand.GlobalFlags = MainGlobalOpts - return restartCmd(&restartCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllLatestAndCIDFile(cmd, args, false, false) - }, - Example: `podman restart ctrID - podman restart --latest - podman restart ctrID1 ctrID2`, - } -) - -func init() { - restartCommand.Command = _restartCommand - restartCommand.SetHelpTemplate(HelpTemplate()) - restartCommand.SetUsageTemplate(UsageTemplate()) - flags := restartCommand.Flags() - flags.BoolVarP(&restartCommand.All, "all", "a", false, "Restart all non-running containers") - flags.BoolVarP(&restartCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - flags.BoolVar(&restartCommand.Running, "running", false, "Restart only running containers when --all is used") - flags.UintVarP(&restartCommand.Timeout, "time", "t", defaultContainerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container") - - markFlagHiddenForRemoteClient("latest", flags) - flags.SetNormalizeFunc(aliasFlags) -} - -func restartCmd(c *cliconfig.RestartValues) error { - all := c.All - if len(c.InputArgs) < 1 && !c.Latest && !all { - return errors.Wrapf(define.ErrInvalidArg, "you must provide at least one container name or ID") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - ok, failures, err := runtime.Restart(getContext(), c) - if err != nil { - if errors.Cause(err) == define.ErrNoSuchCtr { - if len(c.InputArgs) > 1 { - exitCode = define.ExecErrorCodeGeneric - } else { - exitCode = 1 - } - } - return err - } - if len(failures) > 0 { - exitCode = define.ExecErrorCodeGeneric - } - return printCmdResults(ok, failures) -} diff --git a/cmd/podman/restore.go b/cmd/podman/restore.go deleted file mode 100644 index a8db7fa94..000000000 --- a/cmd/podman/restore.go +++ /dev/null @@ -1,96 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - restoreCommand cliconfig.RestoreValues - restoreDescription = ` - podman container restore - - Restores a container from a checkpoint. The container name or ID can be used. -` - _restoreCommand = &cobra.Command{ - Use: "restore [flags] CONTAINER [CONTAINER...]", - Short: "Restores one or more containers from a checkpoint", - Long: restoreDescription, - RunE: func(cmd *cobra.Command, args []string) error { - restoreCommand.InputArgs = args - restoreCommand.GlobalFlags = MainGlobalOpts - restoreCommand.Remote = remoteclient - return restoreCmd(&restoreCommand, cmd) - }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllLatestAndCIDFile(cmd, args, true, false) - }, - Example: `podman container restore ctrID - podman container restore --latest - podman container restore --all`, - } -) - -func init() { - restoreCommand.Command = _restoreCommand - restoreCommand.SetHelpTemplate(HelpTemplate()) - restoreCommand.SetUsageTemplate(UsageTemplate()) - flags := restoreCommand.Flags() - flags.BoolVarP(&restoreCommand.All, "all", "a", false, "Restore all checkpointed containers") - flags.BoolVarP(&restoreCommand.Keep, "keep", "k", false, "Keep all temporary checkpoint files") - flags.BoolVarP(&restoreCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - flags.BoolVar(&restoreCommand.TcpEstablished, "tcp-established", false, "Restore a container with established TCP connections") - flags.StringVarP(&restoreCommand.Import, "import", "i", "", "Restore from exported checkpoint archive (tar.gz)") - flags.StringVarP(&restoreCommand.Name, "name", "n", "", "Specify new name for container restored from exported checkpoint (only works with --import)") - flags.BoolVar(&restoreCommand.IgnoreRootfs, "ignore-rootfs", false, "Do not apply root file-system changes when importing from exported checkpoint") - flags.BoolVar(&restoreCommand.IgnoreStaticIP, "ignore-static-ip", false, "Ignore IP address set via --static-ip") - flags.BoolVar(&restoreCommand.IgnoreStaticMAC, "ignore-static-mac", false, "Ignore MAC address set via --mac-address") - - markFlagHiddenForRemoteClient("latest", flags) -} - -func restoreCmd(c *cliconfig.RestoreValues, cmd *cobra.Command) error { - if rootless.IsRootless() { - return errors.New("restoring a container requires root") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - if c.Import == "" && c.IgnoreRootfs { - return errors.Errorf("--ignore-rootfs can only be used with --import") - } - - if c.Import == "" && c.Name != "" { - return errors.Errorf("--name can only be used with --import") - } - - if c.Name != "" && c.TcpEstablished { - return errors.Errorf("--tcp-established cannot be used with --name") - } - - argLen := len(c.InputArgs) - if c.Import != "" { - if c.All || c.Latest { - return errors.Errorf("Cannot use --import with --all or --latest") - } - if argLen > 0 { - return errors.Errorf("Cannot use --import with positional arguments") - } - } - - if (c.All || c.Latest) && argLen > 0 { - return errors.Errorf("no arguments are needed with --all or --latest") - } - if argLen < 1 && !c.All && !c.Latest && c.Import == "" { - return errors.Errorf("you must provide at least one name or id") - } - - return runtime.Restore(getContext(), c) -} diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go deleted file mode 100644 index 644b0ef76..000000000 --- a/cmd/podman/rm.go +++ /dev/null @@ -1,90 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - rmCommand cliconfig.RmValues - rmDescription = fmt.Sprintf(`Removes one or more containers from the host. The container name or ID can be used. - - Command does not remove images. Running or unusable containers will not be removed without the -f option.`) - _rmCommand = &cobra.Command{ - Use: "rm [flags] CONTAINER [CONTAINER...]", - Short: "Remove one or more containers", - Long: rmDescription, - RunE: func(cmd *cobra.Command, args []string) error { - rmCommand.InputArgs = args - rmCommand.GlobalFlags = MainGlobalOpts - rmCommand.Remote = remoteclient - return rmCmd(&rmCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllLatestAndCIDFile(cmd, args, false, true) - }, - Example: `podman rm imageID - podman rm mywebserver myflaskserver 860a4b23 - podman rm --force --all - podman rm -f c684f0d469f2`, - } -) - -func init() { - rmCommand.Command = _rmCommand - rmCommand.SetHelpTemplate(HelpTemplate()) - rmCommand.SetUsageTemplate(UsageTemplate()) - flags := rmCommand.Flags() - flags.BoolVarP(&rmCommand.All, "all", "a", false, "Remove all containers") - flags.BoolVarP(&rmCommand.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing") - flags.BoolVarP(&rmCommand.Force, "force", "f", false, "Force removal of a running or unusable container. The default is false") - flags.BoolVarP(&rmCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - flags.BoolVar(&rmCommand.Storage, "storage", false, "Remove container from storage library") - flags.BoolVarP(&rmCommand.Volumes, "volumes", "v", false, "Remove anonymous volumes associated with the container") - flags.StringArrayVarP(&rmCommand.CIDFiles, "cidfile", "", nil, "Read the container ID from the file") - markFlagHiddenForRemoteClient("ignore", flags) - markFlagHiddenForRemoteClient("cidfile", flags) - markFlagHiddenForRemoteClient("latest", flags) - markFlagHiddenForRemoteClient("storage", flags) -} - -// rmCmd removes one or more containers -func rmCmd(c *cliconfig.RmValues) error { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - // Storage conflicts with --all/--latest/--volumes/--cidfile/--ignore - if c.Storage { - if c.All || c.Ignore || c.Latest || c.Volumes || c.CIDFiles != nil { - return errors.Errorf("--storage conflicts with --volumes, --all, --latest, --ignore and --cidfile") - } - } - - ok, failures, err := runtime.RemoveContainers(getContext(), c) - if err != nil { - if len(c.InputArgs) < 2 { - exitCode = setExitCode(err) - } - return err - } - - if len(failures) > 0 { - for _, err := range failures { - if errors.Cause(err) == define.ErrWillDeadlock { - logrus.Errorf("Potential deadlock detected - please run 'podman system renumber' to resolve") - } - exitCode = setExitCode(err) - } - } - - return printCmdResults(ok, failures) -} diff --git a/cmd/podman/rmi.go b/cmd/podman/rmi.go deleted file mode 100644 index caaa8984d..000000000 --- a/cmd/podman/rmi.go +++ /dev/null @@ -1,156 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/storage" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - rmiCommand cliconfig.RmiValues - rmiDescription = "Removes one or more previously pulled or locally created images." - _rmiCommand = cobra.Command{ - Use: "rmi [flags] IMAGE [IMAGE...]", - Short: "Removes one or more images from local storage", - Long: rmiDescription, - RunE: func(cmd *cobra.Command, args []string) error { - rmiCommand.InputArgs = args - rmiCommand.GlobalFlags = MainGlobalOpts - rmiCommand.Remote = remoteclient - return rmiCmd(&rmiCommand) - }, - Example: `podman rmi imageID - podman rmi --force alpine - podman rmi c4dfb1609ee2 93fd78260bd1 c0ed59d05ff7`, - } -) - -func rmiInit(command *cliconfig.RmiValues) { - command.SetHelpTemplate(HelpTemplate()) - command.SetUsageTemplate(UsageTemplate()) - flags := command.Flags() - flags.BoolVarP(&command.All, "all", "a", false, "Remove all images") - flags.BoolVarP(&command.Force, "force", "f", false, "Force Removal of the image") -} - -func init() { - rmiCommand.Command = &_rmiCommand - rmiInit(&rmiCommand) -} - -func rmiCmd(c *cliconfig.RmiValues) error { - var ( - lastError error - failureCnt int - ) - - ctx := getContext() - removeAll := c.All - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - args := c.InputArgs - if len(args) == 0 && !removeAll { - return errors.Errorf("image name or ID must be specified") - } - if len(args) > 0 && removeAll { - return errors.Errorf("when using the --all switch, you may not pass any images names or IDs") - } - - images := args - - removeImage := func(img *adapter.ContainerImage) { - response, err := runtime.RemoveImage(ctx, img, c.Force) - if err != nil { - if errors.Cause(err) == storage.ErrImageUsedByContainer { - fmt.Printf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID()) - } - if !adapter.IsImageNotFound(err) { - exitCode = 2 - failureCnt++ - } - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = err - return - } - // Iterate if any images tags were deleted - for _, i := range response.Untagged { - fmt.Printf("Untagged: %s\n", i) - } - // Make sure an image was deleted (and not just untagged); else print it - if len(response.Deleted) > 0 { - fmt.Printf("Deleted: %s\n", response.Deleted) - } - } - - if removeAll { - var imagesToDelete []*adapter.ContainerImage - imagesToDelete, err = runtime.GetRWImages() - if err != nil { - return errors.Wrapf(err, "unable to query local images") - } - 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(ctx) - if err != nil { - return err - } - if isParent { - continue - } - removeImage(i) - } - lastNumberofImages = len(imagesToDelete) - imagesToDelete, err = runtime.GetRWImages() - if err != nil { - return err - } - // If no images are left to delete or there is just one image left and it cannot be deleted, - // lets break out and display the error - if len(imagesToDelete) == 0 || (lastNumberofImages == 1 && lastError != nil) { - break - } - } - } else { - // Create image.image objects for deletion from user input. - // Note that we have to query the storage one-by-one to - // always get the latest state for each image. Otherwise, we - // run inconsistency issues, for instance, with repoTags. - // See https://github.com/containers/libpod/issues/930 as - // an exemplary inconsistency issue. - for _, i := range images { - newImage, err := runtime.NewImageFromLocal(i) - if err != nil { - if lastError != nil { - if !adapter.IsImageNotFound(lastError) { - failureCnt++ - } - fmt.Fprintln(os.Stderr, lastError) - } - lastError = err - continue - } - removeImage(newImage) - } - } - - if adapter.IsImageNotFound(lastError) && failureCnt == 0 { - exitCode = 1 - } - - return lastError -} diff --git a/cmd/podman/root.go b/cmd/podman/root.go new file mode 100644 index 000000000..729ca6aa7 --- /dev/null +++ b/cmd/podman/root.go @@ -0,0 +1,253 @@ +package main + +import ( + "fmt" + "log/syslog" + "os" + "path" + "runtime/pprof" + "strings" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/tracing" + "github.com/containers/libpod/version" + "github.com/opentracing/opentracing-go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + logrusSyslog "github.com/sirupsen/logrus/hooks/syslog" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// HelpTemplate is the help template for podman commands +// This uses the short and long options. +// command should not use this. +const helpTemplate = `{{.Short}} + +Description: + {{.Long}} + +{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` + +// UsageTemplate is the usage template for podman commands +// This blocks the displaying of the global options. The main podman +// command should not use this. +const usageTemplate = `Usage(v2):{{if (and .Runnable (not .HasAvailableSubCommands))}} + {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} + {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} + +Aliases: + {{.NameAndAliases}}{{end}}{{if .HasExample}} + +Examples: + {{.Example}}{{end}}{{if .HasAvailableSubCommands}} + +Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} + +Flags: +{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} +{{end}} +` + +var ( + rootCmd = &cobra.Command{ + Use: path.Base(os.Args[0]), + Long: "Manage pods, containers and images", + SilenceUsage: true, + SilenceErrors: true, + TraverseChildren: true, + PersistentPreRunE: persistentPreRunE, + RunE: registry.SubCommandExists, + PersistentPostRunE: persistentPostRunE, + Version: version.Version, + } + + logLevels = []string{"debug", "info", "warn", "error", "fatal", "panic"} + logLevel = "error" + useSyslog bool +) + +func init() { + // Hooks are called before PersistentPreRunE() + cobra.OnInitialize( + loggingHook, + syslogHook, + ) + + rootFlags(registry.PodmanConfig(), rootCmd.PersistentFlags()) +} + +func Execute() { + if err := rootCmd.ExecuteContext(registry.GetContextWithOptions()); err != nil { + logrus.Error(err) + } else if registry.GetExitCode() == registry.ExecErrorCodeGeneric { + // The exitCode modified from registry.ExecErrorCodeGeneric, + // indicates an application + // running inside of a container failed, as opposed to the + // podman command failed. Must exit with that exit code + // otherwise command exited correctly. + registry.SetExitCode(0) + } + os.Exit(registry.GetExitCode()) +} + +func persistentPreRunE(cmd *cobra.Command, args []string) error { + // TODO: Remove trace statement in podman V2.1 + logrus.Debugf("Called %s.PersistentPreRunE()", cmd.Name()) + + cfg := registry.PodmanConfig() + + // Prep the engines + if _, err := registry.NewImageEngine(cmd, args); err != nil { + return err + } + if _, err := registry.NewContainerEngine(cmd, args); err != nil { + return err + } + + if cmd.Flag("cpu-profile").Changed { + f, err := os.Create(cfg.CpuProfile) + if err != nil { + return errors.Wrapf(err, "unable to create cpu profiling file %s", + cfg.CpuProfile) + } + if err := pprof.StartCPUProfile(f); err != nil { + return err + } + } + + if cmd.Flag("trace").Changed { + tracer, closer := tracing.Init("podman") + opentracing.SetGlobalTracer(tracer) + cfg.SpanCloser = closer + + cfg.Span = tracer.StartSpan("before-context") + cfg.SpanCtx = opentracing.ContextWithSpan(registry.Context(), cfg.Span) + opentracing.StartSpanFromContext(cfg.SpanCtx, cmd.Name()) + } + + // Setup Rootless environment, IFF: + // 1) in ABI mode + // 2) running as non-root + // 3) command doesn't require Parent Namespace + _, found := cmd.Annotations[registry.ParentNSRequired] + if !registry.IsRemote() && rootless.IsRootless() && !found { + err := registry.ContainerEngine().SetupRootless(registry.Context(), cmd) + if err != nil { + return err + } + } + return nil +} + +func persistentPostRunE(cmd *cobra.Command, args []string) error { + // TODO: Remove trace statement in podman V2.1 + logrus.Debugf("Called %s.PersistentPostRunE()", cmd.Name()) + + cfg := registry.PodmanConfig() + if cmd.Flag("cpu-profile").Changed { + pprof.StopCPUProfile() + } + if cmd.Flag("trace").Changed { + cfg.Span.Finish() + cfg.SpanCloser.Close() + } + return nil +} + +func loggingHook() { + var found bool + for _, l := range logLevels { + if l == logLevel { + found = true + break + } + } + if !found { + fmt.Fprintf(os.Stderr, "Log Level \"%s\" is not supported, choose from: %s\n", logLevel, strings.Join(logLevels, ", ")) + os.Exit(1) + } + + level, err := logrus.ParseLevel(logLevel) + if err != nil { + fmt.Fprint(os.Stderr, err.Error()) + os.Exit(1) + } + logrus.SetLevel(level) + + if logrus.IsLevelEnabled(logrus.InfoLevel) { + logrus.Infof("%s filtering at log level %s", os.Args[0], logrus.GetLevel()) + } +} + +func syslogHook() { + if !useSyslog { + return + } + + hook, err := logrusSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "") + if err != nil { + fmt.Fprint(os.Stderr, "Failed to initialize syslog hook: "+err.Error()) + os.Exit(1) + } + if err == nil { + logrus.AddHook(hook) + } +} + +func rootFlags(opts *entities.PodmanConfig, flags *pflag.FlagSet) { + // V2 flags + flags.StringVarP(&opts.Uri, "remote", "r", "", "URL to access Podman service") + flags.StringSliceVar(&opts.Identities, "identity", []string{}, "path to SSH identity file") + + // Override default --help information of `--version` global flag + // TODO: restore -v option for version without breaking -v for volumes + var dummyVersion bool + flags.BoolVar(&dummyVersion, "version", false, "Version of Podman") + + cfg := opts.Config + flags.StringVar(&cfg.Engine.CgroupManager, "cgroup-manager", cfg.Engine.CgroupManager, opts.CGroupUsage) + flags.StringVar(&opts.CpuProfile, "cpu-profile", "", "Path for the cpu profiling results") + flags.StringVar(&opts.ConmonPath, "conmon", "", "Path of the conmon binary") + flags.StringVar(&cfg.Engine.NetworkCmdPath, "network-cmd-path", cfg.Engine.NetworkCmdPath, "Path to the command for configuring the network") + flags.StringVar(&cfg.Network.NetworkConfigDir, "cni-config-dir", cfg.Network.NetworkConfigDir, "Path of the configuration directory for CNI networks") + flags.StringVar(&cfg.Containers.DefaultMountsFile, "default-mounts-file", cfg.Containers.DefaultMountsFile, "Path to default mounts file") + flags.StringVar(&cfg.Engine.EventsLogger, "events-backend", cfg.Engine.EventsLogger, `Events backend to use ("file"|"journald"|"none")`) + flags.StringSliceVar(&cfg.Engine.HooksDir, "hooks-dir", cfg.Engine.HooksDir, "Set the OCI hooks directory path (may be set multiple times)") + flags.IntVar(&opts.MaxWorks, "max-workers", 0, "The maximum number of workers for parallel operations") + flags.StringVar(&cfg.Engine.Namespace, "namespace", cfg.Engine.Namespace, "Set the libpod namespace, used to create separate views of the containers and pods on the system") + flags.StringVar(&cfg.Engine.StaticDir, "root", "", "Path to the root directory in which data, including images, is stored") + flags.StringVar(&opts.Runroot, "runroot", "", "Path to the 'run directory' where all state information is stored") + flags.StringVar(&opts.RuntimePath, "runtime", "", "Path to the OCI-compatible binary used to run containers, default is /usr/bin/runc") + // -s is deprecated due to conflict with -s on subcommands + flags.StringVar(&opts.StorageDriver, "storage-driver", "", "Select which storage driver is used to manage storage of images and containers (default is overlay)") + flags.StringArrayVar(&opts.StorageOpts, "storage-opt", []string{}, "Used to pass an option to the storage driver") + + flags.StringVar(&opts.Engine.TmpDir, "tmpdir", "", "Path to the tmp directory for libpod state content.\n\nNote: use the environment variable 'TMPDIR' to change the temporary storage location for container images, '/var/tmp'.\n") + flags.BoolVar(&opts.Trace, "trace", false, "Enable opentracing output (default false)") + + // Override default --help information of `--help` global flag + var dummyHelp bool + flags.BoolVar(&dummyHelp, "help", false, "Help for podman") + flags.StringVar(&logLevel, "log-level", logLevel, fmt.Sprintf("Log messages above specified level (%s)", strings.Join(logLevels, ", "))) + + // Hide these flags for both ABI and Tunneling + for _, f := range []string{ + "cpu-profile", + "default-mounts-file", + "max-workers", + "trace", + } { + if err := flags.MarkHidden(f); err != nil { + logrus.Warnf("unable to mark %s flag as hidden: %s", f, err.Error()) + } + } + + // Only create these flags for ABI connections + if !registry.IsRemote() { + flags.BoolVar(&useSyslog, "syslog", false, "Output logging information to syslog as well as the console (default false)") + } +} diff --git a/cmd/podman/run.go b/cmd/podman/run.go deleted file mode 100644 index 27247c5b5..000000000 --- a/cmd/podman/run.go +++ /dev/null @@ -1,78 +0,0 @@ -package main - -import ( - "os" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/opentracing/opentracing-go" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - runCommand cliconfig.RunValues - - runDescription = "Runs a command in a new container from the given image" - _runCommand = &cobra.Command{ - Use: "run [flags] IMAGE [COMMAND [ARG...]]", - Short: "Run a command in a new container", - Long: runDescription, - RunE: func(cmd *cobra.Command, args []string) error { - runCommand.InputArgs = args - runCommand.GlobalFlags = MainGlobalOpts - runCommand.Remote = remoteclient - return runCmd(&runCommand) - }, - Example: `podman run imageID ls -alF /etc - podman run --network=host imageID dnf -y install java - podman run --volume /var/hostdir:/var/ctrdir -i -t fedora /bin/bash`, - } -) - -func init() { - runCommand.Command = _runCommand - runCommand.SetHelpTemplate(HelpTemplate()) - runCommand.SetUsageTemplate(UsageTemplate()) - flags := runCommand.Flags() - flags.SetInterspersed(false) - flags.SetNormalizeFunc(aliasFlags) - flags.Bool("sig-proxy", true, "Proxy received signals to the process") - flags.Bool("rmi", false, "Remove container image unless used by other containers") - flags.AddFlagSet(getNetFlags()) - getCreateFlags(&runCommand.PodmanCommand) - markFlagHiddenForRemoteClient("authfile", flags) -} - -func runCmd(c *cliconfig.RunValues) error { - if !remote && c.Bool("trace") { - span, _ := opentracing.StartSpanFromContext(Ctx, "runCmd") - defer span.Finish() - } - if c.String("authfile") != "" { - if _, err := os.Stat(c.String("authfile")); err != nil { - return errors.Wrapf(err, "error checking authfile path %s", c.String("authfile")) - } - } - if err := createInit(&c.PodmanCommand); err != nil { - return err - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - exitCode, err = runtime.Run(getContext(), c, exitCode) - if c.Bool("rmi") { - imageName := c.InputArgs[0] - if newImage, newImageErr := runtime.NewImageFromLocal(imageName); newImageErr != nil { - logrus.Errorf("%s", errors.Wrapf(newImageErr, "failed creating image object")) - } else if _, errImage := runtime.RemoveImage(getContext(), newImage, false); errImage != nil { - logrus.Errorf("%s", errors.Wrapf(errImage, "failed removing image")) - } - } - return err -} diff --git a/cmd/podman/runlabel.go b/cmd/podman/runlabel.go deleted file mode 100644 index 1ec4da650..000000000 --- a/cmd/podman/runlabel.go +++ /dev/null @@ -1,195 +0,0 @@ -package main - -import ( - "fmt" - "io" - "os" - "strings" - - buildahcli "github.com/containers/buildah/pkg/cli" - "github.com/containers/image/v5/types" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/libpod/define" - "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" - "github.com/spf13/cobra" -) - -var ( - runlabelCommand cliconfig.RunlabelValues - runlabelDescription = ` -Executes a command as described by a container image label. -` - _runlabelCommand = &cobra.Command{ - Use: "runlabel [flags] LABEL IMAGE [ARG...]", - Short: "Execute the command described by an image label", - Long: runlabelDescription, - RunE: func(cmd *cobra.Command, args []string) error { - runlabelCommand.InputArgs = args - runlabelCommand.GlobalFlags = MainGlobalOpts - runlabelCommand.Remote = remoteclient - return runlabelCmd(&runlabelCommand) - }, - Example: `podman container runlabel run imageID - podman container runlabel --pull install imageID arg1 arg2 - podman container runlabel --display run myImage`, - } -) - -func init() { - runlabelCommand.Command = _runlabelCommand - runlabelCommand.SetHelpTemplate(HelpTemplate()) - runlabelCommand.SetUsageTemplate(UsageTemplate()) - flags := runlabelCommand.Flags() - flags.StringVar(&runlabelCommand.Creds, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") - flags.BoolVar(&runlabelCommand.Display, "display", false, "Preview the command that the label would run") - flags.BoolVar(&runlabelCommand.Replace, "replace", false, "Replace existing container with a new one from the image") - flags.StringVarP(&runlabelCommand.Name, "name", "n", "", "Assign a name to the container") - - flags.StringVar(&runlabelCommand.Opt1, "opt1", "", "Optional parameter to pass for install") - flags.StringVar(&runlabelCommand.Opt2, "opt2", "", "Optional parameter to pass for install") - flags.StringVar(&runlabelCommand.Opt3, "opt3", "", "Optional parameter to pass for install") - markFlagHidden(flags, "opt1") - markFlagHidden(flags, "opt2") - markFlagHidden(flags, "opt3") - flags.BoolP("pull", "p", false, "Pull the image if it does not exist locally prior to executing the label contents") - flags.BoolVarP(&runlabelCommand.Quiet, "quiet", "q", false, "Suppress output information when installing images") - // Disabled flags for the remote client - if !remote { - flags.StringVar(&runlabelCommand.Authfile, "authfile", buildahcli.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") - flags.StringVar(&runlabelCommand.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") - flags.StringVar(&runlabelCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") - flags.BoolVar(&runlabelCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") - - if err := flags.MarkDeprecated("pull", "podman will pull if not found in local storage"); err != nil { - logrus.Error("unable to mark pull flag deprecated") - } - markFlagHidden(flags, "signature-policy") - } -} - -// installCmd gets the data from the command line and calls installImage -// to copy an image from a registry to a local machine -func runlabelCmd(c *cliconfig.RunlabelValues) error { - var ( - imageName string - stdErr, stdOut io.Writer - stdIn io.Reader - extraArgs []string - ) - - // Evil images could trick into recursively executing the runlabel - // command. Avoid this by setting the "PODMAN_RUNLABEL_NESTED" env - // variable when executing a label first. - nested := os.Getenv("PODMAN_RUNLABEL_NESTED") - if nested == "1" { - return fmt.Errorf("nested runlabel calls: runlabels cannot execute the runlabel command") - } - - opts := make(map[string]string) - runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - if c.Authfile != "" { - if _, err := os.Stat(c.Authfile); err != nil { - return errors.Wrapf(err, "error getting authfile %s", c.Authfile) - } - } - - args := c.InputArgs - if len(args) < 2 { - return errors.Errorf("the runlabel command requires at least 2 arguments: LABEL IMAGE") - } - if c.Display && c.Quiet { - return errors.Errorf("the display and quiet flags cannot be used together.") - } - - if len(args) > 2 { - extraArgs = args[2:] - } - label := args[0] - - runlabelImage := args[1] - - if c.Flag("opt1").Changed { - opts["opt1"] = c.Opt1 - } - - if c.Flag("opt2").Changed { - opts["opt2"] = c.Opt2 - } - if c.Flag("opt3").Changed { - opts["opt3"] = c.Opt3 - } - - ctx := getContext() - - stdErr = os.Stderr - stdOut = os.Stdout - stdIn = os.Stdin - - if c.Quiet { - stdErr = nil - stdOut = nil - stdIn = nil - } - - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerCertPath: c.CertDir, - } - if c.Flag("tls-verify").Changed { - dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) - } - - runLabel, imageName, err := shared.GetRunlabel(label, runlabelImage, ctx, runtime, true, c.Creds, dockerRegistryOptions, c.Authfile, c.SignaturePolicy, stdOut) - if err != nil { - return err - } - if runLabel == "" { - return errors.Errorf("%s does not have a label of %s", runlabelImage, label) - } - - globalOpts := util.GetGlobalOpts(c) - cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, c.Name, opts, extraArgs, globalOpts) - if err != nil { - return err - } - if !c.Quiet { - fmt.Printf("command: %s\n", strings.Join(append([]string{os.Args[0]}, cmd[1:]...), " ")) - if c.Display { - return nil - } - } - - // If container already exists && --replace given -- Nuke it - if c.Replace { - for i, entry := range cmd { - if entry == "--name" { - name := cmd[i+1] - ctr, err := runtime.LookupContainer(name) - if err != nil { - if errors.Cause(err) != define.ErrNoSuchCtr { - logrus.Debugf("Error occurred searching for container %s: %s", name, err.Error()) - return err - } - } else { - logrus.Debugf("Runlabel --replace option given. Container %s will be deleted. The new container will be named %s", ctr.ID(), name) - if err := runtime.RemoveContainer(ctx, ctr, true, false); err != nil { - return err - } - } - break - } - } - } - - return utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...) -} diff --git a/cmd/podman/save.go b/cmd/podman/save.go deleted file mode 100644 index 237ebde03..000000000 --- a/cmd/podman/save.go +++ /dev/null @@ -1,94 +0,0 @@ -package main - -import ( - "os" - "strings" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/shared/parse" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/util" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "golang.org/x/crypto/ssh/terminal" -) - -const ( - ociManifestDir = "oci-dir" - ociArchive = "oci-archive" - v2s2ManifestDir = "docker-dir" - v2s2Archive = "docker-archive" -) - -var validFormats = []string{ociManifestDir, ociArchive, v2s2ManifestDir, v2s2Archive} - -var ( - saveCommand cliconfig.SaveValues - saveDescription = `Save an image to docker-archive or oci-archive on the local machine. Default is docker-archive.` - - _saveCommand = &cobra.Command{ - Use: "save [flags] IMAGE", - Short: "Save image to an archive", - Long: saveDescription, - RunE: func(cmd *cobra.Command, args []string) error { - saveCommand.InputArgs = args - saveCommand.GlobalFlags = MainGlobalOpts - saveCommand.Remote = remoteclient - return saveCmd(&saveCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - format, err := cmd.Flags().GetString("format") - if err != nil { - return err - } - if !util.StringInSlice(format, validFormats) { - return errors.Errorf("format value must be one of %s", strings.Join(validFormats, " ")) - } - return nil - }, - Example: `podman save --quiet -o myimage.tar imageID - podman save --format docker-dir -o ubuntu-dir ubuntu - podman save > alpine-all.tar alpine:latest`, - } -) - -func init() { - saveCommand.Command = _saveCommand - saveCommand.SetHelpTemplate(HelpTemplate()) - saveCommand.SetUsageTemplate(UsageTemplate()) - flags := saveCommand.Flags() - flags.BoolVar(&saveCommand.Compress, "compress", false, "Compress tarball image layers when saving to a directory using the 'dir' transport. (default is same compression type as source)") - flags.StringVar(&saveCommand.Format, "format", v2s2Archive, "Save image to oci-archive, oci-dir (directory with oci manifest type), docker-archive, docker-dir (directory with v2s2 manifest type)") - flags.StringVarP(&saveCommand.Output, "output", "o", "", "Write to a specified file (default: stdout, which must be redirected)") - flags.BoolVarP(&saveCommand.Quiet, "quiet", "q", false, "Suppress the output") -} - -// saveCmd saves the image to either docker-archive or oci -func saveCmd(c *cliconfig.SaveValues) error { - args := c.InputArgs - if len(args) == 0 { - return errors.Errorf("need at least 1 argument") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not create runtime") - } - defer runtime.DeferredShutdown(false) - - if c.Flag("compress").Changed && (c.Format != ociManifestDir && c.Format != v2s2ManifestDir && c.Format == "") { - return errors.Errorf("--compress can only be set when --format is either 'oci-dir' or 'docker-dir'") - } - - if len(c.Output) == 0 { - fi := os.Stdout - if terminal.IsTerminal(int(fi.Fd())) { - return errors.Errorf("refusing to save to terminal. Use -o flag or redirect") - } - c.Output = "/dev/stdout" - } - if err := parse.ValidateFileName(c.Output); err != nil { - return err - } - return runtime.SaveImage(getContext(), c) -} diff --git a/cmd/podman/search.go b/cmd/podman/search.go deleted file mode 100644 index 87a26e544..000000000 --- a/cmd/podman/search.go +++ /dev/null @@ -1,125 +0,0 @@ -package main - -import ( - "os" - "reflect" - "strings" - - buildahcli "github.com/containers/buildah/pkg/cli" - "github.com/containers/buildah/pkg/formats" - "github.com/containers/image/v5/types" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod/image" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - searchCommand cliconfig.SearchValues - searchDescription = `Search registries for a given image. Can search all the default registries or a specific registry. - - Users can limit the number of results, and filter the output based on certain conditions.` - _searchCommand = &cobra.Command{ - Use: "search [flags] TERM", - Short: "Search registry for image", - Long: searchDescription, - RunE: func(cmd *cobra.Command, args []string) error { - searchCommand.InputArgs = args - searchCommand.GlobalFlags = MainGlobalOpts - searchCommand.Remote = remoteclient - return searchCmd(&searchCommand) - }, - Example: `podman search --filter=is-official --limit 3 alpine - podman search registry.fedoraproject.org/ # only works with v2 registries - podman search --format "table {{.Index}} {{.Name}}" registry.fedoraproject.org/fedora`, - } -) - -func init() { - searchCommand.Command = _searchCommand - searchCommand.SetHelpTemplate(HelpTemplate()) - searchCommand.SetUsageTemplate(UsageTemplate()) - flags := searchCommand.Flags() - flags.StringSliceVarP(&searchCommand.Filter, "filter", "f", []string{}, "Filter output based on conditions provided (default [])") - flags.StringVar(&searchCommand.Format, "format", "", "Change the output format to a Go template") - flags.IntVar(&searchCommand.Limit, "limit", 0, "Limit the number of results") - flags.BoolVar(&searchCommand.NoTrunc, "no-trunc", false, "Do not truncate the output") - // Disabled flags for the remote client - if !remote { - flags.StringVar(&searchCommand.Authfile, "authfile", buildahcli.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") - flags.BoolVar(&searchCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") - } -} - -func searchCmd(c *cliconfig.SearchValues) error { - args := c.InputArgs - if len(args) > 1 { - return errors.Errorf("too many arguments. Requires exactly 1") - } - if len(args) == 0 { - return errors.Errorf("no argument given, requires exactly 1 argument") - } - term := args[0] - - filter, err := image.ParseSearchFilter(c.Filter) - if err != nil { - return err - } - - if c.Authfile != "" { - if _, err := os.Stat(c.Authfile); err != nil { - return errors.Wrapf(err, "error getting authfile %s", c.Authfile) - } - } - - searchOptions := image.SearchOptions{ - NoTrunc: c.NoTrunc, - Limit: c.Limit, - Filter: *filter, - Authfile: c.Authfile, - } - if c.Flag("tls-verify").Changed { - searchOptions.InsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) - } - - results, err := image.SearchImages(term, searchOptions) - if err != nil { - return err - } - format := genSearchFormat(c.Format) - if len(results) == 0 { - return nil - } - out := formats.StdoutTemplateArray{Output: searchToGeneric(results), Template: format, Fields: searchHeaderMap()} - return out.Out() -} - -// searchHeaderMap returns the headers of a SearchResult. -func searchHeaderMap() map[string]string { - s := new(image.SearchResult) - v := reflect.Indirect(reflect.ValueOf(s)) - values := make(map[string]string, v.NumField()) - - for i := 0; i < v.NumField(); i++ { - key := v.Type().Field(i).Name - value := key - values[key] = strings.ToUpper(splitCamelCase(value)) - } - return values -} - -func genSearchFormat(format string) string { - if format != "" { - // "\t" from the command line is not being recognized as a tab - // replacing the string "\t" to a tab character if the user passes in "\t" - return strings.Replace(format, `\t`, "\t", -1) - } - return "table {{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\t" -} - -func searchToGeneric(params []image.SearchResult) (genericParams []interface{}) { - for _, v := range params { - genericParams = append(genericParams, interface{}(v)) - } - return genericParams -} diff --git a/cmd/podman/service.go b/cmd/podman/service.go deleted file mode 100644 index bcb37eac5..000000000 --- a/cmd/podman/service.go +++ /dev/null @@ -1,192 +0,0 @@ -// +build varlink,!remoteclient - -package main - -import ( - "fmt" - "net" - "os" - "path/filepath" - "strings" - "time" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/adapter" - api "github.com/containers/libpod/pkg/api/server" - "github.com/containers/libpod/pkg/rootless" - "github.com/containers/libpod/pkg/systemd" - "github.com/containers/libpod/pkg/util" - iopodman "github.com/containers/libpod/pkg/varlink" - "github.com/containers/libpod/pkg/varlinkapi" - "github.com/containers/libpod/version" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/varlink/go/varlink" -) - -var ( - serviceCommand cliconfig.ServiceValues - serviceDescription = `Run an API service - -Enable a listening service for API access to Podman commands. -` - - _serviceCommand = &cobra.Command{ - Use: "service [flags] [URI]", - Short: "Run API service", - Long: serviceDescription, - RunE: func(cmd *cobra.Command, args []string) error { - serviceCommand.InputArgs = args - serviceCommand.GlobalFlags = MainGlobalOpts - return serviceCmd(&serviceCommand) - }, - } -) - -func init() { - serviceCommand.Command = _serviceCommand - serviceCommand.SetHelpTemplate(HelpTemplate()) - serviceCommand.SetUsageTemplate(UsageTemplate()) - flags := serviceCommand.Flags() - flags.Int64VarP(&serviceCommand.Timeout, "timeout", "t", 5, "Time until the service session expires in seconds. Use 0 to disable the timeout") - flags.BoolVar(&serviceCommand.Varlink, "varlink", false, "Use legacy varlink service instead of REST") -} - -func serviceCmd(c *cliconfig.ServiceValues) error { - apiURI, err := resolveApiURI(c) - if err != nil { - return err - } - - // Create a single runtime api consumption - runtime, err := libpodruntime.GetRuntimeDisableFDs(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer func() { - if err := runtime.Shutdown(false); err != nil { - fmt.Fprintf(os.Stderr, "Failed to shutdown libpod runtime: %v", err) - } - }() - - timeout := time.Duration(c.Timeout) * time.Second - if c.Varlink { - return runVarlink(runtime, apiURI, timeout, c) - } - return runREST(runtime, apiURI, timeout) -} - -func resolveApiURI(c *cliconfig.ServiceValues) (string, error) { - var apiURI string - - // When determining _*THE*_ listening endpoint -- - // 1) User input wins always - // 2) systemd socket activation - // 3) rootless honors XDG_RUNTIME_DIR - // 4) if varlink -- adapter.DefaultVarlinkAddress - // 5) lastly adapter.DefaultAPIAddress - - if len(c.InputArgs) > 0 { - apiURI = c.InputArgs[0] - } else if ok := systemd.SocketActivated(); ok { - apiURI = "" - } else if rootless.IsRootless() { - xdg, err := util.GetRuntimeDir() - if err != nil { - return "", err - } - socketName := "podman.sock" - if c.Varlink { - socketName = "io.podman" - } - socketDir := filepath.Join(xdg, "podman", socketName) - if _, err := os.Stat(filepath.Dir(socketDir)); err != nil { - if os.IsNotExist(err) { - if err := os.Mkdir(filepath.Dir(socketDir), 0755); err != nil { - return "", err - } - } else { - return "", err - } - } - apiURI = "unix:" + socketDir - } else if c.Varlink { - apiURI = adapter.DefaultVarlinkAddress - } else { - // For V2, default to the REST socket - apiURI = adapter.DefaultAPIAddress - } - - if "" == apiURI { - logrus.Info("using systemd socket activation to determine API endpoint") - } else { - logrus.Infof("using API endpoint: %s", apiURI) - } - return apiURI, nil -} - -func runREST(r *libpod.Runtime, uri string, timeout time.Duration) error { - logrus.Warn("This function is EXPERIMENTAL") - fmt.Println("This function is EXPERIMENTAL.") - - var listener *net.Listener - if uri != "" { - fields := strings.Split(uri, ":") - if len(fields) == 1 { - return errors.Errorf("%s is an invalid socket destination", uri) - } - address := strings.Join(fields[1:], ":") - l, err := net.Listen(fields[0], address) - if err != nil { - return errors.Wrapf(err, "unable to create socket %s", uri) - } - listener = &l - } - server, err := api.NewServerWithSettings(r, timeout, listener) - if err != nil { - return err - } - defer func() { - if err := server.Shutdown(); err != nil { - fmt.Fprintf(os.Stderr, "Error when stopping service: %s", err) - } - }() - - err = server.Serve() - logrus.Debugf("%d/%d Active connections/Total connections\n", server.ActiveConnections, server.TotalConnections) - return err -} - -func runVarlink(r *libpod.Runtime, uri string, timeout time.Duration, c *cliconfig.ServiceValues) error { - var varlinkInterfaces = []*iopodman.VarlinkInterface{varlinkapi.New(&c.PodmanCommand, r)} - service, err := varlink.NewService( - "Atomic", - "podman", - version.Version, - "https://github.com/containers/libpod", - ) - if err != nil { - return errors.Wrapf(err, "unable to create new varlink service") - } - - for _, i := range varlinkInterfaces { - if err := service.RegisterInterface(i); err != nil { - return errors.Errorf("unable to register varlink interface %v", i) - } - } - - // Run the varlink server at the given address - if err = service.Listen(uri, timeout); err != nil { - switch err.(type) { - case varlink.ServiceTimeoutError: - logrus.Infof("varlink service expired (use --timeout to increase session time beyond %d ms, 0 means never timeout)", timeout.String()) - return nil - default: - return errors.Wrapf(err, "unable to start varlink service") - } - } - return nil -} diff --git a/cmd/podman/service_dummy.go b/cmd/podman/service_dummy.go deleted file mode 100644 index a774c34de..000000000 --- a/cmd/podman/service_dummy.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build !varlink - -package main - -import "github.com/spf13/cobra" - -var ( - _serviceCommand = &cobra.Command{ - Use: "", - } -) diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go deleted file mode 100644 index b5a1e7104..000000000 --- a/cmd/podman/shared/container.go +++ /dev/null @@ -1,887 +0,0 @@ -package shared - -import ( - "context" - "fmt" - "io" - "os" - "path/filepath" - "regexp" - "sort" - "strconv" - "strings" - "sync" - "time" - - "github.com/containers/image/v5/types" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/timetype" - "github.com/containers/libpod/pkg/util" - "github.com/cri-o/ocicni/pkg/ocicni" - "github.com/docker/go-units" - "github.com/google/shlex" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - v1 "k8s.io/api/core/v1" -) - -const ( - cidTruncLength = 12 - podTruncLength = 12 - iidTruncLength = 12 - cmdTruncLength = 17 -) - -// PsOptions describes the struct being formed for ps. -type PsOptions struct { - All bool - Format string - Last int - Latest bool - NoTrunc bool - Pod bool - Quiet bool - Size bool - Sort string - Namespace bool - Sync bool -} - -// BatchContainerStruct is the return object from BatchContainer and contains -// container related information. -type BatchContainerStruct struct { - ConConfig *libpod.ContainerConfig - ConState define.ContainerStatus - ExitCode int32 - Exited bool - Pid int - StartedTime time.Time - ExitedTime time.Time - Size *ContainerSize -} - -// PsContainerOutput is the struct being returned from a parallel -// batch operation. -type PsContainerOutput struct { - ID string - Image string - ImageID string - Command string - Created string - Ports string - Names string - IsInfra bool - Status string - State define.ContainerStatus - Pid int - Size *ContainerSize - Pod string - PodName string - CreatedAt time.Time - ExitedAt time.Time - StartedAt time.Time - Labels map[string]string - PID string - Cgroup string - IPC string - MNT string - NET string - PIDNS string - User string - UTS string - Mounts string -} - -// Namespace describes output for ps namespace. -type Namespace struct { - PID string `json:"pid,omitempty"` - Cgroup string `json:"cgroup,omitempty"` - IPC string `json:"ipc,omitempty"` - MNT string `json:"mnt,omitempty"` - NET string `json:"net,omitempty"` - PIDNS string `json:"pidns,omitempty"` - User string `json:"user,omitempty"` - UTS string `json:"uts,omitempty"` -} - -// ContainerSize holds the size of the container's root filesystem and top -// read-write layer. -type ContainerSize struct { - RootFsSize int64 `json:"rootFsSize"` - RwSize int64 `json:"rwSize"` -} - -// NewBatchContainer runs a batch process under one lock to get container information and only -// be called in PBatch. -func NewBatchContainer(r *libpod.Runtime, ctr *libpod.Container, opts PsOptions) (PsContainerOutput, error) { - var ( - conState define.ContainerStatus - command string - created string - status string - exitedAt time.Time - startedAt time.Time - exitCode int32 - err error - pid int - size *ContainerSize - ns *Namespace - pso PsContainerOutput - ) - batchErr := ctr.Batch(func(c *libpod.Container) error { - if opts.Sync { - if err := c.Sync(); err != nil { - return err - } - } - - conState, err = c.State() - if err != nil { - return errors.Wrapf(err, "unable to obtain container state") - } - command = strings.Join(c.Command(), " ") - created = units.HumanDuration(time.Since(c.CreatedTime())) + " ago" - - exitCode, _, err = c.ExitCode() - if err != nil { - return errors.Wrapf(err, "unable to obtain container exit code") - } - startedAt, err = c.StartedTime() - if err != nil { - logrus.Errorf("error getting started time for %q: %v", c.ID(), err) - } - exitedAt, err = c.FinishedTime() - if err != nil { - logrus.Errorf("error getting exited time for %q: %v", c.ID(), err) - } - if opts.Namespace { - pid, err = c.PID() - if err != nil { - return errors.Wrapf(err, "unable to obtain container pid") - } - ns = GetNamespaces(pid) - } - if opts.Size { - size = new(ContainerSize) - - rootFsSize, err := c.RootFsSize() - if err != nil { - logrus.Errorf("error getting root fs size for %q: %v", c.ID(), err) - } - - rwSize, err := c.RWSize() - if err != nil { - logrus.Errorf("error getting rw size for %q: %v", c.ID(), err) - } - - size.RootFsSize = rootFsSize - size.RwSize = rwSize - } - - return nil - }) - - if batchErr != nil { - return pso, batchErr - } - - switch conState.String() { - case define.ContainerStateExited.String(): - fallthrough - case define.ContainerStateStopped.String(): - exitedSince := units.HumanDuration(time.Since(exitedAt)) - status = fmt.Sprintf("Exited (%d) %s ago", exitCode, exitedSince) - case define.ContainerStateRunning.String(): - status = "Up " + units.HumanDuration(time.Since(startedAt)) + " ago" - case define.ContainerStatePaused.String(): - status = "Paused" - case define.ContainerStateCreated.String(), define.ContainerStateConfigured.String(): - status = "Created" - case define.ContainerStateRemoving.String(): - status = "Removing" - default: - status = "Error" - } - - imageID, imageName := ctr.Image() - cid := ctr.ID() - podID := ctr.PodID() - if !opts.NoTrunc { - cid = cid[0:cidTruncLength] - if len(podID) > podTruncLength { - podID = podID[0:podTruncLength] - } - if len(command) > cmdTruncLength { - command = command[0:cmdTruncLength] + "..." - } - if len(imageID) > iidTruncLength { - imageID = imageID[0:iidTruncLength] - } - } - - ports, err := ctr.PortMappings() - if err != nil { - logrus.Errorf("unable to lookup namespace container for %s", ctr.ID()) - } - - pso.ID = cid - pso.Image = imageName - pso.ImageID = imageID - pso.Command = command - pso.Created = created - pso.Ports = portsToString(ports) - pso.Names = ctr.Name() - pso.IsInfra = ctr.IsInfra() - pso.Status = status - pso.State = conState - pso.Pid = pid - pso.Size = size - pso.ExitedAt = exitedAt - pso.CreatedAt = ctr.CreatedTime() - pso.StartedAt = startedAt - pso.Labels = ctr.Labels() - pso.Mounts = strings.Join(ctr.UserVolumes(), " ") - - // Add pod name and pod ID if requested by user. - // No need to look up the pod if its ID is empty. - if opts.Pod && len(podID) > 0 { - // The pod name is not in the container definition - // so we need to retrieve it using the pod ID. - var podName string - pod, err := r.LookupPod(podID) - if err != nil { - logrus.Errorf("unable to lookup pod for container %s", ctr.ID()) - } else { - podName = pod.Name() - } - - pso.Pod = podID - pso.PodName = podName - } - - if opts.Namespace { - pso.Cgroup = ns.Cgroup - pso.IPC = ns.IPC - pso.MNT = ns.MNT - pso.NET = ns.NET - pso.User = ns.User - pso.UTS = ns.UTS - pso.PIDNS = ns.PIDNS - } - - return pso, nil -} - -type batchFunc func() (PsContainerOutput, error) - -type workerInput struct { - parallelFunc batchFunc - opts PsOptions - cid string - job int -} - -// worker is a "threaded" worker that takes jobs from the channel "queue". -func worker(wg *sync.WaitGroup, jobs <-chan workerInput, results chan<- PsContainerOutput, errors chan<- error) { - for j := range jobs { - r, err := j.parallelFunc() - // If we find an error, we return just the error. - if err != nil { - errors <- err - } else { - // Return the result. - results <- r - } - wg.Done() - } -} - -// GenerateContainerFilterFuncs return ContainerFilter functions based of filter. -func GenerateContainerFilterFuncs(filter, filterValue string, r *libpod.Runtime) (func(container *libpod.Container) bool, error) { - switch filter { - case "id": - return func(c *libpod.Container) bool { - return strings.Contains(c.ID(), filterValue) - }, nil - case "label": - var filterArray = strings.SplitN(filterValue, "=", 2) - var filterKey = filterArray[0] - if len(filterArray) > 1 { - filterValue = filterArray[1] - } else { - filterValue = "" - } - return func(c *libpod.Container) bool { - for labelKey, labelValue := range c.Labels() { - if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) { - return true - } - } - return false - }, nil - case "name": - return func(c *libpod.Container) bool { - match, err := regexp.MatchString(filterValue, c.Name()) - if err != nil { - return false - } - return match - }, nil - case "exited": - exitCode, err := strconv.ParseInt(filterValue, 10, 32) - if err != nil { - return nil, errors.Wrapf(err, "exited code out of range %q", filterValue) - } - return func(c *libpod.Container) bool { - ec, exited, err := c.ExitCode() - if ec == int32(exitCode) && err == nil && exited { - return true - } - return false - }, nil - case "status": - if !util.StringInSlice(filterValue, []string{"created", "running", "paused", "stopped", "exited", "unknown"}) { - return nil, errors.Errorf("%s is not a valid status", filterValue) - } - return func(c *libpod.Container) bool { - status, err := c.State() - if err != nil { - return false - } - if filterValue == "stopped" { - filterValue = "exited" - } - state := status.String() - if status == define.ContainerStateConfigured { - state = "created" - } else if status == define.ContainerStateStopped { - state = "exited" - } - return state == filterValue - }, nil - case "ancestor": - // This needs to refine to match docker - // - ancestor=(<image-name>[:tag]|<image-id>| ⟨image@digest⟩) - containers created from an image or a descendant. - return func(c *libpod.Container) bool { - containerConfig := c.Config() - if strings.Contains(containerConfig.RootfsImageID, filterValue) || strings.Contains(containerConfig.RootfsImageName, filterValue) { - return true - } - return false - }, nil - case "before": - ctr, err := r.LookupContainer(filterValue) - if err != nil { - return nil, errors.Errorf("unable to find container by name or id of %s", filterValue) - } - containerConfig := ctr.Config() - createTime := containerConfig.CreatedTime - return func(c *libpod.Container) bool { - cc := c.Config() - return createTime.After(cc.CreatedTime) - }, nil - case "since": - ctr, err := r.LookupContainer(filterValue) - if err != nil { - return nil, errors.Errorf("unable to find container by name or id of %s", filterValue) - } - containerConfig := ctr.Config() - createTime := containerConfig.CreatedTime - return func(c *libpod.Container) bool { - cc := c.Config() - return createTime.Before(cc.CreatedTime) - }, nil - case "volume": - //- volume=(<volume-name>|<mount-point-destination>) - return func(c *libpod.Container) bool { - containerConfig := c.Config() - var dest string - arr := strings.Split(filterValue, ":") - source := arr[0] - if len(arr) == 2 { - dest = arr[1] - } - for _, mount := range containerConfig.Spec.Mounts { - if dest != "" && (mount.Source == source && mount.Destination == dest) { - return true - } - if dest == "" && mount.Source == source { - return true - } - } - return false - }, nil - case "health": - return func(c *libpod.Container) bool { - hcStatus, err := c.HealthCheckStatus() - if err != nil { - return false - } - return hcStatus == filterValue - }, nil - case "until": - ts, err := timetype.GetTimestamp(filterValue, time.Now()) - if err != nil { - return nil, err - } - seconds, nanoseconds, err := timetype.ParseTimestamps(ts, 0) - if err != nil { - return nil, err - } - until := time.Unix(seconds, nanoseconds) - return func(c *libpod.Container) bool { - if !until.IsZero() && c.CreatedTime().After((until)) { - return true - } - return false - }, nil - } - return nil, errors.Errorf("%s is an invalid filter", filter) -} - -// GetPsContainerOutput returns a slice of containers specifically for ps output. -func GetPsContainerOutput(r *libpod.Runtime, opts PsOptions, filters []string, maxWorkers int) ([]PsContainerOutput, error) { - var ( - filterFuncs []libpod.ContainerFilter - outputContainers []*libpod.Container - ) - - if len(filters) > 0 { - for _, f := range filters { - filterSplit := strings.SplitN(f, "=", 2) - if len(filterSplit) < 2 { - return nil, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) - } - generatedFunc, err := GenerateContainerFilterFuncs(filterSplit[0], filterSplit[1], r) - if err != nil { - return nil, errors.Wrapf(err, "invalid filter") - } - filterFuncs = append(filterFuncs, generatedFunc) - } - } - if !opts.Latest { - // Get all containers. - containers, err := r.GetContainers(filterFuncs...) - if err != nil { - return nil, err - } - - // We only want the last few containers. - if opts.Last > 0 && opts.Last <= len(containers) { - return nil, errors.Errorf("--last not yet supported") - } else { - outputContainers = containers - } - } else { - // Get just the latest container. - // Ignore filters. - latestCtr, err := r.GetLatestContainer() - if err != nil { - return nil, err - } - - outputContainers = []*libpod.Container{latestCtr} - } - - pss := PBatch(r, outputContainers, maxWorkers, opts) - return pss, nil -} - -// PBatch performs batch operations on a container in parallel. It spawns the -// number of workers relative to the number of parallel operations desired. -func PBatch(r *libpod.Runtime, containers []*libpod.Container, workers int, opts PsOptions) []PsContainerOutput { - var wg sync.WaitGroup - psResults := []PsContainerOutput{} - - // If the number of containers in question is less than the number of - // proposed parallel operations, we shouldn't spawn so many workers. - if workers > len(containers) { - workers = len(containers) - } - - jobs := make(chan workerInput, len(containers)) - results := make(chan PsContainerOutput, len(containers)) - batchErrors := make(chan error, len(containers)) - - // Create the workers. - for w := 1; w <= workers; w++ { - go worker(&wg, jobs, results, batchErrors) - } - - // Add jobs to the workers. - for i, j := range containers { - j := j - wg.Add(1) - f := func() (PsContainerOutput, error) { - return NewBatchContainer(r, j, opts) - } - jobs <- workerInput{ - parallelFunc: f, - opts: opts, - cid: j.ID(), - job: i, - } - } - close(jobs) - wg.Wait() - close(results) - close(batchErrors) - for err := range batchErrors { - logrus.Errorf("unable to get container info: %q", err) - } - for res := range results { - // We sort out running vs non-running here to save lots of copying - // later. - if !opts.All && !opts.Latest && opts.Last < 1 { - if !res.IsInfra && res.State == define.ContainerStateRunning { - psResults = append(psResults, res) - } - } else { - psResults = append(psResults, res) - } - } - return psResults -} - -// BatchContainerOp is used in ps to reduce performance hits by "batching" -// locks. -func BatchContainerOp(ctr *libpod.Container, opts PsOptions) (BatchContainerStruct, error) { - var ( - conConfig *libpod.ContainerConfig - conState define.ContainerStatus - err error - exitCode int32 - exited bool - pid int - size *ContainerSize - startedTime time.Time - exitedTime time.Time - ) - - batchErr := ctr.Batch(func(c *libpod.Container) error { - conConfig = c.Config() - conState, err = c.State() - if err != nil { - return errors.Wrapf(err, "unable to obtain container state") - } - - exitCode, exited, err = c.ExitCode() - if err != nil { - return errors.Wrapf(err, "unable to obtain container exit code") - } - startedTime, err = c.StartedTime() - if err != nil { - logrus.Errorf("error getting started time for %q: %v", c.ID(), err) - } - exitedTime, err = c.FinishedTime() - if err != nil { - logrus.Errorf("error getting exited time for %q: %v", c.ID(), err) - } - - if !opts.Size && !opts.Namespace { - return nil - } - - if opts.Namespace { - pid, err = c.PID() - if err != nil { - return errors.Wrapf(err, "unable to obtain container pid") - } - } - if opts.Size { - size = new(ContainerSize) - - rootFsSize, err := c.RootFsSize() - if err != nil { - logrus.Errorf("error getting root fs size for %q: %v", c.ID(), err) - } - - rwSize, err := c.RWSize() - if err != nil { - logrus.Errorf("error getting rw size for %q: %v", c.ID(), err) - } - - size.RootFsSize = rootFsSize - size.RwSize = rwSize - } - return nil - }) - if batchErr != nil { - return BatchContainerStruct{}, batchErr - } - return BatchContainerStruct{ - ConConfig: conConfig, - ConState: conState, - ExitCode: exitCode, - Exited: exited, - Pid: pid, - StartedTime: startedTime, - ExitedTime: exitedTime, - Size: size, - }, nil -} - -// GetNamespaces returns a populated namespace struct. -func GetNamespaces(pid int) *Namespace { - ctrPID := strconv.Itoa(pid) - cgroup, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "cgroup")) - ipc, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "ipc")) - mnt, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "mnt")) - net, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "net")) - pidns, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "pid")) - user, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "user")) - uts, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "uts")) - - return &Namespace{ - PID: ctrPID, - Cgroup: cgroup, - IPC: ipc, - MNT: mnt, - NET: net, - PIDNS: pidns, - User: user, - UTS: uts, - } -} - -// GetNamespaceInfo is an exported wrapper for getNamespaceInfo -func GetNamespaceInfo(path string) (string, error) { - return getNamespaceInfo(path) -} - -func getNamespaceInfo(path string) (string, error) { - val, err := os.Readlink(path) - if err != nil { - return "", errors.Wrapf(err, "error getting info from %q", path) - } - return getStrFromSquareBrackets(val), nil -} - -// getStrFromSquareBrackets gets the string inside [] from a string. -func getStrFromSquareBrackets(cmd string) string { - reg := regexp.MustCompile(`.*\[|\].*`) - arr := strings.Split(reg.ReplaceAllLiteralString(cmd, ""), ",") - return strings.Join(arr, ",") -} - -func comparePorts(i, j ocicni.PortMapping) bool { - if i.ContainerPort != j.ContainerPort { - return i.ContainerPort < j.ContainerPort - } - - if i.HostIP != j.HostIP { - return i.HostIP < j.HostIP - } - - if i.HostPort != j.HostPort { - return i.HostPort < j.HostPort - } - - return i.Protocol < j.Protocol -} - -// formatGroup returns the group as <IP:startPort:lastPort->startPort:lastPort/Proto> -// e.g 0.0.0.0:1000-1006->1000-1006/tcp. -func formatGroup(key string, start, last int32) string { - parts := strings.Split(key, "/") - groupType := parts[0] - var ip string - if len(parts) > 1 { - ip = parts[0] - groupType = parts[1] - } - group := strconv.Itoa(int(start)) - if start != last { - group = fmt.Sprintf("%s-%d", group, last) - } - if ip != "" { - group = fmt.Sprintf("%s:%s->%s", ip, group, group) - } - return fmt.Sprintf("%s/%s", group, groupType) -} - -// portsToString converts the ports used to a string of the from "port1, port2" -// and also groups a continuous list of ports into a readable format. -func portsToString(ports []ocicni.PortMapping) string { - type portGroup struct { - first int32 - last int32 - } - var portDisplay []string - if len(ports) == 0 { - return "" - } - //Sort the ports, so grouping continuous ports become easy. - sort.Slice(ports, func(i, j int) bool { - return comparePorts(ports[i], ports[j]) - }) - - // portGroupMap is used for grouping continuous ports. - portGroupMap := make(map[string]*portGroup) - var groupKeyList []string - - for _, v := range ports { - - hostIP := v.HostIP - if hostIP == "" { - hostIP = "0.0.0.0" - } - // If hostPort and containerPort are not same, consider as individual port. - if v.ContainerPort != v.HostPort { - portDisplay = append(portDisplay, fmt.Sprintf("%s:%d->%d/%s", hostIP, v.HostPort, v.ContainerPort, v.Protocol)) - continue - } - - portMapKey := fmt.Sprintf("%s/%s", hostIP, v.Protocol) - - portgroup, ok := portGroupMap[portMapKey] - if !ok { - portGroupMap[portMapKey] = &portGroup{first: v.ContainerPort, last: v.ContainerPort} - // This list is required to traverse portGroupMap. - groupKeyList = append(groupKeyList, portMapKey) - continue - } - - if portgroup.last == (v.ContainerPort - 1) { - portgroup.last = v.ContainerPort - continue - } - } - // For each portMapKey, format group list and appned to output string. - for _, portKey := range groupKeyList { - group := portGroupMap[portKey] - portDisplay = append(portDisplay, formatGroup(portKey, group.first, group.last)) - } - return strings.Join(portDisplay, ", ") -} - -// GetRunlabel is a helper function for runlabel; it gets the image if needed and begins the -// construction 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{}, &label, util.PullImageMissing) - } 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, globalOpts string) ([]string, []string, error) { - // If no name is provided, we use the image's basename instead. - if name == "" { - baseName, err := image.GetImageBaseName(imageName) - if err != nil { - return nil, nil, err - } - name = baseName - } - // 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, globalOpts) - 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"] - case "PWD": - // I would prefer to use os.getenv but it appears PWD is not in the os env list. - d, err := os.Getwd() - if err != nil { - logrus.Error("unable to determine current working directory") - return "" - } - return d - } - return "" - } - newS := os.Expand(strings.Join(cmd, " "), envmapper) - cmd, err = shlex.Split(newS) - if err != nil { - return nil, nil, err - } - 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 -} - -// GenerateKube generates kubernetes yaml based on a pod or container. -func GenerateKube(name string, service bool, r *libpod.Runtime) (*v1.Pod, *v1.Service, error) { - var ( - pod *libpod.Pod - podYAML *v1.Pod - err error - container *libpod.Container - servicePorts []v1.ServicePort - serviceYAML v1.Service - ) - // Get the container in question. - container, err = r.LookupContainer(name) - if err != nil { - pod, err = r.LookupPod(name) - if err != nil { - return nil, nil, err - } - podYAML, servicePorts, err = pod.GenerateForKube() - } else { - if len(container.Dependencies()) > 0 { - return nil, nil, errors.Wrapf(define.ErrNotImplemented, "containers with dependencies") - } - podYAML, err = container.GenerateForKube() - } - if err != nil { - return nil, nil, err - } - - if service { - serviceYAML = libpod.GenerateKubeServiceFromV1Pod(podYAML, servicePorts) - } - return podYAML, &serviceYAML, nil -} diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go deleted file mode 100644 index 68a36d967..000000000 --- a/cmd/podman/shared/create.go +++ /dev/null @@ -1,978 +0,0 @@ -package shared - -import ( - "context" - "encoding/json" - "fmt" - "io" - "os" - "path/filepath" - goruntime "runtime" - "strconv" - "strings" - "syscall" - "time" - - "github.com/containers/image/v5/manifest" - "github.com/containers/libpod/cmd/podman/shared/parse" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/image" - ann "github.com/containers/libpod/pkg/annotations" - "github.com/containers/libpod/pkg/autoupdate" - envLib "github.com/containers/libpod/pkg/env" - "github.com/containers/libpod/pkg/errorhandling" - "github.com/containers/libpod/pkg/inspect" - ns "github.com/containers/libpod/pkg/namespaces" - "github.com/containers/libpod/pkg/rootless" - "github.com/containers/libpod/pkg/seccomp" - cc "github.com/containers/libpod/pkg/spec" - systemdGen "github.com/containers/libpod/pkg/systemd/generate" - "github.com/containers/libpod/pkg/util" - "github.com/docker/go-connections/nat" - "github.com/docker/go-units" - "github.com/opentracing/opentracing-go" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.Runtime) (*libpod.Container, *cc.CreateConfig, error) { - var ( - healthCheck *manifest.Schema2HealthConfig - err error - cidFile *os.File - ) - if c.Bool("trace") { - span, _ := opentracing.StartSpanFromContext(ctx, "createContainer") - defer span.Finish() - } - if c.Bool("rm") && c.String("restart") != "" && c.String("restart") != "no" { - return nil, nil, errors.Errorf("the --rm option conflicts with --restart") - } - - rtc, err := runtime.GetConfig() - if err != nil { - return nil, nil, err - } - rootfs := "" - if c.Bool("rootfs") { - rootfs = c.InputArgs[0] - } - - if c.IsSet("cidfile") { - cidFile, err = util.OpenExclusiveFile(c.String("cidfile")) - if err != nil && os.IsExist(err) { - return nil, nil, errors.Errorf("container id file exists. Ensure another container is not using it or delete %s", c.String("cidfile")) - } - if err != nil { - return nil, nil, errors.Errorf("error opening cidfile %s", c.String("cidfile")) - } - defer errorhandling.CloseQuiet(cidFile) - defer errorhandling.SyncQuiet(cidFile) - } - - imageName := "" - rawImageName := "" - var imageData *inspect.ImageData = nil - - // Set the storage if there is no rootfs specified - if rootfs == "" { - var writer io.Writer - if !c.Bool("quiet") { - writer = os.Stderr - } - - if len(c.InputArgs) != 0 { - rawImageName = c.InputArgs[0] - } else { - return nil, nil, errors.Errorf("error, image name not provided") - } - - pullType, err := util.ValidatePullType(c.String("pull")) - if err != nil { - return nil, nil, err - } - - overrideOS := c.String("override-os") - overrideArch := c.String("override-arch") - dockerRegistryOptions := image.DockerRegistryOptions{ - OSChoice: overrideOS, - ArchitectureChoice: overrideArch, - } - - newImage, err := runtime.ImageRuntime().New(ctx, rawImageName, rtc.Engine.SignaturePolicyPath, c.String("authfile"), writer, &dockerRegistryOptions, image.SigningOptions{}, nil, pullType) - if err != nil { - return nil, nil, err - } - imageData, err = newImage.InspectNoSize(ctx) - if err != nil { - return nil, nil, err - } - - if overrideOS == "" && imageData.Os != goruntime.GOOS { - logrus.Infof("Using %q (OS) image on %q host", imageData.Os, goruntime.GOOS) - } - - if overrideArch == "" && imageData.Architecture != goruntime.GOARCH { - logrus.Infof("Using %q (architecture) on %q host", imageData.Architecture, goruntime.GOARCH) - } - - names := newImage.Names() - if len(names) > 0 { - imageName = names[0] - } else { - imageName = newImage.ID() - } - - // if the user disabled the healthcheck with "none" or the no-healthcheck - // options is provided, we skip adding it - healthCheckCommandInput := c.String("healthcheck-command") - - // the user didn't disable the healthcheck but did pass in a healthcheck command - // now we need to make a healthcheck from the commandline input - if healthCheckCommandInput != "none" && !c.Bool("no-healthcheck") { - if len(healthCheckCommandInput) > 0 { - healthCheck, err = makeHealthCheckFromCli(c) - if err != nil { - return nil, nil, errors.Wrapf(err, "unable to create healthcheck") - } - } else { - // the user did not disable the health check and did not pass in a healthcheck - // command as input. so now we add healthcheck if it exists AND is correct mediatype - _, mediaType, err := newImage.Manifest(ctx) - if err != nil { - return nil, nil, errors.Wrapf(err, "unable to determine mediatype of image %s", newImage.ID()) - } - if mediaType == manifest.DockerV2Schema2MediaType { - healthCheck, err = newImage.GetHealthCheck(ctx) - if err != nil { - return nil, nil, errors.Wrapf(err, "unable to get healthcheck for %s", c.InputArgs[0]) - } - - if healthCheck != nil { - hcCommand := healthCheck.Test - if len(hcCommand) < 1 || hcCommand[0] == "" || hcCommand[0] == "NONE" { - // disable health check - healthCheck = nil - } else { - // apply defaults if image doesn't override them - if healthCheck.Interval == 0 { - healthCheck.Interval = 30 * time.Second - } - if healthCheck.Timeout == 0 { - healthCheck.Timeout = 30 * time.Second - } - /* Docker default is 0s, so the following would be a no-op - if healthCheck.StartPeriod == 0 { - healthCheck.StartPeriod = 0 * time.Second - } - */ - if healthCheck.Retries == 0 { - healthCheck.Retries = 3 - } - } - } - } - } - } - } - - createConfig, err := ParseCreateOpts(ctx, c, runtime, imageName, rawImageName, imageData) - if err != nil { - return nil, nil, err - } - - // (VR): Ideally we perform the checks _before_ pulling the image but that - // would require some bigger code refactoring of `ParseCreateOpts` and the - // logic here. But as the creation code will be consolidated in the future - // and given auto updates are experimental, we can live with that for now. - // In the end, the user may only need to correct the policy or the raw image - // name. - autoUpdatePolicy, autoUpdatePolicySpecified := createConfig.Labels[autoupdate.Label] - if autoUpdatePolicySpecified { - if _, err := autoupdate.LookupPolicy(autoUpdatePolicy); err != nil { - return nil, nil, err - } - // Now we need to make sure we're having a fully-qualified image reference. - if rootfs != "" { - return nil, nil, errors.Errorf("auto updates do not work with --rootfs") - } - // Make sure the input image is a docker. - if err := autoupdate.ValidateImageReference(rawImageName); err != nil { - return nil, nil, err - } - } - - // Because parseCreateOpts does derive anything from the image, we add health check - // at this point. The rest is done by WithOptions. - createConfig.HealthCheck = healthCheck - - // TODO: Should be able to return this from ParseCreateOpts - var pod *libpod.Pod - if createConfig.Pod != "" { - pod, err = runtime.LookupPod(createConfig.Pod) - if err != nil { - return nil, nil, errors.Wrapf(err, "error looking up pod to join") - } - } - - ctr, err := CreateContainerFromCreateConfig(runtime, createConfig, ctx, pod) - if err != nil { - return nil, nil, err - } - if cidFile != nil { - _, err = cidFile.WriteString(ctr.ID()) - if err != nil { - logrus.Error(err) - } - - } - - logrus.Debugf("New container created %q", ctr.ID()) - return ctr, createConfig, nil -} - -func configureEntrypoint(c *GenericCLIResults, data *inspect.ImageData) []string { - entrypoint := []string{} - if c.IsSet("entrypoint") { - // Force entrypoint to "" - if c.String("entrypoint") == "" { - return entrypoint - } - // Check if entrypoint specified is json - if err := json.Unmarshal([]byte(c.String("entrypoint")), &entrypoint); err == nil { - return entrypoint - } - // Return entrypoint as a single command - return []string{c.String("entrypoint")} - } - if data != nil { - return data.Config.Entrypoint - } - return entrypoint -} - -func configurePod(c *GenericCLIResults, runtime *libpod.Runtime, namespaces map[string]string, podName string) (map[string]string, string, error) { - pod, err := runtime.LookupPod(podName) - if err != nil { - return namespaces, "", err - } - podInfraID, err := pod.InfraContainerID() - if err != nil { - return namespaces, "", err - } - hasUserns := false - if podInfraID != "" { - podCtr, err := runtime.GetContainer(podInfraID) - if err != nil { - return namespaces, "", err - } - mappings, err := podCtr.IDMappings() - if err != nil { - return namespaces, "", err - } - hasUserns = len(mappings.UIDMap) > 0 - } - - if (namespaces["pid"] == cc.Pod) || (!c.IsSet("pid") && pod.SharesPID()) { - namespaces["pid"] = fmt.Sprintf("container:%s", podInfraID) - } - if (namespaces["net"] == cc.Pod) || (!c.IsSet("net") && !c.IsSet("network") && pod.SharesNet()) { - namespaces["net"] = fmt.Sprintf("container:%s", podInfraID) - } - if hasUserns && (namespaces["user"] == cc.Pod) || (!c.IsSet("user") && pod.SharesUser()) { - namespaces["user"] = fmt.Sprintf("container:%s", podInfraID) - } - if (namespaces["ipc"] == cc.Pod) || (!c.IsSet("ipc") && pod.SharesIPC()) { - namespaces["ipc"] = fmt.Sprintf("container:%s", podInfraID) - } - if (namespaces["uts"] == cc.Pod) || (!c.IsSet("uts") && pod.SharesUTS()) { - namespaces["uts"] = fmt.Sprintf("container:%s", podInfraID) - } - return namespaces, podInfraID, nil -} - -// Parses CLI options related to container creation into a config which can be -// parsed into an OCI runtime spec -func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.Runtime, imageName string, rawImageName string, data *inspect.ImageData) (*cc.CreateConfig, error) { - var ( - inputCommand, command []string - memoryLimit, memoryReservation, memorySwap, memoryKernel int64 - blkioWeight uint16 - namespaces map[string]string - ) - - idmappings, err := util.ParseIDMapping(ns.UsernsMode(c.String("userns")), c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidname"), c.String("subgidname")) - if err != nil { - return nil, err - } - - imageID := "" - - inputCommand = c.InputArgs[1:] - if data != nil { - imageID = data.ID - } - - rootfs := "" - if c.Bool("rootfs") { - rootfs = c.InputArgs[0] - } - - if c.String("memory") != "" { - memoryLimit, err = units.RAMInBytes(c.String("memory")) - if err != nil { - return nil, errors.Wrapf(err, "invalid value for memory") - } - } - if c.String("memory-reservation") != "" { - memoryReservation, err = units.RAMInBytes(c.String("memory-reservation")) - if err != nil { - return nil, errors.Wrapf(err, "invalid value for memory-reservation") - } - } - if c.String("memory-swap") != "" { - if c.String("memory-swap") == "-1" { - memorySwap = -1 - } else { - memorySwap, err = units.RAMInBytes(c.String("memory-swap")) - if err != nil { - return nil, errors.Wrapf(err, "invalid value for memory-swap") - } - } - } - if c.String("kernel-memory") != "" { - memoryKernel, err = units.RAMInBytes(c.String("kernel-memory")) - if err != nil { - return nil, errors.Wrapf(err, "invalid value for kernel-memory") - } - } - if c.String("blkio-weight") != "" { - u, err := strconv.ParseUint(c.String("blkio-weight"), 10, 16) - if err != nil { - return nil, errors.Wrapf(err, "invalid value for blkio-weight") - } - blkioWeight = uint16(u) - } - - tty := c.Bool("tty") - - if c.Changed("cpu-period") && c.Changed("cpus") { - return nil, errors.Errorf("--cpu-period and --cpus cannot be set together") - } - if c.Changed("cpu-quota") && c.Changed("cpus") { - return nil, errors.Errorf("--cpu-quota and --cpus cannot be set together") - } - - if c.Bool("no-hosts") && c.Changed("add-host") { - return nil, errors.Errorf("--no-hosts and --add-host 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.Config.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{ - "cgroup": c.String("cgroupns"), - "pid": c.String("pid"), - "net": c.String("network"), - "ipc": c.String("ipc"), - "user": c.String("userns"), - "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 we are adding a container to a pod, we would like to add an annotation for the infra ID - // so kata containers can share VMs inside the pod - var podInfraID string - if c.IsSet("pod") { - 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 := 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, podInfraID, err = configurePod(c, runtime, namespaces, podName) - if err != nil { - return nil, err - } - } - - pidMode := ns.PidMode(namespaces["pid"]) - if !cc.Valid(string(pidMode), pidMode) { - return nil, errors.Errorf("--pid %q is not valid", c.String("pid")) - } - - usernsMode := ns.UsernsMode(namespaces["user"]) - if !cc.Valid(string(usernsMode), usernsMode) { - return nil, errors.Errorf("--userns %q is not valid", namespaces["user"]) - } - - utsMode := ns.UTSMode(namespaces["uts"]) - if !cc.Valid(string(utsMode), utsMode) { - return nil, errors.Errorf("--uts %q is not valid", namespaces["uts"]) - } - - cgroupMode := ns.CgroupMode(namespaces["cgroup"]) - if !cgroupMode.Valid() { - return nil, errors.Errorf("--cgroup %q is not valid", namespaces["cgroup"]) - } - - ipcMode := ns.IpcMode(namespaces["ipc"]) - if !cc.Valid(string(ipcMode), ipcMode) { - return nil, errors.Errorf("--ipc %q is not valid", ipcMode) - } - - // 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(portBindings) > 0 { - return nil, errors.Errorf("cannot set port bindings on an existing container network namespace") - } - } - - // USER - user := c.String("user") - if user == "" { - switch { - case usernsMode.IsKeepID(): - user = fmt.Sprintf("%d:%d", rootless.GetRootlessUID(), rootless.GetRootlessGID()) - case data == nil: - user = "0" - default: - user = data.Config.User - } - } - - // STOP SIGNAL - stopSignal := syscall.SIGTERM - signalString := "" - if data != nil { - signalString = data.Config.StopSignal - } - if c.IsSet("stop-signal") { - signalString = c.String("stop-signal") - } - if signalString != "" { - stopSignal, err = util.ParseSignal(signalString) - if err != nil { - return nil, err - } - } - - // ENVIRONMENT VARIABLES - // - // Precedence order (higher index wins): - // 1) env-host, 2) image data, 3) env-file, 4) env - env := map[string]string{ - "container": "podman", - } - - // First transform the os env into a map. We need it for the labels later in - // any case. - osEnv, err := envLib.ParseSlice(os.Environ()) - if err != nil { - return nil, errors.Wrap(err, "error parsing host environment variables") - } - - // Start with env-host - - if c.Bool("env-host") { - env = envLib.Join(env, osEnv) - } - - // Image data overrides any previous variables - if data != nil { - configEnv, err := envLib.ParseSlice(data.Config.Env) - if err != nil { - return nil, errors.Wrap(err, "error passing image environment variables") - } - env = envLib.Join(env, configEnv) - } - - // env-file overrides any previous variables - if c.IsSet("env-file") { - for _, f := range c.StringSlice("env-file") { - fileEnv, err := envLib.ParseFile(f) - if err != nil { - return nil, err - } - // File env is overridden by env. - env = envLib.Join(env, fileEnv) - } - } - - if c.IsSet("env") { - // env overrides any previous variables - cmdlineEnv := c.StringSlice("env") - if len(cmdlineEnv) > 0 { - parsedEnv, err := envLib.ParseSlice(cmdlineEnv) - if err != nil { - return nil, err - } - env = envLib.Join(env, parsedEnv) - } - } - - // LABEL VARIABLES - labels, err := parse.GetAllLabels(c.StringSlice("label-file"), c.StringArray("label")) - if err != nil { - return nil, errors.Wrapf(err, "unable to process labels") - } - if data != nil { - for key, val := range data.Config.Labels { - if _, ok := labels[key]; !ok { - labels[key] = val - } - } - } - - if systemdUnit, exists := osEnv[systemdGen.EnvVariable]; exists { - labels[systemdGen.EnvVariable] = systemdUnit - } - - // ANNOTATIONS - annotations := make(map[string]string) - - // First, add our default annotations - annotations[ann.TTY] = "false" - if tty { - annotations[ann.TTY] = "true" - } - - // in the event this container is in a pod, and the pod has an infra container - // we will want to configure it as a type "container" instead defaulting to - // the behavior of a "sandbox" container - // In Kata containers: - // - "sandbox" is the annotation that denotes the container should use its own - // VM, which is the default behavior - // - "container" denotes the container should join the VM of the SandboxID - // (the infra container) - if podInfraID != "" { - annotations[ann.SandboxID] = podInfraID - annotations[ann.ContainerType] = ann.ContainerTypeContainer - } - - if data != nil { - // Next, add annotations from the image - for key, value := range data.Annotations { - annotations[key] = value - } - } - // Last, add user annotations - for _, annotation := range c.StringSlice("annotation") { - splitAnnotation := strings.SplitN(annotation, "=", 2) - if len(splitAnnotation) < 2 { - return nil, errors.Errorf("Annotations must be formatted KEY=VALUE") - } - annotations[splitAnnotation[0]] = splitAnnotation[1] - } - - // WORKING DIRECTORY - workDir := "/" - if c.IsSet("workdir") { - workDir = c.String("workdir") - } else if data != nil && data.Config.WorkingDir != "" { - workDir = data.Config.WorkingDir - } - - userCommand := []string{} - entrypoint := configureEntrypoint(c, data) - // Build the command - // If we have an entry point, it goes first - if len(entrypoint) > 0 { - command = entrypoint - } - if len(inputCommand) > 0 { - // User command overrides data CMD - command = append(command, inputCommand...) - userCommand = append(userCommand, inputCommand...) - } else if data != nil && len(data.Config.Cmd) > 0 && !c.IsSet("entrypoint") { - // If not user command, add CMD - command = append(command, data.Config.Cmd...) - userCommand = append(userCommand, data.Config.Cmd...) - } - - if data != nil && len(command) == 0 { - return nil, errors.Errorf("No command specified on command line or as CMD or ENTRYPOINT in this image") - } - - // SHM Size - shmSize, err := units.FromHumanSize(c.String("shm-size")) - if err != nil { - return nil, errors.Wrapf(err, "unable to translate --shm-size") - } - - if c.IsSet("add-host") { - // Verify the additional hosts are in correct format - for _, host := range c.StringSlice("add-host") { - if _, err := parse.ValidateExtraHost(host); err != nil { - return nil, err - } - } - } - - var ( - dnsSearches []string - dnsServers []string - dnsOptions []string - ) - if c.Changed("dns-search") { - dnsSearches = c.StringSlice("dns-search") - // Check for explicit dns-search domain of '' - if len(dnsSearches) == 0 { - return nil, errors.Errorf("'' is not a valid domain") - } - // Validate domains are good - for _, dom := range dnsSearches { - if dom == "." { - if len(dnsSearches) > 1 { - return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'") - } - continue - } - if _, err := parse.ValidateDomain(dom); err != nil { - return nil, err - } - } - } - if c.IsSet("dns") { - dnsServers = append(dnsServers, c.StringSlice("dns")...) - } - if c.IsSet("dns-opt") { - dnsOptions = c.StringSlice("dns-opt") - } - - var ImageVolumes map[string]struct{} - if data != nil && c.String("image-volume") != "ignore" { - ImageVolumes = data.Config.Volumes - } - - var imageVolType = map[string]string{ - "bind": "", - "tmpfs": "", - "ignore": "", - } - if _, ok := imageVolType[c.String("image-volume")]; !ok { - return nil, errors.Errorf("invalid image-volume type %q. Pick one of bind, tmpfs, or ignore", c.String("image-volume")) - } - - systemd := c.String("systemd") == "always" - if !systemd && command != nil { - x, err := strconv.ParseBool(c.String("systemd")) - if err != nil { - return nil, errors.Wrapf(err, "cannot parse bool %s", c.String("systemd")) - } - if x && (command[0] == "/usr/sbin/init" || command[0] == "/sbin/init" || (filepath.Base(command[0]) == "systemd")) { - systemd = true - } - } - if systemd { - if signalString == "" { - stopSignal, err = util.ParseSignal("RTMIN+3") - if err != nil { - return nil, errors.Wrapf(err, "error parsing systemd signal") - } - } - } - // This is done because cobra cannot have two aliased flags. So we have to check - // both - memorySwappiness := c.Int64("memory-swappiness") - - logDriver := libpod.KubernetesLogging - if c.Changed("log-driver") { - logDriver = c.String("log-driver") - } - - pidsLimit := c.Int64("pids-limit") - if c.String("cgroups") == "disabled" && !c.Changed("pids-limit") { - pidsLimit = -1 - } - - pid := &cc.PidConfig{ - PidMode: pidMode, - } - ipc := &cc.IpcConfig{ - IpcMode: ipcMode, - } - - cgroup := &cc.CgroupConfig{ - Cgroups: c.String("cgroups"), - Cgroupns: c.String("cgroupns"), - CgroupParent: c.String("cgroup-parent"), - CgroupMode: cgroupMode, - } - - userns := &cc.UserConfig{ - GroupAdd: c.StringSlice("group-add"), - IDMappings: idmappings, - UsernsMode: usernsMode, - User: user, - } - - uts := &cc.UtsConfig{ - UtsMode: utsMode, - NoHosts: c.Bool("no-hosts"), - HostAdd: c.StringSlice("add-host"), - Hostname: c.String("hostname"), - } - net := &cc.NetworkConfig{ - DNSOpt: dnsOptions, - DNSSearch: dnsSearches, - DNSServers: dnsServers, - HTTPProxy: c.Bool("http-proxy"), - MacAddress: c.String("mac-address"), - Network: c.String("network"), - NetMode: netMode, - IPAddress: c.String("ip"), - Publish: c.StringSlice("publish"), - PublishAll: c.Bool("publish-all"), - PortBindings: portBindings, - } - - sysctl := map[string]string{} - if c.Changed("sysctl") { - sysctl, err = util.ValidateSysctls(c.StringSlice("sysctl")) - if err != nil { - return nil, errors.Wrapf(err, "invalid value for sysctl") - } - } - - secConfig := &cc.SecurityConfig{ - CapAdd: c.StringSlice("cap-add"), - CapDrop: c.StringSlice("cap-drop"), - Privileged: c.Bool("privileged"), - ReadOnlyRootfs: c.Bool("read-only"), - ReadOnlyTmpfs: c.Bool("read-only-tmpfs"), - Sysctl: sysctl, - } - - if c.Changed("security-opt") { - if err := secConfig.SetSecurityOpts(runtime, c.StringArray("security-opt")); err != nil { - return nil, err - } - } - - // SECCOMP - if data != nil { - if value, exists := labels[seccomp.ContainerImageLabel]; exists { - secConfig.SeccompProfileFromImage = value - } - } - if policy, err := seccomp.LookupPolicy(c.String("seccomp-policy")); err != nil { - return nil, err - } else { - secConfig.SeccompPolicy = policy - } - rtc, err := runtime.GetConfig() - if err != nil { - return nil, err - } - volumes := rtc.Containers.Volumes - if c.Changed("volume") { - volumes = append(volumes, c.StringSlice("volume")...) - } - - devices := rtc.Containers.Devices - if c.Changed("device") { - devices = append(devices, c.StringSlice("device")...) - } - - config := &cc.CreateConfig{ - Annotations: annotations, - BuiltinImgVolumes: ImageVolumes, - ConmonPidFile: c.String("conmon-pidfile"), - ImageVolumeType: c.String("image-volume"), - CidFile: c.String("cidfile"), - Command: command, - UserCommand: userCommand, - Detach: c.Bool("detach"), - Devices: devices, - Entrypoint: entrypoint, - Env: env, - // ExposedPorts: ports, - Init: c.Bool("init"), - InitPath: c.String("init-path"), - Image: imageName, - RawImageName: rawImageName, - ImageID: imageID, - Interactive: c.Bool("interactive"), - // IP6Address: c.String("ipv6"), // Not implemented yet - needs CNI support for static v6 - Labels: labels, - // LinkLocalIP: c.StringSlice("link-local-ip"), // Not implemented yet - LogDriver: logDriver, - LogDriverOpt: c.StringSlice("log-opt"), - Name: c.String("name"), - // NetworkAlias: c.StringSlice("network-alias"), // Not implemented - does this make sense in Podman? - Pod: podName, - Quiet: c.Bool("quiet"), - Resources: cc.CreateResourceConfig{ - BlkioWeight: blkioWeight, - BlkioWeightDevice: c.StringSlice("blkio-weight-device"), - CPUShares: c.Uint64("cpu-shares"), - CPUPeriod: c.Uint64("cpu-period"), - CPUsetCPUs: c.String("cpuset-cpus"), - CPUsetMems: c.String("cpuset-mems"), - CPUQuota: c.Int64("cpu-quota"), - CPURtPeriod: c.Uint64("cpu-rt-period"), - CPURtRuntime: c.Int64("cpu-rt-runtime"), - CPUs: c.Float64("cpus"), - DeviceCgroupRules: c.StringSlice("device-cgroup-rule"), - DeviceReadBps: c.StringSlice("device-read-bps"), - DeviceReadIOps: c.StringSlice("device-read-iops"), - DeviceWriteBps: c.StringSlice("device-write-bps"), - DeviceWriteIOps: c.StringSlice("device-write-iops"), - DisableOomKiller: c.Bool("oom-kill-disable"), - ShmSize: shmSize, - Memory: memoryLimit, - MemoryReservation: memoryReservation, - MemorySwap: memorySwap, - MemorySwappiness: int(memorySwappiness), - KernelMemory: memoryKernel, - OomScoreAdj: c.Int("oom-score-adj"), - PidsLimit: pidsLimit, - Ulimit: c.StringSlice("ulimit"), - }, - RestartPolicy: c.String("restart"), - Rm: c.Bool("rm"), - Security: *secConfig, - StopSignal: stopSignal, - StopTimeout: c.Uint("stop-timeout"), - Systemd: systemd, - Tmpfs: c.StringArray("tmpfs"), - Tty: tty, - MountsFlag: c.StringArray("mount"), - Volumes: volumes, - WorkDir: workDir, - Rootfs: rootfs, - VolumesFrom: c.StringSlice("volumes-from"), - Syslog: c.Bool("syslog"), - - Pid: *pid, - Ipc: *ipc, - Cgroup: *cgroup, - User: *userns, - Uts: *uts, - Network: *net, - } - - warnings, err := verifyContainerResources(config, false) - if err != nil { - return nil, err - } - for _, warning := range warnings { - fmt.Fprintln(os.Stderr, warning) - } - return config, nil -} - -func CreateContainerFromCreateConfig(r *libpod.Runtime, createConfig *cc.CreateConfig, ctx context.Context, pod *libpod.Pod) (*libpod.Container, error) { - runtimeSpec, options, err := createConfig.MakeContainerConfig(r, pod) - if err != nil { - return nil, err - } - - // Set the CreateCommand explicitly. Some (future) consumers of libpod - // might not want to set it. - options = append(options, libpod.WithCreateCommand()) - - ctr, err := r.NewContainer(ctx, runtimeSpec, options...) - if err != nil { - return nil, err - } - return ctr, nil -} - -func makeHealthCheckFromCli(c *GenericCLIResults) (*manifest.Schema2HealthConfig, error) { - inCommand := c.String("healthcheck-command") - inInterval := c.String("healthcheck-interval") - inRetries := c.Uint("healthcheck-retries") - inTimeout := c.String("healthcheck-timeout") - inStartPeriod := c.String("healthcheck-start-period") - - // Every healthcheck requires a command - if len(inCommand) == 0 { - return nil, errors.New("Must define a healthcheck command for all healthchecks") - } - - // first try to parse option value as JSON array of strings... - cmd := []string{} - err := json.Unmarshal([]byte(inCommand), &cmd) - if err != nil { - // ...otherwise pass it to "/bin/sh -c" inside the container - cmd = []string{"CMD-SHELL", inCommand} - } - hc := manifest.Schema2HealthConfig{ - Test: cmd, - } - - if inInterval == "disable" { - inInterval = "0" - } - intervalDuration, err := time.ParseDuration(inInterval) - if err != nil { - return nil, errors.Wrapf(err, "invalid healthcheck-interval %s ", inInterval) - } - - hc.Interval = intervalDuration - - if inRetries < 1 { - return nil, errors.New("healthcheck-retries must be greater than 0.") - } - hc.Retries = int(inRetries) - timeoutDuration, err := time.ParseDuration(inTimeout) - if err != nil { - return nil, errors.Wrapf(err, "invalid healthcheck-timeout %s", inTimeout) - } - if timeoutDuration < time.Duration(1) { - return nil, errors.New("healthcheck-timeout must be at least 1 second") - } - hc.Timeout = timeoutDuration - - startPeriodDuration, err := time.ParseDuration(inStartPeriod) - if err != nil { - return nil, errors.Wrapf(err, "invalid healthcheck-start-period %s", inStartPeriod) - } - if startPeriodDuration < time.Duration(0) { - return nil, errors.New("healthcheck-start-period must be 0 seconds or greater") - } - hc.StartPeriod = startPeriodDuration - - return &hc, nil -} diff --git a/cmd/podman/shared/create_cli.go b/cmd/podman/shared/create_cli.go deleted file mode 100644 index 10e27350b..000000000 --- a/cmd/podman/shared/create_cli.go +++ /dev/null @@ -1,192 +0,0 @@ -package shared - -import ( - "fmt" - "strings" - - "github.com/containers/libpod/pkg/cgroups" - cc "github.com/containers/libpod/pkg/spec" - "github.com/containers/libpod/pkg/sysinfo" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// validateSysctl validates a sysctl and returns it. -func validateSysctl(strSlice []string) (map[string]string, error) { - sysctl := make(map[string]string) - validSysctlMap := map[string]bool{ - "kernel.msgmax": true, - "kernel.msgmnb": true, - "kernel.msgmni": true, - "kernel.sem": true, - "kernel.shmall": true, - "kernel.shmmax": true, - "kernel.shmmni": true, - "kernel.shm_rmid_forced": true, - } - validSysctlPrefixes := []string{ - "net.", - "fs.mqueue.", - } - - for _, val := range strSlice { - foundMatch := false - arr := strings.Split(val, "=") - if len(arr) < 2 { - return nil, errors.Errorf("%s is invalid, sysctl values must be in the form of KEY=VALUE", val) - } - if validSysctlMap[arr[0]] { - sysctl[arr[0]] = arr[1] - continue - } - - for _, prefix := range validSysctlPrefixes { - if strings.HasPrefix(arr[0], prefix) { - sysctl[arr[0]] = arr[1] - foundMatch = true - break - } - } - if !foundMatch { - return nil, errors.Errorf("sysctl '%s' is not whitelisted", arr[0]) - } - } - return sysctl, nil -} - -func addWarning(warnings []string, msg string) []string { - logrus.Warn(msg) - return append(warnings, msg) -} - -func verifyContainerResources(config *cc.CreateConfig, update bool) ([]string, error) { - warnings := []string{} - - cgroup2, err := cgroups.IsCgroup2UnifiedMode() - if err != nil || cgroup2 { - return warnings, err - } - - sysInfo := sysinfo.New(true) - - // memory subsystem checks and adjustments - if config.Resources.Memory > 0 && !sysInfo.MemoryLimit { - warnings = addWarning(warnings, "Your kernel does not support memory limit capabilities or the cgroup is not mounted. Limitation discarded.") - config.Resources.Memory = 0 - config.Resources.MemorySwap = -1 - } - if config.Resources.Memory > 0 && config.Resources.MemorySwap != -1 && !sysInfo.SwapLimit { - warnings = addWarning(warnings, "Your kernel does not support swap limit capabilities,or the cgroup is not mounted. Memory limited without swap.") - config.Resources.MemorySwap = -1 - } - if config.Resources.Memory > 0 && config.Resources.MemorySwap > 0 && config.Resources.MemorySwap < config.Resources.Memory { - return warnings, fmt.Errorf("minimum memoryswap limit should be larger than memory limit, see usage") - } - if config.Resources.Memory == 0 && config.Resources.MemorySwap > 0 && !update { - return warnings, fmt.Errorf("you should always set the memory limit when using memoryswap limit, see usage") - } - if config.Resources.MemorySwappiness != -1 { - if !sysInfo.MemorySwappiness { - msg := "Your kernel does not support memory swappiness capabilities, or the cgroup is not mounted. Memory swappiness discarded." - warnings = addWarning(warnings, msg) - config.Resources.MemorySwappiness = -1 - } else { - swappiness := config.Resources.MemorySwappiness - if swappiness < -1 || swappiness > 100 { - return warnings, fmt.Errorf("invalid value: %v, valid memory swappiness range is 0-100", swappiness) - } - } - } - if config.Resources.MemoryReservation > 0 && !sysInfo.MemoryReservation { - warnings = addWarning(warnings, "Your kernel does not support memory soft limit capabilities or the cgroup is not mounted. Limitation discarded.") - config.Resources.MemoryReservation = 0 - } - if config.Resources.Memory > 0 && config.Resources.MemoryReservation > 0 && config.Resources.Memory < config.Resources.MemoryReservation { - return warnings, fmt.Errorf("minimum memory limit cannot be less than memory reservation limit, see usage") - } - if config.Resources.KernelMemory > 0 && !sysInfo.KernelMemory { - warnings = addWarning(warnings, "Your kernel does not support kernel memory limit capabilities or the cgroup is not mounted. Limitation discarded.") - config.Resources.KernelMemory = 0 - } - if config.Resources.DisableOomKiller && !sysInfo.OomKillDisable { - // only produce warnings if the setting wasn't to *disable* the OOM Kill; no point - // warning the caller if they already wanted the feature to be off - warnings = addWarning(warnings, "Your kernel does not support OomKillDisable. OomKillDisable discarded.") - config.Resources.DisableOomKiller = false - } - - if config.Resources.PidsLimit != 0 && !sysInfo.PidsLimit { - warnings = addWarning(warnings, "Your kernel does not support pids limit capabilities or the cgroup is not mounted. PIDs limit discarded.") - config.Resources.PidsLimit = 0 - } - - if config.Resources.CPUShares > 0 && !sysInfo.CPUShares { - warnings = addWarning(warnings, "Your kernel does not support CPU shares or the cgroup is not mounted. Shares discarded.") - config.Resources.CPUShares = 0 - } - if config.Resources.CPUPeriod > 0 && !sysInfo.CPUCfsPeriod { - warnings = addWarning(warnings, "Your kernel does not support CPU cfs period or the cgroup is not mounted. Period discarded.") - config.Resources.CPUPeriod = 0 - } - if config.Resources.CPUPeriod != 0 && (config.Resources.CPUPeriod < 1000 || config.Resources.CPUPeriod > 1000000) { - return warnings, fmt.Errorf("CPU cfs period cannot be less than 1ms (i.e. 1000) or larger than 1s (i.e. 1000000)") - } - if config.Resources.CPUQuota > 0 && !sysInfo.CPUCfsQuota { - warnings = addWarning(warnings, "Your kernel does not support CPU cfs quota or the cgroup is not mounted. Quota discarded.") - config.Resources.CPUQuota = 0 - } - if config.Resources.CPUQuota > 0 && config.Resources.CPUQuota < 1000 { - return warnings, fmt.Errorf("CPU cfs quota cannot be less than 1ms (i.e. 1000)") - } - // cpuset subsystem checks and adjustments - if (config.Resources.CPUsetCPUs != "" || config.Resources.CPUsetMems != "") && !sysInfo.Cpuset { - warnings = addWarning(warnings, "Your kernel does not support cpuset or the cgroup is not mounted. CPUset discarded.") - config.Resources.CPUsetCPUs = "" - config.Resources.CPUsetMems = "" - } - cpusAvailable, err := sysInfo.IsCpusetCpusAvailable(config.Resources.CPUsetCPUs) - if err != nil { - return warnings, fmt.Errorf("invalid value %s for cpuset cpus", config.Resources.CPUsetCPUs) - } - if !cpusAvailable { - return warnings, fmt.Errorf("requested CPUs are not available - requested %s, available: %s", config.Resources.CPUsetCPUs, sysInfo.Cpus) - } - memsAvailable, err := sysInfo.IsCpusetMemsAvailable(config.Resources.CPUsetMems) - if err != nil { - return warnings, fmt.Errorf("invalid value %s for cpuset mems", config.Resources.CPUsetMems) - } - if !memsAvailable { - return warnings, fmt.Errorf("requested memory nodes are not available - requested %s, available: %s", config.Resources.CPUsetMems, sysInfo.Mems) - } - - // blkio subsystem checks and adjustments - if config.Resources.BlkioWeight > 0 && !sysInfo.BlkioWeight { - warnings = addWarning(warnings, "Your kernel does not support Block I/O weight or the cgroup is not mounted. Weight discarded.") - config.Resources.BlkioWeight = 0 - } - if config.Resources.BlkioWeight > 0 && (config.Resources.BlkioWeight < 10 || config.Resources.BlkioWeight > 1000) { - return warnings, fmt.Errorf("range of blkio weight is from 10 to 1000") - } - if len(config.Resources.BlkioWeightDevice) > 0 && !sysInfo.BlkioWeightDevice { - warnings = addWarning(warnings, "Your kernel does not support Block I/O weight_device or the cgroup is not mounted. Weight-device discarded.") - config.Resources.BlkioWeightDevice = []string{} - } - if len(config.Resources.DeviceReadBps) > 0 && !sysInfo.BlkioReadBpsDevice { - warnings = addWarning(warnings, "Your kernel does not support BPS Block I/O read limit or the cgroup is not mounted. Block I/O BPS read limit discarded") - config.Resources.DeviceReadBps = []string{} - } - if len(config.Resources.DeviceWriteBps) > 0 && !sysInfo.BlkioWriteBpsDevice { - warnings = addWarning(warnings, "Your kernel does not support BPS Block I/O write limit or the cgroup is not mounted. Block I/O BPS write limit discarded.") - config.Resources.DeviceWriteBps = []string{} - } - if len(config.Resources.DeviceReadIOps) > 0 && !sysInfo.BlkioReadIOpsDevice { - warnings = addWarning(warnings, "Your kernel does not support IOPS Block read limit or the cgroup is not mounted. Block I/O IOPS read limit discarded.") - config.Resources.DeviceReadIOps = []string{} - } - if len(config.Resources.DeviceWriteIOps) > 0 && !sysInfo.BlkioWriteIOpsDevice { - warnings = addWarning(warnings, "Your kernel does not support IOPS Block I/O write limit or the cgroup is not mounted. Block I/O IOPS write limit discarded.") - config.Resources.DeviceWriteIOps = []string{} - } - - return warnings, nil -} diff --git a/cmd/podman/shared/create_cli_test.go b/cmd/podman/shared/create_cli_test.go deleted file mode 100644 index a045962cb..000000000 --- a/cmd/podman/shared/create_cli_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package shared - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestValidateSysctl(t *testing.T) { - strSlice := []string{"net.core.test1=4", "kernel.msgmax=2"} - result, _ := validateSysctl(strSlice) - assert.Equal(t, result["net.core.test1"], "4") -} - -func TestValidateSysctlBadSysctl(t *testing.T) { - strSlice := []string{"BLAU=BLUE", "GELB^YELLOW"} - _, err := validateSysctl(strSlice) - assert.Error(t, err) -} diff --git a/cmd/podman/shared/funcs.go b/cmd/podman/shared/funcs.go deleted file mode 100644 index 404d0f288..000000000 --- a/cmd/podman/shared/funcs.go +++ /dev/null @@ -1,121 +0,0 @@ -package shared - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/containers/image/v5/types" - "github.com/containers/libpod/libpod/image" - "github.com/google/shlex" - "github.com/pkg/errors" -) - -func GetSystemContext(authfile string) (*types.SystemContext, error) { - if authfile != "" { - if _, err := os.Stat(authfile); err != nil { - return nil, errors.Wrapf(err, "error checking authfile path %s", authfile) - } - } - return image.GetSystemContext("", authfile, false), nil -} - -func substituteCommand(cmd string) (string, error) { - var ( - newCommand string - ) - - // Replace cmd with "/proc/self/exe" if "podman" or "docker" is being - // used. If "/usr/bin/docker" is provided, we also sub in podman. - // Otherwise, leave the command unchanged. - if cmd == "podman" || filepath.Base(cmd) == "docker" { - newCommand = "/proc/self/exe" - } else { - newCommand = cmd - } - - // If cmd is an absolute or relative path, check if the file exists. - // Throw an error if it doesn't exist. - if strings.Contains(newCommand, "/") || strings.HasPrefix(newCommand, ".") { - res, err := filepath.Abs(newCommand) - if err != nil { - return "", err - } - if _, err := os.Stat(res); !os.IsNotExist(err) { - return res, nil - } else if err != nil { - return "", err - } - } - - return newCommand, nil -} - -// GenerateCommand takes a label (string) and converts it to an executable command -func GenerateCommand(command, imageName, name, globalOpts string) ([]string, error) { - var ( - newCommand []string - ) - if name == "" { - name = imageName - } - - cmd, err := shlex.Split(command) - if err != nil { - return nil, err - } - - prog, err := substituteCommand(cmd[0]) - if err != nil { - return nil, err - } - newCommand = append(newCommand, prog) - - for _, arg := range cmd[1:] { - var newArg string - switch arg { - case "IMAGE": - newArg = imageName - case "$IMAGE": - newArg = imageName - case "IMAGE=IMAGE": - newArg = fmt.Sprintf("IMAGE=%s", imageName) - case "IMAGE=$IMAGE": - newArg = fmt.Sprintf("IMAGE=%s", imageName) - case "NAME": - newArg = name - case "NAME=NAME": - newArg = fmt.Sprintf("NAME=%s", name) - case "NAME=$NAME": - newArg = fmt.Sprintf("NAME=%s", name) - case "$NAME": - newArg = name - case "$GLOBAL_OPTS": - newArg = globalOpts - default: - newArg = arg - } - newCommand = append(newCommand, newArg) - } - return newCommand, nil -} - -// GenerateRunEnvironment merges the current environment variables with optional -// environment variables provided by the user -func GenerateRunEnvironment(name, imageName string, opts map[string]string) []string { - newEnv := os.Environ() - newEnv = append(newEnv, fmt.Sprintf("NAME=%s", name)) - newEnv = append(newEnv, fmt.Sprintf("IMAGE=%s", imageName)) - - if opts["opt1"] != "" { - newEnv = append(newEnv, fmt.Sprintf("OPT1=%s", opts["opt1"])) - } - if opts["opt2"] != "" { - newEnv = append(newEnv, fmt.Sprintf("OPT2=%s", opts["opt2"])) - } - if opts["opt3"] != "" { - newEnv = append(newEnv, fmt.Sprintf("OPT3=%s", opts["opt3"])) - } - return newEnv -} diff --git a/cmd/podman/shared/funcs_linux_test.go b/cmd/podman/shared/funcs_linux_test.go deleted file mode 100644 index 88571153f..000000000 --- a/cmd/podman/shared/funcs_linux_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package shared - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGenerateCommand(t *testing.T) { - 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, " ")) -} - -func TestGenerateCommandCheckSubstitution(t *testing.T) { - type subsTest struct { - input string - expected string - shouldFail bool - } - - absTmpFile, err := ioutil.TempFile("", "podmanRunlabelTestAbsolutePath") - assert.Nil(t, err, "error creating tempfile") - defer os.Remove(absTmpFile.Name()) - - relTmpFile, err := ioutil.TempFile("./", "podmanRunlabelTestRelativePath") - assert.Nil(t, err, "error creating tempfile") - defer os.Remove(relTmpFile.Name()) - relTmpCmd, err := filepath.Abs(relTmpFile.Name()) - assert.Nil(t, err, "error getting absolute path for relative tmpfile") - - // this has a (low) potential of race conditions but no other way - removedTmpFile, err := ioutil.TempFile("", "podmanRunlabelTestRemove") - assert.Nil(t, err, "error creating tempfile") - os.Remove(removedTmpFile.Name()) - - absTmpCmd := fmt.Sprintf("%s --flag1 --flag2 --args=foo", absTmpFile.Name()) - tests := []subsTest{ - { - input: "docker run -it alpine:latest", - expected: "/proc/self/exe run -it alpine:latest", - shouldFail: false, - }, - { - input: "podman run -it alpine:latest", - expected: "/proc/self/exe run -it alpine:latest", - shouldFail: false, - }, - { - input: absTmpCmd, - expected: absTmpCmd, - shouldFail: false, - }, - { - input: "./" + relTmpFile.Name(), - expected: relTmpCmd, - shouldFail: false, - }, - { - input: "ls -la", - expected: "ls -la", - shouldFail: false, - }, - { - input: removedTmpFile.Name(), - expected: "", - shouldFail: true, - }, - } - - for _, test := range tests { - newCommand, err := GenerateCommand(test.input, "foo", "bar", "") - if test.shouldFail { - assert.NotNil(t, err) - } else { - assert.Nil(t, err) - } - assert.Equal(t, test.expected, strings.Join(newCommand, " ")) - } -} - -func TestGenerateCommandPath(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" - newCommand, _ := GenerateCommand(inputCommand, "foo", "bar", "") - assert.Equal(t, correctCommand, strings.Join(newCommand, " ")) -} - -func TestGenerateCommandNoSetName(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 foo -e NAME=foo -e IMAGE=foo foo echo install" - newCommand, err := GenerateCommand(inputCommand, "foo", "", "") - assert.Nil(t, err) - assert.Equal(t, correctCommand, strings.Join(newCommand, " ")) -} - -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" - newCommand, err := GenerateCommand(inputCommand, "foo", "", "") - assert.Nil(t, err) - assert.Equal(t, correctCommand, strings.Join(newCommand, " ")) -} - -func TestGenerateCommandAlreadyPodman(t *testing.T) { - inputCommand := "podman 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" - newCommand, err := GenerateCommand(inputCommand, "foo", "bar", "") - assert.Nil(t, err) - assert.Equal(t, correctCommand, strings.Join(newCommand, " ")) -} diff --git a/cmd/podman/shared/funcs_test.go b/cmd/podman/shared/funcs_test.go deleted file mode 100644 index dd856166e..000000000 --- a/cmd/podman/shared/funcs_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package shared - -import ( - "testing" - - "github.com/containers/libpod/pkg/util" - "github.com/stretchr/testify/assert" -) - -var ( - name = "foo" - imageName = "bar" -) - -func TestGenerateRunEnvironment(t *testing.T) { - opts := make(map[string]string) - opts["opt1"] = "one" - opts["opt2"] = "two" - opts["opt3"] = "three" - envs := GenerateRunEnvironment(name, imageName, opts) - assert.True(t, util.StringInSlice("OPT1=one", envs)) - assert.True(t, util.StringInSlice("OPT2=two", envs)) - assert.True(t, util.StringInSlice("OPT3=three", envs)) -} - -func TestGenerateRunEnvironmentNoOpts(t *testing.T) { - opts := make(map[string]string) - envs := GenerateRunEnvironment(name, imageName, opts) - assert.False(t, util.StringInSlice("OPT1=", envs)) - assert.False(t, util.StringInSlice("OPT2=", envs)) - assert.False(t, util.StringInSlice("OPT3=", envs)) -} - -func TestGenerateRunEnvironmentSingleOpt(t *testing.T) { - opts := make(map[string]string) - opts["opt1"] = "one" - envs := GenerateRunEnvironment(name, imageName, opts) - assert.True(t, util.StringInSlice("OPT1=one", envs)) - assert.False(t, util.StringInSlice("OPT2=", envs)) - assert.False(t, util.StringInSlice("OPT3=", envs)) -} - -func TestGenerateRunEnvironmentName(t *testing.T) { - opts := make(map[string]string) - envs := GenerateRunEnvironment(name, imageName, opts) - assert.True(t, util.StringInSlice("NAME=foo", envs)) -} - -func TestGenerateRunEnvironmentImage(t *testing.T) { - opts := make(map[string]string) - envs := GenerateRunEnvironment(name, imageName, opts) - assert.True(t, util.StringInSlice("IMAGE=bar", envs)) -} diff --git a/cmd/podman/shared/intermediate.go b/cmd/podman/shared/intermediate.go deleted file mode 100644 index e76750042..000000000 --- a/cmd/podman/shared/intermediate.go +++ /dev/null @@ -1,479 +0,0 @@ -package shared - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/sirupsen/logrus" -) - -/* -attention - -in this file you will see a lot of struct duplication. this was done because people wanted a strongly typed -varlink mechanism. this resulted in us creating this intermediate layer that allows us to take the input -from the cli and make an intermediate layer which can be transferred as strongly typed structures over a varlink -interface. - -we intentionally avoided heavy use of reflection here because we were concerned about performance impacts to the -non-varlink intermediate layer generation. -*/ - -// GenericCLIResult describes the overall interface for dealing with -// the create command cli in both local and remote uses -type GenericCLIResult interface { - IsSet() bool - Name() string - Value() interface{} -} - -// CRStringSlice describes a string slice cli struct -type CRStringSlice struct { - Val []string - createResult -} - -// CRString describes a string cli struct -type CRString struct { - Val string - createResult -} - -// CRUint64 describes a uint64 cli struct -type CRUint64 struct { - Val uint64 - createResult -} - -// CRFloat64 describes a float64 cli struct -type CRFloat64 struct { - Val float64 - createResult -} - -//CRBool describes a bool cli struct -type CRBool struct { - Val bool - createResult -} - -// CRInt64 describes an int64 cli struct -type CRInt64 struct { - Val int64 - createResult -} - -// CRUint describes a uint cli struct -type CRUint struct { - Val uint - createResult -} - -// CRInt describes an int cli struct -type CRInt struct { - Val int - createResult -} - -// CRStringArray describes a stringarray cli struct -type CRStringArray struct { - Val []string - createResult -} - -type createResult struct { - Flag string - Changed bool -} - -// GenericCLIResults in the intermediate object between the cobra cli -// and createconfig -type GenericCLIResults struct { - results map[string]GenericCLIResult - InputArgs []string -} - -// IsSet returns a bool if the flag was changed -func (f GenericCLIResults) IsSet(flag string) bool { - r := f.findResult(flag) - if r == nil { - return false - } - return r.IsSet() -} - -// Value returns the value of the cli flag -func (f GenericCLIResults) Value(flag string) interface{} { - r := f.findResult(flag) - if r == nil { - return "" - } - return r.Value() -} - -func (f GenericCLIResults) findResult(flag string) GenericCLIResult { - val, ok := f.results[flag] - if ok { - return val - } - logrus.Debugf("unable to find flag %s", flag) - return nil -} - -// Bool is a wrapper to get a bool value from GenericCLIResults -func (f GenericCLIResults) Bool(flag string) bool { - r := f.findResult(flag) - if r == nil { - return false - } - return r.Value().(bool) -} - -// String is a wrapper to get a string value from GenericCLIResults -func (f GenericCLIResults) String(flag string) string { - r := f.findResult(flag) - if r == nil { - return "" - } - return r.Value().(string) -} - -// Uint is a wrapper to get an uint value from GenericCLIResults -func (f GenericCLIResults) Uint(flag string) uint { - r := f.findResult(flag) - if r == nil { - return 0 - } - return r.Value().(uint) -} - -// StringSlice is a wrapper to get a stringslice value from GenericCLIResults -func (f GenericCLIResults) StringSlice(flag string) []string { - r := f.findResult(flag) - if r == nil { - return []string{} - } - return r.Value().([]string) -} - -// StringArray is a wrapper to get a stringslice value from GenericCLIResults -func (f GenericCLIResults) StringArray(flag string) []string { - r := f.findResult(flag) - if r == nil { - return []string{} - } - return r.Value().([]string) -} - -// Uint64 is a wrapper to get an uint64 value from GenericCLIResults -func (f GenericCLIResults) Uint64(flag string) uint64 { - r := f.findResult(flag) - if r == nil { - return 0 - } - return r.Value().(uint64) -} - -// Int64 is a wrapper to get an int64 value from GenericCLIResults -func (f GenericCLIResults) Int64(flag string) int64 { - r := f.findResult(flag) - if r == nil { - return 0 - } - return r.Value().(int64) -} - -// Int is a wrapper to get an int value from GenericCLIResults -func (f GenericCLIResults) Int(flag string) int { - r := f.findResult(flag) - if r == nil { - return 0 - } - return r.Value().(int) -} - -// Float64 is a wrapper to get an float64 value from GenericCLIResults -func (f GenericCLIResults) Float64(flag string) float64 { - r := f.findResult(flag) - if r == nil { - return 0 - } - return r.Value().(float64) -} - -// Float64 is a wrapper to get an float64 value from GenericCLIResults -func (f GenericCLIResults) Changed(flag string) bool { - r := f.findResult(flag) - if r == nil { - return false - } - return r.IsSet() -} - -// IsSet ... -func (c CRStringSlice) IsSet() bool { return c.Changed } - -// Name ... -func (c CRStringSlice) Name() string { return c.Flag } - -// Value ... -func (c CRStringSlice) Value() interface{} { return c.Val } - -// IsSet ... -func (c CRString) IsSet() bool { return c.Changed } - -// Name ... -func (c CRString) Name() string { return c.Flag } - -// Value ... -func (c CRString) Value() interface{} { return c.Val } - -// IsSet ... -func (c CRUint64) IsSet() bool { return c.Changed } - -// Name ... -func (c CRUint64) Name() string { return c.Flag } - -// Value ... -func (c CRUint64) Value() interface{} { return c.Val } - -// IsSet ... -func (c CRFloat64) IsSet() bool { return c.Changed } - -// Name ... -func (c CRFloat64) Name() string { return c.Flag } - -// Value ... -func (c CRFloat64) Value() interface{} { return c.Val } - -// IsSet ... -func (c CRBool) IsSet() bool { return c.Changed } - -// Name ... -func (c CRBool) Name() string { return c.Flag } - -// Value ... -func (c CRBool) Value() interface{} { return c.Val } - -// IsSet ... -func (c CRInt64) IsSet() bool { return c.Changed } - -// Name ... -func (c CRInt64) Name() string { return c.Flag } - -// Value ... -func (c CRInt64) Value() interface{} { return c.Val } - -// IsSet ... -func (c CRUint) IsSet() bool { return c.Changed } - -// Name ... -func (c CRUint) Name() string { return c.Flag } - -// Value ... -func (c CRUint) Value() interface{} { return c.Val } - -// IsSet ... -func (c CRInt) IsSet() bool { return c.Changed } - -// Name ... -func (c CRInt) Name() string { return c.Flag } - -// Value ... -func (c CRInt) Value() interface{} { return c.Val } - -// IsSet ... -func (c CRStringArray) IsSet() bool { return c.Changed } - -// Name ... -func (c CRStringArray) Name() string { return c.Flag } - -// Value ... -func (c CRStringArray) Value() interface{} { return c.Val } - -func newCreateResult(c *cliconfig.PodmanCommand, flag string) createResult { - return createResult{ - Flag: flag, - Changed: c.IsSet(flag), - } -} - -func newCRStringSlice(c *cliconfig.PodmanCommand, flag string) CRStringSlice { - return CRStringSlice{ - Val: c.StringSlice(flag), - createResult: newCreateResult(c, flag), - } -} - -func newCRString(c *cliconfig.PodmanCommand, flag string) CRString { - return CRString{ - Val: c.String(flag), - createResult: newCreateResult(c, flag), - } -} - -func newCRUint64(c *cliconfig.PodmanCommand, flag string) CRUint64 { - return CRUint64{ - Val: c.Uint64(flag), - createResult: newCreateResult(c, flag), - } -} - -func newCRFloat64(c *cliconfig.PodmanCommand, flag string) CRFloat64 { - return CRFloat64{ - Val: c.Float64(flag), - createResult: newCreateResult(c, flag), - } -} - -func newCRBool(c *cliconfig.PodmanCommand, flag string) CRBool { - return CRBool{ - Val: c.Bool(flag), - createResult: newCreateResult(c, flag), - } -} - -func newCRInt64(c *cliconfig.PodmanCommand, flag string) CRInt64 { - return CRInt64{ - Val: c.Int64(flag), - createResult: newCreateResult(c, flag), - } -} - -func newCRUint(c *cliconfig.PodmanCommand, flag string) CRUint { - return CRUint{ - Val: c.Uint(flag), - createResult: newCreateResult(c, flag), - } -} - -func newCRInt(c *cliconfig.PodmanCommand, flag string) CRInt { - return CRInt{ - Val: c.Int(flag), - createResult: newCreateResult(c, flag), - } -} - -func newCRStringArray(c *cliconfig.PodmanCommand, flag string) CRStringArray { - return CRStringArray{ - Val: c.StringArray(flag), - createResult: newCreateResult(c, flag), - } -} - -// NewIntermediateLayer creates a GenericCLIResults from a create or run cli-command -func NewIntermediateLayer(c *cliconfig.PodmanCommand, remote bool) GenericCLIResults { - m := make(map[string]GenericCLIResult) - - m["add-host"] = newCRStringSlice(c, "add-host") - m["annotation"] = newCRStringSlice(c, "annotation") - m["attach"] = newCRStringSlice(c, "attach") - m["blkio-weight"] = newCRString(c, "blkio-weight") - m["blkio-weight-device"] = newCRStringSlice(c, "blkio-weight-device") - m["cap-add"] = newCRStringSlice(c, "cap-add") - m["cap-drop"] = newCRStringSlice(c, "cap-drop") - m["cgroupns"] = newCRString(c, "cgroupns") - m["cgroups"] = newCRString(c, "cgroups") - m["cgroup-parent"] = newCRString(c, "cgroup-parent") - m["cidfile"] = newCRString(c, "cidfile") - m["conmon-pidfile"] = newCRString(c, "conmon-pidfile") - m["cpu-period"] = newCRUint64(c, "cpu-period") - m["cpu-quota"] = newCRInt64(c, "cpu-quota") - m["cpu-rt-period"] = newCRUint64(c, "cpu-rt-period") - m["cpu-rt-runtime"] = newCRInt64(c, "cpu-rt-runtime") - m["cpu-shares"] = newCRUint64(c, "cpu-shares") - m["cpus"] = newCRFloat64(c, "cpus") - m["cpuset-cpus"] = newCRString(c, "cpuset-cpus") - m["cpuset-mems"] = newCRString(c, "cpuset-mems") - m["detach"] = newCRBool(c, "detach") - m["detach-keys"] = newCRString(c, "detach-keys") - m["device"] = newCRStringSlice(c, "device") - m["device-cgroup-rule"] = newCRStringSlice(c, "device-cgroup-rule") - m["device-read-bps"] = newCRStringSlice(c, "device-read-bps") - m["device-read-iops"] = newCRStringSlice(c, "device-read-iops") - m["device-write-bps"] = newCRStringSlice(c, "device-write-bps") - m["device-write-iops"] = newCRStringSlice(c, "device-write-iops") - m["dns"] = newCRStringSlice(c, "dns") - m["dns-opt"] = newCRStringSlice(c, "dns-opt") - m["dns-search"] = newCRStringSlice(c, "dns-search") - m["entrypoint"] = newCRString(c, "entrypoint") - m["env"] = newCRStringArray(c, "env") - m["env-file"] = newCRStringSlice(c, "env-file") - m["expose"] = newCRStringSlice(c, "expose") - m["gidmap"] = newCRStringSlice(c, "gidmap") - m["group-add"] = newCRStringSlice(c, "group-add") - m["help"] = newCRBool(c, "help") - m["healthcheck-command"] = newCRString(c, "health-cmd") - m["healthcheck-interval"] = newCRString(c, "health-interval") - m["healthcheck-retries"] = newCRUint(c, "health-retries") - m["healthcheck-start-period"] = newCRString(c, "health-start-period") - m["healthcheck-timeout"] = newCRString(c, "health-timeout") - m["hostname"] = newCRString(c, "hostname") - m["image-volume"] = newCRString(c, "image-volume") - m["init"] = newCRBool(c, "init") - m["init-path"] = newCRString(c, "init-path") - m["interactive"] = newCRBool(c, "interactive") - m["ip"] = newCRString(c, "ip") - m["ipc"] = newCRString(c, "ipc") - m["kernel-memory"] = newCRString(c, "kernel-memory") - m["label"] = newCRStringArray(c, "label") - m["label-file"] = newCRStringSlice(c, "label-file") - m["log-driver"] = newCRString(c, "log-driver") - m["log-opt"] = newCRStringSlice(c, "log-opt") - m["mac-address"] = newCRString(c, "mac-address") - m["memory"] = newCRString(c, "memory") - m["memory-reservation"] = newCRString(c, "memory-reservation") - m["memory-swap"] = newCRString(c, "memory-swap") - m["memory-swappiness"] = newCRInt64(c, "memory-swappiness") - m["name"] = newCRString(c, "name") - m["network"] = newCRString(c, "network") - m["no-healthcheck"] = newCRBool(c, "no-healthcheck") - m["no-hosts"] = newCRBool(c, "no-hosts") - m["oom-kill-disable"] = newCRBool(c, "oom-kill-disable") - m["oom-score-adj"] = newCRInt(c, "oom-score-adj") - m["override-arch"] = newCRString(c, "override-arch") - m["override-os"] = newCRString(c, "override-os") - m["pid"] = newCRString(c, "pid") - m["pids-limit"] = newCRInt64(c, "pids-limit") - m["pod"] = newCRString(c, "pod") - m["privileged"] = newCRBool(c, "privileged") - m["publish"] = newCRStringSlice(c, "publish") - m["publish-all"] = newCRBool(c, "publish-all") - m["pull"] = newCRString(c, "pull") - m["quiet"] = newCRBool(c, "quiet") - m["read-only"] = newCRBool(c, "read-only") - m["read-only-tmpfs"] = newCRBool(c, "read-only-tmpfs") - m["restart"] = newCRString(c, "restart") - m["rm"] = newCRBool(c, "rm") - m["rootfs"] = newCRBool(c, "rootfs") - m["security-opt"] = newCRStringArray(c, "security-opt") - m["shm-size"] = newCRString(c, "shm-size") - m["stop-signal"] = newCRString(c, "stop-signal") - m["stop-timeout"] = newCRUint(c, "stop-timeout") - m["storage-opt"] = newCRStringSlice(c, "storage-opt") - m["subgidname"] = newCRString(c, "subgidname") - m["subuidname"] = newCRString(c, "subuidname") - m["sysctl"] = newCRStringSlice(c, "sysctl") - m["systemd"] = newCRString(c, "systemd") - m["tmpfs"] = newCRStringArray(c, "tmpfs") - m["tty"] = newCRBool(c, "tty") - m["uidmap"] = newCRStringSlice(c, "uidmap") - m["ulimit"] = newCRStringSlice(c, "ulimit") - m["user"] = newCRString(c, "user") - m["userns"] = newCRString(c, "userns") - m["uts"] = newCRString(c, "uts") - m["mount"] = newCRStringArray(c, "mount") - m["volume"] = newCRStringArray(c, "volume") - m["volumes-from"] = newCRStringSlice(c, "volumes-from") - m["workdir"] = newCRString(c, "workdir") - m["seccomp-policy"] = newCRString(c, "seccomp-policy") - // global flag - if !remote { - m["authfile"] = newCRString(c, "authfile") - m["cgroupns"] = newCRString(c, "cgroupns") - m["env-host"] = newCRBool(c, "env-host") - m["http-proxy"] = newCRBool(c, "http-proxy") - m["trace"] = newCRBool(c, "trace") - m["syslog"] = newCRBool(c, "syslog") - } - - return GenericCLIResults{m, c.InputArgs} -} diff --git a/cmd/podman/shared/intermediate_novarlink.go b/cmd/podman/shared/intermediate_novarlink.go deleted file mode 100644 index c6f011fe0..000000000 --- a/cmd/podman/shared/intermediate_novarlink.go +++ /dev/null @@ -1,70 +0,0 @@ -// +build !varlink -// +build !remoteclient - -package shared - -/* -attention - -in this file you will see a lot of struct duplication. this was done because people wanted a strongly typed -varlink mechanism. this resulted in us creating this intermediate layer that allows us to take the input -from the cli and make an intermediate layer which can be transferred as strongly typed structures over a varlink -interface. - -we intentionally avoided heavy use of reflection here because we were concerned about performance impacts to the -non-varlink intermediate layer generation. -*/ - -// ToString wrapper for build without varlink -func (c CRStringSlice) ToVarlink() interface{} { - var v interface{} - return v -} - -// ToString wrapper for build without varlink -func (c CRString) ToVarlink() interface{} { - var v interface{} - return v -} - -// ToString wrapper for build without varlink -func (c CRBool) ToVarlink() interface{} { - var v interface{} - return v -} - -// ToString wrapper for build without varlink -func (c CRUint64) ToVarlink() interface{} { - var v interface{} - return v -} - -// ToString wrapper for build without varlink -func (c CRInt64) ToVarlink() interface{} { - var v interface{} - return v -} - -// ToString wrapper for build without varlink -func (c CRFloat64) ToVarlink() interface{} { - var v interface{} - return v -} - -// ToString wrapper for build without varlink -func (c CRUint) ToVarlink() interface{} { - var v interface{} - return v -} - -// ToString wrapper for build without varlink -func (c CRStringArray) ToVarlink() interface{} { - var v interface{} - return v -} - -// ToString wrapper for build without varlink -func (c CRInt) ToVarlink() interface{} { - var v interface{} - return v -} diff --git a/cmd/podman/shared/intermediate_varlink.go b/cmd/podman/shared/intermediate_varlink.go deleted file mode 100644 index 82594fb40..000000000 --- a/cmd/podman/shared/intermediate_varlink.go +++ /dev/null @@ -1,440 +0,0 @@ -// +build varlink remoteclient - -package shared - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/rootless" - iopodman "github.com/containers/libpod/pkg/varlink" - "github.com/pkg/errors" -) - -// StringSliceToPtr converts a genericcliresult value into a *[]string -func StringSliceToPtr(g GenericCLIResult) *[]string { - if !g.IsSet() { - return nil - } - newT := g.Value().([]string) - return &newT -} - -// StringToPtr converts a genericcliresult value into a *string -func StringToPtr(g GenericCLIResult) *string { - if !g.IsSet() { - return nil - } - newT := g.Value().(string) - return &newT -} - -// BoolToPtr converts a genericcliresult value into a *bool -func BoolToPtr(g GenericCLIResult) *bool { - if !g.IsSet() { - return nil - } - newT := g.Value().(bool) - return &newT -} - -// AnyIntToInt64Ptr converts a genericcliresult value into an *int64 -func AnyIntToInt64Ptr(g GenericCLIResult) *int64 { - if !g.IsSet() { - return nil - } - var newT int64 - switch g.Value().(type) { - case int: - newT = int64(g.Value().(int)) - case int64: - newT = g.Value().(int64) - case uint64: - newT = int64(g.Value().(uint64)) - case uint: - newT = int64(g.Value().(uint)) - default: - panic(errors.Errorf("invalid int type")) - } - return &newT -} - -// Float64ToPtr converts a genericcliresult into a *float64 -func Float64ToPtr(g GenericCLIResult) *float64 { - if !g.IsSet() { - return nil - } - newT := g.Value().(float64) - return &newT -} - -// MakeVarlink creates a varlink transportable struct from GenericCLIResults -func (g GenericCLIResults) MakeVarlink() iopodman.Create { - v := iopodman.Create{ - Args: g.InputArgs, - AddHost: StringSliceToPtr(g.Find("add-host")), - Annotation: StringSliceToPtr(g.Find("annotation")), - Attach: StringSliceToPtr(g.Find("attach")), - BlkioWeight: StringToPtr(g.Find("blkio-weight")), - BlkioWeightDevice: StringSliceToPtr(g.Find("blkio-weight-device")), - CapAdd: StringSliceToPtr(g.Find("cap-add")), - CapDrop: StringSliceToPtr(g.Find("cap-drop")), - CgroupParent: StringToPtr(g.Find("cgroup-parent")), - CidFile: StringToPtr(g.Find("cidfile")), - ConmonPidfile: StringToPtr(g.Find("conmon-pidfile")), - CpuPeriod: AnyIntToInt64Ptr(g.Find("cpu-period")), - CpuQuota: AnyIntToInt64Ptr(g.Find("cpu-quota")), - CpuRtPeriod: AnyIntToInt64Ptr(g.Find("cpu-rt-period")), - CpuRtRuntime: AnyIntToInt64Ptr(g.Find("cpu-rt-runtime")), - CpuShares: AnyIntToInt64Ptr(g.Find("cpu-shares")), - Cpus: Float64ToPtr(g.Find("cpus")), - CpuSetCpus: StringToPtr(g.Find("cpuset-cpus")), - CpuSetMems: StringToPtr(g.Find("cpuset-mems")), - Detach: BoolToPtr(g.Find("detach")), - DetachKeys: StringToPtr(g.Find("detach-keys")), - Device: StringSliceToPtr(g.Find("device")), - DeviceReadBps: StringSliceToPtr(g.Find("device-read-bps")), - DeviceReadIops: StringSliceToPtr(g.Find("device-read-iops")), - DeviceWriteBps: StringSliceToPtr(g.Find("device-write-bps")), - DeviceWriteIops: StringSliceToPtr(g.Find("device-write-iops")), - Dns: StringSliceToPtr(g.Find("dns")), - DnsOpt: StringSliceToPtr(g.Find("dns-opt")), - DnsSearch: StringSliceToPtr(g.Find("dns-search")), - Entrypoint: StringToPtr(g.Find("entrypoint")), - Env: StringSliceToPtr(g.Find("env")), - EnvFile: StringSliceToPtr(g.Find("env-file")), - Expose: StringSliceToPtr(g.Find("expose")), - Gidmap: StringSliceToPtr(g.Find("gidmap")), - Groupadd: StringSliceToPtr(g.Find("group-add")), - HealthcheckCommand: StringToPtr(g.Find("healthcheck-command")), - HealthcheckInterval: StringToPtr(g.Find("healthcheck-interval")), - HealthcheckRetries: AnyIntToInt64Ptr(g.Find("healthcheck-retries")), - HealthcheckStartPeriod: StringToPtr(g.Find("healthcheck-start-period")), - HealthcheckTimeout: StringToPtr(g.Find("healthcheck-timeout")), - Hostname: StringToPtr(g.Find("hostname")), - ImageVolume: StringToPtr(g.Find("image-volume")), - Init: BoolToPtr(g.Find("init")), - InitPath: StringToPtr(g.Find("init-path")), - Interactive: BoolToPtr(g.Find("interactive")), - Ip: StringToPtr(g.Find("ip")), - Ipc: StringToPtr(g.Find("ipc")), - KernelMemory: StringToPtr(g.Find("kernel-memory")), - Label: StringSliceToPtr(g.Find("label")), - LabelFile: StringSliceToPtr(g.Find("label-file")), - LogDriver: StringToPtr(g.Find("log-driver")), - LogOpt: StringSliceToPtr(g.Find("log-opt")), - MacAddress: StringToPtr(g.Find("mac-address")), - Memory: StringToPtr(g.Find("memory")), - MemoryReservation: StringToPtr(g.Find("memory-reservation")), - MemorySwap: StringToPtr(g.Find("memory-swap")), - MemorySwappiness: AnyIntToInt64Ptr(g.Find("memory-swappiness")), - Name: StringToPtr(g.Find("name")), - Network: StringToPtr(g.Find("network")), - OomKillDisable: BoolToPtr(g.Find("oom-kill-disable")), - OomScoreAdj: AnyIntToInt64Ptr(g.Find("oom-score-adj")), - OverrideOS: StringToPtr(g.Find("override-os")), - OverrideArch: StringToPtr(g.Find("override-arch")), - Pid: StringToPtr(g.Find("pid")), - PidsLimit: AnyIntToInt64Ptr(g.Find("pids-limit")), - Pod: StringToPtr(g.Find("pod")), - Privileged: BoolToPtr(g.Find("privileged")), - Publish: StringSliceToPtr(g.Find("publish")), - PublishAll: BoolToPtr(g.Find("publish-all")), - Pull: StringToPtr(g.Find("pull")), - Quiet: BoolToPtr(g.Find("quiet")), - Readonly: BoolToPtr(g.Find("read-only")), - Readonlytmpfs: BoolToPtr(g.Find("read-only-tmpfs")), - Restart: StringToPtr(g.Find("restart")), - Rm: BoolToPtr(g.Find("rm")), - Rootfs: BoolToPtr(g.Find("rootfs")), - SecurityOpt: StringSliceToPtr(g.Find("security-opt")), - ShmSize: StringToPtr(g.Find("shm-size")), - StopSignal: StringToPtr(g.Find("stop-signal")), - StopTimeout: AnyIntToInt64Ptr(g.Find("stop-timeout")), - StorageOpt: StringSliceToPtr(g.Find("storage-opt")), - Subuidname: StringToPtr(g.Find("subuidname")), - Subgidname: StringToPtr(g.Find("subgidname")), - Sysctl: StringSliceToPtr(g.Find("sysctl")), - Systemd: StringToPtr(g.Find("systemd")), - Tmpfs: StringSliceToPtr(g.Find("tmpfs")), - Tty: BoolToPtr(g.Find("tty")), - Uidmap: StringSliceToPtr(g.Find("uidmap")), - Ulimit: StringSliceToPtr(g.Find("ulimit")), - User: StringToPtr(g.Find("user")), - Userns: StringToPtr(g.Find("userns")), - Uts: StringToPtr(g.Find("uts")), - Mount: StringSliceToPtr(g.Find("mount")), - Volume: StringSliceToPtr(g.Find("volume")), - VolumesFrom: StringSliceToPtr(g.Find("volumes-from")), - WorkDir: StringToPtr(g.Find("workdir")), - } - - return v -} - -func stringSliceFromVarlink(v *[]string, flagName string, defaultValue *[]string) CRStringSlice { - cr := CRStringSlice{} - if v == nil { - cr.Val = []string{} - if defaultValue != nil { - cr.Val = *defaultValue - } - cr.Changed = false - } else { - cr.Val = *v - cr.Changed = true - } - cr.Flag = flagName - return cr -} - -func stringFromVarlink(v *string, flagName string, defaultValue *string) CRString { - cr := CRString{} - if v == nil { - cr.Val = "" - if defaultValue != nil { - cr.Val = *defaultValue - } - cr.Changed = false - } else { - cr.Val = *v - cr.Changed = true - } - cr.Flag = flagName - return cr -} - -func boolFromVarlink(v *bool, flagName string, defaultValue bool) CRBool { - cr := CRBool{} - if v == nil { - // In case a cli bool default value is true - cr.Val = defaultValue - cr.Changed = false - } else { - cr.Val = *v - cr.Changed = true - } - cr.Flag = flagName - return cr -} - -func uint64FromVarlink(v *int64, flagName string, defaultValue *uint64) CRUint64 { - cr := CRUint64{} - if v == nil { - cr.Val = 0 - if defaultValue != nil { - cr.Val = *defaultValue - } - cr.Changed = false - } else { - cr.Val = uint64(*v) - cr.Changed = true - } - cr.Flag = flagName - return cr -} - -func int64FromVarlink(v *int64, flagName string, defaultValue *int64) CRInt64 { - cr := CRInt64{} - if v == nil { - cr.Val = 0 - if defaultValue != nil { - cr.Val = *defaultValue - } - cr.Changed = false - } else { - cr.Val = *v - cr.Changed = true - } - cr.Flag = flagName - return cr -} - -func float64FromVarlink(v *float64, flagName string, defaultValue *float64) CRFloat64 { - cr := CRFloat64{} - if v == nil { - cr.Val = 0 - if defaultValue != nil { - cr.Val = *defaultValue - } - cr.Changed = false - } else { - cr.Val = *v - cr.Changed = true - } - cr.Flag = flagName - return cr -} - -func uintFromVarlink(v *int64, flagName string, defaultValue *uint) CRUint { - cr := CRUint{} - if v == nil { - cr.Val = 0 - if defaultValue != nil { - cr.Val = *defaultValue - } - cr.Changed = false - } else { - cr.Val = uint(*v) - cr.Changed = true - } - cr.Flag = flagName - return cr -} - -func stringArrayFromVarlink(v *[]string, flagName string, defaultValue *[]string) CRStringArray { - cr := CRStringArray{} - if v == nil { - cr.Val = []string{} - if defaultValue != nil { - cr.Val = *defaultValue - } - cr.Changed = false - } else { - cr.Val = *v - cr.Changed = true - } - cr.Flag = flagName - return cr -} - -func intFromVarlink(v *int64, flagName string, defaultValue *int) CRInt { - cr := CRInt{} - if v == nil { - if defaultValue != nil { - cr.Val = *defaultValue - } - cr.Val = 0 - cr.Changed = false - } else { - cr.Val = int(*v) - cr.Changed = true - } - cr.Flag = flagName - return cr -} - -// VarlinkCreateToGeneric creates a GenericCLIResults from the varlink create -// structure. -func VarlinkCreateToGeneric(opts iopodman.Create) GenericCLIResults { - - defaultContainerConfig := cliconfig.GetDefaultConfig() - // TODO | WARN - // We do not get a default network over varlink. Unlike the other default values for some cli - // elements, it seems it gets set to the default anyway. - - var memSwapDefault int64 = -1 - netModeDefault := "bridge" - systemdDefault := "true" - if rootless.IsRootless() { - netModeDefault = "slirp4netns" - } - - m := make(map[string]GenericCLIResult) - m["add-host"] = stringSliceFromVarlink(opts.AddHost, "add-host", nil) - m["annotation"] = stringSliceFromVarlink(opts.Annotation, "annotation", nil) - m["attach"] = stringSliceFromVarlink(opts.Attach, "attach", nil) - m["blkio-weight"] = stringFromVarlink(opts.BlkioWeight, "blkio-weight", nil) - m["blkio-weight-device"] = stringSliceFromVarlink(opts.BlkioWeightDevice, "blkio-weight-device", nil) - m["cap-add"] = stringSliceFromVarlink(opts.CapAdd, "cap-add", nil) - m["cap-drop"] = stringSliceFromVarlink(opts.CapDrop, "cap-drop", nil) - m["cgroup-parent"] = stringFromVarlink(opts.CgroupParent, "cgroup-parent", nil) - m["cidfile"] = stringFromVarlink(opts.CidFile, "cidfile", nil) - m["conmon-pidfile"] = stringFromVarlink(opts.ConmonPidfile, "conmon-file", nil) - m["cpu-period"] = uint64FromVarlink(opts.CpuPeriod, "cpu-period", nil) - m["cpu-quota"] = int64FromVarlink(opts.CpuQuota, "quota", nil) - m["cpu-rt-period"] = uint64FromVarlink(opts.CpuRtPeriod, "cpu-rt-period", nil) - m["cpu-rt-runtime"] = int64FromVarlink(opts.CpuRtRuntime, "cpu-rt-quota", nil) - m["cpu-shares"] = uint64FromVarlink(opts.CpuShares, "cpu-shares", nil) - m["cpus"] = float64FromVarlink(opts.Cpus, "cpus", nil) - m["cpuset-cpus"] = stringFromVarlink(opts.CpuSetCpus, "cpuset-cpus", nil) - m["cpuset-mems"] = stringFromVarlink(opts.CpuSetMems, "cpuset-mems", nil) - m["detach"] = boolFromVarlink(opts.Detach, "detach", false) - m["detach-keys"] = stringFromVarlink(opts.DetachKeys, "detach-keys", nil) - m["device"] = stringSliceFromVarlink(opts.Device, "device", nil) - m["device-read-bps"] = stringSliceFromVarlink(opts.DeviceReadBps, "device-read-bps", nil) - m["device-read-iops"] = stringSliceFromVarlink(opts.DeviceReadIops, "device-read-iops", nil) - m["device-write-bps"] = stringSliceFromVarlink(opts.DeviceWriteBps, "write-device-bps", nil) - m["device-write-iops"] = stringSliceFromVarlink(opts.DeviceWriteIops, "write-device-iops", nil) - m["dns"] = stringSliceFromVarlink(opts.Dns, "dns", nil) - m["dns-opt"] = stringSliceFromVarlink(opts.DnsOpt, "dns-opt", nil) - m["dns-search"] = stringSliceFromVarlink(opts.DnsSearch, "dns-search", nil) - m["entrypoint"] = stringFromVarlink(opts.Entrypoint, "entrypoint", nil) - m["env"] = stringArrayFromVarlink(opts.Env, "env", nil) - m["env-file"] = stringSliceFromVarlink(opts.EnvFile, "env-file", nil) - m["expose"] = stringSliceFromVarlink(opts.Expose, "expose", nil) - m["gidmap"] = stringSliceFromVarlink(opts.Gidmap, "gidmap", nil) - m["group-add"] = stringSliceFromVarlink(opts.Groupadd, "group-add", nil) - m["healthcheck-command"] = stringFromVarlink(opts.HealthcheckCommand, "healthcheck-command", nil) - m["healthcheck-interval"] = stringFromVarlink(opts.HealthcheckInterval, "healthcheck-interval", &cliconfig.DefaultHealthCheckInterval) - m["healthcheck-retries"] = uintFromVarlink(opts.HealthcheckRetries, "healthcheck-retries", &cliconfig.DefaultHealthCheckRetries) - m["healthcheck-start-period"] = stringFromVarlink(opts.HealthcheckStartPeriod, "healthcheck-start-period", &cliconfig.DefaultHealthCheckStartPeriod) - m["healthcheck-timeout"] = stringFromVarlink(opts.HealthcheckTimeout, "healthcheck-timeout", &cliconfig.DefaultHealthCheckTimeout) - m["hostname"] = stringFromVarlink(opts.Hostname, "hostname", nil) - m["image-volume"] = stringFromVarlink(opts.ImageVolume, "image-volume", &cliconfig.DefaultImageVolume) - m["init"] = boolFromVarlink(opts.Init, "init", false) - m["init-path"] = stringFromVarlink(opts.InitPath, "init-path", nil) - m["interactive"] = boolFromVarlink(opts.Interactive, "interactive", false) - m["ip"] = stringFromVarlink(opts.Ip, "ip", nil) - m["ipc"] = stringFromVarlink(opts.Ipc, "ipc", nil) - m["kernel-memory"] = stringFromVarlink(opts.KernelMemory, "kernel-memory", nil) - m["label"] = stringArrayFromVarlink(opts.Label, "label", nil) - m["label-file"] = stringSliceFromVarlink(opts.LabelFile, "label-file", nil) - m["log-driver"] = stringFromVarlink(opts.LogDriver, "log-driver", nil) - m["log-opt"] = stringSliceFromVarlink(opts.LogOpt, "log-opt", nil) - m["mac-address"] = stringFromVarlink(opts.MacAddress, "mac-address", nil) - m["memory"] = stringFromVarlink(opts.Memory, "memory", nil) - m["memory-reservation"] = stringFromVarlink(opts.MemoryReservation, "memory-reservation", nil) - m["memory-swap"] = stringFromVarlink(opts.MemorySwap, "memory-swap", nil) - m["memory-swappiness"] = int64FromVarlink(opts.MemorySwappiness, "memory-swappiness", &memSwapDefault) - m["name"] = stringFromVarlink(opts.Name, "name", nil) - m["network"] = stringFromVarlink(opts.Network, "network", &netModeDefault) - m["no-hosts"] = boolFromVarlink(opts.NoHosts, "no-hosts", false) - m["oom-kill-disable"] = boolFromVarlink(opts.OomKillDisable, "oon-kill-disable", false) - m["oom-score-adj"] = intFromVarlink(opts.OomScoreAdj, "oom-score-adj", nil) - m["override-os"] = stringFromVarlink(opts.OverrideOS, "override-os", nil) - m["override-arch"] = stringFromVarlink(opts.OverrideArch, "override-arch", nil) - m["pid"] = stringFromVarlink(opts.Pid, "pid", nil) - m["pids-limit"] = int64FromVarlink(opts.PidsLimit, "pids-limit", nil) - m["pod"] = stringFromVarlink(opts.Pod, "pod", nil) - m["privileged"] = boolFromVarlink(opts.Privileged, "privileged", false) - m["publish"] = stringSliceFromVarlink(opts.Publish, "publish", nil) - m["publish-all"] = boolFromVarlink(opts.PublishAll, "publish-all", false) - m["pull"] = stringFromVarlink(opts.Pull, "missing", nil) - m["quiet"] = boolFromVarlink(opts.Quiet, "quiet", false) - m["read-only"] = boolFromVarlink(opts.Readonly, "read-only", false) - m["read-only-tmpfs"] = boolFromVarlink(opts.Readonlytmpfs, "read-only-tmpfs", true) - m["restart"] = stringFromVarlink(opts.Restart, "restart", nil) - m["rm"] = boolFromVarlink(opts.Rm, "rm", false) - m["rootfs"] = boolFromVarlink(opts.Rootfs, "rootfs", false) - m["security-opt"] = stringArrayFromVarlink(opts.SecurityOpt, "security-opt", nil) - m["shm-size"] = stringFromVarlink(opts.ShmSize, "shm-size", &defaultContainerConfig.Containers.ShmSize) - m["stop-signal"] = stringFromVarlink(opts.StopSignal, "stop-signal", nil) - m["stop-timeout"] = uintFromVarlink(opts.StopTimeout, "stop-timeout", nil) - m["storage-opt"] = stringSliceFromVarlink(opts.StorageOpt, "storage-opt", nil) - m["subgidname"] = stringFromVarlink(opts.Subgidname, "subgidname", nil) - m["subuidname"] = stringFromVarlink(opts.Subuidname, "subuidname", nil) - m["sysctl"] = stringSliceFromVarlink(opts.Sysctl, "sysctl", nil) - m["systemd"] = stringFromVarlink(opts.Systemd, "systemd", &systemdDefault) - m["tmpfs"] = stringSliceFromVarlink(opts.Tmpfs, "tmpfs", nil) - m["tty"] = boolFromVarlink(opts.Tty, "tty", false) - m["uidmap"] = stringSliceFromVarlink(opts.Uidmap, "uidmap", nil) - m["ulimit"] = stringSliceFromVarlink(opts.Ulimit, "ulimit", nil) - m["user"] = stringFromVarlink(opts.User, "user", nil) - m["userns"] = stringFromVarlink(opts.Userns, "userns", nil) - m["uts"] = stringFromVarlink(opts.Uts, "uts", nil) - m["mount"] = stringArrayFromVarlink(opts.Mount, "mount", nil) - m["volume"] = stringArrayFromVarlink(opts.Volume, "volume", nil) - m["volumes-from"] = stringSliceFromVarlink(opts.VolumesFrom, "volumes-from", nil) - m["workdir"] = stringFromVarlink(opts.WorkDir, "workdir", nil) - - gcli := GenericCLIResults{m, opts.Args} - return gcli -} - -// Find returns a flag from a GenericCLIResults by name -func (g GenericCLIResults) Find(name string) GenericCLIResult { - result, ok := g.results[name] - if ok { - return result - } - panic(errors.Errorf("unable to find generic flag for varlink %s", name)) -} diff --git a/cmd/podman/shared/parallel.go b/cmd/podman/shared/parallel.go deleted file mode 100644 index eb1d40073..000000000 --- a/cmd/podman/shared/parallel.go +++ /dev/null @@ -1,112 +0,0 @@ -package shared - -import ( - "runtime" - "sync" -) - -type pFunc func() error - -// ParallelWorkerInput is a struct used to pass in a slice of parallel funcs to be -// performed on a container ID -type ParallelWorkerInput struct { - ContainerID string - ParallelFunc pFunc -} - -type containerError struct { - ContainerID string - Err error -} - -// ParallelWorker is a "threaded" worker that takes jobs from the channel "queue" -func ParallelWorker(wg *sync.WaitGroup, jobs <-chan ParallelWorkerInput, results chan<- containerError) { - for j := range jobs { - err := j.ParallelFunc() - results <- containerError{ContainerID: j.ContainerID, Err: err} - wg.Done() - } -} - -// 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, int) { - var ( - wg sync.WaitGroup - errorCount int - ) - - resultChan := make(chan containerError, len(functions)) - results := make(map[string]error) - paraJobs := make(chan ParallelWorkerInput, len(functions)) - - // If we have more workers than functions, match up the number of workers and functions - if workers > len(functions) { - workers = len(functions) - } - - // Create the workers - for w := 1; w <= workers; w++ { - go ParallelWorker(&wg, paraJobs, resultChan) - } - - // Add jobs to the workers - for _, j := range functions { - j := j - wg.Add(1) - paraJobs <- j - } - - close(paraJobs) - wg.Wait() - - close(resultChan) - for ctrError := range resultChan { - results[ctrError.ContainerID] = ctrError.Err - if ctrError.Err != nil { - errorCount += 1 - } - } - - return results, errorCount -} - -// Parallelize provides the maximum number of parallel workers (int) as calculated by a basic -// heuristic. This can be overridden by the --max-workers primary switch to podman. -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 "unpause": - if numCpus <= 3 { - return numCpus * 3 - } - return numCpus * 4 - } - return 3 -} diff --git a/cmd/podman/shared/pod.go b/cmd/podman/shared/pod.go deleted file mode 100644 index 50bd88e08..000000000 --- a/cmd/podman/shared/pod.go +++ /dev/null @@ -1,279 +0,0 @@ -package shared - -import ( - "strconv" - "strings" - - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/util" - "github.com/cri-o/ocicni/pkg/ocicni" - "github.com/docker/go-connections/nat" - "github.com/pkg/errors" -) - -// TODO GetPodStatus and CreatePodStatusResults should removed once the adapter -// and shared packages are reworked. It has now been duplicated in libpod proper. - -// GetPodStatus determines the status of the pod based on the -// statuses of the containers in the pod. -// Returns a string representation of the pod status -func GetPodStatus(pod *libpod.Pod) (string, error) { - ctrStatuses, err := pod.Status() - if err != nil { - return define.PodStateErrored, err - } - return CreatePodStatusResults(ctrStatuses) -} - -func CreatePodStatusResults(ctrStatuses map[string]define.ContainerStatus) (string, error) { - ctrNum := len(ctrStatuses) - if ctrNum == 0 { - return define.PodStateCreated, nil - } - statuses := map[string]int{ - define.PodStateStopped: 0, - define.PodStateRunning: 0, - define.PodStatePaused: 0, - define.PodStateCreated: 0, - define.PodStateErrored: 0, - } - for _, ctrStatus := range ctrStatuses { - switch ctrStatus { - case define.ContainerStateExited: - fallthrough - case define.ContainerStateStopped: - statuses[define.PodStateStopped]++ - case define.ContainerStateRunning: - statuses[define.PodStateRunning]++ - case define.ContainerStatePaused: - statuses[define.PodStatePaused]++ - case define.ContainerStateCreated, define.ContainerStateConfigured: - statuses[define.PodStateCreated]++ - default: - statuses[define.PodStateErrored]++ - } - } - - switch { - case statuses[define.PodStateRunning] > 0: - return define.PodStateRunning, nil - case statuses[define.PodStatePaused] == ctrNum: - return define.PodStatePaused, nil - case statuses[define.PodStateStopped] == ctrNum: - return define.PodStateExited, nil - case statuses[define.PodStateStopped] > 0: - return define.PodStateStopped, nil - case statuses[define.PodStateErrored] > 0: - return define.PodStateErrored, nil - default: - return define.PodStateCreated, nil - } -} - -// GetNamespaceOptions transforms a slice of kernel namespaces -// into a slice of pod create options. Currently, not all -// kernel namespaces are supported, and they will be returned in an error -func GetNamespaceOptions(ns []string) ([]libpod.PodCreateOption, error) { - var options []libpod.PodCreateOption - var erroredOptions []libpod.PodCreateOption - for _, toShare := range ns { - switch toShare { - case "cgroup": - options = append(options, libpod.WithPodCgroups()) - case "net": - options = append(options, libpod.WithPodNet()) - case "mnt": - return erroredOptions, errors.Errorf("Mount sharing functionality not supported on pod level") - case "pid": - options = append(options, libpod.WithPodPID()) - case "user": - return erroredOptions, errors.Errorf("User sharing functionality not supported on pod level") - case "ipc": - options = append(options, libpod.WithPodIPC()) - case "uts": - options = append(options, libpod.WithPodUTS()) - case "": - case "none": - return erroredOptions, nil - default: - return erroredOptions, errors.Errorf("Invalid kernel namespace to share: %s. Options are: net, pid, ipc, uts or none", toShare) - } - } - return options, 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 -} - -// GetPodsWithFilters uses the cliconfig to categorize if the latest pod is required. -func GetPodsWithFilters(r *libpod.Runtime, filters string) ([]*libpod.Pod, error) { - filterFuncs, err := GenerateFilterFunction(r, strings.Split(filters, ",")) - if err != nil { - return nil, err - } - return FilterAllPodsWithFilterFunc(r, filterFuncs...) -} - -// FilterAllPodsWithFilterFunc retrieves all pods -// Filters can be provided which will determine which pods are included in the -// output. Multiple filters are handled by ANDing their output, so only pods -// matching all filters are returned -func FilterAllPodsWithFilterFunc(r *libpod.Runtime, filters ...libpod.PodFilter) ([]*libpod.Pod, error) { - pods, err := r.Pods(filters...) - if err != nil { - return nil, err - } - return pods, nil -} - -// GenerateFilterFunction basically gets the filters based on the input by the user -// and filter the pod list based on the criteria. -func GenerateFilterFunction(r *libpod.Runtime, filters []string) ([]libpod.PodFilter, error) { - var filterFuncs []libpod.PodFilter - for _, f := range filters { - filterSplit := strings.SplitN(f, "=", 2) - if len(filterSplit) < 2 { - return nil, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) - } - generatedFunc, err := generatePodFilterFuncs(filterSplit[0], filterSplit[1]) - if err != nil { - return nil, errors.Wrapf(err, "invalid filter") - } - filterFuncs = append(filterFuncs, generatedFunc) - } - - return filterFuncs, nil -} -func generatePodFilterFuncs(filter, filterValue string) ( - func(pod *libpod.Pod) bool, error) { - switch filter { - case "ctr-ids": - return func(p *libpod.Pod) bool { - ctrIds, err := p.AllContainersByID() - if err != nil { - return false - } - return util.StringInSlice(filterValue, ctrIds) - }, nil - case "ctr-names": - return func(p *libpod.Pod) bool { - ctrs, err := p.AllContainers() - if err != nil { - return false - } - for _, ctr := range ctrs { - if filterValue == ctr.Name() { - return true - } - } - return false - }, nil - case "ctr-number": - return func(p *libpod.Pod) bool { - ctrIds, err := p.AllContainersByID() - if err != nil { - return false - } - - fVint, err2 := strconv.Atoi(filterValue) - if err2 != nil { - return false - } - return len(ctrIds) == fVint - }, nil - case "ctr-status": - if !util.StringInSlice(filterValue, - []string{"created", "restarting", "running", "paused", - "exited", "unknown"}) { - return nil, errors.Errorf("%s is not a valid status", filterValue) - } - return func(p *libpod.Pod) bool { - ctr_statuses, err := p.Status() - if err != nil { - return false - } - for _, ctr_status := range ctr_statuses { - state := ctr_status.String() - if ctr_status == define.ContainerStateConfigured { - state = "created" - } - if state == filterValue { - return true - } - } - return false - }, nil - case "id": - return func(p *libpod.Pod) bool { - return strings.Contains(p.ID(), filterValue) - }, nil - case "name": - return func(p *libpod.Pod) bool { - return strings.Contains(p.Name(), filterValue) - }, nil - case "status": - if !util.StringInSlice(filterValue, []string{"stopped", "running", "paused", "exited", "dead", "created"}) { - return nil, errors.Errorf("%s is not a valid pod status", filterValue) - } - return func(p *libpod.Pod) bool { - status, err := p.GetPodStatus() - if err != nil { - return false - } - if strings.ToLower(status) == filterValue { - return true - } - return false - }, nil - case "label": - var filterArray = strings.SplitN(filterValue, "=", 2) - var filterKey = filterArray[0] - if len(filterArray) > 1 { - filterValue = filterArray[1] - } else { - filterValue = "" - } - return func(p *libpod.Pod) bool { - for labelKey, labelValue := range p.Labels() { - if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) { - return true - } - } - return false - }, nil - } - return nil, errors.Errorf("%s is an invalid filter", filter) -} - -var DefaultKernelNamespaces = "cgroup,ipc,net,uts" diff --git a/cmd/podman/shared/volumes_shared.go b/cmd/podman/shared/volumes_shared.go deleted file mode 100644 index 74c0ce011..000000000 --- a/cmd/podman/shared/volumes_shared.go +++ /dev/null @@ -1,109 +0,0 @@ -package shared - -import ( - "context" - "strconv" - "strings" - - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// Remove given set of volumes -func SharedRemoveVolumes(ctx context.Context, runtime *libpod.Runtime, vols []string, all, force bool) ([]string, map[string]error, error) { - var ( - toRemove []*libpod.Volume - success []string - failed map[string]error - ) - - failed = make(map[string]error) - - if all { - vols, err := runtime.Volumes() - if err != nil { - return nil, nil, err - } - toRemove = vols - } else { - for _, v := range vols { - vol, err := runtime.LookupVolume(v) - if err != nil { - failed[v] = err - continue - } - toRemove = append(toRemove, vol) - } - } - - // We could parallelize this, but I haven't heard anyone complain about - // performance here yet, so hold off. - for _, vol := range toRemove { - if err := runtime.RemoveVolume(ctx, vol, force); err != nil { - failed[vol.Name()] = err - continue - } - success = append(success, vol.Name()) - } - - return success, failed, nil -} - -// Handle volume options from CLI. -// Parse "o" option to find UID, GID. -func ParseVolumeOptions(opts map[string]string) ([]libpod.VolumeCreateOption, error) { - libpodOptions := []libpod.VolumeCreateOption{} - volumeOptions := make(map[string]string) - - for key, value := range opts { - switch key { - case "o": - // o has special handling to parse out UID, GID. - // These are separate Libpod options. - splitVal := strings.Split(value, ",") - finalVal := []string{} - for _, o := range splitVal { - // Options will be formatted as either "opt" or - // "opt=value" - splitO := strings.SplitN(o, "=", 2) - switch strings.ToLower(splitO[0]) { - case "uid": - if len(splitO) != 2 { - return nil, errors.Wrapf(define.ErrInvalidArg, "uid option must provide a UID") - } - intUID, err := strconv.Atoi(splitO[1]) - if err != nil { - return nil, errors.Wrapf(err, "cannot convert UID %s to integer", splitO[1]) - } - logrus.Debugf("Removing uid= from options and adding WithVolumeUID for UID %d", intUID) - libpodOptions = append(libpodOptions, libpod.WithVolumeUID(intUID)) - case "gid": - if len(splitO) != 2 { - return nil, errors.Wrapf(define.ErrInvalidArg, "gid option must provide a GID") - } - intGID, err := strconv.Atoi(splitO[1]) - if err != nil { - return nil, errors.Wrapf(err, "cannot convert GID %s to integer", splitO[1]) - } - logrus.Debugf("Removing gid= from options and adding WithVolumeGID for GID %d", intGID) - libpodOptions = append(libpodOptions, libpod.WithVolumeGID(intGID)) - default: - finalVal = append(finalVal, o) - } - } - if len(finalVal) > 0 { - volumeOptions[key] = strings.Join(finalVal, ",") - } - default: - volumeOptions[key] = value - } - } - - if len(volumeOptions) > 0 { - libpodOptions = append(libpodOptions, libpod.WithVolumeOptions(volumeOptions)) - } - - return libpodOptions, nil -} diff --git a/cmd/podman/shared/workers.go b/cmd/podman/shared/workers.go deleted file mode 100644 index a9d6bb77e..000000000 --- a/cmd/podman/shared/workers.go +++ /dev/null @@ -1,138 +0,0 @@ -package shared - -import ( - "reflect" - "runtime" - "strings" - "sync" - - "github.com/sirupsen/logrus" -) - -// JobFunc provides the function signature for the pool'ed functions -type JobFunc func() error - -// Job defines the function to run -type Job struct { - ID string - Fn JobFunc -} - -// JobResult defines the results from the function ran -type JobResult struct { - Job Job - Err error -} - -// Pool defines the worker pool and queues -type Pool struct { - id string - wg *sync.WaitGroup - jobs chan Job - results chan JobResult - size int - capacity int -} - -// NewPool creates and initializes a new Pool -func NewPool(id string, size int, capacity int) *Pool { - var wg sync.WaitGroup - - // min for int... - s := size - if s > capacity { - s = capacity - } - - return &Pool{ - id, - &wg, - make(chan Job, capacity), - make(chan JobResult, capacity), - s, - capacity, - } -} - -// Add Job to pool for parallel processing -func (p *Pool) Add(job Job) { - p.wg.Add(1) - p.jobs <- job -} - -// Run the Job's in the pool, gather and return results -func (p *Pool) Run() ([]string, map[string]error, error) { - var ( - ok = []string{} - failures = map[string]error{} - ) - - for w := 0; w < p.size; w++ { - w := w - go p.newWorker(w) - } - close(p.jobs) - p.wg.Wait() - - close(p.results) - for r := range p.results { - if r.Err == nil { - ok = append(ok, r.Job.ID) - } else { - failures[r.Job.ID] = r.Err - } - } - - if logrus.GetLevel() == logrus.DebugLevel { - for i, f := range failures { - logrus.Debugf("Pool[%s, %s: %s]", p.id, i, f.Error()) - } - } - - return ok, failures, nil -} - -// newWorker creates new parallel workers to monitor jobs channel from Pool -func (p *Pool) newWorker(slot int) { - for job := range p.jobs { - err := job.Fn() - p.results <- JobResult{job, err} - if logrus.GetLevel() == logrus.DebugLevel { - n := strings.Split(runtime.FuncForPC(reflect.ValueOf(job.Fn).Pointer()).Name(), ".") - logrus.Debugf("Worker#%d finished job %s/%s (%v)", slot, n[2:], job.ID, err) - } - p.wg.Done() - } -} - -// DefaultPoolSize provides the maximum number of parallel workers (int) as calculated by a basic -// heuristic. This can be overridden by the --max-workers primary switch to podman. -func DefaultPoolSize(name string) int { - numCpus := runtime.NumCPU() - switch name { - case "init": - fallthrough - case "kill": - fallthrough - case "pause": - fallthrough - case "rm": - fallthrough - case "unpause": - if numCpus <= 3 { - return numCpus * 3 - } - return numCpus * 4 - case "ps": - return 8 - case "restart": - return numCpus * 2 - case "stop": - if numCpus <= 2 { - return 4 - } else { - return numCpus * 3 - } - } - return 3 -} diff --git a/cmd/podman/sign.go b/cmd/podman/sign.go deleted file mode 100644 index 7da3459cf..000000000 --- a/cmd/podman/sign.go +++ /dev/null @@ -1,226 +0,0 @@ -package main - -import ( - "io/ioutil" - "net/url" - "os" - "path/filepath" - "strconv" - "strings" - - "github.com/containers/image/v5/signature" - "github.com/containers/image/v5/transports" - "github.com/containers/image/v5/transports/alltransports" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/rootless" - "github.com/containers/libpod/pkg/trust" - "github.com/containers/libpod/pkg/util" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - signCommand cliconfig.SignValues - signDescription = "Create a signature file that can be used later to verify the image." - _signCommand = &cobra.Command{ - Use: "sign [flags] IMAGE [IMAGE...]", - Short: "Sign an image", - Long: signDescription, - RunE: func(cmd *cobra.Command, args []string) error { - signCommand.InputArgs = args - signCommand.GlobalFlags = MainGlobalOpts - signCommand.Remote = remoteclient - return signCmd(&signCommand) - }, - Example: `podman image sign --sign-by mykey imageID - podman image sign --sign-by mykey --directory ./mykeydir imageID`, - } -) - -func init() { - signCommand.Command = _signCommand - signCommand.SetHelpTemplate(HelpTemplate()) - signCommand.SetUsageTemplate(UsageTemplate()) - flags := signCommand.Flags() - flags.StringVarP(&signCommand.Directory, "directory", "d", "", "Define an alternate directory to store signatures") - flags.StringVar(&signCommand.SignBy, "sign-by", "", "Name of the signing key") - flags.StringVar(&signCommand.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") -} - -// SignatureStoreDir defines default directory to store signatures -const SignatureStoreDir = "/var/lib/containers/sigstore" - -func signCmd(c *cliconfig.SignValues) error { - args := c.InputArgs - if len(args) < 1 { - return errors.Errorf("at least one image name must be specified") - } - runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not create runtime") - } - defer runtime.DeferredShutdown(false) - - signby := c.SignBy - if signby == "" { - return errors.Errorf("please provide an identity") - } - - var sigStoreDir string - if c.Flag("directory").Changed { - sigStoreDir = c.Directory - if _, err := os.Stat(sigStoreDir); err != nil { - return errors.Wrapf(err, "invalid directory %s", sigStoreDir) - } - } - - sc := runtime.SystemContext() - sc.DockerCertPath = c.CertDir - - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerCertPath: c.CertDir, - } - - mech, err := signature.NewGPGSigningMechanism() - if err != nil { - return errors.Wrap(err, "error initializing GPG") - } - defer mech.Close() - if err := mech.SupportsSigning(); err != nil { - return errors.Wrap(err, "signing is not supported") - } - - systemRegistriesDirPath := trust.RegistriesDirPath(sc) - registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath) - if err != nil { - return errors.Wrapf(err, "error reading registry configuration") - } - - for _, signimage := range args { - srcRef, err := alltransports.ParseImageName(signimage) - if err != nil { - return errors.Wrapf(err, "error parsing image name") - } - rawSource, err := srcRef.NewImageSource(getContext(), sc) - if err != nil { - return errors.Wrapf(err, "error getting image source") - } - err = rawSource.Close() - if err != nil { - logrus.Errorf("unable to close new image source %q", err) - } - manifest, _, err := rawSource.GetManifest(getContext(), nil) - if err != nil { - return errors.Wrapf(err, "error getting manifest") - } - dockerReference := rawSource.Reference().DockerReference() - if dockerReference == nil { - return errors.Errorf("cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference())) - } - - // create the signstore file - rtc, err := runtime.GetConfig() - if err != nil { - return err - } - newImage, err := runtime.ImageRuntime().New(getContext(), signimage, rtc.Engine.SignaturePolicyPath, "", os.Stderr, &dockerRegistryOptions, image.SigningOptions{SignBy: signby}, nil, util.PullImageMissing) - if err != nil { - return errors.Wrapf(err, "error pulling image %s", signimage) - } - - if rootless.IsRootless() { - if sigStoreDir == "" { - sigStoreDir = filepath.Join(filepath.Dir(runtime.StorageConfig().GraphRoot), "sigstore") - } - } else { - registryInfo := trust.HaveMatchRegistry(rawSource.Reference().DockerReference().String(), registryConfigs) - if registryInfo != nil { - if sigStoreDir == "" { - sigStoreDir = registryInfo.SigStoreStaging - if sigStoreDir == "" { - sigStoreDir = registryInfo.SigStore - } - } - sigStoreDir, err = isValidSigStoreDir(sigStoreDir) - if err != nil { - return errors.Wrapf(err, "invalid signature storage %s", sigStoreDir) - } - } - if sigStoreDir == "" { - sigStoreDir = SignatureStoreDir - } - } - - repos, err := newImage.RepoDigests() - if err != nil { - return errors.Wrapf(err, "error calculating repo digests for %s", signimage) - } - if len(repos) == 0 { - logrus.Errorf("no repodigests associated with the image %s", signimage) - continue - } - - // create signature - newSig, err := signature.SignDockerManifest(manifest, dockerReference.String(), mech, signby) - if err != nil { - return errors.Wrapf(err, "error creating new signature") - } - - trimmedDigest := strings.TrimPrefix(repos[0], strings.Split(repos[0], "/")[0]) - sigStoreDir = filepath.Join(sigStoreDir, strings.Replace(trimmedDigest, ":", "=", 1)) - if err := os.MkdirAll(sigStoreDir, 0751); err != nil { - // The directory is allowed to exist - if !os.IsExist(err) { - logrus.Errorf("error creating directory %s: %s", sigStoreDir, err) - continue - } - } - sigFilename, err := getSigFilename(sigStoreDir) - if err != nil { - logrus.Errorf("error creating sigstore file: %v", err) - continue - } - err = ioutil.WriteFile(filepath.Join(sigStoreDir, sigFilename), newSig, 0644) - if err != nil { - logrus.Errorf("error storing signature for %s", rawSource.Reference().DockerReference().String()) - continue - } - } - return nil -} - -func getSigFilename(sigStoreDirPath string) (string, error) { - sigFileSuffix := 1 - sigFiles, err := ioutil.ReadDir(sigStoreDirPath) - if err != nil { - return "", err - } - sigFilenames := make(map[string]bool) - for _, file := range sigFiles { - sigFilenames[file.Name()] = true - } - for { - sigFilename := "signature-" + strconv.Itoa(sigFileSuffix) - if _, exists := sigFilenames[sigFilename]; !exists { - return sigFilename, nil - } - sigFileSuffix++ - } -} - -func isValidSigStoreDir(sigStoreDir string) (string, error) { - writeURIs := map[string]bool{"file": true} - url, err := url.Parse(sigStoreDir) - if err != nil { - return sigStoreDir, errors.Wrapf(err, "invalid directory %s", sigStoreDir) - } - _, exists := writeURIs[url.Scheme] - if !exists { - return sigStoreDir, errors.Errorf("writing to %s is not supported. Use a supported scheme", sigStoreDir) - } - sigStoreDir = url.Path - return sigStoreDir, nil -} diff --git a/cmd/podman/start.go b/cmd/podman/start.go deleted file mode 100644 index ee700032f..000000000 --- a/cmd/podman/start.go +++ /dev/null @@ -1,78 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/adapter" - "github.com/opentracing/opentracing-go" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - startCommand cliconfig.StartValues - startDescription = `Starts one or more containers. The container name or ID can be used.` - - _startCommand = &cobra.Command{ - Use: "start [flags] CONTAINER [CONTAINER...]", - Short: "Start one or more containers", - Long: startDescription, - RunE: func(cmd *cobra.Command, args []string) error { - startCommand.InputArgs = args - startCommand.GlobalFlags = MainGlobalOpts - startCommand.Remote = remoteclient - return startCmd(&startCommand) - }, - Example: `podman start --latest - podman start 860a4b231279 5421ab43b45 - podman start --interactive --attach imageID`, - } -) - -func init() { - startCommand.Command = _startCommand - startCommand.SetHelpTemplate(HelpTemplate()) - startCommand.SetUsageTemplate(UsageTemplate()) - flags := startCommand.Flags() - flags.BoolVarP(&startCommand.Attach, "attach", "a", false, "Attach container's STDOUT and STDERR") - flags.StringVar(&startCommand.DetachKeys, "detach-keys", getDefaultDetachKeys(), "Select the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`") - flags.BoolVarP(&startCommand.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached") - flags.BoolVarP(&startCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - flags.BoolVar(&startCommand.SigProxy, "sig-proxy", false, "Proxy received signals to the process (default true if attaching, false otherwise)") - markFlagHiddenForRemoteClient("latest", flags) -} - -func startCmd(c *cliconfig.StartValues) error { - if !remoteclient && c.Bool("trace") { - span, _ := opentracing.StartSpanFromContext(Ctx, "startCmd") - defer span.Finish() - } - - args := c.InputArgs - if len(args) < 1 && !c.Latest { - return errors.Errorf("you must provide at least one container name or id") - } - - attach := c.Attach - - if len(args) > 1 && attach { - return errors.Errorf("you cannot start and attach multiple containers at once") - } - - sigProxy := c.SigProxy || attach - if c.Flag("sig-proxy").Changed { - sigProxy = c.SigProxy - } - - if sigProxy && !attach { - return errors.Wrapf(define.ErrInvalidArg, "you cannot use sig-proxy without --attach") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - exitCode, err = runtime.Start(getContext(), c, sigProxy) - return err -} diff --git a/cmd/podman/stats.go b/cmd/podman/stats.go deleted file mode 100644 index 08fddc47a..000000000 --- a/cmd/podman/stats.go +++ /dev/null @@ -1,305 +0,0 @@ -package main - -import ( - "fmt" - "reflect" - "strings" - "time" - - tm "github.com/buger/goterm" - "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/cgroups" - "github.com/containers/libpod/pkg/rootless" - "github.com/docker/go-units" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -type statsOutputParams struct { - ID string `json:"id"` - Name string `json:"name"` - CPUPerc string `json:"cpu_percent"` - MemUsage string `json:"mem_usage"` - MemPerc string `json:"mem_percent"` - NetIO string `json:"netio"` - BlockIO string `json:"blocki"` - PIDS string `json:"pids"` -} - -var ( - statsCommand cliconfig.StatsValues - - statsDescription = "Display percentage of CPU, memory, network I/O, block I/O and PIDs for one or more containers." - _statsCommand = &cobra.Command{ - Use: "stats [flags] [CONTAINER...]", - Short: "Display a live stream of container resource usage statistics", - Long: statsDescription, - RunE: func(cmd *cobra.Command, args []string) error { - statsCommand.InputArgs = args - statsCommand.GlobalFlags = MainGlobalOpts - statsCommand.Remote = remoteclient - return statsCmd(&statsCommand) - }, - Example: `podman stats --all --no-stream - podman stats ctrID - podman stats --no-stream --format "table {{.ID}} {{.Name}} {{.MemUsage}}" ctrID`, - } -) - -func init() { - statsCommand.Command = _statsCommand - statsCommand.SetHelpTemplate(HelpTemplate()) - statsCommand.SetUsageTemplate(UsageTemplate()) - flags := statsCommand.Flags() - flags.BoolVarP(&statsCommand.All, "all", "a", false, "Show all containers. Only running containers are shown by default. The default is false") - flags.StringVar(&statsCommand.Format, "format", "", "Pretty-print container statistics to JSON or using a Go template") - flags.BoolVarP(&statsCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - flags.BoolVar(&statsCommand.NoReset, "no-reset", false, "Disable resetting the screen between intervals") - flags.BoolVar(&statsCommand.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result, default setting is false") - markFlagHiddenForRemoteClient("latest", flags) -} - -func statsCmd(c *cliconfig.StatsValues) error { - if rootless.IsRootless() { - unified, err := cgroups.IsCgroup2UnifiedMode() - if err != nil { - return err - } - if !unified { - return errors.New("stats is not supported in rootless mode without cgroups v2") - } - } - - all := c.All - latest := c.Latest - ctr := 0 - if all { - ctr += 1 - } - if latest { - ctr += 1 - } - if len(c.InputArgs) > 0 { - ctr += 1 - } - - if ctr > 1 { - return errors.Errorf("--all, --latest and containers cannot be used together") - } - - runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - times := -1 - if c.NoStream { - times = 1 - } - - var ctrs []*libpod.Container - - containerFunc := runtime.GetRunningContainers - switch { - case len(c.InputArgs) > 0: - containerFunc = func() ([]*libpod.Container, error) { return runtime.GetContainersByList(c.InputArgs) } - case latest: - containerFunc = func() ([]*libpod.Container, error) { - lastCtr, err := runtime.GetLatestContainer() - if err != nil { - return nil, err - } - return []*libpod.Container{lastCtr}, nil - } - case all: - containerFunc = runtime.GetAllContainers - } - - ctrs, err = containerFunc() - if err != nil { - return errors.Wrapf(err, "unable to get list of containers") - } - - containerStats := map[string]*libpod.ContainerStats{} - for _, ctr := range ctrs { - initialStats, err := ctr.GetContainerStats(&libpod.ContainerStats{}) - if err != nil { - // when doing "all", don't worry about containers that are not running - cause := errors.Cause(err) - if c.All && (cause == define.ErrCtrRemoved || cause == define.ErrNoSuchCtr || cause == define.ErrCtrStateInvalid) { - continue - } - if cause == cgroups.ErrCgroupV1Rootless { - err = cause - } - return err - } - containerStats[ctr.ID()] = initialStats - } - - format := genStatsFormat(c.Format) - - step := 1 - if times == -1 { - times = 1 - step = 0 - } - for i := 0; i < times; i += step { - reportStats := []*libpod.ContainerStats{} - for _, ctr := range ctrs { - id := ctr.ID() - if _, ok := containerStats[ctr.ID()]; !ok { - initialStats, err := ctr.GetContainerStats(&libpod.ContainerStats{}) - if errors.Cause(err) == define.ErrCtrRemoved || errors.Cause(err) == define.ErrNoSuchCtr || errors.Cause(err) == define.ErrCtrStateInvalid { - // skip dealing with a container that is gone - continue - } - if err != nil { - return err - } - containerStats[id] = initialStats - } - stats, err := ctr.GetContainerStats(containerStats[id]) - if err != nil && errors.Cause(err) != define.ErrNoSuchCtr { - return err - } - // replace the previous measurement with the current one - containerStats[id] = stats - reportStats = append(reportStats, stats) - } - ctrs, err = containerFunc() - if err != nil { - return err - } - if strings.ToLower(format) != formats.JSONString && !c.NoReset { - tm.Clear() - tm.MoveCursor(1, 1) - tm.Flush() - } - if err := outputStats(reportStats, format); err != nil { - return err - } - time.Sleep(time.Second) - } - return nil -} - -func outputStats(stats []*libpod.ContainerStats, format string) error { - var out formats.Writer - var outputStats []statsOutputParams - for _, s := range stats { - outputStats = append(outputStats, getStatsOutputParams(s)) - } - if strings.ToLower(format) == formats.JSONString { - out = formats.JSONStructArray{Output: statsToGeneric(outputStats, []statsOutputParams{})} - } else { - var mapOfHeaders map[string]string - if len(outputStats) == 0 { - params := getStatsOutputParamsEmpty() - mapOfHeaders = params.headerMap() - } else { - mapOfHeaders = outputStats[0].headerMap() - } - out = formats.StdoutTemplateArray{Output: statsToGeneric(outputStats, []statsOutputParams{}), Template: format, Fields: mapOfHeaders} - } - return out.Out() -} - -func genStatsFormat(format string) string { - if format != "" { - // "\t" from the command line is not being recognized as a tab - // replacing the string "\t" to a tab character if the user passes in "\t" - return strings.Replace(format, `\t`, "\t", -1) - } - return "table {{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDS}}" -} - -// imagesToGeneric creates an empty array of interfaces for output -func statsToGeneric(templParams []statsOutputParams, jsonParams []statsOutputParams) (genericParams []interface{}) { - if len(templParams) > 0 { - for _, v := range templParams { - genericParams = append(genericParams, interface{}(v)) - } - return - } - for _, v := range jsonParams { - genericParams = append(genericParams, interface{}(v)) - } - return -} - -// generate the header based on the template provided -func (i *statsOutputParams) headerMap() map[string]string { - v := reflect.Indirect(reflect.ValueOf(i)) - values := make(map[string]string) - - for i := 0; i < v.NumField(); i++ { - key := v.Type().Field(i).Name - value := key - switch value { - case "CPUPerc": - value = "CPU%" - case "MemUsage": - value = "MemUsage/Limit" - case "MemPerc": - value = "Mem%" - } - values[key] = strings.ToUpper(splitCamelCase(value)) - } - return values -} - -func combineHumanValues(a, b uint64) string { - if a == 0 && b == 0 { - return "-- / --" - } - return fmt.Sprintf("%s / %s", units.HumanSize(float64(a)), units.HumanSize(float64(b))) -} - -func floatToPercentString(f float64) string { - strippedFloat, err := libpod.RemoveScientificNotationFromFloat(f) - if err != nil || strippedFloat == 0 { - // If things go bazinga, return a safe value - return "--" - } - return fmt.Sprintf("%.2f", strippedFloat) + "%" -} - -func pidsToString(pid uint64) string { - if pid == 0 { - // If things go bazinga, return a safe value - return "--" - } - return fmt.Sprintf("%d", pid) -} - -func getStatsOutputParams(stats *libpod.ContainerStats) statsOutputParams { - return statsOutputParams{ - Name: stats.Name, - ID: shortID(stats.ContainerID), - CPUPerc: floatToPercentString(stats.CPU), - MemUsage: combineHumanValues(stats.MemUsage, stats.MemLimit), - MemPerc: floatToPercentString(stats.MemPerc), - NetIO: combineHumanValues(stats.NetInput, stats.NetOutput), - BlockIO: combineHumanValues(stats.BlockInput, stats.BlockOutput), - PIDS: pidsToString(stats.PIDs), - } -} - -func getStatsOutputParamsEmpty() statsOutputParams { - return statsOutputParams{ - Name: "", - ID: "", - CPUPerc: "", - MemUsage: "", - MemPerc: "", - NetIO: "", - BlockIO: "", - PIDS: "", - } -} diff --git a/cmd/podman/stop.go b/cmd/podman/stop.go deleted file mode 100644 index 5033218e4..000000000 --- a/cmd/podman/stop.go +++ /dev/null @@ -1,71 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/opentracing/opentracing-go" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - stopCommand cliconfig.StopValues - stopDescription = fmt.Sprintf(`Stops one or more running containers. The container name or ID can be used. - - A timeout to forcibly stop the container can also be set but defaults to %d seconds otherwise.`, defaultContainerConfig.Engine.StopTimeout) - _stopCommand = &cobra.Command{ - Use: "stop [flags] CONTAINER [CONTAINER...]", - Short: "Stop one or more containers", - Long: stopDescription, - RunE: func(cmd *cobra.Command, args []string) error { - stopCommand.InputArgs = args - stopCommand.GlobalFlags = MainGlobalOpts - stopCommand.Remote = remoteclient - return stopCmd(&stopCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllLatestAndCIDFile(cmd, args, false, true) - }, - Example: `podman stop ctrID - podman stop --latest - podman stop --time 2 mywebserver 6e534f14da9d`, - } -) - -func init() { - stopCommand.Command = _stopCommand - stopCommand.SetHelpTemplate(HelpTemplate()) - stopCommand.SetUsageTemplate(UsageTemplate()) - flags := stopCommand.Flags() - flags.BoolVarP(&stopCommand.All, "all", "a", false, "Stop all running containers") - flags.BoolVarP(&stopCommand.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing") - flags.StringArrayVarP(&stopCommand.CIDFiles, "cidfile", "", nil, "Read the container ID from the file") - flags.BoolVarP(&stopCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - flags.UintVarP(&stopCommand.Timeout, "time", "t", defaultContainerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container") - markFlagHiddenForRemoteClient("latest", flags) - markFlagHiddenForRemoteClient("cidfile", flags) - markFlagHiddenForRemoteClient("ignore", flags) - flags.SetNormalizeFunc(aliasFlags) -} - -// stopCmd stops a container or containers -func stopCmd(c *cliconfig.StopValues) error { - if c.Bool("trace") { - span, _ := opentracing.StartSpanFromContext(Ctx, "stopCmd") - defer span.Finish() - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - ok, failures, err := runtime.StopContainers(getContext(), c) - if err != nil { - return err - } - return printCmdResults(ok, failures) -} diff --git a/cmd/podman/system.go b/cmd/podman/system.go deleted file mode 100644 index 921d0c037..000000000 --- a/cmd/podman/system.go +++ /dev/null @@ -1,31 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/spf13/cobra" -) - -var ( - systemDescription = "Manage podman" - - systemCommand = cliconfig.PodmanCommand{ - Command: &cobra.Command{ - Use: "system", - Short: "Manage podman", - Long: systemDescription, - RunE: commandRunE(), - }, - } -) - -var systemCommands = []*cobra.Command{ - _systemResetCommand, - _infoCommand, - _pruneSystemCommand, -} - -func init() { - systemCommand.AddCommand(systemCommands...) - systemCommand.AddCommand(getSystemSubCommands()...) - systemCommand.SetUsageTemplate(UsageTemplate()) -} diff --git a/cmd/podman/system/events.go b/cmd/podman/system/events.go new file mode 100644 index 000000000..3c1943b55 --- /dev/null +++ b/cmd/podman/system/events.go @@ -0,0 +1,103 @@ +package system + +import ( + "bufio" + "context" + "html/template" + "os" + + "github.com/containers/buildah/pkg/formats" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/libpod/events" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + eventsDescription = "Monitor podman events" + eventsCommand = &cobra.Command{ + Use: "events", + Args: cobra.NoArgs, + Short: "Show podman events", + Long: eventsDescription, + RunE: eventsCmd, + Example: `podman events + podman events --filter event=create + podman events --since 1h30s`, + } +) + +var ( + eventOptions entities.EventsOptions + eventFormat string +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: eventsCommand, + }) + flags := eventsCommand.Flags() + flags.StringArrayVar(&eventOptions.Filter, "filter", []string{}, "filter output") + flags.StringVar(&eventFormat, "format", "", "format the output using a Go template") + flags.BoolVar(&eventOptions.Stream, "stream", true, "stream new events; for testing only") + flags.StringVar(&eventOptions.Since, "since", "", "show all events created since timestamp") + flags.StringVar(&eventOptions.Until, "until", "", "show all events until timestamp") + _ = flags.MarkHidden("stream") +} + +func eventsCmd(cmd *cobra.Command, args []string) error { + var ( + err error + eventsError error + tmpl *template.Template + ) + if eventFormat != formats.JSONString { + tmpl, err = template.New("events").Parse(eventFormat) + if err != nil { + return err + } + } + if len(eventOptions.Since) > 0 || len(eventOptions.Until) > 0 { + eventOptions.FromStart = true + } + eventChannel := make(chan *events.Event) + eventOptions.EventChan = eventChannel + + go func() { + eventsError = registry.ContainerEngine().Events(context.Background(), eventOptions) + }() + if eventsError != nil { + return eventsError + } + + w := bufio.NewWriter(os.Stdout) + for event := range eventChannel { + switch { + case eventFormat == formats.JSONString: + jsonStr, err := event.ToJSONString() + if err != nil { + return errors.Wrapf(err, "unable to format json") + } + if _, err := w.Write([]byte(jsonStr)); err != nil { + return err + } + case len(eventFormat) > 0: + if err := tmpl.Execute(w, event); err != nil { + return err + } + default: + if _, err := w.Write([]byte(event.ToHumanReadable())); err != nil { + return err + } + } + if _, err := w.Write([]byte("\n")); err != nil { + return err + } + if err := w.Flush(); err != nil { + return err + } + } + return nil +} diff --git a/cmd/podman/system/info.go b/cmd/podman/system/info.go new file mode 100644 index 000000000..aa0a66ffc --- /dev/null +++ b/cmd/podman/system/info.go @@ -0,0 +1,73 @@ +package system + +import ( + "encoding/json" + "fmt" + "os" + "text/template" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" + "gopkg.in/yaml.v2" +) + +var ( + infoDescription = `Display information pertaining to the host, current storage stats, and build of podman. + + Useful for the user and when reporting issues. +` + infoCommand = &cobra.Command{ + Use: "info", + Args: cobra.NoArgs, + Long: infoDescription, + Short: "Display podman system information", + RunE: info, + Example: `podman info`, + } +) + +var ( + inFormat string + debug bool +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: infoCommand, + }) + flags := infoCommand.Flags() + 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") +} + +func info(cmd *cobra.Command, args []string) error { + info, err := registry.ContainerEngine().Info(registry.GetContext()) + if err != nil { + return err + } + + if inFormat == "json" { + b, err := json.MarshalIndent(info, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil + } + if !cmd.Flag("format").Changed { + b, err := yaml.Marshal(info) + if err != nil { + return err + } + fmt.Println(string(b)) + return nil + } + tmpl, err := template.New("info").Parse(inFormat) + if err != nil { + return err + } + err = tmpl.Execute(os.Stdout, info) + return err +} diff --git a/cmd/podman/system/service.go b/cmd/podman/system/service.go new file mode 100644 index 000000000..fa1a33faa --- /dev/null +++ b/cmd/podman/system/service.go @@ -0,0 +1,124 @@ +package system + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/systemd" + "github.com/containers/libpod/pkg/util" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + srvDescription = `Run an API service + +Enable a listening service for API access to Podman commands. +` + + srvCmd = &cobra.Command{ + Use: "service [flags] [URI]", + Args: cobra.MaximumNArgs(1), + Short: "Run API service", + Long: srvDescription, + RunE: service, + Example: `podman system service --time=0 unix:///tmp/podman.sock + podman system service --varlink --time=0 unix:///tmp/podman.sock`, + } + + srvArgs = struct { + Timeout int64 + Varlink bool + }{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: srvCmd, + Parent: systemCmd, + }) + + flags := srvCmd.Flags() + flags.Int64VarP(&srvArgs.Timeout, "time", "t", 5, "Time until the service session expires in seconds. Use 0 to disable the timeout") + flags.Int64Var(&srvArgs.Timeout, "timeout", 5, "Time until the service session expires in seconds. Use 0 to disable the timeout") + flags.BoolVar(&srvArgs.Varlink, "varlink", false, "Use legacy varlink service instead of REST") + + _ = flags.MarkDeprecated("varlink", "valink API is deprecated.") +} + +func service(cmd *cobra.Command, args []string) error { + apiURI, err := resolveApiURI(args) + if err != nil { + return err + } + logrus.Infof("using API endpoint: \"%s\"", apiURI) + + opts := entities.ServiceOptions{ + URI: apiURI, + Timeout: time.Duration(srvArgs.Timeout) * time.Second, + Command: cmd, + } + + if srvArgs.Varlink { + return registry.ContainerEngine().VarlinkService(registry.GetContext(), opts) + } + + logrus.Warn("This function is EXPERIMENTAL") + fmt.Fprintf(os.Stderr, "This function is EXPERIMENTAL.\n") + return registry.ContainerEngine().RestService(registry.GetContext(), opts) +} + +func resolveApiURI(_url []string) (string, error) { + + // When determining _*THE*_ listening endpoint -- + // 1) User input wins always + // 2) systemd socket activation + // 3) rootless honors XDG_RUNTIME_DIR + // 4) if varlink -- adapter.DefaultVarlinkAddress + // 5) lastly adapter.DefaultAPIAddress + + if _url == nil { + if v, found := os.LookupEnv("PODMAN_SOCKET"); found { + _url = []string{v} + } + } + + switch { + case len(_url) > 0: + return _url[0], nil + case systemd.SocketActivated(): + logrus.Info("using systemd socket activation to determine API endpoint") + return "", nil + case rootless.IsRootless(): + xdg, err := util.GetRuntimeDir() + if err != nil { + return "", err + } + + socketName := "podman.sock" + if srvArgs.Varlink { + socketName = "io.podman" + } + socketDir := filepath.Join(xdg, "podman", socketName) + if _, err := os.Stat(filepath.Dir(socketDir)); err != nil { + if os.IsNotExist(err) { + if err := os.Mkdir(filepath.Dir(socketDir), 0755); err != nil { + return "", err + } + } else { + return "", err + } + } + return "unix:" + socketDir, nil + case srvArgs.Varlink: + return registry.DefaultVarlinkAddress, nil + default: + return registry.DefaultAPIAddress, nil + } +} diff --git a/cmd/podman/system/system.go b/cmd/podman/system/system.go new file mode 100644 index 000000000..6d8c9ebc5 --- /dev/null +++ b/cmd/podman/system/system.go @@ -0,0 +1,25 @@ +package system + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + // Command: podman _system_ + systemCmd = &cobra.Command{ + Use: "system", + Short: "Manage podman", + Long: "Manage podman", + TraverseChildren: true, + RunE: registry.SubCommandExists, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: systemCmd, + }) +} diff --git a/cmd/podman/system/varlink.go b/cmd/podman/system/varlink.go new file mode 100644 index 000000000..c83f5ff76 --- /dev/null +++ b/cmd/podman/system/varlink.go @@ -0,0 +1,52 @@ +package system + +import ( + "time" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + varlinkDescription = `Run varlink interface. Podman varlink listens on the specified unix domain socket for incoming connects. + + Tools speaking varlink protocol can remotely manage pods, containers and images. +` + varlinkCmd = &cobra.Command{ + Use: "varlink [flags] [URI]", + Args: cobra.MinimumNArgs(1), + Short: "Run varlink interface", + Long: varlinkDescription, + RunE: varlinkE, + Example: `podman varlink unix:/run/podman/io.podman + podman varlink --timeout 5000 unix:/run/podman/io.podman`, + } + varlinkArgs = struct { + Timeout int64 + }{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: varlinkCmd, + }) + flags := varlinkCmd.Flags() + flags.Int64VarP(&varlinkArgs.Timeout, "time", "t", 1000, "Time until the varlink session expires in milliseconds. Use 0 to disable the timeout") + flags.Int64Var(&varlinkArgs.Timeout, "timeout", 1000, "Time until the varlink session expires in milliseconds. Use 0 to disable the timeout") + +} + +func varlinkE(cmd *cobra.Command, args []string) error { + uri := registry.DefaultVarlinkAddress + if len(args) > 0 { + uri = args[0] + } + opts := entities.ServiceOptions{ + URI: uri, + Timeout: time.Duration(varlinkArgs.Timeout) * time.Second, + Command: cmd, + } + return registry.ContainerEngine().VarlinkService(registry.GetContext(), opts) +} diff --git a/cmd/podman/version.go b/cmd/podman/system/version.go index 5907241ff..5d3874de3 100644 --- a/cmd/podman/version.go +++ b/cmd/podman/system/version.go @@ -1,4 +1,4 @@ -package main +package system import ( "fmt" @@ -9,52 +9,41 @@ import ( "time" "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/adapter" + "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" ) var ( - versionCommand cliconfig.VersionValues - _versionCommand = &cobra.Command{ + versionCommand = &cobra.Command{ Use: "version", - Args: noSubArgs, + Args: cobra.NoArgs, Short: "Display the Podman Version Information", - RunE: func(cmd *cobra.Command, args []string) error { - versionCommand.InputArgs = args - versionCommand.GlobalFlags = MainGlobalOpts - versionCommand.Remote = remoteclient - return versionCmd(&versionCommand) + RunE: version, + Annotations: map[string]string{ + registry.ParentNSRequired: "", }, } + versionFormat string ) -func init() { - versionCommand.Command = _versionCommand - versionCommand.SetUsageTemplate(UsageTemplate()) - flags := versionCommand.Flags() - flags.StringVarP(&versionCommand.Format, "format", "f", "", "Change the output format to JSON or a Go template") -} -func getRemoteVersion(c *cliconfig.VersionValues) (version define.Version, err error) { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return version, errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - return runtime.GetVersion() -} - type versionStruct struct { Client define.Version Server define.Version } -// versionCmd gets and prints version info for version command -func versionCmd(c *cliconfig.VersionValues) error { +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: versionCommand, + }) + flags := versionCommand.Flags() + flags.StringVarP(&versionFormat, "format", "f", "", "Change the output format to JSON or a Go template") +} +func version(cmd *cobra.Command, args []string) error { var ( v versionStruct err error @@ -63,16 +52,19 @@ func versionCmd(c *cliconfig.VersionValues) error { if err != nil { return errors.Wrapf(err, "unable to determine version") } - if remote { - v.Server, err = getRemoteVersion(c) - if err != nil { - return err - } - } else { - v.Server = v.Client - } + // TODO we need to discuss how to implement + // this more. current endpoints dont have a + // version endpoint. maybe we use info? + //if remote { + // v.Server, err = getRemoteVersion(c) + // if err != nil { + // return err + // } + //} else { + v.Server = v.Client + //} - versionOutputFormat := c.Format + versionOutputFormat := versionFormat if versionOutputFormat != "" { if strings.Join(strings.Fields(versionOutputFormat), "") == "{{json.}}" { versionOutputFormat = formats.JSONString @@ -98,7 +90,7 @@ func versionCmd(c *cliconfig.VersionValues) error { w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) defer w.Flush() - if remote { + if registry.IsRemote() { if _, err := fmt.Fprintf(w, "Client:\n"); err != nil { return err } diff --git a/cmd/podman/system_df.go b/cmd/podman/system_df.go deleted file mode 100644 index 44582a802..000000000 --- a/cmd/podman/system_df.go +++ /dev/null @@ -1,650 +0,0 @@ -//+build !remoteclient - -package main - -import ( - "context" - "fmt" - "os" - "path/filepath" - "strings" - "time" - - "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/libpod/image" - "github.com/docker/go-units" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - dfSystemCommand cliconfig.SystemDfValues - dfSystemDescription = ` - podman system df - - Show podman disk usage - ` - _dfSystemCommand = &cobra.Command{ - Use: "df", - Args: noSubArgs, - Short: "Show podman disk usage", - Long: dfSystemDescription, - RunE: func(cmd *cobra.Command, args []string) error { - dfSystemCommand.GlobalFlags = MainGlobalOpts - dfSystemCommand.Remote = remoteclient - return dfSystemCmd(&dfSystemCommand) - }, - } -) - -type dfMetaData struct { - images []*image.Image - containers []*libpod.Container - activeContainers map[string]*libpod.Container - imagesUsedbyCtrMap map[string][]*libpod.Container - imagesUsedbyActiveCtr map[string][]*libpod.Container - volumes []*libpod.Volume - volumeUsedByContainerMap map[string][]*libpod.Container -} - -type systemDfDiskUsage struct { - Type string - Total int - Active int - Size string - Reclaimable string -} - -type imageVerboseDiskUsage struct { - Repository string - Tag string - ImageID string - Created string - Size string - SharedSize string - UniqueSize string - Containers int -} - -type containerVerboseDiskUsage struct { - ContainerID string - Image string - Command string - LocalVolumes int - Size string - Created string - Status string - Names string -} - -type volumeVerboseDiskUsage struct { - VolumeName string - Links int - Size string -} - -const systemDfDefaultFormat string = "table {{.Type}}\t{{.Total}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}" -const imageVerboseFormat string = "table {{.Repository}}\t{{.Tag}}\t{{.ImageID}}\t{{.Created}}\t{{.Size}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}" -const containerVerboseFormat string = "table {{.ContainerID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.Created}}\t{{.Status}}\t{{.Names}}" -const volumeVerboseFormat string = "table {{.VolumeName}}\t{{.Links}}\t{{.Size}}" - -func init() { - dfSystemCommand.Command = _dfSystemCommand - dfSystemCommand.SetUsageTemplate(UsageTemplate()) - flags := dfSystemCommand.Flags() - flags.BoolVarP(&dfSystemCommand.Verbose, "verbose", "v", false, "Show detailed information on space usage") - flags.StringVar(&dfSystemCommand.Format, "format", "", "Pretty-print images using a Go template") -} - -func dfSystemCmd(c *cliconfig.SystemDfValues) error { - runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "Could not get runtime") - } - defer runtime.DeferredShutdown(false) - - ctx := getContext() - - metaData, err := getDfMetaData(ctx, runtime) - if err != nil { - return errors.Wrapf(err, "error getting disk usage data") - } - - if c.Verbose { - err := verboseOutput(ctx, metaData) - if err != nil { - return err - } - return nil - } - - systemDfDiskUsages, err := getDiskUsage(ctx, runtime, metaData) - if err != nil { - return errors.Wrapf(err, "error getting output of system df") - } - format := systemDfDefaultFormat - if c.Format != "" { - format = strings.Replace(c.Format, `\t`, "\t", -1) - } - return generateSysDfOutput(systemDfDiskUsages, format) -} - -func generateSysDfOutput(systemDfDiskUsages []systemDfDiskUsage, format string) error { - var systemDfHeader = map[string]string{ - "Type": "TYPE", - "Total": "TOTAL", - "Active": "ACTIVE", - "Size": "SIZE", - "Reclaimable": "RECLAIMABLE", - } - out := formats.StdoutTemplateArray{Output: systemDfDiskUsageToGeneric(systemDfDiskUsages), Template: format, Fields: systemDfHeader} - return out.Out() -} - -func getDiskUsage(ctx context.Context, runtime *libpod.Runtime, metaData dfMetaData) ([]systemDfDiskUsage, error) { - imageDiskUsage, err := getImageDiskUsage(ctx, metaData.images, metaData.imagesUsedbyCtrMap, metaData.imagesUsedbyActiveCtr) - if err != nil { - return nil, errors.Wrapf(err, "error getting disk usage of images") - } - containerDiskUsage, err := getContainerDiskUsage(metaData.containers, metaData.activeContainers) - if err != nil { - return nil, errors.Wrapf(err, "error getting disk usage of containers") - } - volumeDiskUsage, err := getVolumeDiskUsage(metaData.volumes, metaData.volumeUsedByContainerMap) - if err != nil { - return nil, errors.Wrapf(err, "error getting disk usage of volumess") - } - - systemDfDiskUsages := []systemDfDiskUsage{imageDiskUsage, containerDiskUsage, volumeDiskUsage} - return systemDfDiskUsages, nil -} - -func getDfMetaData(ctx context.Context, runtime *libpod.Runtime) (dfMetaData, error) { - var metaData dfMetaData - images, err := runtime.ImageRuntime().GetImages() - if err != nil { - return metaData, errors.Wrapf(err, "unable to get images") - } - containers, err := runtime.GetAllContainers() - if err != nil { - return metaData, errors.Wrapf(err, "error getting all containers") - } - volumes, err := runtime.GetAllVolumes() - if err != nil { - return metaData, errors.Wrap(err, "error getting all volumes") - } - activeContainers, err := activeContainers(containers) - if err != nil { - return metaData, errors.Wrapf(err, "error getting active containers") - } - imagesUsedbyCtrMap, imagesUsedbyActiveCtr, err := imagesUsedbyCtr(containers, activeContainers) - if err != nil { - return metaData, errors.Wrapf(err, "error getting getting images used by containers") - } - metaData = dfMetaData{ - images: images, - containers: containers, - activeContainers: activeContainers, - imagesUsedbyCtrMap: imagesUsedbyCtrMap, - imagesUsedbyActiveCtr: imagesUsedbyActiveCtr, - volumes: volumes, - volumeUsedByContainerMap: volumeUsedByContainer(containers), - } - return metaData, nil -} - -func imageUniqueSize(ctx context.Context, images []*image.Image) (map[string]uint64, error) { - imgUniqueSizeMap := make(map[string]uint64) - for _, img := range images { - parentImg := img - for { - next, err := parentImg.GetParent(ctx) - if err != nil { - return nil, errors.Wrapf(err, "error getting parent of image %s", parentImg.ID()) - } - if next == nil { - break - } - parentImg = next - } - imgSize, err := img.Size(ctx) - if err != nil { - return nil, err - } - if img.ID() == parentImg.ID() { - imgUniqueSizeMap[img.ID()] = *imgSize - } else { - parentImgSize, err := parentImg.Size(ctx) - if err != nil { - return nil, errors.Wrapf(err, "error getting size of parent image %s", parentImg.ID()) - } - imgUniqueSizeMap[img.ID()] = *imgSize - *parentImgSize - } - } - return imgUniqueSizeMap, nil -} - -func getImageDiskUsage(ctx context.Context, images []*image.Image, imageUsedbyCintainerMap map[string][]*libpod.Container, imageUsedbyActiveContainerMap map[string][]*libpod.Container) (systemDfDiskUsage, error) { - var ( - numberOfImages int - sumSize uint64 - numberOfActiveImages int - unreclaimableSize uint64 - imageDiskUsage systemDfDiskUsage - reclaimableStr string - ) - - imgUniqueSizeMap, err := imageUniqueSize(ctx, images) - if err != nil { - return imageDiskUsage, errors.Wrapf(err, "error getting unique size of images") - } - - for _, img := range images { - - unreclaimableSize += imageUsedSize(img, imgUniqueSizeMap, imageUsedbyCintainerMap, imageUsedbyActiveContainerMap) - - isParent, err := img.IsParent(ctx) - if err != nil { - return imageDiskUsage, err - } - parent, err := img.GetParent(ctx) - if err != nil { - return imageDiskUsage, errors.Wrapf(err, "error getting parent of image %s", img.ID()) - } - if isParent && parent != nil { - continue - } - numberOfImages++ - if _, isActive := imageUsedbyCintainerMap[img.ID()]; isActive { - numberOfActiveImages++ - } - - if !isParent { - size, err := img.Size(ctx) - if err != nil { - return imageDiskUsage, errors.Wrapf(err, "error getting disk usage of image %s", img.ID()) - } - sumSize += *size - } - - } - sumSizeStr := units.HumanSizeWithPrecision(float64(sumSize), 3) - reclaimable := sumSize - unreclaimableSize - if sumSize != 0 { - reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 100*reclaimable/sumSize) - } else { - reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 0) - } - imageDiskUsage = systemDfDiskUsage{ - Type: "Images", - Total: numberOfImages, - Active: numberOfActiveImages, - Size: sumSizeStr, - Reclaimable: reclaimableStr, - } - return imageDiskUsage, nil -} - -func imageUsedSize(img *image.Image, imgUniqueSizeMap map[string]uint64, imageUsedbyCintainerMap map[string][]*libpod.Container, imageUsedbyActiveContainerMap map[string][]*libpod.Container) uint64 { - var usedSize uint64 - imgUnique := imgUniqueSizeMap[img.ID()] - if _, isCtrActive := imageUsedbyActiveContainerMap[img.ID()]; isCtrActive { - return imgUnique - } - containers := imageUsedbyCintainerMap[img.ID()] - for _, ctr := range containers { - if len(ctr.UserVolumes()) > 0 { - usedSize += imgUnique - return usedSize - } - } - return usedSize -} - -func imagesUsedbyCtr(containers []*libpod.Container, activeContainers map[string]*libpod.Container) (map[string][]*libpod.Container, map[string][]*libpod.Container, error) { - imgCtrMap := make(map[string][]*libpod.Container) - imgActiveCtrMap := make(map[string][]*libpod.Container) - for _, ctr := range containers { - imgID, _ := ctr.Image() - imgCtrMap[imgID] = append(imgCtrMap[imgID], ctr) - if _, isActive := activeContainers[ctr.ID()]; isActive { - imgActiveCtrMap[imgID] = append(imgActiveCtrMap[imgID], ctr) - } - } - return imgCtrMap, imgActiveCtrMap, nil -} - -func getContainerDiskUsage(containers []*libpod.Container, activeContainers map[string]*libpod.Container) (systemDfDiskUsage, error) { - var ( - sumSize int64 - unreclaimableSize int64 - reclaimableStr string - ) - for _, ctr := range containers { - size, err := ctr.RWSize() - if err != nil { - return systemDfDiskUsage{}, errors.Wrapf(err, "error getting size of container %s", ctr.ID()) - } - sumSize += size - } - for _, activeCtr := range activeContainers { - size, err := activeCtr.RWSize() - if err != nil { - return systemDfDiskUsage{}, errors.Wrapf(err, "error getting size of active container %s", activeCtr.ID()) - } - unreclaimableSize += size - } - if sumSize == 0 { - reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(0, 3), 0) - } else { - reclaimable := sumSize - unreclaimableSize - reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 100*reclaimable/sumSize) - } - containerDiskUsage := systemDfDiskUsage{ - Type: "Containers", - Total: len(containers), - Active: len(activeContainers), - Size: units.HumanSizeWithPrecision(float64(sumSize), 3), - Reclaimable: reclaimableStr, - } - return containerDiskUsage, nil -} - -func ctrIsActive(ctr *libpod.Container) (bool, error) { - state, err := ctr.State() - if err != nil { - return false, err - } - return state == define.ContainerStatePaused || state == define.ContainerStateRunning, nil -} - -func activeContainers(containers []*libpod.Container) (map[string]*libpod.Container, error) { - activeContainers := make(map[string]*libpod.Container) - for _, aCtr := range containers { - isActive, err := ctrIsActive(aCtr) - if err != nil { - return nil, err - } - if isActive { - activeContainers[aCtr.ID()] = aCtr - } - } - return activeContainers, nil -} - -func getVolumeDiskUsage(volumes []*libpod.Volume, volumeUsedByContainerMap map[string][]*libpod.Container) (systemDfDiskUsage, error) { - var ( - sumSize int64 - unreclaimableSize int64 - reclaimableStr string - ) - for _, volume := range volumes { - size, err := volumeSize(volume) - if err != nil { - return systemDfDiskUsage{}, errors.Wrapf(err, "error getting size of volime %s", volume.Name()) - } - sumSize += size - if _, exist := volumeUsedByContainerMap[volume.Name()]; exist { - unreclaimableSize += size - } - } - reclaimable := sumSize - unreclaimableSize - if sumSize != 0 { - reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 100*reclaimable/sumSize) - } else { - reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 0) - } - volumesDiskUsage := systemDfDiskUsage{ - Type: "Local Volumes", - Total: len(volumes), - Active: len(volumeUsedByContainerMap), - Size: units.HumanSizeWithPrecision(float64(sumSize), 3), - Reclaimable: reclaimableStr, - } - return volumesDiskUsage, nil -} - -func volumeUsedByContainer(containers []*libpod.Container) map[string][]*libpod.Container { - volumeUsedByContainerMap := make(map[string][]*libpod.Container) - for _, ctr := range containers { - - ctrVolumes := ctr.UserVolumes() - for _, ctrVolume := range ctrVolumes { - volumeUsedByContainerMap[ctrVolume] = append(volumeUsedByContainerMap[ctrVolume], ctr) - } - } - return volumeUsedByContainerMap -} - -func volumeSize(volume *libpod.Volume) (int64, error) { - var size int64 - err := filepath.Walk(volume.MountPoint(), func(path string, info os.FileInfo, err error) error { - if err == nil && !info.IsDir() { - size += info.Size() - } - return err - }) - return size, err -} - -func getImageVerboseDiskUsage(ctx context.Context, images []*image.Image, imagesUsedbyCtr map[string][]*libpod.Container) ([]imageVerboseDiskUsage, error) { - var imagesVerboseDiskUsage []imageVerboseDiskUsage - imgUniqueSizeMap, err := imageUniqueSize(ctx, images) - if err != nil { - return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting unique size of images") - } - for _, img := range images { - isParent, err := img.IsParent(ctx) - if err != nil { - return imagesVerboseDiskUsage, errors.Wrapf(err, "error checking if %s is a parent images", img.ID()) - } - parent, err := img.GetParent(ctx) - if err != nil { - return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting parent of image %s", img.ID()) - } - if isParent && parent != nil { - continue - } - size, err := img.Size(ctx) - if err != nil { - return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting size of image %s", img.ID()) - } - numberOfContainers := 0 - if ctrs, exist := imagesUsedbyCtr[img.ID()]; exist { - numberOfContainers = len(ctrs) - } - var repo string - var tag string - var repotags []string - if len(img.Names()) != 0 { - repotags = []string{img.Names()[0]} - } - repopairs, err := image.ReposToMap(repotags) - if err != nil { - logrus.Errorf("error finding tag/digest for %s", img.ID()) - } - for reponame, tags := range repopairs { - for _, tagname := range tags { - repo = reponame - tag = tagname - } - } - - imageVerbosedf := imageVerboseDiskUsage{ - Repository: repo, - Tag: tag, - ImageID: shortID(img.ID()), - Created: fmt.Sprintf("%s ago", units.HumanDuration(time.Since((img.Created().Local())))), - Size: units.HumanSizeWithPrecision(float64(*size), 3), - SharedSize: units.HumanSizeWithPrecision(float64(*size-imgUniqueSizeMap[img.ID()]), 3), - UniqueSize: units.HumanSizeWithPrecision(float64(imgUniqueSizeMap[img.ID()]), 3), - Containers: numberOfContainers, - } - imagesVerboseDiskUsage = append(imagesVerboseDiskUsage, imageVerbosedf) - } - return imagesVerboseDiskUsage, nil -} - -func getContainerVerboseDiskUsage(containers []*libpod.Container) (containersVerboseDiskUsage []containerVerboseDiskUsage, err error) { - for _, ctr := range containers { - imgID, _ := ctr.Image() - size, err := ctr.RWSize() - if err != nil { - return containersVerboseDiskUsage, errors.Wrapf(err, "error getting size of container %s", ctr.ID()) - } - state, err := ctr.State() - if err != nil { - return containersVerboseDiskUsage, errors.Wrapf(err, "error getting the state of container %s", ctr.ID()) - } - - ctrVerboseData := containerVerboseDiskUsage{ - ContainerID: shortID(ctr.ID()), - Image: shortImageID(imgID), - Command: strings.Join(ctr.Command(), " "), - LocalVolumes: len(ctr.UserVolumes()), - Size: units.HumanSizeWithPrecision(float64(size), 3), - Created: fmt.Sprintf("%s ago", units.HumanDuration(time.Since(ctr.CreatedTime().Local()))), - Status: state.String(), - Names: ctr.Name(), - } - containersVerboseDiskUsage = append(containersVerboseDiskUsage, ctrVerboseData) - - } - return containersVerboseDiskUsage, nil -} - -func getVolumeVerboseDiskUsage(volumes []*libpod.Volume, volumeUsedByContainerMap map[string][]*libpod.Container) (volumesVerboseDiskUsage []volumeVerboseDiskUsage, err error) { - for _, vol := range volumes { - volSize, err := volumeSize(vol) - if err != nil { - return volumesVerboseDiskUsage, errors.Wrapf(err, "error getting size of volume %s", vol.Name()) - } - links := 0 - if linkCtr, exist := volumeUsedByContainerMap[vol.Name()]; exist { - links = len(linkCtr) - } - volumeVerboseData := volumeVerboseDiskUsage{ - VolumeName: vol.Name(), - Links: links, - Size: units.HumanSizeWithPrecision(float64(volSize), 3), - } - volumesVerboseDiskUsage = append(volumesVerboseDiskUsage, volumeVerboseData) - } - return volumesVerboseDiskUsage, nil -} - -func imagesVerboseOutput(ctx context.Context, metaData dfMetaData) error { - var imageVerboseHeader = map[string]string{ - "Repository": "REPOSITORY", - "Tag": "TAG", - "ImageID": "IMAGE ID", - "Created": "CREATED", - "Size": "SIZE", - "SharedSize": "SHARED SIZE", - "UniqueSize": "UNIQUE SIZE", - "Containers": "CONTAINERS", - } - imagesVerboseDiskUsage, err := getImageVerboseDiskUsage(ctx, metaData.images, metaData.imagesUsedbyCtrMap) - if err != nil { - return errors.Wrapf(err, "error getting verbose output of images") - } - if _, err := os.Stderr.WriteString("Images space usage:\n\n"); err != nil { - return err - } - out := formats.StdoutTemplateArray{Output: systemDfImageVerboseDiskUsageToGeneric(imagesVerboseDiskUsage), Template: imageVerboseFormat, Fields: imageVerboseHeader} - return out.Out() -} - -func containersVerboseOutput(ctx context.Context, metaData dfMetaData) error { - var containerVerboseHeader = map[string]string{ - "ContainerID": "CONTAINER ID ", - "Image": "IMAGE", - "Command": "COMMAND", - "LocalVolumes": "LOCAL VOLUMES", - "Size": "SIZE", - "Created": "CREATED", - "Status": "STATUS", - "Names": "NAMES", - } - containersVerboseDiskUsage, err := getContainerVerboseDiskUsage(metaData.containers) - if err != nil { - return errors.Wrapf(err, "error getting verbose output of containers") - } - if _, err := os.Stderr.WriteString("\nContainers space usage:\n\n"); err != nil { - return err - } - out := formats.StdoutTemplateArray{Output: systemDfContainerVerboseDiskUsageToGeneric(containersVerboseDiskUsage), Template: containerVerboseFormat, Fields: containerVerboseHeader} - return out.Out() - -} - -func volumesVerboseOutput(ctx context.Context, metaData dfMetaData) error { - var volumeVerboseHeader = map[string]string{ - "VolumeName": "VOLUME NAME", - "Links": "LINKS", - "Size": "SIZE", - } - volumesVerboseDiskUsage, err := getVolumeVerboseDiskUsage(metaData.volumes, metaData.volumeUsedByContainerMap) - if err != nil { - return errors.Wrapf(err, "error getting verbose output of volumes") - } - if _, err := os.Stderr.WriteString("\nLocal Volumes space usage:\n\n"); err != nil { - return err - } - out := formats.StdoutTemplateArray{Output: systemDfVolumeVerboseDiskUsageToGeneric(volumesVerboseDiskUsage), Template: volumeVerboseFormat, Fields: volumeVerboseHeader} - return out.Out() -} - -func verboseOutput(ctx context.Context, metaData dfMetaData) error { - if err := imagesVerboseOutput(ctx, metaData); err != nil { - return err - } - if err := containersVerboseOutput(ctx, metaData); err != nil { - return err - } - if err := volumesVerboseOutput(ctx, metaData); err != nil { - return err - } - return nil -} - -func systemDfDiskUsageToGeneric(diskUsages []systemDfDiskUsage) (out []interface{}) { - for _, usage := range diskUsages { - out = append(out, interface{}(usage)) - } - return out -} - -func systemDfImageVerboseDiskUsageToGeneric(diskUsages []imageVerboseDiskUsage) (out []interface{}) { - for _, usage := range diskUsages { - out = append(out, interface{}(usage)) - } - return out -} - -func systemDfContainerVerboseDiskUsageToGeneric(diskUsages []containerVerboseDiskUsage) (out []interface{}) { - for _, usage := range diskUsages { - out = append(out, interface{}(usage)) - } - return out -} - -func systemDfVolumeVerboseDiskUsageToGeneric(diskUsages []volumeVerboseDiskUsage) (out []interface{}) { - for _, usage := range diskUsages { - out = append(out, interface{}(usage)) - } - return out -} - -func shortImageID(id string) string { - const imageIDTruncLength int = 4 - if len(id) > imageIDTruncLength { - return id[:imageIDTruncLength] - } - return id -} diff --git a/cmd/podman/system_migrate.go b/cmd/podman/system_migrate.go deleted file mode 100644 index 9c90aeb52..000000000 --- a/cmd/podman/system_migrate.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - migrateCommand cliconfig.SystemMigrateValues - migrateDescription = ` - podman system migrate - - Migrate existing containers to a new version of Podman. -` - - _migrateCommand = &cobra.Command{ - Use: "migrate", - Args: noSubArgs, - Short: "Migrate containers", - Long: migrateDescription, - RunE: func(cmd *cobra.Command, args []string) error { - migrateCommand.InputArgs = args - migrateCommand.GlobalFlags = MainGlobalOpts - return migrateCmd(&migrateCommand) - }, - } -) - -func init() { - migrateCommand.Command = _migrateCommand - migrateCommand.SetHelpTemplate(HelpTemplate()) - migrateCommand.SetUsageTemplate(UsageTemplate()) - flags := migrateCommand.Flags() - flags.StringVar(&migrateCommand.NewRuntime, "new-runtime", "", "Specify a new runtime for all containers") -} - -func migrateCmd(c *cliconfig.SystemMigrateValues) error { - // We need to pass one extra option to NewRuntime. - // This will inform the OCI runtime to start a migrate. - // That's controlled by the last argument to GetRuntime. - r, err := libpodruntime.GetRuntimeMigrate(getContext(), &c.PodmanCommand, c.NewRuntime) - if err != nil { - return errors.Wrapf(err, "error migrating containers") - } - if err := r.Shutdown(false); err != nil { - return err - } - - return nil -} diff --git a/cmd/podman/system_prune.go b/cmd/podman/system_prune.go deleted file mode 100644 index 21b7aa711..000000000 --- a/cmd/podman/system_prune.go +++ /dev/null @@ -1,134 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "os" - "strings" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - pruneSystemCommand cliconfig.SystemPruneValues - pruneSystemDescription = ` - podman system prune - - Remove unused data -` - _pruneSystemCommand = &cobra.Command{ - Use: "prune", - Args: noSubArgs, - Short: "Remove unused data", - Long: pruneSystemDescription, - RunE: func(cmd *cobra.Command, args []string) error { - pruneSystemCommand.InputArgs = args - pruneSystemCommand.GlobalFlags = MainGlobalOpts - pruneSystemCommand.Remote = remoteclient - return pruneSystemCmd(&pruneSystemCommand) - }, - } -) - -func init() { - pruneSystemCommand.Command = _pruneSystemCommand - pruneSystemCommand.SetHelpTemplate(HelpTemplate()) - pruneSystemCommand.SetUsageTemplate(UsageTemplate()) - flags := pruneSystemCommand.Flags() - flags.BoolVarP(&pruneSystemCommand.All, "all", "a", false, "Remove all unused data") - flags.BoolVarP(&pruneSystemCommand.Force, "force", "f", false, "Do not prompt for confirmation") - flags.BoolVar(&pruneSystemCommand.Volume, "volumes", false, "Prune volumes") - -} - -func pruneSystemCmd(c *cliconfig.SystemPruneValues) error { - - // Prompt for confirmation if --force is not set - if !c.Force { - reader := bufio.NewReader(os.Stdin) - volumeString := "" - if c.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 - } - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - // We must clean out pods first because if they may have infra containers - fmt.Println("Deleted Pods") - pruneValues := cliconfig.PodPruneValues{ - PodmanCommand: c.PodmanCommand, - } - ctx := getContext() - ok, failures, lasterr := runtime.PrunePods(ctx, &pruneValues) - - if err := printCmdResults(ok, failures); err != nil { - return err - } - - rmWorkers := shared.Parallelize("rm") - fmt.Println("Deleted Containers") - ok, failures, err = runtime.Prune(ctx, rmWorkers, []string{}) - if err != nil { - if lasterr != nil { - logrus.Errorf("%q", err) - } - lasterr = err - } - if err := printCmdResults(ok, failures); err != nil { - return err - } - - if c.Bool("volumes") { - fmt.Println("Deleted Volumes") - err := volumePrune(runtime, getContext()) - if err != nil { - if lasterr != nil { - logrus.Errorf("%q", lasterr) - } - lasterr = err - } - } - - // Call prune; if any cids are returned, print them and then - // return err in case an error also came up - // TODO: support for filters in system prune - pruneCids, err := runtime.PruneImages(ctx, c.All, []string{}) - if len(pruneCids) > 0 { - fmt.Println("Deleted Images") - for _, cid := range pruneCids { - fmt.Println(cid) - } - } - if err != nil { - if lasterr != nil { - logrus.Errorf("%q", lasterr) - } - lasterr = err - } - return lasterr -} diff --git a/cmd/podman/system_renumber.go b/cmd/podman/system_renumber.go deleted file mode 100644 index 4e90a2d8c..000000000 --- a/cmd/podman/system_renumber.go +++ /dev/null @@ -1,50 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - renumberCommand cliconfig.SystemRenumberValues - renumberDescription = ` - podman system renumber - - Migrate lock numbers to handle a change in maximum number of locks. - Mandatory after the number of locks in libpod.conf is changed. -` - - _renumberCommand = &cobra.Command{ - Use: "renumber", - Args: noSubArgs, - Short: "Migrate lock numbers", - Long: renumberDescription, - RunE: func(cmd *cobra.Command, args []string) error { - renumberCommand.InputArgs = args - renumberCommand.GlobalFlags = MainGlobalOpts - renumberCommand.Remote = remoteclient - return renumberCmd(&renumberCommand) - }, - } -) - -func init() { - renumberCommand.Command = _renumberCommand - renumberCommand.SetHelpTemplate(HelpTemplate()) - renumberCommand.SetUsageTemplate(UsageTemplate()) -} - -func renumberCmd(c *cliconfig.SystemRenumberValues) error { - // We need to pass one extra option to NewRuntime. - // This will inform the OCI runtime to start a renumber. - // That's controlled by the last argument to GetRuntime. - r, err := libpodruntime.GetRuntimeRenumber(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error renumbering locks") - } - _ = r.Shutdown(false) - - return nil -} diff --git a/cmd/podman/tag.go b/cmd/podman/tag.go deleted file mode 100644 index 215b716b8..000000000 --- a/cmd/podman/tag.go +++ /dev/null @@ -1,58 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - tagCommand cliconfig.TagValues - - tagDescription = "Adds one or more additional names to locally-stored image." - _tagCommand = &cobra.Command{ - Use: "tag [flags] IMAGE TARGET_NAME [TARGET_NAME...]", - Short: "Add an additional name to a local image", - Long: tagDescription, - RunE: func(cmd *cobra.Command, args []string) error { - tagCommand.InputArgs = args - tagCommand.GlobalFlags = MainGlobalOpts - tagCommand.Remote = remoteclient - return tagCmd(&tagCommand) - }, - Example: `podman tag 0e3bbc2 fedora:latest - podman tag imageID:latest myNewImage:newTag - podman tag httpd myregistryhost:5000/fedora/httpd:v2`, - } -) - -func init() { - tagCommand.Command = _tagCommand - tagCommand.SetHelpTemplate(HelpTemplate()) - tagCommand.SetUsageTemplate(UsageTemplate()) -} - -func tagCmd(c *cliconfig.TagValues) error { - args := c.InputArgs - if len(args) < 2 { - return errors.Errorf("image name and at least one new name must be specified") - } - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not create runtime") - } - defer runtime.DeferredShutdown(false) - - newImage, err := runtime.NewImageFromLocal(args[0]) - if err != nil { - return err - } - - for _, tagName := range args[1:] { - if err := newImage.TagImage(tagName); err != nil { - return errors.Wrapf(err, "error adding %q to image %q", tagName, newImage.InputName) - } - } - return nil -} diff --git a/cmd/podman/top.go b/cmd/podman/top.go deleted file mode 100644 index bfba90fc0..000000000 --- a/cmd/podman/top.go +++ /dev/null @@ -1,99 +0,0 @@ -package main - -import ( - "fmt" - "os" - "strings" - "text/tabwriter" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/util" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -func getDescriptorString() string { - descriptors, err := util.GetContainerPidInformationDescriptors() - if err == nil { - return fmt.Sprintf(` - Format Descriptors: - %s`, strings.Join(descriptors, ",")) - } - return "" -} - -var ( - topCommand cliconfig.TopValues - topDescription = fmt.Sprintf(`Similar to system "top" command. - - Specify format descriptors to alter the output. - - Running "podman top -l pid pcpu seccomp" will print the process ID, the CPU percentage and the seccomp mode of each process of the latest container. -%s`, getDescriptorString()) - - _topCommand = &cobra.Command{ - Use: "top [flags] CONTAINER [FORMAT-DESCRIPTORS|ARGS]", - Short: "Display the running processes of a container", - Long: topDescription, - RunE: func(cmd *cobra.Command, args []string) error { - topCommand.InputArgs = args - topCommand.GlobalFlags = MainGlobalOpts - topCommand.Remote = remoteclient - return topCmd(&topCommand) - }, - Args: cobra.ArbitraryArgs, - Example: `podman top ctrID -podman top --latest -podman top ctrID pid seccomp args %C -podman top ctrID -eo user,pid,comm`, - } -) - -func init() { - topCommand.Command = _topCommand - topCommand.SetHelpTemplate(HelpTemplate()) - topCommand.SetUsageTemplate(UsageTemplate()) - flags := topCommand.Flags() - flags.SetInterspersed(false) - flags.BoolVar(&topCommand.ListDescriptors, "list-descriptors", false, "") - markFlagHidden(flags, "list-descriptors") - flags.BoolVarP(&topCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - markFlagHiddenForRemoteClient("latest", flags) -} - -func topCmd(c *cliconfig.TopValues) error { - var err error - args := c.InputArgs - - if c.ListDescriptors { - descriptors, err := util.GetContainerPidInformationDescriptors() - if err != nil { - return err - } - fmt.Println(strings.Join(descriptors, "\n")) - return nil - } - - if len(args) < 1 && !c.Latest { - return errors.Errorf("you must provide the name or id of a running container") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - psOutput, err := runtime.Top(c) - if err != nil { - return err - } - w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) - for _, proc := range psOutput { - if _, err := fmt.Fprintln(w, proc); err != nil { - return err - } - } - return w.Flush() -} diff --git a/cmd/podman/tree.go b/cmd/podman/tree.go deleted file mode 100644 index 28c770f0c..000000000 --- a/cmd/podman/tree.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - treeCommand cliconfig.TreeValues - - treeDescription = "Prints layer hierarchy of an image in a tree format" - _treeCommand = &cobra.Command{ - Use: "tree [flags] IMAGE", - Short: treeDescription, - Long: treeDescription, - RunE: func(cmd *cobra.Command, args []string) error { - treeCommand.InputArgs = args - treeCommand.GlobalFlags = MainGlobalOpts - treeCommand.Remote = remoteclient - return treeCmd(&treeCommand) - }, - Example: "podman image tree alpine:latest", - } -) - -func init() { - treeCommand.Command = _treeCommand - treeCommand.SetUsageTemplate(UsageTemplate()) - treeCommand.Flags().BoolVar(&treeCommand.WhatRequires, "whatrequires", false, "Show all child images and layers of the specified image") -} - -func treeCmd(c *cliconfig.TreeValues) error { - args := c.InputArgs - if len(args) == 0 { - return errors.Errorf("an image name must be specified") - } - if len(args) > 1 { - return errors.Errorf("you must provide at most 1 argument") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - tree, err := runtime.ImageTree(c.InputArgs[0], c.WhatRequires) - if err != nil { - return err - } - fmt.Print(tree) - return nil -} diff --git a/cmd/podman/trust.go b/cmd/podman/trust.go deleted file mode 100644 index f13af96bc..000000000 --- a/cmd/podman/trust.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/spf13/cobra" -) - -var ( - trustCommand cliconfig.TrustValues - trustDescription = `Manages which registries you trust as a source of container images based on its location. - The location is determined by the transport and the registry host of the image. Using this container image docker://docker.io/library/busybox as an example, docker is the transport and docker.io is the registry host.` - _trustCommand = &cobra.Command{ - Use: "trust", - Short: "Manage container image trust policy", - Long: trustDescription, - RunE: commandRunE(), - } -) - -func init() { - trustCommand.Command = _trustCommand - trustCommand.SetHelpTemplate(HelpTemplate()) - trustCommand.SetUsageTemplate(UsageTemplate()) - trustCommand.AddCommand(getTrustSubCommands()...) -} diff --git a/cmd/podman/trust_set_show.go b/cmd/podman/trust_set_show.go deleted file mode 100644 index 7d2a5ddc3..000000000 --- a/cmd/podman/trust_set_show.go +++ /dev/null @@ -1,339 +0,0 @@ -package main - -import ( - "io/ioutil" - "os" - "sort" - "strings" - - "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/trust" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - setTrustCommand cliconfig.SetTrustValues - showTrustCommand cliconfig.ShowTrustValues - setTrustDescription = "Set default trust policy or add a new trust policy for a registry" - _setTrustCommand = &cobra.Command{ - Use: "set [flags] REGISTRY", - Short: "Set default trust policy or a new trust policy for a registry", - Long: setTrustDescription, - Example: "", - RunE: func(cmd *cobra.Command, args []string) error { - setTrustCommand.InputArgs = args - setTrustCommand.GlobalFlags = MainGlobalOpts - setTrustCommand.Remote = remoteclient - return setTrustCmd(&setTrustCommand) - }, - } - - showTrustDescription = "Display trust policy for the system" - _showTrustCommand = &cobra.Command{ - Use: "show [flags] [REGISTRY]", - Short: "Display trust policy for the system", - Long: showTrustDescription, - RunE: func(cmd *cobra.Command, args []string) error { - showTrustCommand.InputArgs = args - showTrustCommand.GlobalFlags = MainGlobalOpts - return showTrustCmd(&showTrustCommand) - }, - Example: "", - } -) - -func init() { - setTrustCommand.Command = _setTrustCommand - setTrustCommand.SetHelpTemplate(HelpTemplate()) - setTrustCommand.SetUsageTemplate(UsageTemplate()) - showTrustCommand.Command = _showTrustCommand - showTrustCommand.SetHelpTemplate(HelpTemplate()) - showTrustCommand.SetUsageTemplate(UsageTemplate()) - setFlags := setTrustCommand.Flags() - setFlags.StringVar(&setTrustCommand.PolicyPath, "policypath", "", "") - markFlagHidden(setFlags, "policypath") - setFlags.StringSliceVarP(&setTrustCommand.PubKeysFile, "pubkeysfile", "f", []string{}, `Path of installed public key(s) to trust for TARGET. -Absolute path to keys is added to policy.json. May -used multiple times to define multiple public keys. -File(s) must exist before using this command`) - setFlags.StringVarP(&setTrustCommand.TrustType, "type", "t", "signedBy", "Trust type, accept values: signedBy(default), accept, reject") - - showFlags := showTrustCommand.Flags() - showFlags.BoolVarP(&showTrustCommand.Json, "json", "j", false, "Output as json") - showFlags.StringVar(&showTrustCommand.PolicyPath, "policypath", "", "") - showFlags.BoolVar(&showTrustCommand.Raw, "raw", false, "Output raw policy file") - markFlagHidden(showFlags, "policypath") - showFlags.StringVar(&showTrustCommand.RegistryPath, "registrypath", "", "") - markFlagHidden(showFlags, "registrypath") -} - -func showTrustCmd(c *cliconfig.ShowTrustValues) error { - runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not create runtime") - } - - var ( - policyPath string - systemRegistriesDirPath string - outjson interface{} - ) - if c.Flag("policypath").Changed { - policyPath = c.PolicyPath - } else { - policyPath = trust.DefaultPolicyPath(runtime.SystemContext()) - } - policyContent, err := ioutil.ReadFile(policyPath) - if err != nil { - return errors.Wrapf(err, "unable to read %s", policyPath) - } - if c.Flag("registrypath").Changed { - systemRegistriesDirPath = c.RegistryPath - } else { - systemRegistriesDirPath = trust.RegistriesDirPath(runtime.SystemContext()) - } - - if c.Raw { - _, err := os.Stdout.Write(policyContent) - if err != nil { - return errors.Wrap(err, "could not read raw trust policies") - } - return nil - } - - policyContentStruct, err := trust.GetPolicy(policyPath) - if err != nil { - return errors.Wrapf(err, "could not read trust policies") - } - - if c.Json { - policyJSON, err := getPolicyJSON(policyContentStruct, systemRegistriesDirPath) - if err != nil { - return errors.Wrapf(err, "could not show trust policies in JSON format") - } - outjson = policyJSON - out := formats.JSONStruct{Output: outjson} - return out.Out() - } - - showOutputMap, err := getPolicyShowOutput(policyContentStruct, systemRegistriesDirPath) - if err != nil { - return errors.Wrapf(err, "could not show trust policies") - } - out := formats.StdoutTemplateArray{Output: showOutputMap, Template: "{{.Repo}}\t{{.Trusttype}}\t{{.GPGid}}\t{{.Sigstore}}"} - return out.Out() -} - -func setTrustCmd(c *cliconfig.SetTrustValues) error { - runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not create runtime") - } - var ( - policyPath string - policyContentStruct trust.PolicyContent - newReposContent []trust.RepoContent - ) - args := c.InputArgs - if len(args) != 1 { - return errors.Errorf("default or a registry name must be specified") - } - valid, err := image.IsValidImageURI(args[0]) - if err != nil || !valid { - return errors.Wrapf(err, "invalid image uri %s", args[0]) - } - - trusttype := c.TrustType - if !isValidTrustType(trusttype) { - return errors.Errorf("invalid choice: %s (choose from 'accept', 'reject', 'signedBy')", trusttype) - } - if trusttype == "accept" { - trusttype = "insecureAcceptAnything" - } - - pubkeysfile := c.PubKeysFile - if len(pubkeysfile) == 0 && trusttype == "signedBy" { - return errors.Errorf("At least one public key must be defined for type 'signedBy'") - } - - if c.Flag("policypath").Changed { - policyPath = c.PolicyPath - } else { - policyPath = trust.DefaultPolicyPath(runtime.SystemContext()) - } - _, err = os.Stat(policyPath) - if !os.IsNotExist(err) { - policyContent, err := ioutil.ReadFile(policyPath) - if err != nil { - return errors.Wrapf(err, "unable to read %s", policyPath) - } - if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil { - return errors.Errorf("could not read trust policies") - } - } - if len(pubkeysfile) != 0 { - for _, filepath := range pubkeysfile { - newReposContent = append(newReposContent, trust.RepoContent{Type: trusttype, KeyType: "GPGKeys", KeyPath: filepath}) - } - } else { - newReposContent = append(newReposContent, trust.RepoContent{Type: trusttype}) - } - if args[0] == "default" { - policyContentStruct.Default = newReposContent - } else { - if len(policyContentStruct.Default) == 0 { - return errors.Errorf("Default trust policy must be set.") - } - registryExists := false - for transport, transportval := range policyContentStruct.Transports { - _, registryExists = transportval[args[0]] - if registryExists { - policyContentStruct.Transports[transport][args[0]] = newReposContent - break - } - } - if !registryExists { - if policyContentStruct.Transports == nil { - policyContentStruct.Transports = make(map[string]trust.RepoMap) - } - if policyContentStruct.Transports["docker"] == nil { - policyContentStruct.Transports["docker"] = make(map[string][]trust.RepoContent) - } - policyContentStruct.Transports["docker"][args[0]] = append(policyContentStruct.Transports["docker"][args[0]], newReposContent...) - } - } - - data, err := json.MarshalIndent(policyContentStruct, "", " ") - if err != nil { - return errors.Wrapf(err, "error setting trust policy") - } - err = ioutil.WriteFile(policyPath, data, 0644) - if err != nil { - return errors.Wrapf(err, "error setting trust policy") - } - return nil -} - -func sortShowOutputMapKey(m map[string]trust.ShowOutput) []string { - keys := make([]string, len(m)) - i := 0 - for k := range m { - keys[i] = k - i++ - } - sort.Strings(keys) - return keys -} - -func isValidTrustType(t string) bool { - if t == "accept" || t == "insecureAcceptAnything" || t == "reject" || t == "signedBy" { - return true - } - return false -} - -func getPolicyJSON(policyContentStruct trust.PolicyContent, systemRegistriesDirPath string) (map[string]map[string]interface{}, error) { - registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath) - if err != nil { - return nil, err - } - - policyJSON := make(map[string]map[string]interface{}) - if len(policyContentStruct.Default) > 0 { - policyJSON["* (default)"] = make(map[string]interface{}) - policyJSON["* (default)"]["type"] = policyContentStruct.Default[0].Type - } - for transname, transval := range policyContentStruct.Transports { - for repo, repoval := range transval { - policyJSON[repo] = make(map[string]interface{}) - policyJSON[repo]["type"] = repoval[0].Type - policyJSON[repo]["transport"] = transname - keyarr := []string{} - for _, repoele := range repoval { - if len(repoele.KeyPath) > 0 { - keyarr = append(keyarr, repoele.KeyPath) - } - if len(repoele.KeyData) > 0 { - keyarr = append(keyarr, repoele.KeyData) - } - } - policyJSON[repo]["keys"] = keyarr - policyJSON[repo]["sigstore"] = "" - registryNamespace := trust.HaveMatchRegistry(repo, registryConfigs) - if registryNamespace != nil { - policyJSON[repo]["sigstore"] = registryNamespace.SigStore - } - } - } - return policyJSON, nil -} - -var typeDescription = map[string]string{"insecureAcceptAnything": "accept", "signedBy": "signed", "reject": "reject"} - -func trustTypeDescription(trustType string) string { - trustDescription, exist := typeDescription[trustType] - if !exist { - logrus.Warnf("invalid trust type %s", trustType) - } - return trustDescription -} - -func getPolicyShowOutput(policyContentStruct trust.PolicyContent, systemRegistriesDirPath string) ([]interface{}, error) { - var output []interface{} - - registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath) - if err != nil { - return nil, err - } - - trustShowOutputMap := make(map[string]trust.ShowOutput) - if len(policyContentStruct.Default) > 0 { - defaultPolicyStruct := trust.ShowOutput{ - Repo: "default", - Trusttype: trustTypeDescription(policyContentStruct.Default[0].Type), - } - trustShowOutputMap["* (default)"] = defaultPolicyStruct - } - for _, transval := range policyContentStruct.Transports { - for repo, repoval := range transval { - tempTrustShowOutput := trust.ShowOutput{ - Repo: repo, - Trusttype: repoval[0].Type, - } - // TODO - keyarr is not used and I don't know its intent; commenting out for now for someone to fix later - //keyarr := []string{} - uids := []string{} - for _, repoele := range repoval { - if len(repoele.KeyPath) > 0 { - //keyarr = append(keyarr, repoele.KeyPath) - uids = append(uids, trust.GetGPGIdFromKeyPath(repoele.KeyPath)...) - } - if len(repoele.KeyData) > 0 { - //keyarr = append(keyarr, string(repoele.KeyData)) - uids = append(uids, trust.GetGPGIdFromKeyData(repoele.KeyData)...) - } - } - tempTrustShowOutput.GPGid = strings.Join(uids, ", ") - - registryNamespace := trust.HaveMatchRegistry(repo, registryConfigs) - if registryNamespace != nil { - tempTrustShowOutput.Sigstore = registryNamespace.SigStore - } - trustShowOutputMap[repo] = tempTrustShowOutput - } - } - - sortedRepos := sortShowOutputMapKey(trustShowOutputMap) - for _, reponame := range sortedRepos { - showOutput, exists := trustShowOutputMap[reponame] - if exists { - output = append(output, interface{}(showOutput)) - } - } - return output, nil -} diff --git a/cmd/podman/umount.go b/cmd/podman/umount.go deleted file mode 100644 index 6ad485c2c..000000000 --- a/cmd/podman/umount.go +++ /dev/null @@ -1,62 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - umountCommand cliconfig.UmountValues - - description = `Container storage increments a mount counter each time a container is mounted. - - When a container is unmounted, the mount counter is decremented. The container's root filesystem is physically unmounted only when the mount counter reaches zero indicating no other processes are using the mount. - - An unmount can be forced with the --force flag. -` - _umountCommand = &cobra.Command{ - Use: "umount [flags] CONTAINER [CONTAINER...]", - Aliases: []string{"unmount"}, - Short: "Unmounts working container's root filesystem", - Long: description, - RunE: func(cmd *cobra.Command, args []string) error { - umountCommand.InputArgs = args - umountCommand.GlobalFlags = MainGlobalOpts - umountCommand.Remote = remoteclient - return umountCmd(&umountCommand) - }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllLatestAndCIDFile(cmd, args, false, false) - }, - Example: `podman umount ctrID - podman umount ctrID1 ctrID2 ctrID3 - podman umount --all`, - } -) - -func init() { - umountCommand.Command = _umountCommand - umountCommand.SetHelpTemplate(HelpTemplate()) - umountCommand.SetUsageTemplate(UsageTemplate()) - flags := umountCommand.Flags() - flags.BoolVarP(&umountCommand.All, "all", "a", false, "Umount all of the currently mounted containers") - flags.BoolVarP(&umountCommand.Force, "force", "f", false, "Force the complete umount all of the currently mounted containers") - flags.BoolVarP(&umountCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - markFlagHiddenForRemoteClient("latest", flags) -} - -func umountCmd(c *cliconfig.UmountValues) error { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating runtime") - } - defer runtime.DeferredShutdown(false) - - ok, failures, err := runtime.UmountRootFilesystems(getContext(), c) - if err != nil { - return err - } - return printCmdResults(ok, failures) -} diff --git a/cmd/podman/unpause.go b/cmd/podman/unpause.go deleted file mode 100644 index ae24b0e66..000000000 --- a/cmd/podman/unpause.go +++ /dev/null @@ -1,69 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - unpauseCommand cliconfig.UnpauseValues - - unpauseDescription = `Unpauses one or more previously paused containers. The container name or ID can be used.` - _unpauseCommand = &cobra.Command{ - Use: "unpause [flags] CONTAINER [CONTAINER...]", - Short: "Unpause the processes in one or more containers", - Long: unpauseDescription, - RunE: func(cmd *cobra.Command, args []string) error { - unpauseCommand.InputArgs = args - unpauseCommand.GlobalFlags = MainGlobalOpts - unpauseCommand.Remote = remoteclient - return unpauseCmd(&unpauseCommand) - }, - Example: `podman unpause ctrID - podman unpause --all`, - } -) - -func init() { - unpauseCommand.Command = _unpauseCommand - unpauseCommand.SetHelpTemplate(HelpTemplate()) - unpauseCommand.SetUsageTemplate(UsageTemplate()) - flags := unpauseCommand.Flags() - flags.BoolVarP(&unpauseCommand.All, "all", "a", false, "Unpause all paused containers") -} - -func unpauseCmd(c *cliconfig.UnpauseValues) error { - if rootless.IsRootless() && !remoteclient { - return errors.New("unpause is not supported for rootless containers") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.DeferredShutdown(false) - - args := c.InputArgs - if len(args) < 1 && !c.All { - return errors.Errorf("you must provide at least one container name or id") - } - ok, failures, err := runtime.UnpauseContainers(getContext(), c) - if err != nil { - if errors.Cause(err) == define.ErrNoSuchCtr { - if len(c.InputArgs) > 1 { - exitCode = define.ExecErrorCodeGeneric - } else { - exitCode = 1 - } - } - return err - } - if len(failures) > 0 { - exitCode = define.ExecErrorCodeGeneric - } - return printCmdResults(ok, failures) -} diff --git a/cmd/podman/unshare.go b/cmd/podman/unshare.go deleted file mode 100644 index 28d17a319..000000000 --- a/cmd/podman/unshare.go +++ /dev/null @@ -1,75 +0,0 @@ -// +build !remoteclient - -package main - -import ( - "fmt" - "os" - "os/exec" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - unshareDescription = "Runs a command in a modified user namespace." - _unshareCommand = &cobra.Command{ - Use: "unshare [flags] [COMMAND [ARG]]", - Short: "Run a command in a modified user namespace", - Long: unshareDescription, - RunE: func(cmd *cobra.Command, args []string) error { - unshareCommand.InputArgs = args - unshareCommand.GlobalFlags = MainGlobalOpts - return unshareCmd(&unshareCommand) - }, - Example: `podman unshare id - podman unshare cat /proc/self/uid_map, - podman unshare podman-script.sh`, - } - unshareCommand cliconfig.PodmanCommand -) - -func init() { - unshareCommand.Command = _unshareCommand - unshareCommand.SetHelpTemplate(HelpTemplate()) - unshareCommand.SetUsageTemplate(UsageTemplate()) - flags := _unshareCommand.Flags() - flags.SetInterspersed(false) -} - -func unshareEnv(graphroot, runroot string) []string { - return append(os.Environ(), "_CONTAINERS_USERNS_CONFIGURED=done", - fmt.Sprintf("CONTAINERS_GRAPHROOT=%s", graphroot), - fmt.Sprintf("CONTAINERS_RUNROOT=%s", runroot)) -} - -// unshareCmd execs whatever using the ID mappings that we want to use for ourselves -func unshareCmd(c *cliconfig.PodmanCommand) error { - - if isRootless := rootless.IsRootless(); !isRootless { - return errors.Errorf("please use unshare with rootless") - } - // exec the specified command, if there is one - if len(c.InputArgs) < 1 { - // try to exec the shell, if one's set - shell, shellSet := os.LookupEnv("SHELL") - if !shellSet { - return errors.Errorf("no command specified and no $SHELL specified") - } - c.InputArgs = []string{shell} - } - - runtime, err := adapter.GetRuntime(getContext(), c) - if err != nil { - return err - } - cmd := exec.Command(c.InputArgs[0], c.InputArgs[1:]...) - cmd.Env = unshareEnv(runtime.StorageConfig().GraphRoot, runtime.StorageConfig().RunRoot) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() -} diff --git a/cmd/podman/untag.go b/cmd/podman/untag.go deleted file mode 100644 index 9ff62b808..000000000 --- a/cmd/podman/untag.go +++ /dev/null @@ -1,67 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - untagCommand cliconfig.UntagValues - - _untagCommand = &cobra.Command{ - Use: "untag [flags] IMAGE [NAME...]", - Short: "Remove a name from a local image", - Long: "Removes one or more names from a locally-stored image.", - RunE: func(cmd *cobra.Command, args []string) error { - untagCommand.InputArgs = args - untagCommand.GlobalFlags = MainGlobalOpts - untagCommand.Remote = remoteclient - return untag(&untagCommand) - }, - Example: `podman untag 0e3bbc2 - podman untag imageID:latest otherImageName:latest - podman untag httpd myregistryhost:5000/fedora/httpd:v2`, - } -) - -func init() { - untagCommand.Command = _untagCommand - untagCommand.SetHelpTemplate(HelpTemplate()) - untagCommand.SetUsageTemplate(UsageTemplate()) -} - -func untag(c *cliconfig.UntagValues) error { - args := c.InputArgs - - if len(args) == 0 { - return errors.Errorf("at least one image name needs to be specified") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not create runtime") - } - defer runtime.DeferredShutdown(false) - - newImage, err := runtime.NewImageFromLocal(args[0]) - if err != nil { - return err - } - - tags := args[1:] - if len(args) == 1 { - // Remove all tags if not explicitly specified - tags = newImage.Names() - } - logrus.Debugf("Tags to be removed: %v", tags) - - for _, tag := range tags { - if err := newImage.UntagImage(tag); err != nil { - return errors.Wrapf(err, "removing %q from %q", tag, newImage.InputName) - } - } - return nil -} diff --git a/cmd/podman/utils.go b/cmd/podman/utils.go deleted file mode 100644 index 938a3f41e..000000000 --- a/cmd/podman/utils.go +++ /dev/null @@ -1,90 +0,0 @@ -package main - -import ( - "fmt" - "os" - "reflect" - "runtime/debug" - - "github.com/sirupsen/logrus" - "github.com/spf13/pflag" -) - -// print results from CLI command -func printCmdResults(ok []string, failures map[string]error) error { - for _, id := range ok { - fmt.Println(id) - } - - if len(failures) > 0 { - keys := reflect.ValueOf(failures).MapKeys() - lastKey := keys[len(keys)-1].String() - lastErr := failures[lastKey] - delete(failures, lastKey) - - for _, err := range failures { - outputError(err) - } - return lastErr - } - return nil -} - -// markFlagHiddenForRemoteClient makes the flag not appear as part of the CLI -// on the remote-client -func markFlagHiddenForRemoteClient(flagName string, flags *pflag.FlagSet) { - if remoteclient { - if err := flags.MarkHidden(flagName); err != nil { - debug.PrintStack() - logrus.Errorf("unable to mark %s as hidden in the remote-client", flagName) - } - } -} - -// markFlagHidden is a helper function to log an error if marking -// a flag as hidden happens to fail -func markFlagHidden(flags *pflag.FlagSet, flag string) { - if err := flags.MarkHidden(flag); err != nil { - logrus.Errorf("unable to mark flag '%s' as hidden: %q", flag, err) - } -} - -func aliasFlags(f *pflag.FlagSet, name string) pflag.NormalizedName { - switch name { - case "healthcheck-command": - name = "health-cmd" - case "healthcheck-interval": - name = "health-interval" - case "healthcheck-retries": - name = "health-retries" - case "healthcheck-start-period": - name = "health-start-period" - case "healthcheck-timeout": - name = "health-timeout" - case "net": - name = "network" - case "timeout": - name = "time" - } - return pflag.NormalizedName(name) -} - -// Check if a file exists and is not a directory -func checkIfFileExists(name string) bool { - file, err := os.Stat(name) - // All errors return file == nil - if err != nil { - return false - } - return !file.IsDir() -} - -// Check if a file is or is not a directory -func fileIsDir(name string) bool { - file, err := os.Stat(name) - // All errors return file == nil - if err != nil { - return false - } - return file.IsDir() -} diff --git a/cmd/podman/utils/alias.go b/cmd/podman/utils/alias.go new file mode 100644 index 000000000..54b3c5e89 --- /dev/null +++ b/cmd/podman/utils/alias.go @@ -0,0 +1,24 @@ +package utils + +import "github.com/spf13/pflag" + +// AliasFlags is a function to handle backwards compatability with old flags +func AliasFlags(f *pflag.FlagSet, name string) pflag.NormalizedName { + switch name { + case "healthcheck-command": + name = "health-cmd" + case "healthcheck-interval": + name = "health-interval" + case "healthcheck-retries": + name = "health-retries" + case "healthcheck-start-period": + name = "health-start-period" + case "healthcheck-timeout": + name = "health-timeout" + case "net": + name = "network" + case "timeout": + name = "time" + } + return pflag.NormalizedName(name) +} diff --git a/cmd/podman/utils/error.go b/cmd/podman/utils/error.go new file mode 100644 index 000000000..3464f0779 --- /dev/null +++ b/cmd/podman/utils/error.go @@ -0,0 +1,16 @@ +package utils + +import "fmt" + +type OutputErrors []error + +func (o OutputErrors) PrintErrors() (lastError error) { + if len(o) == 0 { + return + } + lastError = o[len(o)-1] + for e := 0; e < len(o)-1; e++ { + fmt.Println(o[e]) + } + return +} diff --git a/cmd/podman/varlink.go b/cmd/podman/varlink.go deleted file mode 100644 index 20334ec96..000000000 --- a/cmd/podman/varlink.go +++ /dev/null @@ -1,119 +0,0 @@ -// +build varlink,!remoteclient - -package main - -import ( - "fmt" - "os" - "path/filepath" - "time" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" - "github.com/containers/libpod/pkg/util" - iopodman "github.com/containers/libpod/pkg/varlink" - "github.com/containers/libpod/pkg/varlinkapi" - "github.com/containers/libpod/version" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/varlink/go/varlink" -) - -var ( - varlinkCommand cliconfig.VarlinkValues - varlinkDescription = `Run varlink interface. Podman varlink listens on the specified unix domain socket for incoming connects. - - Tools speaking varlink protocol can remotely manage pods, containers and images. -` - _varlinkCommand = &cobra.Command{ - Use: "varlink [flags] [URI]", - Short: "Run varlink interface", - Long: varlinkDescription, - RunE: func(cmd *cobra.Command, args []string) error { - varlinkCommand.InputArgs = args - varlinkCommand.GlobalFlags = MainGlobalOpts - return varlinkCmd(&varlinkCommand) - }, - Example: `podman varlink unix:/run/podman/io.podman - podman varlink --timeout 5000 unix:/run/podman/io.podman`, - } -) - -func init() { - varlinkCommand.Command = _varlinkCommand - varlinkCommand.SetHelpTemplate(HelpTemplate()) - varlinkCommand.SetUsageTemplate(UsageTemplate()) - flags := varlinkCommand.Flags() - flags.Int64VarP(&varlinkCommand.Timeout, "timeout", "t", 1000, "Time until the varlink session expires in milliseconds. Use 0 to disable the timeout") -} - -func varlinkCmd(c *cliconfig.VarlinkValues) error { - varlinkURI := adapter.DefaultVarlinkAddress - if rootless.IsRootless() { - xdg, err := util.GetRuntimeDir() - if err != nil { - return err - } - socketDir := filepath.Join(xdg, "podman/io.podman") - if _, err := os.Stat(filepath.Dir(socketDir)); os.IsNotExist(err) { - if err := os.Mkdir(filepath.Dir(socketDir), 0755); err != nil { - return err - } - } - varlinkURI = fmt.Sprintf("unix:%s", socketDir) - } - args := c.InputArgs - - if len(args) > 1 { - return errors.Errorf("too many arguments. You may optionally provide 1") - } - - if len(args) > 0 { - varlinkURI = args[0] - } - - logrus.Debugf("Using varlink socket: %s", varlinkURI) - timeout := time.Duration(c.Timeout) * time.Millisecond - - // Create a single runtime for varlink - runtime, err := libpodruntime.GetRuntimeDisableFDs(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - var varlinkInterfaces = []*iopodman.VarlinkInterface{varlinkapi.New(&c.PodmanCommand, runtime)} - // Register varlink service. The metadata can be retrieved with: - // $ varlink info [varlink address URI] - service, err := varlink.NewService( - "Atomic", - "podman", - version.Version, - "https://github.com/containers/libpod", - ) - if err != nil { - return errors.Wrapf(err, "unable to create new varlink service") - } - - for _, i := range varlinkInterfaces { - if err := service.RegisterInterface(i); err != nil { - return errors.Errorf("unable to register varlink interface %v", i) - } - } - - // Run the varlink server at the given address - if err = service.Listen(varlinkURI, timeout); err != nil { - switch err.(type) { - case varlink.ServiceTimeoutError: - logrus.Infof("varlink service expired (use --timeout to increase session time beyond %d ms, 0 means never timeout)", c.Int64("timeout")) - return nil - default: - return errors.Wrapf(err, "unable to start varlink service") - } - } - - return nil -} diff --git a/cmd/podman/varlink_dummy.go b/cmd/podman/varlink_dummy.go deleted file mode 100644 index 430511d72..000000000 --- a/cmd/podman/varlink_dummy.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build !varlink - -package main - -import "github.com/spf13/cobra" - -var ( - _varlinkCommand = &cobra.Command{ - Use: "", - } -) diff --git a/cmd/podman/volume.go b/cmd/podman/volume.go deleted file mode 100644 index 2a071d0c7..000000000 --- a/cmd/podman/volume.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/spf13/cobra" -) - -var volumeDescription = `Volumes are created in and can be shared between containers.` - -var volumeCommand = cliconfig.PodmanCommand{ - Command: &cobra.Command{ - Use: "volume", - Short: "Manage volumes", - Long: volumeDescription, - RunE: commandRunE(), - }, -} -var volumeSubcommands = []*cobra.Command{ - _volumeCreateCommand, - _volumeLsCommand, - _volumeRmCommand, - _volumeInspectCommand, - _volumePruneCommand, -} - -func init() { - volumeCommand.SetUsageTemplate(UsageTemplate()) - volumeCommand.AddCommand(volumeSubcommands...) - rootCmd.AddCommand(volumeCommand.Command) -} diff --git a/cmd/podman/volume_create.go b/cmd/podman/volume_create.go deleted file mode 100644 index 52189657b..000000000 --- a/cmd/podman/volume_create.go +++ /dev/null @@ -1,69 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/shared/parse" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - volumeCreateCommand cliconfig.VolumeCreateValues - volumeCreateDescription = `If using the default driver, "local", the volume will be created on the host in the volumes directory under container storage.` - - _volumeCreateCommand = &cobra.Command{ - Use: "create [flags] [NAME]", - Short: "Create a new volume", - Long: volumeCreateDescription, - RunE: func(cmd *cobra.Command, args []string) error { - volumeCreateCommand.InputArgs = args - volumeCreateCommand.GlobalFlags = MainGlobalOpts - volumeCreateCommand.Remote = remoteclient - return volumeCreateCmd(&volumeCreateCommand) - }, - Example: `podman volume create myvol - podman volume create - podman volume create --label foo=bar myvol`, - } -) - -func init() { - volumeCreateCommand.Command = _volumeCreateCommand - volumeCommand.SetHelpTemplate(HelpTemplate()) - volumeCreateCommand.SetUsageTemplate(UsageTemplate()) - flags := volumeCreateCommand.Flags() - flags.StringVar(&volumeCreateCommand.Driver, "driver", "", "Specify volume driver name (default local)") - flags.StringSliceVarP(&volumeCreateCommand.Label, "label", "l", []string{}, "Set metadata for a volume (default [])") - flags.StringArrayVarP(&volumeCreateCommand.Opt, "opt", "o", []string{}, "Set driver specific options (default [])") -} - -func volumeCreateCmd(c *cliconfig.VolumeCreateValues) error { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - if len(c.InputArgs) > 1 { - return errors.Errorf("too many arguments, create takes at most 1 argument") - } - - labels, err := parse.GetAllLabels([]string{}, c.Label) - if err != nil { - return errors.Wrapf(err, "unable to process labels") - } - - opts, err := parse.GetAllLabels([]string{}, c.Opt) - if err != nil { - return errors.Wrapf(err, "unable to process options") - } - - volumeName, err := runtime.CreateVolume(getContext(), c, labels, opts) - if err == nil { - fmt.Println(volumeName) - } - return err -} diff --git a/cmd/podman/volume_inspect.go b/cmd/podman/volume_inspect.go deleted file mode 100644 index 94c99a58c..000000000 --- a/cmd/podman/volume_inspect.go +++ /dev/null @@ -1,79 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - volumeInspectCommand cliconfig.VolumeInspectValues - volumeInspectDescription = `Display detailed information on one or more volumes. - - Use a Go template to change the format from JSON.` - _volumeInspectCommand = &cobra.Command{ - Use: "inspect [flags] VOLUME [VOLUME...]", - Short: "Display detailed information on one or more volumes", - Long: volumeInspectDescription, - RunE: func(cmd *cobra.Command, args []string) error { - volumeInspectCommand.InputArgs = args - volumeInspectCommand.GlobalFlags = MainGlobalOpts - volumeInspectCommand.Remote = remoteclient - return volumeInspectCmd(&volumeInspectCommand) - }, - Example: `podman volume inspect myvol - podman volume inspect --all - podman volume inspect --format "{{.Driver}} {{.Scope}}" myvol`, - } -) - -func init() { - volumeInspectCommand.Command = _volumeInspectCommand - volumeInspectCommand.SetHelpTemplate(HelpTemplate()) - volumeInspectCommand.SetUsageTemplate(UsageTemplate()) - flags := volumeInspectCommand.Flags() - flags.BoolVarP(&volumeInspectCommand.All, "all", "a", false, "Inspect all volumes") - flags.StringVarP(&volumeInspectCommand.Format, "format", "f", "json", "Format volume output using Go template") - -} - -func volumeInspectCmd(c *cliconfig.VolumeInspectValues) error { - if (c.All && len(c.InputArgs) > 0) || (!c.All && len(c.InputArgs) < 1) { - return errors.New("provide one or more volume names or use --all") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - vols, err := runtime.InspectVolumes(getContext(), c) - if err != nil { - return err - } - - switch c.Format { - case "", formats.JSONString: - // Normal format - JSON string - jsonOut, err := json.MarshalIndent(vols, "", " ") - if err != nil { - return errors.Wrapf(err, "error marshalling inspect JSON") - } - fmt.Println(string(jsonOut)) - default: - // It's a Go template. - interfaces := make([]interface{}, len(vols)) - for i, vol := range vols { - interfaces[i] = vol - } - out := formats.StdoutTemplateArray{Output: interfaces, Template: c.Format} - return out.Out() - } - - return nil -} diff --git a/cmd/podman/volume_ls.go b/cmd/podman/volume_ls.go deleted file mode 100644 index 938124278..000000000 --- a/cmd/podman/volume_ls.go +++ /dev/null @@ -1,293 +0,0 @@ -package main - -import ( - "reflect" - "strings" - - "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -// volumeOptions is the "ls" command options -type volumeLsOptions struct { - Format string - Quiet bool -} - -// volumeLsTemplateParams is the template parameters to list the volumes -type volumeLsTemplateParams struct { - Name string - Labels string - MountPoint string - Driver string - Options string - Scope string -} - -// volumeLsJSONParams is the JSON parameters to list the volumes -type volumeLsJSONParams struct { - Name string `json:"name"` - Labels map[string]string `json:"labels"` - MountPoint string `json:"mountPoint"` - Driver string `json:"driver"` - Options map[string]string `json:"options"` - Scope string `json:"scope"` -} - -var ( - volumeLsCommand cliconfig.VolumeLsValues - - volumeLsDescription = ` -podman volume ls - -List all available volumes. The output of the volumes can be filtered -and the output format can be changed to JSON or a user specified Go template.` - _volumeLsCommand = &cobra.Command{ - Use: "ls", - Aliases: []string{"list"}, - Args: noSubArgs, - Short: "List volumes", - Long: volumeLsDescription, - RunE: func(cmd *cobra.Command, args []string) error { - volumeLsCommand.InputArgs = args - volumeLsCommand.GlobalFlags = MainGlobalOpts - volumeLsCommand.Remote = remoteclient - return volumeLsCmd(&volumeLsCommand) - }, - } -) - -func init() { - volumeLsCommand.Command = _volumeLsCommand - volumeLsCommand.SetHelpTemplate(HelpTemplate()) - volumeLsCommand.SetUsageTemplate(UsageTemplate()) - flags := volumeLsCommand.Flags() - - flags.StringVarP(&volumeLsCommand.Filter, "filter", "f", "", "Filter volume output") - flags.StringVar(&volumeLsCommand.Format, "format", "table {{.Driver}}\t{{.Name}}", "Format volume output using Go template") - flags.BoolVarP(&volumeLsCommand.Quiet, "quiet", "q", false, "Print volume output in quiet mode") -} - -func volumeLsCmd(c *cliconfig.VolumeLsValues) error { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - opts := volumeLsOptions{ - Quiet: c.Quiet, - } - opts.Format = genVolLsFormat(c) - - // Get the filter functions based on any filters set - var filterFuncs []adapter.VolumeFilter - if c.Filter != "" { - filters := strings.Split(c.Filter, ",") - for _, f := range filters { - filterSplit := strings.Split(f, "=") - if len(filterSplit) < 2 { - return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) - } - generatedFunc, err := generateVolumeFilterFuncs(filterSplit[0], filterSplit[1]) - if err != nil { - return errors.Wrapf(err, "invalid filter") - } - filterFuncs = append(filterFuncs, generatedFunc) - } - } - - volumes, err := runtime.Volumes(getContext()) - if err != nil { - return err - } - // Get the volumes that match the filter - volsFiltered := make([]*adapter.Volume, 0, len(volumes)) - for _, vol := range volumes { - include := true - for _, filter := range filterFuncs { - include = include && filter(vol) - } - - if include { - volsFiltered = append(volsFiltered, vol) - } - } - return generateVolLsOutput(volsFiltered, opts) -} - -// generate the template based on conditions given -func genVolLsFormat(c *cliconfig.VolumeLsValues) string { - var format string - if c.Format != "" { - // "\t" from the command line is not being recognized as a tab - // replacing the string "\t" to a tab character if the user passes in "\t" - format = strings.Replace(c.Format, `\t`, "\t", -1) - } - if c.Quiet { - format = "{{.Name}}" - } - return format -} - -// Convert output to genericParams for printing -func volLsToGeneric(templParams []volumeLsTemplateParams, jsonParams []volumeLsJSONParams) (genericParams []interface{}) { - if len(templParams) > 0 { - for _, v := range templParams { - genericParams = append(genericParams, interface{}(v)) - } - return - } - for _, v := range jsonParams { - genericParams = append(genericParams, interface{}(v)) - } - return -} - -// generate the accurate header based on template given -func (vol *volumeLsTemplateParams) volHeaderMap() map[string]string { - v := reflect.Indirect(reflect.ValueOf(vol)) - values := make(map[string]string) - - for i := 0; i < v.NumField(); i++ { - key := v.Type().Field(i).Name - value := key - if value == "Name" { - value = "Volume" + value - } - values[key] = strings.ToUpper(splitCamelCase(value)) - } - return values -} - -// getVolTemplateOutput returns all the volumes in the volumeLsTemplateParams format -func getVolTemplateOutput(lsParams []volumeLsJSONParams, opts volumeLsOptions) ([]volumeLsTemplateParams, error) { - var lsOutput []volumeLsTemplateParams - - for _, lsParam := range lsParams { - var ( - labels string - options string - ) - - for k, v := range lsParam.Labels { - label := k - if v != "" { - label += "=" + v - } - labels += label - } - for k, v := range lsParam.Options { - option := k - if v != "" { - option += "=" + v - } - options += option - } - params := volumeLsTemplateParams{ - Name: lsParam.Name, - Driver: lsParam.Driver, - MountPoint: lsParam.MountPoint, - Scope: lsParam.Scope, - Labels: labels, - Options: options, - } - - lsOutput = append(lsOutput, params) - } - return lsOutput, nil -} - -// getVolJSONParams returns the volumes in JSON format -func getVolJSONParams(volumes []*adapter.Volume) []volumeLsJSONParams { - var lsOutput []volumeLsJSONParams - - for _, volume := range volumes { - params := volumeLsJSONParams{ - Name: volume.Name(), - Labels: volume.Labels(), - MountPoint: volume.MountPoint(), - Driver: volume.Driver(), - Options: volume.Options(), - Scope: volume.Scope(), - } - - lsOutput = append(lsOutput, params) - } - return lsOutput -} - -// generateVolLsOutput generates the output based on the format, JSON or Go Template, and prints it out -func generateVolLsOutput(volumes []*adapter.Volume, opts volumeLsOptions) error { - if len(volumes) == 0 && opts.Format != formats.JSONString { - return nil - } - lsOutput := getVolJSONParams(volumes) - var out formats.Writer - - switch opts.Format { - case formats.JSONString: - out = formats.JSONStructArray{Output: volLsToGeneric([]volumeLsTemplateParams{}, lsOutput)} - default: - lsOutput, err := getVolTemplateOutput(lsOutput, opts) - if err != nil { - return errors.Wrapf(err, "unable to create volume output") - } - out = formats.StdoutTemplateArray{Output: volLsToGeneric(lsOutput, []volumeLsJSONParams{}), Template: opts.Format, Fields: lsOutput[0].volHeaderMap()} - } - return out.Out() -} - -// generateVolumeFilterFuncs returns the true if the volume matches the filter set, otherwise it returns false. -func generateVolumeFilterFuncs(filter, filterValue string) (func(volume *adapter.Volume) bool, error) { - switch filter { - case "name": - return func(v *adapter.Volume) bool { - return strings.Contains(v.Name(), filterValue) - }, nil - case "driver": - return func(v *adapter.Volume) bool { - return v.Driver() == filterValue - }, nil - case "scope": - return func(v *adapter.Volume) bool { - return v.Scope() == filterValue - }, nil - case "label": - filterArray := strings.SplitN(filterValue, "=", 2) - filterKey := filterArray[0] - if len(filterArray) > 1 { - filterValue = filterArray[1] - } else { - filterValue = "" - } - return func(v *adapter.Volume) bool { - for labelKey, labelValue := range v.Labels() { - if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) { - return true - } - } - return false - }, nil - case "opt": - filterArray := strings.SplitN(filterValue, "=", 2) - filterKey := filterArray[0] - if len(filterArray) > 1 { - filterValue = filterArray[1] - } else { - filterValue = "" - } - return func(v *adapter.Volume) bool { - for labelKey, labelValue := range v.Options() { - if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) { - return true - } - } - return false - }, nil - } - return nil, errors.Errorf("%s is an invalid filter", filter) -} diff --git a/cmd/podman/volume_prune.go b/cmd/podman/volume_prune.go deleted file mode 100644 index 48ed68509..000000000 --- a/cmd/podman/volume_prune.go +++ /dev/null @@ -1,86 +0,0 @@ -package main - -import ( - "bufio" - "context" - "fmt" - "os" - "strings" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var ( - volumePruneCommand cliconfig.VolumePruneValues - volumePruneDescription = `Volumes that are not currently owned by a container will be removed. - - The command prompts for confirmation which can be overridden with the --force flag. - Note all data will be destroyed.` - _volumePruneCommand = &cobra.Command{ - Use: "prune", - Args: noSubArgs, - Short: "Remove all unused volumes", - Long: volumePruneDescription, - RunE: func(cmd *cobra.Command, args []string) error { - volumePruneCommand.InputArgs = args - volumePruneCommand.GlobalFlags = MainGlobalOpts - volumePruneCommand.Remote = remoteclient - return volumePruneCmd(&volumePruneCommand) - }, - } -) - -func init() { - volumePruneCommand.Command = _volumePruneCommand - volumePruneCommand.SetHelpTemplate(HelpTemplate()) - volumePruneCommand.SetUsageTemplate(UsageTemplate()) - flags := volumePruneCommand.Flags() - - flags.BoolVarP(&volumePruneCommand.Force, "force", "f", false, "Do not prompt for confirmation") -} - -func volumePrune(runtime *adapter.LocalRuntime, ctx context.Context) error { - prunedNames, prunedErrors := runtime.PruneVolumes(ctx) - for _, name := range prunedNames { - fmt.Println(name) - } - if len(prunedErrors) == 0 { - return nil - } - // Grab the last error - lastError := prunedErrors[len(prunedErrors)-1] - // Remove the last error from the error slice - prunedErrors = prunedErrors[:len(prunedErrors)-1] - - for _, err := range prunedErrors { - logrus.Errorf("%q", err) - } - return lastError -} - -func volumePruneCmd(c *cliconfig.VolumePruneValues) error { - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - - // Prompt for confirmation if --force is not set - if !c.Force { - reader := bufio.NewReader(os.Stdin) - fmt.Println("WARNING! This will remove all volumes not used by at least one container.") - fmt.Print("Are you sure you want to continue? [y/N] ") - answer, err := reader.ReadString('\n') - if err != nil { - return errors.Wrapf(err, "error reading input") - } - if strings.ToLower(answer)[0] != 'y' { - return nil - } - } - return volumePrune(runtime, getContext()) -} diff --git a/cmd/podman/volume_rm.go b/cmd/podman/volume_rm.go deleted file mode 100644 index 2fa6a8d03..000000000 --- a/cmd/podman/volume_rm.go +++ /dev/null @@ -1,58 +0,0 @@ -package main - -import ( - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - volumeRmCommand cliconfig.VolumeRmValues - volumeRmDescription = `Remove one or more existing volumes. - - By default only volumes that are not being used by any containers will be removed. To remove the volumes anyways, use the --force flag.` - _volumeRmCommand = &cobra.Command{ - Use: "rm [flags] VOLUME [VOLUME...]", - Aliases: []string{"remove"}, - Short: "Remove one or more volumes", - Long: volumeRmDescription, - RunE: func(cmd *cobra.Command, args []string) error { - volumeRmCommand.InputArgs = args - volumeRmCommand.GlobalFlags = MainGlobalOpts - volumeRmCommand.Remote = remoteclient - return volumeRmCmd(&volumeRmCommand) - }, - Example: `podman volume rm myvol1 myvol2 - podman volume rm --all - podman volume rm --force myvol`, - } -) - -func init() { - volumeRmCommand.Command = _volumeRmCommand - volumeRmCommand.SetHelpTemplate(HelpTemplate()) - volumeRmCommand.SetUsageTemplate(UsageTemplate()) - flags := volumeRmCommand.Flags() - flags.BoolVarP(&volumeRmCommand.All, "all", "a", false, "Remove all volumes") - flags.BoolVarP(&volumeRmCommand.Force, "force", "f", false, "Remove a volume by force, even if it is being used by a container") -} - -func volumeRmCmd(c *cliconfig.VolumeRmValues) error { - var err error - - if (len(c.InputArgs) > 0 && c.All) || (len(c.InputArgs) < 1 && !c.All) { - return errors.New("choose either one or more volumes or all") - } - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.DeferredShutdown(false) - deletedVolumeNames, deletedVolumeErrors, err := runtime.RemoveVolumes(getContext(), c) - if err != nil { - return err - } - return printCmdResults(deletedVolumeNames, deletedVolumeErrors) -} diff --git a/cmd/podman/volumes/create.go b/cmd/podman/volumes/create.go new file mode 100644 index 000000000..df0731791 --- /dev/null +++ b/cmd/podman/volumes/create.go @@ -0,0 +1,72 @@ +package volumes + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + createDescription = `If using the default driver, "local", the volume will be created on the host in the volumes directory under container storage.` + + createCommand = &cobra.Command{ + Use: "create [flags] [NAME]", + Short: "Create a new volume", + Long: createDescription, + RunE: create, + Example: `podman volume create myvol + podman volume create + podman volume create --label foo=bar myvol`, + } +) + +var ( + createOpts = entities.VolumeCreateOptions{} + opts = struct { + Label []string + Opts []string + }{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: createCommand, + Parent: volumeCmd, + }) + flags := createCommand.Flags() + flags.StringVar(&createOpts.Driver, "driver", "", "Specify volume driver name (default local)") + flags.StringSliceVarP(&opts.Label, "label", "l", []string{}, "Set metadata for a volume (default [])") + flags.StringArrayVarP(&opts.Opts, "opt", "o", []string{}, "Set driver specific options (default [])") +} + +func create(cmd *cobra.Command, args []string) error { + var ( + err error + ) + if len(args) > 1 { + return errors.Errorf("too many arguments, create takes at most 1 argument") + } + if len(args) > 0 { + createOpts.Name = args[0] + } + createOpts.Label, err = parse.GetAllLabels([]string{}, opts.Label) + if err != nil { + return errors.Wrapf(err, "unable to process labels") + } + createOpts.Options, err = parse.GetAllLabels([]string{}, opts.Opts) + if err != nil { + return errors.Wrapf(err, "unable to process options") + } + response, err := registry.ContainerEngine().VolumeCreate(context.Background(), createOpts) + if err != nil { + return err + } + fmt.Println(response.IdOrName) + return nil +} diff --git a/cmd/podman/volumes/inspect.go b/cmd/podman/volumes/inspect.go new file mode 100644 index 000000000..57e773aef --- /dev/null +++ b/cmd/podman/volumes/inspect.go @@ -0,0 +1,74 @@ +package volumes + +import ( + "encoding/json" + "fmt" + "html/template" + "os" + + "github.com/containers/buildah/pkg/formats" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "golang.org/x/net/context" +) + +var ( + volumeInspectDescription = `Display detailed information on one or more volumes. + + Use a Go template to change the format from JSON.` + inspectCommand = &cobra.Command{ + Use: "inspect [flags] VOLUME [VOLUME...]", + Short: "Display detailed information on one or more volumes", + Long: volumeInspectDescription, + RunE: inspect, + Example: `podman volume inspect myvol + podman volume inspect --all + podman volume inspect --format "{{.Driver}} {{.Scope}}" myvol`, + } +) + +var ( + inspectOpts = entities.VolumeInspectOptions{} + inspectFormat string +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: inspectCommand, + Parent: volumeCmd, + }) + flags := inspectCommand.Flags() + flags.BoolVarP(&inspectOpts.All, "all", "a", false, "Inspect all volumes") + flags.StringVarP(&inspectFormat, "format", "f", "json", "Format volume output using Go template") +} + +func inspect(cmd *cobra.Command, args []string) error { + if (inspectOpts.All && len(args) > 0) || (!inspectOpts.All && len(args) < 1) { + return errors.New("provide one or more volume names or use --all") + } + responses, err := registry.ContainerEngine().VolumeInspect(context.Background(), args, inspectOpts) + if err != nil { + return err + } + switch inspectFormat { + case "", formats.JSONString: + jsonOut, err := json.MarshalIndent(responses, "", " ") + if err != nil { + return errors.Wrapf(err, "error marshalling inspect JSON") + } + fmt.Println(string(jsonOut)) + default: + tmpl, err := template.New("volumeInspect").Parse(inspectFormat) + if err != nil { + return err + } + if err := tmpl.Execute(os.Stdout, responses); err != nil { + return err + } + } + return nil + +} diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go new file mode 100644 index 000000000..fd89db01f --- /dev/null +++ b/cmd/podman/volumes/list.go @@ -0,0 +1,98 @@ +package volumes + +import ( + "context" + "html/template" + "io" + "os" + "strings" + "text/tabwriter" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + volumeLsDescription = ` +podman volume ls + +List all available volumes. The output of the volumes can be filtered +and the output format can be changed to JSON or a user specified Go template.` + lsCommand = &cobra.Command{ + Use: "ls", + Aliases: []string{"list"}, + Args: cobra.NoArgs, + Short: "List volumes", + Long: volumeLsDescription, + RunE: list, + } +) + +var ( + // Temporary struct to hold cli values. + cliOpts = struct { + Filter []string + Format string + Quiet bool + }{} + lsOpts = entities.VolumeListOptions{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: lsCommand, + Parent: volumeCmd, + }) + flags := lsCommand.Flags() + flags.StringSliceVarP(&cliOpts.Filter, "filter", "f", []string{}, "Filter volume output") + flags.StringVar(&cliOpts.Format, "format", "{{.Driver}}\t{{.Name}}\n", "Format volume output using Go template") + flags.BoolVarP(&cliOpts.Quiet, "quiet", "q", false, "Print volume output in quiet mode") +} + +func list(cmd *cobra.Command, args []string) error { + var w io.Writer = os.Stdout + if cliOpts.Quiet && cmd.Flag("format").Changed { + return errors.New("quiet and format flags cannot be used together") + } + for _, f := range cliOpts.Filter { + filterSplit := strings.Split(f, "=") + if len(filterSplit) < 2 { + return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) + } + lsOpts.Filter[filterSplit[0]] = append(lsOpts.Filter[filterSplit[0]], filterSplit[1:]...) + } + responses, err := registry.ContainerEngine().VolumeList(context.Background(), lsOpts) + if err != nil { + return err + } + // "\t" from the command line is not being recognized as a tab + // replacing the string "\t" to a tab character if the user passes in "\t" + cliOpts.Format = strings.Replace(cliOpts.Format, `\t`, "\t", -1) + if cliOpts.Quiet { + cliOpts.Format = "{{.Name}}\n" + } + headers := "DRIVER\tVOLUME NAME\n" + row := cliOpts.Format + if !strings.HasSuffix(cliOpts.Format, "\n") { + row += "\n" + } + format := "{{range . }}" + row + "{{end}}" + if !cliOpts.Quiet && !cmd.Flag("format").Changed { + w = tabwriter.NewWriter(os.Stdout, 12, 2, 2, ' ', 0) + format = headers + format + } + tmpl, err := template.New("listVolume").Parse(format) + if err != nil { + return err + } + if err := tmpl.Execute(w, responses); err != nil { + return err + } + if flusher, ok := w.(interface{ Flush() error }); ok { + return flusher.Flush() + } + return nil +} diff --git a/cmd/podman/volumes/prune.go b/cmd/podman/volumes/prune.go new file mode 100644 index 000000000..197a9da9b --- /dev/null +++ b/cmd/podman/volumes/prune.go @@ -0,0 +1,74 @@ +package volumes + +import ( + "bufio" + "context" + "fmt" + "os" + "strings" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + volumePruneDescription = `Volumes that are not currently owned by a container will be removed. + + The command prompts for confirmation which can be overridden with the --force flag. + Note all data will be destroyed.` + pruneCommand = &cobra.Command{ + Use: "prune", + Args: cobra.NoArgs, + Short: "Remove all unused volumes", + Long: volumePruneDescription, + RunE: prune, + } +) + +var ( + pruneOptions entities.VolumePruneOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: pruneCommand, + Parent: volumeCmd, + }) + flags := pruneCommand.Flags() + flags.BoolVarP(&pruneOptions.Force, "force", "f", false, "Do not prompt for confirmation") +} + +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) + fmt.Println("WARNING! This will remove all volumes not used by at least one container.") + fmt.Print("Are you sure you want to continue? [y/N] ") + answer, err := reader.ReadString('\n') + if err != nil { + return errors.Wrapf(err, "error reading input") + } + if strings.ToLower(answer)[0] != 'y' { + return nil + } + } + responses, err := registry.ContainerEngine().VolumePrune(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() +} diff --git a/cmd/podman/volumes/rm.go b/cmd/podman/volumes/rm.go new file mode 100644 index 000000000..f5a898ff3 --- /dev/null +++ b/cmd/podman/volumes/rm.go @@ -0,0 +1,64 @@ +package volumes + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + volumeRmDescription = `Remove one or more existing volumes. + + By default only volumes that are not being used by any containers will be removed. To remove the volumes anyways, use the --force flag.` + rmCommand = &cobra.Command{ + Use: "rm [flags] VOLUME [VOLUME...]", + Aliases: []string{"remove"}, + Short: "Remove one or more volumes", + Long: volumeRmDescription, + RunE: rm, + Example: `podman volume rm myvol1 myvol2 + podman volume rm --all + podman volume rm --force myvol`, + } +) + +var ( + rmOptions = entities.VolumeRmOptions{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: rmCommand, + Parent: volumeCmd, + }) + flags := rmCommand.Flags() + flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all volumes") + flags.BoolVarP(&rmOptions.Force, "force", "f", false, "Remove a volume by force, even if it is being used by a container") +} + +func rm(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + ) + if (len(args) > 0 && rmOptions.All) || (len(args) < 1 && !rmOptions.All) { + return errors.New("choose either one or more volumes or all") + } + responses, err := registry.ContainerEngine().VolumeRm(context.Background(), args, rmOptions) + 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() +} diff --git a/cmd/podman/volumes/volume.go b/cmd/podman/volumes/volume.go new file mode 100644 index 000000000..06943da62 --- /dev/null +++ b/cmd/podman/volumes/volume.go @@ -0,0 +1,25 @@ +package volumes + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + // Command: podman _volume_ + volumeCmd = &cobra.Command{ + Use: "volume", + Short: "Manage volumes", + Long: "Volumes are created in and can be shared between containers", + TraverseChildren: true, + RunE: registry.SubCommandExists, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: volumeCmd, + }) +} diff --git a/cmd/podman/wait.go b/cmd/podman/wait.go deleted file mode 100644 index d6a707bb8..000000000 --- a/cmd/podman/wait.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - "time" - - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - waitCommand cliconfig.WaitValues - - waitDescription = `Block until one or more containers stop and then print their exit codes. -` - _waitCommand = &cobra.Command{ - Use: "wait [flags] CONTAINER [CONTAINER...]", - Short: "Block on one or more containers", - Long: waitDescription, - RunE: func(cmd *cobra.Command, args []string) error { - waitCommand.InputArgs = args - waitCommand.GlobalFlags = MainGlobalOpts - waitCommand.Remote = remoteclient - return waitCmd(&waitCommand) - }, - Example: `podman wait --latest - podman wait --interval 5000 ctrID - podman wait ctrID1 ctrID2`, - } -) - -func init() { - waitCommand.Command = _waitCommand - waitCommand.SetHelpTemplate(HelpTemplate()) - waitCommand.SetUsageTemplate(UsageTemplate()) - flags := waitCommand.Flags() - flags.UintVarP(&waitCommand.Interval, "interval", "i", 250, "Milliseconds to wait before polling for completion") - flags.BoolVarP(&waitCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - markFlagHiddenForRemoteClient("latest", flags) -} - -func waitCmd(c *cliconfig.WaitValues) error { - args := c.InputArgs - if len(args) < 1 && !c.Latest { - return errors.Errorf("you must provide at least one container name or id") - } - - if c.Interval == 0 { - return errors.Errorf("interval must be greater then 0") - } - interval := time.Duration(c.Interval) * time.Millisecond - - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "error creating runtime") - } - defer runtime.DeferredShutdown(false) - - ok, failures, err := runtime.WaitOnContainers(getContext(), c, interval) - if err != nil { - return err - } - return printCmdResults(ok, failures) -} |