parse.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. package opts
  2. import (
  3. "fmt"
  4. "path"
  5. "strconv"
  6. "strings"
  7. "github.com/docker/docker/opts"
  8. flag "github.com/docker/docker/pkg/mflag"
  9. "github.com/docker/docker/pkg/mount"
  10. "github.com/docker/docker/pkg/signal"
  11. "github.com/docker/engine-api/types/container"
  12. "github.com/docker/engine-api/types/strslice"
  13. "github.com/docker/go-connections/nat"
  14. "github.com/docker/go-units"
  15. )
  16. // Parse parses the specified args for the specified command and generates a Config,
  17. // a HostConfig and returns them with the specified command.
  18. // If the specified args are not valid, it will return an error.
  19. func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) {
  20. var (
  21. // FIXME: use utils.ListOpts for attach and volumes?
  22. flAttach = opts.NewListOpts(ValidateAttach)
  23. flVolumes = opts.NewListOpts(nil)
  24. flTmpfs = opts.NewListOpts(nil)
  25. flBlkioWeightDevice = NewWeightdeviceOpt(ValidateWeightDevice)
  26. flDeviceReadBps = NewThrottledeviceOpt(ValidateThrottleBpsDevice)
  27. flDeviceWriteBps = NewThrottledeviceOpt(ValidateThrottleBpsDevice)
  28. flLinks = opts.NewListOpts(ValidateLink)
  29. flDeviceReadIOps = NewThrottledeviceOpt(ValidateThrottleIOpsDevice)
  30. flDeviceWriteIOps = NewThrottledeviceOpt(ValidateThrottleIOpsDevice)
  31. flEnv = opts.NewListOpts(ValidateEnv)
  32. flLabels = opts.NewListOpts(ValidateEnv)
  33. flDevices = opts.NewListOpts(ValidateDevice)
  34. flUlimits = NewUlimitOpt(nil)
  35. flPublish = opts.NewListOpts(nil)
  36. flExpose = opts.NewListOpts(nil)
  37. flDNS = opts.NewListOpts(opts.ValidateIPAddress)
  38. flDNSSearch = opts.NewListOpts(opts.ValidateDNSSearch)
  39. flDNSOptions = opts.NewListOpts(nil)
  40. flExtraHosts = opts.NewListOpts(ValidateExtraHost)
  41. flVolumesFrom = opts.NewListOpts(nil)
  42. flEnvFile = opts.NewListOpts(nil)
  43. flCapAdd = opts.NewListOpts(nil)
  44. flCapDrop = opts.NewListOpts(nil)
  45. flGroupAdd = opts.NewListOpts(nil)
  46. flSecurityOpt = opts.NewListOpts(nil)
  47. flLabelsFile = opts.NewListOpts(nil)
  48. flLoggingOpts = opts.NewListOpts(nil)
  49. flPrivileged = cmd.Bool([]string{"-privileged"}, false, "Give extended privileges to this container")
  50. flPidMode = cmd.String([]string{"-pid"}, "", "PID namespace to use")
  51. flUTSMode = cmd.String([]string{"-uts"}, "", "UTS namespace to use")
  52. flPublishAll = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to random ports")
  53. flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached")
  54. flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
  55. flOomKillDisable = cmd.Bool([]string{"-oom-kill-disable"}, false, "Disable OOM Killer")
  56. flOomScoreAdj = cmd.Int([]string{"-oom-score-adj"}, 0, "Tune host's OOM preferences (-1000 to 1000)")
  57. flContainerIDFile = cmd.String([]string{"-cidfile"}, "", "Write the container ID to the file")
  58. flEntrypoint = cmd.String([]string{"-entrypoint"}, "", "Overwrite the default ENTRYPOINT of the image")
  59. flHostname = cmd.String([]string{"h", "-hostname"}, "", "Container host name")
  60. flMemoryString = cmd.String([]string{"m", "-memory"}, "", "Memory limit")
  61. flMemoryReservation = cmd.String([]string{"-memory-reservation"}, "", "Memory soft limit")
  62. flMemorySwap = cmd.String([]string{"-memory-swap"}, "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
  63. flKernelMemory = cmd.String([]string{"-kernel-memory"}, "", "Kernel memory limit")
  64. flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: <name|uid>[:<group|gid>])")
  65. flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
  66. flCPUShares = cmd.Int64([]string{"#c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
  67. flCPUPeriod = cmd.Int64([]string{"-cpu-period"}, 0, "Limit CPU CFS (Completely Fair Scheduler) period")
  68. flCPUQuota = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
  69. flCpusetCpus = cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
  70. flCpusetMems = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
  71. flBlkioWeight = cmd.Uint16([]string{"-blkio-weight"}, 0, "Block IO (relative weight), between 10 and 1000")
  72. flSwappiness = cmd.Int64([]string{"-memory-swappiness"}, -1, "Tune container memory swappiness (0 to 100)")
  73. flNetMode = cmd.String([]string{"-net"}, "default", "Connect a container to a network")
  74. flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
  75. flIpcMode = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
  76. flRestartPolicy = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
  77. flReadonlyRootfs = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only")
  78. flLoggingDriver = cmd.String([]string{"-log-driver"}, "", "Logging driver for container")
  79. flCgroupParent = cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
  80. flVolumeDriver = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container")
  81. flStopSignal = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
  82. flIsolation = cmd.String([]string{"-isolation"}, "", "Container isolation level")
  83. flShmSize = cmd.String([]string{"-shm-size"}, "", "Size of /dev/shm, default value is 64MB")
  84. )
  85. cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR")
  86. cmd.Var(&flBlkioWeightDevice, []string{"-blkio-weight-device"}, "Block IO weight (relative device weight)")
  87. cmd.Var(&flDeviceReadBps, []string{"-device-read-bps"}, "Limit read rate (bytes per second) from a device")
  88. cmd.Var(&flDeviceWriteBps, []string{"-device-write-bps"}, "Limit write rate (bytes per second) to a device")
  89. cmd.Var(&flDeviceReadIOps, []string{"-device-read-iops"}, "Limit read rate (IO per second) from a device")
  90. cmd.Var(&flDeviceWriteIOps, []string{"-device-write-iops"}, "Limit write rate (IO per second) to a device")
  91. cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume")
  92. cmd.Var(&flTmpfs, []string{"-tmpfs"}, "Mount a tmpfs directory")
  93. cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
  94. cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container")
  95. cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container")
  96. cmd.Var(&flLabelsFile, []string{"-label-file"}, "Read in a line delimited file of labels")
  97. cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
  98. cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
  99. cmd.Var(&flPublish, []string{"p", "-publish"}, "Publish a container's port(s) to the host")
  100. cmd.Var(&flExpose, []string{"-expose"}, "Expose a port or a range of ports")
  101. cmd.Var(&flDNS, []string{"-dns"}, "Set custom DNS servers")
  102. cmd.Var(&flDNSSearch, []string{"-dns-search"}, "Set custom DNS search domains")
  103. cmd.Var(&flDNSOptions, []string{"-dns-opt"}, "Set DNS options")
  104. cmd.Var(&flExtraHosts, []string{"-add-host"}, "Add a custom host-to-IP mapping (host:ip)")
  105. cmd.Var(&flVolumesFrom, []string{"-volumes-from"}, "Mount volumes from the specified container(s)")
  106. cmd.Var(&flCapAdd, []string{"-cap-add"}, "Add Linux capabilities")
  107. cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capabilities")
  108. cmd.Var(&flGroupAdd, []string{"-group-add"}, "Add additional groups to join")
  109. cmd.Var(&flSecurityOpt, []string{"-security-opt"}, "Security Options")
  110. cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
  111. cmd.Var(&flLoggingOpts, []string{"-log-opt"}, "Log driver options")
  112. cmd.Require(flag.Min, 1)
  113. if err := cmd.ParseFlags(args, true); err != nil {
  114. return nil, nil, cmd, err
  115. }
  116. var (
  117. attachStdin = flAttach.Get("stdin")
  118. attachStdout = flAttach.Get("stdout")
  119. attachStderr = flAttach.Get("stderr")
  120. )
  121. // Validate the input mac address
  122. if *flMacAddress != "" {
  123. if _, err := ValidateMACAddress(*flMacAddress); err != nil {
  124. return nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
  125. }
  126. }
  127. if *flStdin {
  128. attachStdin = true
  129. }
  130. // If -a is not set attach to the output stdio
  131. if flAttach.Len() == 0 {
  132. attachStdout = true
  133. attachStderr = true
  134. }
  135. var err error
  136. var flMemory int64
  137. if *flMemoryString != "" {
  138. flMemory, err = units.RAMInBytes(*flMemoryString)
  139. if err != nil {
  140. return nil, nil, cmd, err
  141. }
  142. }
  143. var MemoryReservation int64
  144. if *flMemoryReservation != "" {
  145. MemoryReservation, err = units.RAMInBytes(*flMemoryReservation)
  146. if err != nil {
  147. return nil, nil, cmd, err
  148. }
  149. }
  150. var memorySwap int64
  151. if *flMemorySwap != "" {
  152. if *flMemorySwap == "-1" {
  153. memorySwap = -1
  154. } else {
  155. memorySwap, err = units.RAMInBytes(*flMemorySwap)
  156. if err != nil {
  157. return nil, nil, cmd, err
  158. }
  159. }
  160. }
  161. var KernelMemory int64
  162. if *flKernelMemory != "" {
  163. KernelMemory, err = units.RAMInBytes(*flKernelMemory)
  164. if err != nil {
  165. return nil, nil, cmd, err
  166. }
  167. }
  168. swappiness := *flSwappiness
  169. if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
  170. return nil, nil, cmd, fmt.Errorf("Invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
  171. }
  172. var shmSize int64
  173. if *flShmSize != "" {
  174. shmSize, err = units.RAMInBytes(*flShmSize)
  175. if err != nil {
  176. return nil, nil, cmd, err
  177. }
  178. }
  179. var binds []string
  180. // add any bind targets to the list of container volumes
  181. for bind := range flVolumes.GetMap() {
  182. if arr := volumeSplitN(bind, 2); len(arr) > 1 {
  183. // after creating the bind mount we want to delete it from the flVolumes values because
  184. // we do not want bind mounts being committed to image configs
  185. binds = append(binds, bind)
  186. flVolumes.Delete(bind)
  187. }
  188. }
  189. // Can't evaluate options passed into --tmpfs until we actually mount
  190. tmpfs := make(map[string]string)
  191. for _, t := range flTmpfs.GetAll() {
  192. if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
  193. if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil {
  194. return nil, nil, cmd, err
  195. }
  196. tmpfs[arr[0]] = arr[1]
  197. } else {
  198. tmpfs[arr[0]] = ""
  199. }
  200. }
  201. var (
  202. parsedArgs = cmd.Args()
  203. runCmd *strslice.StrSlice
  204. entrypoint *strslice.StrSlice
  205. image = cmd.Arg(0)
  206. )
  207. if len(parsedArgs) > 1 {
  208. runCmd = strslice.New(parsedArgs[1:]...)
  209. }
  210. if *flEntrypoint != "" {
  211. entrypoint = strslice.New(*flEntrypoint)
  212. }
  213. var (
  214. domainname string
  215. hostname = *flHostname
  216. parts = strings.SplitN(hostname, ".", 2)
  217. )
  218. if len(parts) > 1 {
  219. hostname = parts[0]
  220. domainname = parts[1]
  221. }
  222. ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll())
  223. if err != nil {
  224. return nil, nil, cmd, err
  225. }
  226. // Merge in exposed ports to the map of published ports
  227. for _, e := range flExpose.GetAll() {
  228. if strings.Contains(e, ":") {
  229. return nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e)
  230. }
  231. //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
  232. proto, port := nat.SplitProtoPort(e)
  233. //parse the start and end port and create a sequence of ports to expose
  234. //if expose a port, the start and end port are the same
  235. start, end, err := nat.ParsePortRange(port)
  236. if err != nil {
  237. return nil, nil, cmd, fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
  238. }
  239. for i := start; i <= end; i++ {
  240. p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
  241. if err != nil {
  242. return nil, nil, cmd, err
  243. }
  244. if _, exists := ports[p]; !exists {
  245. ports[p] = struct{}{}
  246. }
  247. }
  248. }
  249. // parse device mappings
  250. deviceMappings := []container.DeviceMapping{}
  251. for _, device := range flDevices.GetAll() {
  252. deviceMapping, err := ParseDevice(device)
  253. if err != nil {
  254. return nil, nil, cmd, err
  255. }
  256. deviceMappings = append(deviceMappings, deviceMapping)
  257. }
  258. // collect all the environment variables for the container
  259. envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
  260. if err != nil {
  261. return nil, nil, cmd, err
  262. }
  263. // collect all the labels for the container
  264. labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
  265. if err != nil {
  266. return nil, nil, cmd, err
  267. }
  268. ipcMode := container.IpcMode(*flIpcMode)
  269. if !ipcMode.Valid() {
  270. return nil, nil, cmd, fmt.Errorf("--ipc: invalid IPC mode")
  271. }
  272. pidMode := container.PidMode(*flPidMode)
  273. if !pidMode.Valid() {
  274. return nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode")
  275. }
  276. utsMode := container.UTSMode(*flUTSMode)
  277. if !utsMode.Valid() {
  278. return nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode")
  279. }
  280. restartPolicy, err := ParseRestartPolicy(*flRestartPolicy)
  281. if err != nil {
  282. return nil, nil, cmd, err
  283. }
  284. loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll())
  285. if err != nil {
  286. return nil, nil, cmd, err
  287. }
  288. resources := container.Resources{
  289. CgroupParent: *flCgroupParent,
  290. Memory: flMemory,
  291. MemoryReservation: MemoryReservation,
  292. MemorySwap: memorySwap,
  293. MemorySwappiness: flSwappiness,
  294. KernelMemory: KernelMemory,
  295. OomKillDisable: *flOomKillDisable,
  296. CPUShares: *flCPUShares,
  297. CPUPeriod: *flCPUPeriod,
  298. CpusetCpus: *flCpusetCpus,
  299. CpusetMems: *flCpusetMems,
  300. CPUQuota: *flCPUQuota,
  301. BlkioWeight: *flBlkioWeight,
  302. BlkioWeightDevice: flBlkioWeightDevice.GetList(),
  303. BlkioDeviceReadBps: flDeviceReadBps.GetList(),
  304. BlkioDeviceWriteBps: flDeviceWriteBps.GetList(),
  305. BlkioDeviceReadIOps: flDeviceReadIOps.GetList(),
  306. BlkioDeviceWriteIOps: flDeviceWriteIOps.GetList(),
  307. Ulimits: flUlimits.GetList(),
  308. Devices: deviceMappings,
  309. }
  310. config := &container.Config{
  311. Hostname: hostname,
  312. Domainname: domainname,
  313. ExposedPorts: ports,
  314. User: *flUser,
  315. Tty: *flTty,
  316. // TODO: deprecated, it comes from -n, --networking
  317. // it's still needed internally to set the network to disabled
  318. // if e.g. bridge is none in daemon opts, and in inspect
  319. NetworkDisabled: false,
  320. OpenStdin: *flStdin,
  321. AttachStdin: attachStdin,
  322. AttachStdout: attachStdout,
  323. AttachStderr: attachStderr,
  324. Env: envVariables,
  325. Cmd: runCmd,
  326. Image: image,
  327. Volumes: flVolumes.GetMap(),
  328. MacAddress: *flMacAddress,
  329. Entrypoint: entrypoint,
  330. WorkingDir: *flWorkingDir,
  331. Labels: ConvertKVStringsToMap(labels),
  332. StopSignal: *flStopSignal,
  333. }
  334. hostConfig := &container.HostConfig{
  335. Binds: binds,
  336. ContainerIDFile: *flContainerIDFile,
  337. OomScoreAdj: *flOomScoreAdj,
  338. Privileged: *flPrivileged,
  339. PortBindings: portBindings,
  340. Links: flLinks.GetAll(),
  341. PublishAllPorts: *flPublishAll,
  342. // Make sure the dns fields are never nil.
  343. // New containers don't ever have those fields nil,
  344. // but pre created containers can still have those nil values.
  345. // See https://github.com/docker/docker/pull/17779
  346. // for a more detailed explanation on why we don't want that.
  347. DNS: flDNS.GetAllOrEmpty(),
  348. DNSSearch: flDNSSearch.GetAllOrEmpty(),
  349. DNSOptions: flDNSOptions.GetAllOrEmpty(),
  350. ExtraHosts: flExtraHosts.GetAll(),
  351. VolumesFrom: flVolumesFrom.GetAll(),
  352. NetworkMode: container.NetworkMode(*flNetMode),
  353. IpcMode: ipcMode,
  354. PidMode: pidMode,
  355. UTSMode: utsMode,
  356. CapAdd: strslice.New(flCapAdd.GetAll()...),
  357. CapDrop: strslice.New(flCapDrop.GetAll()...),
  358. GroupAdd: flGroupAdd.GetAll(),
  359. RestartPolicy: restartPolicy,
  360. SecurityOpt: flSecurityOpt.GetAll(),
  361. ReadonlyRootfs: *flReadonlyRootfs,
  362. LogConfig: container.LogConfig{Type: *flLoggingDriver, Config: loggingOpts},
  363. VolumeDriver: *flVolumeDriver,
  364. Isolation: container.IsolationLevel(*flIsolation),
  365. ShmSize: shmSize,
  366. Resources: resources,
  367. Tmpfs: tmpfs,
  368. }
  369. // When allocating stdin in attached mode, close stdin at client disconnect
  370. if config.OpenStdin && config.AttachStdin {
  371. config.StdinOnce = true
  372. }
  373. return config, hostConfig, cmd, nil
  374. }
  375. // reads a file of line terminated key=value pairs and override that with override parameter
  376. func readKVStrings(files []string, override []string) ([]string, error) {
  377. envVariables := []string{}
  378. for _, ef := range files {
  379. parsedVars, err := ParseEnvFile(ef)
  380. if err != nil {
  381. return nil, err
  382. }
  383. envVariables = append(envVariables, parsedVars...)
  384. }
  385. // parse the '-e' and '--env' after, to allow override
  386. envVariables = append(envVariables, override...)
  387. return envVariables, nil
  388. }
  389. // ConvertKVStringsToMap converts ["key=value"] to {"key":"value"}
  390. func ConvertKVStringsToMap(values []string) map[string]string {
  391. result := make(map[string]string, len(values))
  392. for _, value := range values {
  393. kv := strings.SplitN(value, "=", 2)
  394. if len(kv) == 1 {
  395. result[kv[0]] = ""
  396. } else {
  397. result[kv[0]] = kv[1]
  398. }
  399. }
  400. return result
  401. }
  402. func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
  403. loggingOptsMap := ConvertKVStringsToMap(loggingOpts)
  404. if loggingDriver == "none" && len(loggingOpts) > 0 {
  405. return map[string]string{}, fmt.Errorf("Invalid logging opts for driver %s", loggingDriver)
  406. }
  407. return loggingOptsMap, nil
  408. }
  409. // ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect
  410. func ParseRestartPolicy(policy string) (container.RestartPolicy, error) {
  411. p := container.RestartPolicy{}
  412. if policy == "" {
  413. return p, nil
  414. }
  415. var (
  416. parts = strings.Split(policy, ":")
  417. name = parts[0]
  418. )
  419. p.Name = name
  420. switch name {
  421. case "always", "unless-stopped":
  422. if len(parts) > 1 {
  423. return p, fmt.Errorf("maximum restart count not valid with restart policy of \"%s\"", name)
  424. }
  425. case "no":
  426. // do nothing
  427. case "on-failure":
  428. if len(parts) > 2 {
  429. return p, fmt.Errorf("restart count format is not valid, usage: 'on-failure:N' or 'on-failure'")
  430. }
  431. if len(parts) == 2 {
  432. count, err := strconv.Atoi(parts[1])
  433. if err != nil {
  434. return p, err
  435. }
  436. p.MaximumRetryCount = count
  437. }
  438. default:
  439. return p, fmt.Errorf("invalid restart policy %s", name)
  440. }
  441. return p, nil
  442. }
  443. // ParseDevice parses a device mapping string to a container.DeviceMapping struct
  444. func ParseDevice(device string) (container.DeviceMapping, error) {
  445. src := ""
  446. dst := ""
  447. permissions := "rwm"
  448. arr := strings.Split(device, ":")
  449. switch len(arr) {
  450. case 3:
  451. permissions = arr[2]
  452. fallthrough
  453. case 2:
  454. if ValidDeviceMode(arr[1]) {
  455. permissions = arr[1]
  456. } else {
  457. dst = arr[1]
  458. }
  459. fallthrough
  460. case 1:
  461. src = arr[0]
  462. default:
  463. return container.DeviceMapping{}, fmt.Errorf("Invalid device specification: %s", device)
  464. }
  465. if dst == "" {
  466. dst = src
  467. }
  468. deviceMapping := container.DeviceMapping{
  469. PathOnHost: src,
  470. PathInContainer: dst,
  471. CgroupPermissions: permissions,
  472. }
  473. return deviceMapping, nil
  474. }
  475. // ParseLink parses and validates the specified string as a link format (name:alias)
  476. func ParseLink(val string) (string, string, error) {
  477. if val == "" {
  478. return "", "", fmt.Errorf("empty string specified for links")
  479. }
  480. arr := strings.Split(val, ":")
  481. if len(arr) > 2 {
  482. return "", "", fmt.Errorf("bad format for links: %s", val)
  483. }
  484. if len(arr) == 1 {
  485. return val, val, nil
  486. }
  487. // This is kept because we can actually get an HostConfig with links
  488. // from an already created container and the format is not `foo:bar`
  489. // but `/foo:/c1/bar`
  490. if strings.HasPrefix(arr[0], "/") {
  491. _, alias := path.Split(arr[1])
  492. return arr[0][1:], alias, nil
  493. }
  494. return arr[0], arr[1], nil
  495. }
  496. // ValidateLink validates that the specified string has a valid link format (containerName:alias).
  497. func ValidateLink(val string) (string, error) {
  498. if _, _, err := ParseLink(val); err != nil {
  499. return val, err
  500. }
  501. return val, nil
  502. }
  503. // ValidDeviceMode checks if the mode for device is valid or not.
  504. // Valid mode is a composition of r (read), w (write), and m (mknod).
  505. func ValidDeviceMode(mode string) bool {
  506. var legalDeviceMode = map[rune]bool{
  507. 'r': true,
  508. 'w': true,
  509. 'm': true,
  510. }
  511. if mode == "" {
  512. return false
  513. }
  514. for _, c := range mode {
  515. if !legalDeviceMode[c] {
  516. return false
  517. }
  518. legalDeviceMode[c] = false
  519. }
  520. return true
  521. }
  522. // ValidateDevice validates a path for devices
  523. // It will make sure 'val' is in the form:
  524. // [host-dir:]container-path[:mode]
  525. // It also validates the device mode.
  526. func ValidateDevice(val string) (string, error) {
  527. return validatePath(val, ValidDeviceMode)
  528. }
  529. func validatePath(val string, validator func(string) bool) (string, error) {
  530. var containerPath string
  531. var mode string
  532. if strings.Count(val, ":") > 2 {
  533. return val, fmt.Errorf("bad format for path: %s", val)
  534. }
  535. split := strings.SplitN(val, ":", 3)
  536. if split[0] == "" {
  537. return val, fmt.Errorf("bad format for path: %s", val)
  538. }
  539. switch len(split) {
  540. case 1:
  541. containerPath = split[0]
  542. val = path.Clean(containerPath)
  543. case 2:
  544. if isValid := validator(split[1]); isValid {
  545. containerPath = split[0]
  546. mode = split[1]
  547. val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode)
  548. } else {
  549. containerPath = split[1]
  550. val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath))
  551. }
  552. case 3:
  553. containerPath = split[1]
  554. mode = split[2]
  555. if isValid := validator(split[2]); !isValid {
  556. return val, fmt.Errorf("bad mode specified: %s", mode)
  557. }
  558. val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode)
  559. }
  560. if !path.IsAbs(containerPath) {
  561. return val, fmt.Errorf("%s is not an absolute path", containerPath)
  562. }
  563. return val, nil
  564. }
  565. // SplitN splits raw into a maximum of n parts, separated by a separator colon.
  566. // A separator colon is the last `:` character in the regex `[/:\\]?[a-zA-Z]:` (note `\\` is `\` escaped).
  567. // This allows to correctly split strings such as `C:\foo:D:\:rw`.
  568. func volumeSplitN(raw string, n int) []string {
  569. var array []string
  570. if len(raw) == 0 || raw[0] == ':' {
  571. // invalid
  572. return nil
  573. }
  574. // numberOfParts counts the number of parts separated by a separator colon
  575. numberOfParts := 0
  576. // left represents the left-most cursor in raw, updated at every `:` character considered as a separator.
  577. left := 0
  578. // right represents the right-most cursor in raw incremented with the loop. Note this
  579. // starts at index 1 as index 0 is already handle above as a special case.
  580. for right := 1; right < len(raw); right++ {
  581. // stop parsing if reached maximum number of parts
  582. if n >= 0 && numberOfParts >= n {
  583. break
  584. }
  585. if raw[right] != ':' {
  586. continue
  587. }
  588. potentialDriveLetter := raw[right-1]
  589. if (potentialDriveLetter >= 'A' && potentialDriveLetter <= 'Z') || (potentialDriveLetter >= 'a' && potentialDriveLetter <= 'z') {
  590. if right > 1 {
  591. beforePotentialDriveLetter := raw[right-2]
  592. if beforePotentialDriveLetter != ':' && beforePotentialDriveLetter != '/' && beforePotentialDriveLetter != '\\' {
  593. // e.g. `C:` is not preceded by any delimiter, therefore it was not a drive letter but a path ending with `C:`.
  594. array = append(array, raw[left:right])
  595. left = right + 1
  596. numberOfParts++
  597. }
  598. // else, `C:` is considered as a drive letter and not as a delimiter, so we continue parsing.
  599. }
  600. // if right == 1, then `C:` is the beginning of the raw string, therefore `:` is again not considered a delimiter and we continue parsing.
  601. } else {
  602. // if `:` is not preceded by a potential drive letter, then consider it as a delimiter.
  603. array = append(array, raw[left:right])
  604. left = right + 1
  605. numberOfParts++
  606. }
  607. }
  608. // need to take care of the last part
  609. if left < len(raw) {
  610. if n >= 0 && numberOfParts >= n {
  611. // if the maximum number of parts is reached, just append the rest to the last part
  612. // left-1 is at the last `:` that needs to be included since not considered a separator.
  613. array[n-1] += raw[left-1:]
  614. } else {
  615. array = append(array, raw[left:])
  616. }
  617. }
  618. return array
  619. }