Merge pull request #5710 from baude/v2create

v2podman container create
This commit is contained in:
OpenShift Merge Robot
2020-04-03 23:53:45 +02:00
committed by GitHub
26 changed files with 3110 additions and 63 deletions

View File

@ -1,2 +1,2 @@
all:
GO111MODULE=off go build -tags 'ABISupport systemd'
CGO_ENABLED=1 GO111MODULE=off go build -tags 'ABISupport systemd seccomp'

View File

@ -0,0 +1,534 @@
package common
import (
"fmt"
"os"
buildahcli "github.com/containers/buildah/pkg/cli"
"github.com/containers/common/pkg/config"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
)
const (
sizeWithUnitFormat = "(format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))"
)
var (
defaultContainerConfig = getDefaultContainerConfig()
)
func getDefaultContainerConfig() *config.Config {
defaultContainerConfig, err := config.Default()
if err != nil {
logrus.Error(err)
os.Exit(1)
}
return defaultContainerConfig
}
func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
//createFlags := c.Flags()
createFlags := pflag.FlagSet{}
createFlags.StringSliceVar(
&cf.Annotation,
"annotation", []string{},
"Add annotations to container (key:value)",
)
createFlags.StringSliceVarP(
&cf.Attach,
"attach", "a", []string{},
"Attach to STDIN, STDOUT or STDERR",
)
createFlags.StringVar(
&cf.Authfile,
"authfile", buildahcli.GetDefaultAuthFile(),
"Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override",
)
createFlags.StringVar(
&cf.BlkIOWeight,
"blkio-weight", "",
"Block IO weight (relative weight) accepts a weight value between 10 and 1000.",
)
createFlags.StringSliceVar(
&cf.BlkIOWeightDevice,
"blkio-weight-device", []string{},
"Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`)",
)
createFlags.StringSliceVar(
&cf.CapAdd,
"cap-add", []string{},
"Add capabilities to the container",
)
createFlags.StringSliceVar(
&cf.CapDrop,
"cap-drop", []string{},
"Drop capabilities from the container",
)
createFlags.StringVar(
&cf.CGroupsNS,
"cgroupns", getDefaultCgroupNS(),
"cgroup namespace to use",
)
createFlags.StringVar(
&cf.CGroups,
"cgroups", "enabled",
`control container cgroup configuration ("enabled"|"disabled"|"no-conmon")`,
)
createFlags.StringVar(
&cf.CGroupParent,
"cgroup-parent", "",
"Optional parent cgroup for the container",
)
createFlags.StringVar(
&cf.CIDFile,
"cidfile", "",
"Write the container ID to the file",
)
createFlags.StringVar(
&cf.ConmonPIDFile,
"conmon-pidfile", "",
"Path to the file that will receive the PID of conmon",
)
createFlags.Uint64Var(
&cf.CPUPeriod,
"cpu-period", 0,
"Limit the CPU CFS (Completely Fair Scheduler) period",
)
createFlags.Int64Var(
&cf.CPUQuota,
"cpu-quota", 0,
"Limit the CPU CFS (Completely Fair Scheduler) quota",
)
createFlags.Uint64Var(
&cf.CPURTPeriod,
"cpu-rt-period", 0,
"Limit the CPU real-time period in microseconds",
)
createFlags.Int64Var(
&cf.CPURTRuntime,
"cpu-rt-runtime", 0,
"Limit the CPU real-time runtime in microseconds",
)
createFlags.Uint64Var(
&cf.CPUShares,
"cpu-shares", 0,
"CPU shares (relative weight)",
)
createFlags.Float64Var(
&cf.CPUS,
"cpus", 0,
"Number of CPUs. The default is 0.000 which means no limit",
)
createFlags.StringVar(
&cf.CPUSetCPUs,
"cpuset-cpus", "",
"CPUs in which to allow execution (0-3, 0,1)",
)
createFlags.StringVar(
&cf.CPUSetMems,
"cpuset-mems", "",
"Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.",
)
createFlags.BoolVarP(
&cf.Detach,
"detach", "d", false,
"Run container in background and print container ID",
)
createFlags.StringVar(
&cf.DetachKeys,
"detach-keys", getDefaultDetachKeys(),
"Override the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-cf`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`",
)
createFlags.StringSliceVar(
&cf.Device,
"device", getDefaultDevices(),
fmt.Sprintf("Add a host device to the container"),
)
createFlags.StringSliceVar(
&cf.DeviceCGroupRule,
"device-cgroup-rule", []string{},
"Add a rule to the cgroup allowed devices list",
)
createFlags.StringSliceVar(
&cf.DeviceReadBPs,
"device-read-bps", []string{},
"Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)",
)
createFlags.StringSliceVar(
&cf.DeviceReadIOPs,
"device-read-iops", []string{},
"Limit read rate (IO per second) from a device (e.g. --device-read-iops=/dev/sda:1000)",
)
createFlags.StringSliceVar(
&cf.DeviceWriteBPs,
"device-write-bps", []string{},
"Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)",
)
createFlags.StringSliceVar(
&cf.DeviceWriteIOPs,
"device-write-iops", []string{},
"Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)",
)
createFlags.StringVar(
&cf.Entrypoint,
"entrypoint", "",
"Overwrite the default ENTRYPOINT of the image",
)
createFlags.StringArrayVarP(
&cf.env,
"env", "e", getDefaultEnv(),
"Set environment variables in container",
)
createFlags.BoolVar(
&cf.EnvHost,
"env-host", false, "Use all current host environment variables in container",
)
createFlags.StringSliceVar(
&cf.EnvFile,
"env-file", []string{},
"Read in a file of environment variables",
)
createFlags.StringSliceVar(
&cf.Expose,
"expose", []string{},
"Expose a port or a range of ports",
)
createFlags.StringSliceVar(
&cf.GIDMap,
"gidmap", []string{},
"GID map to use for the user namespace",
)
createFlags.StringSliceVar(
&cf.GroupAdd,
"group-add", []string{},
"Add additional groups to join",
)
createFlags.Bool(
"help", false, "",
)
createFlags.StringVar(
&cf.HealthCmd,
"health-cmd", "",
"set a healthcheck command for the container ('none' disables the existing healthcheck)",
)
createFlags.StringVar(
&cf.HealthInterval,
"health-interval", cliconfig.DefaultHealthCheckInterval,
"set an interval for the healthchecks (a value of disable results in no automatic timer setup)",
)
createFlags.UintVar(
&cf.HealthRetries,
"health-retries", cliconfig.DefaultHealthCheckRetries,
"the number of retries allowed before a healthcheck is considered to be unhealthy",
)
createFlags.StringVar(
&cf.HealthStartPeriod,
"health-start-period", cliconfig.DefaultHealthCheckStartPeriod,
"the initialization time needed for a container to bootstrap",
)
createFlags.StringVar(
&cf.HealthTimeout,
"health-timeout", cliconfig.DefaultHealthCheckTimeout,
"the maximum time allowed to complete the healthcheck before an interval is considered failed",
)
createFlags.StringVarP(
&cf.Hostname,
"hostname", "h", "",
"Set container hostname",
)
createFlags.BoolVar(
&cf.HTTPProxy,
"http-proxy", true,
"Set proxy environment variables in the container based on the host proxy vars",
)
createFlags.StringVar(
&cf.ImageVolume,
"image-volume", cliconfig.DefaultImageVolume,
`Tells podman how to handle the builtin image volumes ("bind"|"tmpfs"|"ignore")`,
)
createFlags.BoolVar(
&cf.Init,
"init", false,
"Run an init binary inside the container that forwards signals and reaps processes",
)
createFlags.StringVar(
&cf.InitPath,
"init-path", getDefaultInitPath(),
// Do not use the Value field for setting the default value to determine user input (i.e., non-empty string)
fmt.Sprintf("Path to the container-init binary"),
)
createFlags.BoolVarP(
&cf.Interactive,
"interactive", "i", false,
"Keep STDIN open even if not attached",
)
createFlags.StringVar(
&cf.IPC,
"ipc", getDefaultIPCNS(),
"IPC namespace to use",
)
createFlags.StringVar(
&cf.KernelMemory,
"kernel-memory", "",
"Kernel memory limit "+sizeWithUnitFormat,
)
createFlags.StringArrayVarP(
&cf.Label,
"label", "l", []string{},
"Set metadata on container",
)
createFlags.StringSliceVar(
&cf.LabelFile,
"label-file", []string{},
"Read in a line delimited file of labels",
)
createFlags.StringVar(
&cf.LogDriver,
"log-driver", "",
"Logging driver for the container",
)
createFlags.StringSliceVar(
&cf.LogOptions,
"log-opt", []string{},
"Logging driver options",
)
createFlags.StringVarP(
&cf.Memory,
"memory", "m", "",
"Memory limit "+sizeWithUnitFormat,
)
createFlags.StringVar(
&cf.MemoryReservation,
"memory-reservation", "",
"Memory soft limit "+sizeWithUnitFormat,
)
createFlags.StringVar(
&cf.MemorySwap,
"memory-swap", "",
"Swap limit equal to memory plus swap: '-1' to enable unlimited swap",
)
createFlags.Int64Var(
&cf.MemorySwappiness,
"memory-swappiness", -1,
"Tune container memory swappiness (0 to 100, or -1 for system default)",
)
createFlags.StringVar(
&cf.Name,
"name", "",
"Assign a name to the container",
)
createFlags.BoolVar(
&cf.NoHealthCheck,
"no-healthcheck", false,
"Disable healthchecks on container",
)
createFlags.BoolVar(
&cf.OOMKillDisable,
"oom-kill-disable", false,
"Disable OOM Killer",
)
createFlags.IntVar(
&cf.OOMScoreAdj,
"oom-score-adj", 0,
"Tune the host's OOM preferences (-1000 to 1000)",
)
createFlags.StringVar(
&cf.OverrideArch,
"override-arch", "",
"use `ARCH` instead of the architecture of the machine for choosing images",
)
//markFlagHidden(createFlags, "override-arch")
createFlags.StringVar(
&cf.OverrideOS,
"override-os", "",
"use `OS` instead of the running OS for choosing images",
)
//markFlagHidden(createFlags, "override-os")
createFlags.StringVar(
&cf.PID,
"pid", getDefaultPidNS(),
"PID namespace to use",
)
createFlags.Int64Var(
&cf.PIDsLimit,
"pids-limit", getDefaultPidsLimit(),
getDefaultPidsDescription(),
)
createFlags.StringVar(
&cf.Pod,
"pod", "",
"Run container in an existing pod",
)
createFlags.BoolVar(
&cf.Privileged,
"privileged", false,
"Give extended privileges to container",
)
createFlags.BoolVarP(
&cf.PublishAll,
"publish-all", "P", false,
"Publish all exposed ports to random ports on the host interface",
)
createFlags.StringVar(
&cf.Pull,
"pull", "missing",
`Pull image before creating ("always"|"missing"|"never")`,
)
createFlags.BoolVarP(
&cf.Quiet,
"quiet", "q", false,
"Suppress output information when pulling images",
)
createFlags.BoolVar(
&cf.ReadOnly,
"read-only", false,
"Make containers root filesystem read-only",
)
createFlags.BoolVar(
&cf.ReadOnlyTmpFS,
"read-only-tmpfs", true,
"When running containers in read-only mode mount a read-write tmpfs on /run, /tmp and /var/tmp",
)
createFlags.StringVar(
&cf.Restart,
"restart", "",
`Restart policy to apply when a container exits ("always"|"no"|"on-failure")`,
)
createFlags.BoolVar(
&cf.Rm,
"rm", false,
"Remove container (and pod if created) after exit",
)
createFlags.BoolVar(
&cf.RootFS,
"rootfs", false,
"The first argument is not an image but the rootfs to the exploded container",
)
createFlags.StringArrayVar(
&cf.SecurityOpt,
"security-opt", getDefaultSecurityOptions(),
fmt.Sprintf("Security Options"),
)
createFlags.StringVar(
&cf.ShmSize,
"shm-size", getDefaultShmSize(),
"Size of /dev/shm "+sizeWithUnitFormat,
)
createFlags.StringVar(
&cf.StopSignal,
"stop-signal", "",
"Signal to stop a container. Default is SIGTERM",
)
createFlags.UintVar(
&cf.StopTimeout,
"stop-timeout", defaultContainerConfig.Engine.StopTimeout,
"Timeout (in seconds) to stop a container. Default is 10",
)
createFlags.StringSliceVar(
&cf.StoreageOpt,
"storage-opt", []string{},
"Storage driver options per container",
)
createFlags.StringVar(
&cf.SubUIDName,
"subgidname", "",
"Name of range listed in /etc/subgid for use in user namespace",
)
createFlags.StringVar(
&cf.SubGIDName,
"subuidname", "",
"Name of range listed in /etc/subuid for use in user namespace",
)
createFlags.StringSliceVar(
&cf.Sysctl,
"sysctl", getDefaultSysctls(),
"Sysctl options",
)
createFlags.StringVar(
&cf.SystemdD,
"systemd", "true",
`Run container in systemd mode ("true"|"false"|"always")`,
)
createFlags.StringArrayVar(
&cf.TmpFS,
"tmpfs", []string{},
"Mount a temporary filesystem (`tmpfs`) into a container",
)
createFlags.BoolVarP(
&cf.TTY,
"tty", "t", false,
"Allocate a pseudo-TTY for container",
)
createFlags.StringSliceVar(
&cf.UIDMap,
"uidmap", []string{},
"UID map to use for the user namespace",
)
createFlags.StringSliceVar(
&cf.Ulimit,
"ulimit", getDefaultUlimits(),
"Ulimit options",
)
createFlags.StringVarP(
&cf.User,
"user", "u", "",
"Username or UID (format: <name|uid>[:<group|gid>])",
)
createFlags.StringVar(
&cf.UserNS,
"userns", getDefaultUserNS(),
"User namespace to use",
)
createFlags.StringVar(
&cf.UTS,
"uts", getDefaultUTSNS(),
"UTS namespace to use",
)
createFlags.StringArrayVar(
&cf.Mount,
"mount", []string{},
"Attach a filesystem mount to the container",
)
createFlags.StringArrayVarP(
&cf.Volume,
"volume", "v", getDefaultVolumes(),
"Bind mount a volume into the container",
)
createFlags.StringSliceVar(
&cf.VolumesFrom,
"volumes-from", []string{},
"Mount volumes from the specified container(s)",
)
createFlags.StringVarP(
&cf.Workdir,
"workdir", "w", "",
"Working directory inside the container",
)
createFlags.StringVar(
&cf.SeccompPolicy,
"seccomp-policy", "default",
"Policy for selecting a seccomp profile (experimental)",
)
return &createFlags
}
func AliasFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
switch name {
case "healthcheck-command":
name = "health-cmd"
case "healthcheck-interval":
name = "health-interval"
case "healthcheck-retries":
name = "health-retries"
case "healthcheck-start-period":
name = "health-start-period"
case "healthcheck-timeout":
name = "health-timeout"
case "net":
name = "network"
}
return pflag.NormalizedName(name)
}

