parsers.go 7.8 KB

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