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