View File

@ -0,0 +1,103 @@
package common
import "github.com/containers/libpod/pkg/domain/entities"
type ContainerCLIOpts struct {
Annotation []string
Attach []string
Authfile string
BlkIOWeight string
BlkIOWeightDevice []string
CapAdd []string
CapDrop []string
CGroupsNS string
CGroups string
CGroupParent string
CIDFile string
ConmonPIDFile string
CPUPeriod uint64
CPUQuota int64
CPURTPeriod uint64
CPURTRuntime int64
CPUShares uint64
CPUS float64
CPUSetCPUs string
CPUSetMems string
Detach bool
DetachKeys string
Device []string
DeviceCGroupRule []string
DeviceReadBPs []string
DeviceReadIOPs []string
DeviceWriteBPs []string
DeviceWriteIOPs []string
Entrypoint string
env []string
EnvHost bool
EnvFile []string
Expose []string
GIDMap []string
GroupAdd []string
HealthCmd string
HealthInterval string
HealthRetries uint
HealthStartPeriod string
HealthTimeout string
Hostname string
HTTPProxy bool
ImageVolume string
Init bool
InitPath string
Interactive bool
IPC string
KernelMemory string
Label []string
LabelFile []string
LogDriver string
LogOptions []string
Memory string
MemoryReservation string
MemorySwap string
MemorySwappiness int64
Name string
NoHealthCheck bool
OOMKillDisable bool
OOMScoreAdj int
OverrideArch string
OverrideOS string
PID string
PIDsLimit int64
Pod string
Privileged bool
PublishAll bool
Pull string
Quiet bool
ReadOnly bool
ReadOnlyTmpFS bool
Restart string
Rm bool
RootFS bool
SecurityOpt []string
ShmSize string
StopSignal string
StopTimeout uint
StoreageOpt []string
SubUIDName string
SubGIDName string
Sysctl []string
SystemdD string
TmpFS []string
TTY bool
UIDMap []string
Ulimit []string
User string
UserNS string
UTS string
Mount []string
Volume []string
VolumesFrom []string
Workdir string
SeccompPolicy string
Net *entities.NetOptions
}

View File

@ -0,0 +1,51 @@
package common
import (
"github.com/containers/libpod/cmd/podmanV2/parse"
"github.com/containers/libpod/pkg/util"
"github.com/pkg/errors"
)
// validate determines if the flags and values given by the user are valid. things checked
// by validate must not need any state information on the flag (i.e. changed)
func (c *ContainerCLIOpts) validate() error {
var ()
if c.Rm && c.Restart != "" && c.Restart != "no" {
return errors.Errorf("the --rm option conflicts with --restart")
}
if _, err := util.ValidatePullType(c.Pull); err != nil {
return err
}
// Verify the additional hosts are in correct format
for _, host := range c.Net.AddHosts {
if _, err := parse.ValidateExtraHost(host); err != nil {
return err
}
}
if dnsSearches := c.Net.DNSSearch; len(dnsSearches) > 0 {
// Validate domains are good
for _, dom := range dnsSearches {
if dom == "." {
if len(dnsSearches) > 1 {
return errors.Errorf("cannot pass additional search domains when also specifying '.'")
}
continue
}
if _, err := parse.ValidateDomain(dom); err != nil {
return err
}
}
}
var imageVolType = map[string]string{
"bind": "",
"tmpfs": "",
"ignore": "",
}
if _, ok := imageVolType[c.ImageVolume]; !ok {
return errors.Errorf("invalid image-volume type %q. Pick one of bind, tmpfs, or ignore", c.ImageVolume)
}
return nil
}

View File

@ -0,0 +1,121 @@
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/sysinfo"
"github.com/opencontainers/selinux/go-selinux"
)
// 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 defaultContainerConfig.Containers.SeccompProfile != "" && defaultContainerConfig.Containers.SeccompProfile != parse.SeccompDefaultPath {
securityOpts = append(securityOpts, fmt.Sprintf("seccomp=%s", defaultContainerConfig.Containers.SeccompProfile))
}
if apparmor.IsEnabled() && defaultContainerConfig.Containers.ApparmorProfile != "" {
securityOpts = append(securityOpts, fmt.Sprintf("apparmor=%s", defaultContainerConfig.Containers.ApparmorProfile))
}
if selinux.GetEnabled() && !defaultContainerConfig.Containers.EnableLabeling {
securityOpts = append(securityOpts, fmt.Sprintf("label=%s", selinux.DisableSecOpt()[0]))
}
return securityOpts
}
// getDefaultSysctls
func getDefaultSysctls() []string {
return defaultContainerConfig.Containers.DefaultSysctls
}
func getDefaultVolumes() []string {
return defaultContainerConfig.Containers.Volumes
}
func getDefaultDevices() []string {
return defaultContainerConfig.Containers.Devices
}
func getDefaultDNSServers() []string { //nolint
return defaultContainerConfig.Containers.DNSServers
}
func getDefaultDNSSearches() []string { //nolint
return defaultContainerConfig.Containers.DNSSearches
}
func getDefaultDNSOptions() []string { //nolint
return defaultContainerConfig.Containers.DNSOptions
}
func getDefaultEnv() []string {
return defaultContainerConfig.Containers.Env
}
func getDefaultInitPath() string {
return defaultContainerConfig.Containers.InitPath
}
func getDefaultIPCNS() string {
return defaultContainerConfig.Containers.IPCNS
}
func getDefaultPidNS() string {
return defaultContainerConfig.Containers.PidNS
}
func getDefaultNetNS() string { //nolint
if defaultContainerConfig.Containers.NetNS == "private" && rootless.IsRootless() {
return "slirp4netns"
}
return defaultContainerConfig.Containers.NetNS
}
func getDefaultCgroupNS() string {
return defaultContainerConfig.Containers.CgroupNS
}
func getDefaultUTSNS() string {
return defaultContainerConfig.Containers.UTSNS
}
func getDefaultShmSize() string {
return defaultContainerConfig.Containers.ShmSize
}
func getDefaultUlimits() []string {
return defaultContainerConfig.Containers.DefaultUlimits
}
func getDefaultUserNS() string {
userns := os.Getenv("PODMAN_USERNS")
if userns != "" {
return userns
}
return defaultContainerConfig.Containers.UserNS
}
func getDefaultPidsLimit() int64 {
if rootless.IsRootless() {
cgroup2, _ := cgroups.IsCgroup2UnifiedMode()
if cgroup2 {
return defaultContainerConfig.Containers.PidsLimit
}
}
return sysinfo.GetDefaultPidsLimit()
}
func getDefaultPidsDescription() string {
return "Tune container pids limit (set 0 for unlimited)"
}
func getDefaultDetachKeys() string {
return defaultContainerConfig.Engine.DetachKeys
}

