summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/podman/common/create.go56
-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/netflags.go107
-rw-r--r--cmd/podman/common/ports.go112
-rw-r--r--cmd/podman/common/specgen.go464
-rw-r--r--cmd/podman/common/volumes.go569
-rw-r--r--cmd/podman/containers/attach.go38
-rw-r--r--cmd/podman/containers/commit.go36
-rw-r--r--cmd/podman/containers/container.go19
-rw-r--r--cmd/podman/containers/cp.go55
-rw-r--r--cmd/podman/containers/create.go123
-rw-r--r--cmd/podman/containers/diff.go6
-rw-r--r--cmd/podman/containers/exec.go39
-rw-r--r--cmd/podman/containers/exists.go3
-rw-r--r--cmd/podman/containers/export.go25
-rw-r--r--cmd/podman/containers/inspect.go1
-rw-r--r--cmd/podman/containers/kill.go36
-rw-r--r--cmd/podman/containers/list.go7
-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.go32
-rw-r--r--cmd/podman/containers/restart.go40
-rw-r--r--cmd/podman/containers/rm.go55
-rw-r--r--cmd/podman/containers/run.go77
-rw-r--r--cmd/podman/containers/start.go41
-rw-r--r--cmd/podman/containers/stop.go42
-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.go37
-rw-r--r--cmd/podman/diff.go14
-rw-r--r--cmd/podman/images/diff.go12
-rw-r--r--cmd/podman/images/exists.go4
-rw-r--r--cmd/podman/images/history.go2
-rw-r--r--cmd/podman/images/image.go3
-rw-r--r--cmd/podman/images/inspect.go35
-rw-r--r--cmd/podman/images/list.go2
-rw-r--r--cmd/podman/images/load.go3
-rw-r--r--cmd/podman/images/pull.go7
-rw-r--r--cmd/podman/images/push.go8
-rw-r--r--cmd/podman/images/rm.go32
-rw-r--r--cmd/podman/images/tree.go40
-rw-r--r--cmd/podman/inspect.go31
-rw-r--r--cmd/podman/login.go68
-rw-r--r--cmd/podman/logout.go57
-rw-r--r--cmd/podman/main.go1
-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.go27
-rw-r--r--cmd/podman/pods/create.go40
-rw-r--r--cmd/podman/pods/exists.go3
-rw-r--r--cmd/podman/pods/inspect.go3
-rw-r--r--cmd/podman/pods/pod.go5
-rw-r--r--cmd/podman/pods/ps.go103
-rw-r--r--cmd/podman/pods/stats.go189
-rw-r--r--cmd/podman/pods/stop.go3
-rw-r--r--cmd/podman/registry/json.go20
-rw-r--r--cmd/podman/report/diff.go3
-rw-r--r--cmd/podman/report/report.go6
-rw-r--r--cmd/podman/root.go3
-rw-r--r--cmd/podman/system/info.go3
-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.go3
-rw-r--r--cmd/podman/volumes/create.go2
-rw-r--r--cmd/podman/volumes/inspect.go1
-rw-r--r--cmd/podman/volumes/list.go17
-rw-r--r--cmd/podman/volumes/volume.go3
73 files changed, 2668 insertions, 756 deletions
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go
index 49a40dfa0..a0aed984c 100644
--- a/cmd/podman/common/create.go
+++ b/cmd/podman/common/create.go
@@ -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/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..a96bafabd 100644
--- a/cmd/podman/common/ports.go
+++ b/cmd/podman/common/ports.go
@@ -1,28 +1,11 @@
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 {
@@ -30,97 +13,10 @@ func ExposedPorts(expose []string, publish []ocicni.PortMapping, publishAll bool
_, 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)
+ _, _, 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 85b344b3c..5d5816ea4 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,43 +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
}
- s.User = c.User
- 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
@@ -69,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
- s.Pod = c.Pod
+ // 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.CgroupNS = specgen.Namespace{
- // NSMode: ,
- // Value: "",
- //}
+ s.Terminal = c.TTY
- //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")
- // }
- //}
+ 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
- // 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"
@@ -191,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)
@@ -259,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 {
@@ -277,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)
@@ -313,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{}
@@ -329,19 +463,12 @@ 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
- }
+ s.CgroupParent = c.CGroupParent
+ s.CgroupsMode = c.CGroupsMode
+ s.Groups = c.GroupAdd
// TODO WTF
//cgroup := &cc.CgroupConfig{
- // Cgroups: c.String("cgroups"),
// Cgroupns: c.String("cgroupns"),
- // CgroupParent: c.String("cgroup-parent"),
- // CgroupMode: cgroupMode,
//}
//
//userns := &cc.UserConfig{
@@ -358,6 +485,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
// Hostname: c.String("hostname"),
//}
+ s.Hostname = c.Hostname
sysctl := map[string]string{}
if ctl := c.Sysctl; len(ctl) > 0 {
sysctl, err = util.ValidateSysctls(ctl)
@@ -411,19 +539,20 @@ 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
// TODO conflict on populate?
@@ -432,6 +561,10 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
// 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
// init - initbinary
@@ -440,32 +573,6 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
// quiet
//DeviceCgroupRules: c.StringSlice("device-cgroup-rule"),
- if bps := c.DeviceReadBPs; len(bps) > 0 {
- if s.ThrottleReadBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
- return err
- }
- }
-
- if bps := c.DeviceWriteBPs; len(bps) > 0 {
- if s.ThrottleWriteBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
- return err
- }
- }
-
- if iops := c.DeviceReadIOPs; len(iops) > 0 {
- if s.ThrottleReadIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
- return err
- }
- }
-
- if iops := c.DeviceWriteIOPs; len(iops) > 0 {
- if s.ThrottleWriteIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
- return err
- }
- }
-
- s.ResourceLimits.Memory.DisableOOMKiller = &c.OOMKillDisable
-
// Rlimits/Ulimits
for _, u := range c.Ulimit {
if u == "host" {
@@ -495,30 +602,18 @@ 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
@@ -537,10 +632,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..78b52ad1b 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,6 +53,23 @@ 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 {
attachOpts.Stdin = os.Stdin
if attachOpts.NoStdin {
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..97b73cdd0 100644
--- a/cmd/podman/containers/container.go
+++ b/cmd/podman/containers/container.go
@@ -1,16 +1,16 @@
package containers
import (
- "os"
-
- "github.com/containers/common/pkg/config"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
- "github.com/sirupsen/logrus"
+ "github.com/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",
@@ -20,7 +20,7 @@ var (
RunE: registry.SubCommandExists,
}
- defaultContainerConfig = getDefaultContainerConfig()
+ containerConfig = util.DefaultContainerConfig()
)
func init() {
@@ -29,12 +29,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..da550b606 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,12 +31,29 @@ 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},
@@ -39,30 +61,46 @@ func init() {
})
//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..046dac53e 100644
--- a/cmd/podman/containers/diff.go
+++ b/cmd/podman/containers/diff.go
@@ -45,7 +45,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..3749c934a 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: containerCommitCommand,
+ })
+
+ 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..e640ca5e1 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"
@@ -37,7 +36,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..f9ef1ddbd 100644
--- a/cmd/podman/containers/inspect.go
+++ b/cmd/podman/containers/inspect.go
@@ -11,7 +11,6 @@ import (
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
- json "github.com/json-iterator/go"
"github.com/spf13/cobra"
)
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..b5019ddd2 100644
--- a/cmd/podman/containers/list.go
+++ b/cmd/podman/containers/list.go
@@ -14,7 +14,7 @@ var (
Args: cobra.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 +27,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..0e50140ca
--- /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 == userPort {
+ fmt.Printf("%s:%d\n", hostIP, v.HostPort)
+ found = true
+ break
+ }
+ }
+ if !found && port != "" {
+ return errors.Errorf("failed to find published port %q", port)
+ }
+ }
+ return nil
+}
diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go
index 57b81a609..82434e9cc 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"
@@ -19,6 +18,7 @@ import (
"github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -48,7 +48,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")
@@ -166,14 +169,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 = listOpts.Format
+ 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 +227,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 +251,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 +294,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, " ")
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 151f71885..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,12 +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 c1560be08..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,17 +45,12 @@ 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")
+ flags.UintVarP(&stopTimeout, "time", "t", containerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container")
if registry.IsRemote() {
_ = flags.MarkHidden("latest")
@@ -54,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 47f28f4c6..da746361d 100644
--- a/cmd/podman/containers/wait.go
+++ b/cmd/podman/containers/wait.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 (
@@ -26,6 +27,16 @@ var (
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,13 +44,7 @@ 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")
@@ -49,6 +54,24 @@ func init() {
}
}
+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..ec94c0918 100644
--- a/cmd/podman/diff.go
+++ b/cmd/podman/diff.go
@@ -46,10 +46,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 +56,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/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..6464e6cd8 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"
@@ -34,7 +32,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..b8d216cc1 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)
}
diff --git a/cmd/podman/images/image.go b/cmd/podman/images/image.go
index 37e46ab9e..604f49251 100644
--- a/cmd/podman/images/image.go
+++ b/cmd/podman/images/image.go
@@ -7,6 +7,9 @@ import (
)
var (
+ // Pull in configured json library
+ json = registry.JsonLibrary()
+
// Command: podman _image_
imageCmd = &cobra.Command{
Use: "image",
diff --git a/cmd/podman/images/inspect.go b/cmd/podman/images/inspect.go
index 4482ceee5..91c9445eb 100644
--- a/cmd/podman/images/inspect.go
+++ b/cmd/podman/images/inspect.go
@@ -2,7 +2,6 @@ package images
import (
"context"
- "encoding/json"
"fmt"
"os"
"strings"
@@ -20,11 +19,13 @@ import (
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 on an image identified by name or ID.`,
+ RunE: inspect,
+ Example: `podman inspect alpine
+ podman inspect --format "imageId: {{.Id}} size: {{.Size}}" alpine
+ podman inspect --format "image: {{.ImageName}} driver: {{.Driver}}" myctr`,
}
inspectOpts *entities.InspectOptions
)
@@ -39,14 +40,14 @@ func init() {
}
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 inspectOpts.Size {
+ return fmt.Errorf("--size can only be used for containers")
}
-
- if len(args) > 0 && latestContainer {
- return errors.Errorf("you cannot provide additional arguments with --latest")
+ if inspectOpts.Latest {
+ return fmt.Errorf("--latest can only be used for containers")
+ }
+ if len(args) == 0 {
+ return errors.Errorf("image name must be specified: podman image inspect [options [...]] name")
}
results, err := registry.ImageEngine().Inspect(context.Background(), args, *inspectOpts)
@@ -82,10 +83,14 @@ func inspect(cmd *cobra.Command, args []string) error {
}
}
+ var lastErr error
for id, e := range results.Errors {
- fmt.Fprintf(os.Stderr, "%s: %s\n", id, e.Error())
+ if lastErr != nil {
+ fmt.Fprintf(os.Stderr, "%s: %s\n", id, lastErr.Error())
+ }
+ lastErr = e
}
- return nil
+ return lastErr
}
func inspectFormat(row string) string {
diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go
index 63ddc5d56..b979cb6af 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"
)
@@ -127,7 +126,6 @@ func writeJSON(imageS []*entities.ImageSummary) error {
imgs = append(imgs, h)
}
- json := jsoniter.ConfigCompatibleWithStandardLibrary
enc := json.NewEncoder(os.Stdout)
return enc.Encode(imgs)
}
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/pull.go b/cmd/podman/images/pull.go
index fb107d00c..f996d0681 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"
)
@@ -99,6 +101,11 @@ func imagePull(cmd *cobra.Command, args []string) error {
if cmd.Flags().Changed("tls-verify") {
pullOptsAPI.TLSVerify = types.NewOptionalBool(pullOptions.TLSVerifyCLI)
}
+ if pullOptsAPI.Authfile != "" {
+ if _, err := os.Stat(pullOptsAPI.Authfile); err != nil {
+ return errors.Wrapf(err, "error getting authfile %s", pullOptsAPI.Authfile)
+ }
+ }
// Let's do all the remaining Yoga in the API to prevent us from
// scattering logic across (too) many parts of the code.
diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go
index f12a5ac86..ef2ffd0d7 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"
@@ -114,6 +116,12 @@ func imagePush(cmd *cobra.Command, args []string) error {
pushOptsAPI.TLSVerify = types.NewOptionalBool(pushOptions.TLSVerifyCLI)
}
+ if pushOptsAPI.Authfile != "" {
+ if _, err := os.Stat(pushOptsAPI.Authfile); err != nil {
+ return errors.Wrapf(err, "error getting authfile %s", pushOptsAPI.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)
diff --git a/cmd/podman/images/rm.go b/cmd/podman/images/rm.go
index 135fda387..da6a90d2b 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,25 @@ 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 {
+ 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/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 e67bc326b..93bf58bdd 100644
--- a/cmd/podman/inspect.go
+++ b/cmd/podman/inspect.go
@@ -1,10 +1,8 @@
package main
import (
- "context"
"fmt"
- "github.com/containers/image/v5/docker/reference"
"github.com/containers/libpod/cmd/podman/common"
"github.com/containers/libpod/cmd/podman/containers"
"github.com/containers/libpod/cmd/podman/images"
@@ -21,11 +19,12 @@ var (
// 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,
+ Example: `podman inspect alpine
+ podman inspect --format "imageId: {{.Id}} size: {{.Size}}" alpine`,
}
)
@@ -35,21 +34,25 @@ func init() {
Command: inspectCmd,
})
inspectOpts = common.AddInspectFlagSet(inspectCmd)
+ flags := inspectCmd.Flags()
+ flags.StringVarP(&inspectOpts.Type, "type", "t", "", "Return JSON for specified type, (image or container) (default \"all\")")
+ if !registry.IsRemote() {
+ flags.BoolVarP(&inspectOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of (containers only)")
+ }
}
func inspect(cmd *cobra.Command, args []string) error {
- // First check if the input is even valid for an image
- if _, err := reference.Parse(args[0]); err == nil {
- if found, err := registry.ImageEngine().Exists(context.Background(), args[0]); err != nil {
- return err
- } else if found.Value {
- return images.Inspect(cmd, args, inspectOpts)
+ switch inspectOpts.Type {
+ case "image":
+ return images.Inspect(cmd, args, inspectOpts)
+ case "container":
+ return containers.Inspect(cmd, args, inspectOpts)
+ case "":
+ if err := images.Inspect(cmd, args, inspectOpts); err == nil {
+ return nil
}
- }
- if found, err := registry.ContainerEngine().ContainerExists(context.Background(), args[0]); err != nil {
- return err
- } else if found.Value {
return containers.Inspect(cmd, args, inspectOpts)
+ default:
+ return fmt.Errorf("invalid type %q is must be 'container' or 'image'", inspectOpts.Type)
}
- return fmt.Errorf("%s not found on system", args[0])
}
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 2d9e45177..8109eca2f 100644
--- a/cmd/podman/main.go
+++ b/cmd/podman/main.go
@@ -6,6 +6,7 @@ import (
_ "github.com/containers/libpod/cmd/podman/containers"
_ "github.com/containers/libpod/cmd/podman/healthcheck"
_ "github.com/containers/libpod/cmd/podman/images"
+ _ "github.com/containers/libpod/cmd/podman/manifest"
_ "github.com/containers/libpod/cmd/podman/networks"
_ "github.com/containers/libpod/cmd/podman/pods"
"github.com/containers/libpod/cmd/podman/registry"
diff --git a/cmd/podman/manifest/add.go b/cmd/podman/manifest/add.go
new file mode 100644
index 000000000..c83beff7a
--- /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",
+ 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..4f3e27774
--- /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",
+ 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..36ecdc87b
--- /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 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..b9ac7ea68
--- /dev/null
+++ b/cmd/podman/manifest/manifest.go
@@ -0,0 +1,27 @@
+package manifest
+
+import (
+ "github.com/containers/libpod/cmd/podman/registry"
+ "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: registry.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/pods/create.go b/cmd/podman/pods/create.go
index 63dab4707..ff21166f3 100644
--- a/cmd/podman/pods/create.go
+++ b/cmd/podman/pods/create.go
@@ -9,7 +9,6 @@ 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/pkg/domain/entities"
"github.com/containers/libpod/pkg/errorhandling"
"github.com/containers/libpod/pkg/specgen"
@@ -50,8 +49,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 +69,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) {
@@ -123,21 +136,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..5a94bf150 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"
@@ -37,7 +36,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..e86b8aba4 100644
--- a/cmd/podman/pods/pod.go
+++ b/cmd/podman/pods/pod.go
@@ -3,10 +3,14 @@ package pods
import (
"github.com/containers/libpod/cmd/podman/registry"
"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",
@@ -15,6 +19,7 @@ var (
TraverseChildren: true,
RunE: registry.SubCommandExists,
}
+ containerConfig = util.DefaultContainerConfig()
)
func init() {
diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go
index 8cb7b6266..6d0d9cf7f 100644
--- a/cmd/podman/pods/ps.go
+++ b/cmd/podman/pods/ps.go
@@ -2,19 +2,18 @@ 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/pkg/domain/entities"
+ "github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -34,7 +33,7 @@ var (
var (
defaultHeaders string = "POD ID\tNAME\tSTATUS\tCREATED"
- inputFilters string
+ inputFilters []string
noTrunc bool
psInput entities.PodPSOptions
)
@@ -50,7 +49,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 +68,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 +87,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 +105,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 +136,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 +162,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 +182,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 +203,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 +239,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/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/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/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 667f7e588..84c3867f2 100644
--- a/cmd/podman/root.go
+++ b/cmd/podman/root.go
@@ -155,6 +155,9 @@ func persistentPostRunE(cmd *cobra.Command, args []string) error {
cfg.Span.Finish()
cfg.SpanCloser.Close()
}
+
+ registry.ImageEngine().Shutdown(registry.Context())
+ registry.ContainerEngine().Shutdown(registry.Context())
return nil
}
diff --git a/cmd/podman/system/info.go b/cmd/podman/system/info.go
index aa0a66ffc..8b36ef549 100644
--- a/cmd/podman/system/info.go
+++ b/cmd/podman/system/info.go
@@ -1,15 +1,14 @@
package system
import (
- "encoding/json"
"fmt"
"os"
"text/template"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
+ "github.com/ghodss/yaml"
"github.com/spf13/cobra"
- "gopkg.in/yaml.v2"
)
var (
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..2d55e8c13 100644
--- a/cmd/podman/system/system.go
+++ b/cmd/podman/system/system.go
@@ -7,6 +7,9 @@ import (
)
var (
+ // Pull in configured json library
+ json = registry.JsonLibrary()
+
// Command: podman _system_
systemCmd = &cobra.Command{
Use: "system",
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 feaaee176..79f65ea4a 100644
--- a/cmd/podman/volumes/inspect.go
+++ b/cmd/podman/volumes/inspect.go
@@ -1,7 +1,6 @@
package volumes
import (
- "encoding/json"
"fmt"
"html/template"
"os"
diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go
index f75de6b4b..7f5a55b14 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"
@@ -57,6 +58,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 +72,10 @@ 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
}
@@ -99,3 +107,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/volume.go b/cmd/podman/volumes/volume.go
index 06943da62..4d74ff084 100644
--- a/cmd/podman/volumes/volume.go
+++ b/cmd/podman/volumes/volume.go
@@ -7,6 +7,9 @@ import (
)
var (
+ // Pull in configured json library
+ json = registry.JsonLibrary()
+
// Command: podman _volume_
volumeCmd = &cobra.Command{
Use: "volume",