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