github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/cli/command/container/opts.go (about) 1 package container 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "path" 9 "reflect" 10 "regexp" 11 "strconv" 12 "strings" 13 "time" 14 15 "github.com/docker/cli/cli/compose/loader" 16 "github.com/docker/cli/opts" 17 "github.com/docker/docker/api/types/container" 18 networktypes "github.com/docker/docker/api/types/network" 19 "github.com/docker/docker/api/types/strslice" 20 "github.com/docker/docker/api/types/versions" 21 "github.com/docker/docker/errdefs" 22 "github.com/docker/docker/pkg/signal" 23 "github.com/docker/go-connections/nat" 24 "github.com/pkg/errors" 25 "github.com/sirupsen/logrus" 26 "github.com/spf13/pflag" 27 ) 28 29 var ( 30 deviceCgroupRuleRegexp = regexp.MustCompile(`^[acb] ([0-9]+|\*):([0-9]+|\*) [rwm]{1,3}$`) 31 ) 32 33 // containerOptions is a data object with all the options for creating a container 34 type containerOptions struct { 35 attach opts.ListOpts 36 volumes opts.ListOpts 37 tmpfs opts.ListOpts 38 mounts opts.MountOpt 39 blkioWeightDevice opts.WeightdeviceOpt 40 deviceReadBps opts.ThrottledeviceOpt 41 deviceWriteBps opts.ThrottledeviceOpt 42 links opts.ListOpts 43 aliases opts.ListOpts 44 linkLocalIPs opts.ListOpts 45 deviceReadIOps opts.ThrottledeviceOpt 46 deviceWriteIOps opts.ThrottledeviceOpt 47 env opts.ListOpts 48 labels opts.ListOpts 49 deviceCgroupRules opts.ListOpts 50 devices opts.ListOpts 51 gpus opts.GpuOpts 52 ulimits *opts.UlimitOpt 53 sysctls *opts.MapOpts 54 publish opts.ListOpts 55 expose opts.ListOpts 56 dns opts.ListOpts 57 dnsSearch opts.ListOpts 58 dnsOptions opts.ListOpts 59 extraHosts opts.ListOpts 60 volumesFrom opts.ListOpts 61 envFile opts.ListOpts 62 capAdd opts.ListOpts 63 capDrop opts.ListOpts 64 groupAdd opts.ListOpts 65 securityOpt opts.ListOpts 66 storageOpt opts.ListOpts 67 labelsFile opts.ListOpts 68 loggingOpts opts.ListOpts 69 privileged bool 70 pidMode string 71 utsMode string 72 usernsMode string 73 cgroupnsMode string 74 publishAll bool 75 stdin bool 76 tty bool 77 oomKillDisable bool 78 oomScoreAdj int 79 containerIDFile string 80 entrypoint string 81 hostname string 82 domainname string 83 memory opts.MemBytes 84 memoryReservation opts.MemBytes 85 memorySwap opts.MemSwapBytes 86 kernelMemory opts.MemBytes 87 user string 88 workingDir string 89 cpuCount int64 90 cpuShares int64 91 cpuPercent int64 92 cpuPeriod int64 93 cpuRealtimePeriod int64 94 cpuRealtimeRuntime int64 95 cpuQuota int64 96 cpus opts.NanoCPUs 97 cpusetCpus string 98 cpusetMems string 99 blkioWeight uint16 100 ioMaxBandwidth opts.MemBytes 101 ioMaxIOps uint64 102 swappiness int64 103 netMode opts.NetworkOpt 104 macAddress string 105 ipv4Address string 106 ipv6Address string 107 ipcMode string 108 pidsLimit int64 109 restartPolicy string 110 readonlyRootfs bool 111 loggingDriver string 112 cgroupParent string 113 volumeDriver string 114 stopSignal string 115 stopTimeout int 116 isolation string 117 shmSize opts.MemBytes 118 noHealthcheck bool 119 healthCmd string 120 healthInterval time.Duration 121 healthTimeout time.Duration 122 healthStartPeriod time.Duration 123 healthRetries int 124 runtime string 125 autoRemove bool 126 init bool 127 128 Image string 129 Args []string 130 } 131 132 // addFlags adds all command line flags that will be used by parse to the FlagSet 133 func addFlags(flags *pflag.FlagSet) *containerOptions { 134 copts := &containerOptions{ 135 aliases: opts.NewListOpts(nil), 136 attach: opts.NewListOpts(validateAttach), 137 blkioWeightDevice: opts.NewWeightdeviceOpt(opts.ValidateWeightDevice), 138 capAdd: opts.NewListOpts(nil), 139 capDrop: opts.NewListOpts(nil), 140 dns: opts.NewListOpts(opts.ValidateIPAddress), 141 dnsOptions: opts.NewListOpts(nil), 142 dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch), 143 deviceCgroupRules: opts.NewListOpts(validateDeviceCgroupRule), 144 deviceReadBps: opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice), 145 deviceReadIOps: opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice), 146 deviceWriteBps: opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice), 147 deviceWriteIOps: opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice), 148 devices: opts.NewListOpts(nil), // devices can only be validated after we know the server OS 149 env: opts.NewListOpts(opts.ValidateEnv), 150 envFile: opts.NewListOpts(nil), 151 expose: opts.NewListOpts(nil), 152 extraHosts: opts.NewListOpts(opts.ValidateExtraHost), 153 groupAdd: opts.NewListOpts(nil), 154 labels: opts.NewListOpts(opts.ValidateLabel), 155 labelsFile: opts.NewListOpts(nil), 156 linkLocalIPs: opts.NewListOpts(nil), 157 links: opts.NewListOpts(opts.ValidateLink), 158 loggingOpts: opts.NewListOpts(nil), 159 publish: opts.NewListOpts(nil), 160 securityOpt: opts.NewListOpts(nil), 161 storageOpt: opts.NewListOpts(nil), 162 sysctls: opts.NewMapOpts(nil, opts.ValidateSysctl), 163 tmpfs: opts.NewListOpts(nil), 164 ulimits: opts.NewUlimitOpt(nil), 165 volumes: opts.NewListOpts(nil), 166 volumesFrom: opts.NewListOpts(nil), 167 } 168 169 // General purpose flags 170 flags.VarP(&copts.attach, "attach", "a", "Attach to STDIN, STDOUT or STDERR") 171 flags.Var(&copts.deviceCgroupRules, "device-cgroup-rule", "Add a rule to the cgroup allowed devices list") 172 flags.Var(&copts.devices, "device", "Add a host device to the container") 173 flags.Var(&copts.gpus, "gpus", "GPU devices to add to the container ('all' to pass all GPUs)") 174 flags.SetAnnotation("gpus", "version", []string{"1.40"}) 175 flags.VarP(&copts.env, "env", "e", "Set environment variables") 176 flags.Var(&copts.envFile, "env-file", "Read in a file of environment variables") 177 flags.StringVar(&copts.entrypoint, "entrypoint", "", "Overwrite the default ENTRYPOINT of the image") 178 flags.Var(&copts.groupAdd, "group-add", "Add additional groups to join") 179 flags.StringVarP(&copts.hostname, "hostname", "h", "", "Container host name") 180 flags.StringVar(&copts.domainname, "domainname", "", "Container NIS domain name") 181 flags.BoolVarP(&copts.stdin, "interactive", "i", false, "Keep STDIN open even if not attached") 182 flags.VarP(&copts.labels, "label", "l", "Set meta data on a container") 183 flags.Var(&copts.labelsFile, "label-file", "Read in a line delimited file of labels") 184 flags.BoolVar(&copts.readonlyRootfs, "read-only", false, "Mount the container's root filesystem as read only") 185 flags.StringVar(&copts.restartPolicy, "restart", "no", "Restart policy to apply when a container exits") 186 flags.StringVar(&copts.stopSignal, "stop-signal", signal.DefaultStopSignal, "Signal to stop a container") 187 flags.IntVar(&copts.stopTimeout, "stop-timeout", 0, "Timeout (in seconds) to stop a container") 188 flags.SetAnnotation("stop-timeout", "version", []string{"1.25"}) 189 flags.Var(copts.sysctls, "sysctl", "Sysctl options") 190 flags.BoolVarP(&copts.tty, "tty", "t", false, "Allocate a pseudo-TTY") 191 flags.Var(copts.ulimits, "ulimit", "Ulimit options") 192 flags.StringVarP(&copts.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])") 193 flags.StringVarP(&copts.workingDir, "workdir", "w", "", "Working directory inside the container") 194 flags.BoolVar(&copts.autoRemove, "rm", false, "Automatically remove the container when it exits") 195 196 // Security 197 flags.Var(&copts.capAdd, "cap-add", "Add Linux capabilities") 198 flags.Var(&copts.capDrop, "cap-drop", "Drop Linux capabilities") 199 flags.BoolVar(&copts.privileged, "privileged", false, "Give extended privileges to this container") 200 flags.Var(&copts.securityOpt, "security-opt", "Security Options") 201 flags.StringVar(&copts.usernsMode, "userns", "", "User namespace to use") 202 flags.StringVar(&copts.cgroupnsMode, "cgroupns", "", `Cgroup namespace to use (host|private) 203 'host': Run the container in the Docker host's cgroup namespace 204 'private': Run the container in its own private cgroup namespace 205 '': Use the cgroup namespace as configured by the 206 default-cgroupns-mode option on the daemon (default)`) 207 flags.SetAnnotation("cgroupns", "version", []string{"1.41"}) 208 209 // Network and port publishing flag 210 flags.Var(&copts.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)") 211 flags.Var(&copts.dns, "dns", "Set custom DNS servers") 212 // We allow for both "--dns-opt" and "--dns-option", although the latter is the recommended way. 213 // This is to be consistent with service create/update 214 flags.Var(&copts.dnsOptions, "dns-opt", "Set DNS options") 215 flags.Var(&copts.dnsOptions, "dns-option", "Set DNS options") 216 flags.MarkHidden("dns-opt") 217 flags.Var(&copts.dnsSearch, "dns-search", "Set custom DNS search domains") 218 flags.Var(&copts.expose, "expose", "Expose a port or a range of ports") 219 flags.StringVar(&copts.ipv4Address, "ip", "", "IPv4 address (e.g., 172.30.100.104)") 220 flags.StringVar(&copts.ipv6Address, "ip6", "", "IPv6 address (e.g., 2001:db8::33)") 221 flags.Var(&copts.links, "link", "Add link to another container") 222 flags.Var(&copts.linkLocalIPs, "link-local-ip", "Container IPv4/IPv6 link-local addresses") 223 flags.StringVar(&copts.macAddress, "mac-address", "", "Container MAC address (e.g., 92:d0:c6:0a:29:33)") 224 flags.VarP(&copts.publish, "publish", "p", "Publish a container's port(s) to the host") 225 flags.BoolVarP(&copts.publishAll, "publish-all", "P", false, "Publish all exposed ports to random ports") 226 // We allow for both "--net" and "--network", although the latter is the recommended way. 227 flags.Var(&copts.netMode, "net", "Connect a container to a network") 228 flags.Var(&copts.netMode, "network", "Connect a container to a network") 229 flags.MarkHidden("net") 230 // We allow for both "--net-alias" and "--network-alias", although the latter is the recommended way. 231 flags.Var(&copts.aliases, "net-alias", "Add network-scoped alias for the container") 232 flags.Var(&copts.aliases, "network-alias", "Add network-scoped alias for the container") 233 flags.MarkHidden("net-alias") 234 235 // Logging and storage 236 flags.StringVar(&copts.loggingDriver, "log-driver", "", "Logging driver for the container") 237 flags.StringVar(&copts.volumeDriver, "volume-driver", "", "Optional volume driver for the container") 238 flags.Var(&copts.loggingOpts, "log-opt", "Log driver options") 239 flags.Var(&copts.storageOpt, "storage-opt", "Storage driver options for the container") 240 flags.Var(&copts.tmpfs, "tmpfs", "Mount a tmpfs directory") 241 flags.Var(&copts.volumesFrom, "volumes-from", "Mount volumes from the specified container(s)") 242 flags.VarP(&copts.volumes, "volume", "v", "Bind mount a volume") 243 flags.Var(&copts.mounts, "mount", "Attach a filesystem mount to the container") 244 245 // Health-checking 246 flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health") 247 flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check (ms|s|m|h) (default 0s)") 248 flags.IntVar(&copts.healthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy") 249 flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ms|s|m|h) (default 0s)") 250 flags.DurationVar(&copts.healthStartPeriod, "health-start-period", 0, "Start period for the container to initialize before starting health-retries countdown (ms|s|m|h) (default 0s)") 251 flags.SetAnnotation("health-start-period", "version", []string{"1.29"}) 252 flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK") 253 254 // Resource management 255 flags.Uint16Var(&copts.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)") 256 flags.Var(&copts.blkioWeightDevice, "blkio-weight-device", "Block IO weight (relative device weight)") 257 flags.StringVar(&copts.containerIDFile, "cidfile", "", "Write the container ID to the file") 258 flags.StringVar(&copts.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)") 259 flags.StringVar(&copts.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)") 260 flags.Int64Var(&copts.cpuCount, "cpu-count", 0, "CPU count (Windows only)") 261 flags.SetAnnotation("cpu-count", "ostype", []string{"windows"}) 262 flags.Int64Var(&copts.cpuPercent, "cpu-percent", 0, "CPU percent (Windows only)") 263 flags.SetAnnotation("cpu-percent", "ostype", []string{"windows"}) 264 flags.Int64Var(&copts.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period") 265 flags.Int64Var(&copts.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota") 266 flags.Int64Var(&copts.cpuRealtimePeriod, "cpu-rt-period", 0, "Limit CPU real-time period in microseconds") 267 flags.SetAnnotation("cpu-rt-period", "version", []string{"1.25"}) 268 flags.Int64Var(&copts.cpuRealtimeRuntime, "cpu-rt-runtime", 0, "Limit CPU real-time runtime in microseconds") 269 flags.SetAnnotation("cpu-rt-runtime", "version", []string{"1.25"}) 270 flags.Int64VarP(&copts.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)") 271 flags.Var(&copts.cpus, "cpus", "Number of CPUs") 272 flags.SetAnnotation("cpus", "version", []string{"1.25"}) 273 flags.Var(&copts.deviceReadBps, "device-read-bps", "Limit read rate (bytes per second) from a device") 274 flags.Var(&copts.deviceReadIOps, "device-read-iops", "Limit read rate (IO per second) from a device") 275 flags.Var(&copts.deviceWriteBps, "device-write-bps", "Limit write rate (bytes per second) to a device") 276 flags.Var(&copts.deviceWriteIOps, "device-write-iops", "Limit write rate (IO per second) to a device") 277 flags.Var(&copts.ioMaxBandwidth, "io-maxbandwidth", "Maximum IO bandwidth limit for the system drive (Windows only)") 278 flags.SetAnnotation("io-maxbandwidth", "ostype", []string{"windows"}) 279 flags.Uint64Var(&copts.ioMaxIOps, "io-maxiops", 0, "Maximum IOps limit for the system drive (Windows only)") 280 flags.SetAnnotation("io-maxiops", "ostype", []string{"windows"}) 281 flags.Var(&copts.kernelMemory, "kernel-memory", "Kernel memory limit") 282 flags.VarP(&copts.memory, "memory", "m", "Memory limit") 283 flags.Var(&copts.memoryReservation, "memory-reservation", "Memory soft limit") 284 flags.Var(&copts.memorySwap, "memory-swap", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap") 285 flags.Int64Var(&copts.swappiness, "memory-swappiness", -1, "Tune container memory swappiness (0 to 100)") 286 flags.BoolVar(&copts.oomKillDisable, "oom-kill-disable", false, "Disable OOM Killer") 287 flags.IntVar(&copts.oomScoreAdj, "oom-score-adj", 0, "Tune host's OOM preferences (-1000 to 1000)") 288 flags.Int64Var(&copts.pidsLimit, "pids-limit", 0, "Tune container pids limit (set -1 for unlimited)") 289 290 // Low-level execution (cgroups, namespaces, ...) 291 flags.StringVar(&copts.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container") 292 flags.StringVar(&copts.ipcMode, "ipc", "", "IPC mode to use") 293 flags.StringVar(&copts.isolation, "isolation", "", "Container isolation technology") 294 flags.StringVar(&copts.pidMode, "pid", "", "PID namespace to use") 295 flags.Var(&copts.shmSize, "shm-size", "Size of /dev/shm") 296 flags.StringVar(&copts.utsMode, "uts", "", "UTS namespace to use") 297 flags.StringVar(&copts.runtime, "runtime", "", "Runtime to use for this container") 298 299 flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes") 300 flags.SetAnnotation("init", "version", []string{"1.25"}) 301 return copts 302 } 303 304 type containerConfig struct { 305 Config *container.Config 306 HostConfig *container.HostConfig 307 NetworkingConfig *networktypes.NetworkingConfig 308 } 309 310 // parse parses the args for the specified command and generates a Config, 311 // a HostConfig and returns them with the specified command. 312 // If the specified args are not valid, it will return an error. 313 // nolint: gocyclo 314 func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*containerConfig, error) { 315 var ( 316 attachStdin = copts.attach.Get("stdin") 317 attachStdout = copts.attach.Get("stdout") 318 attachStderr = copts.attach.Get("stderr") 319 ) 320 321 // Validate the input mac address 322 if copts.macAddress != "" { 323 if _, err := opts.ValidateMACAddress(copts.macAddress); err != nil { 324 return nil, errors.Errorf("%s is not a valid mac address", copts.macAddress) 325 } 326 } 327 if copts.stdin { 328 attachStdin = true 329 } 330 // If -a is not set, attach to stdout and stderr 331 if copts.attach.Len() == 0 { 332 attachStdout = true 333 attachStderr = true 334 } 335 336 var err error 337 338 swappiness := copts.swappiness 339 if swappiness != -1 && (swappiness < 0 || swappiness > 100) { 340 return nil, errors.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness) 341 } 342 343 mounts := copts.mounts.Value() 344 if len(mounts) > 0 && copts.volumeDriver != "" { 345 logrus.Warn("`--volume-driver` is ignored for volumes specified via `--mount`. Use `--mount type=volume,volume-driver=...` instead.") 346 } 347 var binds []string 348 volumes := copts.volumes.GetMap() 349 // add any bind targets to the list of container volumes 350 for bind := range copts.volumes.GetMap() { 351 parsed, _ := loader.ParseVolume(bind) 352 if parsed.Source != "" { 353 // after creating the bind mount we want to delete it from the copts.volumes values because 354 // we do not want bind mounts being committed to image configs 355 binds = append(binds, bind) 356 // We should delete from the map (`volumes`) here, as deleting from copts.volumes will not work if 357 // there are duplicates entries. 358 delete(volumes, bind) 359 } 360 } 361 362 // Can't evaluate options passed into --tmpfs until we actually mount 363 tmpfs := make(map[string]string) 364 for _, t := range copts.tmpfs.GetAll() { 365 if arr := strings.SplitN(t, ":", 2); len(arr) > 1 { 366 tmpfs[arr[0]] = arr[1] 367 } else { 368 tmpfs[arr[0]] = "" 369 } 370 } 371 372 var ( 373 runCmd strslice.StrSlice 374 entrypoint strslice.StrSlice 375 ) 376 377 if len(copts.Args) > 0 { 378 runCmd = strslice.StrSlice(copts.Args) 379 } 380 381 if copts.entrypoint != "" { 382 entrypoint = strslice.StrSlice{copts.entrypoint} 383 } else if flags.Changed("entrypoint") { 384 // if `--entrypoint=` is parsed then Entrypoint is reset 385 entrypoint = []string{""} 386 } 387 388 publishOpts := copts.publish.GetAll() 389 var ( 390 ports map[nat.Port]struct{} 391 portBindings map[nat.Port][]nat.PortBinding 392 convertedOpts []string 393 ) 394 395 convertedOpts, err = convertToStandardNotation(publishOpts) 396 if err != nil { 397 return nil, err 398 } 399 400 ports, portBindings, err = nat.ParsePortSpecs(convertedOpts) 401 if err != nil { 402 return nil, err 403 } 404 405 // Merge in exposed ports to the map of published ports 406 for _, e := range copts.expose.GetAll() { 407 if strings.Contains(e, ":") { 408 return nil, errors.Errorf("invalid port format for --expose: %s", e) 409 } 410 // support two formats for expose, original format <portnum>/[<proto>] 411 // or <startport-endport>/[<proto>] 412 proto, port := nat.SplitProtoPort(e) 413 // parse the start and end port and create a sequence of ports to expose 414 // if expose a port, the start and end port are the same 415 start, end, err := nat.ParsePortRange(port) 416 if err != nil { 417 return nil, errors.Errorf("invalid range format for --expose: %s, error: %s", e, err) 418 } 419 for i := start; i <= end; i++ { 420 p, err := nat.NewPort(proto, strconv.FormatUint(i, 10)) 421 if err != nil { 422 return nil, err 423 } 424 if _, exists := ports[p]; !exists { 425 ports[p] = struct{}{} 426 } 427 } 428 } 429 430 // validate and parse device mappings. Note we do late validation of the 431 // device path (as opposed to during flag parsing), as at the time we are 432 // parsing flags, we haven't yet sent a _ping to the daemon to determine 433 // what operating system it is. 434 deviceMappings := []container.DeviceMapping{} 435 for _, device := range copts.devices.GetAll() { 436 var ( 437 validated string 438 deviceMapping container.DeviceMapping 439 err error 440 ) 441 validated, err = validateDevice(device, serverOS) 442 if err != nil { 443 return nil, err 444 } 445 deviceMapping, err = parseDevice(validated, serverOS) 446 if err != nil { 447 return nil, err 448 } 449 deviceMappings = append(deviceMappings, deviceMapping) 450 } 451 452 // collect all the environment variables for the container 453 envVariables, err := opts.ReadKVEnvStrings(copts.envFile.GetAll(), copts.env.GetAll()) 454 if err != nil { 455 return nil, err 456 } 457 458 // collect all the labels for the container 459 labels, err := opts.ReadKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll()) 460 if err != nil { 461 return nil, err 462 } 463 464 pidMode := container.PidMode(copts.pidMode) 465 if !pidMode.Valid() { 466 return nil, errors.Errorf("--pid: invalid PID mode") 467 } 468 469 utsMode := container.UTSMode(copts.utsMode) 470 if !utsMode.Valid() { 471 return nil, errors.Errorf("--uts: invalid UTS mode") 472 } 473 474 usernsMode := container.UsernsMode(copts.usernsMode) 475 if !usernsMode.Valid() { 476 return nil, errors.Errorf("--userns: invalid USER mode") 477 } 478 479 cgroupnsMode := container.CgroupnsMode(copts.cgroupnsMode) 480 if !cgroupnsMode.Valid() { 481 return nil, errors.Errorf("--cgroupns: invalid CGROUP mode") 482 } 483 484 restartPolicy, err := opts.ParseRestartPolicy(copts.restartPolicy) 485 if err != nil { 486 return nil, err 487 } 488 489 loggingOpts, err := parseLoggingOpts(copts.loggingDriver, copts.loggingOpts.GetAll()) 490 if err != nil { 491 return nil, err 492 } 493 494 securityOpts, err := parseSecurityOpts(copts.securityOpt.GetAll()) 495 if err != nil { 496 return nil, err 497 } 498 499 securityOpts, maskedPaths, readonlyPaths := parseSystemPaths(securityOpts) 500 501 storageOpts, err := parseStorageOpts(copts.storageOpt.GetAll()) 502 if err != nil { 503 return nil, err 504 } 505 506 // Healthcheck 507 var healthConfig *container.HealthConfig 508 haveHealthSettings := copts.healthCmd != "" || 509 copts.healthInterval != 0 || 510 copts.healthTimeout != 0 || 511 copts.healthStartPeriod != 0 || 512 copts.healthRetries != 0 513 if copts.noHealthcheck { 514 if haveHealthSettings { 515 return nil, errors.Errorf("--no-healthcheck conflicts with --health-* options") 516 } 517 test := strslice.StrSlice{"NONE"} 518 healthConfig = &container.HealthConfig{Test: test} 519 } else if haveHealthSettings { 520 var probe strslice.StrSlice 521 if copts.healthCmd != "" { 522 args := []string{"CMD-SHELL", copts.healthCmd} 523 probe = strslice.StrSlice(args) 524 } 525 if copts.healthInterval < 0 { 526 return nil, errors.Errorf("--health-interval cannot be negative") 527 } 528 if copts.healthTimeout < 0 { 529 return nil, errors.Errorf("--health-timeout cannot be negative") 530 } 531 if copts.healthRetries < 0 { 532 return nil, errors.Errorf("--health-retries cannot be negative") 533 } 534 if copts.healthStartPeriod < 0 { 535 return nil, fmt.Errorf("--health-start-period cannot be negative") 536 } 537 538 healthConfig = &container.HealthConfig{ 539 Test: probe, 540 Interval: copts.healthInterval, 541 Timeout: copts.healthTimeout, 542 StartPeriod: copts.healthStartPeriod, 543 Retries: copts.healthRetries, 544 } 545 } 546 547 resources := container.Resources{ 548 CgroupParent: copts.cgroupParent, 549 Memory: copts.memory.Value(), 550 MemoryReservation: copts.memoryReservation.Value(), 551 MemorySwap: copts.memorySwap.Value(), 552 MemorySwappiness: &copts.swappiness, 553 KernelMemory: copts.kernelMemory.Value(), 554 OomKillDisable: &copts.oomKillDisable, 555 NanoCPUs: copts.cpus.Value(), 556 CPUCount: copts.cpuCount, 557 CPUPercent: copts.cpuPercent, 558 CPUShares: copts.cpuShares, 559 CPUPeriod: copts.cpuPeriod, 560 CpusetCpus: copts.cpusetCpus, 561 CpusetMems: copts.cpusetMems, 562 CPUQuota: copts.cpuQuota, 563 CPURealtimePeriod: copts.cpuRealtimePeriod, 564 CPURealtimeRuntime: copts.cpuRealtimeRuntime, 565 PidsLimit: &copts.pidsLimit, 566 BlkioWeight: copts.blkioWeight, 567 BlkioWeightDevice: copts.blkioWeightDevice.GetList(), 568 BlkioDeviceReadBps: copts.deviceReadBps.GetList(), 569 BlkioDeviceWriteBps: copts.deviceWriteBps.GetList(), 570 BlkioDeviceReadIOps: copts.deviceReadIOps.GetList(), 571 BlkioDeviceWriteIOps: copts.deviceWriteIOps.GetList(), 572 IOMaximumIOps: copts.ioMaxIOps, 573 IOMaximumBandwidth: uint64(copts.ioMaxBandwidth), 574 Ulimits: copts.ulimits.GetList(), 575 DeviceCgroupRules: copts.deviceCgroupRules.GetAll(), 576 Devices: deviceMappings, 577 DeviceRequests: copts.gpus.Value(), 578 } 579 580 config := &container.Config{ 581 Hostname: copts.hostname, 582 Domainname: copts.domainname, 583 ExposedPorts: ports, 584 User: copts.user, 585 Tty: copts.tty, 586 // TODO: deprecated, it comes from -n, --networking 587 // it's still needed internally to set the network to disabled 588 // if e.g. bridge is none in daemon opts, and in inspect 589 NetworkDisabled: false, 590 OpenStdin: copts.stdin, 591 AttachStdin: attachStdin, 592 AttachStdout: attachStdout, 593 AttachStderr: attachStderr, 594 Env: envVariables, 595 Cmd: runCmd, 596 Image: copts.Image, 597 Volumes: volumes, 598 MacAddress: copts.macAddress, 599 Entrypoint: entrypoint, 600 WorkingDir: copts.workingDir, 601 Labels: opts.ConvertKVStringsToMap(labels), 602 Healthcheck: healthConfig, 603 } 604 if flags.Changed("stop-signal") { 605 config.StopSignal = copts.stopSignal 606 } 607 if flags.Changed("stop-timeout") { 608 config.StopTimeout = &copts.stopTimeout 609 } 610 611 hostConfig := &container.HostConfig{ 612 Binds: binds, 613 ContainerIDFile: copts.containerIDFile, 614 OomScoreAdj: copts.oomScoreAdj, 615 AutoRemove: copts.autoRemove, 616 Privileged: copts.privileged, 617 PortBindings: portBindings, 618 Links: copts.links.GetAll(), 619 PublishAllPorts: copts.publishAll, 620 // Make sure the dns fields are never nil. 621 // New containers don't ever have those fields nil, 622 // but pre created containers can still have those nil values. 623 // See https://github.com/docker/docker/pull/17779 624 // for a more detailed explanation on why we don't want that. 625 DNS: copts.dns.GetAllOrEmpty(), 626 DNSSearch: copts.dnsSearch.GetAllOrEmpty(), 627 DNSOptions: copts.dnsOptions.GetAllOrEmpty(), 628 ExtraHosts: copts.extraHosts.GetAll(), 629 VolumesFrom: copts.volumesFrom.GetAll(), 630 IpcMode: container.IpcMode(copts.ipcMode), 631 NetworkMode: container.NetworkMode(copts.netMode.NetworkMode()), 632 PidMode: pidMode, 633 UTSMode: utsMode, 634 UsernsMode: usernsMode, 635 CgroupnsMode: cgroupnsMode, 636 CapAdd: strslice.StrSlice(copts.capAdd.GetAll()), 637 CapDrop: strslice.StrSlice(copts.capDrop.GetAll()), 638 GroupAdd: copts.groupAdd.GetAll(), 639 RestartPolicy: restartPolicy, 640 SecurityOpt: securityOpts, 641 StorageOpt: storageOpts, 642 ReadonlyRootfs: copts.readonlyRootfs, 643 LogConfig: container.LogConfig{Type: copts.loggingDriver, Config: loggingOpts}, 644 VolumeDriver: copts.volumeDriver, 645 Isolation: container.Isolation(copts.isolation), 646 ShmSize: copts.shmSize.Value(), 647 Resources: resources, 648 Tmpfs: tmpfs, 649 Sysctls: copts.sysctls.GetAll(), 650 Runtime: copts.runtime, 651 Mounts: mounts, 652 MaskedPaths: maskedPaths, 653 ReadonlyPaths: readonlyPaths, 654 } 655 656 if copts.autoRemove && !hostConfig.RestartPolicy.IsNone() { 657 return nil, errors.Errorf("Conflicting options: --restart and --rm") 658 } 659 660 // only set this value if the user provided the flag, else it should default to nil 661 if flags.Changed("init") { 662 hostConfig.Init = &copts.init 663 } 664 665 // When allocating stdin in attached mode, close stdin at client disconnect 666 if config.OpenStdin && config.AttachStdin { 667 config.StdinOnce = true 668 } 669 670 networkingConfig := &networktypes.NetworkingConfig{ 671 EndpointsConfig: make(map[string]*networktypes.EndpointSettings), 672 } 673 674 networkingConfig.EndpointsConfig, err = parseNetworkOpts(copts) 675 if err != nil { 676 return nil, err 677 } 678 679 return &containerConfig{ 680 Config: config, 681 HostConfig: hostConfig, 682 NetworkingConfig: networkingConfig, 683 }, nil 684 } 685 686 // parseNetworkOpts converts --network advanced options to endpoint-specs, and combines 687 // them with the old --network-alias and --links. If returns an error if conflicting options 688 // are found. 689 // 690 // this function may return _multiple_ endpoints, which is not currently supported 691 // by the daemon, but may be in future; it's up to the daemon to produce an error 692 // in case that is not supported. 693 func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.EndpointSettings, error) { 694 var ( 695 endpoints = make(map[string]*networktypes.EndpointSettings, len(copts.netMode.Value())) 696 hasUserDefined, hasNonUserDefined bool 697 ) 698 699 for i, n := range copts.netMode.Value() { 700 n := n 701 if container.NetworkMode(n.Target).IsUserDefined() { 702 hasUserDefined = true 703 } else { 704 hasNonUserDefined = true 705 } 706 if i == 0 { 707 // The first network corresponds with what was previously the "only" 708 // network, and what would be used when using the non-advanced syntax 709 // `--network-alias`, `--link`, `--ip`, `--ip6`, and `--link-local-ip` 710 // are set on this network, to preserve backward compatibility with 711 // the non-advanced notation 712 if err := applyContainerOptions(&n, copts); err != nil { 713 return nil, err 714 } 715 } 716 ep, err := parseNetworkAttachmentOpt(n) 717 if err != nil { 718 return nil, err 719 } 720 if _, ok := endpoints[n.Target]; ok { 721 return nil, errdefs.InvalidParameter(errors.Errorf("network %q is specified multiple times", n.Target)) 722 } 723 724 // For backward compatibility: if no custom options are provided for the network, 725 // and only a single network is specified, omit the endpoint-configuration 726 // on the client (the daemon will still create it when creating the container) 727 if i == 0 && len(copts.netMode.Value()) == 1 { 728 if ep == nil || reflect.DeepEqual(*ep, networktypes.EndpointSettings{}) { 729 continue 730 } 731 } 732 endpoints[n.Target] = ep 733 } 734 if hasUserDefined && hasNonUserDefined { 735 return nil, errdefs.InvalidParameter(errors.New("conflicting options: cannot attach both user-defined and non-user-defined network-modes")) 736 } 737 return endpoints, nil 738 } 739 740 func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOptions) error { 741 // TODO should copts.MacAddress actually be set on the first network? (currently it's not) 742 // TODO should we error if _any_ advanced option is used? (i.e. forbid to combine advanced notation with the "old" flags (`--network-alias`, `--link`, `--ip`, `--ip6`)? 743 if len(n.Aliases) > 0 && copts.aliases.Len() > 0 { 744 return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --network-alias and per-network alias")) 745 } 746 if len(n.Links) > 0 && copts.links.Len() > 0 { 747 return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --link and per-network links")) 748 } 749 if n.IPv4Address != "" && copts.ipv4Address != "" { 750 return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --ip and per-network IPv4 address")) 751 } 752 if n.IPv6Address != "" && copts.ipv6Address != "" { 753 return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --ip6 and per-network IPv6 address")) 754 } 755 if copts.aliases.Len() > 0 { 756 n.Aliases = make([]string, copts.aliases.Len()) 757 copy(n.Aliases, copts.aliases.GetAll()) 758 } 759 if copts.links.Len() > 0 { 760 n.Links = make([]string, copts.links.Len()) 761 copy(n.Links, copts.links.GetAll()) 762 } 763 if copts.ipv4Address != "" { 764 n.IPv4Address = copts.ipv4Address 765 } 766 if copts.ipv6Address != "" { 767 n.IPv6Address = copts.ipv6Address 768 } 769 770 // TODO should linkLocalIPs be added to the _first_ network only, or to _all_ networks? (should this be a per-network option as well?) 771 if copts.linkLocalIPs.Len() > 0 { 772 n.LinkLocalIPs = make([]string, copts.linkLocalIPs.Len()) 773 copy(n.LinkLocalIPs, copts.linkLocalIPs.GetAll()) 774 } 775 return nil 776 } 777 778 func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.EndpointSettings, error) { 779 if strings.TrimSpace(ep.Target) == "" { 780 return nil, errors.New("no name set for network") 781 } 782 if !container.NetworkMode(ep.Target).IsUserDefined() { 783 if len(ep.Aliases) > 0 { 784 return nil, errors.New("network-scoped aliases are only supported for user-defined networks") 785 } 786 if len(ep.Links) > 0 { 787 return nil, errors.New("links are only supported for user-defined networks") 788 } 789 } 790 791 epConfig := &networktypes.EndpointSettings{} 792 epConfig.Aliases = append(epConfig.Aliases, ep.Aliases...) 793 if len(ep.DriverOpts) > 0 { 794 epConfig.DriverOpts = make(map[string]string) 795 epConfig.DriverOpts = ep.DriverOpts 796 } 797 if len(ep.Links) > 0 { 798 epConfig.Links = ep.Links 799 } 800 if ep.IPv4Address != "" || ep.IPv6Address != "" || len(ep.LinkLocalIPs) > 0 { 801 epConfig.IPAMConfig = &networktypes.EndpointIPAMConfig{ 802 IPv4Address: ep.IPv4Address, 803 IPv6Address: ep.IPv6Address, 804 LinkLocalIPs: ep.LinkLocalIPs, 805 } 806 } 807 return epConfig, nil 808 } 809 810 func convertToStandardNotation(ports []string) ([]string, error) { 811 optsList := []string{} 812 for _, publish := range ports { 813 if strings.Contains(publish, "=") { 814 params := map[string]string{"protocol": "tcp"} 815 for _, param := range strings.Split(publish, ",") { 816 opt := strings.Split(param, "=") 817 if len(opt) < 2 { 818 return optsList, errors.Errorf("invalid publish opts format (should be name=value but got '%s')", param) 819 } 820 821 params[opt[0]] = opt[1] 822 } 823 optsList = append(optsList, fmt.Sprintf("%s:%s/%s", params["published"], params["target"], params["protocol"])) 824 } else { 825 optsList = append(optsList, publish) 826 } 827 } 828 return optsList, nil 829 } 830 831 func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) { 832 loggingOptsMap := opts.ConvertKVStringsToMap(loggingOpts) 833 if loggingDriver == "none" && len(loggingOpts) > 0 { 834 return map[string]string{}, errors.Errorf("invalid logging opts for driver %s", loggingDriver) 835 } 836 return loggingOptsMap, nil 837 } 838 839 // takes a local seccomp daemon, reads the file contents for sending to the daemon 840 func parseSecurityOpts(securityOpts []string) ([]string, error) { 841 for key, opt := range securityOpts { 842 con := strings.SplitN(opt, "=", 2) 843 if len(con) == 1 && con[0] != "no-new-privileges" { 844 if strings.Contains(opt, ":") { 845 con = strings.SplitN(opt, ":", 2) 846 } else { 847 return securityOpts, errors.Errorf("Invalid --security-opt: %q", opt) 848 } 849 } 850 if con[0] == "seccomp" && con[1] != "unconfined" { 851 f, err := ioutil.ReadFile(con[1]) 852 if err != nil { 853 return securityOpts, errors.Errorf("opening seccomp profile (%s) failed: %v", con[1], err) 854 } 855 b := bytes.NewBuffer(nil) 856 if err := json.Compact(b, f); err != nil { 857 return securityOpts, errors.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err) 858 } 859 securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes()) 860 } 861 } 862 863 return securityOpts, nil 864 } 865 866 // parseSystemPaths checks if `systempaths=unconfined` security option is set, 867 // and returns the `MaskedPaths` and `ReadonlyPaths` accordingly. An updated 868 // list of security options is returned with this option removed, because the 869 // `unconfined` option is handled client-side, and should not be sent to the 870 // daemon. 871 func parseSystemPaths(securityOpts []string) (filtered, maskedPaths, readonlyPaths []string) { 872 filtered = securityOpts[:0] 873 for _, opt := range securityOpts { 874 if opt == "systempaths=unconfined" { 875 maskedPaths = []string{} 876 readonlyPaths = []string{} 877 } else { 878 filtered = append(filtered, opt) 879 } 880 } 881 882 return filtered, maskedPaths, readonlyPaths 883 } 884 885 // parses storage options per container into a map 886 func parseStorageOpts(storageOpts []string) (map[string]string, error) { 887 m := make(map[string]string) 888 for _, option := range storageOpts { 889 if strings.Contains(option, "=") { 890 opt := strings.SplitN(option, "=", 2) 891 m[opt[0]] = opt[1] 892 } else { 893 return nil, errors.Errorf("invalid storage option") 894 } 895 } 896 return m, nil 897 } 898 899 // parseDevice parses a device mapping string to a container.DeviceMapping struct 900 func parseDevice(device, serverOS string) (container.DeviceMapping, error) { 901 switch serverOS { 902 case "linux": 903 return parseLinuxDevice(device) 904 case "windows": 905 return parseWindowsDevice(device) 906 } 907 return container.DeviceMapping{}, errors.Errorf("unknown server OS: %s", serverOS) 908 } 909 910 // parseLinuxDevice parses a device mapping string to a container.DeviceMapping struct 911 // knowing that the target is a Linux daemon 912 func parseLinuxDevice(device string) (container.DeviceMapping, error) { 913 src := "" 914 dst := "" 915 permissions := "rwm" 916 arr := strings.Split(device, ":") 917 switch len(arr) { 918 case 3: 919 permissions = arr[2] 920 fallthrough 921 case 2: 922 if validDeviceMode(arr[1]) { 923 permissions = arr[1] 924 } else { 925 dst = arr[1] 926 } 927 fallthrough 928 case 1: 929 src = arr[0] 930 default: 931 return container.DeviceMapping{}, errors.Errorf("invalid device specification: %s", device) 932 } 933 934 if dst == "" { 935 dst = src 936 } 937 938 deviceMapping := container.DeviceMapping{ 939 PathOnHost: src, 940 PathInContainer: dst, 941 CgroupPermissions: permissions, 942 } 943 return deviceMapping, nil 944 } 945 946 // parseWindowsDevice parses a device mapping string to a container.DeviceMapping struct 947 // knowing that the target is a Windows daemon 948 func parseWindowsDevice(device string) (container.DeviceMapping, error) { 949 return container.DeviceMapping{PathOnHost: device}, nil 950 } 951 952 // validateDeviceCgroupRule validates a device cgroup rule string format 953 // It will make sure 'val' is in the form: 954 // 'type major:minor mode' 955 func validateDeviceCgroupRule(val string) (string, error) { 956 if deviceCgroupRuleRegexp.MatchString(val) { 957 return val, nil 958 } 959 960 return val, errors.Errorf("invalid device cgroup format '%s'", val) 961 } 962 963 // validDeviceMode checks if the mode for device is valid or not. 964 // Valid mode is a composition of r (read), w (write), and m (mknod). 965 func validDeviceMode(mode string) bool { 966 var legalDeviceMode = map[rune]bool{ 967 'r': true, 968 'w': true, 969 'm': true, 970 } 971 if mode == "" { 972 return false 973 } 974 for _, c := range mode { 975 if !legalDeviceMode[c] { 976 return false 977 } 978 legalDeviceMode[c] = false 979 } 980 return true 981 } 982 983 // validateDevice validates a path for devices 984 func validateDevice(val string, serverOS string) (string, error) { 985 switch serverOS { 986 case "linux": 987 return validateLinuxPath(val, validDeviceMode) 988 case "windows": 989 // Windows does validation entirely server-side 990 return val, nil 991 } 992 return "", errors.Errorf("unknown server OS: %s", serverOS) 993 } 994 995 // validateLinuxPath is the implementation of validateDevice knowing that the 996 // target server operating system is a Linux daemon. 997 // It will make sure 'val' is in the form: 998 // [host-dir:]container-path[:mode] 999 // It also validates the device mode. 1000 func validateLinuxPath(val string, validator func(string) bool) (string, error) { 1001 var containerPath string 1002 var mode string 1003 1004 if strings.Count(val, ":") > 2 { 1005 return val, errors.Errorf("bad format for path: %s", val) 1006 } 1007 1008 split := strings.SplitN(val, ":", 3) 1009 if split[0] == "" { 1010 return val, errors.Errorf("bad format for path: %s", val) 1011 } 1012 switch len(split) { 1013 case 1: 1014 containerPath = split[0] 1015 val = path.Clean(containerPath) 1016 case 2: 1017 if isValid := validator(split[1]); isValid { 1018 containerPath = split[0] 1019 mode = split[1] 1020 val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode) 1021 } else { 1022 containerPath = split[1] 1023 val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath)) 1024 } 1025 case 3: 1026 containerPath = split[1] 1027 mode = split[2] 1028 if isValid := validator(split[2]); !isValid { 1029 return val, errors.Errorf("bad mode specified: %s", mode) 1030 } 1031 val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode) 1032 } 1033 1034 if !path.IsAbs(containerPath) { 1035 return val, errors.Errorf("%s is not an absolute path", containerPath) 1036 } 1037 return val, nil 1038 } 1039 1040 // validateAttach validates that the specified string is a valid attach option. 1041 func validateAttach(val string) (string, error) { 1042 s := strings.ToLower(val) 1043 for _, str := range []string{"stdin", "stdout", "stderr"} { 1044 if s == str { 1045 return s, nil 1046 } 1047 } 1048 return val, errors.Errorf("valid streams are STDIN, STDOUT and STDERR") 1049 } 1050 1051 func validateAPIVersion(c *containerConfig, serverAPIVersion string) error { 1052 for _, m := range c.HostConfig.Mounts { 1053 if m.BindOptions != nil && m.BindOptions.NonRecursive && versions.LessThan(serverAPIVersion, "1.40") { 1054 return errors.Errorf("bind-nonrecursive requires API v1.40 or later") 1055 } 1056 } 1057 return nil 1058 }