View File

@ -0,0 +1,126 @@
package common
import (
"fmt"
"net"
"strconv"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// ExposedPorts parses user and image ports and returns binding information
func ExposedPorts(expose []string, publish []ocicni.PortMapping, publishAll bool, imageExposedPorts map[string]struct{}) ([]ocicni.PortMapping, error) {
containerPorts := make(map[string]string)
// TODO this needs to be added into a something that
// has access to an imageengine
// add expose ports from the image itself
//for expose := range imageExposedPorts {
// _, port := nat.SplitProtoPort(expose)
// containerPorts[port] = ""
//}
// add the expose ports from the user (--expose)
// can be single or a range
for _, expose := range expose {
//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
_, port := nat.SplitProtoPort(expose)
//parse the start and end port and create a sequence of ports to expose
//if expose a port, the start and end port are the same
start, end, err := nat.ParsePortRange(port)
if err != nil {
return nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", expose, err)
}
for i := start; i <= end; i++ {
containerPorts[strconv.Itoa(int(i))] = ""
}
}
// TODO/FIXME this is hell reencarnated
// parse user inputted port bindings
pbPorts, portBindings, err := nat.ParsePortSpecs([]string{})
if err != nil {
return nil, err
}
// delete exposed container ports if being used by -p
for i := range pbPorts {
delete(containerPorts, i.Port())
}
// iterate container ports and make port bindings from them
if publishAll {
for e := range containerPorts {
//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
//proto, port := nat.SplitProtoPort(e)
p, err := nat.NewPort("tcp", e)
if err != nil {
return nil, err
}
rp, err := getRandomPort()
if err != nil {
return nil, err
}
logrus.Debug(fmt.Sprintf("Using random host port %d with container port %d", rp, p.Int()))
portBindings[p] = CreatePortBinding(rp, "")
}
}
// We need to see if any host ports are not populated and if so, we need to assign a
// random port to them.
for k, pb := range portBindings {
if pb[0].HostPort == "" {
hostPort, err := getRandomPort()
if err != nil {
return nil, err
}
logrus.Debug(fmt.Sprintf("Using random host port %d with container port %s", hostPort, k.Port()))
pb[0].HostPort = strconv.Itoa(hostPort)
}
}
var pms []ocicni.PortMapping
for k, v := range portBindings {
for _, pb := range v {
hp, err := strconv.Atoi(pb.HostPort)
if err != nil {
return nil, err
}
pms = append(pms, ocicni.PortMapping{
HostPort: int32(hp),
ContainerPort: int32(k.Int()),
//Protocol: "",
HostIP: pb.HostIP,
})
}
}
return pms, nil
}
func getRandomPort() (int, error) {
l, err := net.Listen("tcp", ":0")
if err != nil {
return 0, errors.Wrapf(err, "unable to get free port")
}
defer l.Close()
_, randomPort, err := net.SplitHostPort(l.Addr().String())
if err != nil {
return 0, errors.Wrapf(err, "unable to determine free port")
}
rp, err := strconv.Atoi(randomPort)
if err != nil {
return 0, errors.Wrapf(err, "unable to convert random port to int")
}
return rp, nil
}
//CreatePortBinding takes port (int) and IP (string) and creates an array of portbinding structs
func CreatePortBinding(hostPort int, hostIP string) []nat.PortBinding {
pb := nat.PortBinding{
HostPort: strconv.Itoa(hostPort),
}
pb.HostIP = hostIP
return []nat.PortBinding{pb}
}

View File

@ -0,0 +1,647 @@
package common
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/containers/image/v5/manifest"
"github.com/containers/libpod/cmd/podmanV2/parse"
"github.com/containers/libpod/libpod"
ann "github.com/containers/libpod/pkg/annotations"
envLib "github.com/containers/libpod/pkg/env"
ns "github.com/containers/libpod/pkg/namespaces"
"github.com/containers/libpod/pkg/specgen"
systemdGen "github.com/containers/libpod/pkg/systemd/generate"
"github.com/containers/libpod/pkg/util"
"github.com/docker/go-units"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) error {
var (
err error
//namespaces map[string]string
)
// validate flags as needed
if err := c.validate(); err != nil {
return nil
}
inputCommand := args[1:]
if len(c.HealthCmd) > 0 {
s.HealthConfig, err = makeHealthCheckFromCli(c.HealthCmd, c.HealthInterval, c.HealthRetries, c.HealthTimeout, c.HealthStartPeriod)
if err != nil {
return err
}
}
s.IDMappings, err = util.ParseIDMapping(ns.UsernsMode(c.UserNS), c.UIDMap, c.GIDMap, c.SubUIDName, c.SubGIDName)
if err != nil {
return err
}
if m := c.Memory; len(m) > 0 {
ml, err := units.RAMInBytes(m)
if err != nil {
return errors.Wrapf(err, "invalid value for memory")
}
s.ResourceLimits.Memory.Limit = &ml
}
if m := c.MemoryReservation; len(m) > 0 {
mr, err := units.RAMInBytes(m)
if err != nil {
return errors.Wrapf(err, "invalid value for memory")
}
s.ResourceLimits.Memory.Reservation = &mr
}
if m := c.MemorySwap; len(m) > 0 {
var ms int64
if m == "-1" {
ms = int64(-1)
s.ResourceLimits.Memory.Swap = &ms
} else {
ms, err = units.RAMInBytes(m)
if err != nil {
return errors.Wrapf(err, "invalid value for memory")
}
}
s.ResourceLimits.Memory.Swap = &ms
}
if m := c.KernelMemory; len(m) > 0 {
mk, err := units.RAMInBytes(m)
if err != nil {
return errors.Wrapf(err, "invalid value for kernel-memory")
}
s.ResourceLimits.Memory.Kernel = &mk
}
if b := c.BlkIOWeight; len(b) > 0 {
u, err := strconv.ParseUint(b, 10, 16)
if err != nil {
return errors.Wrapf(err, "invalid value for blkio-weight")
}
nu := uint16(u)
s.ResourceLimits.BlockIO.Weight = &nu
}
s.Terminal = c.TTY
ep, err := ExposedPorts(c.Expose, c.Net.PublishPorts, c.PublishAll, nil)
if err != nil {
return err
}
s.PortMappings = ep
s.Pod = c.Pod
//s.CgroupNS = specgen.Namespace{
// NSMode: ,
// Value: "",
//}
//s.UserNS = specgen.Namespace{}
// Kernel Namespaces
// TODO Fix handling of namespace from pod
// Instead of integrating here, should be done in libpod
// However, that also involves setting up security opts
// when the pod's namespace is integrated
//namespaces = map[string]string{
// "cgroup": c.CGroupsNS,
// "pid": c.PID,
// //"net": c.Net.Network.Value, // TODO need help here
// "ipc": c.IPC,
// "user": c.User,
// "uts": c.UTS,
//}
//
//if len(c.PID) > 0 {
// split := strings.SplitN(c.PID, ":", 2)
// // need a way to do thsi
// specgen.Namespace{
// NSMode: split[0],
// }
// //Value: split1 if len allows
//}
// TODO this is going to have be done after things like pod creation are done because
// pod creation changes these values.
//pidMode := ns.PidMode(namespaces["pid"])
//usernsMode := ns.UsernsMode(namespaces["user"])
//utsMode := ns.UTSMode(namespaces["uts"])
//cgroupMode := ns.CgroupMode(namespaces["cgroup"])
//ipcMode := ns.IpcMode(namespaces["ipc"])
//// Make sure if network is set to container namespace, port binding is not also being asked for
//netMode := ns.NetworkMode(namespaces["net"])
//if netMode.IsContainer() {
// if len(portBindings) > 0 {
// return nil, errors.Errorf("cannot set port bindings on an existing container network namespace")
// }
//}
// TODO Remove when done with namespaces for realz
// Setting a default for IPC to get this working
s.IpcNS = specgen.Namespace{
NSMode: specgen.Private,
Value: "",
}
// TODO this is going to have to be done the libpod/server end of things
// USER
//user := c.String("user")
//if user == "" {
// switch {
// case usernsMode.IsKeepID():
// user = fmt.Sprintf("%d:%d", rootless.GetRootlessUID(), rootless.GetRootlessGID())
// case data == nil:
// user = "0"
// default:
// user = data.Config.User
// }
//}
// STOP SIGNAL
signalString := "TERM"
if sig := c.StopSignal; len(sig) > 0 {
signalString = sig
}
stopSignal, err := util.ParseSignal(signalString)
if err != nil {
return err
}
s.StopSignal = &stopSignal
// ENVIRONMENT VARIABLES
//
// Precedence order (higher index wins):
// 1) env-host, 2) image data, 3) env-file, 4) env
env := map[string]string{
"container": "podman",
}
// First transform the os env into a map. We need it for the labels later in
// any case.
osEnv, err := envLib.ParseSlice(os.Environ())
if err != nil {
return errors.Wrap(err, "error parsing host environment variables")
}
if c.EnvHost {
env = envLib.Join(env, osEnv)
}
// env-file overrides any previous variables
for _, f := range c.EnvFile {
fileEnv, err := envLib.ParseFile(f)
if err != nil {
return err
}
// File env is overridden by env.
env = envLib.Join(env, fileEnv)
}
// env overrides any previous variables
if cmdLineEnv := c.env; len(cmdLineEnv) > 0 {
parsedEnv, err := envLib.ParseSlice(cmdLineEnv)
if err != nil {
return err
}
env = envLib.Join(env, parsedEnv)
}
s.Env = env
// LABEL VARIABLES
labels, err := parse.GetAllLabels(c.LabelFile, c.Label)
if err != nil {
return errors.Wrapf(err, "unable to process labels")
}
if systemdUnit, exists := osEnv[systemdGen.EnvVariable]; exists {
labels[systemdGen.EnvVariable] = systemdUnit
}
s.Labels = labels
// ANNOTATIONS
annotations := make(map[string]string)
// First, add our default annotations
annotations[ann.TTY] = "false"
if c.TTY {
annotations[ann.TTY] = "true"
}
// Last, add user annotations
for _, annotation := range c.Annotation {
splitAnnotation := strings.SplitN(annotation, "=", 2)
if len(splitAnnotation) < 2 {
return errors.Errorf("Annotations must be formatted KEY=VALUE")
}
annotations[splitAnnotation[0]] = splitAnnotation[1]
}
s.Annotations = annotations
workDir := "/"
if wd := c.Workdir; len(wd) > 0 {
workDir = wd
}
s.WorkDir = workDir
entrypoint := []string{}
userCommand := []string{}
if ep := c.Entrypoint; len(ep) > 0 {
// Check if entrypoint specified is json
if err := json.Unmarshal([]byte(c.Entrypoint), &entrypoint); err != nil {
entrypoint = append(entrypoint, ep)
}
}
var command []string
// Build the command
// If we have an entry point, it goes first
if len(entrypoint) > 0 {
command = entrypoint
}
if len(inputCommand) > 0 {
// User command overrides data CMD
command = append(command, inputCommand...)
userCommand = append(userCommand, inputCommand...)
}
if len(inputCommand) > 0 {
s.Command = userCommand
} else {
s.Command = command
}
// SHM Size
shmSize, err := units.FromHumanSize(c.ShmSize)
if err != nil {
return errors.Wrapf(err, "unable to translate --shm-size")
}
s.ShmSize = &shmSize
s.HostAdd = c.Net.AddHosts
s.DNSServer = c.Net.DNSServers
s.DNSSearch = c.Net.DNSSearch
s.DNSOption = c.Net.DNSOptions
// deferred, must be added on libpod side
//var ImageVolumes map[string]struct{}
//if data != nil && c.String("image-volume") != "ignore" {
// ImageVolumes = data.Config.Volumes
//}
s.ImageVolumeMode = c.ImageVolume
systemd := c.SystemdD == "always"
if !systemd && command != nil {
x, err := strconv.ParseBool(c.SystemdD)
if err != nil {
return errors.Wrapf(err, "cannot parse bool %s", c.SystemdD)
}
if x && (command[0] == "/usr/sbin/init" || command[0] == "/sbin/init" || (filepath.Base(command[0]) == "systemd")) {
systemd = true
}
}
if systemd {
if s.StopSignal == nil {
stopSignal, err = util.ParseSignal("RTMIN+3")
if err != nil {
return errors.Wrapf(err, "error parsing systemd signal")
}
s.StopSignal = &stopSignal
}
}
swappiness := uint64(c.MemorySwappiness)
if s.ResourceLimits == nil {
s.ResourceLimits = &specs.LinuxResources{}
}
if s.ResourceLimits.Memory == nil {
s.ResourceLimits.Memory = &specs.LinuxMemory{}
}
s.ResourceLimits.Memory.Swappiness = &swappiness
if s.LogConfiguration == nil {
s.LogConfiguration = &specgen.LogConfig{}
}
s.LogConfiguration.Driver = libpod.KubernetesLogging
if ld := c.LogDriver; len(ld) > 0 {
s.LogConfiguration.Driver = ld
}
if s.ResourceLimits.Pids == nil {
s.ResourceLimits.Pids = &specs.LinuxPids{}
}
s.ResourceLimits.Pids.Limit = c.PIDsLimit
if c.CGroups == "disabled" && c.PIDsLimit > 0 {
s.ResourceLimits.Pids.Limit = -1
}
// TODO WTF
//cgroup := &cc.CgroupConfig{
// Cgroups: c.String("cgroups"),
// Cgroupns: c.String("cgroupns"),
// CgroupParent: c.String("cgroup-parent"),
// CgroupMode: cgroupMode,
//}
//
//userns := &cc.UserConfig{
// GroupAdd: c.StringSlice("group-add"),
// IDMappings: idmappings,
// UsernsMode: usernsMode,
// User: user,
//}
//
//uts := &cc.UtsConfig{
// UtsMode: utsMode,
// NoHosts: c.Bool("no-hosts"),
// HostAdd: c.StringSlice("add-host"),
// Hostname: c.String("hostname"),
//}
sysctl := map[string]string{}
if ctl := c.Sysctl; len(ctl) > 0 {
sysctl, err = util.ValidateSysctls(ctl)
if err != nil {
return err
}
}
s.Sysctl = sysctl
s.CapAdd = c.CapAdd
s.CapDrop = c.CapDrop
s.Privileged = c.Privileged
s.ReadOnlyFilesystem = c.ReadOnly
// TODO
// ouitside of specgen and oci though
// defaults to true, check spec/storage
//s.readon = c.ReadOnlyTmpFS
// TODO convert to map?
// check if key=value and convert
sysmap := make(map[string]string)
for _, ctl := range c.Sysctl {
splitCtl := strings.SplitN(ctl, "=", 2)
if len(splitCtl) < 2 {
return errors.Errorf("invalid sysctl value %q", ctl)
}
sysmap[splitCtl[0]] = splitCtl[1]
}
s.Sysctl = sysmap
for _, opt := range c.SecurityOpt {
if opt == "no-new-privileges" {
s.ContainerSecurityConfig.NoNewPrivileges = true
} else {
con := strings.SplitN(opt, "=", 2)
if len(con) != 2 {
return fmt.Errorf("invalid --security-opt 1: %q", opt)
}
switch con[0] {
case "label":
// TODO selinux opts and label opts are the same thing
s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, con[1])
case "apparmor":
s.ContainerSecurityConfig.ApparmorProfile = con[1]
case "seccomp":
s.SeccompProfilePath = con[1]
default:
return fmt.Errorf("invalid --security-opt 2: %q", opt)
}
}
}
// TODO any idea why this was done
// storage.go from spec/
// grab it
//volumes := rtc.Containers.Volumes
// TODO conflict on populate?
//if v := c.Volume; len(v)> 0 {
// s.Volumes = append(volumes, c.StringSlice("volume")...)
//}
//s.volu
//s.Mounts = c.Mount
s.VolumesFrom = c.VolumesFrom
// TODO any idea why this was done
//devices := rtc.Containers.Devices
// TODO conflict on populate?
//
//if c.Changed("device") {
// devices = append(devices, c.StringSlice("device")...)
//}
// TODO things i cannot find in spec
// we dont think these are in the spec
// init - initbinary
// initpath
s.Stdin = c.Interactive
// quiet
//DeviceCgroupRules: c.StringSlice("device-cgroup-rule"),
if bps := c.DeviceReadBPs; len(bps) > 0 {
if s.ThrottleReadBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
return err
}
}
if bps := c.DeviceWriteBPs; len(bps) > 0 {
if s.ThrottleWriteBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
return err
}
}
if iops := c.DeviceReadIOPs; len(iops) > 0 {
if s.ThrottleReadIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
return err
}
}
if iops := c.DeviceWriteIOPs; len(iops) > 0 {
if s.ThrottleWriteIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
return err
}
}
s.ResourceLimits.Memory.DisableOOMKiller = &c.OOMKillDisable
// Rlimits/Ulimits
for _, u := range c.Ulimit {
if u == "host" {
s.Rlimits = nil
break
}
ul, err := units.ParseUlimit(u)
if err != nil {
return errors.Wrapf(err, "ulimit option %q requires name=SOFT:HARD, failed to be parsed", u)
}
rl := specs.POSIXRlimit{
Type: ul.Name,
Hard: uint64(ul.Hard),
Soft: uint64(ul.Soft),
}
s.Rlimits = append(s.Rlimits, rl)
}
//Tmpfs: c.StringArray("tmpfs"),
// TODO how to handle this?
//Syslog: c.Bool("syslog"),
logOpts := make(map[string]string)
for _, o := range c.LogOptions {
split := strings.SplitN(o, "=", 2)
if len(split) < 2 {
return errors.Errorf("invalid log option %q", o)
}
logOpts[split[0]] = split[1]
}
s.LogConfiguration.Options = logOpts
s.Name = c.Name
if err := parseWeightDevices(c.BlkIOWeightDevice, s); err != nil {
return err
}
if s.ResourceLimits.CPU == nil {
s.ResourceLimits.CPU = &specs.LinuxCPU{}
}
s.ResourceLimits.CPU.Shares = &c.CPUShares
s.ResourceLimits.CPU.Period = &c.CPUPeriod
// TODO research these
//s.ResourceLimits.CPU.Cpus = c.CPUS
//s.ResourceLimits.CPU.Cpus = c.CPUSetCPUs
//s.ResourceLimits.CPU. = c.CPUSetCPUs
s.ResourceLimits.CPU.Mems = c.CPUSetMems
s.ResourceLimits.CPU.Quota = &c.CPUQuota
s.ResourceLimits.CPU.RealtimePeriod = &c.CPURTPeriod
s.ResourceLimits.CPU.RealtimeRuntime = &c.CPURTRuntime
s.OOMScoreAdj = &c.OOMScoreAdj
s.RestartPolicy = c.Restart
s.Remove = c.Rm
s.StopTimeout = &c.StopTimeout
// TODO where should we do this?
//func verifyContainerResources(config *cc.CreateConfig, update bool) ([]string, error) {
return nil
}
func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, startPeriod string) (*manifest.Schema2HealthConfig, error) {
// Every healthcheck requires a command
if len(inCmd) == 0 {
return nil, errors.New("Must define a healthcheck command for all healthchecks")
}
// first try to parse option value as JSON array of strings...
cmd := []string{}
err := json.Unmarshal([]byte(inCmd), &cmd)
if err != nil {
// ...otherwise pass it to "/bin/sh -c" inside the container
cmd = []string{"CMD-SHELL", inCmd}
}
hc := manifest.Schema2HealthConfig{
Test: cmd,
}
if interval == "disable" {
interval = "0"
}
intervalDuration, err := time.ParseDuration(interval)
if err != nil {
return nil, errors.Wrapf(err, "invalid healthcheck-interval %s ", interval)
}
hc.Interval = intervalDuration
if retries < 1 {
return nil, errors.New("healthcheck-retries must be greater than 0.")
}
hc.Retries = int(retries)
timeoutDuration, err := time.ParseDuration(timeout)
if err != nil {
return nil, errors.Wrapf(err, "invalid healthcheck-timeout %s", timeout)
}
if timeoutDuration < time.Duration(1) {
return nil, errors.New("healthcheck-timeout must be at least 1 second")
}
hc.Timeout = timeoutDuration
startPeriodDuration, err := time.ParseDuration(startPeriod)
if err != nil {
return nil, errors.Wrapf(err, "invalid healthcheck-start-period %s", startPeriod)
}
if startPeriodDuration < time.Duration(0) {
return nil, errors.New("healthcheck-start-period must be 0 seconds or greater")
}
hc.StartPeriod = startPeriodDuration
return &hc, nil
}
func parseWeightDevices(weightDevs []string, s *specgen.SpecGenerator) error {
for _, val := range weightDevs {
split := strings.SplitN(val, ":", 2)
if len(split) != 2 {
return fmt.Errorf("bad format: %s", val)
}
if !strings.HasPrefix(split[0], "/dev/") {
return fmt.Errorf("bad format for device path: %s", val)
}
weight, err := strconv.ParseUint(split[1], 10, 0)
if err != nil {
return fmt.Errorf("invalid weight for device: %s", val)
}
if weight > 0 && (weight < 10 || weight > 1000) {
return fmt.Errorf("invalid weight for device: %s", val)
}
w := uint16(weight)
s.WeightDevice[split[0]] = specs.LinuxWeightDevice{
Weight: &w,
LeafWeight: nil,
}
}
return nil
}
func parseThrottleBPSDevices(bpsDevices []string) (map[string]specs.LinuxThrottleDevice, error) {
td := make(map[string]specs.LinuxThrottleDevice)
for _, val := range bpsDevices {
split := strings.SplitN(val, ":", 2)
if len(split) != 2 {
return nil, fmt.Errorf("bad format: %s", val)
}
if !strings.HasPrefix(split[0], "/dev/") {
return nil, fmt.Errorf("bad format for device path: %s", val)
}
rate, err := units.RAMInBytes(split[1])
if err != nil {
return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
}
if rate < 0 {
return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
}
td[split[0]] = specs.LinuxThrottleDevice{Rate: uint64(rate)}
}
return td, nil
}
func parseThrottleIOPsDevices(iopsDevices []string) (map[string]specs.LinuxThrottleDevice, error) {
td := make(map[string]specs.LinuxThrottleDevice)
for _, val := range iopsDevices {
split := strings.SplitN(val, ":", 2)
if len(split) != 2 {
return nil, fmt.Errorf("bad format: %s", val)
}
if !strings.HasPrefix(split[0], "/dev/") {
return nil, fmt.Errorf("bad format for device path: %s", val)
}
rate, err := strconv.ParseUint(split[1], 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val)
}
td[split[0]] = specs.LinuxThrottleDevice{Rate: rate}
}
return td, nil
}

