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