mirror of
https://github.com/containers/podman.git
synced 2025-05-20 00:27:03 +08:00
@ -1,2 +1,2 @@
|
|||||||
all:
|
all:
|
||||||
GO111MODULE=off go build -tags 'ABISupport systemd'
|
CGO_ENABLED=1 GO111MODULE=off go build -tags 'ABISupport systemd seccomp'
|
||||||
|
534
cmd/podmanV2/common/create.go
Normal file
534
cmd/podmanV2/common/create.go
Normal 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)
|
||||||
|
}
|
103
cmd/podmanV2/common/create_opts.go
Normal file
103
cmd/podmanV2/common/create_opts.go
Normal 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
|
||||||
|
}
|
51
cmd/podmanV2/common/createparse.go
Normal file
51
cmd/podmanV2/common/createparse.go
Normal 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
|
||||||
|
|
||||||
|
}
|
121
cmd/podmanV2/common/default.go
Normal file
121
cmd/podmanV2/common/default.go
Normal 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
|
||||||
|
}
|
126
cmd/podmanV2/common/ports.go
Normal file
126
cmd/podmanV2/common/ports.go
Normal 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}
|
||||||
|
}
|
647
cmd/podmanV2/common/specgen.go
Normal file
647
cmd/podmanV2/common/specgen.go
Normal 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
|
||||||
|
}
|
102
cmd/podmanV2/containers/create.go
Normal file
102
cmd/podmanV2/containers/create.go
Normal 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
|
||||||
|
}
|
50
cmd/podmanV2/parse/common.go
Normal file
50
cmd/podmanV2/parse/common.go
Normal 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
|
||||||
|
}
|
@ -13,7 +13,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -187,47 +186,3 @@ func ValidURL(urlStr string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
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
|
|
||||||
}
|
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/containers/libpod/libpod"
|
"github.com/containers/libpod/libpod"
|
||||||
"github.com/containers/libpod/pkg/api/handlers/utils"
|
"github.com/containers/libpod/pkg/api/handlers/utils"
|
||||||
"github.com/containers/libpod/pkg/specgen"
|
"github.com/containers/libpod/pkg/specgen"
|
||||||
|
"github.com/containers/libpod/pkg/specgen/generate"
|
||||||
"github.com/pkg/errors"
|
"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()"))
|
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
utils.InternalServerError(w, err)
|
utils.InternalServerError(w, err)
|
||||||
return
|
return
|
||||||
|
@ -153,3 +153,7 @@ type RestoreReport struct {
|
|||||||
Err error
|
Err error
|
||||||
Id string
|
Id string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ContainerCreateReport struct {
|
||||||
|
Id string
|
||||||
|
}
|
||||||
|
@ -4,12 +4,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/containers/libpod/libpod/define"
|
"github.com/containers/libpod/libpod/define"
|
||||||
|
"github.com/containers/libpod/pkg/specgen"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContainerEngine interface {
|
type ContainerEngine interface {
|
||||||
ContainerCommit(ctx context.Context, nameOrId string, options CommitOptions) (*CommitReport, error)
|
ContainerCommit(ctx context.Context, nameOrId string, options CommitOptions) (*CommitReport, error)
|
||||||
ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error)
|
ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error)
|
||||||
ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, 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)
|
ContainerExists(ctx context.Context, nameOrId string) (*BoolReport, error)
|
||||||
ContainerInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]*ContainerInspectReport, error)
|
ContainerInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]*ContainerInspectReport, error)
|
||||||
ContainerExport(ctx context.Context, nameOrId string, options ContainerExportOptions) error
|
ContainerExport(ctx context.Context, nameOrId string, options ContainerExportOptions) error
|
||||||
|
@ -16,6 +16,8 @@ import (
|
|||||||
"github.com/containers/libpod/pkg/checkpoint"
|
"github.com/containers/libpod/pkg/checkpoint"
|
||||||
"github.com/containers/libpod/pkg/domain/entities"
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
"github.com/containers/libpod/pkg/signal"
|
"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/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@ -442,3 +444,14 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
|
|||||||
}
|
}
|
||||||
return reports, nil
|
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
|
||||||
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/containers/libpod/pkg/api/handlers/libpod"
|
"github.com/containers/libpod/pkg/api/handlers/libpod"
|
||||||
"github.com/containers/libpod/pkg/bindings/containers"
|
"github.com/containers/libpod/pkg/bindings/containers"
|
||||||
"github.com/containers/libpod/pkg/domain/entities"
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
|
"github.com/containers/libpod/pkg/specgen"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -296,3 +297,11 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
|
|||||||
}
|
}
|
||||||
return reports, nil
|
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
|
||||||
|
}
|
||||||
|
93
pkg/specgen/config_linux.go
Normal file
93
pkg/specgen/config_linux.go
Normal 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
|
||||||
|
//}
|
@ -17,7 +17,6 @@ import (
|
|||||||
func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) {
|
func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) {
|
||||||
var seccompConfig *spec.LinuxSeccomp
|
var seccompConfig *spec.LinuxSeccomp
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
scp, err := seccomp.LookupPolicy(s.SeccompPolicy)
|
scp, err := seccomp.LookupPolicy(s.SeccompPolicy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -14,7 +14,7 @@ var (
|
|||||||
// SystemDValues describes the only values that SystemD can be
|
// SystemDValues describes the only values that SystemD can be
|
||||||
SystemDValues = []string{"true", "false", "always"}
|
SystemDValues = []string{"true", "false", "always"}
|
||||||
// ImageVolumeModeValues describes the only values that ImageVolumeMode can be
|
// 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 {
|
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
|
// Validate verifies that the given SpecGenerator is valid and satisfies required
|
||||||
// input for creating a container.
|
// input for creating a container.
|
||||||
func (s *SpecGenerator) validate() error {
|
func (s *SpecGenerator) Validate() error {
|
||||||
|
|
||||||
//
|
//
|
||||||
// ContainerBasicConfig
|
// ContainerBasicConfig
|
||||||
|
168
pkg/specgen/generate/container.go
Normal file
168
pkg/specgen/generate/container.go
Normal 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
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package specgen
|
package generate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -7,14 +7,15 @@ import (
|
|||||||
"github.com/containers/common/pkg/config"
|
"github.com/containers/common/pkg/config"
|
||||||
"github.com/containers/libpod/libpod"
|
"github.com/containers/libpod/libpod"
|
||||||
"github.com/containers/libpod/libpod/define"
|
"github.com/containers/libpod/libpod/define"
|
||||||
|
"github.com/containers/libpod/pkg/specgen"
|
||||||
"github.com/containers/storage"
|
"github.com/containers/storage"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MakeContainer creates a container based on the SpecGenerator
|
// MakeContainer creates a container based on the SpecGenerator
|
||||||
func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, error) {
|
func MakeContainer(rt *libpod.Runtime, s *specgen.SpecGenerator) (*libpod.Container, error) {
|
||||||
if err := s.validate(); err != nil {
|
if err := s.Validate(); err != nil {
|
||||||
return nil, errors.Wrap(err, "invalid config provided")
|
return nil, errors.Wrap(err, "invalid config provided")
|
||||||
}
|
}
|
||||||
rtc, err := rt.GetConfig()
|
rtc, err := rt.GetConfig()
|
||||||
@ -22,7 +23,7 @@ func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, er
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
options, err := s.createContainerOptions(rt)
|
options, err := createContainerOptions(rt, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -31,7 +32,7 @@ func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
newImage, err := rt.ImageRuntime().NewFromLocal(s.Image)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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))
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return rt.NewContainer(context.Background(), runtimeSpec, options...)
|
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 options []libpod.CtrCreateOption
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@ -114,7 +115,7 @@ func (s *SpecGenerator) createContainerOptions(rt *libpod.Runtime) ([]libpod.Ctr
|
|||||||
options = append(options, libpod.WithPrivileged(s.Privileged))
|
options = append(options, libpod.WithPrivileged(s.Privileged))
|
||||||
|
|
||||||
// Get namespace related options
|
// Get namespace related options
|
||||||
namespaceOptions, err := s.generateNamespaceContainerOpts(rt)
|
namespaceOptions, err := s.GenerateNamespaceContainerOpts(rt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -149,7 +150,7 @@ func (s *SpecGenerator) createContainerOptions(rt *libpod.Runtime) ([]libpod.Ctr
|
|||||||
return options, nil
|
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.
|
// 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
|
// But we can't assume that the caller is Podman - it could be another
|
||||||
// user of the API.
|
// user of the API.
|
@ -16,6 +16,9 @@ import (
|
|||||||
type NamespaceMode string
|
type NamespaceMode string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// Default indicates the spec generator should determine
|
||||||
|
// a sane default
|
||||||
|
Default NamespaceMode = "default"
|
||||||
// Host means the the namespace is derived from
|
// Host means the the namespace is derived from
|
||||||
// the host
|
// the host
|
||||||
Host NamespaceMode = "host"
|
Host NamespaceMode = "host"
|
||||||
@ -83,7 +86,7 @@ func validateNetNS(n *Namespace) error {
|
|||||||
return nil
|
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
|
// invalid from the get-go
|
||||||
func (n *Namespace) validate() error {
|
func (n *Namespace) validate() error {
|
||||||
if n == nil {
|
if n == nil {
|
||||||
@ -103,7 +106,7 @@ func (n *Namespace) validate() error {
|
|||||||
return nil
|
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
|
var portBindings []ocicni.PortMapping
|
||||||
options := make([]libpod.CtrCreateOption, 0)
|
options := make([]libpod.CtrCreateOption, 0)
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/opencontainers/runtime-tools/generate"
|
"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 (
|
var (
|
||||||
inUserNS bool
|
inUserNS bool
|
||||||
)
|
)
|
||||||
|
165
pkg/specgen/security.go
Normal file
165
pkg/specgen/security.go
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
@ -4,8 +4,9 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/containers/image/v5/manifest"
|
|
||||||
"github.com/containers/libpod/libpod"
|
"github.com/containers/libpod/libpod"
|
||||||
|
|
||||||
|
"github.com/containers/image/v5/manifest"
|
||||||
"github.com/containers/libpod/pkg/rootless"
|
"github.com/containers/libpod/pkg/rootless"
|
||||||
"github.com/containers/storage"
|
"github.com/containers/storage"
|
||||||
"github.com/cri-o/ocicni/pkg/ocicni"
|
"github.com/cri-o/ocicni/pkg/ocicni"
|
||||||
@ -371,6 +372,16 @@ type ContainerResourceConfig struct {
|
|||||||
// processes to kill for the container's process.
|
// processes to kill for the container's process.
|
||||||
// Optional.
|
// Optional.
|
||||||
OOMScoreAdj *int `json:"oom_score_adj,omitempty"`
|
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
|
// ContainerHealthCheckConfig describes a container healthcheck with attributes
|
||||||
|
885
pkg/specgen/storage.go
Normal file
885
pkg/specgen/storage.go
Normal 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
|
||||||
|
}
|
Reference in New Issue
Block a user