View File

@ -0,0 +1,102 @@
package containers
import (
"fmt"
"github.com/containers/libpod/cmd/podmanV2/common"
"github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/specgen"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var (
createDescription = `Creates a new container from the given image or storage and prepares it for running the specified command.
The container ID is then printed to stdout. You can then start it at any time with the podman start <container_id> command. The container will be created with the initial state 'created'.`
createCommand = &cobra.Command{
Use: "create [flags] IMAGE [COMMAND [ARG...]]",
Short: "Create but do not start a container",
Long: createDescription,
RunE: create,
PersistentPreRunE: preRunE,
Args: cobra.MinimumNArgs(1),
Example: `podman create alpine ls
podman create --annotation HELLO=WORLD alpine ls
podman create -t -i --name myctr alpine ls`,
}
)
var (
cliVals common.ContainerCLIOpts
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: createCommand,
})
//common.GetCreateFlags(createCommand)
flags := createCommand.Flags()
flags.AddFlagSet(common.GetCreateFlags(&cliVals))
flags.AddFlagSet(common.GetNetFlags())
flags.SetNormalizeFunc(common.AliasFlags)
}
func create(cmd *cobra.Command, args []string) error {
var (
err error
rawImageInput string
)
cliVals.Net, err = common.NetFlagsToNetOptions(cmd)
if err != nil {
return err
}
if rfs := cliVals.RootFS; !rfs {
rawImageInput = args[0]
}
if err := createInit(cmd); err != nil {
return err
}
//TODO rootfs still
s := specgen.NewSpecGenerator(rawImageInput)
if err := common.FillOutSpecGen(s, &cliVals, args); err != nil {
return err
}
report, err := registry.ContainerEngine().ContainerCreate(registry.GetContext(), s)
if err != nil {
return err
}
fmt.Println(report.Id)
return nil
}
func createInit(c *cobra.Command) error {
if c.Flag("privileged").Changed && c.Flag("security-opt").Changed {
logrus.Warn("setting security options with --privileged has no effect")
}
if (c.Flag("dns").Changed || c.Flag("dns-opt").Changed || c.Flag("dns-search").Changed) && (cliVals.Net.Network.NSMode == specgen.NoNetwork || cliVals.Net.Network.IsContainer()) {
return errors.Errorf("conflicting options: dns and the network mode.")
}
if c.Flag("cpu-period").Changed && c.Flag("cpus").Changed {
return errors.Errorf("--cpu-period and --cpus cannot be set together")
}
if c.Flag("cpu-quota").Changed && c.Flag("cpus").Changed {
return errors.Errorf("--cpu-quota and --cpus cannot be set together")
}
if c.Flag("no-hosts").Changed && c.Flag("add-host").Changed {
return errors.Errorf("--no-hosts and --add-host cannot be set together")
}
// Docker-compatibility: the "-h" flag for run/create is reserved for
// the hostname (see https://github.com/containers/libpod/issues/1367).
return nil
}

