parsers.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. // Package parsers provides helper functions to parse and validate different type
  2. // of string. It can be hosts, unix addresses, tcp addresses, filters, kernel
  3. // operating system versions.
  4. package parsers
  5. import (
  6. "fmt"
  7. "net"
  8. "net/url"
  9. "path"
  10. "runtime"
  11. "strconv"
  12. "strings"
  13. )
  14. // ParseDockerDaemonHost parses the specified address and returns an address that will be used as the host.
  15. // Depending of the address specified, will use the defaultTCPAddr or defaultUnixAddr
  16. // defaultUnixAddr must be a absolute file path (no `unix://` prefix)
  17. // defaultTCPAddr must be the full `tcp://host:port` form
  18. func ParseDockerDaemonHost(defaultTCPAddr, defaultUnixAddr, addr string) (string, error) {
  19. addr = strings.TrimSpace(addr)
  20. if addr == "" {
  21. if runtime.GOOS != "windows" {
  22. return fmt.Sprintf("unix://%s", defaultUnixAddr), nil
  23. }
  24. return defaultTCPAddr, nil
  25. }
  26. addrParts := strings.Split(addr, "://")
  27. if len(addrParts) == 1 {
  28. addrParts = []string{"tcp", addrParts[0]}
  29. }
  30. switch addrParts[0] {
  31. case "tcp":
  32. return ParseTCPAddr(addrParts[1], defaultTCPAddr)
  33. case "unix":
  34. return ParseUnixAddr(addrParts[1], defaultUnixAddr)
  35. case "fd":
  36. return addr, nil
  37. default:
  38. return "", fmt.Errorf("Invalid bind address format: %s", addr)
  39. }
  40. }
  41. // ParseUnixAddr parses and validates that the specified address is a valid UNIX
  42. // socket address. It returns a formatted UNIX socket address, either using the
  43. // address parsed from addr, or the contents of defaultAddr if addr is a blank
  44. // string.
  45. func ParseUnixAddr(addr string, defaultAddr string) (string, error) {
  46. addr = strings.TrimPrefix(addr, "unix://")
  47. if strings.Contains(addr, "://") {
  48. return "", fmt.Errorf("Invalid proto, expected unix: %s", addr)
  49. }
  50. if addr == "" {
  51. addr = defaultAddr
  52. }
  53. return fmt.Sprintf("unix://%s", addr), nil
  54. }
  55. // ParseTCPAddr parses and validates that the specified address is a valid TCP
  56. // address. It returns a formatted TCP address, either using the address parsed
  57. // from tryAddr, or the contents of defaultAddr if tryAddr is a blank string.
  58. // tryAddr is expected to have already been Trim()'d
  59. // defaultAddr must be in the full `tcp://host:port` form
  60. func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) {
  61. if tryAddr == "" || tryAddr == "tcp://" {
  62. return defaultAddr, nil
  63. }
  64. addr := strings.TrimPrefix(tryAddr, "tcp://")
  65. if strings.Contains(addr, "://") || addr == "" {
  66. return "", fmt.Errorf("Invalid proto, expected tcp: %s", tryAddr)
  67. }
  68. u, err := url.Parse("tcp://" + addr)
  69. if err != nil {
  70. return "", err
  71. }
  72. host, port, err := net.SplitHostPort(u.Host)
  73. if err != nil {
  74. return "", fmt.Errorf("Invalid bind address format: %s", tryAddr)
  75. }
  76. defaultAddr = strings.TrimPrefix(defaultAddr, "tcp://")
  77. defaultHost, defaultPort, err := net.SplitHostPort(defaultAddr)
  78. if err != nil {
  79. return "", err
  80. }
  81. if host == "" {
  82. host = defaultHost
  83. }
  84. if port == "" {
  85. port = defaultPort
  86. }
  87. p, err := strconv.Atoi(port)
  88. if err != nil && p == 0 {
  89. return "", fmt.Errorf("Invalid bind address format: %s", tryAddr)
  90. }
  91. if net.ParseIP(host).To4() == nil && strings.Contains(host, ":") {
  92. // This is either an ipv6 address
  93. host = "[" + host + "]"
  94. }
  95. return fmt.Sprintf("tcp://%s:%d%s", host, p, u.Path), nil
  96. }
  97. // ParseRepositoryTag gets a repos name and returns the right reposName + tag|digest
  98. // The tag can be confusing because of a port in a repository name.
  99. // Ex: localhost.localdomain:5000/samalba/hipache:latest
  100. // Digest ex: localhost:5000/foo/bar@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb
  101. func ParseRepositoryTag(repos string) (string, string) {
  102. n := strings.Index(repos, "@")
  103. if n >= 0 {
  104. parts := strings.Split(repos, "@")
  105. return parts[0], parts[1]
  106. }
  107. n = strings.LastIndex(repos, ":")
  108. if n < 0 {
  109. return repos, ""
  110. }
  111. if tag := repos[n+1:]; !strings.Contains(tag, "/") {
  112. return repos[:n], tag
  113. }
  114. return repos, ""
  115. }
  116. // PartParser parses and validates the specified string (data) using the specified template
  117. // e.g. ip:public:private -> 192.168.0.1:80:8000
  118. func PartParser(template, data string) (map[string]string, error) {
  119. // ip:public:private
  120. var (
  121. templateParts = strings.Split(template, ":")
  122. parts = strings.Split(data, ":")
  123. out = make(map[string]string, len(templateParts))
  124. )
  125. if len(parts) != len(templateParts) {
  126. return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template)
  127. }
  128. for i, t := range templateParts {
  129. value := ""
  130. if len(parts) > i {
  131. value = parts[i]
  132. }
  133. out[t] = value
  134. }
  135. return out, nil
  136. }
  137. // ParseKeyValueOpt parses and validates the specified string as a key/value pair (key=value)
  138. func ParseKeyValueOpt(opt string) (string, string, error) {
  139. parts := strings.SplitN(opt, "=", 2)
  140. if len(parts) != 2 {
  141. return "", "", fmt.Errorf("Unable to parse key/value option: %s", opt)
  142. }
  143. return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
  144. }
  145. // ParsePortRange parses and validates the specified string as a port-range (8000-9000)
  146. func ParsePortRange(ports string) (uint64, uint64, error) {
  147. if ports == "" {
  148. return 0, 0, fmt.Errorf("Empty string specified for ports.")
  149. }
  150. if !strings.Contains(ports, "-") {
  151. start, err := strconv.ParseUint(ports, 10, 16)
  152. end := start
  153. return start, end, err
  154. }
  155. parts := strings.Split(ports, "-")
  156. start, err := strconv.ParseUint(parts[0], 10, 16)
  157. if err != nil {
  158. return 0, 0, err
  159. }
  160. end, err := strconv.ParseUint(parts[1], 10, 16)
  161. if err != nil {
  162. return 0, 0, err
  163. }
  164. if end < start {
  165. return 0, 0, fmt.Errorf("Invalid range specified for the Port: %s", ports)
  166. }
  167. return start, end, nil
  168. }
  169. // ParseLink parses and validates the specified string as a link format (name:alias)
  170. func ParseLink(val string) (string, string, error) {
  171. if val == "" {
  172. return "", "", fmt.Errorf("empty string specified for links")
  173. }
  174. arr := strings.Split(val, ":")
  175. if len(arr) > 2 {
  176. return "", "", fmt.Errorf("bad format for links: %s", val)
  177. }
  178. if len(arr) == 1 {
  179. return val, val, nil
  180. }
  181. // This is kept because we can actually get an HostConfig with links
  182. // from an already created container and the format is not `foo:bar`
  183. // but `/foo:/c1/bar`
  184. if strings.HasPrefix(arr[0], "/") {
  185. _, alias := path.Split(arr[1])
  186. return arr[0][1:], alias, nil
  187. }
  188. return arr[0], arr[1], nil
  189. }
  190. // ParseUintList parses and validates the specified string as the value
  191. // found in some cgroup file (e.g. `cpuset.cpus`, `cpuset.mems`), which could be
  192. // one of the formats below. Note that duplicates are actually allowed in the
  193. // input string. It returns a `map[int]bool` with available elements from `val`
  194. // set to `true`.
  195. // Supported formats:
  196. // 7
  197. // 1-6
  198. // 0,3-4,7,8-10
  199. // 0-0,0,1-7
  200. // 03,1-3 <- this is gonna get parsed as [1,2,3]
  201. // 3,2,1
  202. // 0-2,3,1
  203. func ParseUintList(val string) (map[int]bool, error) {
  204. if val == "" {
  205. return map[int]bool{}, nil
  206. }
  207. availableInts := make(map[int]bool)
  208. split := strings.Split(val, ",")
  209. errInvalidFormat := fmt.Errorf("invalid format: %s", val)
  210. for _, r := range split {
  211. if !strings.Contains(r, "-") {
  212. v, err := strconv.Atoi(r)
  213. if err != nil {
  214. return nil, errInvalidFormat
  215. }
  216. availableInts[v] = true
  217. } else {
  218. split := strings.SplitN(r, "-", 2)
  219. min, err := strconv.Atoi(split[0])
  220. if err != nil {
  221. return nil, errInvalidFormat
  222. }
  223. max, err := strconv.Atoi(split[1])
  224. if err != nil {
  225. return nil, errInvalidFormat
  226. }
  227. if max < min {
  228. return nil, errInvalidFormat
  229. }
  230. for i := min; i <= max; i++ {
  231. availableInts[i] = true
  232. }
  233. }
  234. }
  235. return availableInts, nil
  236. }