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