View File

@ -0,0 +1,50 @@
package parse
import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
// CheckAllLatestAndCIDFile checks that --all and --latest are used correctly.
// If cidfile is set, also check for the --cidfile flag.
func CheckAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool, cidfile bool) error {
argLen := len(args)
if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil {
if !cidfile {
return errors.New("unable to lookup values for 'latest' or 'all'")
} else if c.Flags().Lookup("cidfile") == nil {
return errors.New("unable to lookup values for 'latest', 'all' or 'cidfile'")
}
}
specifiedAll, _ := c.Flags().GetBool("all")
specifiedLatest, _ := c.Flags().GetBool("latest")
specifiedCIDFile := false
if cid, _ := c.Flags().GetStringArray("cidfile"); len(cid) > 0 {
specifiedCIDFile = true
}
if specifiedCIDFile && (specifiedAll || specifiedLatest) {
return errors.Errorf("--all, --latest and --cidfile cannot be used together")
} else if specifiedAll && specifiedLatest {
return errors.Errorf("--all and --latest cannot be used together")
}
if ignoreArgLen {
return nil
}
if (argLen > 0) && (specifiedAll || specifiedLatest) {
return errors.Errorf("no arguments are needed with --all or --latest")
} else if cidfile && (argLen > 0) && (specifiedAll || specifiedLatest || specifiedCIDFile) {
return errors.Errorf("no arguments are needed with --all, --latest or --cidfile")
}
if specifiedCIDFile {
return nil
}
if argLen < 1 && !specifiedAll && !specifiedLatest && !specifiedCIDFile {
return errors.Errorf("you must provide at least one name or id")
}
return nil
}

View File

@ -13,7 +13,6 @@ import (
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
const (
@ -187,47 +186,3 @@ func ValidURL(urlStr string) error {
}
return nil
}
// checkAllLatestAndCIDFile checks that --all and --latest are used correctly.
// If cidfile is set, also check for the --cidfile flag.
func CheckAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool, cidfile bool) error {
argLen := len(args)
if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil {
if !cidfile {
return errors.New("unable to lookup values for 'latest' or 'all'")
} else if c.Flags().Lookup("cidfile") == nil {
return errors.New("unable to lookup values for 'latest', 'all' or 'cidfile'")
}
}
specifiedAll, _ := c.Flags().GetBool("all")
specifiedLatest, _ := c.Flags().GetBool("latest")
specifiedCIDFile := false
if cid, _ := c.Flags().GetStringArray("cidfile"); len(cid) > 0 {
specifiedCIDFile = true
}
if specifiedCIDFile && (specifiedAll || specifiedLatest) {
return errors.Errorf("--all, --latest and --cidfile cannot be used together")
} else if specifiedAll && specifiedLatest {
return errors.Errorf("--all and --latest cannot be used together")
}
if ignoreArgLen {
return nil
}
if (argLen > 0) && (specifiedAll || specifiedLatest) {
return errors.Errorf("no arguments are needed with --all or --latest")
} else if cidfile && (argLen > 0) && (specifiedAll || specifiedLatest || specifiedCIDFile) {
return errors.Errorf("no arguments are needed with --all, --latest or --cidfile")
}
if specifiedCIDFile {
return nil
}
if argLen < 1 && !specifiedAll && !specifiedLatest && !specifiedCIDFile {
return errors.Errorf("you must provide at least one name or id")
}
return nil
}

View File

@ -7,6 +7,7 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/specgen"
"github.com/containers/libpod/pkg/specgen/generate"
"github.com/pkg/errors"
)
@ -19,7 +20,11 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
return
}
ctr, err := sg.MakeContainer(runtime)
if err := generate.CompleteSpec(r.Context(), runtime, &sg); err != nil {
utils.InternalServerError(w, err)
return
}
ctr, err := generate.MakeContainer(runtime, &sg)
if err != nil {
utils.InternalServerError(w, err)
return

View File

@ -153,3 +153,7 @@ type RestoreReport struct {
Err error
Id string
}
type ContainerCreateReport struct {
Id string
}

View File

@ -4,12 +4,14 @@ import (
"context"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/specgen"
)
type ContainerEngine interface {
ContainerCommit(ctx context.Context, nameOrId string, options CommitOptions) (*CommitReport, error)
ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error)
ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error)
ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error)
ContainerExists(ctx context.Context, nameOrId string) (*BoolReport, error)
ContainerInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]*ContainerInspectReport, error)
ContainerExport(ctx context.Context, nameOrId string, options ContainerExportOptions) error

View File

@ -16,6 +16,8 @@ import (
"github.com/containers/libpod/pkg/checkpoint"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/signal"
"github.com/containers/libpod/pkg/specgen"
"github.com/containers/libpod/pkg/specgen/generate"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@ -442,3 +444,14 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
}
return reports, nil
}
func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*entities.ContainerCreateReport, error) {
if err := generate.CompleteSpec(ctx, ic.Libpod, s); err != nil {
return nil, err
}
ctr, err := generate.MakeContainer(ic.Libpod, s)
if err != nil {
return nil, err
}
return &entities.ContainerCreateReport{Id: ctr.ID()}, nil
}

View File

@ -10,6 +10,7 @@ import (
"github.com/containers/libpod/pkg/api/handlers/libpod"
"github.com/containers/libpod/pkg/bindings/containers"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/specgen"
"github.com/pkg/errors"
)
@ -296,3 +297,11 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
}
return reports, nil
}
func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*entities.ContainerCreateReport, error) {
response, err := containers.CreateWithSpec(ic.ClientCxt, s)
if err != nil {
return nil, err
}
return &entities.ContainerCreateReport{Id: response.ID}, nil
}

View File

