summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/podman/build.go470
-rw-r--r--cmd/podman/common/create.go58
-rw-r--r--cmd/podman/common/create_opts.go4
-rw-r--r--cmd/podman/common/createparse.go21
-rw-r--r--cmd/podman/common/default.go121
-rw-r--r--cmd/podman/common/inspect.go18
-rw-r--r--cmd/podman/common/netflags.go107
-rw-r--r--cmd/podman/common/ports.go118
-rw-r--r--cmd/podman/common/specgen.go499
-rw-r--r--cmd/podman/common/volumes.go569
-rw-r--r--cmd/podman/containers/attach.go47
-rw-r--r--cmd/podman/containers/commit.go36
-rw-r--r--cmd/podman/containers/container.go22
-rw-r--r--cmd/podman/containers/cp.go55
-rw-r--r--cmd/podman/containers/create.go125
-rw-r--r--cmd/podman/containers/diff.go9
-rw-r--r--cmd/podman/containers/exec.go39
-rw-r--r--cmd/podman/containers/exists.go8
-rw-r--r--cmd/podman/containers/export.go25
-rw-r--r--cmd/podman/containers/inspect.go54
-rw-r--r--cmd/podman/containers/kill.go36
-rw-r--r--cmd/podman/containers/list.go10
-rw-r--r--cmd/podman/containers/mount.go29
-rw-r--r--cmd/podman/containers/pause.go25
-rw-r--r--cmd/podman/containers/port.go123
-rw-r--r--cmd/podman/containers/ps.go47
-rw-r--r--cmd/podman/containers/restart.go40
-rw-r--r--cmd/podman/containers/rm.go55
-rw-r--r--cmd/podman/containers/run.go78
-rw-r--r--cmd/podman/containers/start.go41
-rw-r--r--cmd/podman/containers/stop.go45
-rw-r--r--cmd/podman/containers/top.go38
-rw-r--r--cmd/podman/containers/unmount.go30
-rw-r--r--cmd/podman/containers/unpause.go26
-rw-r--r--cmd/podman/containers/wait.go42
-rw-r--r--cmd/podman/diff.go17
-rw-r--r--cmd/podman/generate/generate.go28
-rw-r--r--cmd/podman/generate/systemd.go57
-rw-r--r--cmd/podman/healthcheck/healthcheck.go3
-rw-r--r--cmd/podman/images/diff.go12
-rw-r--r--cmd/podman/images/exists.go5
-rw-r--r--cmd/podman/images/history.go22
-rw-r--r--cmd/podman/images/image.go6
-rw-r--r--cmd/podman/images/images.go13
-rw-r--r--cmd/podman/images/inspect.go97
-rw-r--r--cmd/podman/images/list.go87
-rw-r--r--cmd/podman/images/load.go3
-rw-r--r--cmd/podman/images/prune.go3
-rw-r--r--cmd/podman/images/pull.go12
-rw-r--r--cmd/podman/images/push.go14
-rw-r--r--cmd/podman/images/rm.go35
-rw-r--r--cmd/podman/images/search.go27
-rw-r--r--cmd/podman/images/tree.go40
-rw-r--r--cmd/podman/inspect.go36
-rw-r--r--cmd/podman/inspect/inspect.go159
-rw-r--r--cmd/podman/login.go68
-rw-r--r--cmd/podman/logout.go57
-rw-r--r--cmd/podman/main.go12
-rw-r--r--cmd/podman/manifest/add.go50
-rw-r--r--cmd/podman/manifest/create.go44
-rw-r--r--cmd/podman/manifest/inspect.go39
-rw-r--r--cmd/podman/manifest/manifest.go28
-rw-r--r--cmd/podman/networks/network.go6
-rw-r--r--cmd/podman/parse/net.go4
-rw-r--r--cmd/podman/parse/net_test.go4
-rw-r--r--cmd/podman/pods/create.go45
-rw-r--r--cmd/podman/pods/exists.go4
-rw-r--r--cmd/podman/pods/inspect.go3
-rw-r--r--cmd/podman/pods/pod.go8
-rw-r--r--cmd/podman/pods/prune.go75
-rw-r--r--cmd/podman/pods/ps.go107
-rw-r--r--cmd/podman/pods/rm.go4
-rw-r--r--cmd/podman/pods/stats.go189
-rw-r--r--cmd/podman/pods/stop.go3
-rw-r--r--cmd/podman/registry/config.go16
-rw-r--r--cmd/podman/registry/json.go20
-rw-r--r--cmd/podman/registry/registry.go26
-rw-r--r--cmd/podman/registry/remote.go2
-rw-r--r--cmd/podman/report/diff.go3
-rw-r--r--cmd/podman/report/report.go6
-rw-r--r--cmd/podman/root.go48
-rw-r--r--cmd/podman/system/events.go3
-rw-r--r--cmd/podman/system/info.go6
-rw-r--r--cmd/podman/system/service.go30
-rw-r--r--cmd/podman/system/service_abi.go57
-rw-r--r--cmd/podman/system/service_unsupported.go14
-rw-r--r--cmd/podman/system/system.go6
-rw-r--r--cmd/podman/system/version.go9
-rw-r--r--cmd/podman/utils/alias.go2
-rw-r--r--cmd/podman/utils/utils.go22
-rw-r--r--cmd/podman/validate/args.go32
-rw-r--r--cmd/podman/validate/choice.go46
-rw-r--r--cmd/podman/volumes/create.go2
-rw-r--r--cmd/podman/volumes/inspect.go8
-rw-r--r--cmd/podman/volumes/list.go23
-rw-r--r--cmd/podman/volumes/prune.go3
-rw-r--r--cmd/podman/volumes/volume.go6
97 files changed, 3797 insertions, 1089 deletions
diff --git a/cmd/podman/build.go b/cmd/podman/build.go
new file mode 100644
index 000000000..43a2f7ab5
--- /dev/null
+++ b/cmd/podman/build.go
@@ -0,0 +1,470 @@
+package main
+
+import (
+ "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/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/docker/go-units"
+ "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+)
+
+// buildFlagsWrapper are local to cmd/ as the build code is using Buildah-internal
+// types. Hence, after parsing, we are converting buildFlagsWrapper to the entities'
+// options which essentially embed the Buildah types.
+type buildFlagsWrapper struct {
+ // Buildah stuff first
+ buildahCLI.BudResults
+ buildahCLI.LayerResults
+ buildahCLI.FromAndBudResults
+ buildahCLI.NameSpaceResults
+ buildahCLI.UserNSResults
+
+ // SquashAll squashes all layers into a single layer.
+ SquashAll bool
+}
+
+var (
+ // Command: podman _diff_ Object_ID
+ buildDescription = "Builds an OCI or Docker image using instructions from one or more Containerfiles and a specified build context directory."
+ buildCmd = &cobra.Command{
+ Use: "build [flags] [CONTEXT]",
+ Short: "Build an image using instructions from Containerfiles",
+ Long: buildDescription,
+ TraverseChildren: true,
+ RunE: build,
+ Example: `podman build .
+ podman build --creds=username:password -t imageName -f Containerfile.simple .
+ podman build --layers --force-rm --tag imageName .`,
+ }
+
+ buildOpts = buildFlagsWrapper{}
+)
+
+// 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"
+}
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: buildCmd,
+ })
+ flags := buildCmd.Flags()
+
+ // Podman flags
+ flags.BoolVarP(&buildOpts.SquashAll, "squash-all", "", false, "Squash all layers into a single layer")
+
+ // Bud flags
+ budFlags := buildahCLI.GetBudFlags(&buildOpts.BudResults)
+ // --pull flag
+ flag := budFlags.Lookup("pull")
+ if err := flag.Value.Set("true"); err != nil {
+ logrus.Errorf("unable to set --pull to true: %v", err)
+ }
+ flag.DefValue = "true"
+ flags.AddFlagSet(&budFlags)
+
+ // Layer flags
+ layerFlags := buildahCLI.GetLayerFlags(&buildOpts.LayerResults)
+ // --layers flag
+ flag = layerFlags.Lookup("layers")
+ useLayersVal := useLayers()
+ if err := flag.Value.Set(useLayersVal); err != nil {
+ logrus.Errorf("unable to set --layers to %v: %v", useLayersVal, err)
+ }
+ flag.DefValue = useLayersVal
+ // --force-rm flag
+ flag = layerFlags.Lookup("force-rm")
+ if err := flag.Value.Set("true"); err != nil {
+ logrus.Errorf("unable to set --force-rm to true: %v", err)
+ }
+ flag.DefValue = "true"
+ flags.AddFlagSet(&layerFlags)
+
+ // FromAndBud flags
+ fromAndBudFlags, err := buildahCLI.GetFromAndBudFlags(&buildOpts.FromAndBudResults, &buildOpts.UserNSResults, &buildOpts.NameSpaceResults)
+ if err != nil {
+ logrus.Errorf("error setting up build flags: %v", err)
+ os.Exit(1)
+ }
+ flags.AddFlagSet(&fromAndBudFlags)
+}
+
+// build executes the build command.
+func build(cmd *cobra.Command, args []string) error {
+ if (cmd.Flags().Changed("squash") && cmd.Flags().Changed("layers")) ||
+ (cmd.Flags().Changed("squash-all") && cmd.Flags().Changed("layers")) ||
+ (cmd.Flags().Changed("squash-all") && cmd.Flags().Changed("squash")) {
+ return errors.New("cannot specify --squash, --squash-all and --layers options together")
+ }
+
+ contextDir, containerFiles, err := extractContextAndFiles(args, buildOpts.File)
+ if err != nil {
+ return err
+ }
+
+ ie, err := registry.NewImageEngine(cmd, args)
+ if err != nil {
+ return err
+ }
+
+ apiBuildOpts, err := buildFlagsWrapperToOptions(cmd, contextDir, &buildOpts)
+ if err != nil {
+ return err
+ }
+
+ _, err = ie.Build(registry.GetContext(), containerFiles, *apiBuildOpts)
+ return err
+}
+
+// extractContextAndFiles parses args and files to extract a context directory
+// and {Container,Docker}files.
+//
+// TODO: this was copied and altered from the v1 client which in turn was
+// copied and altered from the Buildah code. Ideally, all of this code should
+// be cleanly consolidated into a package that is shared between Buildah and
+// Podman.
+func extractContextAndFiles(args, files []string) (string, []string, error) {
+ // Extract container files from the CLI (i.e., --file/-f) first.
+ var containerFiles []string
+ for _, f := range files {
+ if f == "-" {
+ containerFiles = append(containerFiles, "/dev/stdin")
+ } else {
+ containerFiles = append(containerFiles, f)
+ }
+ }
+
+ // Determine context directory.
+ var contextDir string
+ if len(args) > 0 {
+ // The context directory could be a URL. Try to handle that.
+ tempDir, subDir, err := imagebuildah.TempDirForURL("", "buildah", args[0])
+ if err != nil {
+ return "", nil, 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(args[0])
+ if err != nil {
+ return "", nil, errors.Wrapf(err, "error determining path to directory %q", args[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 "", nil, errors.Wrapf(err, "error determining path to file %q", containerFiles[i])
+ }
+ contextDir = filepath.Dir(absFile)
+ break
+ }
+ }
+
+ if contextDir == "" {
+ return "", nil, errors.Errorf("no context directory and no Containerfile specified")
+ }
+ if !utils.IsDir(contextDir) {
+ return "", nil, errors.Errorf("context must be a directory: %q", contextDir)
+ }
+ if len(containerFiles) == 0 {
+ if utils.FileExists(filepath.Join(contextDir, "Containerfile")) {
+ containerFiles = append(containerFiles, filepath.Join(contextDir, "Containerfile"))
+ } else {
+ containerFiles = append(containerFiles, filepath.Join(contextDir, "Dockerfile"))
+ }
+ }
+
+ return contextDir, containerFiles, nil
+}
+
+// buildFlagsWrapperToOptions converts the local build flags to the build options used
+// in the API which embed Buildah types used across the build code. Doing the
+// conversion here prevents the API from doing that (redundantly).
+//
+// TODO: this code should really be in Buildah.
+func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buildFlagsWrapper) (*entities.BuildOptions, error) {
+ output := ""
+ tags := []string{}
+ if c.Flag("tag").Changed {
+ tags = flags.Tag
+ if len(tags) > 0 {
+ output = tags[0]
+ tags = tags[1:]
+ }
+ }
+
+ pullPolicy := imagebuildah.PullNever
+ if flags.Pull {
+ pullPolicy = imagebuildah.PullIfMissing
+ }
+ if flags.PullAlways {
+ pullPolicy = imagebuildah.PullAlways
+ }
+
+ args := make(map[string]string)
+ if c.Flag("build-arg").Changed {
+ for _, arg := range flags.BuildArg {
+ av := strings.SplitN(arg, "=", 2)
+ if len(av) > 1 {
+ args[av[0]] = av[1]
+ } else {
+ delete(args, av[0])
+ }
+ }
+ }
+ // Check to see if the BUILDAH_LAYERS environment variable is set and
+ // override command-line.
+ if _, ok := os.LookupEnv("BUILDAH_LAYERS"); ok {
+ flags.Layers = true
+ }
+
+ // `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") && buildOpts.Squash {
+ flags.Squash = false
+ flags.Layers = false
+ }
+ // Squash-all invoked, squash both new and old layers into one.
+ if c.Flags().Changed("squash-all") {
+ flags.Squash = true
+ flags.Layers = 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(flags.Logfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
+ if err != nil {
+ return nil, errors.Errorf("error opening logfile %q: %v", flags.Logfile, err)
+ }
+ defer f.Close()
+ logrus.SetOutput(f)
+ stdout = f
+ stderr = f
+ reporter = f
+ }
+
+ var memoryLimit, memorySwap int64
+ var err error
+ if c.Flags().Changed("memory") {
+ memoryLimit, err = units.RAMInBytes(flags.Memory)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if c.Flags().Changed("memory-swap") {
+ memorySwap, err = units.RAMInBytes(flags.MemorySwap)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ nsValues, err := getNsValues(flags)
+ if err != nil {
+ return nil, 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
+ }
+ }
+
+ // `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") && flags.Squash {
+ flags.Squash = false
+ flags.Layers = false
+ }
+ // Squash-all invoked, squash both new and old layers into one.
+ if c.Flags().Changed("squash-all") {
+ flags.Squash = true
+ flags.Layers = false
+ }
+
+ compression := imagebuildah.Gzip
+ if flags.DisableCompression {
+ compression = imagebuildah.Uncompressed
+ }
+
+ isolation, err := parse.IsolationOption(flags.Isolation)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error parsing ID mapping options")
+ }
+
+ usernsOption, idmappingOptions, err := parse.IDMappingOptions(c, isolation)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error parsing ID mapping options")
+ }
+ nsValues = append(nsValues, usernsOption...)
+
+ systemContext, err := parse.SystemContextFromOptions(c)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error building system context")
+ }
+
+ format := ""
+ flags.Format = strings.ToLower(flags.Format)
+ switch {
+ case strings.HasPrefix(flags.Format, buildah.OCI):
+ format = buildah.OCIv1ImageManifest
+ case strings.HasPrefix(flags.Format, buildah.DOCKER):
+ format = buildah.Dockerv2ImageManifest
+ default:
+ return nil, errors.Errorf("unrecognized image type %q", flags.Format)
+ }
+
+ runtimeFlags := []string{}
+ for _, arg := range flags.RuntimeFlags {
+ runtimeFlags = append(runtimeFlags, "--"+arg)
+ }
+
+ // FIXME: the code below needs to be enabled (and adjusted) once the
+ // global/root flags are supported.
+
+ // conf, err := runtime.GetConfig()
+ // if err != nil {
+ // return err
+ // }
+ // if conf != nil && conf.Engine.CgroupManager == config.SystemdCgroupsManager {
+ // runtimeFlags = append(runtimeFlags, "--systemd-cgroup")
+ // }
+
+ opts := imagebuildah.BuildOptions{
+ AddCapabilities: flags.CapAdd,
+ AdditionalTags: tags,
+ Annotations: flags.Annotation,
+ Architecture: flags.Arch,
+ Args: args,
+ BlobDirectory: flags.BlobCache,
+ CNIConfigDir: flags.CNIConfigDir,
+ CNIPluginPath: flags.CNIPlugInPath,
+ CommonBuildOpts: &buildah.CommonBuildOptions{
+ AddHost: flags.AddHost,
+ CgroupParent: flags.CgroupParent,
+ CPUPeriod: flags.CPUPeriod,
+ CPUQuota: flags.CPUQuota,
+ CPUShares: flags.CPUShares,
+ CPUSetCPUs: flags.CPUSetCPUs,
+ CPUSetMems: flags.CPUSetMems,
+ Memory: memoryLimit,
+ MemorySwap: memorySwap,
+ ShmSize: flags.ShmSize,
+ Ulimit: flags.Ulimit,
+ Volumes: flags.Volumes,
+ },
+ Compression: compression,
+ ConfigureNetwork: networkPolicy,
+ ContextDirectory: contextDir,
+ // DefaultMountsFilePath: FIXME: this requires global flags to be working!
+ Devices: flags.Devices,
+ DropCapabilities: flags.CapDrop,
+ Err: stderr,
+ ForceRmIntermediateCtrs: flags.ForceRm,
+ IDMappingOptions: idmappingOptions,
+ IIDFile: flags.Iidfile,
+ In: stdin,
+ Isolation: isolation,
+ Labels: flags.Label,
+ Layers: flags.Layers,
+ NamespaceOptions: nsValues,
+ NoCache: flags.NoCache,
+ OS: flags.OS,
+ Out: stdout,
+ Output: output,
+ OutputFormat: format,
+ PullPolicy: pullPolicy,
+ Quiet: flags.Quiet,
+ RemoveIntermediateCtrs: flags.Rm,
+ ReportWriter: reporter,
+ RuntimeArgs: runtimeFlags,
+ SignBy: flags.SignBy,
+ SignaturePolicyPath: flags.SignaturePolicy,
+ Squash: flags.Squash,
+ SystemContext: systemContext,
+ Target: flags.Target,
+ TransientMounts: flags.Volumes,
+ }
+
+ return &entities.BuildOptions{BuildOptions: opts}, nil
+}
+
+func getNsValues(flags *buildFlagsWrapper) ([]buildah.NamespaceOption, error) {
+ var ret []buildah.NamespaceOption
+ if flags.Network != "" {
+ switch {
+ case flags.Network == "host":
+ ret = append(ret, buildah.NamespaceOption{
+ Name: string(specs.NetworkNamespace),
+ Host: true,
+ })
+ case flags.Network == "container":
+ ret = append(ret, buildah.NamespaceOption{
+ Name: string(specs.NetworkNamespace),
+ })
+ case flags.Network[0] == '/':
+ ret = append(ret, buildah.NamespaceOption{
+ Name: string(specs.NetworkNamespace),
+ Path: flags.Network,
+ })
+ default:
+ return nil, errors.Errorf("unsupported configuration network=%s", flags.Network)
+ }
+ }
+ return ret, nil
+}
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go
index a7c8435c9..a0aed984c 100644
--- a/cmd/podman/common/create.go
+++ b/cmd/podman/common/create.go
@@ -10,7 +10,7 @@ import (
const sizeWithUnitFormat = "(format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))"
-var containerConfig = registry.NewPodmanConfig()
+var containerConfig = registry.PodmanConfig()
func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
createFlags := pflag.FlagSet{}
@@ -49,14 +49,15 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
"cap-drop", []string{},
"Drop capabilities from the container",
)
+ cgroupNS := ""
createFlags.StringVar(
- &cf.CGroupsNS,
- "cgroupns", getDefaultCgroupNS(),
+ &cgroupNS,
+ "cgroupns", containerConfig.CgroupNS(),
"cgroup namespace to use",
)
createFlags.StringVar(
- &cf.CGroups,
- "cgroups", "enabled",
+ &cf.CGroupsMode,
+ "cgroups", containerConfig.Cgroups(),
`control container cgroup configuration ("enabled"|"disabled"|"no-conmon")`,
)
createFlags.StringVar(
@@ -121,12 +122,12 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
)
createFlags.StringVar(
&cf.DetachKeys,
- "detach-keys", GetDefaultDetachKeys(),
+ "detach-keys", containerConfig.DetachKeys(),
"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(),
+ &cf.Devices,
+ "device", containerConfig.Devices(),
fmt.Sprintf("Add a host device to the container"),
)
createFlags.StringSliceVar(
@@ -161,7 +162,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
)
createFlags.StringArrayVarP(
&cf.env,
- "env", "e", getDefaultEnv(),
+ "env", "e", containerConfig.Env(),
"Set environment variables in container",
)
createFlags.BoolVar(
@@ -238,7 +239,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
)
createFlags.StringVar(
&cf.InitPath,
- "init-path", getDefaultInitPath(),
+ "init-path", containerConfig.InitPath(),
// 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"),
)
@@ -247,9 +248,10 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
"interactive", "i", false,
"Keep STDIN open even if not attached",
)
+ ipcNS := ""
createFlags.StringVar(
- &cf.IPC,
- "ipc", getDefaultIPCNS(),
+ &ipcNS,
+ "ipc", containerConfig.IPCNS(),
"IPC namespace to use",
)
createFlags.StringVar(
@@ -329,15 +331,16 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
"use `OS` instead of the running OS for choosing images",
)
// markFlagHidden(createFlags, "override-os")
+ pid := ""
createFlags.StringVar(
- &cf.PID,
- "pid", getDefaultPidNS(),
+ &pid,
+ "pid", containerConfig.PidNS(),
"PID namespace to use",
)
createFlags.Int64Var(
&cf.PIDsLimit,
- "pids-limit", getDefaultPidsLimit(),
- getDefaultPidsDescription(),
+ "pids-limit", containerConfig.PidsLimit(),
+ "Tune container pids limit (set 0 for unlimited, -1 for server defaults)",
)
createFlags.StringVar(
&cf.Pod,
@@ -391,12 +394,13 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
)
createFlags.StringArrayVar(
&cf.SecurityOpt,
- "security-opt", getDefaultSecurityOptions(),
+ "security-opt", containerConfig.SecurityOptions(),
"Security Options",
)
+ shmSize := ""
createFlags.StringVar(
- &cf.ShmSize,
- "shm-size", getDefaultShmSize(),
+ &shmSize,
+ "shm-size", containerConfig.ShmSize(),
"Size of /dev/shm "+sizeWithUnitFormat,
)
createFlags.StringVar(
@@ -427,7 +431,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
createFlags.StringSliceVar(
&cf.Sysctl,
- "sysctl", getDefaultSysctls(),
+ "sysctl", containerConfig.Sysctls(),
"Sysctl options",
)
createFlags.StringVar(
@@ -452,7 +456,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
)
createFlags.StringSliceVar(
&cf.Ulimit,
- "ulimit", getDefaultUlimits(),
+ "ulimit", containerConfig.Ulimits(),
"Ulimit options",
)
createFlags.StringVarP(
@@ -460,14 +464,16 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
"user", "u", "",
"Username or UID (format: <name|uid>[:<group|gid>])",
)
+ userNS := ""
createFlags.StringVar(
- &cf.UserNS,
- "userns", getDefaultUserNS(),
+ &userNS,
+ "userns", containerConfig.Containers.UserNS,
"User namespace to use",
)
+ utsNS := ""
createFlags.StringVar(
- &cf.UTS,
- "uts", getDefaultUTSNS(),
+ &utsNS,
+ "uts", containerConfig.Containers.UTSNS,
"UTS namespace to use",
)
createFlags.StringArrayVar(
@@ -477,7 +483,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
)
createFlags.StringArrayVarP(
&cf.Volume,
- "volume", "v", getDefaultVolumes(),
+ "volume", "v", containerConfig.Volumes(),
"Bind mount a volume into the container",
)
createFlags.StringSliceVar(
diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go
index 9d12e4b26..2f08bb6a6 100644
--- a/cmd/podman/common/create_opts.go
+++ b/cmd/podman/common/create_opts.go
@@ -11,7 +11,7 @@ type ContainerCLIOpts struct {
CapAdd []string
CapDrop []string
CGroupsNS string
- CGroups string
+ CGroupsMode string
CGroupParent string
CIDFile string
ConmonPIDFile string
@@ -25,7 +25,7 @@ type ContainerCLIOpts struct {
CPUSetMems string
Detach bool
DetachKeys string
- Device []string
+ Devices []string
DeviceCGroupRule []string
DeviceReadBPs []string
DeviceReadIOPs []string
diff --git a/cmd/podman/common/createparse.go b/cmd/podman/common/createparse.go
index aca6f752e..fe6e322c2 100644
--- a/cmd/podman/common/createparse.go
+++ b/cmd/podman/common/createparse.go
@@ -1,7 +1,6 @@
package common
import (
- "github.com/containers/libpod/cmd/podman/parse"
"github.com/containers/libpod/pkg/util"
"github.com/pkg/errors"
)
@@ -17,27 +16,7 @@ func (c *ContainerCLIOpts) validate() error {
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": "",
diff --git a/cmd/podman/common/default.go b/cmd/podman/common/default.go
index 853f87ab6..7233b2091 100644
--- a/cmd/podman/common/default.go
+++ b/cmd/podman/common/default.go
@@ -1,16 +1,7 @@
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"
+ "github.com/containers/libpod/cmd/podman/registry"
)
var (
@@ -24,112 +15,6 @@ var (
DefaultHealthCheckTimeout = "30s"
// DefaultImageVolume default value
DefaultImageVolume = "bind"
+ // Pull in configured json library
+ json = registry.JsonLibrary()
)
-
-// 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
deleted file mode 100644
index dfc6fe679..000000000
--- a/cmd/podman/common/inspect.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package common
-
-import (
- "github.com/containers/libpod/pkg/domain/entities"
- "github.com/spf13/cobra"
-)
-
-// AddInspectFlagSet takes a command and adds the inspect flags and returns an InspectOptions object
-// Since this cannot live in `package main` it lives here until a better home is found
-func AddInspectFlagSet(cmd *cobra.Command) *entities.InspectOptions {
- opts := entities.InspectOptions{}
-
- flags := cmd.Flags()
- flags.BoolVarP(&opts.Size, "size", "s", false, "Display total file size")
- flags.StringVarP(&opts.Format, "format", "f", "", "Change the output format to a Go template")
-
- return &opts
-}
diff --git a/cmd/podman/common/netflags.go b/cmd/podman/common/netflags.go
index 41eed2988..2bb45476b 100644
--- a/cmd/podman/common/netflags.go
+++ b/cmd/podman/common/netflags.go
@@ -3,7 +3,11 @@ package common
import (
"net"
+ "github.com/containers/libpod/cmd/podman/parse"
+ "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/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -15,15 +19,15 @@ func GetNetFlags() *pflag.FlagSet {
"Add a custom host-to-IP mapping (host:ip) (default [])",
)
netFlags.StringSlice(
- "dns", getDefaultDNSServers(),
+ "dns", containerConfig.DNSServers(),
"Set custom DNS servers",
)
netFlags.StringSlice(
- "dns-opt", getDefaultDNSOptions(),
+ "dns-opt", containerConfig.DNSOptions(),
"Set custom DNS options",
)
netFlags.StringSlice(
- "dns-search", getDefaultDNSSearches(),
+ "dns-search", containerConfig.DNSSearches(),
"Set custom DNS search domains",
)
netFlags.String(
@@ -35,7 +39,7 @@ func GetNetFlags() *pflag.FlagSet {
"Container MAC address (e.g. 92:d0:c6:0a:29:33)",
)
netFlags.String(
- "network", getDefaultNetNS(),
+ "network", containerConfig.NetNS(),
"Connect a container to a network",
)
netFlags.StringSliceP(
@@ -58,20 +62,60 @@ func NetFlagsToNetOptions(cmd *cobra.Command) (*entities.NetOptions, error) {
if err != nil {
return nil, err
}
- servers, err := cmd.Flags().GetStringSlice("dns")
- if err != nil {
- return nil, err
+ // Verify the additional hosts are in correct format
+ for _, host := range opts.AddHosts {
+ if _, err := parse.ValidateExtraHost(host); err != nil {
+ return nil, err
+ }
}
- for _, d := range servers {
- if d == "none" {
- opts.DNSHost = true
- break
+
+ if cmd.Flags().Changed("dns") {
+ servers, err := cmd.Flags().GetStringSlice("dns")
+ if err != nil {
+ return nil, err
+ }
+ for _, d := range servers {
+ if d == "none" {
+ opts.UseImageResolvConf = true
+ if len(servers) > 1 {
+ return nil, errors.Errorf("%s is not allowed to be specified with other DNS ip addresses", d)
+ }
+ break
+ }
+ dns := net.ParseIP(d)
+ if dns == nil {
+ return nil, errors.Errorf("%s is not an ip address", d)
+ }
+ opts.DNSServers = append(opts.DNSServers, dns)
}
- opts.DNSServers = append(opts.DNSServers, net.ParseIP(d))
}
- opts.DNSSearch, err = cmd.Flags().GetStringSlice("dns-search")
- if err != nil {
- return nil, err
+
+ if cmd.Flags().Changed("dns-opt") {
+ options, err := cmd.Flags().GetStringSlice("dns-opt")
+ if err != nil {
+ return nil, err
+ }
+ opts.DNSOptions = options
+ }
+
+ if cmd.Flags().Changed("dns-search") {
+ dnsSearches, err := cmd.Flags().GetStringSlice("dns-search")
+ if err != nil {
+ return nil, err
+ }
+ // 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
+ }
+ }
+ opts.DNSSearch = dnsSearches
}
m, err := cmd.Flags().GetString("mac-address")
@@ -85,6 +129,7 @@ func NetFlagsToNetOptions(cmd *cobra.Command) (*entities.NetOptions, error) {
}
opts.StaticMAC = &mac
}
+
inputPorts, err := cmd.Flags().GetStringSlice("publish")
if err != nil {
return nil, err
@@ -95,6 +140,38 @@ func NetFlagsToNetOptions(cmd *cobra.Command) (*entities.NetOptions, error) {
return nil, err
}
}
+
+ ip, err := cmd.Flags().GetString("ip")
+ if err != nil {
+ return nil, err
+ }
+ if ip != "" {
+ staticIP := net.ParseIP(ip)
+ if staticIP == nil {
+ return nil, errors.Errorf("%s is not an ip address", ip)
+ }
+ if staticIP.To4() == nil {
+ return nil, errors.Wrapf(define.ErrInvalidArg, "%s is not an IPv4 address", ip)
+ }
+ opts.StaticIP = &staticIP
+ }
+
opts.NoHosts, err = cmd.Flags().GetBool("no-hosts")
+
+ if cmd.Flags().Changed("network") {
+ network, err := cmd.Flags().GetString("network")
+ if err != nil {
+ return nil, err
+ }
+
+ ns, cniNets, err := specgen.ParseNetworkNamespace(network)
+ if err != nil {
+ return nil, err
+ }
+
+ opts.Network = ns
+ opts.CNINetworks = cniNets
+ }
+
return &opts, err
}
diff --git a/cmd/podman/common/ports.go b/cmd/podman/common/ports.go
index 7e2b1e79d..2092bbe53 100644
--- a/cmd/podman/common/ports.go
+++ b/cmd/podman/common/ports.go
@@ -1,126 +1,22 @@
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] = ""
- //}
-
+func verifyExpose(expose []string) error {
// add the expose ports from the user (--expose)
// can be single or a range
for _, expose := range expose {
- //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
+ // support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
_, port := nat.SplitProtoPort(expose)
- //parse the start and end port and create a sequence of ports to expose
- //if expose a port, the start and end port are the same
- start, end, err := nat.ParsePortRange(port)
+ // parse the start and end port and create a sequence of ports to expose
+ // if expose a port, the start and end port are the same
+ _, _, err := nat.ParsePortRange(port)
if err != nil {
- return 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 errors.Wrapf(err, "invalid range format for --expose: %s", expose)
}
}
- 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}
+ return nil
}
diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go
index 87194a9fb..96cd630a3 100644
--- a/cmd/podman/common/specgen.go
+++ b/cmd/podman/common/specgen.go
@@ -1,7 +1,6 @@
package common
import (
- "encoding/json"
"fmt"
"os"
"path/filepath"
@@ -23,42 +22,135 @@ import (
"github.com/pkg/errors"
)
-func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) error {
- var (
- err error
- //namespaces map[string]string
- )
+func getCPULimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) (*specs.LinuxCPU, error) {
+ cpu := &specs.LinuxCPU{}
+ hasLimits := false
- // validate flags as needed
- if err := c.validate(); err != nil {
- return nil
+ if c.CPUShares > 0 {
+ cpu.Shares = &c.CPUShares
+ hasLimits = true
+ }
+ if c.CPUPeriod > 0 {
+ cpu.Period = &c.CPUPeriod
+ hasLimits = true
+ }
+ if c.CPUSetCPUs != "" {
+ cpu.Cpus = c.CPUSetCPUs
+ hasLimits = true
+ }
+ if c.CPUSetMems != "" {
+ cpu.Mems = c.CPUSetMems
+ hasLimits = true
+ }
+ if c.CPUQuota > 0 {
+ cpu.Quota = &c.CPUQuota
+ hasLimits = true
+ }
+ if c.CPURTPeriod > 0 {
+ cpu.RealtimePeriod = &c.CPURTPeriod
+ hasLimits = true
+ }
+ if c.CPURTRuntime > 0 {
+ cpu.RealtimeRuntime = &c.CPURTRuntime
+ hasLimits = true
}
- inputCommand := args[1:]
- if len(c.HealthCmd) > 0 {
- s.HealthConfig, err = makeHealthCheckFromCli(c.HealthCmd, c.HealthInterval, c.HealthRetries, c.HealthTimeout, c.HealthStartPeriod)
+ if !hasLimits {
+ return nil, nil
+ }
+ return cpu, nil
+}
+
+func getIOLimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) (*specs.LinuxBlockIO, error) {
+ var err error
+ io := &specs.LinuxBlockIO{}
+ hasLimits := false
+ if b := c.BlkIOWeight; len(b) > 0 {
+ u, err := strconv.ParseUint(b, 10, 16)
if err != nil {
- return err
+ return nil, errors.Wrapf(err, "invalid value for blkio-weight")
}
+ nu := uint16(u)
+ io.Weight = &nu
+ hasLimits = true
}
- s.IDMappings, err = util.ParseIDMapping(ns.UsernsMode(c.UserNS), c.UIDMap, c.GIDMap, c.SubUIDName, c.SubGIDName)
- if err != nil {
- return err
+ if len(c.BlkIOWeightDevice) > 0 {
+ if err := parseWeightDevices(c.BlkIOWeightDevice, s); err != nil {
+ return nil, err
+ }
+ hasLimits = true
+ }
+
+ if bps := c.DeviceReadBPs; len(bps) > 0 {
+ if s.ThrottleReadBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
+ return nil, err
+ }
+ hasLimits = true
+ }
+
+ if bps := c.DeviceWriteBPs; len(bps) > 0 {
+ if s.ThrottleWriteBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
+ return nil, err
+ }
+ hasLimits = true
+ }
+
+ if iops := c.DeviceReadIOPs; len(iops) > 0 {
+ if s.ThrottleReadIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
+ return nil, err
+ }
+ hasLimits = true
+ }
+
+ if iops := c.DeviceWriteIOPs; len(iops) > 0 {
+ if s.ThrottleWriteIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
+ return nil, err
+ }
+ hasLimits = true
+ }
+
+ if !hasLimits {
+ return nil, nil
+ }
+ return io, nil
+}
+
+func getPidsLimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) (*specs.LinuxPids, error) {
+ pids := &specs.LinuxPids{}
+ hasLimits := false
+ if c.CGroupsMode == "disabled" && c.PIDsLimit > 0 {
+ return nil, nil
+ }
+ if c.PIDsLimit > 0 {
+ pids.Limit = c.PIDsLimit
+ hasLimits = true
+ }
+ if !hasLimits {
+ return nil, nil
}
+ return pids, nil
+}
+
+func getMemoryLimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) (*specs.LinuxMemory, error) {
+ var err error
+ memory := &specs.LinuxMemory{}
+ hasLimits := false
if m := c.Memory; len(m) > 0 {
ml, err := units.RAMInBytes(m)
if err != nil {
- return errors.Wrapf(err, "invalid value for memory")
+ return nil, errors.Wrapf(err, "invalid value for memory")
}
- s.ResourceLimits.Memory.Limit = &ml
+ memory.Limit = &ml
+ hasLimits = true
}
if m := c.MemoryReservation; len(m) > 0 {
mr, err := units.RAMInBytes(m)
if err != nil {
- return errors.Wrapf(err, "invalid value for memory")
+ return nil, errors.Wrapf(err, "invalid value for memory")
}
- s.ResourceLimits.Memory.Reservation = &mr
+ memory.Reservation = &mr
+ hasLimits = true
}
if m := c.MemorySwap; len(m) > 0 {
var ms int64
@@ -68,99 +160,106 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
} else {
ms, err = units.RAMInBytes(m)
if err != nil {
- return errors.Wrapf(err, "invalid value for memory")
+ return nil, errors.Wrapf(err, "invalid value for memory")
}
}
- s.ResourceLimits.Memory.Swap = &ms
+ memory.Swap = &ms
+ hasLimits = true
}
if m := c.KernelMemory; len(m) > 0 {
mk, err := units.RAMInBytes(m)
if err != nil {
- return errors.Wrapf(err, "invalid value for kernel-memory")
+ return nil, errors.Wrapf(err, "invalid value for kernel-memory")
}
- s.ResourceLimits.Memory.Kernel = &mk
+ memory.Kernel = &mk
+ hasLimits = true
}
- if b := c.BlkIOWeight; len(b) > 0 {
- u, err := strconv.ParseUint(b, 10, 16)
+ if c.MemorySwappiness >= 0 {
+ swappiness := uint64(c.MemorySwappiness)
+ memory.Swappiness = &swappiness
+ hasLimits = true
+ }
+ if c.OOMKillDisable {
+ memory.DisableOOMKiller = &c.OOMKillDisable
+ hasLimits = true
+ }
+ if !hasLimits {
+ return nil, nil
+ }
+ return memory, nil
+}
+
+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
+ }
+
+ s.User = c.User
+ inputCommand := args[1:]
+ if len(c.HealthCmd) > 0 {
+ if c.NoHealthCheck {
+ return errors.New("Cannot specify both --no-healthcheck and --health-cmd")
+ }
+ s.HealthConfig, err = makeHealthCheckFromCli(c.HealthCmd, c.HealthInterval, c.HealthRetries, c.HealthTimeout, c.HealthStartPeriod)
if err != nil {
- return errors.Wrapf(err, "invalid value for blkio-weight")
+ return err
+ }
+ } else if c.NoHealthCheck {
+ s.HealthConfig = &manifest.Schema2HealthConfig{
+ Test: []string{"NONE"},
}
- nu := uint16(u)
- s.ResourceLimits.BlockIO.Weight = &nu
}
- s.Terminal = c.TTY
- ep, err := ExposedPorts(c.Expose, c.Net.PublishPorts, c.PublishAll, nil)
+ userNS := ns.UsernsMode(c.UserNS)
+ s.IDMappings, err = util.ParseIDMapping(userNS, c.UIDMap, c.GIDMap, c.SubUIDName, c.SubGIDName)
if err != nil {
return err
}
- s.PortMappings = ep
+ // If some mappings are specified, assume a private user namespace
+ if userNS.IsDefaultValue() && (!s.IDMappings.HostUIDMapping || !s.IDMappings.HostGIDMapping) {
+ s.UserNS.NSMode = specgen.Private
+ }
+
+ s.Terminal = c.TTY
+
+ if err := verifyExpose(c.Expose); err != nil {
+ return err
+ }
+ // We are not handling the Expose flag yet.
+ // s.PortsExpose = c.Expose
+ s.PortMappings = c.Net.PublishPorts
+ s.PublishImagePorts = c.PublishAll
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
- // }
- //}
+ for k, v := range map[string]*specgen.Namespace{
+ c.IPC: &s.IpcNS,
+ c.PID: &s.PidNS,
+ c.UTS: &s.UtsNS,
+ c.CGroupsNS: &s.CgroupNS,
+ } {
+ if k != "" {
+ *v, err = specgen.ParseNamespace(k)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ // userns must be treated differently
+ if c.UserNS != "" {
+ s.UserNS, err = specgen.ParseUserNamespace(c.UserNS)
+ if err != nil {
+ return err
+ }
+ }
+ if c.Net != nil {
+ s.NetNS = c.Net.Network
+ }
// STOP SIGNAL
signalString := "TERM"
@@ -190,7 +289,23 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
if c.EnvHost {
env = envLib.Join(env, osEnv)
+ } else if c.HTTPProxy {
+ for _, envSpec := range []string{
+ "http_proxy",
+ "HTTP_PROXY",
+ "https_proxy",
+ "HTTPS_PROXY",
+ "ftp_proxy",
+ "FTP_PROXY",
+ "no_proxy",
+ "NO_PROXY",
+ } {
+ if v, ok := osEnv[envSpec]; ok {
+ env[envSpec] = v
+ }
+ }
}
+
// env-file overrides any previous variables
for _, f := range c.EnvFile {
fileEnv, err := envLib.ParseFile(f)
@@ -258,6 +373,8 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
var command []string
+ s.Entrypoint = entrypoint
+
// Build the command
// If we have an entry point, it goes first
if len(entrypoint) > 0 {
@@ -276,23 +393,27 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
}
// SHM Size
- shmSize, err := units.FromHumanSize(c.ShmSize)
- if err != nil {
- return errors.Wrapf(err, "unable to translate --shm-size")
+ if c.ShmSize != "" {
+ shmSize, err := units.FromHumanSize(c.ShmSize)
+ if err != nil {
+ return errors.Wrapf(err, "unable to translate --shm-size")
+ }
+ s.ShmSize = &shmSize
}
- s.ShmSize = &shmSize
s.HostAdd = c.Net.AddHosts
- s.DNSServer = c.Net.DNSServers
+ s.UseImageResolvConf = c.Net.UseImageResolvConf
+ s.DNSServers = 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.DNSOptions = c.Net.DNSOptions
+ s.StaticIP = c.Net.StaticIP
+ s.StaticMAC = c.Net.StaticMAC
+ s.UseImageHosts = c.Net.NoHosts
s.ImageVolumeMode = c.ImageVolume
+ if s.ImageVolumeMode == "bind" {
+ s.ImageVolumeMode = "anonymous"
+ }
+
systemd := c.SystemdD == "always"
if !systemd && command != nil {
x, err := strconv.ParseBool(c.SystemdD)
@@ -312,14 +433,28 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
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, err = getMemoryLimits(s, c, args)
+ if err != nil {
+ return err
+ }
+ s.ResourceLimits.BlockIO, err = getIOLimits(s, c, args)
+ if err != nil {
+ return err
+ }
+ s.ResourceLimits.Pids, err = getPidsLimits(s, c, args)
+ if err != nil {
+ return err
+ }
+ s.ResourceLimits.CPU, err = getCPULimits(s, c, args)
+ if err != nil {
+ return err
+ }
+ if s.ResourceLimits.CPU == nil && s.ResourceLimits.Pids == nil && s.ResourceLimits.BlockIO == nil && s.ResourceLimits.Memory == nil {
+ s.ResourceLimits = nil
}
- s.ResourceLimits.Memory.Swappiness = &swappiness
if s.LogConfiguration == nil {
s.LogConfiguration = &specgen.LogConfig{}
@@ -328,35 +463,11 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
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"),
- //}
+ s.CgroupParent = c.CGroupParent
+ s.CgroupsMode = c.CGroupsMode
+ s.Groups = c.GroupAdd
+ s.Hostname = c.Hostname
sysctl := map[string]string{}
if ctl := c.Sysctl; len(ctl) > 0 {
sysctl, err = util.ValidateSysctls(ctl)
@@ -374,7 +485,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
// TODO
// ouitside of specgen and oci though
// defaults to true, check spec/storage
- //s.readon = c.ReadOnlyTmpFS
+ // s.readon = c.ReadOnlyTmpFS
// TODO convert to map?
// check if key=value and convert
sysmap := make(map[string]string)
@@ -410,26 +521,31 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
}
}
- // 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.SeccompPolicy = c.SeccompPolicy
- //s.Mounts = c.Mount
+ // TODO: should parse out options
s.VolumesFrom = c.VolumesFrom
+ // Only add read-only tmpfs mounts in case that we are read-only and the
+ // read-only tmpfs flag has been set.
+ mounts, volumes, err := parseVolumes(c.Volume, c.Mount, c.TmpFS, c.ReadOnlyTmpFS && c.ReadOnly)
+ if err != nil {
+ return err
+ }
+ s.Mounts = mounts
+ s.Volumes = volumes
+
// TODO any idea why this was done
- //devices := rtc.Containers.Devices
+ // devices := rtc.Containers.Devices
// TODO conflict on populate?
//
- //if c.Changed("device") {
+ // if c.Changed("device") {
// devices = append(devices, c.StringSlice("device")...)
- //}
+ // }
+
+ for _, dev := range c.Devices {
+ s.Devices = append(s.Devices, specs.LinuxDevice{Path: dev})
+ }
// TODO things i cannot find in spec
// we dont think these are in the spec
@@ -437,33 +553,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
// 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
+ // DeviceCgroupRules: c.StringSlice("device-cgroup-rule"),
// Rlimits/Ulimits
for _, u := range c.Ulimit {
@@ -483,10 +573,10 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
s.Rlimits = append(s.Rlimits, rl)
}
- //Tmpfs: c.StringArray("tmpfs"),
+ // Tmpfs: c.StringArray("tmpfs"),
// TODO how to handle this?
- //Syslog: c.Bool("syslog"),
+ // Syslog: c.Bool("syslog"),
logOpts := make(map[string]string)
for _, o := range c.LogOptions {
@@ -494,37 +584,25 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
if len(split) < 2 {
return errors.Errorf("invalid log option %q", o)
}
- logOpts[split[0]] = split[1]
+ switch {
+ case split[0] == "driver":
+ s.LogConfiguration.Driver = split[1]
+ case split[0] == "path":
+ s.LogConfiguration.Path = split[1]
+ default:
+ 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) {
+ // func verifyContainerResources(config *cc.CreateConfig, update bool) ([]string, error) {
return nil
}
@@ -536,10 +614,15 @@ func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, start
// 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}
+
+ if inCmd == "none" {
+ cmd = []string{"NONE"}
+ } else {
+ 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,
diff --git a/cmd/podman/common/volumes.go b/cmd/podman/common/volumes.go
new file mode 100644
index 000000000..6b0b6e9cf
--- /dev/null
+++ b/cmd/podman/common/volumes.go
@@ -0,0 +1,569 @@
+package common
+
+import (
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/buildah/pkg/parse"
+ "github.com/containers/libpod/pkg/specgen"
+ "github.com/containers/libpod/pkg/util"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ // TypeBind is the type for mounting host dir
+ TypeBind = "bind"
+ // TypeVolume is the type for named volumes
+ TypeVolume = "volume"
+ // TypeTmpfs is the type for mounting tmpfs
+ TypeTmpfs = "tmpfs"
+)
+
+var (
+ errDuplicateDest = errors.Errorf("duplicate mount destination")
+ optionArgError = errors.Errorf("must provide an argument for option")
+ noDestError = errors.Errorf("must set volume destination")
+)
+
+// Parse all volume-related options in the create config into a set of mounts
+// and named volumes to add to the container.
+// Handles --volumes, --mount, and --tmpfs flags.
+// Does not handle image volumes, init, and --volumes-from flags.
+// Can also add tmpfs mounts from read-only tmpfs.
+// TODO: handle options parsing/processing via containers/storage/pkg/mount
+func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bool) ([]spec.Mount, []*specgen.NamedVolume, error) {
+ // Get mounts from the --mounts flag.
+ unifiedMounts, unifiedVolumes, err := getMounts(mountFlag)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Next --volumes flag.
+ volumeMounts, volumeVolumes, err := getVolumeMounts(volumeFlag)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Next --tmpfs flag.
+ tmpfsMounts, err := getTmpfsMounts(tmpfsFlag)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Unify mounts from --mount, --volume, --tmpfs.
+ // Start with --volume.
+ for dest, mount := range volumeMounts {
+ if _, ok := unifiedMounts[dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ }
+ unifiedMounts[dest] = mount
+ }
+ for dest, volume := range volumeVolumes {
+ if _, ok := unifiedVolumes[dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ }
+ unifiedVolumes[dest] = volume
+ }
+ // Now --tmpfs
+ for dest, tmpfs := range tmpfsMounts {
+ if _, ok := unifiedMounts[dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ }
+ unifiedMounts[dest] = tmpfs
+ }
+
+ // If requested, add tmpfs filesystems for read-only containers.
+ if addReadOnlyTmpfs {
+ readonlyTmpfs := []string{"/tmp", "/var/tmp", "/run"}
+ options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"}
+ for _, dest := range readonlyTmpfs {
+ if _, ok := unifiedMounts[dest]; ok {
+ continue
+ }
+ if _, ok := unifiedVolumes[dest]; ok {
+ continue
+ }
+ localOpts := options
+ if dest == "/run" {
+ localOpts = append(localOpts, "noexec", "size=65536k")
+ } else {
+ localOpts = append(localOpts, "exec")
+ }
+ unifiedMounts[dest] = spec.Mount{
+ Destination: dest,
+ Type: TypeTmpfs,
+ Source: "tmpfs",
+ Options: localOpts,
+ }
+ }
+ }
+
+ // Check for conflicts between named volumes and mounts
+ for dest := range unifiedMounts {
+ if _, ok := unifiedVolumes[dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
+ }
+ }
+ for dest := range unifiedVolumes {
+ if _, ok := unifiedMounts[dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
+ }
+ }
+
+ // Final step: maps to arrays
+ finalMounts := make([]spec.Mount, 0, len(unifiedMounts))
+ for _, mount := range unifiedMounts {
+ if mount.Type == TypeBind {
+ absSrc, err := filepath.Abs(mount.Source)
+ if err != nil {
+ return nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source)
+ }
+ mount.Source = absSrc
+ }
+ finalMounts = append(finalMounts, mount)
+ }
+ finalVolumes := make([]*specgen.NamedVolume, 0, len(unifiedVolumes))
+ for _, volume := range unifiedVolumes {
+ finalVolumes = append(finalVolumes, volume)
+ }
+
+ return finalMounts, finalVolumes, nil
+}
+
+// getMounts takes user-provided input from the --mount flag and creates OCI
+// spec mounts and Libpod named volumes.
+// podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
+// podman run --mount type=tmpfs,target=/dev/shm ...
+// podman run --mount type=volume,source=test-volume, ...
+func getMounts(mountFlag []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) {
+ finalMounts := make(map[string]spec.Mount)
+ finalNamedVolumes := make(map[string]*specgen.NamedVolume)
+
+ errInvalidSyntax := errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,options]")
+
+ // TODO(vrothberg): the manual parsing can be replaced with a regular expression
+ // to allow a more robust parsing of the mount format and to give
+ // precise errors regarding supported format versus supported options.
+ for _, mount := range mountFlag {
+ arr := strings.SplitN(mount, ",", 2)
+ if len(arr) < 2 {
+ return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount)
+ }
+ kv := strings.Split(arr[0], "=")
+ // TODO: type is not explicitly required in Docker.
+ // If not specified, it defaults to "volume".
+ if len(kv) != 2 || kv[0] != "type" {
+ return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount)
+ }
+
+ tokens := strings.Split(arr[1], ",")
+ switch kv[1] {
+ case TypeBind:
+ mount, err := getBindMount(tokens)
+ if err != nil {
+ return nil, nil, err
+ }
+ if _, ok := finalMounts[mount.Destination]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination)
+ }
+ finalMounts[mount.Destination] = mount
+ case TypeTmpfs:
+ mount, err := getTmpfsMount(tokens)
+ if err != nil {
+ return nil, nil, err
+ }
+ if _, ok := finalMounts[mount.Destination]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination)
+ }
+ finalMounts[mount.Destination] = mount
+ case "volume":
+ volume, err := getNamedVolume(tokens)
+ if err != nil {
+ return nil, nil, err
+ }
+ if _, ok := finalNamedVolumes[volume.Dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, volume.Dest)
+ }
+ finalNamedVolumes[volume.Dest] = volume
+ default:
+ return nil, nil, errors.Errorf("invalid filesystem type %q", kv[1])
+ }
+ }
+
+ return finalMounts, finalNamedVolumes, nil
+}
+
+// Parse a single bind mount entry from the --mount flag.
+func getBindMount(args []string) (spec.Mount, error) {
+ newMount := spec.Mount{
+ Type: TypeBind,
+ }
+
+ var setSource, setDest, setRORW, setSuid, setDev, setExec, setRelabel bool
+
+ for _, val := range args {
+ kv := strings.Split(val, "=")
+ switch kv[0] {
+ case "bind-nonrecursive":
+ newMount.Options = append(newMount.Options, "bind")
+ case "ro", "rw":
+ if setRORW {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' or 'rw' options more than once")
+ }
+ setRORW = true
+ // Can be formatted as one of:
+ // ro
+ // ro=[true|false]
+ // rw
+ // rw=[true|false]
+ switch len(kv) {
+ case 1:
+ newMount.Options = append(newMount.Options, kv[0])
+ case 2:
+ switch strings.ToLower(kv[1]) {
+ case "true":
+ newMount.Options = append(newMount.Options, kv[0])
+ case "false":
+ // Set the opposite only for rw
+ // ro's opposite is the default
+ if kv[0] == "rw" {
+ newMount.Options = append(newMount.Options, "ro")
+ }
+ default:
+ return newMount, errors.Wrapf(optionArgError, "%s must be set to true or false, instead received %q", kv[0], kv[1])
+ }
+ default:
+ return newMount, errors.Wrapf(optionArgError, "badly formatted option %q", val)
+ }
+ case "nosuid", "suid":
+ if setSuid {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once")
+ }
+ setSuid = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "nodev", "dev":
+ if setDev {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once")
+ }
+ setDev = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "noexec", "exec":
+ if setExec {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once")
+ }
+ setExec = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z":
+ newMount.Options = append(newMount.Options, kv[0])
+ case "bind-propagation":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ newMount.Options = append(newMount.Options, kv[1])
+ case "src", "source":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := parse.ValidateVolumeHostDir(kv[1]); err != nil {
+ return newMount, err
+ }
+ newMount.Source = kv[1]
+ setSource = true
+ case "target", "dst", "destination":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
+ return newMount, err
+ }
+ newMount.Destination = filepath.Clean(kv[1])
+ setDest = true
+ case "relabel":
+ if setRelabel {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'relabel' option more than once")
+ }
+ setRelabel = true
+ if len(kv) != 2 {
+ return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0])
+ }
+ switch kv[1] {
+ case "private":
+ newMount.Options = append(newMount.Options, "z")
+ case "shared":
+ newMount.Options = append(newMount.Options, "Z")
+ default:
+ return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0])
+ }
+ default:
+ return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ }
+ }
+
+ if !setDest {
+ return newMount, noDestError
+ }
+
+ if !setSource {
+ newMount.Source = newMount.Destination
+ }
+
+ options, err := parse.ValidateVolumeOpts(newMount.Options)
+ if err != nil {
+ return newMount, err
+ }
+ newMount.Options = options
+ return newMount, nil
+}
+
+// Parse a single tmpfs mount entry from the --mount flag
+func getTmpfsMount(args []string) (spec.Mount, error) {
+ newMount := spec.Mount{
+ Type: TypeTmpfs,
+ Source: TypeTmpfs,
+ }
+
+ var setDest, setRORW, setSuid, setDev, setExec, setTmpcopyup bool
+
+ for _, val := range args {
+ kv := strings.Split(val, "=")
+ switch kv[0] {
+ case "tmpcopyup", "notmpcopyup":
+ if setTmpcopyup {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'tmpcopyup' and 'notmpcopyup' options more than once")
+ }
+ setTmpcopyup = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "ro", "rw":
+ if setRORW {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once")
+ }
+ setRORW = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "nosuid", "suid":
+ if setSuid {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once")
+ }
+ setSuid = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "nodev", "dev":
+ if setDev {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once")
+ }
+ setDev = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "noexec", "exec":
+ if setExec {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once")
+ }
+ setExec = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "tmpfs-mode":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ newMount.Options = append(newMount.Options, fmt.Sprintf("mode=%s", kv[1]))
+ case "tmpfs-size":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ newMount.Options = append(newMount.Options, fmt.Sprintf("size=%s", kv[1]))
+ case "src", "source":
+ return newMount, errors.Errorf("source is not supported with tmpfs mounts")
+ case "target", "dst", "destination":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
+ return newMount, err
+ }
+ newMount.Destination = filepath.Clean(kv[1])
+ setDest = true
+ default:
+ return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ }
+ }
+
+ if !setDest {
+ return newMount, noDestError
+ }
+
+ return newMount, nil
+}
+
+// Parse a single volume mount entry from the --mount flag.
+// Note that the volume-label option for named volumes is currently NOT supported.
+// TODO: add support for --volume-label
+func getNamedVolume(args []string) (*specgen.NamedVolume, error) {
+ newVolume := new(specgen.NamedVolume)
+
+ var setSource, setDest, setRORW, setSuid, setDev, setExec bool
+
+ for _, val := range args {
+ kv := strings.Split(val, "=")
+ switch kv[0] {
+ case "ro", "rw":
+ if setRORW {
+ return nil, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once")
+ }
+ setRORW = true
+ newVolume.Options = append(newVolume.Options, kv[0])
+ case "nosuid", "suid":
+ if setSuid {
+ return nil, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once")
+ }
+ setSuid = true
+ newVolume.Options = append(newVolume.Options, kv[0])
+ case "nodev", "dev":
+ if setDev {
+ return nil, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once")
+ }
+ setDev = true
+ newVolume.Options = append(newVolume.Options, kv[0])
+ case "noexec", "exec":
+ if setExec {
+ return nil, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once")
+ }
+ setExec = true
+ newVolume.Options = append(newVolume.Options, kv[0])
+ case "volume-label":
+ return nil, errors.Errorf("the --volume-label option is not presently implemented")
+ case "src", "source":
+ if len(kv) == 1 {
+ return nil, errors.Wrapf(optionArgError, kv[0])
+ }
+ newVolume.Name = kv[1]
+ setSource = true
+ case "target", "dst", "destination":
+ if len(kv) == 1 {
+ return nil, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
+ return nil, err
+ }
+ newVolume.Dest = filepath.Clean(kv[1])
+ setDest = true
+ default:
+ return nil, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ }
+ }
+
+ if !setSource {
+ return nil, errors.Errorf("must set source volume")
+ }
+ if !setDest {
+ return nil, noDestError
+ }
+
+ return newVolume, nil
+}
+
+func getVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) {
+ mounts := make(map[string]spec.Mount)
+ volumes := make(map[string]*specgen.NamedVolume)
+
+ volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]")
+
+ for _, vol := range volumeFlag {
+ var (
+ options []string
+ src string
+ dest string
+ err error
+ )
+
+ splitVol := strings.Split(vol, ":")
+ if len(splitVol) > 3 {
+ return nil, nil, errors.Wrapf(volumeFormatErr, vol)
+ }
+
+ src = splitVol[0]
+ if len(splitVol) == 1 {
+ // This is an anonymous named volume. Only thing given
+ // is destination.
+ // Name/source will be blank, and populated by libpod.
+ src = ""
+ dest = splitVol[0]
+ } else if len(splitVol) > 1 {
+ dest = splitVol[1]
+ }
+ if len(splitVol) > 2 {
+ if options, err = parse.ValidateVolumeOpts(strings.Split(splitVol[2], ",")); err != nil {
+ return nil, nil, err
+ }
+ }
+
+ // Do not check source dir for anonymous volumes
+ if len(splitVol) > 1 {
+ if err := parse.ValidateVolumeHostDir(src); err != nil {
+ return nil, nil, err
+ }
+ }
+ if err := parse.ValidateVolumeCtrDir(dest); err != nil {
+ return nil, nil, err
+ }
+
+ cleanDest := filepath.Clean(dest)
+
+ if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") {
+ // This is not a named volume
+ newMount := spec.Mount{
+ Destination: cleanDest,
+ Type: string(TypeBind),
+ Source: src,
+ Options: options,
+ }
+ if _, ok := mounts[newMount.Destination]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination)
+ }
+ mounts[newMount.Destination] = newMount
+ } else {
+ // This is a named volume
+ newNamedVol := new(specgen.NamedVolume)
+ newNamedVol.Name = src
+ newNamedVol.Dest = cleanDest
+ newNamedVol.Options = options
+
+ if _, ok := volumes[newNamedVol.Dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, newNamedVol.Dest)
+ }
+ volumes[newNamedVol.Dest] = newNamedVol
+ }
+
+ logrus.Debugf("User mount %s:%s options %v", src, dest, options)
+ }
+
+ return mounts, volumes, nil
+}
+
+// GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts
+func getTmpfsMounts(tmpfsFlag []string) (map[string]spec.Mount, error) {
+ m := make(map[string]spec.Mount)
+ for _, i := range tmpfsFlag {
+ // Default options if nothing passed
+ var options []string
+ spliti := strings.Split(i, ":")
+ destPath := spliti[0]
+ if err := parse.ValidateVolumeCtrDir(spliti[0]); err != nil {
+ return nil, err
+ }
+ if len(spliti) > 1 {
+ options = strings.Split(spliti[1], ",")
+ }
+
+ if _, ok := m[destPath]; ok {
+ return nil, errors.Wrapf(errDuplicateDest, destPath)
+ }
+
+ mount := spec.Mount{
+ Destination: filepath.Clean(destPath),
+ Type: string(TypeTmpfs),
+ Options: options,
+ Source: string(TypeTmpfs),
+ }
+ m[destPath] = mount
+ }
+ return m, nil
+}
diff --git a/cmd/podman/containers/attach.go b/cmd/podman/containers/attach.go
index 700be1f84..ee4d811d7 100644
--- a/cmd/podman/containers/attach.go
+++ b/cmd/podman/containers/attach.go
@@ -3,11 +3,11 @@ 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"
+ "github.com/spf13/pflag"
)
var (
@@ -27,19 +27,24 @@ var (
podman attach 1234
podman attach --no-stdin foobar`,
}
+
+ containerAttachCommand = &cobra.Command{
+ Use: attachCommand.Use,
+ Short: attachCommand.Short,
+ Long: attachCommand.Long,
+ RunE: attachCommand.RunE,
+ Example: `podman container attach ctrID
+ podman container attach 1234
+ podman container 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 `_`")
+func attachFlags(flags *pflag.FlagSet) {
+ flags.StringVar(&attachOpts.DetachKeys, "detach-keys", containerConfig.DetachKeys(), "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")
@@ -48,12 +53,36 @@ func init() {
}
}
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: attachCommand,
+ })
+ flags := attachCommand.Flags()
+ attachFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: containerAttachCommand,
+ Parent: containerCmd,
+ })
+ containerAttachFlags := containerAttachCommand.Flags()
+ attachFlags(containerAttachFlags)
+}
+
func attach(cmd *cobra.Command, args []string) error {
+ if len(args) > 1 || (len(args) == 0 && !attachOpts.Latest) {
+ return errors.Errorf("attach requires the name or id of one running container or the latest flag")
+ }
+ var name string
+ if len(args) > 0 {
+ name = args[0]
+ }
attachOpts.Stdin = os.Stdin
if attachOpts.NoStdin {
attachOpts.Stdin = nil
}
attachOpts.Stdout = os.Stdout
attachOpts.Stderr = os.Stderr
- return registry.ContainerEngine().ContainerAttach(registry.GetContext(), args[0], attachOpts)
+ return registry.ContainerEngine().ContainerAttach(registry.GetContext(), name, attachOpts)
}
diff --git a/cmd/podman/containers/commit.go b/cmd/podman/containers/commit.go
index eaba07981..137e486eb 100644
--- a/cmd/podman/containers/commit.go
+++ b/cmd/podman/containers/commit.go
@@ -11,6 +11,7 @@ import (
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -28,6 +29,17 @@ var (
podman commit containerID`,
}
+ containerCommitCommand = &cobra.Command{
+ Use: commitCommand.Use,
+ Short: commitCommand.Short,
+ Long: commitCommand.Long,
+ RunE: commitCommand.RunE,
+ Example: `podman container commit -q --message "committing container to image" reverent_golick image-committed
+ podman container commit -q --author "firstName lastName" reverent_golick image-committed
+ podman container commit -q --pause=false containerID image-committed
+ podman container 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"}
)
@@ -39,12 +51,7 @@ var (
iidFile string
)
-func init() {
- registry.Commands = append(registry.Commands, registry.CliCommand{
- Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
- Command: commitCommand,
- })
- flags := commitCommand.Flags()
+func commitFlags(flags *pflag.FlagSet) {
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")
@@ -53,8 +60,25 @@ func init() {
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 init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: commitCommand,
+ })
+ flags := commitCommand.Flags()
+ commitFlags(flags)
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerCommitCommand,
+ Parent: containerCmd,
+ })
+ containerCommitFlags := containerCommitCommand.Flags()
+ commitFlags(containerCommitFlags)
}
+
func commit(cmd *cobra.Command, args []string) error {
container := args[0]
if len(args) > 1 {
diff --git a/cmd/podman/containers/container.go b/cmd/podman/containers/container.go
index 8564b23f4..a102318fb 100644
--- a/cmd/podman/containers/container.go
+++ b/cmd/podman/containers/container.go
@@ -1,26 +1,27 @@
package containers
import (
- "os"
-
- "github.com/containers/common/pkg/config"
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
- "github.com/sirupsen/logrus"
+ "github.com/containers/libpod/pkg/util"
"github.com/spf13/cobra"
)
var (
+ // Pull in configured json library
+ json = registry.JsonLibrary()
+
// Command: podman _container_
containerCmd = &cobra.Command{
Use: "container",
Short: "Manage containers",
Long: "Manage containers",
TraverseChildren: true,
- RunE: registry.SubCommandExists,
+ RunE: validate.SubCommandExists,
}
- defaultContainerConfig = getDefaultContainerConfig()
+ containerConfig = util.DefaultContainerConfig()
)
func init() {
@@ -29,12 +30,3 @@ func init() {
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/cp.go b/cmd/podman/containers/cp.go
new file mode 100644
index 000000000..f0f9a158d
--- /dev/null
+++ b/cmd/podman/containers/cp.go
@@ -0,0 +1,55 @@
+package containers
+
+import (
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/cgroups"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/rootless"
+ "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+)
+
+var (
+ 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,
+ Args: cobra.ExactArgs(2),
+ RunE: cp,
+ Example: "podman cp [CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH",
+ }
+)
+
+var (
+ cpOpts entities.ContainerCpOptions
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: cpCommand,
+ })
+ flags := cpCommand.Flags()
+ flags.BoolVar(&cpOpts.Extract, "extract", false, "Extract the tar file into the destination directory.")
+ flags.BoolVar(&cpOpts.Pause, "pause", copyPause(), "Pause the container while copying")
+}
+
+func cp(cmd *cobra.Command, args []string) error {
+ _, err := registry.ContainerEngine().ContainerCp(registry.GetContext(), args[0], args[1], cpOpts)
+ return err
+}
+
+func copyPause() bool {
+ if 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/containers/create.go b/cmd/podman/containers/create.go
index 292d5c1ad..3e47a8b4f 100644
--- a/cmd/podman/containers/create.go
+++ b/cmd/podman/containers/create.go
@@ -2,14 +2,19 @@ package containers
import (
"fmt"
+ "os"
+ "github.com/containers/common/pkg/config"
"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/errorhandling"
"github.com/containers/libpod/pkg/specgen"
+ "github.com/containers/libpod/pkg/util"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -26,43 +31,76 @@ var (
podman create --annotation HELLO=WORLD alpine ls
podman create -t -i --name myctr alpine ls`,
}
+
+ containerCreateCommand = &cobra.Command{
+ Use: createCommand.Use,
+ Short: createCommand.Short,
+ Long: createCommand.Long,
+ RunE: createCommand.RunE,
+ Example: `podman container create alpine ls
+ podman container create --annotation HELLO=WORLD alpine ls
+ podman container create -t -i --name myctr alpine ls`,
+ }
)
var (
cliVals common.ContainerCLIOpts
)
+func createFlags(flags *pflag.FlagSet) {
+ flags.SetInterspersed(false)
+ flags.AddFlagSet(common.GetCreateFlags(&cliVals))
+ flags.AddFlagSet(common.GetNetFlags())
+ flags.SetNormalizeFunc(common.AliasFlags)
+}
+
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: createCommand,
})
- //common.GetCreateFlags(createCommand)
+ // common.GetCreateFlags(createCommand)
flags := createCommand.Flags()
- flags.SetInterspersed(false)
- flags.AddFlagSet(common.GetCreateFlags(&cliVals))
- flags.AddFlagSet(common.GetNetFlags())
- flags.SetNormalizeFunc(common.AliasFlags)
+ createFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerCreateCommand,
+ Parent: containerCmd,
+ })
+
+ containerCreateFlags := containerCreateCommand.Flags()
+ createFlags(containerCreateFlags)
}
func create(cmd *cobra.Command, args []string) error {
var (
- err error
- rawImageInput string
+ err error
)
cliVals.Net, err = common.NetFlagsToNetOptions(cmd)
if err != nil {
return err
}
- if rfs := cliVals.RootFS; !rfs {
- rawImageInput = args[0]
+ cidFile, err := openCidFile(cliVals.CIDFile)
+ if err != nil {
+ return err
+ }
+
+ if cidFile != nil {
+ defer errorhandling.CloseQuiet(cidFile)
+ defer errorhandling.SyncQuiet(cidFile)
}
if err := createInit(cmd); err != nil {
return err
}
- //TODO rootfs still
- s := specgen.NewSpecGenerator(rawImageInput)
+
+ if !cliVals.RootFS {
+ if err := pullImage(args[0]); err != nil {
+ return err
+ }
+ }
+ s := specgen.NewSpecGenerator(args[0], cliVals.RootFS)
if err := common.FillOutSpecGen(s, &cliVals, args); err != nil {
return err
}
@@ -71,6 +109,14 @@ func create(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
+
+ if cidFile != nil {
+ _, err = cidFile.WriteString(report.Id)
+ if err != nil {
+ logrus.Error(err)
+ }
+ }
+
fmt.Println(report.Id)
return nil
}
@@ -80,6 +126,10 @@ func createInit(c *cobra.Command) error {
logrus.Warn("setting security options with --privileged has no effect")
}
+ if c.Flag("shm-size").Changed {
+ cliVals.ShmSize = c.Flag("shm-size").Value.String()
+ }
+
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.")
}
@@ -94,9 +144,62 @@ func createInit(c *cobra.Command) error {
if c.Flag("no-hosts").Changed && c.Flag("add-host").Changed {
return errors.Errorf("--no-hosts and --add-host cannot be set together")
}
+ if c.Flag("userns").Changed {
+ cliVals.UserNS = c.Flag("userns").Value.String()
+ }
+ if c.Flag("ipc").Changed {
+ cliVals.IPC = c.Flag("ipc").Value.String()
+ }
+ if c.Flag("uts").Changed {
+ cliVals.UTS = c.Flag("uts").Value.String()
+ }
+ if c.Flag("pid").Changed {
+ cliVals.PID = c.Flag("pid").Value.String()
+ }
+ if c.Flag("cgroupns").Changed {
+ cliVals.CGroupsNS = c.Flag("cgroupns").Value.String()
+ }
// Docker-compatibility: the "-h" flag for run/create is reserved for
// the hostname (see https://github.com/containers/libpod/issues/1367).
return nil
}
+
+func pullImage(imageName string) error {
+ br, err := registry.ImageEngine().Exists(registry.GetContext(), imageName)
+ 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(), imageName, entities.ImagePullOptions{
+ Authfile: cliVals.Authfile,
+ Quiet: cliVals.Quiet,
+ })
+ if pullErr != nil {
+ return pullErr
+ }
+ }
+ return nil
+}
+
+func openCidFile(cidfile string) (*os.File, error) {
+ if cidfile == "" {
+ return nil, nil
+ }
+ cidFile, err := util.OpenExclusiveFile(cidfile)
+ if err != nil && os.IsExist(err) {
+ return nil, errors.Errorf("container id file exists. Ensure another container is not using it or delete %s", cidfile)
+ }
+ if err != nil {
+ return nil, errors.Errorf("error opening cidfile %s", cidfile)
+ }
+ return cidFile, nil
+}
diff --git a/cmd/podman/containers/diff.go b/cmd/podman/containers/diff.go
index ebc0d8ea1..59b788010 100644
--- a/cmd/podman/containers/diff.go
+++ b/cmd/podman/containers/diff.go
@@ -3,6 +3,7 @@ package containers
import (
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/cmd/podman/report"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@@ -12,7 +13,7 @@ var (
// podman container _diff_
diffCmd = &cobra.Command{
Use: "diff [flags] CONTAINER",
- Args: registry.IdOrLatestArgs,
+ Args: validate.IdOrLatestArgs,
Short: "Inspect changes on container's file systems",
Long: `Displays changes on a container filesystem. The container will be compared to its parent layer.`,
RunE: diff,
@@ -45,7 +46,11 @@ func diff(cmd *cobra.Command, args []string) error {
return errors.New("container must be specified: podman container diff [options [...]] ID-NAME")
}
- results, err := registry.ContainerEngine().ContainerDiff(registry.GetContext(), args[0], entities.DiffOptions{})
+ var id string
+ if len(args) > 0 {
+ id = args[0]
+ }
+ results, err := registry.ContainerEngine().ContainerDiff(registry.GetContext(), id, *diffOpts)
if err != nil {
return err
}
diff --git a/cmd/podman/containers/exec.go b/cmd/podman/containers/exec.go
index 68ecb2196..2bff8ae33 100644
--- a/cmd/podman/containers/exec.go
+++ b/cmd/podman/containers/exec.go
@@ -4,12 +4,12 @@ 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"
+ "github.com/spf13/pflag"
)
var (
@@ -24,6 +24,16 @@ var (
podman exec -it -w /tmp myCtr pwd
podman exec --user root ctrID ls`,
}
+
+ containerExecCommand = &cobra.Command{
+ Use: execCommand.Use,
+ Short: execCommand.Short,
+ Long: execCommand.Long,
+ RunE: execCommand.RunE,
+ Example: `podman container exec -it ctrID ls
+ podman container exec -it -w /tmp myCtr pwd
+ podman container exec --user root ctrID ls`,
+ }
)
var (
@@ -31,14 +41,9 @@ var (
execOpts entities.ExecOptions
)
-func init() {
- registry.Commands = append(registry.Commands, registry.CliCommand{
- Mode: []entities.EngineMode{entities.ABIMode},
- Command: execCommand,
- })
- flags := execCommand.Flags()
+func execFlags(flags *pflag.FlagSet) {
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.StringVar(&execOpts.DetachKeys, "detach-keys", containerConfig.DetachKeys(), "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")
@@ -52,8 +57,26 @@ func init() {
_ = flags.MarkHidden("latest")
_ = flags.MarkHidden("preserve-fds")
}
+}
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: execCommand,
+ })
+ flags := execCommand.Flags()
+ execFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: containerExecCommand,
+ Parent: containerCmd,
+ })
+
+ containerExecFlags := containerExecCommand.Flags()
+ execFlags(containerExecFlags)
}
+
func exec(cmd *cobra.Command, args []string) error {
var nameOrId string
execOpts.Cmd = args
diff --git a/cmd/podman/containers/exists.go b/cmd/podman/containers/exists.go
index f1bc09f78..81ba8a282 100644
--- a/cmd/podman/containers/exists.go
+++ b/cmd/podman/containers/exists.go
@@ -2,7 +2,6 @@ package containers
import (
"context"
- "os"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
@@ -18,8 +17,9 @@ var (
Long: containerExistsDescription,
Example: `podman container exists containerID
podman container exists myctr || podman run --name myctr [etc...]`,
- RunE: exists,
- Args: cobra.ExactArgs(1),
+ RunE: exists,
+ Args: cobra.ExactArgs(1),
+ DisableFlagsInUseLine: true,
}
)
@@ -37,7 +37,7 @@ func exists(cmd *cobra.Command, args []string) error {
return err
}
if !response.Value {
- os.Exit(1)
+ registry.SetExitCode(1)
}
return nil
}
diff --git a/cmd/podman/containers/export.go b/cmd/podman/containers/export.go
index 5110812d1..fb5bd468f 100644
--- a/cmd/podman/containers/export.go
+++ b/cmd/podman/containers/export.go
@@ -9,6 +9,7 @@ import (
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
"golang.org/x/crypto/ssh/terminal"
)
@@ -25,19 +26,41 @@ var (
Example: `podman export ctrID > myCtr.tar
podman export --output="myCtr.tar" ctrID`,
}
+
+ containerExportCommand = &cobra.Command{
+ Use: exportCommand.Use,
+ Short: exportCommand.Short,
+ Long: exportCommand.Long,
+ RunE: exportCommand.RunE,
+ Example: `podman container export ctrID > myCtr.tar
+ podman container export --output="myCtr.tar" ctrID`,
+ }
)
var (
exportOpts entities.ContainerExportOptions
)
+func exportFlags(flags *pflag.FlagSet) {
+ flags.StringVarP(&exportOpts.Output, "output", "o", "", "Write to a specified file (default: stdout, which must be redirected)")
+}
+
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)")
+ exportFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerExportCommand,
+ Parent: containerCmd,
+ })
+
+ containerExportFlags := containerExportCommand.Flags()
+ exportFlags(containerExportFlags)
}
func export(cmd *cobra.Command, args []string) error {
diff --git a/cmd/podman/containers/inspect.go b/cmd/podman/containers/inspect.go
index 8d591832b..4549a4ef6 100644
--- a/cmd/podman/containers/inspect.go
+++ b/cmd/podman/containers/inspect.go
@@ -1,17 +1,9 @@
package containers
import (
- "context"
- "fmt"
- "os"
- "strings"
- "text/template"
-
- "github.com/containers/libpod/cmd/podman/common"
+ "github.com/containers/libpod/cmd/podman/inspect"
"github.com/containers/libpod/cmd/podman/registry"
-
"github.com/containers/libpod/pkg/domain/entities"
- json "github.com/json-iterator/go"
"github.com/spf13/cobra"
)
@@ -21,7 +13,7 @@ var (
Use: "inspect [flags] CONTAINER",
Short: "Display the configuration of a container",
Long: `Displays the low-level information on a container identified by name or ID.`,
- RunE: inspect,
+ RunE: inspectExec,
Example: `podman container inspect myCtr
podman container inspect -l --format '{{.Id}} {{.Config.Labels}}'`,
}
@@ -34,45 +26,9 @@ func init() {
Command: inspectCmd,
Parent: containerCmd,
})
- inspectOpts = common.AddInspectFlagSet(inspectCmd)
- flags := inspectCmd.Flags()
-
- if !registry.IsRemote() {
- flags.BoolVarP(&inspectOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
- }
-
-}
-
-func inspect(cmd *cobra.Command, args []string) error {
- responses, err := registry.ContainerEngine().ContainerInspect(context.Background(), args, *inspectOpts)
- if err != nil {
- return err
- }
- if inspectOpts.Format == "" {
- b, err := json.MarshalIndent(responses, "", " ")
- if err != nil {
- return err
- }
- fmt.Println(string(b))
- return nil
- }
- format := inspectOpts.Format
- if !strings.HasSuffix(format, "\n") {
- format += "\n"
- }
- tmpl, err := template.New("inspect").Parse(format)
- if err != nil {
- return err
- }
- for _, i := range responses {
- if err := tmpl.Execute(os.Stdout, i); err != nil {
- return err
- }
- }
- return nil
+ inspectOpts = inspect.AddInspectFlagSet(inspectCmd)
}
-func Inspect(cmd *cobra.Command, args []string, options *entities.InspectOptions) error {
- inspectOpts = options
- return inspect(cmd, args)
+func inspectExec(cmd *cobra.Command, args []string) error {
+ return inspect.Inspect(args, *inspectOpts)
}
diff --git a/cmd/podman/containers/kill.go b/cmd/podman/containers/kill.go
index 5341457fb..8b4a384fe 100644
--- a/cmd/podman/containers/kill.go
+++ b/cmd/podman/containers/kill.go
@@ -11,6 +11,7 @@ import (
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/signal"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -27,18 +28,23 @@ var (
podman kill 860a4b23
podman kill --signal TERM ctrID`,
}
+
+ containerKillCommand = &cobra.Command{
+ Use: killCommand.Use,
+ Short: killCommand.Short,
+ Long: killCommand.Long,
+ RunE: killCommand.RunE,
+ Example: `podman container kill mywebserver
+ podman container kill 860a4b23
+ podman container 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()
+func killFlags(flags *pflag.FlagSet) {
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")
@@ -47,6 +53,24 @@ func init() {
}
}
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: killCommand,
+ })
+ flags := killCommand.Flags()
+ killFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerKillCommand,
+ Parent: containerCmd,
+ })
+
+ containerKillFlags := containerKillCommand.Flags()
+ killFlags(containerKillFlags)
+}
+
func kill(cmd *cobra.Command, args []string) error {
var (
err error
diff --git a/cmd/podman/containers/list.go b/cmd/podman/containers/list.go
index 938fb63d3..c200a49aa 100644
--- a/cmd/podman/containers/list.go
+++ b/cmd/podman/containers/list.go
@@ -2,6 +2,7 @@ package containers
import (
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)
@@ -11,10 +12,10 @@ var (
listCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
- Args: cobra.NoArgs,
+ Args: validate.NoArgs,
Short: "List containers",
Long: "Prints out information about the containers",
- RunE: containers,
+ RunE: ps,
Example: `podman container list -a
podman container list -a --format "{{.ID}} {{.Image}} {{.Labels}} {{.Mounts}}"
podman container list --size --sort names`,
@@ -27,8 +28,5 @@ func init() {
Command: listCmd,
Parent: containerCmd,
})
-}
-
-func containers(cmd *cobra.Command, args []string) error {
- return nil
+ listFlagSet(listCmd.Flags())
}
diff --git a/cmd/podman/containers/mount.go b/cmd/podman/containers/mount.go
index 25eec46ca..0bdac72cb 100644
--- a/cmd/podman/containers/mount.go
+++ b/cmd/podman/containers/mount.go
@@ -1,7 +1,6 @@
package containers
import (
- "encoding/json"
"fmt"
"os"
"text/tabwriter"
@@ -12,6 +11,7 @@ import (
"github.com/containers/libpod/cmd/podman/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -34,22 +34,41 @@ var (
registry.ParentNSRequired: "",
},
}
+
+ containerMountCommmand = &cobra.Command{
+ Use: mountCommand.Use,
+ Short: mountCommand.Short,
+ Long: mountCommand.Long,
+ RunE: mountCommand.RunE,
+ }
)
var (
mountOpts entities.ContainerMountOptions
)
+func mountFlags(flags *pflag.FlagSet) {
+ 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 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")
+ mountFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: containerMountCommmand,
+ Parent: containerCmd,
+ })
+ containerMountFlags := containerMountCommmand.Flags()
+ mountFlags(containerMountFlags)
}
func mount(cmd *cobra.Command, args []string) error {
diff --git a/cmd/podman/containers/pause.go b/cmd/podman/containers/pause.go
index f3654b5c1..b932c4539 100644
--- a/cmd/podman/containers/pause.go
+++ b/cmd/podman/containers/pause.go
@@ -10,6 +10,7 @@ import (
"github.com/containers/libpod/pkg/rootless"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -24,16 +25,38 @@ var (
podman pause -a`,
}
+ containerPauseCommand = &cobra.Command{
+ Use: pauseCommand.Use,
+ Short: pauseCommand.Short,
+ Long: pauseCommand.Long,
+ RunE: pauseCommand.RunE,
+ Example: `podman container pause mywebserver
+ podman container pause 860a4b23
+ podman container pause -a`,
+ }
+
pauseOpts = entities.PauseUnPauseOptions{}
)
+func pauseFlags(flags *pflag.FlagSet) {
+ flags.BoolVarP(&pauseOpts.All, "all", "a", false, "Pause all running containers")
+}
+
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")
+ pauseFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerPauseCommand,
+ Parent: containerCmd,
+ })
+ containerPauseFlags := containerPauseCommand.Flags()
+ pauseFlags(containerPauseFlags)
}
func pause(cmd *cobra.Command, args []string) error {
diff --git a/cmd/podman/containers/port.go b/cmd/podman/containers/port.go
new file mode 100644
index 000000000..2e3386aa9
--- /dev/null
+++ b/cmd/podman/containers/port.go
@@ -0,0 +1,123 @@
+package containers
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/containers/libpod/cmd/podman/parse"
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/cri-o/ocicni/pkg/ocicni"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ 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: port,
+ Args: func(cmd *cobra.Command, args []string) error {
+ return parse.CheckAllLatestAndCIDFile(cmd, args, true, false)
+ },
+ Example: `podman port --all
+ podman port ctrID 80/tcp
+ podman port --latest 80`,
+ }
+)
+
+var (
+ portOpts entities.ContainerPortOptions
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: portCommand,
+ })
+ flags := portCommand.Flags()
+ flags.BoolVarP(&portOpts.All, "all", "a", false, "Display port information for all containers")
+ flags.BoolVarP(&portOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
+ if registry.IsRemote() {
+ _ = flags.MarkHidden("latest")
+ }
+}
+
+func port(cmd *cobra.Command, args []string) error {
+ var (
+ container string
+ err error
+ userPort ocicni.PortMapping
+ )
+
+ if len(args) == 0 && !portOpts.Latest && !portOpts.All {
+ return errors.Errorf("you must supply a running container name or id")
+ }
+ if !portOpts.Latest && len(args) >= 1 {
+ container = args[0]
+ }
+ port := ""
+ if len(args) > 1 && !portOpts.Latest {
+ port = args[1]
+ }
+ if len(args) == 1 && portOpts.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")
+ }
+
+ portNum, err := strconv.Atoi(fields[0])
+ if err != nil {
+ return err
+ }
+ userPort = ocicni.PortMapping{
+ HostPort: 0,
+ ContainerPort: int32(portNum),
+ Protocol: fields[1],
+ HostIP: "",
+ }
+ }
+
+ reports, err := registry.ContainerEngine().ContainerPort(registry.GetContext(), container, portOpts)
+ if err != nil {
+ return err
+ }
+ var found bool
+ // Iterate mappings
+ for _, report := range reports {
+ for _, v := range report.Ports {
+ hostIP := v.HostIP
+ // Set host IP to 0.0.0.0 if blank
+ if hostIP == "" {
+ hostIP = "0.0.0.0"
+ }
+ if portOpts.All {
+ fmt.Printf("%s\t", report.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
+ }
+ if v.ContainerPort == userPort.ContainerPort {
+ 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/containers/ps.go b/cmd/podman/containers/ps.go
index 57b81a609..c5696a158 100644
--- a/cmd/podman/containers/ps.go
+++ b/cmd/podman/containers/ps.go
@@ -1,7 +1,6 @@
package containers
import (
- "encoding/json"
"fmt"
"os"
"sort"
@@ -14,18 +13,20 @@ import (
tm "github.com/buger/goterm"
"github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
psDescription = "Prints out information about the containers"
psCommand = &cobra.Command{
Use: "ps",
- Args: checkFlags,
+ Args: validate.NoArgs,
Short: "List containers",
Long: psDescription,
RunE: ps,
@@ -40,7 +41,7 @@ var (
}
filters []string
noTrunc bool
- defaultHeaders string = "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES"
+ defaultHeaders = "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES"
)
func init() {
@@ -48,7 +49,10 @@ func init() {
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: psCommand,
})
- flags := psCommand.Flags()
+ listFlagSet(psCommand.Flags())
+}
+
+func listFlagSet(flags *pflag.FlagSet) {
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")
@@ -60,9 +64,12 @@ func init() {
flags.BoolVarP(&listOpts.Pod, "pod", "p", false, "Print the ID and name of the pod the containers are associated with")
flags.BoolVarP(&listOpts.Quiet, "quiet", "q", false, "Print the numeric IDs of the containers only")
flags.BoolVarP(&listOpts.Size, "size", "s", false, "Display the total file sizes")
- flags.StringVar(&listOpts.Sort, "sort", "created", "Sort output by command, created, id, image, names, runningfor, size, or status")
flags.BoolVar(&listOpts.Sync, "sync", false, "Sync container state with OCI runtime")
flags.UintVarP(&listOpts.Watch, "watch", "w", 0, "Watch the ps output on an interval in seconds")
+
+ created := validate.ChoiceValue(&listOpts.Sort, "command", "created", "id", "image", "names", "runningfor", "size", "status")
+ flags.Var(created, "sort", "Sort output by: "+created.Choices())
+
if registry.IsRemote() {
_ = flags.MarkHidden("latest")
}
@@ -138,6 +145,9 @@ func getResponses() ([]entities.ListContainer, error) {
func ps(cmd *cobra.Command, args []string) error {
var responses []psReporter
+ if err := checkFlags(cmd, args); err != nil {
+ return err
+ }
for _, f := range filters {
split := strings.SplitN(f, "=", 2)
if len(split) == 1 {
@@ -166,14 +176,14 @@ func ps(cmd *cobra.Command, args []string) error {
responses = append(responses, psReporter{r})
}
- headers, row := createPsOut()
+ headers, format := createPsOut()
if cmd.Flag("format").Changed {
- row = listOpts.Format
- if !strings.HasPrefix(row, "\n") {
- row += "\n"
+ format = strings.TrimPrefix(listOpts.Format, "table ")
+ if !strings.HasPrefix(format, "\n") {
+ format += "\n"
}
}
- format := "{{range . }}" + row + "{{end}}"
+ format = "{{range . }}" + format + "{{end}}"
if !listOpts.Quiet && !cmd.Flag("format").Changed {
format = headers + format
}
@@ -224,7 +234,7 @@ func createPsOut() (string, string) {
}
headers := defaultHeaders
row += "{{.ID}}"
- row += "\t{{.Image}}\t{{.Command}}\t{{.CreatedHuman}}\t{{.State}}\t{{.Ports}}\t{{.Names}}"
+ row += "\t{{.Image}}\t{{.Command}}\t{{.CreatedHuman}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}"
if listOpts.Pod {
headers += "\tPOD ID\tPODNAME"
@@ -248,6 +258,14 @@ type psReporter struct {
entities.ListContainer
}
+// ImageID returns the ID of the container
+func (l psReporter) ImageID() string {
+ if !noTrunc {
+ return l.ListContainer.ImageID[0:12]
+ }
+ return l.ListContainer.ImageID
+}
+
// ID returns the ID of the container
func (l psReporter) ID() string {
if !noTrunc {
@@ -283,6 +301,11 @@ func (l psReporter) State() string {
return state
}
+// Status is a synonym for State()
+func (l psReporter) Status() string {
+ return l.State()
+}
+
// Command returns the container command in string format
func (l psReporter) Command() string {
return strings.Join(l.ListContainer.Command, " ")
@@ -332,7 +355,7 @@ func portsToString(ports []ocicni.PortMapping) string {
if len(ports) == 0 {
return ""
}
- //Sort the ports, so grouping continuous ports become easy.
+ // Sort the ports, so grouping continuous ports become easy.
sort.Slice(ports, func(i, j int) bool {
return comparePorts(ports[i], ports[j])
})
diff --git a/cmd/podman/containers/restart.go b/cmd/podman/containers/restart.go
index 68b6de4ca..1a9d7f6c7 100644
--- a/cmd/podman/containers/restart.go
+++ b/cmd/podman/containers/restart.go
@@ -11,12 +11,13 @@ import (
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
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)
+ A timeout before forcibly stopping can be set, but defaults to %d seconds.`, containerConfig.Engine.StopTimeout)
restartCommand = &cobra.Command{
Use: "restart [flags] CONTAINER [CONTAINER...]",
@@ -30,6 +31,16 @@ var (
podman restart --latest
podman restart ctrID1 ctrID2`,
}
+
+ containerRestartCommand = &cobra.Command{
+ Use: restartCommand.Use,
+ Short: restartCommand.Short,
+ Long: restartCommand.Long,
+ RunE: restartCommand.RunE,
+ Example: `podman container restart ctrID
+ podman container restart --latest
+ podman container restart ctrID1 ctrID2`,
+ }
)
var (
@@ -37,22 +48,35 @@ var (
restartTimeout uint
)
-func init() {
- registry.Commands = append(registry.Commands, registry.CliCommand{
- Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
- Command: restartCommand,
- })
- flags := restartCommand.Flags()
+func restartFlags(flags *pflag.FlagSet) {
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")
+ flags.UintVarP(&restartTimeout, "time", "t", containerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container")
if registry.IsRemote() {
_ = flags.MarkHidden("latest")
}
flags.SetNormalizeFunc(utils.AliasFlags)
}
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: restartCommand,
+ })
+ flags := restartCommand.Flags()
+ restartFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerRestartCommand,
+ Parent: containerCmd,
+ })
+
+ containerRestartFlags := containerRestartCommand.Flags()
+ restartFlags(containerRestartFlags)
+}
+
func restart(cmd *cobra.Command, args []string) error {
var (
errs utils.OutputErrors
diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go
index a22880d93..3021853a9 100644
--- a/cmd/podman/containers/rm.go
+++ b/cmd/podman/containers/rm.go
@@ -12,6 +12,7 @@ import (
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -31,18 +32,24 @@ var (
podman rm --force --all
podman rm -f c684f0d469f2`,
}
+
+ containerRmCommand = &cobra.Command{
+ Use: rmCommand.Use,
+ Short: rmCommand.Use,
+ Long: rmCommand.Long,
+ RunE: rmCommand.RunE,
+ Example: `podman container rm imageID
+ podman container rm mywebserver myflaskserver 860a4b23
+ podman container rm --force --all
+ podman container 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()
+func rmFlags(flags *pflag.FlagSet) {
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")
@@ -56,7 +63,24 @@ func init() {
_ = flags.MarkHidden("cidfile")
_ = flags.MarkHidden("storage")
}
+}
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: rmCommand,
+ })
+ flags := rmCommand.Flags()
+ rmFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerRmCommand,
+ Parent: containerCmd,
+ })
+ containerRmFlags := containerRmCommand.Flags()
+ rmFlags(containerRmFlags)
}
func rm(cmd *cobra.Command, args []string) error {
@@ -71,11 +95,9 @@ func rm(cmd *cobra.Command, args []string) error {
}
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)
- //}
+ if len(args) < 2 {
+ setExitCode(err)
+ }
return err
}
for _, r := range responses {
@@ -84,6 +106,7 @@ func rm(cmd *cobra.Command, args []string) error {
if errors.Cause(err) == define.ErrWillDeadlock {
logrus.Errorf("Potential deadlock detected - please run 'podman system renumber' to resolve")
}
+ setExitCode(r.Err)
errs = append(errs, r.Err)
} else {
fmt.Println(r.Id)
@@ -91,3 +114,13 @@ func rm(cmd *cobra.Command, args []string) error {
}
return errs.PrintErrors()
}
+
+func setExitCode(err error) {
+ cause := errors.Cause(err)
+ switch cause {
+ case define.ErrNoSuchCtr:
+ registry.SetExitCode(1)
+ case define.ErrCtrStateInvalid:
+ registry.SetExitCode(2)
+ }
+}
diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go
index 91edb6bda..e3fe4cd0b 100644
--- a/cmd/podman/containers/run.go
+++ b/cmd/podman/containers/run.go
@@ -5,15 +5,16 @@ import (
"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/errorhandling"
"github.com/containers/libpod/pkg/specgen"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -27,6 +28,16 @@ var (
podman run --network=host imageID dnf -y install java
podman run --volume /var/hostdir:/var/ctrdir -i -t fedora /bin/bash`,
}
+
+ containerRunCommand = &cobra.Command{
+ Use: runCommand.Use,
+ Short: runCommand.Short,
+ Long: runCommand.Long,
+ RunE: runCommand.RunE,
+ Example: `podman container run imageID ls -alF /etc
+ podman container run --network=host imageID dnf -y install java
+ podman container run --volume /var/hostdir:/var/ctrdir -i -t fedora /bin/bash`,
+ }
)
var (
@@ -38,12 +49,7 @@ var (
runRmi bool
)
-func init() {
- registry.Commands = append(registry.Commands, registry.CliCommand{
- Mode: []entities.EngineMode{entities.ABIMode},
- Command: runCommand,
- })
- flags := runCommand.Flags()
+func runFlags(flags *pflag.FlagSet) {
flags.SetInterspersed(false)
flags.AddFlagSet(common.GetCreateFlags(&cliVals))
flags.AddFlagSet(common.GetNetFlags())
@@ -54,6 +60,23 @@ func init() {
_ = flags.MarkHidden("authfile")
}
}
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: runCommand,
+ })
+ flags := runCommand.Flags()
+ runFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: containerRunCommand,
+ Parent: containerCmd,
+ })
+
+ containerRunFlags := containerRunCommand.Flags()
+ runFlags(containerRunFlags)
+}
func run(cmd *cobra.Command, args []string) error {
var err error
@@ -67,31 +90,26 @@ func run(cmd *cobra.Command, args []string) error {
return errors.Wrapf(err, "error checking authfile path %s", af)
}
}
- runOpts.Rm = cliVals.Rm
- if err := createInit(cmd); err != nil {
+ cidFile, err := openCidFile(cliVals.CIDFile)
+ if err != nil {
return err
}
- br, err := registry.ImageEngine().Exists(registry.GetContext(), args[0])
- if err != nil {
- return err
+ if cidFile != nil {
+ defer errorhandling.CloseQuiet(cidFile)
+ defer errorhandling.SyncQuiet(cidFile)
}
- pullPolicy, err := config.ValidatePullPolicy(cliVals.Pull)
- if err != nil {
+ runOpts.Rm = cliVals.Rm
+ if err := createInit(cmd); 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 !cliVals.RootFS {
+ if err := pullImage(args[0]); err != nil {
+ return err
}
}
+
// If -i is not set, clear stdin
if !cliVals.Interactive {
runOpts.InputStream = nil
@@ -120,7 +138,7 @@ func run(cmd *cobra.Command, args []string) error {
}
runOpts.Detach = cliVals.Detach
runOpts.DetachKeys = cliVals.DetachKeys
- s := specgen.NewSpecGenerator(args[0])
+ s := specgen.NewSpecGenerator(args[0], cliVals.RootFS)
if err := common.FillOutSpecGen(s, &cliVals, args); err != nil {
return err
}
@@ -134,11 +152,19 @@ func run(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
+ if cidFile != nil {
+ _, err = cidFile.WriteString(report.Id)
+ if err != nil {
+ logrus.Error(err)
+ }
+ }
+
if cliVals.Detach {
fmt.Println(report.Id)
+ return nil
}
if runRmi {
- _, err := registry.ImageEngine().Delete(registry.GetContext(), []string{args[0]}, entities.ImageDeleteOptions{})
+ _, err := registry.ImageEngine().Remove(registry.GetContext(), []string{args[0]}, entities.ImageRemoveOptions{})
if err != nil {
logrus.Errorf("%s", errors.Wrapf(err, "failed removing image"))
}
diff --git a/cmd/podman/containers/start.go b/cmd/podman/containers/start.go
index 33e5a3094..381bf8e26 100644
--- a/cmd/podman/containers/start.go
+++ b/cmd/podman/containers/start.go
@@ -4,13 +4,13 @@ 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"
+ "github.com/spf13/pflag"
)
var (
@@ -20,25 +20,29 @@ var (
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`,
}
+
+ containerStartCommand = &cobra.Command{
+ Use: startCommand.Use,
+ Short: startCommand.Short,
+ Long: startCommand.Long,
+ RunE: startCommand.RunE,
+ Example: `podman container start --latest
+ podman container start 860a4b231279 5421ab43b45
+ podman container 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()
+func startFlags(flags *pflag.FlagSet) {
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.StringVar(&startOptions.DetachKeys, "detach-keys", containerConfig.DetachKeys(), "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)")
@@ -46,11 +50,30 @@ func init() {
_ = flags.MarkHidden("latest")
_ = flags.MarkHidden("sig-proxy")
}
+}
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: startCommand,
+ })
+ flags := startCommand.Flags()
+ startFlags(flags)
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: containerStartCommand,
+ Parent: containerCmd,
+ })
+
+ containerStartFlags := containerStartCommand.Flags()
+ startFlags(containerStartFlags)
}
func start(cmd *cobra.Command, args []string) error {
var errs utils.OutputErrors
+ if len(args) == 0 && !startOptions.Latest {
+ return errors.New("start requires at least one argument")
+ }
if len(args) > 1 && startOptions.Attach {
return errors.Errorf("you cannot start and attach multiple containers at once")
}
diff --git a/cmd/podman/containers/stop.go b/cmd/podman/containers/stop.go
index 1ee9186a7..4a451134a 100644
--- a/cmd/podman/containers/stop.go
+++ b/cmd/podman/containers/stop.go
@@ -9,12 +9,13 @@ import (
"github.com/containers/libpod/cmd/podman/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
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)
+ A timeout to forcibly stop the container can also be set but defaults to %d seconds otherwise.`, containerConfig.Engine.StopTimeout)
stopCommand = &cobra.Command{
Use: "stop [flags] CONTAINER [CONTAINER...]",
Short: "Stop one or more containers",
@@ -27,6 +28,16 @@ var (
podman stop --latest
podman stop --time 2 mywebserver 6e534f14da9d`,
}
+
+ containerStopCommand = &cobra.Command{
+ Use: stopCommand.Use,
+ Short: stopCommand.Short,
+ Long: stopCommand.Long,
+ RunE: stopCommand.RunE,
+ Example: `podman container stop ctrID
+ podman container stop --latest
+ podman container stop --time 2 mywebserver 6e534f14da9d`,
+ }
)
var (
@@ -34,18 +45,14 @@ var (
stopTimeout uint
)
-func init() {
- registry.Commands = append(registry.Commands, registry.CliCommand{
- Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
- Command: stopCommand,
- })
- flags := stopCommand.Flags()
+func stopFlags(flags *pflag.FlagSet) {
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.PodmanOptions.EngineMode == entities.ABIMode {
+ flags.UintVarP(&stopTimeout, "time", "t", containerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container")
+
+ if registry.IsRemote() {
_ = flags.MarkHidden("latest")
_ = flags.MarkHidden("cidfile")
_ = flags.MarkHidden("ignore")
@@ -53,11 +60,29 @@ func init() {
flags.SetNormalizeFunc(utils.AliasFlags)
}
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: stopCommand,
+ })
+ flags := stopCommand.Flags()
+ stopFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerStopCommand,
+ Parent: containerCmd,
+ })
+
+ containerStopFlags := containerStopCommand.Flags()
+ stopFlags(containerStopFlags)
+}
+
func stop(cmd *cobra.Command, args []string) error {
var (
errs utils.OutputErrors
)
- stopOptions.Timeout = defaultContainerConfig.Engine.StopTimeout
+ stopOptions.Timeout = containerConfig.Engine.StopTimeout
if cmd.Flag("time").Changed {
stopOptions.Timeout = stopTimeout
}
diff --git a/cmd/podman/containers/top.go b/cmd/podman/containers/top.go
index db5213863..732a08623 100644
--- a/cmd/podman/containers/top.go
+++ b/cmd/podman/containers/top.go
@@ -12,6 +12,7 @@ import (
"github.com/containers/psgo"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -36,25 +37,46 @@ 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,
- })
+ containerTopCommand = &cobra.Command{
+ Use: topCommand.Use,
+ Short: topCommand.Short,
+ Long: topCommand.Long,
+ RunE: topCommand.RunE,
+ Example: `podman container top ctrID
+podman container top --latest
+podman container top ctrID pid seccomp args %C
+podman container top ctrID -eo user,pid,comm`,
+ }
+)
- flags := topCommand.Flags()
+func topFlags(flags *pflag.FlagSet) {
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 init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: topCommand,
+ })
+ flags := topCommand.Flags()
+ topFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerTopCommand,
+ Parent: containerCmd,
+ })
+ containerTopFlags := containerTopCommand.Flags()
+ topFlags(containerTopFlags)
+}
+
func top(cmd *cobra.Command, args []string) error {
if topOptions.ListDescriptors {
fmt.Println(strings.Join(psgo.ListDescriptors(), "\n"))
diff --git a/cmd/podman/containers/unmount.go b/cmd/podman/containers/unmount.go
index 3dbfc1eae..a4550abbd 100644
--- a/cmd/podman/containers/unmount.go
+++ b/cmd/podman/containers/unmount.go
@@ -8,6 +8,7 @@ import (
"github.com/containers/libpod/cmd/podman/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -30,21 +31,44 @@ var (
podman umount ctrID1 ctrID2 ctrID3
podman umount --all`,
}
+
+ containerUnmountCommand = &cobra.Command{
+ Use: umountCommand.Use,
+ Short: umountCommand.Short,
+ Long: umountCommand.Long,
+ RunE: umountCommand.RunE,
+ Example: `podman container umount ctrID
+ podman container umount ctrID1 ctrID2 ctrID3
+ podman container umount --all`,
+ }
)
var (
unmountOpts entities.ContainerUnmountOptions
)
+func umountFlags(flags *pflag.FlagSet) {
+ 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 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")
+ umountFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: containerUnmountCommand,
+ Parent: containerCmd,
+ })
+
+ containerUmountFlags := containerUnmountCommand.Flags()
+ umountFlags(containerUmountFlags)
}
func unmount(cmd *cobra.Command, args []string) error {
diff --git a/cmd/podman/containers/unpause.go b/cmd/podman/containers/unpause.go
index ef874b042..adf8d12ee 100644
--- a/cmd/podman/containers/unpause.go
+++ b/cmd/podman/containers/unpause.go
@@ -10,6 +10,7 @@ import (
"github.com/containers/libpod/pkg/rootless"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -23,16 +24,37 @@ var (
podman unpause --all`,
}
unPauseOptions = entities.PauseUnPauseOptions{}
+
+ containerUnpauseCommand = &cobra.Command{
+ Use: unpauseCommand.Use,
+ Short: unpauseCommand.Short,
+ Long: unpauseCommand.Long,
+ RunE: unpauseCommand.RunE,
+ Example: `podman container unpause ctrID
+ podman container unpause --all`,
+ }
)
+func unpauseFlags(flags *pflag.FlagSet) {
+ flags.BoolVarP(&unPauseOptions.All, "all", "a", false, "Pause all running containers")
+}
+
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")
+ unpauseFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: unpauseCommand,
+ Parent: containerCmd,
+ })
+
+ unpauseCommandFlags := containerUnpauseCommand.Flags()
+ unpauseFlags(unpauseCommandFlags)
}
func unpause(cmd *cobra.Command, args []string) error {
diff --git a/cmd/podman/containers/wait.go b/cmd/podman/containers/wait.go
index 83c164e16..eac1e2956 100644
--- a/cmd/podman/containers/wait.go
+++ b/cmd/podman/containers/wait.go
@@ -7,10 +7,12 @@ import (
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/cmd/podman/utils"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -21,11 +23,21 @@ var (
Short: "Block on one or more containers",
Long: waitDescription,
RunE: wait,
- Args: registry.IdOrLatestArgs,
+ Args: validate.IdOrLatestArgs,
Example: `podman wait --latest
podman wait --interval 5000 ctrID
podman wait ctrID1 ctrID2`,
}
+
+ containerWaitCommand = &cobra.Command{
+ Use: waitCommand.Use,
+ Short: waitCommand.Short,
+ Long: waitCommand.Long,
+ RunE: waitCommand.RunE,
+ Example: `podman container wait --latest
+ podman container wait --interval 5000 ctrID
+ podman container wait ctrID1 ctrID2`,
+ }
)
var (
@@ -33,22 +45,34 @@ var (
waitCondition string
)
-func init() {
- registry.Commands = append(registry.Commands, registry.CliCommand{
- Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
- Command: waitCommand,
- })
-
- flags := waitCommand.Flags()
+func waitFlags(flags *pflag.FlagSet) {
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.PodmanOptions.EngineMode == entities.ABIMode {
+ if registry.IsRemote() {
// TODO: This is the same as V1. We could skip creating the flag altogether in V2...
_ = flags.MarkHidden("latest")
}
}
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: waitCommand,
+ })
+ flags := waitCommand.Flags()
+ waitFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerWaitCommand,
+ Parent: containerCmd,
+ })
+
+ containerWaitFlags := containerWaitCommand.Flags()
+ waitFlags(containerWaitFlags)
+}
+
func wait(cmd *cobra.Command, args []string) error {
var (
err error
diff --git a/cmd/podman/diff.go b/cmd/podman/diff.go
index 8db76e8af..1ff2fce40 100644
--- a/cmd/podman/diff.go
+++ b/cmd/podman/diff.go
@@ -6,6 +6,7 @@ import (
"github.com/containers/libpod/cmd/podman/containers"
"github.com/containers/libpod/cmd/podman/images"
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)
@@ -17,7 +18,7 @@ var (
diffDescription = `Displays changes on a container or image's filesystem. The container or image will be compared to its parent layer.`
diffCmd = &cobra.Command{
Use: "diff [flags] {CONTAINER_ID | IMAGE_ID}",
- Args: registry.IdOrLatestArgs,
+ Args: validate.IdOrLatestArgs,
Short: "Display the changes of object's file system",
Long: diffDescription,
TraverseChildren: true,
@@ -46,10 +47,9 @@ func init() {
}
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)
+ // Latest implies looking for a container
+ if diffOpts.Latest {
+ return containers.Diff(cmd, args, diffOpts)
}
if found, err := registry.ContainerEngine().ContainerExists(registry.GetContext(), args[0]); err != nil {
@@ -57,5 +57,12 @@ func diff(cmd *cobra.Command, args []string) error {
} else if found.Value {
return containers.Diff(cmd, args, diffOpts)
}
+
+ if found, err := registry.ImageEngine().Exists(registry.GetContext(), args[0]); err != nil {
+ return err
+ } else if found.Value {
+ return images.Diff(cmd, args, diffOpts)
+ }
+
return fmt.Errorf("%s not found on system", args[0])
}
diff --git a/cmd/podman/generate/generate.go b/cmd/podman/generate/generate.go
new file mode 100644
index 000000000..b112e666a
--- /dev/null
+++ b/cmd/podman/generate/generate.go
@@ -0,0 +1,28 @@
+package pods
+
+import (
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/spf13/cobra"
+)
+
+var (
+ // Command: podman _generate_
+ generateCmd = &cobra.Command{
+ Use: "generate",
+ Short: "Generate structured data based on containers and pods.",
+ Long: "Generate structured data (e.g., Kubernetes yaml or systemd units) based on containers and pods.",
+ TraverseChildren: true,
+ RunE: validate.SubCommandExists,
+ }
+ containerConfig = util.DefaultContainerConfig()
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: generateCmd,
+ })
+}
diff --git a/cmd/podman/generate/systemd.go b/cmd/podman/generate/systemd.go
new file mode 100644
index 000000000..55d770249
--- /dev/null
+++ b/cmd/podman/generate/systemd.go
@@ -0,0 +1,57 @@
+package pods
+
+import (
+ "fmt"
+
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/spf13/cobra"
+)
+
+var (
+ systemdTimeout uint
+ systemdOptions = entities.GenerateSystemdOptions{}
+ systemdDescription = `Generate systemd units for a pod or container.
+ The generated units can later be controlled via systemctl(1).`
+
+ systemdCmd = &cobra.Command{
+ Use: "systemd [flags] CTR|POD",
+ Short: "Generate systemd units.",
+ Long: systemdDescription,
+ RunE: systemd,
+ Args: cobra.MinimumNArgs(1),
+ Example: `podman generate systemd CTR
+ podman generate systemd --new --time 10 CTR
+ podman generate systemd --files --name POD`,
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: systemdCmd,
+ Parent: generateCmd,
+ })
+ flags := systemdCmd.Flags()
+ flags.BoolVarP(&systemdOptions.Name, "name", "n", false, "Use container/pod names instead of IDs")
+ flags.BoolVarP(&systemdOptions.Files, "files", "f", false, "Generate .service files instead of printing to stdout")
+ flags.UintVarP(&systemdTimeout, "time", "t", containerConfig.Engine.StopTimeout, "Stop timeout override")
+ flags.StringVar(&systemdOptions.RestartPolicy, "restart-policy", "on-failure", "Systemd restart-policy")
+ flags.BoolVarP(&systemdOptions.New, "new", "", false, "Create a new container instead of starting an existing one")
+ flags.SetNormalizeFunc(utils.AliasFlags)
+}
+
+func systemd(cmd *cobra.Command, args []string) error {
+ if cmd.Flags().Changed("time") {
+ systemdOptions.StopTimeout = &systemdTimeout
+ }
+
+ report, err := registry.ContainerEngine().GenerateSystemd(registry.GetContext(), args[0], systemdOptions)
+ if err != nil {
+ return err
+ }
+
+ fmt.Println(report.Output)
+ return nil
+}
diff --git a/cmd/podman/healthcheck/healthcheck.go b/cmd/podman/healthcheck/healthcheck.go
index 794a94615..ce90dba31 100644
--- a/cmd/podman/healthcheck/healthcheck.go
+++ b/cmd/podman/healthcheck/healthcheck.go
@@ -2,6 +2,7 @@ package healthcheck
import (
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)
@@ -13,7 +14,7 @@ var (
Short: "Manage Healthcheck",
Long: "Manage Healthcheck",
TraverseChildren: true,
- RunE: registry.SubCommandExists,
+ RunE: validate.SubCommandExists,
}
)
diff --git a/cmd/podman/images/diff.go b/cmd/podman/images/diff.go
index dd98dc4d6..7cfacfc6c 100644
--- a/cmd/podman/images/diff.go
+++ b/cmd/podman/images/diff.go
@@ -11,8 +11,8 @@ import (
var (
// podman container _inspect_
diffCmd = &cobra.Command{
- Use: "diff [flags] CONTAINER",
- Args: registry.IdOrLatestArgs,
+ Use: "diff [flags] IMAGE",
+ Args: cobra.ExactArgs(1),
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,
@@ -32,16 +32,16 @@ func init() {
diffOpts = &entities.DiffOptions{}
flags := diffCmd.Flags()
flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive")
- _ = flags.MarkHidden("archive")
+ _ = flags.MarkDeprecated("archive", "Provided for backwards compatibility, has no impact on output.")
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")
+ if diffOpts.Latest {
+ return errors.New("image diff does not support --latest")
}
- results, err := registry.ImageEngine().Diff(registry.GetContext(), args[0], entities.DiffOptions{})
+ results, err := registry.ImageEngine().Diff(registry.GetContext(), args[0], *diffOpts)
if err != nil {
return err
}
diff --git a/cmd/podman/images/exists.go b/cmd/podman/images/exists.go
index 0bb288b96..13191113f 100644
--- a/cmd/podman/images/exists.go
+++ b/cmd/podman/images/exists.go
@@ -1,8 +1,6 @@
package images
import (
- "os"
-
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
@@ -17,6 +15,7 @@ var (
RunE: exists,
Example: `podman image exists ID
podman image exists IMAGE && podman pull IMAGE`,
+ DisableFlagsInUseLine: true,
}
)
@@ -34,7 +33,7 @@ func exists(cmd *cobra.Command, args []string) error {
return err
}
if !found.Value {
- os.Exit(1)
+ registry.SetExitCode(1)
}
return nil
}
diff --git a/cmd/podman/images/history.go b/cmd/podman/images/history.go
index c92072bff..ce153aa46 100644
--- a/cmd/podman/images/history.go
+++ b/cmd/podman/images/history.go
@@ -13,7 +13,6 @@ import (
"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"
)
@@ -77,7 +76,6 @@ func history(cmd *cobra.Command, args []string) error {
layers[i].ImageHistoryLayer = l
layers[i].Created = l.Created.Format(time.RFC3339)
}
- json := jsoniter.ConfigCompatibleWithStandardLibrary
enc := json.NewEncoder(os.Stdout)
err = enc.Encode(layers)
}
@@ -91,22 +89,20 @@ func history(cmd *cobra.Command, args []string) error {
hdr := "ID\tCREATED\tCREATED BY\tSIZE\tCOMMENT\n"
row := "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n"
- if len(opts.format) > 0 {
+ switch {
+ case len(opts.format) > 0:
hdr = ""
row = opts.format
if !strings.HasSuffix(opts.format, "\n") {
row += "\n"
}
- } else {
- switch {
- case opts.human:
- row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n"
- case opts.noTrunc:
- row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n"
- case opts.quiet:
- hdr = ""
- row = "{{.ID}}\n"
- }
+ case opts.quiet:
+ hdr = ""
+ row = "{{.ID}}\n"
+ case opts.human:
+ row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n"
+ case opts.noTrunc:
+ row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n"
}
format := hdr + "{{range . }}" + row + "{{end}}"
diff --git a/cmd/podman/images/image.go b/cmd/podman/images/image.go
index 37e46ab9e..790c16c05 100644
--- a/cmd/podman/images/image.go
+++ b/cmd/podman/images/image.go
@@ -2,18 +2,22 @@ package images
import (
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)
var (
+ // Pull in configured json library
+ json = registry.JsonLibrary()
+
// Command: podman _image_
imageCmd = &cobra.Command{
Use: "image",
Short: "Manage images",
Long: "Manage images",
TraverseChildren: true,
- RunE: registry.SubCommandExists,
+ RunE: validate.SubCommandExists,
}
)
diff --git a/cmd/podman/images/images.go b/cmd/podman/images/images.go
index fd3ede26a..96ef344bf 100644
--- a/cmd/podman/images/images.go
+++ b/cmd/podman/images/images.go
@@ -11,12 +11,13 @@ import (
var (
// podman _images_ Alias for podman image _list_
imagesCmd = &cobra.Command{
- Use: strings.Replace(listCmd.Use, "list", "images", 1),
- Args: listCmd.Args,
- Short: listCmd.Short,
- Long: listCmd.Long,
- RunE: listCmd.RunE,
- Example: strings.Replace(listCmd.Example, "podman image list", "podman images", -1),
+ Use: strings.Replace(listCmd.Use, "list", "images", 1),
+ Args: listCmd.Args,
+ Short: listCmd.Short,
+ Long: listCmd.Long,
+ RunE: listCmd.RunE,
+ Example: strings.Replace(listCmd.Example, "podman image list", "podman images", -1),
+ DisableFlagsInUseLine: true,
}
)
diff --git a/cmd/podman/images/inspect.go b/cmd/podman/images/inspect.go
index 4482ceee5..8c727eb07 100644
--- a/cmd/podman/images/inspect.go
+++ b/cmd/podman/images/inspect.go
@@ -1,30 +1,22 @@
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/inspect"
"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`,
+ Use: "inspect [flags] IMAGE",
+ Short: "Display the configuration of an image",
+ Long: `Displays the low-level information of an image identified by name or ID.`,
+ RunE: inspectExec,
+ Example: `podman inspect alpine
+ podman inspect --format "imageId: {{.Id}} size: {{.Size}}" alpine
+ podman inspect --format "image: {{.ImageName}} driver: {{.Driver}}" myctr`,
}
inspectOpts *entities.InspectOptions
)
@@ -35,74 +27,11 @@ func init() {
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
+ inspectOpts = inspect.AddInspectFlagSet(inspectCmd)
+ flags := inspectCmd.Flags()
+ _ = flags.MarkHidden("latest") // Shared with container-inspect but not wanted here.
}
-func Inspect(cmd *cobra.Command, args []string, options *entities.InspectOptions) error {
- inspectOpts = options
- return inspect(cmd, args)
+func inspectExec(cmd *cobra.Command, args []string) error {
+ return inspect.Inspect(args, *inspectOpts)
}
diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go
index 366dfc4ba..83c039ed3 100644
--- a/cmd/podman/images/list.go
+++ b/cmd/podman/images/list.go
@@ -14,7 +14,6 @@ import (
"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"
)
@@ -33,7 +32,7 @@ type listFlagType struct {
var (
// Command: podman image _list_
listCmd = &cobra.Command{
- Use: "list [flag] [IMAGE]",
+ Use: "list [FLAGS] [IMAGE]",
Aliases: []string{"ls"},
Args: cobra.MaximumNArgs(1),
Short: "List images in local storage",
@@ -42,6 +41,7 @@ var (
Example: `podman image list --format json
podman image list --sort repository --format "table {{.ID}} {{.Repository}} {{.Tag}}"
podman image list --filter dangling=true`,
+ DisableFlagsInUseLine: true,
}
// Options to pull data
@@ -99,38 +99,54 @@ func images(cmd *cobra.Command, args []string) error {
return err
}
- imageS := summaries
- sort.Slice(imageS, sortFunc(listFlag.sort, imageS))
+ switch {
+ case listFlag.quiet:
+ return writeId(summaries)
+ case cmd.Flag("format").Changed && listFlag.format == "json":
+ return writeJSON(summaries)
+ default:
+ return writeTemplate(summaries)
+ }
+}
- if cmd.Flag("format").Changed && listFlag.format == "json" {
- return writeJSON(imageS)
- } else {
- return writeTemplate(imageS, err)
+func writeId(imageS []*entities.ImageSummary) error {
+ var ids = map[string]struct{}{}
+ for _, e := range imageS {
+ i := "sha256:" + e.ID
+ if !listFlag.noTrunc {
+ i = fmt.Sprintf("%12.12s", e.ID)
+ }
+ ids[i] = struct{}{}
}
+ for k := range ids {
+ fmt.Fprint(os.Stdout, k+"\n")
+ }
+ return nil
}
func writeJSON(imageS []*entities.ImageSummary) error {
type image struct {
entities.ImageSummary
- Created string
+ Created string
+ CreatedAt 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.Created = units.HumanDuration(time.Since(e.Created)) + " ago"
+ h.CreatedAt = e.Created.Format(time.RFC3339Nano)
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 {
+func writeTemplate(imageS []*entities.ImageSummary) error {
var (
hdr, row string
)
@@ -142,10 +158,11 @@ func writeTemplate(imageS []*entities.ImageSummary, err error) error {
h.Repository, h.Tag = tokenRepoTag(tag)
imgs = append(imgs, h)
}
- if e.IsReadOnly() {
- listFlag.readOnly = true
- }
+ listFlag.readOnly = e.IsReadOnly()
}
+
+ sort.Slice(imgs, sortFunc(listFlag.sort, imgs))
+
if len(listFlag.format) < 1 {
hdr, row = imageListFormat(listFlag)
} else {
@@ -175,37 +192,33 @@ func tokenRepoTag(tag string) (string, string) {
}
}
-func sortFunc(key string, data []*entities.ImageSummary) func(i, j int) bool {
+func sortFunc(key string, data []imageReporter) func(i, j int) bool {
switch key {
case "id":
return func(i, j int) bool {
- return data[i].ID < data[j].ID
+ return data[i].ID() < data[j].ID()
}
case "repository":
return func(i, j int) bool {
- return data[i].RepoTags[0] < data[j].RepoTags[0]
+ return data[i].Repository < data[j].Repository
}
case "size":
return func(i, j int) bool {
- return data[i].Size < data[j].Size
+ return data[i].size() < data[j].size()
}
case "tag":
return func(i, j int) bool {
- return data[i].RepoTags[0] < data[j].RepoTags[0]
+ return data[i].Tag < data[j].Tag
}
default:
// case "created":
return func(i, j int) bool {
- return data[i].Created >= data[j].Created
+ return data[i].created().After(data[j].created())
}
}
}
func imageListFormat(flags listFlagType) (string, string) {
- if flags.quiet {
- return "", "{{.ID}}\n"
- }
-
// Defaults
hdr := "REPOSITORY\tTAG"
row := "{{.Repository}}\t{{if .Tag}}{{.Tag}}{{else}}<none>{{end}}"
@@ -255,11 +268,15 @@ func (i imageReporter) ID() string {
if !listFlag.noTrunc && len(i.ImageSummary.ID) >= 12 {
return i.ImageSummary.ID[0:12]
}
- return i.ImageSummary.ID
+ return "sha256:" + i.ImageSummary.ID
}
func (i imageReporter) Created() string {
- return units.HumanDuration(time.Since(time.Unix(i.ImageSummary.Created, 0))) + " ago"
+ return units.HumanDuration(time.Since(i.ImageSummary.Created)) + " ago"
+}
+
+func (i imageReporter) created() time.Time {
+ return i.ImageSummary.Created
}
func (i imageReporter) Size() string {
@@ -271,3 +288,19 @@ func (i imageReporter) Size() string {
func (i imageReporter) History() string {
return strings.Join(i.ImageSummary.History, ", ")
}
+
+func (i imageReporter) CreatedAt() string {
+ return i.ImageSummary.Created.String()
+}
+
+func (i imageReporter) CreatedSince() string {
+ return i.Created()
+}
+
+func (i imageReporter) CreatedTime() string {
+ return i.CreatedAt()
+}
+
+func (i imageReporter) size() int64 {
+ return i.ImageSummary.Size
+}
diff --git a/cmd/podman/images/load.go b/cmd/podman/images/load.go
index 23c657b59..f49f95002 100644
--- a/cmd/podman/images/load.go
+++ b/cmd/podman/images/load.go
@@ -6,6 +6,7 @@ import (
"io"
"io/ioutil"
"os"
+ "strings"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/libpod/cmd/podman/parse"
@@ -89,6 +90,6 @@ func load(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
- fmt.Println("Loaded image: " + response.Name)
+ fmt.Println("Loaded image(s): " + strings.Join(response.Names, ","))
return nil
}
diff --git a/cmd/podman/images/prune.go b/cmd/podman/images/prune.go
index b90d889be..53a1966c1 100644
--- a/cmd/podman/images/prune.go
+++ b/cmd/podman/images/prune.go
@@ -7,6 +7,7 @@ import (
"strings"
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@@ -18,7 +19,7 @@ var (
If an image is not being used by a container, it will be removed from the system.`
pruneCmd = &cobra.Command{
Use: "prune",
- Args: cobra.NoArgs,
+ Args: validate.NoArgs,
Short: "Remove unused images",
Long: pruneDescription,
RunE: prune,
diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go
index fb107d00c..fead5f7ed 100644
--- a/cmd/podman/images/pull.go
+++ b/cmd/podman/images/pull.go
@@ -2,11 +2,13 @@ package images
import (
"fmt"
+ "os"
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"
)
@@ -91,18 +93,22 @@ func pullFlags(flags *pflag.FlagSet) {
// imagePull is implement the command for pulling images.
func imagePull(cmd *cobra.Command, args []string) error {
- pullOptsAPI := pullOptions.ImagePullOptions
// TLS verification in c/image is controlled via a `types.OptionalBool`
// which allows for distinguishing among set-true, set-false, unspecified
// which is important to implement a sane way of dealing with defaults of
// boolean CLI flags.
if cmd.Flags().Changed("tls-verify") {
- pullOptsAPI.TLSVerify = types.NewOptionalBool(pullOptions.TLSVerifyCLI)
+ pullOptions.SkipTLSVerify = types.NewOptionalBool(!pullOptions.TLSVerifyCLI)
+ }
+ if pullOptions.Authfile != "" {
+ if _, err := os.Stat(pullOptions.Authfile); err != nil {
+ return errors.Wrapf(err, "error getting authfile %s", pullOptions.Authfile)
+ }
}
// Let's do all the remaining Yoga in the API to prevent us from
// scattering logic across (too) many parts of the code.
- pullReport, err := registry.ImageEngine().Pull(registry.GetContext(), args[0], pullOptsAPI)
+ pullReport, err := registry.ImageEngine().Pull(registry.GetContext(), args[0], pullOptions.ImagePullOptions)
if err != nil {
return err
}
diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go
index f12a5ac86..0b3502d61 100644
--- a/cmd/podman/images/push.go
+++ b/cmd/podman/images/push.go
@@ -1,6 +1,8 @@
package images
import (
+ "os"
+
buildahcli "github.com/containers/buildah/pkg/cli"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/cmd/podman/registry"
@@ -96,6 +98,7 @@ func imagePush(cmd *cobra.Command, args []string) error {
switch len(args) {
case 1:
source = args[0]
+ destination = args[0]
case 2:
source = args[0]
destination = args[1]
@@ -105,16 +108,21 @@ func imagePush(cmd *cobra.Command, args []string) error {
return errors.New("push requires at least one image name, or optionally a second to specify a different destination")
}
- pushOptsAPI := pushOptions.ImagePushOptions
// TLS verification in c/image is controlled via a `types.OptionalBool`
// which allows for distinguishing among set-true, set-false, unspecified
// which is important to implement a sane way of dealing with defaults of
// boolean CLI flags.
if cmd.Flags().Changed("tls-verify") {
- pushOptsAPI.TLSVerify = types.NewOptionalBool(pushOptions.TLSVerifyCLI)
+ pushOptions.SkipTLSVerify = types.NewOptionalBool(!pushOptions.TLSVerifyCLI)
+ }
+
+ if pushOptions.Authfile != "" {
+ if _, err := os.Stat(pushOptions.Authfile); err != nil {
+ return errors.Wrapf(err, "error getting authfile %s", pushOptions.Authfile)
+ }
}
// Let's do all the remaining Yoga in the API to prevent us from scattering
// logic across (too) many parts of the code.
- return registry.ImageEngine().Push(registry.GetContext(), source, destination, pushOptsAPI)
+ return registry.ImageEngine().Push(registry.GetContext(), source, destination, pushOptions.ImagePushOptions)
}
diff --git a/cmd/podman/images/rm.go b/cmd/podman/images/rm.go
index 135fda387..1cf5fa365 100644
--- a/cmd/podman/images/rm.go
+++ b/cmd/podman/images/rm.go
@@ -2,7 +2,6 @@ package images
import (
"fmt"
- "os"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
@@ -23,7 +22,7 @@ var (
podman image rm c4dfb1609ee2 93fd78260bd1 c0ed59d05ff7`,
}
- imageOpts = entities.ImageDeleteOptions{}
+ imageOpts = entities.ImageRemoveOptions{}
)
func init() {
@@ -40,32 +39,28 @@ 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 {
+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
+
+ report, err := registry.ImageEngine().Remove(registry.GetContext(), args, imageOpts)
+ if report != nil {
+ for _, u := range report.Untagged {
+ fmt.Println("Untagged: " + u)
}
+ for _, d := range report.Deleted {
+ // Make sure an image was deleted (and not just untagged); else print it
+ if len(d) > 0 {
+ fmt.Println("Deleted: " + d)
+ }
+ }
+ registry.SetExitCode(report.ExitCode)
}
- for _, u := range report.Untagged {
- fmt.Println("Untagged: " + u)
- }
- for _, d := range report.Deleted {
- fmt.Println("Deleted: " + d)
- }
- return nil
+ return err
}
diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go
index fdad94d45..a8abfb339 100644
--- a/cmd/podman/images/search.go
+++ b/cmd/podman/images/search.go
@@ -1,6 +1,7 @@
package images
import (
+ "os"
"reflect"
"strings"
@@ -47,14 +48,15 @@ var (
// Command: podman image search
imageSearchCmd = &cobra.Command{
- Use: searchCmd.Use,
- Short: searchCmd.Short,
- Long: searchCmd.Long,
- RunE: searchCmd.RunE,
- Args: searchCmd.Args,
+ Use: searchCmd.Use,
+ Short: searchCmd.Short,
+ Long: searchCmd.Long,
+ RunE: searchCmd.RunE,
+ Args: searchCmd.Args,
+ Annotations: searchCmd.Annotations,
Example: `podman image search --filter=is-official --limit 3 alpine
- podman image search registry.fedoraproject.org/ # only works with v2 registries
- podman image search --format "table {{.Index}} {{.Name}}" registry.fedoraproject.org/fedora`,
+ podman image search registry.fedoraproject.org/ # only works with v2 registries
+ podman image search --format "table {{.Index}} {{.Name}}" registry.fedoraproject.org/fedora`,
}
)
@@ -103,16 +105,21 @@ func imageSearch(cmd *cobra.Command, args []string) error {
return errors.Errorf("search requires exactly one argument")
}
- sarchOptsAPI := searchOptions.ImageSearchOptions
// TLS verification in c/image is controlled via a `types.OptionalBool`
// which allows for distinguishing among set-true, set-false, unspecified
// which is important to implement a sane way of dealing with defaults of
// boolean CLI flags.
if cmd.Flags().Changed("tls-verify") {
- sarchOptsAPI.TLSVerify = types.NewOptionalBool(pullOptions.TLSVerifyCLI)
+ searchOptions.SkipTLSVerify = types.NewOptionalBool(!searchOptions.TLSVerifyCLI)
}
- searchReport, err := registry.ImageEngine().Search(registry.GetContext(), searchTerm, sarchOptsAPI)
+ if searchOptions.Authfile != "" {
+ if _, err := os.Stat(searchOptions.Authfile); err != nil {
+ return errors.Wrapf(err, "error getting authfile %s", searchOptions.Authfile)
+ }
+ }
+
+ searchReport, err := registry.ImageEngine().Search(registry.GetContext(), searchTerm, searchOptions.ImageSearchOptions)
if err != nil {
return err
}
diff --git a/cmd/podman/images/tree.go b/cmd/podman/images/tree.go
new file mode 100644
index 000000000..5e82e9dea
--- /dev/null
+++ b/cmd/podman/images/tree.go
@@ -0,0 +1,40 @@
+package images
+
+import (
+ "fmt"
+
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/spf13/cobra"
+)
+
+var (
+ treeDescription = "Prints layer hierarchy of an image in a tree format"
+ treeCmd = &cobra.Command{
+ Use: "tree [flags] IMAGE",
+ Args: cobra.ExactArgs(1),
+ Short: treeDescription,
+ Long: treeDescription,
+ RunE: tree,
+ Example: "podman image tree alpine:latest",
+ }
+ treeOpts entities.ImageTreeOptions
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: treeCmd,
+ Parent: imageCmd,
+ })
+ treeCmd.Flags().BoolVar(&treeOpts.WhatRequires, "whatrequires", false, "Show all child images and layers of the specified image")
+}
+
+func tree(_ *cobra.Command, args []string) error {
+ results, err := registry.ImageEngine().Tree(registry.Context(), args[0], treeOpts)
+ if err != nil {
+ return err
+ }
+ fmt.Println(results.Tree)
+ return nil
+}
diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go
index 0393303e8..a5fdaedc2 100644
--- a/cmd/podman/inspect.go
+++ b/cmd/podman/inspect.go
@@ -1,31 +1,26 @@
package main
import (
- "context"
- "fmt"
-
- "github.com/containers/libpod/cmd/podman/common"
- "github.com/containers/libpod/cmd/podman/containers"
- "github.com/containers/libpod/cmd/podman/images"
+ "github.com/containers/libpod/cmd/podman/inspect"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)
-// Inspect is one of the outlier commands in that it operates on images/containers/...
-
var (
- inspectOpts *entities.InspectOptions
-
// Command: podman _inspect_ Object_ID
inspectCmd = &cobra.Command{
Use: "inspect [flags] {CONTAINER_ID | IMAGE_ID}",
- 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,
+ RunE: inspectExec,
+ Example: `podman inspect fedora
+ podman inspect --type image fedora
+ podman inspect CtrID ImgID
+ podman inspect --format "imageId: {{.Id}} size: {{.Size}}" fedora`,
}
+ inspectOpts *entities.InspectOptions
)
func init() {
@@ -33,20 +28,9 @@ func init() {
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: inspectCmd,
})
- inspectOpts = common.AddInspectFlagSet(inspectCmd)
+ inspectOpts = inspect.AddInspectFlagSet(inspectCmd)
}
-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)
- }
-
- 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 fmt.Errorf("%s not found on system", args[0])
+func inspectExec(cmd *cobra.Command, args []string) error {
+ return inspect.Inspect(args, *inspectOpts)
}
diff --git a/cmd/podman/inspect/inspect.go b/cmd/podman/inspect/inspect.go
new file mode 100644
index 000000000..223ce00f0
--- /dev/null
+++ b/cmd/podman/inspect/inspect.go
@@ -0,0 +1,159 @@
+package inspect
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "github.com/containers/buildah/pkg/formats"
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+const (
+ // ImageType is the image type.
+ ImageType = "image"
+ // ContainerType is the container type.
+ ContainerType = "container"
+ // AllType can be of type ImageType or ContainerType.
+ AllType = "all"
+)
+
+// AddInspectFlagSet takes a command and adds the inspect flags and returns an
+// InspectOptions object.
+func AddInspectFlagSet(cmd *cobra.Command) *entities.InspectOptions {
+ opts := entities.InspectOptions{}
+
+ flags := cmd.Flags()
+ flags.BoolVarP(&opts.Size, "size", "s", false, "Display total file size")
+ flags.StringVarP(&opts.Format, "format", "f", "json", "Format the output to a Go template or json")
+ flags.StringVarP(&opts.Type, "type", "t", AllType, fmt.Sprintf("Specify inspect-oject type (%q, %q or %q)", ImageType, ContainerType, AllType))
+ flags.BoolVarP(&opts.Latest, "latest", "l", false, "Act on the latest container Podman is aware of")
+
+ return &opts
+}
+
+// Inspect inspects the specified container/image names or IDs.
+func Inspect(namesOrIDs []string, options entities.InspectOptions) error {
+ inspector, err := newInspector(options)
+ if err != nil {
+ return err
+ }
+ return inspector.inspect(namesOrIDs)
+}
+
+// inspector allows for inspecting images and containers.
+type inspector struct {
+ containerEngine entities.ContainerEngine
+ imageEngine entities.ImageEngine
+ options entities.InspectOptions
+}
+
+// newInspector creates a new inspector based on the specified options.
+func newInspector(options entities.InspectOptions) (*inspector, error) {
+ switch options.Type {
+ case ImageType, ContainerType, AllType:
+ // Valid types.
+ default:
+ return nil, errors.Errorf("invalid type %q: must be %q, %q or %q", options.Type, ImageType, ContainerType, AllType)
+ }
+ if options.Type == ImageType {
+ if options.Latest {
+ return nil, errors.Errorf("latest is not supported for type %q", ImageType)
+ }
+ if options.Size {
+ return nil, errors.Errorf("size is not supported for type %q", ImageType)
+ }
+ }
+ return &inspector{
+ containerEngine: registry.ContainerEngine(),
+ imageEngine: registry.ImageEngine(),
+ options: options,
+ }, nil
+}
+
+// inspect inspects the specified container/image names or IDs.
+func (i *inspector) inspect(namesOrIDs []string) error {
+ // data - dumping place for inspection results.
+ var data []interface{}
+ ctx := context.Background()
+
+ if len(namesOrIDs) == 0 {
+ if !i.options.Latest {
+ return errors.New("no containers or images specified")
+ }
+ }
+
+ tmpType := i.options.Type
+ if i.options.Latest {
+ if len(namesOrIDs) > 0 {
+ return errors.New("latest and containers are not allowed")
+ }
+ tmpType = ContainerType // -l works with --type=all
+ }
+
+ // Inspect - note that AllType requires us to expensively query one-by-one.
+ switch tmpType {
+ case AllType:
+ all, err := i.inspectAll(ctx, namesOrIDs)
+ if err != nil {
+ return err
+ }
+ data = all
+ case ImageType:
+ imgData, err := i.imageEngine.Inspect(ctx, namesOrIDs, i.options)
+ if err != nil {
+ return err
+ }
+ for i := range imgData {
+ data = append(data, imgData[i])
+ }
+ case ContainerType:
+ ctrData, err := i.containerEngine.ContainerInspect(ctx, namesOrIDs, i.options)
+ if err != nil {
+ return err
+ }
+ for i := range ctrData {
+ data = append(data, ctrData[i])
+ }
+ default:
+ return errors.Errorf("invalid type %q: must be %q, %q or %q", i.options.Type, ImageType, ContainerType, AllType)
+ }
+
+ var out formats.Writer
+ if i.options.Format == "json" || i.options.Format == "" { // "" for backwards compat
+ out = formats.JSONStructArray{Output: data}
+ } else {
+ out = formats.StdoutTemplateArray{Output: data, Template: inspectFormat(i.options.Format)}
+ }
+ return out.Out()
+}
+
+func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]interface{}, error) {
+ var data []interface{}
+ for _, name := range namesOrIDs {
+ imgData, err := i.imageEngine.Inspect(ctx, []string{name}, i.options)
+ if err == nil {
+ data = append(data, imgData[0])
+ continue
+ }
+ ctrData, err := i.containerEngine.ContainerInspect(ctx, []string{name}, i.options)
+ if err != nil {
+ return nil, err
+ }
+ data = append(data, ctrData[0])
+ }
+ return data, nil
+}
+
+func inspectFormat(row string) string {
+ r := strings.NewReplacer(
+ "{{.Id}}", formats.IDString,
+ ".Src", ".Source",
+ ".Dst", ".Destination",
+ ".ImageID", ".Image",
+ )
+ return r.Replace(row)
+}
diff --git a/cmd/podman/login.go b/cmd/podman/login.go
new file mode 100644
index 000000000..1843a764d
--- /dev/null
+++ b/cmd/podman/login.go
@@ -0,0 +1,68 @@
+package main
+
+import (
+ "context"
+ "os"
+
+ "github.com/containers/common/pkg/auth"
+ "github.com/containers/image/v5/types"
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/spf13/cobra"
+)
+
+type loginOptionsWrapper struct {
+ auth.LoginOptions
+ tlsVerify bool
+}
+
+var (
+ loginOptions = loginOptionsWrapper{}
+ loginCommand = &cobra.Command{
+ Use: "login [flags] REGISTRY",
+ Short: "Login to a container registry",
+ Long: "Login to a container registry on a specified server.",
+ RunE: login,
+ Args: cobra.ExactArgs(1),
+ Example: `podman login quay.io
+ podman login --username ... --password ... quay.io
+ podman login --authfile dir/auth.json quay.io`,
+ }
+)
+
+func init() {
+ // Note that the local and the remote client behave the same: both
+ // store credentials locally while the remote client will pass them
+ // over the wire to the endpoint.
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: loginCommand,
+ })
+ flags := loginCommand.Flags()
+
+ // Flags from the auth package.
+ flags.AddFlagSet(auth.GetLoginFlags(&loginOptions.LoginOptions))
+
+ // Podman flags.
+ flags.BoolVarP(&loginOptions.tlsVerify, "tls-verify", "", false, "Require HTTPS and verify certificates when contacting registries")
+ flags.BoolVarP(&loginOptions.GetLoginSet, "get-login", "", false, "Return the current login user for the registry")
+ loginOptions.Stdin = os.Stdin
+ loginOptions.Stdout = os.Stdout
+}
+
+// Implementation of podman-login.
+func login(cmd *cobra.Command, args []string) error {
+ var skipTLS types.OptionalBool
+
+ if cmd.Flags().Changed("tls-verify") {
+ skipTLS = types.NewOptionalBool(!loginOptions.tlsVerify)
+ }
+
+ sysCtx := types.SystemContext{
+ AuthFilePath: loginOptions.AuthFile,
+ DockerCertPath: loginOptions.CertDir,
+ DockerInsecureSkipTLSVerify: skipTLS,
+ }
+
+ return auth.Login(context.Background(), &sysCtx, &loginOptions.LoginOptions, args[0])
+}
diff --git a/cmd/podman/logout.go b/cmd/podman/logout.go
new file mode 100644
index 000000000..77bdc92b4
--- /dev/null
+++ b/cmd/podman/logout.go
@@ -0,0 +1,57 @@
+package main
+
+import (
+ "os"
+
+ "github.com/containers/common/pkg/auth"
+ "github.com/containers/image/v5/types"
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ logoutOptions = auth.LogoutOptions{}
+ logoutCommand = &cobra.Command{
+ Use: "logout [flags] REGISTRY",
+ Short: "Logout of a container registry",
+ Long: "Remove the cached username and password for the registry.",
+ RunE: logout,
+ Args: cobra.MaximumNArgs(1),
+ Example: `podman logout quay.io
+ podman logout --authfile dir/auth.json quay.io
+ podman logout --all`,
+ }
+)
+
+func init() {
+ // Note that the local and the remote client behave the same: both
+ // store credentials locally while the remote client will pass them
+ // over the wire to the endpoint.
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: logoutCommand,
+ })
+ flags := logoutCommand.Flags()
+
+ // Flags from the auth package.
+ flags.AddFlagSet(auth.GetLogoutFlags(&logoutOptions))
+ logoutOptions.Stdin = os.Stdin
+ logoutOptions.Stdout = os.Stdout
+}
+
+// Implementation of podman-logout.
+func logout(cmd *cobra.Command, args []string) error {
+ sysCtx := types.SystemContext{AuthFilePath: logoutOptions.AuthFile}
+
+ registry := ""
+ if len(args) > 0 {
+ if logoutOptions.All {
+ return errors.New("--all takes no arguments")
+ }
+ registry = args[0]
+ }
+
+ return auth.Logout(&sysCtx, &logoutOptions, registry)
+}
diff --git a/cmd/podman/main.go b/cmd/podman/main.go
index 5f7bfe3c9..481214a38 100644
--- a/cmd/podman/main.go
+++ b/cmd/podman/main.go
@@ -4,9 +4,10 @@ import (
"os"
_ "github.com/containers/libpod/cmd/podman/containers"
+ _ "github.com/containers/libpod/cmd/podman/generate"
_ "github.com/containers/libpod/cmd/podman/healthcheck"
_ "github.com/containers/libpod/cmd/podman/images"
- _ "github.com/containers/libpod/cmd/podman/networks"
+ _ "github.com/containers/libpod/cmd/podman/manifest"
_ "github.com/containers/libpod/cmd/podman/pods"
"github.com/containers/libpod/cmd/podman/registry"
_ "github.com/containers/libpod/cmd/podman/system"
@@ -14,12 +15,6 @@ import (
"github.com/containers/storage/pkg/reexec"
)
-func init() {
- // This is the bootstrap configuration, if user gives
- // CLI flags parts of this configuration may be overwritten
- registry.PodmanOptions = registry.NewPodmanConfig()
-}
-
func main() {
if reexec.Init() {
// We were invoked with a different argv[0] indicating that we
@@ -27,9 +22,10 @@ func main() {
return
}
+ cfg := registry.PodmanConfig()
for _, c := range registry.Commands {
for _, m := range c.Mode {
- if registry.PodmanOptions.EngineMode == m {
+ if cfg.EngineMode == m {
parent := rootCmd
if c.Parent != nil {
parent = c.Parent
diff --git a/cmd/podman/manifest/add.go b/cmd/podman/manifest/add.go
new file mode 100644
index 000000000..38f832fad
--- /dev/null
+++ b/cmd/podman/manifest/add.go
@@ -0,0 +1,50 @@
+package manifest
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ manifestAddOpts = entities.ManifestAddOptions{}
+ addCmd = &cobra.Command{
+ Use: "add [flags] LIST LIST",
+ Short: "Add images to a manifest list or image index",
+ Long: "Adds an image to a manifest list or image index.",
+ RunE: add,
+ Example: `podman manifest add mylist:v1.11 image:v1.11-amd64
+ podman manifest add mylist:v1.11 transport:imageName`,
+ Args: cobra.ExactArgs(2),
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: addCmd,
+ Parent: manifestCmd,
+ })
+ flags := addCmd.Flags()
+ flags.BoolVar(&manifestAddOpts.All, "all", false, "add all of the list's images if the image is a list")
+ flags.StringSliceVar(&manifestAddOpts.Annotation, "annotation", nil, "set an `annotation` for the specified image")
+ flags.StringVar(&manifestAddOpts.Arch, "arch", "", "override the `architecture` of the specified image")
+ flags.StringSliceVar(&manifestAddOpts.Features, "features", nil, "override the `features` of the specified image")
+ flags.StringVar(&manifestAddOpts.OS, "os", "", "override the `OS` of the specified image")
+ flags.StringVar(&manifestAddOpts.OSVersion, "os-version", "", "override the OS `version` of the specified image")
+ flags.StringVar(&manifestAddOpts.Variant, "variant", "", "override the `Variant` of the specified image")
+}
+
+func add(cmd *cobra.Command, args []string) error {
+ manifestAddOpts.Images = []string{args[1], args[0]}
+ listID, err := registry.ImageEngine().ManifestAdd(context.Background(), manifestAddOpts)
+ if err != nil {
+ return errors.Wrapf(err, "error adding to manifest list %s", args[0])
+ }
+ fmt.Printf("%s\n", listID)
+ return nil
+}
diff --git a/cmd/podman/manifest/create.go b/cmd/podman/manifest/create.go
new file mode 100644
index 000000000..9c0097b90
--- /dev/null
+++ b/cmd/podman/manifest/create.go
@@ -0,0 +1,44 @@
+package manifest
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ manifestCreateOpts = entities.ManifestCreateOptions{}
+ createCmd = &cobra.Command{
+ Use: "create [flags] LIST [IMAGE]",
+ Short: "Create manifest list or image index",
+ Long: "Creates manifest lists or image indexes.",
+ RunE: create,
+ Example: `podman manifest create mylist:v1.11
+ podman manifest create mylist:v1.11 arch-specific-image-to-add
+ podman manifest create --all mylist:v1.11 transport:tagged-image-to-add`,
+ Args: cobra.MinimumNArgs(1),
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: createCmd,
+ Parent: manifestCmd,
+ })
+ flags := createCmd.Flags()
+ flags.BoolVar(&manifestCreateOpts.All, "all", false, "add all of the lists' images if the images to add are lists")
+}
+
+func create(cmd *cobra.Command, args []string) error {
+ imageID, err := registry.ImageEngine().ManifestCreate(context.Background(), args[:1], args[1:], manifestCreateOpts)
+ if err != nil {
+ return errors.Wrapf(err, "error creating manifest %s", args[0])
+ }
+ fmt.Printf("%s\n", imageID)
+ return nil
+}
diff --git a/cmd/podman/manifest/inspect.go b/cmd/podman/manifest/inspect.go
new file mode 100644
index 000000000..5112aa5b2
--- /dev/null
+++ b/cmd/podman/manifest/inspect.go
@@ -0,0 +1,39 @@
+package manifest
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ inspectCmd = &cobra.Command{
+ Use: "inspect [flags] IMAGE",
+ Short: "Display the contents of a manifest list or image index",
+ Long: "Display the contents of a manifest list or image index.",
+ RunE: inspect,
+ Example: "podman manifest inspect localhost/list",
+ Args: cobra.ExactArgs(1),
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: inspectCmd,
+ Parent: manifestCmd,
+ })
+}
+
+func inspect(cmd *cobra.Command, args []string) error {
+ buf, err := registry.ImageEngine().ManifestInspect(context.Background(), args[0])
+ if err != nil {
+ return errors.Wrapf(err, "error inspect manifest %s", args[0])
+ }
+ fmt.Printf("%s\n", buf)
+ return nil
+}
diff --git a/cmd/podman/manifest/manifest.go b/cmd/podman/manifest/manifest.go
new file mode 100644
index 000000000..b78879b34
--- /dev/null
+++ b/cmd/podman/manifest/manifest.go
@@ -0,0 +1,28 @@
+package manifest
+
+import (
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/spf13/cobra"
+)
+
+var (
+ manifestDescription = "Creates, modifies, and pushes manifest lists and image indexes."
+ manifestCmd = &cobra.Command{
+ Use: "manifest",
+ Short: "Manipulate manifest lists and image indexes",
+ Long: manifestDescription,
+ TraverseChildren: true,
+ RunE: validate.SubCommandExists,
+ Example: `podman manifest create localhost/list
+ podman manifest inspect localhost/list`,
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: manifestCmd,
+ })
+}
diff --git a/cmd/podman/networks/network.go b/cmd/podman/networks/network.go
index 3cee86bcc..e2a928312 100644
--- a/cmd/podman/networks/network.go
+++ b/cmd/podman/networks/network.go
@@ -2,6 +2,7 @@ package images
import (
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)
@@ -13,10 +14,13 @@ var (
Short: "Manage networks",
Long: "Manage networks",
TraverseChildren: true,
- RunE: registry.SubCommandExists,
+ RunE: validate.SubCommandExists,
}
)
+// TODO add the following to main.go to get networks back onto the
+// command list.
+// _ "github.com/containers/libpod/cmd/podman/networks"
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode},
diff --git a/cmd/podman/parse/net.go b/cmd/podman/parse/net.go
index 03cda268c..f93c4ab1e 100644
--- a/cmd/podman/parse/net.go
+++ b/cmd/podman/parse/net.go
@@ -1,4 +1,4 @@
-//nolint
+// nolint
// most of these validate and parse functions have been taken from projectatomic/docker
// and modified for cri-o
package parse
@@ -46,7 +46,7 @@ var (
// validateExtraHost validates that the specified string is a valid extrahost and returns it.
// ExtraHost is in the form of name:ip where the ip has to be a valid ip (ipv4 or ipv6).
// for add-host flag
-func ValidateExtraHost(val string) (string, error) { //nolint
+func ValidateExtraHost(val string) (string, error) { // nolint
// allow for IPv6 addresses in extra hosts by only splitting on first ":"
arr := strings.SplitN(val, ":", 2)
if len(arr) != 2 || len(arr[0]) == 0 {
diff --git a/cmd/podman/parse/net_test.go b/cmd/podman/parse/net_test.go
index a6ddc2be9..51c8509df 100644
--- a/cmd/podman/parse/net_test.go
+++ b/cmd/podman/parse/net_test.go
@@ -1,4 +1,4 @@
-//nolint
+// nolint
// most of these validate and parse functions have been taken from projectatomic/docker
// and modified for cri-o
package parse
@@ -41,7 +41,7 @@ func TestValidateExtraHost(t *testing.T) {
want string
wantErr bool
}{
- //2001:0db8:85a3:0000:0000:8a2e:0370:7334
+ // 2001:0db8:85a3:0000:0000:8a2e:0370:7334
{name: "good-ipv4", args: args{val: "foobar:192.168.1.1"}, want: "foobar:192.168.1.1", wantErr: false},
{name: "bad-ipv4", args: args{val: "foobar:999.999.999.99"}, want: "", wantErr: true},
{name: "bad-ipv4", args: args{val: "foobar:999.999.999"}, want: "", wantErr: true},
diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go
index 63dab4707..85b96d37b 100644
--- a/cmd/podman/pods/create.go
+++ b/cmd/podman/pods/create.go
@@ -9,7 +9,7 @@ import (
"github.com/containers/libpod/cmd/podman/common"
"github.com/containers/libpod/cmd/podman/parse"
"github.com/containers/libpod/cmd/podman/registry"
- "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/errorhandling"
"github.com/containers/libpod/pkg/specgen"
@@ -25,7 +25,7 @@ var (
createCommand = &cobra.Command{
Use: "create",
- Args: cobra.NoArgs,
+ Args: validate.NoArgs,
Short: "Create a new empty pod",
Long: podCreateDescription,
RunE: create,
@@ -50,8 +50,8 @@ func init() {
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.StringVar(&createOptions.InfraImage, "infra-image", containerConfig.Engine.InfraImage, "The image of the infra container to associate with the pod")
+ flags.StringVar(&createOptions.InfraCommand, "infra-command", containerConfig.Engine.InfraCommand, "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")
@@ -70,10 +70,24 @@ func create(cmd *cobra.Command, args []string) error {
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")
+ 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 && share != "none" && share != "" {
+ return fmt.Errorf("cannot set share(%s) namespaces without an infra container", cmd.Flag("share").Value)
+ }
+ createOptions.Share = nil
+ } else {
+ createOptions.Share = strings.Split(share, ",")
}
- createOptions.Share = strings.Split(share, ",")
+
if cmd.Flag("pod-id-file").Changed {
podIdFile, err = util.OpenExclusiveFile(podIDFile)
if err != nil && os.IsExist(err) {
@@ -103,7 +117,7 @@ func create(cmd *cobra.Command, args []string) error {
case "slip4netns":
n.NSMode = specgen.Slirp
default:
- if strings.HasPrefix(netInput, "container:") { //nolint
+ if strings.HasPrefix(netInput, "container:") { // nolint
split := strings.Split(netInput, ":")
if len(split) != 2 {
return errors.Errorf("invalid network paramater: %q", netInput)
@@ -123,21 +137,6 @@ func create(cmd *cobra.Command, args []string) error {
}
}
- 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
diff --git a/cmd/podman/pods/exists.go b/cmd/podman/pods/exists.go
index ad0e28b90..cf3e3eae5 100644
--- a/cmd/podman/pods/exists.go
+++ b/cmd/podman/pods/exists.go
@@ -2,7 +2,6 @@ package pods
import (
"context"
- "os"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
@@ -20,6 +19,7 @@ var (
Args: cobra.ExactArgs(1),
Example: `podman pod exists podID
podman pod exists mypod || podman pod create --name mypod`,
+ DisableFlagsInUseLine: true,
}
)
@@ -37,7 +37,7 @@ func exists(cmd *cobra.Command, args []string) error {
return err
}
if !response.Value {
- os.Exit(1)
+ registry.SetExitCode(1)
}
return nil
}
diff --git a/cmd/podman/pods/inspect.go b/cmd/podman/pods/inspect.go
index 901ae50b2..1e333247b 100644
--- a/cmd/podman/pods/inspect.go
+++ b/cmd/podman/pods/inspect.go
@@ -6,7 +6,6 @@ import (
"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"
)
@@ -55,7 +54,7 @@ func inspect(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
- b, err := jsoniter.MarshalIndent(responses, "", " ")
+ b, err := json.MarshalIndent(responses, "", " ")
if err != nil {
return err
}
diff --git a/cmd/podman/pods/pod.go b/cmd/podman/pods/pod.go
index 1cac50e40..edca08202 100644
--- a/cmd/podman/pods/pod.go
+++ b/cmd/podman/pods/pod.go
@@ -2,19 +2,25 @@ package pods
import (
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/util"
"github.com/spf13/cobra"
)
var (
+ // Pull in configured json library
+ json = registry.JsonLibrary()
+
// Command: podman _pod_
podCmd = &cobra.Command{
Use: "pod",
Short: "Manage pods",
Long: "Manage pods",
TraverseChildren: true,
- RunE: registry.SubCommandExists,
+ RunE: validate.SubCommandExists,
}
+ containerConfig = util.DefaultContainerConfig()
)
func init() {
diff --git a/cmd/podman/pods/prune.go b/cmd/podman/pods/prune.go
new file mode 100644
index 000000000..091e3375b
--- /dev/null
+++ b/cmd/podman/pods/prune.go
@@ -0,0 +1,75 @@
+package pods
+
+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 (
+ pruneOptions = entities.PodPruneOptions{}
+)
+
+var (
+ pruneDescription = fmt.Sprintf(`podman pod prune Removes all exited pods`)
+
+ pruneCommand = &cobra.Command{
+ Use: "prune [flags]",
+ Short: "Remove all stopped pods and their containers",
+ Long: pruneDescription,
+ RunE: prune,
+ Example: `podman pod prune`,
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: pruneCommand,
+ Parent: podCmd,
+ })
+ flags := pruneCommand.Flags()
+ flags.BoolVarP(&pruneOptions.Force, "force", "f", false, "Do not prompt for confirmation. The default is false")
+}
+
+func prune(cmd *cobra.Command, args []string) error {
+ var (
+ errs utils.OutputErrors
+ )
+ if len(args) > 0 {
+ return errors.Errorf("`%s` takes no arguments", cmd.CommandPath())
+ }
+ if !pruneOptions.Force {
+ reader := bufio.NewReader(os.Stdin)
+ fmt.Println("WARNING! This will remove all stopped/exited pods..")
+ 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().PodPrune(context.Background(), pruneOptions)
+
+ if err != nil {
+ return err
+ }
+ for _, r := range responses {
+ if r.Err == nil {
+ fmt.Println(r.Id)
+ } else {
+ errs = append(errs, r.Err)
+ }
+ }
+ return errs.PrintErrors()
+}
diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go
index 8cb7b6266..b97dfeb66 100644
--- a/cmd/podman/pods/ps.go
+++ b/cmd/podman/pods/ps.go
@@ -2,19 +2,19 @@ package pods
import (
"context"
- "encoding/json"
"fmt"
"io"
"os"
+ "sort"
"strings"
"text/tabwriter"
"text/template"
"time"
- "github.com/docker/go-units"
-
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
+ "github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -29,12 +29,13 @@ var (
Short: "list pods",
Long: psDescription,
RunE: pods,
+ Args: validate.NoArgs,
}
)
var (
- defaultHeaders string = "POD ID\tNAME\tSTATUS\tCREATED"
- inputFilters string
+ defaultHeaders = "POD ID\tNAME\tSTATUS\tCREATED"
+ inputFilters []string
noTrunc bool
psInput entities.PodPSOptions
)
@@ -50,7 +51,7 @@ func init() {
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.StringSliceVarP(&inputFilters, "filter", "f", []string{}, "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")
@@ -69,8 +70,13 @@ func pods(cmd *cobra.Command, args []string) error {
row string
lpr []ListPodReporter
)
+
+ if psInput.Quiet && len(psInput.Format) > 0 {
+ return errors.New("quiet and format cannot be used together")
+ }
if cmd.Flag("filter").Changed {
- for _, f := range strings.Split(inputFilters, ",") {
+ psInput.Filters = make(map[string][]string)
+ for _, f := range 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)
@@ -83,6 +89,10 @@ func pods(cmd *cobra.Command, args []string) error {
return err
}
+ if err := sortPodPsOutput(psInput.Sort, responses); err != nil {
+ return err
+ }
+
if psInput.Format == "json" {
b, err := json.MarshalIndent(responses, "", " ")
if err != nil {
@@ -97,11 +107,7 @@ func pods(cmd *cobra.Command, args []string) error {
}
headers, row := createPodPsOut()
if psInput.Quiet {
- if noTrunc {
- row = "{{.Id}}\n"
- } else {
- row = "{{slice .Id 0 12}}\n"
- }
+ row = "{{.Id}}\n"
}
if cmd.Flag("format").Changed {
row = psInput.Format
@@ -132,11 +138,7 @@ func pods(cmd *cobra.Command, args []string) error {
func createPodPsOut() (string, string) {
var row string
headers := defaultHeaders
- if noTrunc {
- row += "{{.Id}}"
- } else {
- row += "{{slice .Id 0 12}}"
- }
+ row += "{{.Id}}"
row += "\t{{.Name}}\t{{.Status}}\t{{.Created}}"
@@ -162,11 +164,7 @@ func createPodPsOut() (string, string) {
}
headers += "\tINFRA ID\n"
- if noTrunc {
- row += "\t{{.InfraId}}\n"
- } else {
- row += "\t{{slice .InfraId 0 12}}\n"
- }
+ row += "\t{{.InfraId}}\n"
return headers, row
}
@@ -186,6 +184,19 @@ func (l ListPodReporter) NumberOfContainers() int {
return len(l.Containers)
}
+// ID is a wrapper to Id for compat, typos
+func (l ListPodReporter) ID() string {
+ return l.Id()
+}
+
+// Id returns the Pod id
+func (l ListPodReporter) Id() string {
+ if noTrunc {
+ return l.ListPodsReport.Id
+ }
+ return l.ListPodsReport.Id[0:12]
+}
+
// Added for backwards compatibility with podmanv1
func (l ListPodReporter) InfraID() string {
return l.InfraId()
@@ -194,6 +205,9 @@ func (l ListPodReporter) InfraID() string {
// InfraId returns the infra container id for the pod
// depending on trunc
func (l ListPodReporter) InfraId() string {
+ if len(l.ListPodsReport.InfraId) == 0 {
+ return ""
+ }
if noTrunc {
return l.ListPodsReport.InfraId
}
@@ -227,3 +241,52 @@ func (l ListPodReporter) ContainerStatuses() string {
}
return strings.Join(statuses, ",")
}
+
+func sortPodPsOutput(sortBy string, lprs []*entities.ListPodsReport) error {
+ switch sortBy {
+ case "created":
+ sort.Sort(podPsSortedCreated{lprs})
+ case "id":
+ sort.Sort(podPsSortedId{lprs})
+ case "name":
+ sort.Sort(podPsSortedName{lprs})
+ case "number":
+ sort.Sort(podPsSortedNumber{lprs})
+ case "status":
+ sort.Sort(podPsSortedStatus{lprs})
+ default:
+ return errors.Errorf("invalid option for --sort, options are: id, names, or number")
+ }
+ return nil
+}
+
+type lprSort []*entities.ListPodsReport
+
+func (a lprSort) Len() int { return len(a) }
+func (a lprSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+
+type podPsSortedCreated struct{ lprSort }
+
+func (a podPsSortedCreated) Less(i, j int) bool {
+ return a.lprSort[i].Created.After(a.lprSort[j].Created)
+}
+
+type podPsSortedId struct{ lprSort }
+
+func (a podPsSortedId) Less(i, j int) bool { return a.lprSort[i].Id < a.lprSort[j].Id }
+
+type podPsSortedNumber struct{ lprSort }
+
+func (a podPsSortedNumber) Less(i, j int) bool {
+ return len(a.lprSort[i].Containers) < len(a.lprSort[j].Containers)
+}
+
+type podPsSortedName struct{ lprSort }
+
+func (a podPsSortedName) Less(i, j int) bool { return a.lprSort[i].Name < a.lprSort[j].Name }
+
+type podPsSortedStatus struct{ lprSort }
+
+func (a podPsSortedStatus) Less(i, j int) bool {
+ return a.lprSort[i].Status < a.lprSort[j].Status
+}
diff --git a/cmd/podman/pods/rm.go b/cmd/podman/pods/rm.go
index ea3a6476a..4b9882f8a 100644
--- a/cmd/podman/pods/rm.go
+++ b/cmd/podman/pods/rm.go
@@ -41,10 +41,10 @@ func init() {
})
flags := rmCommand.Flags()
- flags.BoolVarP(&rmOptions.All, "all", "a", false, "Restart all running pods")
+ flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all running pods")
flags.BoolVarP(&rmOptions.Force, "force", "f", false, "Force removal of a running pod by first stopping all containers, then removing all containers in the pod. The default is false")
flags.BoolVarP(&rmOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified pod is missing")
- flags.BoolVarP(&rmOptions.Latest, "latest", "l", false, "Restart the latest pod podman is aware of")
+ flags.BoolVarP(&rmOptions.Latest, "latest", "l", false, "Remove the latest pod podman is aware of")
if registry.IsRemote() {
_ = flags.MarkHidden("latest")
_ = flags.MarkHidden("ignore")
diff --git a/cmd/podman/pods/stats.go b/cmd/podman/pods/stats.go
new file mode 100644
index 000000000..7c3597d9a
--- /dev/null
+++ b/cmd/podman/pods/stats.go
@@ -0,0 +1,189 @@
+package pods
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "reflect"
+ "strings"
+ "text/tabwriter"
+ "text/template"
+ "time"
+
+ "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/containers/libpod/pkg/util/camelcase"
+ "github.com/spf13/cobra"
+)
+
+type podStatsOptionsWrapper struct {
+ entities.PodStatsOptions
+
+ // Format - pretty-print to JSON or a go template.
+ Format string
+ // NoReset - do not reset the screen when streaming.
+ NoReset bool
+ // NoStream - do not stream stats but write them once.
+ NoStream bool
+}
+
+var (
+ statsOptions = podStatsOptionsWrapper{}
+ statsDescription = `Display the containers' resource-usage statistics of one or more running pod`
+ // Command: podman pod _pod_
+ statsCmd = &cobra.Command{
+ Use: "stats [flags] [POD...]",
+ Short: "Display resource-usage statistics of pods",
+ Long: statsDescription,
+ RunE: stats,
+ Example: `podman pod stats
+ podman pod stats a69b23034235 named-pod
+ podman pod stats --latest
+ podman pod stats --all`,
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: statsCmd,
+ Parent: podCmd,
+ })
+
+ flags := statsCmd.Flags()
+ flags.BoolVarP(&statsOptions.All, "all", "a", false, "Provide stats for all pods")
+ flags.StringVar(&statsOptions.Format, "format", "", "Pretty-print container statistics to JSON or using a Go template")
+ flags.BoolVarP(&statsOptions.Latest, "latest", "l", false, "Provide stats on the latest pod Podman is aware of")
+ flags.BoolVar(&statsOptions.NoReset, "no-reset", false, "Disable resetting the screen when streaming")
+ flags.BoolVar(&statsOptions.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result")
+
+ if registry.IsRemote() {
+ _ = flags.MarkHidden("latest")
+ }
+}
+
+func stats(cmd *cobra.Command, args []string) error {
+ // Validate input.
+ if err := entities.ValidatePodStatsOptions(args, &statsOptions.PodStatsOptions); err != nil {
+ return err
+ }
+
+ format := statsOptions.Format
+ doJson := strings.ToLower(format) == formats.JSONString
+ header := getPodStatsHeader(format)
+
+ for {
+ reports, err := registry.ContainerEngine().PodStats(context.Background(), args, statsOptions.PodStatsOptions)
+ if err != nil {
+ return err
+ }
+ // Print the stats in the requested format and configuration.
+ if doJson {
+ if err := printJSONPodStats(reports); err != nil {
+ return err
+ }
+ } else {
+ if !statsOptions.NoReset {
+ goterm.Clear()
+ goterm.MoveCursor(1, 1)
+ goterm.Flush()
+ }
+ if len(format) == 0 {
+ printPodStatsLines(reports)
+ } else if err := printFormattedPodStatsLines(format, reports, header); err != nil {
+ return err
+ }
+ }
+ if statsOptions.NoStream {
+ break
+ }
+ time.Sleep(time.Second)
+ }
+
+ return nil
+}
+
+func printJSONPodStats(stats []*entities.PodStatsReport) error {
+ b, err := json.MarshalIndent(&stats, "", " ")
+ if err != nil {
+ return err
+ }
+ fmt.Fprintf(os.Stdout, "%s\n", string(b))
+ return nil
+}
+
+func printPodStatsLines(stats []*entities.PodStatsReport) {
+ 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 printFormattedPodStatsLines(format string, stats []*entities.PodStatsReport, 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 _, s := range stats {
+ if err := dataTmpl.Execute(w, s); err != nil {
+ return err
+ }
+ fmt.Fprintln(w, "")
+ }
+ // Flush the writer
+ return w.Flush()
+
+}
+
+// getPodStatsHeader returns the stats header for the specified options.
+func getPodStatsHeader(format string) map[string]string {
+ headerNames := make(map[string]string)
+ if format == "" {
+ return headerNames
+ }
+ // Make a map of the field names for the headers
+ v := reflect.ValueOf(entities.PodStatsReport{})
+ t := v.Type()
+ for i := 0; i < t.NumField(); i++ {
+ split := camelcase.Split(t.Field(i).Name)
+ value := strings.ToUpper(strings.Join(split, " "))
+ switch value {
+ case "CPU", "MEM":
+ value += " %"
+ case "MEM USAGE":
+ value = "MEM USAGE / LIMIT"
+ }
+ headerNames[t.Field(i).Name] = value
+ }
+ return headerNames
+}
diff --git a/cmd/podman/pods/stop.go b/cmd/podman/pods/stop.go
index 683d9c00a..daf05d640 100644
--- a/cmd/podman/pods/stop.go
+++ b/cmd/podman/pods/stop.go
@@ -47,11 +47,10 @@ func init() {
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")
+ flags.UintVarP(&timeout, "time", "t", containerConfig.Engine.StopTimeout, "Seconds to wait for pod stop before killing the container")
if registry.IsRemote() {
_ = flags.MarkHidden("latest")
_ = flags.MarkHidden("ignore")
-
}
flags.SetNormalizeFunc(utils.AliasFlags)
}
diff --git a/cmd/podman/registry/config.go b/cmd/podman/registry/config.go
index 358f9172e..fc6eb538e 100644
--- a/cmd/podman/registry/config.go
+++ b/cmd/podman/registry/config.go
@@ -6,6 +6,7 @@ import (
"path/filepath"
"runtime"
"strings"
+ "sync"
"github.com/containers/common/pkg/config"
"github.com/containers/libpod/pkg/domain/entities"
@@ -19,11 +20,18 @@ const (
)
var (
- PodmanOptions entities.PodmanConfig
+ podmanOptions entities.PodmanConfig
+ podmanSync sync.Once
)
-// NewPodmanConfig creates a PodmanConfig from the environment
-func NewPodmanConfig() entities.PodmanConfig {
+// 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)
@@ -63,7 +71,7 @@ func NewPodmanConfig() entities.PodmanConfig {
cfg.Network.NetworkConfigDir = ""
}
- return entities.PodmanConfig{Config: cfg, EngineMode: mode}
+ podmanOptions = entities.PodmanConfig{Config: cfg, EngineMode: mode}
}
// SetXdgDirs ensures the XDG_RUNTIME_DIR env and XDG_CONFIG_HOME variables are set.
diff --git a/cmd/podman/registry/json.go b/cmd/podman/registry/json.go
new file mode 100644
index 000000000..f25406c3c
--- /dev/null
+++ b/cmd/podman/registry/json.go
@@ -0,0 +1,20 @@
+package registry
+
+import (
+ "sync"
+
+ jsoniter "github.com/json-iterator/go"
+)
+
+var (
+ json jsoniter.API
+ jsonSync sync.Once
+)
+
+// JsonLibrary provides a "encoding/json" compatible API
+func JsonLibrary() jsoniter.API {
+ jsonSync.Do(func() {
+ json = jsoniter.ConfigCompatibleWithStandardLibrary
+ })
+ return json
+}
diff --git a/cmd/podman/registry/registry.go b/cmd/podman/registry/registry.go
index 1c5e5d21b..69e2babfc 100644
--- a/cmd/podman/registry/registry.go
+++ b/cmd/podman/registry/registry.go
@@ -5,7 +5,6 @@ import (
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/infra"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -49,8 +48,8 @@ func ImageEngine() entities.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)
+ podmanOptions.FlagSet = cmd.Flags()
+ engine, err := infra.NewImageEngine(&podmanOptions)
if err != nil {
return nil, err
}
@@ -66,8 +65,8 @@ func ContainerEngine() entities.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)
+ podmanOptions.FlagSet = cmd.Flags()
+ engine, err := infra.NewContainerEngine(&podmanOptions)
if err != nil {
return nil, err
}
@@ -76,21 +75,6 @@ func NewContainerEngine(cmd *cobra.Command, args []string) (entities.ContainerEn
return containerEngine, nil
}
-func SubCommandExists(cmd *cobra.Command, args []string) error {
- if len(args) > 0 {
- return errors.Errorf("unrecognized command `%[1]s %[2]s`\nTry '%[1]s --help' for more information.", cmd.CommandPath(), args[0])
- }
- return errors.Errorf("missing command '%[1]s COMMAND'\nTry '%[1]s --help' for more information.", cmd.CommandPath())
-}
-
-// IdOrLatestArgs used to validate a nameOrId was provided or the "--latest" flag
-func IdOrLatestArgs(cmd *cobra.Command, args []string) error {
- if len(args) > 1 || (len(args) == 0 && !cmd.Flag("latest").Changed) {
- return errors.New(`command requires a name, id or the "--latest" flag`)
- }
- return nil
-}
-
type PodmanOptionsKey struct{}
func Context() context.Context {
@@ -101,7 +85,7 @@ func Context() context.Context {
}
func ContextWithOptions(ctx context.Context) context.Context {
- cliCtx = context.WithValue(ctx, PodmanOptionsKey{}, PodmanOptions)
+ cliCtx = context.WithValue(ctx, PodmanOptionsKey{}, podmanOptions)
return cliCtx
}
diff --git a/cmd/podman/registry/remote.go b/cmd/podman/registry/remote.go
index 5378701e7..95870750e 100644
--- a/cmd/podman/registry/remote.go
+++ b/cmd/podman/registry/remote.go
@@ -5,5 +5,5 @@ import (
)
func IsRemote() bool {
- return PodmanOptions.EngineMode == entities.TunnelMode
+ return podmanOptions.EngineMode == entities.TunnelMode
}
diff --git a/cmd/podman/report/diff.go b/cmd/podman/report/diff.go
index b36189d75..0730f06e8 100644
--- a/cmd/podman/report/diff.go
+++ b/cmd/podman/report/diff.go
@@ -6,7 +6,6 @@ import (
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/storage/pkg/archive"
- jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
)
@@ -31,7 +30,7 @@ func ChangesToJSON(diffs *entities.DiffReport) error {
}
}
- json := jsoniter.ConfigCompatibleWithStandardLibrary
+ // Pull in configured json library
enc := json.NewEncoder(os.Stdout)
return enc.Encode(body)
}
diff --git a/cmd/podman/report/report.go b/cmd/podman/report/report.go
new file mode 100644
index 000000000..8392f10e0
--- /dev/null
+++ b/cmd/podman/report/report.go
@@ -0,0 +1,6 @@
+package report
+
+import "github.com/containers/libpod/cmd/podman/registry"
+
+// Pull in configured json library
+var json = registry.JsonLibrary()
diff --git a/cmd/podman/root.go b/cmd/podman/root.go
index 259e10c55..375faf8b1 100644
--- a/cmd/podman/root.go
+++ b/cmd/podman/root.go
@@ -9,6 +9,7 @@ import (
"strings"
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/tracing"
@@ -34,7 +35,7 @@ Description:
// UsageTemplate is the usage template for podman commands
// This blocks the displaying of the global options. The main podman
// command should not use this.
-const usageTemplate = `Usage(v2):{{if (and .Runnable (not .HasAvailableSubCommands))}}
+const usageTemplate = `Usage:{{if (and .Runnable (not .HasAvailableSubCommands))}}
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
@@ -60,7 +61,7 @@ var (
SilenceErrors: true,
TraverseChildren: true,
PersistentPreRunE: persistentPreRunE,
- RunE: registry.SubCommandExists,
+ RunE: validate.SubCommandExists,
PersistentPostRunE: persistentPostRunE,
Version: version.Version,
}
@@ -77,12 +78,16 @@ func init() {
syslogHook,
)
- rootFlags(registry.PodmanOptions, rootCmd.PersistentFlags())
+ rootFlags(registry.PodmanConfig(), rootCmd.PersistentFlags())
+
+ // "version" is a local flag to avoid collisions with sub-commands that use "-v"
+ var dummyVersion bool
+ rootCmd.Flags().BoolVarP(&dummyVersion, "version", "v", false, "Version of Podman")
}
func Execute() {
if err := rootCmd.ExecuteContext(registry.GetContextWithOptions()); err != nil {
- logrus.Error(err)
+ fmt.Fprintln(os.Stderr, "Error:", err.Error())
} else if registry.GetExitCode() == registry.ExecErrorCodeGeneric {
// The exitCode modified from registry.ExecErrorCodeGeneric,
// indicates an application
@@ -96,11 +101,9 @@ func Execute() {
func persistentPreRunE(cmd *cobra.Command, args []string) error {
// TODO: Remove trace statement in podman V2.1
- logrus.Debugf("Called %s.PersistentPreRunE()", cmd.Name())
+ logrus.Debugf("Called %s.PersistentPreRunE(%s)", cmd.Name(), strings.Join(os.Args, " "))
- // Update PodmanOptions now that we "know" more
- // TODO: pass in path overriding configuration file
- registry.PodmanOptions = registry.NewPodmanConfig()
+ cfg := registry.PodmanConfig()
// Prep the engines
if _, err := registry.NewImageEngine(cmd, args); err != nil {
@@ -111,10 +114,10 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
}
if cmd.Flag("cpu-profile").Changed {
- f, err := os.Create(registry.PodmanOptions.CpuProfile)
+ f, err := os.Create(cfg.CpuProfile)
if err != nil {
return errors.Wrapf(err, "unable to create cpu profiling file %s",
- registry.PodmanOptions.CpuProfile)
+ cfg.CpuProfile)
}
if err := pprof.StartCPUProfile(f); err != nil {
return err
@@ -124,11 +127,11 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
if cmd.Flag("trace").Changed {
tracer, closer := tracing.Init("podman")
opentracing.SetGlobalTracer(tracer)
- registry.PodmanOptions.SpanCloser = closer
+ cfg.SpanCloser = closer
- registry.PodmanOptions.Span = tracer.StartSpan("before-context")
- registry.PodmanOptions.SpanCtx = opentracing.ContextWithSpan(registry.Context(), registry.PodmanOptions.Span)
- opentracing.StartSpanFromContext(registry.PodmanOptions.SpanCtx, cmd.Name())
+ cfg.Span = tracer.StartSpan("before-context")
+ cfg.SpanCtx = opentracing.ContextWithSpan(registry.Context(), cfg.Span)
+ opentracing.StartSpanFromContext(cfg.SpanCtx, cmd.Name())
}
// Setup Rootless environment, IFF:
@@ -147,15 +150,19 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
func persistentPostRunE(cmd *cobra.Command, args []string) error {
// TODO: Remove trace statement in podman V2.1
- logrus.Debugf("Called %s.PersistentPostRunE()", cmd.Name())
+ logrus.Debugf("Called %s.PersistentPostRunE(%s)", cmd.Name(), strings.Join(os.Args, " "))
+ cfg := registry.PodmanConfig()
if cmd.Flag("cpu-profile").Changed {
pprof.StopCPUProfile()
}
if cmd.Flag("trace").Changed {
- registry.PodmanOptions.Span.Finish()
- registry.PodmanOptions.SpanCloser.Close()
+ cfg.Span.Finish()
+ cfg.SpanCloser.Close()
}
+
+ registry.ImageEngine().Shutdown(registry.Context())
+ registry.ContainerEngine().Shutdown(registry.Context())
return nil
}
@@ -199,16 +206,11 @@ func syslogHook() {
}
}
-func rootFlags(opts entities.PodmanConfig, flags *pflag.FlagSet) {
+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")
diff --git a/cmd/podman/system/events.go b/cmd/podman/system/events.go
index 3c1943b55..6aae62dc0 100644
--- a/cmd/podman/system/events.go
+++ b/cmd/podman/system/events.go
@@ -8,6 +8,7 @@ import (
"github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
@@ -18,7 +19,7 @@ var (
eventsDescription = "Monitor podman events"
eventsCommand = &cobra.Command{
Use: "events",
- Args: cobra.NoArgs,
+ Args: validate.NoArgs,
Short: "Show podman events",
Long: eventsDescription,
RunE: eventsCmd,
diff --git a/cmd/podman/system/info.go b/cmd/podman/system/info.go
index aa0a66ffc..26be794c5 100644
--- a/cmd/podman/system/info.go
+++ b/cmd/podman/system/info.go
@@ -1,15 +1,15 @@
package system
import (
- "encoding/json"
"fmt"
"os"
"text/template"
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
+ "github.com/ghodss/yaml"
"github.com/spf13/cobra"
- "gopkg.in/yaml.v2"
)
var (
@@ -19,7 +19,7 @@ var (
`
infoCommand = &cobra.Command{
Use: "info",
- Args: cobra.NoArgs,
+ Args: validate.NoArgs,
Long: infoDescription,
Short: "Display podman system information",
RunE: info,
diff --git a/cmd/podman/system/service.go b/cmd/podman/system/service.go
index fa1a33faa..f4b91dd78 100644
--- a/cmd/podman/system/service.go
+++ b/cmd/podman/system/service.go
@@ -2,8 +2,10 @@ package system
import (
"fmt"
+ "net/url"
"os"
"path/filepath"
+ "syscall"
"time"
"github.com/containers/libpod/cmd/podman/registry"
@@ -57,7 +59,24 @@ func service(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
- logrus.Infof("using API endpoint: \"%s\"", apiURI)
+ logrus.Infof("using API endpoint: '%s'", apiURI)
+
+ // Clean up any old existing unix domain socket
+ if len(apiURI) > 0 {
+ uri, err := url.Parse(apiURI)
+ if err != nil {
+ return err
+ }
+
+ // socket activation uses a unix:// socket in the shipped unit files but apiURI is coded as "" at this layer.
+ if "unix" == uri.Scheme && !registry.IsRemote() {
+ if err := syscall.Unlink(uri.Path); err != nil && !os.IsNotExist(err) {
+ return err
+ }
+ mask := syscall.Umask(0177)
+ defer syscall.Umask(mask)
+ }
+ }
opts := entities.ServiceOptions{
URI: apiURI,
@@ -71,11 +90,11 @@ func service(cmd *cobra.Command, args []string) error {
logrus.Warn("This function is EXPERIMENTAL")
fmt.Fprintf(os.Stderr, "This function is EXPERIMENTAL.\n")
- return registry.ContainerEngine().RestService(registry.GetContext(), opts)
+
+ return restService(opts, cmd.Flags(), registry.PodmanConfig())
}
func resolveApiURI(_url []string) (string, error) {
-
// When determining _*THE*_ listening endpoint --
// 1) User input wins always
// 2) systemd socket activation
@@ -83,14 +102,15 @@ func resolveApiURI(_url []string) (string, error) {
// 4) if varlink -- adapter.DefaultVarlinkAddress
// 5) lastly adapter.DefaultAPIAddress
- if _url == nil {
+ if len(_url) == 0 {
if v, found := os.LookupEnv("PODMAN_SOCKET"); found {
+ logrus.Debugf("PODMAN_SOCKET='%s' used to determine API endpoint", v)
_url = []string{v}
}
}
switch {
- case len(_url) > 0:
+ case len(_url) > 0 && _url[0] != "":
return _url[0], nil
case systemd.SocketActivated():
logrus.Info("using systemd socket activation to determine API endpoint")
diff --git a/cmd/podman/system/service_abi.go b/cmd/podman/system/service_abi.go
new file mode 100644
index 000000000..3da6ccfc7
--- /dev/null
+++ b/cmd/podman/system/service_abi.go
@@ -0,0 +1,57 @@
+// +build ABISupport
+
+package system
+
+import (
+ "context"
+ "net"
+ "strings"
+
+ api "github.com/containers/libpod/pkg/api/server"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/spf13/pflag"
+)
+
+func restService(opts entities.ServiceOptions, flags *pflag.FlagSet, cfg *entities.PodmanConfig) error {
+ var (
+ listener *net.Listener
+ err error
+ )
+
+ if opts.URI != "" {
+ fields := strings.Split(opts.URI, ":")
+ if len(fields) == 1 {
+ return errors.Errorf("%s is an invalid socket destination", opts.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", opts.URI)
+ }
+ listener = &l
+ }
+
+ rt, err := infra.GetRuntime(context.Background(), flags, cfg)
+ if err != nil {
+ return err
+ }
+
+ server, err := api.NewServerWithSettings(rt, opts.Timeout, listener)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if err := server.Shutdown(); err != nil {
+ logrus.Warnf("Error when stopping API service: %s", err)
+ }
+ }()
+
+ err = server.Serve()
+ if listener != nil {
+ _ = (*listener).Close()
+ }
+ return err
+}
diff --git a/cmd/podman/system/service_unsupported.go b/cmd/podman/system/service_unsupported.go
new file mode 100644
index 000000000..95f8189f6
--- /dev/null
+++ b/cmd/podman/system/service_unsupported.go
@@ -0,0 +1,14 @@
+// +build !ABISupport
+
+package system
+
+import (
+ "errors"
+
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/spf13/pflag"
+)
+
+func restService(opts entities.ServiceOptions, flags *pflag.FlagSet, cfg *entities.PodmanConfig) error {
+ return errors.New("not supported")
+}
diff --git a/cmd/podman/system/system.go b/cmd/podman/system/system.go
index 6d8c9ebc5..d9691ad2a 100644
--- a/cmd/podman/system/system.go
+++ b/cmd/podman/system/system.go
@@ -2,18 +2,22 @@ package system
import (
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)
var (
+ // Pull in configured json library
+ json = registry.JsonLibrary()
+
// Command: podman _system_
systemCmd = &cobra.Command{
Use: "system",
Short: "Manage podman",
Long: "Manage podman",
TraverseChildren: true,
- RunE: registry.SubCommandExists,
+ RunE: validate.SubCommandExists,
}
)
diff --git a/cmd/podman/system/version.go b/cmd/podman/system/version.go
index 5d3874de3..065eef309 100644
--- a/cmd/podman/system/version.go
+++ b/cmd/podman/system/version.go
@@ -10,6 +10,7 @@ import (
"github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
@@ -19,7 +20,7 @@ import (
var (
versionCommand = &cobra.Command{
Use: "version",
- Args: cobra.NoArgs,
+ Args: validate.NoArgs,
Short: "Display the Podman Version Information",
RunE: version,
Annotations: map[string]string{
@@ -55,14 +56,14 @@ func version(cmd *cobra.Command, args []string) error {
// TODO we need to discuss how to implement
// this more. current endpoints dont have a
// version endpoint. maybe we use info?
- //if remote {
+ // if remote {
// v.Server, err = getRemoteVersion(c)
// if err != nil {
// return err
// }
- //} else {
+ // } else {
v.Server = v.Client
- //}
+ // }
versionOutputFormat := versionFormat
if versionOutputFormat != "" {
diff --git a/cmd/podman/utils/alias.go b/cmd/podman/utils/alias.go
index 54b3c5e89..e484461c5 100644
--- a/cmd/podman/utils/alias.go
+++ b/cmd/podman/utils/alias.go
@@ -2,7 +2,7 @@ package utils
import "github.com/spf13/pflag"
-// AliasFlags is a function to handle backwards compatability with old flags
+// AliasFlags is a function to handle backwards compatibility with old flags
func AliasFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
switch name {
case "healthcheck-command":
diff --git a/cmd/podman/utils/utils.go b/cmd/podman/utils/utils.go
new file mode 100644
index 000000000..c7d105ba4
--- /dev/null
+++ b/cmd/podman/utils/utils.go
@@ -0,0 +1,22 @@
+package utils
+
+import "os"
+
+// IsDir returns true if the specified path refers to a directory.
+func IsDir(path string) bool {
+ file, err := os.Stat(path)
+ if err != nil {
+ return false
+ }
+ return file.IsDir()
+}
+
+// FileExists returns true if path refers to an existing file.
+func FileExists(path string) bool {
+ file, err := os.Stat(path)
+ // All errors return file == nil
+ if err != nil {
+ return false
+ }
+ return !file.IsDir()
+}
diff --git a/cmd/podman/validate/args.go b/cmd/podman/validate/args.go
new file mode 100644
index 000000000..14b4d7897
--- /dev/null
+++ b/cmd/podman/validate/args.go
@@ -0,0 +1,32 @@
+package validate
+
+import (
+ "fmt"
+
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+// NoArgs returns an error if any args are included.
+func NoArgs(cmd *cobra.Command, args []string) error {
+ if len(args) > 0 {
+ return fmt.Errorf("`%s` takes no arguments", cmd.CommandPath())
+ }
+ return nil
+}
+
+// SubCommandExists returns an error if no sub command is provided
+func SubCommandExists(cmd *cobra.Command, args []string) error {
+ if len(args) > 0 {
+ return errors.Errorf("unrecognized command `%[1]s %[2]s`\nTry '%[1]s --help' for more information.", cmd.CommandPath(), args[0])
+ }
+ return errors.Errorf("missing command '%[1]s COMMAND'\nTry '%[1]s --help' for more information.", cmd.CommandPath())
+}
+
+// IdOrLatestArgs used to validate a nameOrId was provided or the "--latest" flag
+func IdOrLatestArgs(cmd *cobra.Command, args []string) error {
+ if len(args) > 1 || (len(args) == 0 && !cmd.Flag("latest").Changed) {
+ return fmt.Errorf("`%s` requires a name, id or the \"--latest\" flag", cmd.CommandPath())
+ }
+ return nil
+}
diff --git a/cmd/podman/validate/choice.go b/cmd/podman/validate/choice.go
new file mode 100644
index 000000000..572c5f4a5
--- /dev/null
+++ b/cmd/podman/validate/choice.go
@@ -0,0 +1,46 @@
+package validate
+
+import (
+ "fmt"
+ "strings"
+)
+
+// Honors cobra.Value interface
+type choiceValue struct {
+ value *string
+ choices []string
+}
+
+// ChoiceValue may be used in cobra FlagSet methods Var/VarP/VarPF() to select from a set of values
+//
+// Example:
+// created := validate.ChoiceValue(&opts.Sort, "command", "created", "id", "image", "names", "runningfor", "size", "status")
+// flags.Var(created, "sort", "Sort output by: "+created.Choices())
+func ChoiceValue(p *string, choices ...string) *choiceValue {
+ return &choiceValue{
+ value: p,
+ choices: choices,
+ }
+}
+
+func (c *choiceValue) String() string {
+ return *c.value
+}
+
+func (c *choiceValue) Set(value string) error {
+ for _, v := range c.choices {
+ if v == value {
+ *c.value = value
+ return nil
+ }
+ }
+ return fmt.Errorf("%q is not a valid value. Choose from: %q", value, c.Choices())
+}
+
+func (c *choiceValue) Choices() string {
+ return strings.Join(c.choices, ", ")
+}
+
+func (c *choiceValue) Type() string {
+ return "choice"
+}
diff --git a/cmd/podman/volumes/create.go b/cmd/podman/volumes/create.go
index df0731791..1bec8d0e7 100644
--- a/cmd/podman/volumes/create.go
+++ b/cmd/podman/volumes/create.go
@@ -40,7 +40,7 @@ func init() {
Parent: volumeCmd,
})
flags := createCommand.Flags()
- flags.StringVar(&createOpts.Driver, "driver", "", "Specify volume driver name (default local)")
+ flags.StringVar(&createOpts.Driver, "driver", "local", "Specify volume driver name")
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 [])")
}
diff --git a/cmd/podman/volumes/inspect.go b/cmd/podman/volumes/inspect.go
index 57e773aef..79f65ea4a 100644
--- a/cmd/podman/volumes/inspect.go
+++ b/cmd/podman/volumes/inspect.go
@@ -1,10 +1,10 @@
package volumes
import (
- "encoding/json"
"fmt"
"html/template"
"os"
+ "strings"
"github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/registry"
@@ -61,7 +61,11 @@ func inspect(cmd *cobra.Command, args []string) error {
}
fmt.Println(string(jsonOut))
default:
- tmpl, err := template.New("volumeInspect").Parse(inspectFormat)
+ if !strings.HasSuffix(inspectFormat, "\n") {
+ inspectFormat += "\n"
+ }
+ format := "{{range . }}" + inspectFormat + "{{end}}"
+ tmpl, err := template.New("volumeInspect").Parse(format)
if err != nil {
return err
}
diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go
index fd89db01f..72bf9f25b 100644
--- a/cmd/podman/volumes/list.go
+++ b/cmd/podman/volumes/list.go
@@ -2,6 +2,7 @@ package volumes
import (
"context"
+ "fmt"
"html/template"
"io"
"os"
@@ -9,6 +10,7 @@ import (
"text/tabwriter"
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@@ -23,7 +25,7 @@ and the output format can be changed to JSON or a user specified Go template.`
lsCommand = &cobra.Command{
Use: "ls",
Aliases: []string{"list"},
- Args: cobra.NoArgs,
+ Args: validate.NoArgs,
Short: "List volumes",
Long: volumeLsDescription,
RunE: list,
@@ -57,6 +59,9 @@ func list(cmd *cobra.Command, args []string) error {
if cliOpts.Quiet && cmd.Flag("format").Changed {
return errors.New("quiet and format flags cannot be used together")
}
+ if len(cliOpts.Filter) > 0 {
+ lsOpts.Filter = make(map[string][]string)
+ }
for _, f := range cliOpts.Filter {
filterSplit := strings.Split(f, "=")
if len(filterSplit) < 2 {
@@ -68,6 +73,13 @@ func list(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
+ if cliOpts.Format == "json" {
+ return outputJSON(responses)
+ }
+
+ if len(responses) < 1 {
+ return nil
+ }
// "\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)
@@ -96,3 +108,12 @@ func list(cmd *cobra.Command, args []string) error {
}
return nil
}
+
+func outputJSON(vols []*entities.VolumeListReport) error {
+ b, err := json.MarshalIndent(vols, "", " ")
+ if err != nil {
+ return err
+ }
+ fmt.Println(string(b))
+ return nil
+}
diff --git a/cmd/podman/volumes/prune.go b/cmd/podman/volumes/prune.go
index 197a9da9b..2c3ed88f3 100644
--- a/cmd/podman/volumes/prune.go
+++ b/cmd/podman/volumes/prune.go
@@ -9,6 +9,7 @@ import (
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/cmd/podman/utils"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@@ -21,7 +22,7 @@ var (
Note all data will be destroyed.`
pruneCommand = &cobra.Command{
Use: "prune",
- Args: cobra.NoArgs,
+ Args: validate.NoArgs,
Short: "Remove all unused volumes",
Long: volumePruneDescription,
RunE: prune,
diff --git a/cmd/podman/volumes/volume.go b/cmd/podman/volumes/volume.go
index 06943da62..3e90d178c 100644
--- a/cmd/podman/volumes/volume.go
+++ b/cmd/podman/volumes/volume.go
@@ -2,18 +2,22 @@ package volumes
import (
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)
var (
+ // Pull in configured json library
+ json = registry.JsonLibrary()
+
// 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,
+ RunE: validate.SubCommandExists,
}
)