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