@ -0,0 +1,93 @@
package specgen
//func createBlockIO() (*spec.LinuxBlockIO, error) {
// var ret *spec.LinuxBlockIO
// bio := &spec.LinuxBlockIO{}
// if c.Resources.BlkioWeight > 0 {
// ret = bio
// bio.Weight = &c.Resources.BlkioWeight
// }
// if len(c.Resources.BlkioWeightDevice) > 0 {
// var lwds []spec.LinuxWeightDevice
// ret = bio
// for _, i := range c.Resources.BlkioWeightDevice {
// wd, err := ValidateweightDevice(i)
// if err != nil {
// return ret, errors.Wrapf(err, "invalid values for blkio-weight-device")
// }
// wdStat, err := GetStatFromPath(wd.Path)
// if err != nil {
// return ret, errors.Wrapf(err, "error getting stat from path %q", wd.Path)
// }
// lwd := spec.LinuxWeightDevice{
// Weight: &wd.Weight,
// }
// lwd.Major = int64(unix.Major(wdStat.Rdev))
// lwd.Minor = int64(unix.Minor(wdStat.Rdev))
// lwds = append(lwds, lwd)
// }
// bio.WeightDevice = lwds
// }
// if len(c.Resources.DeviceReadBps) > 0 {
// ret = bio
// readBps, err := makeThrottleArray(c.Resources.DeviceReadBps, bps)
// if err != nil {
// return ret, err
// }
// bio.ThrottleReadBpsDevice = readBps
// }
// if len(c.Resources.DeviceWriteBps) > 0 {
// ret = bio
// writeBpds, err := makeThrottleArray(c.Resources.DeviceWriteBps, bps)
// if err != nil {
// return ret, err
// }
// bio.ThrottleWriteBpsDevice = writeBpds
// }
// if len(c.Resources.DeviceReadIOps) > 0 {
// ret = bio
// readIOps, err := makeThrottleArray(c.Resources.DeviceReadIOps, iops)
// if err != nil {
// return ret, err
// }
// bio.ThrottleReadIOPSDevice = readIOps
// }
// if len(c.Resources.DeviceWriteIOps) > 0 {
// ret = bio
// writeIOps, err := makeThrottleArray(c.Resources.DeviceWriteIOps, iops)
// if err != nil {
// return ret, err
// }
// bio.ThrottleWriteIOPSDevice = writeIOps
// }
// return ret, nil
//}
//func makeThrottleArray(throttleInput []string, rateType int) ([]spec.LinuxThrottleDevice, error) {
// var (
// ltds []spec.LinuxThrottleDevice
// t *throttleDevice
// err error
// )
// for _, i := range throttleInput {
// if rateType == bps {
// t, err = validateBpsDevice(i)
// } else {
// t, err = validateIOpsDevice(i)
// }
// if err != nil {
// return []spec.LinuxThrottleDevice{}, err
// }
// ltdStat, err := GetStatFromPath(t.path)
// if err != nil {
// return ltds, errors.Wrapf(err, "error getting stat from path %q", t.path)
// }
// ltd := spec.LinuxThrottleDevice{
// Rate: t.rate,
// }
// ltd.Major = int64(unix.Major(ltdStat.Rdev))
// ltd.Minor = int64(unix.Minor(ltdStat.Rdev))
// ltds = append(ltds, ltd)
// }
// return ltds, nil
//}

View File

