diff options
Diffstat (limited to 'cmd')
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", |