github.com/yogeshlonkar/moby@v1.13.2-0.20201203103638-c0b64beaea94/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 if copts.autoRemove && !hostConfig.RestartPolicy.IsNone() { 625 return nil, nil, nil, fmt.Errorf("Conflicting options: --restart and --rm") 626 } 627 628 // only set this value if the user provided the flag, else it should default to nil 629 if flags.Changed("init") { 630 hostConfig.Init = &copts.init 631 } 632 633 // When allocating stdin in attached mode, close stdin at client disconnect 634 if config.OpenStdin && config.AttachStdin { 635 config.StdinOnce = true 636 } 637 638 networkingConfig := &networktypes.NetworkingConfig{ 639 EndpointsConfig: make(map[string]*networktypes.EndpointSettings), 640 } 641 642 if copts.ipv4Address != "" || copts.ipv6Address != "" || copts.linkLocalIPs.Len() > 0 { 643 epConfig := &networktypes.EndpointSettings{} 644 networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig 645 646 epConfig.IPAMConfig = &networktypes.EndpointIPAMConfig{ 647 IPv4Address: copts.ipv4Address, 648 IPv6Address: copts.ipv6Address, 649 } 650 651 if copts.linkLocalIPs.Len() > 0 { 652 epConfig.IPAMConfig.LinkLocalIPs = make([]string, copts.linkLocalIPs.Len()) 653 copy(epConfig.IPAMConfig.LinkLocalIPs, copts.linkLocalIPs.GetAll()) 654 } 655 } 656 657 if hostConfig.NetworkMode.IsUserDefined() && len(hostConfig.Links) > 0 { 658 epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] 659 if epConfig == nil { 660 epConfig = &networktypes.EndpointSettings{} 661 } 662 epConfig.Links = make([]string, len(hostConfig.Links)) 663 copy(epConfig.Links, hostConfig.Links) 664 networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig 665 } 666 667 if copts.aliases.Len() > 0 { 668 epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] 669 if epConfig == nil { 670 epConfig = &networktypes.EndpointSettings{} 671 } 672 epConfig.Aliases = make([]string, copts.aliases.Len()) 673 copy(epConfig.Aliases, copts.aliases.GetAll()) 674 networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig 675 } 676 677 return config, hostConfig, networkingConfig, nil 678 } 679 680 // ReadKVStrings reads a file of line terminated key=value pairs, and overrides any keys 681 // present in the file with additional pairs specified in the override parameter 682 func ReadKVStrings(files []string, override []string) ([]string, error) { 683 envVariables := []string{} 684 for _, ef := range files { 685 parsedVars, err := ParseEnvFile(ef) 686 if err != nil { 687 return nil, err 688 } 689 envVariables = append(envVariables, parsedVars...) 690 } 691 // parse the '-e' and '--env' after, to allow override 692 envVariables = append(envVariables, override...) 693 694 return envVariables, nil 695 } 696 697 // ConvertKVStringsToMap converts ["key=value"] to {"key":"value"} 698 func ConvertKVStringsToMap(values []string) map[string]string { 699 result := make(map[string]string, len(values)) 700 for _, value := range values { 701 kv := strings.SplitN(value, "=", 2) 702 if len(kv) == 1 { 703 result[kv[0]] = "" 704 } else { 705 result[kv[0]] = kv[1] 706 } 707 } 708 709 return result 710 } 711 712 // ConvertKVStringsToMapWithNil converts ["key=value"] to {"key":"value"} 713 // but set unset keys to nil - meaning the ones with no "=" in them. 714 // We use this in cases where we need to distinguish between 715 // FOO= and FOO 716 // where the latter case just means FOO was mentioned but not given a value 717 func ConvertKVStringsToMapWithNil(values []string) map[string]*string { 718 result := make(map[string]*string, len(values)) 719 for _, value := range values { 720 kv := strings.SplitN(value, "=", 2) 721 if len(kv) == 1 { 722 result[kv[0]] = nil 723 } else { 724 result[kv[0]] = &kv[1] 725 } 726 } 727 728 return result 729 } 730 731 func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) { 732 loggingOptsMap := ConvertKVStringsToMap(loggingOpts) 733 if loggingDriver == "none" && len(loggingOpts) > 0 { 734 return map[string]string{}, fmt.Errorf("invalid logging opts for driver %s", loggingDriver) 735 } 736 return loggingOptsMap, nil 737 } 738 739 // takes a local seccomp daemon, reads the file contents for sending to the daemon 740 func parseSecurityOpts(securityOpts []string) ([]string, error) { 741 for key, opt := range securityOpts { 742 con := strings.SplitN(opt, "=", 2) 743 if len(con) == 1 && con[0] != "no-new-privileges" { 744 if strings.Contains(opt, ":") { 745 con = strings.SplitN(opt, ":", 2) 746 } else { 747 return securityOpts, fmt.Errorf("Invalid --security-opt: %q", opt) 748 } 749 } 750 if con[0] == "seccomp" && con[1] != "unconfined" { 751 f, err := ioutil.ReadFile(con[1]) 752 if err != nil { 753 return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %v", con[1], err) 754 } 755 b := bytes.NewBuffer(nil) 756 if err := json.Compact(b, f); err != nil { 757 return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err) 758 } 759 securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes()) 760 } 761 } 762 763 return securityOpts, nil 764 } 765 766 // parses storage options per container into a map 767 func parseStorageOpts(storageOpts []string) (map[string]string, error) { 768 m := make(map[string]string) 769 for _, option := range storageOpts { 770 if strings.Contains(option, "=") { 771 opt := strings.SplitN(option, "=", 2) 772 m[opt[0]] = opt[1] 773 } else { 774 return nil, fmt.Errorf("invalid storage option") 775 } 776 } 777 return m, nil 778 } 779 780 // ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect 781 func ParseRestartPolicy(policy string) (container.RestartPolicy, error) { 782 p := container.RestartPolicy{} 783 784 if policy == "" { 785 return p, nil 786 } 787 788 parts := strings.Split(policy, ":") 789 790 if len(parts) > 2 { 791 return p, fmt.Errorf("invalid restart policy format") 792 } 793 if len(parts) == 2 { 794 count, err := strconv.Atoi(parts[1]) 795 if err != nil { 796 return p, fmt.Errorf("maximum retry count must be an integer") 797 } 798 799 p.MaximumRetryCount = count 800 } 801 802 p.Name = parts[0] 803 804 return p, nil 805 } 806 807 // ParseDevice parses a device mapping string to a container.DeviceMapping struct 808 func ParseDevice(device string) (container.DeviceMapping, error) { 809 src := "" 810 dst := "" 811 permissions := "rwm" 812 arr := strings.Split(device, ":") 813 switch len(arr) { 814 case 3: 815 permissions = arr[2] 816 fallthrough 817 case 2: 818 if ValidDeviceMode(arr[1]) { 819 permissions = arr[1] 820 } else { 821 dst = arr[1] 822 } 823 fallthrough 824 case 1: 825 src = arr[0] 826 default: 827 return container.DeviceMapping{}, fmt.Errorf("invalid device specification: %s", device) 828 } 829 830 if dst == "" { 831 dst = src 832 } 833 834 deviceMapping := container.DeviceMapping{ 835 PathOnHost: src, 836 PathInContainer: dst, 837 CgroupPermissions: permissions, 838 } 839 return deviceMapping, nil 840 } 841 842 // ParseLink parses and validates the specified string as a link format (name:alias) 843 func ParseLink(val string) (string, string, error) { 844 if val == "" { 845 return "", "", fmt.Errorf("empty string specified for links") 846 } 847 arr := strings.Split(val, ":") 848 if len(arr) > 2 { 849 return "", "", fmt.Errorf("bad format for links: %s", val) 850 } 851 if len(arr) == 1 { 852 return val, val, nil 853 } 854 // This is kept because we can actually get a HostConfig with links 855 // from an already created container and the format is not `foo:bar` 856 // but `/foo:/c1/bar` 857 if strings.HasPrefix(arr[0], "/") { 858 _, alias := path.Split(arr[1]) 859 return arr[0][1:], alias, nil 860 } 861 return arr[0], arr[1], nil 862 } 863 864 // ValidateLink validates that the specified string has a valid link format (containerName:alias). 865 func ValidateLink(val string) (string, error) { 866 if _, _, err := ParseLink(val); err != nil { 867 return val, err 868 } 869 return val, nil 870 } 871 872 // ValidDeviceMode checks if the mode for device is valid or not. 873 // Valid mode is a composition of r (read), w (write), and m (mknod). 874 func ValidDeviceMode(mode string) bool { 875 var legalDeviceMode = map[rune]bool{ 876 'r': true, 877 'w': true, 878 'm': true, 879 } 880 if mode == "" { 881 return false 882 } 883 for _, c := range mode { 884 if !legalDeviceMode[c] { 885 return false 886 } 887 legalDeviceMode[c] = false 888 } 889 return true 890 } 891 892 // ValidateDevice validates a path for devices 893 // It will make sure 'val' is in the form: 894 // [host-dir:]container-path[:mode] 895 // It also validates the device mode. 896 func ValidateDevice(val string) (string, error) { 897 return validatePath(val, ValidDeviceMode) 898 } 899 900 func validatePath(val string, validator func(string) bool) (string, error) { 901 var containerPath string 902 var mode string 903 904 if strings.Count(val, ":") > 2 { 905 return val, fmt.Errorf("bad format for path: %s", val) 906 } 907 908 split := strings.SplitN(val, ":", 3) 909 if split[0] == "" { 910 return val, fmt.Errorf("bad format for path: %s", val) 911 } 912 switch len(split) { 913 case 1: 914 containerPath = split[0] 915 val = path.Clean(containerPath) 916 case 2: 917 if isValid := validator(split[1]); isValid { 918 containerPath = split[0] 919 mode = split[1] 920 val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode) 921 } else { 922 containerPath = split[1] 923 val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath)) 924 } 925 case 3: 926 containerPath = split[1] 927 mode = split[2] 928 if isValid := validator(split[2]); !isValid { 929 return val, fmt.Errorf("bad mode specified: %s", mode) 930 } 931 val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode) 932 } 933 934 if !path.IsAbs(containerPath) { 935 return val, fmt.Errorf("%s is not an absolute path", containerPath) 936 } 937 return val, nil 938 } 939 940 // volumeSplitN splits raw into a maximum of n parts, separated by a separator colon. 941 // A separator colon is the last `:` character in the regex `[:\\]?[a-zA-Z]:` (note `\\` is `\` escaped). 942 // In Windows driver letter appears in two situations: 943 // a. `^[a-zA-Z]:` (A colon followed by `^[a-zA-Z]:` is OK as colon is the separator in volume option) 944 // b. A string in the format like `\\?\C:\Windows\...` (UNC). 945 // Therefore, a driver letter can only follow either a `:` or `\\` 946 // This allows to correctly split strings such as `C:\foo:D:\:rw` or `/tmp/q:/foo`. 947 func volumeSplitN(raw string, n int) []string { 948 var array []string 949 if len(raw) == 0 || raw[0] == ':' { 950 // invalid 951 return nil 952 } 953 // numberOfParts counts the number of parts separated by a separator colon 954 numberOfParts := 0 955 // left represents the left-most cursor in raw, updated at every `:` character considered as a separator. 956 left := 0 957 // right represents the right-most cursor in raw incremented with the loop. Note this 958 // starts at index 1 as index 0 is already handle above as a special case. 959 for right := 1; right < len(raw); right++ { 960 // stop parsing if reached maximum number of parts 961 if n >= 0 && numberOfParts >= n { 962 break 963 } 964 if raw[right] != ':' { 965 continue 966 } 967 potentialDriveLetter := raw[right-1] 968 if (potentialDriveLetter >= 'A' && potentialDriveLetter <= 'Z') || (potentialDriveLetter >= 'a' && potentialDriveLetter <= 'z') { 969 if right > 1 { 970 beforePotentialDriveLetter := raw[right-2] 971 // Only `:` or `\\` are checked (`/` could fall into the case of `/tmp/q:/foo`) 972 if beforePotentialDriveLetter != ':' && beforePotentialDriveLetter != '\\' { 973 // e.g. `C:` is not preceded by any delimiter, therefore it was not a drive letter but a path ending with `C:`. 974 array = append(array, raw[left:right]) 975 left = right + 1 976 numberOfParts++ 977 } 978 // else, `C:` is considered as a drive letter and not as a delimiter, so we continue parsing. 979 } 980 // if right == 1, then `C:` is the beginning of the raw string, therefore `:` is again not considered a delimiter and we continue parsing. 981 } else { 982 // if `:` is not preceded by a potential drive letter, then consider it as a delimiter. 983 array = append(array, raw[left:right]) 984 left = right + 1 985 numberOfParts++ 986 } 987 } 988 // need to take care of the last part 989 if left < len(raw) { 990 if n >= 0 && numberOfParts >= n { 991 // if the maximum number of parts is reached, just append the rest to the last part 992 // left-1 is at the last `:` that needs to be included since not considered a separator. 993 array[n-1] += raw[left-1:] 994 } else { 995 array = append(array, raw[left:]) 996 } 997 } 998 return array 999 }