@ -17,7 +17,6 @@ import (
func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) {
var seccompConfig *spec.LinuxSeccomp
var err error
scp, err := seccomp.LookupPolicy(s.SeccompPolicy)
if err != nil {
return nil, err

View File

@ -14,7 +14,7 @@ var (
// SystemDValues describes the only values that SystemD can be
SystemDValues = []string{"true", "false", "always"}
// ImageVolumeModeValues describes the only values that ImageVolumeMode can be
ImageVolumeModeValues = []string{"ignore", "tmpfs", "anonymous"}
ImageVolumeModeValues = []string{"ignore", "tmpfs", "bind"}
)
func exclusiveOptions(opt1, opt2 string) error {
@ -23,7 +23,7 @@ func exclusiveOptions(opt1, opt2 string) error {
// Validate verifies that the given SpecGenerator is valid and satisfies required
// input for creating a container.
func (s *SpecGenerator) validate() error {
func (s *SpecGenerator) Validate() error {
//
// ContainerBasicConfig

View File

@ -0,0 +1,168 @@
package generate
import (
"context"
"github.com/containers/libpod/libpod"
ann "github.com/containers/libpod/pkg/annotations"
envLib "github.com/containers/libpod/pkg/env"
"github.com/containers/libpod/pkg/signal"
"github.com/containers/libpod/pkg/specgen"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerator) error {
newImage, err := r.ImageRuntime().NewFromLocal(s.Image)
if err != nil {
return err
}
// Image stop signal
if s.StopSignal == nil && newImage.Config != nil {
sig, err := signal.ParseSignalNameOrNumber(newImage.Config.StopSignal)
if err != nil {
return err
}
s.StopSignal = &sig
}
// Image envs from the image if they don't exist
// already
if newImage.Config != nil && len(newImage.Config.Env) > 0 {
envs, err := envLib.ParseSlice(newImage.Config.Env)
if err != nil {
return err
}
for k, v := range envs {
if _, exists := s.Env[k]; !exists {
s.Env[v] = k
}
}
}
// labels from the image that dont exist already
if config := newImage.Config; config != nil {
for k, v := range config.Labels {
if _, exists := s.Labels[k]; !exists {
s.Labels[k] = v
}
}
}
// annotations
// in the event this container is in a pod, and the pod has an infra container
// we will want to configure it as a type "container" instead defaulting to
// the behavior of a "sandbox" container
// In Kata containers:
// - "sandbox" is the annotation that denotes the container should use its own
// VM, which is the default behavior
// - "container" denotes the container should join the VM of the SandboxID
// (the infra container)
s.Annotations = make(map[string]string)
if len(s.Pod) > 0 {
s.Annotations[ann.SandboxID] = s.Pod
s.Annotations[ann.ContainerType] = ann.ContainerTypeContainer
}
//
// Next, add annotations from the image
annotations, err := newImage.Annotations(ctx)
if err != nil {
return err
}
for k, v := range annotations {
annotations[k] = v
}
// entrypoint
if config := newImage.Config; config != nil {
if len(s.Entrypoint) < 1 && len(config.Entrypoint) > 0 {
s.Entrypoint = config.Entrypoint
}
if len(s.Command) < 1 && len(config.Cmd) > 0 {
s.Command = config.Cmd
}
if len(s.Command) < 1 && len(s.Entrypoint) < 1 {
return errors.Errorf("No command provided or as CMD or ENTRYPOINT in this image")
}
// workdir
if len(s.WorkDir) < 1 && len(config.WorkingDir) > 1 {
s.WorkDir = config.WorkingDir
}
}
if len(s.SeccompProfilePath) < 1 {
p, err := libpod.DefaultSeccompPath()
if err != nil {
return err
}
s.SeccompProfilePath = p
}
if user := s.User; len(user) == 0 {
switch {
// TODO This should be enabled when namespaces actually work
//case usernsMode.IsKeepID():
// user = fmt.Sprintf("%d:%d", rootless.GetRootlessUID(), rootless.GetRootlessGID())
case newImage.Config == nil || (newImage.Config != nil && len(newImage.Config.User) == 0):
s.User = "0"
default:
s.User = newImage.Config.User
}
}
if err := finishThrottleDevices(s); err != nil {
return err
}
return nil
}
// finishThrottleDevices takes the temporary representation of the throttle
// devices in the specgen and looks up the major and major minors. it then
// sets the throttle devices proper in the specgen
func finishThrottleDevices(s *specgen.SpecGenerator) error {
if bps := s.ThrottleReadBpsDevice; len(bps) > 0 {
for k, v := range bps {
statT := unix.Stat_t{}
if err := unix.Stat(k, &statT); err != nil {
return err
}
v.Major = (int64(unix.Major(statT.Rdev)))
v.Minor = (int64(unix.Minor(statT.Rdev)))
s.ResourceLimits.BlockIO.ThrottleReadBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleReadBpsDevice, v)
}
}
if bps := s.ThrottleWriteBpsDevice; len(bps) > 0 {
for k, v := range bps {
statT := unix.Stat_t{}
if err := unix.Stat(k, &statT); err != nil {
return err
}
v.Major = (int64(unix.Major(statT.Rdev)))
v.Minor = (int64(unix.Minor(statT.Rdev)))
s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice, v)
}
}
if iops := s.ThrottleReadIOPSDevice; len(iops) > 0 {
for k, v := range iops {
statT := unix.Stat_t{}
if err := unix.Stat(k, &statT); err != nil {
return err
}
v.Major = (int64(unix.Major(statT.Rdev)))
v.Minor = (int64(unix.Minor(statT.Rdev)))
s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice, v)
}
}
if iops := s.ThrottleWriteBpsDevice; len(iops) > 0 {
for k, v := range iops {
statT := unix.Stat_t{}
if err := unix.Stat(k, &statT); err != nil {
return err
}
v.Major = (int64(unix.Major(statT.Rdev)))
v.Minor = (int64(unix.Minor(statT.Rdev)))
s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice, v)
}
}
return nil
}

View File

@ -1,4 +1,4 @@
package specgen
package generate
import (
"context"
@ -7,14 +7,15 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/specgen"
"github.com/containers/storage"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// MakeContainer creates a container based on the SpecGenerator
func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, error) {
if err := s.validate(); err != nil {
func MakeContainer(rt *libpod.Runtime, s *specgen.SpecGenerator) (*libpod.Container, error) {
if err := s.Validate(); err != nil {
return nil, errors.Wrap(err, "invalid config provided")
}
rtc, err := rt.GetConfig()
@ -22,7 +23,7 @@ func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, er
return nil, err
}
options, err := s.createContainerOptions(rt)
options, err := createContainerOptions(rt, s)
if err != nil {
return nil, err
}
@ -31,7 +32,7 @@ func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, er
if err != nil {
return nil, err
}
options = append(options, s.createExitCommandOption(rt.StorageConfig(), rtc, podmanPath))
options = append(options, createExitCommandOption(s, rt.StorageConfig(), rtc, podmanPath))
newImage, err := rt.ImageRuntime().NewFromLocal(s.Image)
if err != nil {
return nil, err
@ -39,14 +40,14 @@ func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, er
options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName))
runtimeSpec, err := s.toOCISpec(rt, newImage)
runtimeSpec, err := s.ToOCISpec(rt, newImage)
if err != nil {
return nil, err
}
return rt.NewContainer(context.Background(), runtimeSpec, options...)
}
func (s *SpecGenerator) createContainerOptions(rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) {
func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator) ([]libpod.CtrCreateOption, error) {
var options []libpod.CtrCreateOption
var err error
@ -114,7 +115,7 @@ func (s *SpecGenerator) createContainerOptions(rt *libpod.Runtime) ([]libpod.Ctr
options = append(options, libpod.WithPrivileged(s.Privileged))
// Get namespace related options
namespaceOptions, err := s.generateNamespaceContainerOpts(rt)
namespaceOptions, err := s.GenerateNamespaceContainerOpts(rt)
if err != nil {
return nil, err
}
@ -149,7 +150,7 @@ func (s *SpecGenerator) createContainerOptions(rt *libpod.Runtime) ([]libpod.Ctr
return options, nil
}
func (s *SpecGenerator) createExitCommandOption(storageConfig storage.StoreOptions, config *config.Config, podmanPath string) libpod.CtrCreateOption {
func createExitCommandOption(s *specgen.SpecGenerator, storageConfig storage.StoreOptions, config *config.Config, podmanPath string) libpod.CtrCreateOption {
// We need a cleanup process for containers in the current model.
// But we can't assume that the caller is Podman - it could be another
// user of the API.

View File

@ -16,6 +16,9 @@ import (
type NamespaceMode string
const (
// Default indicates the spec generator should determine
// a sane default
Default NamespaceMode = "default"
// Host means the the namespace is derived from
// the host
Host NamespaceMode = "host"
@ -83,7 +86,7 @@ func validateNetNS(n *Namespace) error {
return nil
}
// validate perform simple validation on the namespace to make sure it is not
// Validate perform simple validation on the namespace to make sure it is not
// invalid from the get-go
func (n *Namespace) validate() error {
if n == nil {
@ -103,7 +106,7 @@ func (n *Namespace) validate() error {
return nil
}
func (s *SpecGenerator) generateNamespaceContainerOpts(rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) {
func (s *SpecGenerator) GenerateNamespaceContainerOpts(rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) {
var portBindings []ocicni.PortMapping
options := make([]libpod.CtrCreateOption, 0)

View File

@ -11,7 +11,7 @@ import (
"github.com/opencontainers/runtime-tools/generate"
)
func (s *SpecGenerator) toOCISpec(rt *libpod.Runtime, newImage *image.Image) (*spec.Spec, error) {
func (s *SpecGenerator) ToOCISpec(rt *libpod.Runtime, newImage *image.Image) (*spec.Spec, error) {
var (
inUserNS bool
)

165
pkg/specgen/security.go Normal file
View File

@ -0,0 +1,165 @@
package specgen
// ToCreateOptions convert the SecurityConfig to a slice of container create
// options.
/*
func (c *SecurityConfig) ToCreateOptions() ([]libpod.CtrCreateOption, error) {
options := make([]libpod.CtrCreateOption, 0)
options = append(options, libpod.WithSecLabels(c.LabelOpts))
options = append(options, libpod.WithPrivileged(c.Privileged))
return options, nil
}
*/
// SetLabelOpts sets the label options of the SecurityConfig according to the
// input.
/*
func (c *SecurityConfig) SetLabelOpts(runtime *libpod.Runtime, pidConfig *PidConfig, ipcConfig *IpcConfig) error {
if c.Privileged {
c.LabelOpts = label.DisableSecOpt()
return nil
}
var labelOpts []string
if pidConfig.PidMode.IsHost() {
labelOpts = append(labelOpts, label.DisableSecOpt()...)
} else if pidConfig.PidMode.IsContainer() {
ctr, err := runtime.LookupContainer(pidConfig.PidMode.Container())
if err != nil {
return errors.Wrapf(err, "container %q not found", pidConfig.PidMode.Container())
}
secopts, err := label.DupSecOpt(ctr.ProcessLabel())
if err != nil {
return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel())
}
labelOpts = append(labelOpts, secopts...)
}
if ipcConfig.IpcMode.IsHost() {
labelOpts = append(labelOpts, label.DisableSecOpt()...)
} else if ipcConfig.IpcMode.IsContainer() {
ctr, err := runtime.LookupContainer(ipcConfig.IpcMode.Container())
if err != nil {
return errors.Wrapf(err, "container %q not found", ipcConfig.IpcMode.Container())
}
secopts, err := label.DupSecOpt(ctr.ProcessLabel())
if err != nil {
return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel())
}
labelOpts = append(labelOpts, secopts...)
}
c.LabelOpts = append(c.LabelOpts, labelOpts...)
return nil
}
*/
// SetSecurityOpts the the security options (labels, apparmor, seccomp, etc.).
func SetSecurityOpts(securityOpts []string) error {
return nil
}
// ConfigureGenerator configures the generator according to the input.
/*
func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserConfig) error {
// HANDLE CAPABILITIES
// NOTE: Must happen before SECCOMP
if c.Privileged {
g.SetupPrivileged(true)
}
useNotRoot := func(user string) bool {
if user == "" || user == "root" || user == "0" {
return false
}
return true
}
configSpec := g.Config
var err error
var defaultCaplist []string
bounding := configSpec.Process.Capabilities.Bounding
if useNotRoot(user.User) {
configSpec.Process.Capabilities.Bounding = defaultCaplist
}
defaultCaplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, c.CapAdd, c.CapDrop)
if err != nil {
return err
}
privCapRequired := []string{}
if !c.Privileged && len(c.CapRequired) > 0 {
// Pass CapRequired in CapAdd field to normalize capabilities names
capRequired, err := capabilities.MergeCapabilities(nil, c.CapRequired, nil)
if err != nil {
logrus.Errorf("capabilities requested by user or image are not valid: %q", strings.Join(c.CapRequired, ","))
} else {
// Verify all capRequiered are in the defaultCapList
for _, cap := range capRequired {
if !util.StringInSlice(cap, defaultCaplist) {
privCapRequired = append(privCapRequired, cap)
}
}
}
if len(privCapRequired) == 0 {
defaultCaplist = capRequired
} else {
logrus.Errorf("capabilities requested by user or image are not allowed by default: %q", strings.Join(privCapRequired, ","))
}
}
configSpec.Process.Capabilities.Bounding = defaultCaplist
configSpec.Process.Capabilities.Permitted = defaultCaplist
configSpec.Process.Capabilities.Inheritable = defaultCaplist
configSpec.Process.Capabilities.Effective = defaultCaplist
configSpec.Process.Capabilities.Ambient = defaultCaplist
if useNotRoot(user.User) {
defaultCaplist, err = capabilities.MergeCapabilities(bounding, c.CapAdd, c.CapDrop)
if err != nil {
return err
}
}
configSpec.Process.Capabilities.Bounding = defaultCaplist
// HANDLE SECCOMP
if c.SeccompProfilePath != "unconfined" {
seccompConfig, err := getSeccompConfig(c, configSpec)
if err != nil {
return err
}
configSpec.Linux.Seccomp = seccompConfig
}
// Clear default Seccomp profile from Generator for privileged containers
if c.SeccompProfilePath == "unconfined" || c.Privileged {
configSpec.Linux.Seccomp = nil
}
for _, opt := range c.SecurityOpts {
// Split on both : and =
splitOpt := strings.Split(opt, "=")
if len(splitOpt) == 1 {
splitOpt = strings.Split(opt, ":")
}
if len(splitOpt) < 2 {
continue
}
switch splitOpt[0] {
case "label":
configSpec.Annotations[libpod.InspectAnnotationLabel] = splitOpt[1]
case "seccomp":
configSpec.Annotations[libpod.InspectAnnotationSeccomp] = splitOpt[1]
case "apparmor":
configSpec.Annotations[libpod.InspectAnnotationApparmor] = splitOpt[1]
}
}
g.SetRootReadonly(c.ReadOnlyRootfs)
for sysctlKey, sysctlVal := range c.Sysctl {
g.AddLinuxSysctl(sysctlKey, sysctlVal)
}
return nil
}
*/

View File

@ -4,8 +4,9 @@ import (
"net"
"syscall"
"github.com/containers/image/v5/manifest"
"github.com/containers/libpod/libpod"
"github.com/containers/image/v5/manifest"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/storage"
"github.com/cri-o/ocicni/pkg/ocicni"
@ -371,6 +372,16 @@ type ContainerResourceConfig struct {
// processes to kill for the container's process.
// Optional.
OOMScoreAdj *int `json:"oom_score_adj,omitempty"`
// Weight per cgroup per device, can override BlkioWeight
WeightDevice map[string]spec.LinuxWeightDevice `json:"weightDevice,omitempty"`
// IO read rate limit per cgroup per device, bytes per second
ThrottleReadBpsDevice map[string]spec.LinuxThrottleDevice `json:"throttleReadBpsDevice,omitempty"`
// IO write rate limit per cgroup per device, bytes per second
ThrottleWriteBpsDevice map[string]spec.LinuxThrottleDevice `json:"throttleWriteBpsDevice,omitempty"`
// IO read rate limit per cgroup per device, IO per second
ThrottleReadIOPSDevice map[string]spec.LinuxThrottleDevice `json:"throttleReadIOPSDevice,omitempty"`
// IO write rate limit per cgroup per device, IO per second
ThrottleWriteIOPSDevice map[string]spec.LinuxThrottleDevice `json:"throttleWriteIOPSDevice,omitempty"`
}
// ContainerHealthCheckConfig describes a container healthcheck with attributes

885
pkg/specgen/storage.go Normal file
View File

@ -0,0 +1,885 @@
package specgen
//nolint
import (
"fmt"
"path"
"path/filepath"
"strings"
"github.com/containers/libpod/libpod"
"github.com/containers/buildah/pkg/parse"
"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") //nolint
optionArgError = errors.Errorf("must provide an argument for option") //nolint
noDestError = errors.Errorf("must set volume destination") //nolint
)
// Parse all volume-related options in the create config into a set of mounts
// and named volumes to add to the container.
// Handles --volumes-from, --volumes, --tmpfs, --init, and --init-path flags.
// TODO: Named volume options - should we default to rprivate? It bakes into a
// bind mount under the hood...
// TODO: handle options parsing/processing via containers/storage/pkg/mount
func (s *SpecGenerator) parseVolumes(mounts, volMounts, tmpMounts []string) error { //nolint
// TODO this needs to come from the image and erquires a runtime
// Add image volumes.
//baseMounts, baseVolumes, err := config.getImageVolumes()
//if err != nil {
// return nil, nil, err
//}
// Add --volumes-from.
// Overrides image volumes unconditionally.
//vFromMounts, vFromVolumes, err := config.getVolumesFrom(runtime)
//if err != nil {
// return nil, nil, err
//}
//for dest, mount := range vFromMounts {
// baseMounts[dest] = mount
//}
//for dest, volume := range vFromVolumes {
// baseVolumes[dest] = volume
//}
// Next mounts from the --mounts flag.
// Do not override yet.
//unifiedMounts, _, err := getMounts(mounts)
//if err != nil {
// return err
//}
//
//// Next --volumes flag.
//// Do not override yet.
//volumeMounts, _ , err := getVolumeMounts(volMounts)
//if err != nil {
// return err
//}
//
//// Next --tmpfs flag.
//// Do not override yet.
//tmpfsMounts, err := getTmpfsMounts(tmpMounts)
//if err != nil {
// return err
//}
//// Unify mounts from --mount, --volume, --tmpfs.
//// Also add mounts + volumes directly from createconfig.
//// 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
//}
//// Now spec mounts and volumes
//for _, mount := range config.Mounts {
// dest := mount.Destination
// if _, ok := unifiedMounts[dest]; ok {
// return nil, nil, errors.Wrapf(errDuplicateDest, dest)
// }
// unifiedMounts[dest] = mount
//}
//for _, volume := range config.NamedVolumes {
// dest := volume.Dest
// if _, ok := unifiedVolumes[dest]; ok {
// return nil, nil, errors.Wrapf(errDuplicateDest, dest)
// }
// unifiedVolumes[dest] = volume
//}
//
//// If requested, add container init binary
//if config.Init {
// initPath := config.InitPath
// if initPath == "" {
// rtc, err := runtime.GetConfig()
// if err != nil {
// return nil, nil, err
// }
// initPath = rtc.Engine.InitPath
// }
// initMount, err := config.addContainerInitBinary(initPath)
// if err != nil {
// return nil, nil, err
// }
// if _, ok := unifiedMounts[initMount.Destination]; ok {
// return nil, nil, errors.Wrapf(errDuplicateDest, "conflict with mount added by --init to %q", initMount.Destination)
// }
// unifiedMounts[initMount.Destination] = initMount
//}
//
//// Before superseding, we need to find volume mounts which conflict with
//// named volumes, and vice versa.
//// We'll delete the conflicts here as we supersede.
//for dest := range unifiedMounts {
// if _, ok := baseVolumes[dest]; ok {
// delete(baseVolumes, dest)
// }
//}
//for dest := range unifiedVolumes {
// if _, ok := baseMounts[dest]; ok {
// delete(baseMounts, dest)
// }
//}
//
//// Supersede volumes-from/image volumes with unified volumes from above.
//// This is an unconditional replacement.
//for dest, mount := range unifiedMounts {
// baseMounts[dest] = mount
//}
//for dest, volume := range unifiedVolumes {
// baseVolumes[dest] = volume
//}
//
//// If requested, add tmpfs filesystems for read-only containers.
//if config.Security.ReadOnlyRootfs && config.Security.ReadOnlyTmpfs {
// readonlyTmpfs := []string{"/tmp", "/var/tmp", "/run"}
// options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"}
// for _, dest := range readonlyTmpfs {
// if _, ok := baseMounts[dest]; ok {
// continue
// }
// if _, ok := baseVolumes[dest]; ok {
// continue
// }
// localOpts := options
// if dest == "/run" {
// localOpts = append(localOpts, "noexec", "size=65536k")
// } else {
// localOpts = append(localOpts, "exec")
// }
// baseMounts[dest] = spec.Mount{
// Destination: dest,
// Type: "tmpfs",
// Source: "tmpfs",
// Options: localOpts,
// }
// }
//}
//
//// Check for conflicts between named volumes and mounts
//for dest := range baseMounts {
// if _, ok := baseVolumes[dest]; ok {
// return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
// }
//}
//for dest := range baseVolumes {
// if _, ok := baseMounts[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(baseMounts))
//for _, mount := range baseMounts {
// 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([]*define.ContainerNamedVolume, 0, len(baseVolumes))
//for _, volume := range baseVolumes {
// finalVolumes = append(finalVolumes, volume)
//}
//return finalMounts, finalVolumes, nil
return nil
}
// Parse volumes from - a set of containers whose volumes we will mount in.
// Grab the containers, retrieve any user-created spec mounts and all named
// volumes, and return a list of them.
// Conflicts are resolved simply - the last container specified wins.
// Container names may be suffixed by mount options after a colon.
// TODO: We should clean these paths if possible
// TODO deferred baude
func getVolumesFrom() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint
// Both of these are maps of mount destination to mount type.
// We ensure that each destination is only mounted to once in this way.
//finalMounts := make(map[string]spec.Mount)
//finalNamedVolumes := make(map[string]*define.ContainerNamedVolume)
//
//for _, vol := range config.VolumesFrom {
// var (
// options = []string{}
// err error
// splitVol = strings.SplitN(vol, ":", 2)
// )
// if len(splitVol) == 2 {
// splitOpts := strings.Split(splitVol[1], ",")
// for _, checkOpt := range splitOpts {
// switch checkOpt {
// case "z", "ro", "rw":
// // Do nothing, these are valid options
// default:
// return nil, nil, errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z'", splitVol[1])
// }
// }
//
// if options, err = parse.ValidateVolumeOpts(splitOpts); err != nil {
// return nil, nil, err
// }
// }
// ctr, err := runtime.LookupContainer(splitVol[0])
// if err != nil {
// return nil, nil, errors.Wrapf(err, "error looking up container %q for volumes-from", splitVol[0])
// }
//
// logrus.Debugf("Adding volumes from container %s", ctr.ID())
//
// // Look up the container's user volumes. This gets us the
// // destinations of all mounts the user added to the container.
// userVolumesArr := ctr.UserVolumes()
//
// // We're going to need to access them a lot, so convert to a map
// // to reduce looping.
// // We'll also use the map to indicate if we missed any volumes along the way.
// userVolumes := make(map[string]bool)
// for _, dest := range userVolumesArr {
// userVolumes[dest] = false
// }
//
// // Now we get the container's spec and loop through its volumes
// // and append them in if we can find them.
// spec := ctr.Spec()
// if spec == nil {
// return nil, nil, errors.Errorf("error retrieving container %s spec for volumes-from", ctr.ID())
// }
// for _, mnt := range spec.Mounts {
// if mnt.Type != TypeBind {
// continue
// }
// if _, exists := userVolumes[mnt.Destination]; exists {
// userVolumes[mnt.Destination] = true
//
// if len(options) != 0 {
// mnt.Options = options
// }
//
// if _, ok := finalMounts[mnt.Destination]; ok {
// logrus.Debugf("Overriding mount to %s with new mount from container %s", mnt.Destination, ctr.ID())
// }
// finalMounts[mnt.Destination] = mnt
// }
// }
//
// // We're done with the spec mounts. Add named volumes.
// // Add these unconditionally - none of them are automatically
// // part of the container, as some spec mounts are.
// namedVolumes := ctr.NamedVolumes()
// for _, namedVol := range namedVolumes {
// if _, exists := userVolumes[namedVol.Dest]; exists {
// userVolumes[namedVol.Dest] = true
// }
//
// if len(options) != 0 {
// namedVol.Options = options
// }
//
// if _, ok := finalMounts[namedVol.Dest]; ok {
// logrus.Debugf("Overriding named volume mount to %s with new named volume from container %s", namedVol.Dest, ctr.ID())
// }
// finalNamedVolumes[namedVol.Dest] = namedVol
// }
//
// // Check if we missed any volumes
// for volDest, found := range userVolumes {
// if !found {
// logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID())
// }
// }
//}
//
//return finalMounts, finalNamedVolumes, nil
return nil, nil, 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(mounts []string) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint
finalMounts := make(map[string]spec.Mount)
finalNamedVolumes := make(map[string]*libpod.ContainerNamedVolume)
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 mounts {
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) { //nolint
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) { //nolint
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) (*libpod.ContainerNamedVolume, error) { //nolint
newVolume := new(libpod.ContainerNamedVolume)
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(vols []string) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint
mounts := make(map[string]spec.Mount)
volumes := make(map[string]*libpod.ContainerNamedVolume)
volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]")
for _, vol := range vols {
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(libpod.ContainerNamedVolume)
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
}
// Get mounts for container's image volumes
// TODO deferred baude
func getImageVolumes() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint
//mounts := make(map[string]spec.Mount)
//volumes := make(map[string]*define.ContainerNamedVolume)
//
//if config.ImageVolumeType == "ignore" {
// return mounts, volumes, nil
//}
//
//for vol := range config.BuiltinImgVolumes {
// cleanDest := filepath.Clean(vol)
// logrus.Debugf("Adding image volume at %s", cleanDest)
// if config.ImageVolumeType == "tmpfs" {
// // Tmpfs image volumes are handled as mounts
// mount := spec.Mount{
// Destination: cleanDest,
// Source: TypeTmpfs,
// Type: TypeTmpfs,
// Options: []string{"rprivate", "rw", "nodev", "exec"},
// }
// mounts[cleanDest] = mount
// } else {
// // Anonymous volumes have no name.
// namedVolume := new(define.ContainerNamedVolume)
// namedVolume.Options = []string{"rprivate", "rw", "nodev", "exec"}
// namedVolume.Dest = cleanDest
// volumes[cleanDest] = namedVolume
// }
//}
//
//return mounts, volumes, nil
return nil, nil, nil
}
// GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts
func getTmpfsMounts(mounts []string) (map[string]spec.Mount, error) { //nolint
m := make(map[string]spec.Mount)
for _, i := range mounts {
// 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
}
// AddContainerInitBinary adds the init binary specified by path iff the
// container will run in a private PID namespace that is not shared with the
// host or another pre-existing container, where an init-like process is
// already running.
//
// Note that AddContainerInitBinary prepends "/dev/init" "--" to the command
// to execute the bind-mounted binary as PID 1.
// TODO this needs to be worked on to work in new env
func addContainerInitBinary(path string) (spec.Mount, error) { //nolint
mount := spec.Mount{
Destination: "/dev/init",
Type: TypeBind,
Source: path,
Options: []string{TypeBind, "ro"},
}
//if path == "" {
// return mount, fmt.Errorf("please specify a path to the container-init binary")
//}
//if !config.Pid.PidMode.IsPrivate() {
// return mount, fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)")
//}
//if config.Systemd {
// return mount, fmt.Errorf("cannot use container-init binary with systemd")
//}
//if _, err := os.Stat(path); os.IsNotExist(err) {
// return mount, errors.Wrap(err, "container-init binary not found on the host")
//}
//config.Command = append([]string{"/dev/init", "--"}, config.Command...)
return mount, nil
}
// Supersede existing mounts in the spec with new, user-specified mounts.
// TODO: Should we unmount subtree mounts? E.g., if /tmp/ is mounted by
// one mount, and we already have /tmp/a and /tmp/b, should we remove
// the /tmp/a and /tmp/b mounts in favor of the more general /tmp?
func SupercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount {
if len(mounts) > 0 {
// If we have overlappings mounts, remove them from the spec in favor of
// the user-added volume mounts
destinations := make(map[string]bool)
for _, mount := range mounts {
destinations[path.Clean(mount.Destination)] = true
}
// Copy all mounts from spec to defaultMounts, except for
// - mounts overridden by a user supplied mount;
// - all mounts under /dev if a user supplied /dev is present;
mountDev := destinations["/dev"]
for _, mount := range configMount {
if _, ok := destinations[path.Clean(mount.Destination)]; !ok {
if mountDev && strings.HasPrefix(mount.Destination, "/dev/") {
// filter out everything under /dev if /dev is user-mounted
continue
}
logrus.Debugf("Adding mount %s", mount.Destination)
mounts = append(mounts, mount)
}
}
return mounts
}
return configMount
}
func InitFSMounts(mounts []spec.Mount) error {
for i, m := range mounts {
switch {
case m.Type == TypeBind:
opts, err := util.ProcessOptions(m.Options, false, m.Source)
if err != nil {
return err
}
mounts[i].Options = opts
case m.Type == TypeTmpfs && filepath.Clean(m.Destination) != "/dev":
opts, err := util.ProcessOptions(m.Options, true, "")
if err != nil {
return err
}
mounts[i].Options = opts
}
}
return nil
}