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