github.com/DaoCloud/dao@v0.0.0-20161212064103-c3dbfd13ee36/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", "附加标准输入、标准输出和标准错误") 149 flags.Var(&copts.flDevices, "device", "为容器添加一个宿主机设备Add") 150 flags.VarP(&copts.flEnv, "env", "e", "设置容器运行时环境变量") 151 flags.Var(&copts.flEnvFile, "env-file", "从一个文件中为容器读取环境变量") 152 flags.StringVar(&copts.flEntrypoint, "entrypoint", "", "覆盖镜像默认的ENTRYPOINT") 153 flags.Var(&copts.flGroupAdd, "group-add", "添加容器加入的额外组") 154 flags.StringVarP(&copts.flHostname, "hostname", "h", "", "容器的主机名") 155 flags.BoolVarP(&copts.flStdin, "interactive", "i", false, "即使不被附加也保持标准输入打开") 156 flags.VarP(&copts.flLabels, "label", "l", "为一个容器上设置元数据") 157 flags.Var(&copts.flLabelsFile, "label-file", "从一个标签文件中读取标签信息") 158 flags.BoolVar(&copts.flReadonlyRootfs, "read-only", false, "将容器的根文件系统挂载为只读模式") 159 flags.StringVar(&copts.flRestartPolicy, "restart", "no", "当一个容器退出时为其采取的重启策略") 160 flags.StringVar(&copts.flStopSignal, "stop-signal", signal.DefaultStopSignal, fmt.Sprintf("停止一个容器的信号, 默认是 %v", signal.DefaultStopSignal)) 161 flags.Var(copts.flSysctls, "sysctl", "系统控制 sysctl 选项") 162 flags.BoolVarP(&copts.flTty, "tty", "t", false, "分配一个伪终端") 163 flags.Var(copts.flUlimits, "ulimit", "用户限制 Ulimit 选项") 164 flags.StringVarP(&copts.flUser, "user", "u", "", "用户名或用户ID (格式: <用户名|用户ID>[:<组|组ID>])") 165 flags.StringVarP(&copts.flWorkingDir, "workdir", "w", "", "进程在容器内部的工作目录") 166 167 // Security 168 flags.Var(&copts.flCapAdd, "cap-add", "添加 Linux 特权") 169 flags.Var(&copts.flCapDrop, "cap-drop", "丢弃 Linux 特权") 170 flags.BoolVar(&copts.flPrivileged, "privileged", false, "授予容器所有的特权") 171 flags.Var(&copts.flSecurityOpt, "security-opt", "安全选项") 172 flags.StringVar(&copts.flUsernsMode, "userns", "", "使用的用户命名空间") 173 174 // Network and port publishing flag 175 flags.Var(&copts.flExtraHosts, "add-host", "为容器添加一个自定义的主机名到IP的映射(主机名:IP)") 176 flags.Var(&copts.flDNS, "dns", "设置自定义的DNS服务器地址") 177 flags.Var(&copts.flDNSOptions, "dns-opt", "设置DNS选项") 178 flags.Var(&copts.flDNSSearch, "dns-search", "设置自定义的DNS搜索域") 179 flags.Var(&copts.flExpose, "expose", "暴露一个或者指定范围的端口") 180 flags.StringVar(&copts.flIPv4Address, "ip", "", "容器的IPv4地址(比如172.30.100.104)") 181 flags.StringVar(&copts.flIPv6Address, "ip6", "", "容器的IPv6地址(比如2001:db8::33)") 182 flags.Var(&copts.flLinks, "link", "添加到另一个容器的连接") 183 flags.Var(&copts.flLinkLocalIPs, "link-local-ip", "容器 IPv4/IPv6 本地连接地址") 184 flags.StringVar(&copts.flMacAddress, "mac-address", "", "容器MAC地址 (e.g. 92:d0:c6:0a:29:33)") 185 flags.VarP(&copts.flPublish, "publish", "p", "将容器内部端口映射到宿主机的指定端口") 186 flags.BoolVarP(&copts.flPublishAll, "publish-all", "P", false, "映射容器内部的所有端口到宿主机上的随机端口") 187 // We allow for both "--net" and "--network", although the latter is the recommended way. 188 flags.StringVar(&copts.flNetMode, "net", "default", "为容器指定网络类型") 189 flags.StringVar(&copts.flNetMode, "network", "default", "为容器指定网络类型") 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", "为容器添加网络范围内的别名") 193 flags.Var(&copts.flAliases, "network-alias", "为容器添加网络范围内的别名") 194 flags.MarkHidden("net-alias") 195 196 // Logging and storage 197 flags.StringVar(&copts.flLoggingDriver, "log-driver", "", "为容器指定日志驱动") 198 flags.StringVar(&copts.flVolumeDriver, "volume-driver", "", "为容器指定可选的存储卷驱动") 199 flags.Var(&copts.flLoggingOpts, "log-opt", "日志驱动选项") 200 flags.Var(&copts.flStorageOpt, "storage-opt", "为容器设置存储驱动选项") 201 flags.Var(&copts.flTmpfs, "tmpfs", "挂载一个临时文件系统目录") 202 flags.Var(&copts.flVolumesFrom, "volumes-from", "从指定的容器挂载存储卷") 203 flags.VarP(&copts.flVolumes, "volume", "v", "绑定挂载一个存储卷") 204 205 // Health-checking 206 flags.StringVar(&copts.flHealthCmd, "health-cmd", "", "运行健康的检查的命令") 207 flags.DurationVar(&copts.flHealthInterval, "health-interval", 0, "运行健康检查的时间间隔") 208 flags.IntVar(&copts.flHealthRetries, "health-retries", 0, "需要汇报不健康的最终错误次数") 209 flags.DurationVar(&copts.flHealthTimeout, "health-timeout", 0, "允许一次健康检查运行的最长时间") 210 flags.BoolVar(&copts.flNoHealthcheck, "no-healthcheck", false, "禁用容器内任何指定的健康检查") 211 212 // Resource management 213 flags.Uint16Var(&copts.flBlkioWeight, "blkio-weight", 0, "磁盘IO设置(相对值),从10到1000") 214 flags.Var(&copts.flBlkioWeightDevice, "blkio-weight-device", "磁盘设备IO设置(相对值),从10到1000") 215 flags.StringVar(&copts.flContainerIDFile, "cidfile", "", "写容器ID的文件路径地址") 216 flags.StringVar(&copts.flCpusetCpus, "cpuset-cpus", "", "允许容器执行的CPU核指定(0-3,0,1): 0-3代表运行运行在0,1,2,3这4个核上") 217 flags.StringVar(&copts.flCpusetMems, "cpuset-mems", "", "允许容器执行的CPU内存所在核指定(0-3,0,1): 0-3代表运行运行在0,1,2,3这4个核上") 218 flags.Int64Var(&copts.flCPUPercent, "cpu-percent", 0, "CPU百分比(只支持Windows)") 219 flags.Int64Var(&copts.flCPUPeriod, "cpu-period", 0, "限制CPU绝对公平调度算法(CFS)的时间周期") 220 flags.Int64Var(&copts.flCPUQuota, "cpu-quota", 0, "限制CPU绝对公平调度算法(CFS)的时间限额") 221 flags.Int64VarP(&copts.flCPUShares, "cpu-shares", "c", 0, "CPU计算资源的值(相对值)") 222 flags.Var(&copts.flDeviceReadBps, "device-read-bps", "限制一个设备的读速率(bps)") 223 flags.Var(&copts.flDeviceReadIOps, "device-read-iops", "限制一个设备的读速率(IOps)") 224 flags.Var(&copts.flDeviceWriteBps, "device-write-bps", "限制一个设备的写速率(bps)") 225 flags.Var(&copts.flDeviceWriteIOps, "device-write-iops", "限制一个设备的写速率(IOps)") 226 flags.StringVar(&copts.flIOMaxBandwidth, "io-maxbandwidth", "", "系统驱动的最大IO带宽限制(只支持Windows)") 227 flags.Uint64Var(&copts.flIOMaxIOps, "io-maxiops", 0, "系统驱动的最大IOps限制(只支持Windows)") 228 flags.StringVar(&copts.flKernelMemory, "kernel-memory", "", "内核内存限制") 229 flags.StringVarP(&copts.flMemoryString, "memory", "m", "", "内存限制") 230 flags.StringVar(&copts.flMemoryReservation, "memory-reservation", "", "内存软限制") 231 flags.StringVar(&copts.flMemorySwap, "memory-swap", "", "交换内存限制 等于 实际内存 + 交换区内存: '-1' 代表启用不受限的交换区内存") 232 flags.Int64Var(&copts.flSwappiness, "memory-swappiness", -1, "设置容器内存swappiness参数 (0 到 100)") 233 flags.BoolVar(&copts.flOomKillDisable, "oom-kill-disable", false, "禁用OOM Killer") 234 flags.IntVar(&copts.flOomScoreAdj, "oom-score-adj", 0, "设置OOM偏好参数 (-1000 至 1000)") 235 flags.Int64Var(&copts.flPidsLimit, "pids-limit", 0, "设置容器进程上限(设置-1代表没有限制)") 236 237 // Low-level execution (cgroups, namespaces, ...) 238 flags.StringVar(&copts.flCgroupParent, "cgroup-parent", "", "为容器设置的可选cgroup父系统") 239 flags.StringVar(&copts.flIpcMode, "ipc", "", "使用的IPC命名空间") 240 flags.StringVar(&copts.flIsolation, "isolation", "", "容器的隔离技术") 241 flags.StringVar(&copts.flPidMode, "pid", "", "使用的PID命名空间") 242 flags.StringVar(&copts.flShmSize, "shm-size", "", "内存共享文件的/dev/shm的大小, 默认值为64MB") 243 flags.StringVar(&copts.flUTSMode, "uts", "", "使用的UTS命名空间") 244 flags.StringVar(&copts.flRuntime, "runtime", "", "为容器选择的容器运行时驱动类型") 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 }