github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/container/opts.go (about) 1 package container 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "os" 8 "path" 9 "path/filepath" 10 "reflect" 11 "regexp" 12 "strconv" 13 "strings" 14 "time" 15 16 "github.com/docker/cli/cli/command" 17 "github.com/docker/cli/cli/compose/loader" 18 "github.com/docker/cli/opts" 19 "github.com/docker/docker/api/types/container" 20 mounttypes "github.com/docker/docker/api/types/mount" 21 networktypes "github.com/docker/docker/api/types/network" 22 "github.com/docker/docker/api/types/strslice" 23 "github.com/docker/docker/errdefs" 24 "github.com/docker/go-connections/nat" 25 "github.com/pkg/errors" 26 "github.com/sirupsen/logrus" 27 "github.com/spf13/pflag" 28 cdi "tags.cncf.io/container-device-interface/pkg/parser" 29 ) 30 31 const ( 32 // TODO(thaJeztah): define these in the API-types, or query available defaults 33 // from the daemon, or require "local" profiles to be an absolute path or 34 // relative paths starting with "./". The daemon-config has consts for this 35 // but we don't want to import that package: 36 // https://github.com/moby/moby/blob/v23.0.0/daemon/config/config.go#L63-L67 37 38 // seccompProfileDefault is the built-in default seccomp profile. 39 seccompProfileDefault = "builtin" 40 // seccompProfileUnconfined is a special profile name for seccomp to use an 41 // "unconfined" seccomp profile. 42 seccompProfileUnconfined = "unconfined" 43 ) 44 45 var deviceCgroupRuleRegexp = regexp.MustCompile(`^[acb] ([0-9]+|\*):([0-9]+|\*) [rwm]{1,3}$`) 46 47 // containerOptions is a data object with all the options for creating a container 48 type containerOptions struct { 49 attach opts.ListOpts 50 volumes opts.ListOpts 51 tmpfs opts.ListOpts 52 mounts opts.MountOpt 53 blkioWeightDevice opts.WeightdeviceOpt 54 deviceReadBps opts.ThrottledeviceOpt 55 deviceWriteBps opts.ThrottledeviceOpt 56 links opts.ListOpts 57 aliases opts.ListOpts 58 linkLocalIPs opts.ListOpts 59 deviceReadIOps opts.ThrottledeviceOpt 60 deviceWriteIOps opts.ThrottledeviceOpt 61 env opts.ListOpts 62 labels opts.ListOpts 63 deviceCgroupRules opts.ListOpts 64 devices opts.ListOpts 65 gpus opts.GpuOpts 66 ulimits *opts.UlimitOpt 67 sysctls *opts.MapOpts 68 publish opts.ListOpts 69 expose opts.ListOpts 70 dns opts.ListOpts 71 dnsSearch opts.ListOpts 72 dnsOptions opts.ListOpts 73 extraHosts opts.ListOpts 74 volumesFrom opts.ListOpts 75 envFile opts.ListOpts 76 capAdd opts.ListOpts 77 capDrop opts.ListOpts 78 groupAdd opts.ListOpts 79 securityOpt opts.ListOpts 80 storageOpt opts.ListOpts 81 labelsFile opts.ListOpts 82 loggingOpts opts.ListOpts 83 privileged bool 84 pidMode string 85 utsMode string 86 usernsMode string 87 cgroupnsMode string 88 publishAll bool 89 stdin bool 90 tty bool 91 oomKillDisable bool 92 oomScoreAdj int 93 containerIDFile string 94 entrypoint string 95 hostname string 96 domainname string 97 memory opts.MemBytes 98 memoryReservation opts.MemBytes 99 memorySwap opts.MemSwapBytes 100 kernelMemory opts.MemBytes 101 user string 102 workingDir string 103 cpuCount int64 104 cpuShares int64 105 cpuPercent int64 106 cpuPeriod int64 107 cpuRealtimePeriod int64 108 cpuRealtimeRuntime int64 109 cpuQuota int64 110 cpus opts.NanoCPUs 111 cpusetCpus string 112 cpusetMems string 113 blkioWeight uint16 114 ioMaxBandwidth opts.MemBytes 115 ioMaxIOps uint64 116 swappiness int64 117 netMode opts.NetworkOpt 118 macAddress string 119 ipv4Address string 120 ipv6Address string 121 ipcMode string 122 pidsLimit int64 123 restartPolicy string 124 readonlyRootfs bool 125 loggingDriver string 126 cgroupParent string 127 volumeDriver string 128 stopSignal string 129 stopTimeout int 130 isolation string 131 shmSize opts.MemBytes 132 noHealthcheck bool 133 healthCmd string 134 healthInterval time.Duration 135 healthTimeout time.Duration 136 healthStartPeriod time.Duration 137 healthStartInterval time.Duration 138 healthRetries int 139 runtime string 140 autoRemove bool 141 init bool 142 annotations *opts.MapOpts 143 144 Image string 145 Args []string 146 } 147 148 // addFlags adds all command line flags that will be used by parse to the FlagSet 149 func addFlags(flags *pflag.FlagSet) *containerOptions { 150 copts := &containerOptions{ 151 aliases: opts.NewListOpts(nil), 152 attach: opts.NewListOpts(validateAttach), 153 blkioWeightDevice: opts.NewWeightdeviceOpt(opts.ValidateWeightDevice), 154 capAdd: opts.NewListOpts(nil), 155 capDrop: opts.NewListOpts(nil), 156 dns: opts.NewListOpts(opts.ValidateIPAddress), 157 dnsOptions: opts.NewListOpts(nil), 158 dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch), 159 deviceCgroupRules: opts.NewListOpts(validateDeviceCgroupRule), 160 deviceReadBps: opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice), 161 deviceReadIOps: opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice), 162 deviceWriteBps: opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice), 163 deviceWriteIOps: opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice), 164 devices: opts.NewListOpts(nil), // devices can only be validated after we know the server OS 165 env: opts.NewListOpts(opts.ValidateEnv), 166 envFile: opts.NewListOpts(nil), 167 expose: opts.NewListOpts(nil), 168 extraHosts: opts.NewListOpts(opts.ValidateExtraHost), 169 groupAdd: opts.NewListOpts(nil), 170 labels: opts.NewListOpts(opts.ValidateLabel), 171 labelsFile: opts.NewListOpts(nil), 172 linkLocalIPs: opts.NewListOpts(nil), 173 links: opts.NewListOpts(opts.ValidateLink), 174 loggingOpts: opts.NewListOpts(nil), 175 publish: opts.NewListOpts(nil), 176 securityOpt: opts.NewListOpts(nil), 177 storageOpt: opts.NewListOpts(nil), 178 sysctls: opts.NewMapOpts(nil, opts.ValidateSysctl), 179 tmpfs: opts.NewListOpts(nil), 180 ulimits: opts.NewUlimitOpt(nil), 181 volumes: opts.NewListOpts(nil), 182 volumesFrom: opts.NewListOpts(nil), 183 annotations: opts.NewMapOpts(nil, nil), 184 } 185 186 // General purpose flags 187 flags.VarP(&copts.attach, "attach", "a", "Attach to STDIN, STDOUT or STDERR") 188 flags.Var(&copts.deviceCgroupRules, "device-cgroup-rule", "Add a rule to the cgroup allowed devices list") 189 flags.Var(&copts.devices, "device", "Add a host device to the container") 190 flags.Var(&copts.gpus, "gpus", "GPU devices to add to the container ('all' to pass all GPUs)") 191 flags.SetAnnotation("gpus", "version", []string{"1.40"}) 192 flags.VarP(&copts.env, "env", "e", "Set environment variables") 193 flags.Var(&copts.envFile, "env-file", "Read in a file of environment variables") 194 flags.StringVar(&copts.entrypoint, "entrypoint", "", "Overwrite the default ENTRYPOINT of the image") 195 flags.Var(&copts.groupAdd, "group-add", "Add additional groups to join") 196 flags.StringVarP(&copts.hostname, "hostname", "h", "", "Container host name") 197 flags.StringVar(&copts.domainname, "domainname", "", "Container NIS domain name") 198 flags.BoolVarP(&copts.stdin, "interactive", "i", false, "Keep STDIN open even if not attached") 199 flags.VarP(&copts.labels, "label", "l", "Set meta data on a container") 200 flags.Var(&copts.labelsFile, "label-file", "Read in a line delimited file of labels") 201 flags.BoolVar(&copts.readonlyRootfs, "read-only", false, "Mount the container's root filesystem as read only") 202 flags.StringVar(&copts.restartPolicy, "restart", string(container.RestartPolicyDisabled), "Restart policy to apply when a container exits") 203 flags.StringVar(&copts.stopSignal, "stop-signal", "", "Signal to stop the container") 204 flags.IntVar(&copts.stopTimeout, "stop-timeout", 0, "Timeout (in seconds) to stop a container") 205 flags.SetAnnotation("stop-timeout", "version", []string{"1.25"}) 206 flags.Var(copts.sysctls, "sysctl", "Sysctl options") 207 flags.BoolVarP(&copts.tty, "tty", "t", false, "Allocate a pseudo-TTY") 208 flags.Var(copts.ulimits, "ulimit", "Ulimit options") 209 flags.StringVarP(&copts.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])") 210 flags.StringVarP(&copts.workingDir, "workdir", "w", "", "Working directory inside the container") 211 flags.BoolVar(&copts.autoRemove, "rm", false, "Automatically remove the container when it exits") 212 213 // Security 214 flags.Var(&copts.capAdd, "cap-add", "Add Linux capabilities") 215 flags.Var(&copts.capDrop, "cap-drop", "Drop Linux capabilities") 216 flags.BoolVar(&copts.privileged, "privileged", false, "Give extended privileges to this container") 217 flags.Var(&copts.securityOpt, "security-opt", "Security Options") 218 flags.StringVar(&copts.usernsMode, "userns", "", "User namespace to use") 219 flags.StringVar(&copts.cgroupnsMode, "cgroupns", "", `Cgroup namespace to use (host|private) 220 'host': Run the container in the Docker host's cgroup namespace 221 'private': Run the container in its own private cgroup namespace 222 '': Use the cgroup namespace as configured by the 223 default-cgroupns-mode option on the daemon (default)`) 224 flags.SetAnnotation("cgroupns", "version", []string{"1.41"}) 225 226 // Network and port publishing flag 227 flags.Var(&copts.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)") 228 flags.Var(&copts.dns, "dns", "Set custom DNS servers") 229 // We allow for both "--dns-opt" and "--dns-option", although the latter is the recommended way. 230 // This is to be consistent with service create/update 231 flags.Var(&copts.dnsOptions, "dns-opt", "Set DNS options") 232 flags.Var(&copts.dnsOptions, "dns-option", "Set DNS options") 233 flags.MarkHidden("dns-opt") 234 flags.Var(&copts.dnsSearch, "dns-search", "Set custom DNS search domains") 235 flags.Var(&copts.expose, "expose", "Expose a port or a range of ports") 236 flags.StringVar(&copts.ipv4Address, "ip", "", "IPv4 address (e.g., 172.30.100.104)") 237 flags.StringVar(&copts.ipv6Address, "ip6", "", "IPv6 address (e.g., 2001:db8::33)") 238 flags.Var(&copts.links, "link", "Add link to another container") 239 flags.Var(&copts.linkLocalIPs, "link-local-ip", "Container IPv4/IPv6 link-local addresses") 240 flags.StringVar(&copts.macAddress, "mac-address", "", "Container MAC address (e.g., 92:d0:c6:0a:29:33)") 241 flags.VarP(&copts.publish, "publish", "p", "Publish a container's port(s) to the host") 242 flags.BoolVarP(&copts.publishAll, "publish-all", "P", false, "Publish all exposed ports to random ports") 243 // We allow for both "--net" and "--network", although the latter is the recommended way. 244 flags.Var(&copts.netMode, "net", "Connect a container to a network") 245 flags.Var(&copts.netMode, "network", "Connect a container to a network") 246 flags.MarkHidden("net") 247 // We allow for both "--net-alias" and "--network-alias", although the latter is the recommended way. 248 flags.Var(&copts.aliases, "net-alias", "Add network-scoped alias for the container") 249 flags.Var(&copts.aliases, "network-alias", "Add network-scoped alias for the container") 250 flags.MarkHidden("net-alias") 251 252 // Logging and storage 253 flags.StringVar(&copts.loggingDriver, "log-driver", "", "Logging driver for the container") 254 flags.StringVar(&copts.volumeDriver, "volume-driver", "", "Optional volume driver for the container") 255 flags.Var(&copts.loggingOpts, "log-opt", "Log driver options") 256 flags.Var(&copts.storageOpt, "storage-opt", "Storage driver options for the container") 257 flags.Var(&copts.tmpfs, "tmpfs", "Mount a tmpfs directory") 258 flags.Var(&copts.volumesFrom, "volumes-from", "Mount volumes from the specified container(s)") 259 flags.VarP(&copts.volumes, "volume", "v", "Bind mount a volume") 260 flags.Var(&copts.mounts, "mount", "Attach a filesystem mount to the container") 261 262 // Health-checking 263 flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health") 264 flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check (ms|s|m|h) (default 0s)") 265 flags.IntVar(&copts.healthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy") 266 flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ms|s|m|h) (default 0s)") 267 flags.DurationVar(&copts.healthStartPeriod, "health-start-period", 0, "Start period for the container to initialize before starting health-retries countdown (ms|s|m|h) (default 0s)") 268 flags.SetAnnotation("health-start-period", "version", []string{"1.29"}) 269 flags.DurationVar(&copts.healthStartInterval, "health-start-interval", 0, "Time between running the check during the start period (ms|s|m|h) (default 0s)") 270 flags.SetAnnotation("health-start-interval", "version", []string{"1.44"}) 271 flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK") 272 273 // Resource management 274 flags.Uint16Var(&copts.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)") 275 flags.Var(&copts.blkioWeightDevice, "blkio-weight-device", "Block IO weight (relative device weight)") 276 flags.StringVar(&copts.containerIDFile, "cidfile", "", "Write the container ID to the file") 277 flags.StringVar(&copts.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)") 278 flags.StringVar(&copts.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)") 279 flags.Int64Var(&copts.cpuCount, "cpu-count", 0, "CPU count (Windows only)") 280 flags.SetAnnotation("cpu-count", "ostype", []string{"windows"}) 281 flags.Int64Var(&copts.cpuPercent, "cpu-percent", 0, "CPU percent (Windows only)") 282 flags.SetAnnotation("cpu-percent", "ostype", []string{"windows"}) 283 flags.Int64Var(&copts.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period") 284 flags.Int64Var(&copts.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota") 285 flags.Int64Var(&copts.cpuRealtimePeriod, "cpu-rt-period", 0, "Limit CPU real-time period in microseconds") 286 flags.SetAnnotation("cpu-rt-period", "version", []string{"1.25"}) 287 flags.Int64Var(&copts.cpuRealtimeRuntime, "cpu-rt-runtime", 0, "Limit CPU real-time runtime in microseconds") 288 flags.SetAnnotation("cpu-rt-runtime", "version", []string{"1.25"}) 289 flags.Int64VarP(&copts.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)") 290 flags.Var(&copts.cpus, "cpus", "Number of CPUs") 291 flags.SetAnnotation("cpus", "version", []string{"1.25"}) 292 flags.Var(&copts.deviceReadBps, "device-read-bps", "Limit read rate (bytes per second) from a device") 293 flags.Var(&copts.deviceReadIOps, "device-read-iops", "Limit read rate (IO per second) from a device") 294 flags.Var(&copts.deviceWriteBps, "device-write-bps", "Limit write rate (bytes per second) to a device") 295 flags.Var(&copts.deviceWriteIOps, "device-write-iops", "Limit write rate (IO per second) to a device") 296 flags.Var(&copts.ioMaxBandwidth, "io-maxbandwidth", "Maximum IO bandwidth limit for the system drive (Windows only)") 297 flags.SetAnnotation("io-maxbandwidth", "ostype", []string{"windows"}) 298 flags.Uint64Var(&copts.ioMaxIOps, "io-maxiops", 0, "Maximum IOps limit for the system drive (Windows only)") 299 flags.SetAnnotation("io-maxiops", "ostype", []string{"windows"}) 300 flags.Var(&copts.kernelMemory, "kernel-memory", "Kernel memory limit") 301 flags.VarP(&copts.memory, "memory", "m", "Memory limit") 302 flags.Var(&copts.memoryReservation, "memory-reservation", "Memory soft limit") 303 flags.Var(&copts.memorySwap, "memory-swap", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap") 304 flags.Int64Var(&copts.swappiness, "memory-swappiness", -1, "Tune container memory swappiness (0 to 100)") 305 flags.BoolVar(&copts.oomKillDisable, "oom-kill-disable", false, "Disable OOM Killer") 306 flags.IntVar(&copts.oomScoreAdj, "oom-score-adj", 0, "Tune host's OOM preferences (-1000 to 1000)") 307 flags.Int64Var(&copts.pidsLimit, "pids-limit", 0, "Tune container pids limit (set -1 for unlimited)") 308 309 // Low-level execution (cgroups, namespaces, ...) 310 flags.StringVar(&copts.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container") 311 flags.StringVar(&copts.ipcMode, "ipc", "", "IPC mode to use") 312 flags.StringVar(&copts.isolation, "isolation", "", "Container isolation technology") 313 flags.StringVar(&copts.pidMode, "pid", "", "PID namespace to use") 314 flags.Var(&copts.shmSize, "shm-size", "Size of /dev/shm") 315 flags.StringVar(&copts.utsMode, "uts", "", "UTS namespace to use") 316 flags.StringVar(&copts.runtime, "runtime", "", "Runtime to use for this container") 317 318 flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes") 319 flags.SetAnnotation("init", "version", []string{"1.25"}) 320 321 flags.Var(copts.annotations, "annotation", "Add an annotation to the container (passed through to the OCI runtime)") 322 flags.SetAnnotation("annotation", "version", []string{"1.43"}) 323 324 return copts 325 } 326 327 type containerConfig struct { 328 Config *container.Config 329 HostConfig *container.HostConfig 330 NetworkingConfig *networktypes.NetworkingConfig 331 } 332 333 // parse parses the args for the specified command and generates a Config, 334 // a HostConfig and returns them with the specified command. 335 // If the specified args are not valid, it will return an error. 336 // 337 //nolint:gocyclo 338 func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*containerConfig, error) { 339 var ( 340 attachStdin = copts.attach.Get("stdin") 341 attachStdout = copts.attach.Get("stdout") 342 attachStderr = copts.attach.Get("stderr") 343 ) 344 345 // Validate the input mac address 346 if copts.macAddress != "" { 347 if _, err := opts.ValidateMACAddress(copts.macAddress); err != nil { 348 return nil, errors.Errorf("%s is not a valid mac address", copts.macAddress) 349 } 350 } 351 if copts.stdin { 352 attachStdin = true 353 } 354 // If -a is not set, attach to stdout and stderr 355 if copts.attach.Len() == 0 { 356 attachStdout = true 357 attachStderr = true 358 } 359 360 var err error 361 362 swappiness := copts.swappiness 363 if swappiness != -1 && (swappiness < 0 || swappiness > 100) { 364 return nil, errors.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness) 365 } 366 367 mounts := copts.mounts.Value() 368 if len(mounts) > 0 && copts.volumeDriver != "" { 369 logrus.Warn("`--volume-driver` is ignored for volumes specified via `--mount`. Use `--mount type=volume,volume-driver=...` instead.") 370 } 371 var binds []string 372 volumes := copts.volumes.GetMap() 373 // add any bind targets to the list of container volumes 374 for bind := range copts.volumes.GetMap() { 375 parsed, err := loader.ParseVolume(bind) 376 if err != nil { 377 return nil, err 378 } 379 380 if parsed.Source != "" { 381 toBind := bind 382 383 if parsed.Type == string(mounttypes.TypeBind) { 384 if hostPart, targetPath, ok := strings.Cut(bind, ":"); ok { 385 if strings.HasPrefix(hostPart, "."+string(filepath.Separator)) || hostPart == "." { 386 if absHostPart, err := filepath.Abs(hostPart); err == nil { 387 hostPart = absHostPart 388 } 389 } 390 toBind = hostPart + ":" + targetPath 391 } 392 } 393 394 // after creating the bind mount we want to delete it from the copts.volumes values because 395 // we do not want bind mounts being committed to image configs 396 binds = append(binds, toBind) 397 // We should delete from the map (`volumes`) here, as deleting from copts.volumes will not work if 398 // there are duplicates entries. 399 delete(volumes, bind) 400 } 401 } 402 403 // Can't evaluate options passed into --tmpfs until we actually mount 404 tmpfs := make(map[string]string) 405 for _, t := range copts.tmpfs.GetAll() { 406 k, v, _ := strings.Cut(t, ":") 407 tmpfs[k] = v 408 } 409 410 var ( 411 runCmd strslice.StrSlice 412 entrypoint strslice.StrSlice 413 ) 414 415 if len(copts.Args) > 0 { 416 runCmd = copts.Args 417 } 418 419 if copts.entrypoint != "" { 420 entrypoint = strslice.StrSlice{copts.entrypoint} 421 } else if flags.Changed("entrypoint") { 422 // if `--entrypoint=` is parsed then Entrypoint is reset 423 entrypoint = []string{""} 424 } 425 426 publishOpts := copts.publish.GetAll() 427 var ( 428 ports map[nat.Port]struct{} 429 portBindings map[nat.Port][]nat.PortBinding 430 convertedOpts []string 431 ) 432 433 convertedOpts, err = convertToStandardNotation(publishOpts) 434 if err != nil { 435 return nil, err 436 } 437 438 ports, portBindings, err = nat.ParsePortSpecs(convertedOpts) 439 if err != nil { 440 return nil, err 441 } 442 443 // Merge in exposed ports to the map of published ports 444 for _, e := range copts.expose.GetAll() { 445 if strings.Contains(e, ":") { 446 return nil, errors.Errorf("invalid port format for --expose: %s", e) 447 } 448 // support two formats for expose, original format <portnum>/[<proto>] 449 // or <startport-endport>/[<proto>] 450 proto, port := nat.SplitProtoPort(e) 451 // parse the start and end port and create a sequence of ports to expose 452 // if expose a port, the start and end port are the same 453 start, end, err := nat.ParsePortRange(port) 454 if err != nil { 455 return nil, errors.Errorf("invalid range format for --expose: %s, error: %s", e, err) 456 } 457 for i := start; i <= end; i++ { 458 p, err := nat.NewPort(proto, strconv.FormatUint(i, 10)) 459 if err != nil { 460 return nil, err 461 } 462 if _, exists := ports[p]; !exists { 463 ports[p] = struct{}{} 464 } 465 } 466 } 467 468 // validate and parse device mappings. Note we do late validation of the 469 // device path (as opposed to during flag parsing), as at the time we are 470 // parsing flags, we haven't yet sent a _ping to the daemon to determine 471 // what operating system it is. 472 deviceMappings := []container.DeviceMapping{} 473 var cdiDeviceNames []string 474 for _, device := range copts.devices.GetAll() { 475 var ( 476 validated string 477 deviceMapping container.DeviceMapping 478 err error 479 ) 480 if cdi.IsQualifiedName(device) { 481 cdiDeviceNames = append(cdiDeviceNames, device) 482 continue 483 } 484 validated, err = validateDevice(device, serverOS) 485 if err != nil { 486 return nil, err 487 } 488 deviceMapping, err = parseDevice(validated, serverOS) 489 if err != nil { 490 return nil, err 491 } 492 deviceMappings = append(deviceMappings, deviceMapping) 493 } 494 495 // collect all the environment variables for the container 496 envVariables, err := opts.ReadKVEnvStrings(copts.envFile.GetAll(), copts.env.GetAll()) 497 if err != nil { 498 return nil, err 499 } 500 501 // collect all the labels for the container 502 labels, err := opts.ReadKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll()) 503 if err != nil { 504 return nil, err 505 } 506 507 pidMode := container.PidMode(copts.pidMode) 508 if !pidMode.Valid() { 509 return nil, errors.Errorf("--pid: invalid PID mode") 510 } 511 512 utsMode := container.UTSMode(copts.utsMode) 513 if !utsMode.Valid() { 514 return nil, errors.Errorf("--uts: invalid UTS mode") 515 } 516 517 usernsMode := container.UsernsMode(copts.usernsMode) 518 if !usernsMode.Valid() { 519 return nil, errors.Errorf("--userns: invalid USER mode") 520 } 521 522 cgroupnsMode := container.CgroupnsMode(copts.cgroupnsMode) 523 if !cgroupnsMode.Valid() { 524 return nil, errors.Errorf("--cgroupns: invalid CGROUP mode") 525 } 526 527 restartPolicy, err := opts.ParseRestartPolicy(copts.restartPolicy) 528 if err != nil { 529 return nil, err 530 } 531 532 loggingOpts, err := parseLoggingOpts(copts.loggingDriver, copts.loggingOpts.GetAll()) 533 if err != nil { 534 return nil, err 535 } 536 537 securityOpts, err := parseSecurityOpts(copts.securityOpt.GetAll()) 538 if err != nil { 539 return nil, err 540 } 541 542 securityOpts, maskedPaths, readonlyPaths := parseSystemPaths(securityOpts) 543 544 storageOpts, err := parseStorageOpts(copts.storageOpt.GetAll()) 545 if err != nil { 546 return nil, err 547 } 548 549 // Healthcheck 550 var healthConfig *container.HealthConfig 551 haveHealthSettings := copts.healthCmd != "" || 552 copts.healthInterval != 0 || 553 copts.healthTimeout != 0 || 554 copts.healthStartPeriod != 0 || 555 copts.healthRetries != 0 || 556 copts.healthStartInterval != 0 557 if copts.noHealthcheck { 558 if haveHealthSettings { 559 return nil, errors.Errorf("--no-healthcheck conflicts with --health-* options") 560 } 561 healthConfig = &container.HealthConfig{Test: strslice.StrSlice{"NONE"}} 562 } else if haveHealthSettings { 563 var probe strslice.StrSlice 564 if copts.healthCmd != "" { 565 probe = []string{"CMD-SHELL", copts.healthCmd} 566 } 567 if copts.healthInterval < 0 { 568 return nil, errors.Errorf("--health-interval cannot be negative") 569 } 570 if copts.healthTimeout < 0 { 571 return nil, errors.Errorf("--health-timeout cannot be negative") 572 } 573 if copts.healthRetries < 0 { 574 return nil, errors.Errorf("--health-retries cannot be negative") 575 } 576 if copts.healthStartPeriod < 0 { 577 return nil, fmt.Errorf("--health-start-period cannot be negative") 578 } 579 if copts.healthStartInterval < 0 { 580 return nil, fmt.Errorf("--health-start-interval cannot be negative") 581 } 582 583 healthConfig = &container.HealthConfig{ 584 Test: probe, 585 Interval: copts.healthInterval, 586 Timeout: copts.healthTimeout, 587 StartPeriod: copts.healthStartPeriod, 588 StartInterval: copts.healthStartInterval, 589 Retries: copts.healthRetries, 590 } 591 } 592 593 deviceRequests := copts.gpus.Value() 594 if len(cdiDeviceNames) > 0 { 595 cdiDeviceRequest := container.DeviceRequest{ 596 Driver: "cdi", 597 DeviceIDs: cdiDeviceNames, 598 } 599 deviceRequests = append(deviceRequests, cdiDeviceRequest) 600 } 601 602 resources := container.Resources{ 603 CgroupParent: copts.cgroupParent, 604 Memory: copts.memory.Value(), 605 MemoryReservation: copts.memoryReservation.Value(), 606 MemorySwap: copts.memorySwap.Value(), 607 MemorySwappiness: &copts.swappiness, 608 KernelMemory: copts.kernelMemory.Value(), 609 OomKillDisable: &copts.oomKillDisable, 610 NanoCPUs: copts.cpus.Value(), 611 CPUCount: copts.cpuCount, 612 CPUPercent: copts.cpuPercent, 613 CPUShares: copts.cpuShares, 614 CPUPeriod: copts.cpuPeriod, 615 CpusetCpus: copts.cpusetCpus, 616 CpusetMems: copts.cpusetMems, 617 CPUQuota: copts.cpuQuota, 618 CPURealtimePeriod: copts.cpuRealtimePeriod, 619 CPURealtimeRuntime: copts.cpuRealtimeRuntime, 620 PidsLimit: &copts.pidsLimit, 621 BlkioWeight: copts.blkioWeight, 622 BlkioWeightDevice: copts.blkioWeightDevice.GetList(), 623 BlkioDeviceReadBps: copts.deviceReadBps.GetList(), 624 BlkioDeviceWriteBps: copts.deviceWriteBps.GetList(), 625 BlkioDeviceReadIOps: copts.deviceReadIOps.GetList(), 626 BlkioDeviceWriteIOps: copts.deviceWriteIOps.GetList(), 627 IOMaximumIOps: copts.ioMaxIOps, 628 IOMaximumBandwidth: uint64(copts.ioMaxBandwidth), 629 Ulimits: copts.ulimits.GetList(), 630 DeviceCgroupRules: copts.deviceCgroupRules.GetAll(), 631 Devices: deviceMappings, 632 DeviceRequests: deviceRequests, 633 } 634 635 config := &container.Config{ 636 Hostname: copts.hostname, 637 Domainname: copts.domainname, 638 ExposedPorts: ports, 639 User: copts.user, 640 Tty: copts.tty, 641 OpenStdin: copts.stdin, 642 AttachStdin: attachStdin, 643 AttachStdout: attachStdout, 644 AttachStderr: attachStderr, 645 Env: envVariables, 646 Cmd: runCmd, 647 Image: copts.Image, 648 Volumes: volumes, 649 MacAddress: copts.macAddress, 650 Entrypoint: entrypoint, 651 WorkingDir: copts.workingDir, 652 Labels: opts.ConvertKVStringsToMap(labels), 653 StopSignal: copts.stopSignal, 654 Healthcheck: healthConfig, 655 } 656 if flags.Changed("stop-timeout") { 657 config.StopTimeout = &copts.stopTimeout 658 } 659 660 hostConfig := &container.HostConfig{ 661 Binds: binds, 662 ContainerIDFile: copts.containerIDFile, 663 OomScoreAdj: copts.oomScoreAdj, 664 AutoRemove: copts.autoRemove, 665 Privileged: copts.privileged, 666 PortBindings: portBindings, 667 Links: copts.links.GetAll(), 668 PublishAllPorts: copts.publishAll, 669 // Make sure the dns fields are never nil. 670 // New containers don't ever have those fields nil, 671 // but pre created containers can still have those nil values. 672 // See https://github.com/docker/docker/pull/17779 673 // for a more detailed explanation on why we don't want that. 674 DNS: copts.dns.GetAllOrEmpty(), 675 DNSSearch: copts.dnsSearch.GetAllOrEmpty(), 676 DNSOptions: copts.dnsOptions.GetAllOrEmpty(), 677 ExtraHosts: copts.extraHosts.GetAll(), 678 VolumesFrom: copts.volumesFrom.GetAll(), 679 IpcMode: container.IpcMode(copts.ipcMode), 680 NetworkMode: container.NetworkMode(copts.netMode.NetworkMode()), 681 PidMode: pidMode, 682 UTSMode: utsMode, 683 UsernsMode: usernsMode, 684 CgroupnsMode: cgroupnsMode, 685 CapAdd: strslice.StrSlice(copts.capAdd.GetAll()), 686 CapDrop: strslice.StrSlice(copts.capDrop.GetAll()), 687 GroupAdd: copts.groupAdd.GetAll(), 688 RestartPolicy: restartPolicy, 689 SecurityOpt: securityOpts, 690 StorageOpt: storageOpts, 691 ReadonlyRootfs: copts.readonlyRootfs, 692 LogConfig: container.LogConfig{Type: copts.loggingDriver, Config: loggingOpts}, 693 VolumeDriver: copts.volumeDriver, 694 Isolation: container.Isolation(copts.isolation), 695 ShmSize: copts.shmSize.Value(), 696 Resources: resources, 697 Tmpfs: tmpfs, 698 Sysctls: copts.sysctls.GetAll(), 699 Runtime: copts.runtime, 700 Mounts: mounts, 701 MaskedPaths: maskedPaths, 702 ReadonlyPaths: readonlyPaths, 703 Annotations: copts.annotations.GetAll(), 704 } 705 706 if copts.autoRemove && !hostConfig.RestartPolicy.IsNone() { 707 return nil, errors.Errorf("Conflicting options: --restart and --rm") 708 } 709 710 // only set this value if the user provided the flag, else it should default to nil 711 if flags.Changed("init") { 712 hostConfig.Init = &copts.init 713 } 714 715 // When allocating stdin in attached mode, close stdin at client disconnect 716 if config.OpenStdin && config.AttachStdin { 717 config.StdinOnce = true 718 } 719 720 networkingConfig := &networktypes.NetworkingConfig{ 721 EndpointsConfig: make(map[string]*networktypes.EndpointSettings), 722 } 723 724 networkingConfig.EndpointsConfig, err = parseNetworkOpts(copts) 725 if err != nil { 726 return nil, err 727 } 728 729 // Put the endpoint-specific MacAddress of the "main" network attachment into the container Config for backward 730 // compatibility with older daemons. 731 if nw, ok := networkingConfig.EndpointsConfig[hostConfig.NetworkMode.NetworkName()]; ok { 732 config.MacAddress = nw.MacAddress //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44. 733 } 734 735 return &containerConfig{ 736 Config: config, 737 HostConfig: hostConfig, 738 NetworkingConfig: networkingConfig, 739 }, nil 740 } 741 742 // parseNetworkOpts converts --network advanced options to endpoint-specs, and combines 743 // them with the old --network-alias and --links. If returns an error if conflicting options 744 // are found. 745 // 746 // this function may return _multiple_ endpoints, which is not currently supported 747 // by the daemon, but may be in future; it's up to the daemon to produce an error 748 // in case that is not supported. 749 func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.EndpointSettings, error) { 750 var ( 751 endpoints = make(map[string]*networktypes.EndpointSettings, len(copts.netMode.Value())) 752 hasUserDefined, hasNonUserDefined bool 753 ) 754 755 if len(copts.netMode.Value()) == 0 { 756 n := opts.NetworkAttachmentOpts{ 757 Target: "default", 758 } 759 if err := applyContainerOptions(&n, copts); err != nil { 760 return nil, err 761 } 762 ep, err := parseNetworkAttachmentOpt(n) 763 if err != nil { 764 return nil, err 765 } 766 endpoints["default"] = ep 767 } 768 769 for i, n := range copts.netMode.Value() { 770 n := n 771 if container.NetworkMode(n.Target).IsUserDefined() { 772 hasUserDefined = true 773 } else { 774 hasNonUserDefined = true 775 } 776 if i == 0 { 777 // The first network corresponds with what was previously the "only" 778 // network, and what would be used when using the non-advanced syntax 779 // `--network-alias`, `--link`, `--ip`, `--ip6`, and `--link-local-ip` 780 // are set on this network, to preserve backward compatibility with 781 // the non-advanced notation 782 if err := applyContainerOptions(&n, copts); err != nil { 783 return nil, err 784 } 785 } 786 ep, err := parseNetworkAttachmentOpt(n) 787 if err != nil { 788 return nil, err 789 } 790 if _, ok := endpoints[n.Target]; ok { 791 return nil, errdefs.InvalidParameter(errors.Errorf("network %q is specified multiple times", n.Target)) 792 } 793 794 // For backward compatibility: if no custom options are provided for the network, 795 // and only a single network is specified, omit the endpoint-configuration 796 // on the client (the daemon will still create it when creating the container) 797 if i == 0 && len(copts.netMode.Value()) == 1 { 798 if ep == nil || reflect.DeepEqual(*ep, networktypes.EndpointSettings{}) { 799 continue 800 } 801 } 802 endpoints[n.Target] = ep 803 } 804 if hasUserDefined && hasNonUserDefined { 805 return nil, errdefs.InvalidParameter(errors.New("conflicting options: cannot attach both user-defined and non-user-defined network-modes")) 806 } 807 return endpoints, nil 808 } 809 810 func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOptions) error { //nolint:gocyclo 811 // TODO should we error if _any_ advanced option is used? (i.e. forbid to combine advanced notation with the "old" flags (`--network-alias`, `--link`, `--ip`, `--ip6`)? 812 if len(n.Aliases) > 0 && copts.aliases.Len() > 0 { 813 return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --network-alias and per-network alias")) 814 } 815 if len(n.Links) > 0 && copts.links.Len() > 0 { 816 return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --link and per-network links")) 817 } 818 if n.IPv4Address != "" && copts.ipv4Address != "" { 819 return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --ip and per-network IPv4 address")) 820 } 821 if n.IPv6Address != "" && copts.ipv6Address != "" { 822 return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --ip6 and per-network IPv6 address")) 823 } 824 if n.MacAddress != "" && copts.macAddress != "" { 825 return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --mac-address and per-network MAC address")) 826 } 827 if len(n.LinkLocalIPs) > 0 && copts.linkLocalIPs.Len() > 0 { 828 return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --link-local-ip and per-network link-local IP addresses")) 829 } 830 if copts.aliases.Len() > 0 { 831 n.Aliases = make([]string, copts.aliases.Len()) 832 copy(n.Aliases, copts.aliases.GetAll()) 833 } 834 if n.Target != "default" && copts.links.Len() > 0 { 835 n.Links = make([]string, copts.links.Len()) 836 copy(n.Links, copts.links.GetAll()) 837 } 838 if copts.ipv4Address != "" { 839 n.IPv4Address = copts.ipv4Address 840 } 841 if copts.ipv6Address != "" { 842 n.IPv6Address = copts.ipv6Address 843 } 844 if copts.macAddress != "" { 845 n.MacAddress = copts.macAddress 846 } 847 if copts.linkLocalIPs.Len() > 0 { 848 n.LinkLocalIPs = make([]string, copts.linkLocalIPs.Len()) 849 copy(n.LinkLocalIPs, copts.linkLocalIPs.GetAll()) 850 } 851 return nil 852 } 853 854 func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.EndpointSettings, error) { 855 if strings.TrimSpace(ep.Target) == "" { 856 return nil, errors.New("no name set for network") 857 } 858 if !container.NetworkMode(ep.Target).IsUserDefined() { 859 if len(ep.Aliases) > 0 { 860 return nil, errors.New("network-scoped aliases are only supported for user-defined networks") 861 } 862 if len(ep.Links) > 0 { 863 return nil, errors.New("links are only supported for user-defined networks") 864 } 865 } 866 867 epConfig := &networktypes.EndpointSettings{} 868 epConfig.Aliases = append(epConfig.Aliases, ep.Aliases...) 869 if len(ep.DriverOpts) > 0 { 870 epConfig.DriverOpts = make(map[string]string) 871 epConfig.DriverOpts = ep.DriverOpts 872 } 873 if len(ep.Links) > 0 { 874 epConfig.Links = ep.Links 875 } 876 if ep.IPv4Address != "" || ep.IPv6Address != "" || len(ep.LinkLocalIPs) > 0 { 877 epConfig.IPAMConfig = &networktypes.EndpointIPAMConfig{ 878 IPv4Address: ep.IPv4Address, 879 IPv6Address: ep.IPv6Address, 880 LinkLocalIPs: ep.LinkLocalIPs, 881 } 882 } 883 if ep.MacAddress != "" { 884 if _, err := opts.ValidateMACAddress(ep.MacAddress); err != nil { 885 return nil, errors.Errorf("%s is not a valid mac address", ep.MacAddress) 886 } 887 epConfig.MacAddress = ep.MacAddress 888 } 889 return epConfig, nil 890 } 891 892 func convertToStandardNotation(ports []string) ([]string, error) { 893 optsList := []string{} 894 for _, publish := range ports { 895 if strings.Contains(publish, "=") { 896 params := map[string]string{"protocol": "tcp"} 897 for _, param := range strings.Split(publish, ",") { 898 k, v, ok := strings.Cut(param, "=") 899 if !ok || k == "" { 900 return optsList, errors.Errorf("invalid publish opts format (should be name=value but got '%s')", param) 901 } 902 params[k] = v 903 } 904 optsList = append(optsList, fmt.Sprintf("%s:%s/%s", params["published"], params["target"], params["protocol"])) 905 } else { 906 optsList = append(optsList, publish) 907 } 908 } 909 return optsList, nil 910 } 911 912 func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) { 913 loggingOptsMap := opts.ConvertKVStringsToMap(loggingOpts) 914 if loggingDriver == "none" && len(loggingOpts) > 0 { 915 return map[string]string{}, errors.Errorf("invalid logging opts for driver %s", loggingDriver) 916 } 917 return loggingOptsMap, nil 918 } 919 920 // takes a local seccomp daemon, reads the file contents for sending to the daemon 921 func parseSecurityOpts(securityOpts []string) ([]string, error) { 922 for key, opt := range securityOpts { 923 k, v, ok := strings.Cut(opt, "=") 924 if !ok && k != "no-new-privileges" { 925 k, v, ok = strings.Cut(opt, ":") 926 } 927 if (!ok || v == "") && k != "no-new-privileges" { 928 // "no-new-privileges" is the only option that does not require a value. 929 return securityOpts, errors.Errorf("Invalid --security-opt: %q", opt) 930 } 931 if k == "seccomp" { 932 switch v { 933 case seccompProfileDefault, seccompProfileUnconfined: 934 // known special names for built-in profiles, nothing to do. 935 default: 936 // value may be a filename, in which case we send the profile's 937 // content if it's valid JSON. 938 f, err := os.ReadFile(v) 939 if err != nil { 940 return securityOpts, errors.Errorf("opening seccomp profile (%s) failed: %v", v, err) 941 } 942 b := bytes.NewBuffer(nil) 943 if err := json.Compact(b, f); err != nil { 944 return securityOpts, errors.Errorf("compacting json for seccomp profile (%s) failed: %v", v, err) 945 } 946 securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes()) 947 } 948 } 949 } 950 951 return securityOpts, nil 952 } 953 954 // parseSystemPaths checks if `systempaths=unconfined` security option is set, 955 // and returns the `MaskedPaths` and `ReadonlyPaths` accordingly. An updated 956 // list of security options is returned with this option removed, because the 957 // `unconfined` option is handled client-side, and should not be sent to the 958 // daemon. 959 func parseSystemPaths(securityOpts []string) (filtered, maskedPaths, readonlyPaths []string) { 960 filtered = securityOpts[:0] 961 for _, opt := range securityOpts { 962 if opt == "systempaths=unconfined" { 963 maskedPaths = []string{} 964 readonlyPaths = []string{} 965 } else { 966 filtered = append(filtered, opt) 967 } 968 } 969 970 return filtered, maskedPaths, readonlyPaths 971 } 972 973 // parses storage options per container into a map 974 func parseStorageOpts(storageOpts []string) (map[string]string, error) { 975 m := make(map[string]string) 976 for _, option := range storageOpts { 977 k, v, ok := strings.Cut(option, "=") 978 if !ok { 979 return nil, errors.Errorf("invalid storage option") 980 } 981 m[k] = v 982 } 983 return m, nil 984 } 985 986 // parseDevice parses a device mapping string to a container.DeviceMapping struct 987 func parseDevice(device, serverOS string) (container.DeviceMapping, error) { 988 switch serverOS { 989 case "linux": 990 return parseLinuxDevice(device) 991 case "windows": 992 return parseWindowsDevice(device) 993 } 994 return container.DeviceMapping{}, errors.Errorf("unknown server OS: %s", serverOS) 995 } 996 997 // parseLinuxDevice parses a device mapping string to a container.DeviceMapping struct 998 // knowing that the target is a Linux daemon 999 func parseLinuxDevice(device string) (container.DeviceMapping, error) { 1000 var src, dst string 1001 permissions := "rwm" 1002 // We expect 3 parts at maximum; limit to 4 parts to detect invalid options. 1003 arr := strings.SplitN(device, ":", 4) 1004 switch len(arr) { 1005 case 3: 1006 permissions = arr[2] 1007 fallthrough 1008 case 2: 1009 if validDeviceMode(arr[1]) { 1010 permissions = arr[1] 1011 } else { 1012 dst = arr[1] 1013 } 1014 fallthrough 1015 case 1: 1016 src = arr[0] 1017 default: 1018 return container.DeviceMapping{}, errors.Errorf("invalid device specification: %s", device) 1019 } 1020 1021 if dst == "" { 1022 dst = src 1023 } 1024 1025 deviceMapping := container.DeviceMapping{ 1026 PathOnHost: src, 1027 PathInContainer: dst, 1028 CgroupPermissions: permissions, 1029 } 1030 return deviceMapping, nil 1031 } 1032 1033 // parseWindowsDevice parses a device mapping string to a container.DeviceMapping struct 1034 // knowing that the target is a Windows daemon 1035 func parseWindowsDevice(device string) (container.DeviceMapping, error) { 1036 return container.DeviceMapping{PathOnHost: device}, nil 1037 } 1038 1039 // validateDeviceCgroupRule validates a device cgroup rule string format 1040 // It will make sure 'val' is in the form: 1041 // 1042 // 'type major:minor mode' 1043 func validateDeviceCgroupRule(val string) (string, error) { 1044 if deviceCgroupRuleRegexp.MatchString(val) { 1045 return val, nil 1046 } 1047 1048 return val, errors.Errorf("invalid device cgroup format '%s'", val) 1049 } 1050 1051 // validDeviceMode checks if the mode for device is valid or not. 1052 // Valid mode is a composition of r (read), w (write), and m (mknod). 1053 func validDeviceMode(mode string) bool { 1054 legalDeviceMode := map[rune]bool{ 1055 'r': true, 1056 'w': true, 1057 'm': true, 1058 } 1059 if mode == "" { 1060 return false 1061 } 1062 for _, c := range mode { 1063 if !legalDeviceMode[c] { 1064 return false 1065 } 1066 legalDeviceMode[c] = false 1067 } 1068 return true 1069 } 1070 1071 // validateDevice validates a path for devices 1072 func validateDevice(val string, serverOS string) (string, error) { 1073 switch serverOS { 1074 case "linux": 1075 return validateLinuxPath(val, validDeviceMode) 1076 case "windows": 1077 // Windows does validation entirely server-side 1078 return val, nil 1079 } 1080 return "", errors.Errorf("unknown server OS: %s", serverOS) 1081 } 1082 1083 // validateLinuxPath is the implementation of validateDevice knowing that the 1084 // target server operating system is a Linux daemon. 1085 // It will make sure 'val' is in the form: 1086 // 1087 // [host-dir:]container-path[:mode] 1088 // 1089 // It also validates the device mode. 1090 func validateLinuxPath(val string, validator func(string) bool) (string, error) { 1091 var containerPath string 1092 var mode string 1093 1094 if strings.Count(val, ":") > 2 { 1095 return val, errors.Errorf("bad format for path: %s", val) 1096 } 1097 1098 split := strings.SplitN(val, ":", 3) 1099 if split[0] == "" { 1100 return val, errors.Errorf("bad format for path: %s", val) 1101 } 1102 switch len(split) { 1103 case 1: 1104 containerPath = split[0] 1105 val = path.Clean(containerPath) 1106 case 2: 1107 if isValid := validator(split[1]); isValid { 1108 containerPath = split[0] 1109 mode = split[1] 1110 val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode) 1111 } else { 1112 containerPath = split[1] 1113 val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath)) 1114 } 1115 case 3: 1116 containerPath = split[1] 1117 mode = split[2] 1118 if isValid := validator(split[2]); !isValid { 1119 return val, errors.Errorf("bad mode specified: %s", mode) 1120 } 1121 val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode) 1122 } 1123 1124 if !path.IsAbs(containerPath) { 1125 return val, errors.Errorf("%s is not an absolute path", containerPath) 1126 } 1127 return val, nil 1128 } 1129 1130 // validateAttach validates that the specified string is a valid attach option. 1131 func validateAttach(val string) (string, error) { 1132 s := strings.ToLower(val) 1133 for _, str := range []string{"stdin", "stdout", "stderr"} { 1134 if s == str { 1135 return s, nil 1136 } 1137 } 1138 return val, errors.Errorf("valid streams are STDIN, STDOUT and STDERR") 1139 } 1140 1141 func validateAPIVersion(c *containerConfig, serverAPIVersion string) error { 1142 for _, m := range c.HostConfig.Mounts { 1143 if err := command.ValidateMountWithAPIVersion(m, serverAPIVersion); err != nil { 1144 return err 1145 } 1146 } 1147 return nil 1148 }