github.com/flavio/docker@v0.1.3-0.20170117145210-f63d1a6eec47/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 "strconv" 10 "strings" 11 "time" 12 13 "github.com/docker/docker/api/types/container" 14 networktypes "github.com/docker/docker/api/types/network" 15 "github.com/docker/docker/api/types/strslice" 16 "github.com/docker/docker/opts" 17 "github.com/docker/docker/pkg/signal" 18 runconfigopts "github.com/docker/docker/runconfig/opts" 19 "github.com/docker/go-connections/nat" 20 units "github.com/docker/go-units" 21 "github.com/spf13/pflag" 22 ) 23 24 // containerOptions is a data object with all the options for creating a container 25 type containerOptions struct { 26 attach opts.ListOpts 27 volumes opts.ListOpts 28 tmpfs opts.ListOpts 29 blkioWeightDevice opts.WeightdeviceOpt 30 deviceReadBps opts.ThrottledeviceOpt 31 deviceWriteBps opts.ThrottledeviceOpt 32 links opts.ListOpts 33 aliases opts.ListOpts 34 linkLocalIPs opts.ListOpts 35 deviceReadIOps opts.ThrottledeviceOpt 36 deviceWriteIOps opts.ThrottledeviceOpt 37 env opts.ListOpts 38 labels opts.ListOpts 39 devices opts.ListOpts 40 ulimits *opts.UlimitOpt 41 sysctls *opts.MapOpts 42 publish opts.ListOpts 43 expose opts.ListOpts 44 dns opts.ListOpts 45 dnsSearch opts.ListOpts 46 dnsOptions opts.ListOpts 47 extraHosts opts.ListOpts 48 volumesFrom opts.ListOpts 49 envFile opts.ListOpts 50 capAdd opts.ListOpts 51 capDrop opts.ListOpts 52 groupAdd opts.ListOpts 53 securityOpt opts.ListOpts 54 storageOpt opts.ListOpts 55 labelsFile opts.ListOpts 56 loggingOpts opts.ListOpts 57 privileged bool 58 pidMode string 59 utsMode string 60 usernsMode string 61 publishAll bool 62 stdin bool 63 tty bool 64 oomKillDisable bool 65 oomScoreAdj int 66 containerIDFile string 67 entrypoint string 68 hostname string 69 memoryString string 70 memoryReservation string 71 memorySwap string 72 kernelMemory string 73 user string 74 workingDir string 75 cpuCount int64 76 cpuShares int64 77 cpuPercent int64 78 cpuPeriod int64 79 cpuRealtimePeriod int64 80 cpuRealtimeRuntime int64 81 cpuQuota int64 82 cpus opts.NanoCPUs 83 cpusetCpus string 84 cpusetMems string 85 blkioWeight uint16 86 ioMaxBandwidth string 87 ioMaxIOps uint64 88 swappiness int64 89 netMode string 90 macAddress string 91 ipv4Address string 92 ipv6Address string 93 ipcMode string 94 pidsLimit int64 95 restartPolicy string 96 readonlyRootfs bool 97 loggingDriver string 98 cgroupParent string 99 volumeDriver string 100 stopSignal string 101 stopTimeout int 102 isolation string 103 shmSize string 104 noHealthcheck bool 105 healthCmd string 106 healthInterval time.Duration 107 healthTimeout time.Duration 108 healthRetries int 109 runtime string 110 autoRemove bool 111 init bool 112 initPath string 113 credentialSpec string 114 115 Image string 116 Args []string 117 } 118 119 // addFlags adds all command line flags that will be used by parse to the FlagSet 120 func addFlags(flags *pflag.FlagSet) *containerOptions { 121 copts := &containerOptions{ 122 aliases: opts.NewListOpts(nil), 123 attach: opts.NewListOpts(validateAttach), 124 blkioWeightDevice: opts.NewWeightdeviceOpt(opts.ValidateWeightDevice), 125 capAdd: opts.NewListOpts(nil), 126 capDrop: opts.NewListOpts(nil), 127 dns: opts.NewListOpts(opts.ValidateIPAddress), 128 dnsOptions: opts.NewListOpts(nil), 129 dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch), 130 deviceReadBps: opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice), 131 deviceReadIOps: opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice), 132 deviceWriteBps: opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice), 133 deviceWriteIOps: opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice), 134 devices: opts.NewListOpts(validateDevice), 135 env: opts.NewListOpts(opts.ValidateEnv), 136 envFile: opts.NewListOpts(nil), 137 expose: opts.NewListOpts(nil), 138 extraHosts: opts.NewListOpts(opts.ValidateExtraHost), 139 groupAdd: opts.NewListOpts(nil), 140 labels: opts.NewListOpts(opts.ValidateEnv), 141 labelsFile: opts.NewListOpts(nil), 142 linkLocalIPs: opts.NewListOpts(nil), 143 links: opts.NewListOpts(opts.ValidateLink), 144 loggingOpts: opts.NewListOpts(nil), 145 publish: opts.NewListOpts(nil), 146 securityOpt: opts.NewListOpts(nil), 147 storageOpt: opts.NewListOpts(nil), 148 sysctls: opts.NewMapOpts(nil, opts.ValidateSysctl), 149 tmpfs: opts.NewListOpts(nil), 150 ulimits: opts.NewUlimitOpt(nil), 151 volumes: opts.NewListOpts(nil), 152 volumesFrom: opts.NewListOpts(nil), 153 } 154 155 // General purpose flags 156 flags.VarP(&copts.attach, "attach", "a", "Attach to STDIN, STDOUT or STDERR") 157 flags.Var(&copts.devices, "device", "Add a host device to the container") 158 flags.VarP(&copts.env, "env", "e", "Set environment variables") 159 flags.Var(&copts.envFile, "env-file", "Read in a file of environment variables") 160 flags.StringVar(&copts.entrypoint, "entrypoint", "", "Overwrite the default ENTRYPOINT of the image") 161 flags.Var(&copts.groupAdd, "group-add", "Add additional groups to join") 162 flags.StringVarP(&copts.hostname, "hostname", "h", "", "Container host name") 163 flags.BoolVarP(&copts.stdin, "interactive", "i", false, "Keep STDIN open even if not attached") 164 flags.VarP(&copts.labels, "label", "l", "Set meta data on a container") 165 flags.Var(&copts.labelsFile, "label-file", "Read in a line delimited file of labels") 166 flags.BoolVar(&copts.readonlyRootfs, "read-only", false, "Mount the container's root filesystem as read only") 167 flags.StringVar(&copts.restartPolicy, "restart", "no", "Restart policy to apply when a container exits") 168 flags.StringVar(&copts.stopSignal, "stop-signal", signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal)) 169 flags.IntVar(&copts.stopTimeout, "stop-timeout", 0, "Timeout (in seconds) to stop a container") 170 flags.SetAnnotation("stop-timeout", "version", []string{"1.25"}) 171 flags.Var(copts.sysctls, "sysctl", "Sysctl options") 172 flags.BoolVarP(&copts.tty, "tty", "t", false, "Allocate a pseudo-TTY") 173 flags.Var(copts.ulimits, "ulimit", "Ulimit options") 174 flags.StringVarP(&copts.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])") 175 flags.StringVarP(&copts.workingDir, "workdir", "w", "", "Working directory inside the container") 176 flags.BoolVar(&copts.autoRemove, "rm", false, "Automatically remove the container when it exits") 177 178 // Security 179 flags.Var(&copts.capAdd, "cap-add", "Add Linux capabilities") 180 flags.Var(&copts.capDrop, "cap-drop", "Drop Linux capabilities") 181 flags.BoolVar(&copts.privileged, "privileged", false, "Give extended privileges to this container") 182 flags.Var(&copts.securityOpt, "security-opt", "Security Options") 183 flags.StringVar(&copts.usernsMode, "userns", "", "User namespace to use") 184 flags.StringVar(&copts.credentialSpec, "credentialspec", "", "Credential spec for managed service account (Windows only)") 185 186 // Network and port publishing flag 187 flags.Var(&copts.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)") 188 flags.Var(&copts.dns, "dns", "Set custom DNS servers") 189 // We allow for both "--dns-opt" and "--dns-option", although the latter is the recommended way. 190 // This is to be consistent with service create/update 191 flags.Var(&copts.dnsOptions, "dns-opt", "Set DNS options") 192 flags.Var(&copts.dnsOptions, "dns-option", "Set DNS options") 193 flags.MarkHidden("dns-opt") 194 flags.Var(&copts.dnsSearch, "dns-search", "Set custom DNS search domains") 195 flags.Var(&copts.expose, "expose", "Expose a port or a range of ports") 196 flags.StringVar(&copts.ipv4Address, "ip", "", "IPv4 address (e.g., 172.30.100.104)") 197 flags.StringVar(&copts.ipv6Address, "ip6", "", "IPv6 address (e.g., 2001:db8::33)") 198 flags.Var(&copts.links, "link", "Add link to another container") 199 flags.Var(&copts.linkLocalIPs, "link-local-ip", "Container IPv4/IPv6 link-local addresses") 200 flags.StringVar(&copts.macAddress, "mac-address", "", "Container MAC address (e.g., 92:d0:c6:0a:29:33)") 201 flags.VarP(&copts.publish, "publish", "p", "Publish a container's port(s) to the host") 202 flags.BoolVarP(&copts.publishAll, "publish-all", "P", false, "Publish all exposed ports to random ports") 203 // We allow for both "--net" and "--network", although the latter is the recommended way. 204 flags.StringVar(&copts.netMode, "net", "default", "Connect a container to a network") 205 flags.StringVar(&copts.netMode, "network", "default", "Connect a container to a network") 206 flags.MarkHidden("net") 207 // We allow for both "--net-alias" and "--network-alias", although the latter is the recommended way. 208 flags.Var(&copts.aliases, "net-alias", "Add network-scoped alias for the container") 209 flags.Var(&copts.aliases, "network-alias", "Add network-scoped alias for the container") 210 flags.MarkHidden("net-alias") 211 212 // Logging and storage 213 flags.StringVar(&copts.loggingDriver, "log-driver", "", "Logging driver for the container") 214 flags.StringVar(&copts.volumeDriver, "volume-driver", "", "Optional volume driver for the container") 215 flags.Var(&copts.loggingOpts, "log-opt", "Log driver options") 216 flags.Var(&copts.storageOpt, "storage-opt", "Storage driver options for the container") 217 flags.Var(&copts.tmpfs, "tmpfs", "Mount a tmpfs directory") 218 flags.Var(&copts.volumesFrom, "volumes-from", "Mount volumes from the specified container(s)") 219 flags.VarP(&copts.volumes, "volume", "v", "Bind mount a volume") 220 221 // Health-checking 222 flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health") 223 flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check (ns|us|ms|s|m|h) (default 0s)") 224 flags.IntVar(&copts.healthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy") 225 flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ns|us|ms|s|m|h) (default 0s)") 226 flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK") 227 228 // Resource management 229 flags.Uint16Var(&copts.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)") 230 flags.Var(&copts.blkioWeightDevice, "blkio-weight-device", "Block IO weight (relative device weight)") 231 flags.StringVar(&copts.containerIDFile, "cidfile", "", "Write the container ID to the file") 232 flags.StringVar(&copts.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)") 233 flags.StringVar(&copts.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)") 234 flags.Int64Var(&copts.cpuCount, "cpu-count", 0, "CPU count (Windows only)") 235 flags.Int64Var(&copts.cpuPercent, "cpu-percent", 0, "CPU percent (Windows only)") 236 flags.Int64Var(&copts.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period") 237 flags.Int64Var(&copts.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota") 238 flags.Int64Var(&copts.cpuRealtimePeriod, "cpu-rt-period", 0, "Limit CPU real-time period in microseconds") 239 flags.Int64Var(&copts.cpuRealtimeRuntime, "cpu-rt-runtime", 0, "Limit CPU real-time runtime in microseconds") 240 flags.Int64VarP(&copts.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)") 241 flags.Var(&copts.cpus, "cpus", "Number of CPUs") 242 flags.Var(&copts.deviceReadBps, "device-read-bps", "Limit read rate (bytes per second) from a device") 243 flags.Var(&copts.deviceReadIOps, "device-read-iops", "Limit read rate (IO per second) from a device") 244 flags.Var(&copts.deviceWriteBps, "device-write-bps", "Limit write rate (bytes per second) to a device") 245 flags.Var(&copts.deviceWriteIOps, "device-write-iops", "Limit write rate (IO per second) to a device") 246 flags.StringVar(&copts.ioMaxBandwidth, "io-maxbandwidth", "", "Maximum IO bandwidth limit for the system drive (Windows only)") 247 flags.Uint64Var(&copts.ioMaxIOps, "io-maxiops", 0, "Maximum IOps limit for the system drive (Windows only)") 248 flags.StringVar(&copts.kernelMemory, "kernel-memory", "", "Kernel memory limit") 249 flags.StringVarP(&copts.memoryString, "memory", "m", "", "Memory limit") 250 flags.StringVar(&copts.memoryReservation, "memory-reservation", "", "Memory soft limit") 251 flags.StringVar(&copts.memorySwap, "memory-swap", "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap") 252 flags.Int64Var(&copts.swappiness, "memory-swappiness", -1, "Tune container memory swappiness (0 to 100)") 253 flags.BoolVar(&copts.oomKillDisable, "oom-kill-disable", false, "Disable OOM Killer") 254 flags.IntVar(&copts.oomScoreAdj, "oom-score-adj", 0, "Tune host's OOM preferences (-1000 to 1000)") 255 flags.Int64Var(&copts.pidsLimit, "pids-limit", 0, "Tune container pids limit (set -1 for unlimited)") 256 257 // Low-level execution (cgroups, namespaces, ...) 258 flags.StringVar(&copts.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container") 259 flags.StringVar(&copts.ipcMode, "ipc", "", "IPC namespace to use") 260 flags.StringVar(&copts.isolation, "isolation", "", "Container isolation technology") 261 flags.StringVar(&copts.pidMode, "pid", "", "PID namespace to use") 262 flags.StringVar(&copts.shmSize, "shm-size", "", "Size of /dev/shm, default value is 64MB") 263 flags.StringVar(&copts.utsMode, "uts", "", "UTS namespace to use") 264 flags.StringVar(&copts.runtime, "runtime", "", "Runtime to use for this container") 265 266 flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes") 267 flags.StringVar(&copts.initPath, "init-path", "", "Path to the docker-init binary") 268 return copts 269 } 270 271 // parse parses the args for the specified command and generates a Config, 272 // a HostConfig and returns them with the specified command. 273 // If the specified args are not valid, it will return an error. 274 func parse(flags *pflag.FlagSet, copts *containerOptions) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) { 275 var ( 276 attachStdin = copts.attach.Get("stdin") 277 attachStdout = copts.attach.Get("stdout") 278 attachStderr = copts.attach.Get("stderr") 279 ) 280 281 // Validate the input mac address 282 if copts.macAddress != "" { 283 if _, err := opts.ValidateMACAddress(copts.macAddress); err != nil { 284 return nil, nil, nil, fmt.Errorf("%s is not a valid mac address", copts.macAddress) 285 } 286 } 287 if copts.stdin { 288 attachStdin = true 289 } 290 // If -a is not set, attach to stdout and stderr 291 if copts.attach.Len() == 0 { 292 attachStdout = true 293 attachStderr = true 294 } 295 296 var err error 297 298 var memory int64 299 if copts.memoryString != "" { 300 memory, err = units.RAMInBytes(copts.memoryString) 301 if err != nil { 302 return nil, nil, nil, err 303 } 304 } 305 306 var memoryReservation int64 307 if copts.memoryReservation != "" { 308 memoryReservation, err = units.RAMInBytes(copts.memoryReservation) 309 if err != nil { 310 return nil, nil, nil, err 311 } 312 } 313 314 var memorySwap int64 315 if copts.memorySwap != "" { 316 if copts.memorySwap == "-1" { 317 memorySwap = -1 318 } else { 319 memorySwap, err = units.RAMInBytes(copts.memorySwap) 320 if err != nil { 321 return nil, nil, nil, err 322 } 323 } 324 } 325 326 var kernelMemory int64 327 if copts.kernelMemory != "" { 328 kernelMemory, err = units.RAMInBytes(copts.kernelMemory) 329 if err != nil { 330 return nil, nil, nil, err 331 } 332 } 333 334 swappiness := copts.swappiness 335 if swappiness != -1 && (swappiness < 0 || swappiness > 100) { 336 return nil, nil, nil, fmt.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness) 337 } 338 339 var shmSize int64 340 if copts.shmSize != "" { 341 shmSize, err = units.RAMInBytes(copts.shmSize) 342 if err != nil { 343 return nil, nil, nil, err 344 } 345 } 346 347 // TODO FIXME units.RAMInBytes should have a uint64 version 348 var maxIOBandwidth int64 349 if copts.ioMaxBandwidth != "" { 350 maxIOBandwidth, err = units.RAMInBytes(copts.ioMaxBandwidth) 351 if err != nil { 352 return nil, nil, nil, err 353 } 354 if maxIOBandwidth < 0 { 355 return nil, nil, nil, fmt.Errorf("invalid value: %s. Maximum IO Bandwidth must be positive", copts.ioMaxBandwidth) 356 } 357 } 358 359 var binds []string 360 volumes := copts.volumes.GetMap() 361 // add any bind targets to the list of container volumes 362 for bind := range copts.volumes.GetMap() { 363 if arr := volumeSplitN(bind, 2); len(arr) > 1 { 364 // after creating the bind mount we want to delete it from the copts.volumes values because 365 // we do not want bind mounts being committed to image configs 366 binds = append(binds, bind) 367 // We should delete from the map (`volumes`) here, as deleting from copts.volumes will not work if 368 // there are duplicates entries. 369 delete(volumes, bind) 370 } 371 } 372 373 // Can't evaluate options passed into --tmpfs until we actually mount 374 tmpfs := make(map[string]string) 375 for _, t := range copts.tmpfs.GetAll() { 376 if arr := strings.SplitN(t, ":", 2); len(arr) > 1 { 377 tmpfs[arr[0]] = arr[1] 378 } else { 379 tmpfs[arr[0]] = "" 380 } 381 } 382 383 var ( 384 runCmd strslice.StrSlice 385 entrypoint strslice.StrSlice 386 ) 387 388 if len(copts.Args) > 0 { 389 runCmd = strslice.StrSlice(copts.Args) 390 } 391 392 if copts.entrypoint != "" { 393 entrypoint = strslice.StrSlice{copts.entrypoint} 394 } else if flags.Changed("entrypoint") { 395 // if `--entrypoint=` is parsed then Entrypoint is reset 396 entrypoint = []string{""} 397 } 398 399 ports, portBindings, err := nat.ParsePortSpecs(copts.publish.GetAll()) 400 if err != nil { 401 return nil, nil, nil, err 402 } 403 404 // Merge in exposed ports to the map of published ports 405 for _, e := range copts.expose.GetAll() { 406 if strings.Contains(e, ":") { 407 return nil, nil, nil, fmt.Errorf("invalid port format for --expose: %s", e) 408 } 409 //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>] 410 proto, port := nat.SplitProtoPort(e) 411 //parse the start and end port and create a sequence of ports to expose 412 //if expose a port, the start and end port are the same 413 start, end, err := nat.ParsePortRange(port) 414 if err != nil { 415 return nil, nil, nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", e, err) 416 } 417 for i := start; i <= end; i++ { 418 p, err := nat.NewPort(proto, strconv.FormatUint(i, 10)) 419 if err != nil { 420 return nil, nil, nil, err 421 } 422 if _, exists := ports[p]; !exists { 423 ports[p] = struct{}{} 424 } 425 } 426 } 427 428 // parse device mappings 429 deviceMappings := []container.DeviceMapping{} 430 for _, device := range copts.devices.GetAll() { 431 deviceMapping, err := parseDevice(device) 432 if err != nil { 433 return nil, nil, nil, err 434 } 435 deviceMappings = append(deviceMappings, deviceMapping) 436 } 437 438 // collect all the environment variables for the container 439 envVariables, err := runconfigopts.ReadKVStrings(copts.envFile.GetAll(), copts.env.GetAll()) 440 if err != nil { 441 return nil, nil, nil, err 442 } 443 444 // collect all the labels for the container 445 labels, err := runconfigopts.ReadKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll()) 446 if err != nil { 447 return nil, nil, nil, err 448 } 449 450 ipcMode := container.IpcMode(copts.ipcMode) 451 if !ipcMode.Valid() { 452 return nil, nil, nil, fmt.Errorf("--ipc: invalid IPC mode") 453 } 454 455 pidMode := container.PidMode(copts.pidMode) 456 if !pidMode.Valid() { 457 return nil, nil, nil, fmt.Errorf("--pid: invalid PID mode") 458 } 459 460 utsMode := container.UTSMode(copts.utsMode) 461 if !utsMode.Valid() { 462 return nil, nil, nil, fmt.Errorf("--uts: invalid UTS mode") 463 } 464 465 usernsMode := container.UsernsMode(copts.usernsMode) 466 if !usernsMode.Valid() { 467 return nil, nil, nil, fmt.Errorf("--userns: invalid USER mode") 468 } 469 470 restartPolicy, err := runconfigopts.ParseRestartPolicy(copts.restartPolicy) 471 if err != nil { 472 return nil, nil, nil, err 473 } 474 475 loggingOpts, err := parseLoggingOpts(copts.loggingDriver, copts.loggingOpts.GetAll()) 476 if err != nil { 477 return nil, nil, nil, err 478 } 479 480 securityOpts, err := parseSecurityOpts(copts.securityOpt.GetAll()) 481 if err != nil { 482 return nil, nil, nil, err 483 } 484 485 storageOpts, err := parseStorageOpts(copts.storageOpt.GetAll()) 486 if err != nil { 487 return nil, nil, nil, err 488 } 489 490 // Healthcheck 491 var healthConfig *container.HealthConfig 492 haveHealthSettings := copts.healthCmd != "" || 493 copts.healthInterval != 0 || 494 copts.healthTimeout != 0 || 495 copts.healthRetries != 0 496 if copts.noHealthcheck { 497 if haveHealthSettings { 498 return nil, nil, nil, fmt.Errorf("--no-healthcheck conflicts with --health-* options") 499 } 500 test := strslice.StrSlice{"NONE"} 501 healthConfig = &container.HealthConfig{Test: test} 502 } else if haveHealthSettings { 503 var probe strslice.StrSlice 504 if copts.healthCmd != "" { 505 args := []string{"CMD-SHELL", copts.healthCmd} 506 probe = strslice.StrSlice(args) 507 } 508 if copts.healthInterval < 0 { 509 return nil, nil, nil, fmt.Errorf("--health-interval cannot be negative") 510 } 511 if copts.healthTimeout < 0 { 512 return nil, nil, nil, fmt.Errorf("--health-timeout cannot be negative") 513 } 514 515 healthConfig = &container.HealthConfig{ 516 Test: probe, 517 Interval: copts.healthInterval, 518 Timeout: copts.healthTimeout, 519 Retries: copts.healthRetries, 520 } 521 } 522 523 resources := container.Resources{ 524 CgroupParent: copts.cgroupParent, 525 Memory: memory, 526 MemoryReservation: memoryReservation, 527 MemorySwap: memorySwap, 528 MemorySwappiness: &copts.swappiness, 529 KernelMemory: kernelMemory, 530 OomKillDisable: &copts.oomKillDisable, 531 NanoCPUs: copts.cpus.Value(), 532 CPUCount: copts.cpuCount, 533 CPUPercent: copts.cpuPercent, 534 CPUShares: copts.cpuShares, 535 CPUPeriod: copts.cpuPeriod, 536 CpusetCpus: copts.cpusetCpus, 537 CpusetMems: copts.cpusetMems, 538 CPUQuota: copts.cpuQuota, 539 CPURealtimePeriod: copts.cpuRealtimePeriod, 540 CPURealtimeRuntime: copts.cpuRealtimeRuntime, 541 PidsLimit: copts.pidsLimit, 542 BlkioWeight: copts.blkioWeight, 543 BlkioWeightDevice: copts.blkioWeightDevice.GetList(), 544 BlkioDeviceReadBps: copts.deviceReadBps.GetList(), 545 BlkioDeviceWriteBps: copts.deviceWriteBps.GetList(), 546 BlkioDeviceReadIOps: copts.deviceReadIOps.GetList(), 547 BlkioDeviceWriteIOps: copts.deviceWriteIOps.GetList(), 548 IOMaximumIOps: copts.ioMaxIOps, 549 IOMaximumBandwidth: uint64(maxIOBandwidth), 550 Ulimits: copts.ulimits.GetList(), 551 Devices: deviceMappings, 552 } 553 554 config := &container.Config{ 555 Hostname: copts.hostname, 556 ExposedPorts: ports, 557 User: copts.user, 558 Tty: copts.tty, 559 // TODO: deprecated, it comes from -n, --networking 560 // it's still needed internally to set the network to disabled 561 // if e.g. bridge is none in daemon opts, and in inspect 562 NetworkDisabled: false, 563 OpenStdin: copts.stdin, 564 AttachStdin: attachStdin, 565 AttachStdout: attachStdout, 566 AttachStderr: attachStderr, 567 Env: envVariables, 568 Cmd: runCmd, 569 Image: copts.Image, 570 Volumes: volumes, 571 MacAddress: copts.macAddress, 572 Entrypoint: entrypoint, 573 WorkingDir: copts.workingDir, 574 Labels: runconfigopts.ConvertKVStringsToMap(labels), 575 Healthcheck: healthConfig, 576 } 577 if flags.Changed("stop-signal") { 578 config.StopSignal = copts.stopSignal 579 } 580 if flags.Changed("stop-timeout") { 581 config.StopTimeout = &copts.stopTimeout 582 } 583 584 hostConfig := &container.HostConfig{ 585 Binds: binds, 586 ContainerIDFile: copts.containerIDFile, 587 OomScoreAdj: copts.oomScoreAdj, 588 AutoRemove: copts.autoRemove, 589 Privileged: copts.privileged, 590 PortBindings: portBindings, 591 Links: copts.links.GetAll(), 592 PublishAllPorts: copts.publishAll, 593 // Make sure the dns fields are never nil. 594 // New containers don't ever have those fields nil, 595 // but pre created containers can still have those nil values. 596 // See https://github.com/docker/docker/pull/17779 597 // for a more detailed explanation on why we don't want that. 598 DNS: copts.dns.GetAllOrEmpty(), 599 DNSSearch: copts.dnsSearch.GetAllOrEmpty(), 600 DNSOptions: copts.dnsOptions.GetAllOrEmpty(), 601 ExtraHosts: copts.extraHosts.GetAll(), 602 VolumesFrom: copts.volumesFrom.GetAll(), 603 NetworkMode: container.NetworkMode(copts.netMode), 604 IpcMode: ipcMode, 605 PidMode: pidMode, 606 UTSMode: utsMode, 607 UsernsMode: usernsMode, 608 CapAdd: strslice.StrSlice(copts.capAdd.GetAll()), 609 CapDrop: strslice.StrSlice(copts.capDrop.GetAll()), 610 GroupAdd: copts.groupAdd.GetAll(), 611 RestartPolicy: restartPolicy, 612 SecurityOpt: securityOpts, 613 StorageOpt: storageOpts, 614 ReadonlyRootfs: copts.readonlyRootfs, 615 LogConfig: container.LogConfig{Type: copts.loggingDriver, Config: loggingOpts}, 616 VolumeDriver: copts.volumeDriver, 617 Isolation: container.Isolation(copts.isolation), 618 ShmSize: shmSize, 619 Resources: resources, 620 Tmpfs: tmpfs, 621 Sysctls: copts.sysctls.GetAll(), 622 Runtime: copts.runtime, 623 } 624 625 if copts.autoRemove && !hostConfig.RestartPolicy.IsNone() { 626 return nil, nil, nil, fmt.Errorf("Conflicting options: --restart and --rm") 627 } 628 629 // only set this value if the user provided the flag, else it should default to nil 630 if flags.Changed("init") { 631 hostConfig.Init = &copts.init 632 } 633 634 // When allocating stdin in attached mode, close stdin at client disconnect 635 if config.OpenStdin && config.AttachStdin { 636 config.StdinOnce = true 637 } 638 639 networkingConfig := &networktypes.NetworkingConfig{ 640 EndpointsConfig: make(map[string]*networktypes.EndpointSettings), 641 } 642 643 if copts.ipv4Address != "" || copts.ipv6Address != "" || copts.linkLocalIPs.Len() > 0 { 644 epConfig := &networktypes.EndpointSettings{} 645 networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig 646 647 epConfig.IPAMConfig = &networktypes.EndpointIPAMConfig{ 648 IPv4Address: copts.ipv4Address, 649 IPv6Address: copts.ipv6Address, 650 } 651 652 if copts.linkLocalIPs.Len() > 0 { 653 epConfig.IPAMConfig.LinkLocalIPs = make([]string, copts.linkLocalIPs.Len()) 654 copy(epConfig.IPAMConfig.LinkLocalIPs, copts.linkLocalIPs.GetAll()) 655 } 656 } 657 658 if hostConfig.NetworkMode.IsUserDefined() && len(hostConfig.Links) > 0 { 659 epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] 660 if epConfig == nil { 661 epConfig = &networktypes.EndpointSettings{} 662 } 663 epConfig.Links = make([]string, len(hostConfig.Links)) 664 copy(epConfig.Links, hostConfig.Links) 665 networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig 666 } 667 668 if copts.aliases.Len() > 0 { 669 epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] 670 if epConfig == nil { 671 epConfig = &networktypes.EndpointSettings{} 672 } 673 epConfig.Aliases = make([]string, copts.aliases.Len()) 674 copy(epConfig.Aliases, copts.aliases.GetAll()) 675 networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig 676 } 677 678 return config, hostConfig, networkingConfig, nil 679 } 680 681 func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) { 682 loggingOptsMap := runconfigopts.ConvertKVStringsToMap(loggingOpts) 683 if loggingDriver == "none" && len(loggingOpts) > 0 { 684 return map[string]string{}, fmt.Errorf("invalid logging opts for driver %s", loggingDriver) 685 } 686 return loggingOptsMap, nil 687 } 688 689 // takes a local seccomp daemon, reads the file contents for sending to the daemon 690 func parseSecurityOpts(securityOpts []string) ([]string, error) { 691 for key, opt := range securityOpts { 692 con := strings.SplitN(opt, "=", 2) 693 if len(con) == 1 && con[0] != "no-new-privileges" { 694 if strings.Contains(opt, ":") { 695 con = strings.SplitN(opt, ":", 2) 696 } else { 697 return securityOpts, fmt.Errorf("Invalid --security-opt: %q", opt) 698 } 699 } 700 if con[0] == "seccomp" && con[1] != "unconfined" { 701 f, err := ioutil.ReadFile(con[1]) 702 if err != nil { 703 return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %v", con[1], err) 704 } 705 b := bytes.NewBuffer(nil) 706 if err := json.Compact(b, f); err != nil { 707 return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err) 708 } 709 securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes()) 710 } 711 } 712 713 return securityOpts, nil 714 } 715 716 // parses storage options per container into a map 717 func parseStorageOpts(storageOpts []string) (map[string]string, error) { 718 m := make(map[string]string) 719 for _, option := range storageOpts { 720 if strings.Contains(option, "=") { 721 opt := strings.SplitN(option, "=", 2) 722 m[opt[0]] = opt[1] 723 } else { 724 return nil, fmt.Errorf("invalid storage option") 725 } 726 } 727 return m, nil 728 } 729 730 // parseDevice parses a device mapping string to a container.DeviceMapping struct 731 func parseDevice(device string) (container.DeviceMapping, error) { 732 src := "" 733 dst := "" 734 permissions := "rwm" 735 arr := strings.Split(device, ":") 736 switch len(arr) { 737 case 3: 738 permissions = arr[2] 739 fallthrough 740 case 2: 741 if validDeviceMode(arr[1]) { 742 permissions = arr[1] 743 } else { 744 dst = arr[1] 745 } 746 fallthrough 747 case 1: 748 src = arr[0] 749 default: 750 return container.DeviceMapping{}, fmt.Errorf("invalid device specification: %s", device) 751 } 752 753 if dst == "" { 754 dst = src 755 } 756 757 deviceMapping := container.DeviceMapping{ 758 PathOnHost: src, 759 PathInContainer: dst, 760 CgroupPermissions: permissions, 761 } 762 return deviceMapping, nil 763 } 764 765 // validDeviceMode checks if the mode for device is valid or not. 766 // Valid mode is a composition of r (read), w (write), and m (mknod). 767 func validDeviceMode(mode string) bool { 768 var legalDeviceMode = map[rune]bool{ 769 'r': true, 770 'w': true, 771 'm': true, 772 } 773 if mode == "" { 774 return false 775 } 776 for _, c := range mode { 777 if !legalDeviceMode[c] { 778 return false 779 } 780 legalDeviceMode[c] = false 781 } 782 return true 783 } 784 785 // validateDevice validates a path for devices 786 // It will make sure 'val' is in the form: 787 // [host-dir:]container-path[:mode] 788 // It also validates the device mode. 789 func validateDevice(val string) (string, error) { 790 return validatePath(val, validDeviceMode) 791 } 792 793 func validatePath(val string, validator func(string) bool) (string, error) { 794 var containerPath string 795 var mode string 796 797 if strings.Count(val, ":") > 2 { 798 return val, fmt.Errorf("bad format for path: %s", val) 799 } 800 801 split := strings.SplitN(val, ":", 3) 802 if split[0] == "" { 803 return val, fmt.Errorf("bad format for path: %s", val) 804 } 805 switch len(split) { 806 case 1: 807 containerPath = split[0] 808 val = path.Clean(containerPath) 809 case 2: 810 if isValid := validator(split[1]); isValid { 811 containerPath = split[0] 812 mode = split[1] 813 val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode) 814 } else { 815 containerPath = split[1] 816 val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath)) 817 } 818 case 3: 819 containerPath = split[1] 820 mode = split[2] 821 if isValid := validator(split[2]); !isValid { 822 return val, fmt.Errorf("bad mode specified: %s", mode) 823 } 824 val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode) 825 } 826 827 if !path.IsAbs(containerPath) { 828 return val, fmt.Errorf("%s is not an absolute path", containerPath) 829 } 830 return val, nil 831 } 832 833 // volumeSplitN splits raw into a maximum of n parts, separated by a separator colon. 834 // A separator colon is the last `:` character in the regex `[:\\]?[a-zA-Z]:` (note `\\` is `\` escaped). 835 // In Windows driver letter appears in two situations: 836 // a. `^[a-zA-Z]:` (A colon followed by `^[a-zA-Z]:` is OK as colon is the separator in volume option) 837 // b. A string in the format like `\\?\C:\Windows\...` (UNC). 838 // Therefore, a driver letter can only follow either a `:` or `\\` 839 // This allows to correctly split strings such as `C:\foo:D:\:rw` or `/tmp/q:/foo`. 840 func volumeSplitN(raw string, n int) []string { 841 var array []string 842 if len(raw) == 0 || raw[0] == ':' { 843 // invalid 844 return nil 845 } 846 // numberOfParts counts the number of parts separated by a separator colon 847 numberOfParts := 0 848 // left represents the left-most cursor in raw, updated at every `:` character considered as a separator. 849 left := 0 850 // right represents the right-most cursor in raw incremented with the loop. Note this 851 // starts at index 1 as index 0 is already handle above as a special case. 852 for right := 1; right < len(raw); right++ { 853 // stop parsing if reached maximum number of parts 854 if n >= 0 && numberOfParts >= n { 855 break 856 } 857 if raw[right] != ':' { 858 continue 859 } 860 potentialDriveLetter := raw[right-1] 861 if (potentialDriveLetter >= 'A' && potentialDriveLetter <= 'Z') || (potentialDriveLetter >= 'a' && potentialDriveLetter <= 'z') { 862 if right > 1 { 863 beforePotentialDriveLetter := raw[right-2] 864 // Only `:` or `\\` are checked (`/` could fall into the case of `/tmp/q:/foo`) 865 if beforePotentialDriveLetter != ':' && beforePotentialDriveLetter != '\\' { 866 // e.g. `C:` is not preceded by any delimiter, therefore it was not a drive letter but a path ending with `C:`. 867 array = append(array, raw[left:right]) 868 left = right + 1 869 numberOfParts++ 870 } 871 // else, `C:` is considered as a drive letter and not as a delimiter, so we continue parsing. 872 } 873 // if right == 1, then `C:` is the beginning of the raw string, therefore `:` is again not considered a delimiter and we continue parsing. 874 } else { 875 // if `:` is not preceded by a potential drive letter, then consider it as a delimiter. 876 array = append(array, raw[left:right]) 877 left = right + 1 878 numberOfParts++ 879 } 880 } 881 // need to take care of the last part 882 if left < len(raw) { 883 if n >= 0 && numberOfParts >= n { 884 // if the maximum number of parts is reached, just append the rest to the last part 885 // left-1 is at the last `:` that needs to be included since not considered a separator. 886 array[n-1] += raw[left-1:] 887 } else { 888 array = append(array, raw[left:]) 889 } 890 } 891 return array 892 } 893 894 // validateAttach validates that the specified string is a valid attach option. 895 func validateAttach(val string) (string, error) { 896 s := strings.ToLower(val) 897 for _, str := range []string{"stdin", "stdout", "stderr"} { 898 if s == str { 899 return s, nil 900 } 901 } 902 return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR") 903 }