github.com/goern/docker@v1.9.0-rc1/pkg/parsers/parsers.go (about) 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 6 import ( 7 "fmt" 8 "net/url" 9 "path" 10 "runtime" 11 "strconv" 12 "strings" 13 ) 14 15 // ParseDockerDaemonHost parses the specified address and returns an address that will be used as the host. 16 // Depending of the address specified, will use the defaultTCPAddr or defaultUnixAddr 17 // defaultUnixAddr must be a absolute file path (no `unix://` prefix) 18 // defaultTCPAddr must be the full `tcp://host:port` form 19 func ParseDockerDaemonHost(defaultTCPAddr, defaultUnixAddr, addr string) (string, error) { 20 addr = strings.TrimSpace(addr) 21 if addr == "" { 22 if runtime.GOOS != "windows" { 23 return fmt.Sprintf("unix://%s", defaultUnixAddr), nil 24 } 25 return defaultTCPAddr, nil 26 } 27 addrParts := strings.Split(addr, "://") 28 if len(addrParts) == 1 { 29 addrParts = []string{"tcp", addrParts[0]} 30 } 31 32 switch addrParts[0] { 33 case "tcp": 34 return ParseTCPAddr(addrParts[1], defaultTCPAddr) 35 case "unix": 36 return ParseUnixAddr(addrParts[1], defaultUnixAddr) 37 case "fd": 38 return addr, nil 39 default: 40 return "", fmt.Errorf("Invalid bind address format: %s", addr) 41 } 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 59 // ParseTCPAddr parses and validates that the specified address is a valid TCP 60 // address. It returns a formatted TCP address, either using the address parsed 61 // from tryAddr, or the contents of defaultAddr if tryAddr is a blank string. 62 // tryAddr is expected to have already been Trim()'d 63 // defaultAddr must be in the full `tcp://host:port` form 64 func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) { 65 if tryAddr == "" || tryAddr == "tcp://" { 66 return defaultAddr, nil 67 } 68 addr := strings.TrimPrefix(tryAddr, "tcp://") 69 if strings.Contains(addr, "://") || addr == "" { 70 return "", fmt.Errorf("Invalid proto, expected tcp: %s", tryAddr) 71 } 72 73 u, err := url.Parse("tcp://" + addr) 74 if err != nil { 75 return "", err 76 } 77 hostParts := strings.Split(u.Host, ":") 78 if len(hostParts) != 2 { 79 return "", fmt.Errorf("Invalid bind address format: %s", tryAddr) 80 } 81 defaults := strings.Split(defaultAddr, ":") 82 if len(defaults) != 3 { 83 return "", fmt.Errorf("Invalid defaults address format: %s", defaultAddr) 84 } 85 86 host := hostParts[0] 87 if host == "" { 88 host = strings.TrimPrefix(defaults[1], "//") 89 } 90 if hostParts[1] == "" { 91 hostParts[1] = defaults[2] 92 } 93 p, err := strconv.Atoi(hostParts[1]) 94 if err != nil && p == 0 { 95 return "", fmt.Errorf("Invalid bind address format: %s", tryAddr) 96 } 97 return fmt.Sprintf("tcp://%s:%d%s", host, p, u.Path), nil 98 } 99 100 // ParseRepositoryTag gets a repos name and returns the right reposName + tag|digest 101 // The tag can be confusing because of a port in a repository name. 102 // Ex: localhost.localdomain:5000/samalba/hipache:latest 103 // Digest ex: localhost:5000/foo/bar@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb 104 func ParseRepositoryTag(repos string) (string, string) { 105 n := strings.Index(repos, "@") 106 if n >= 0 { 107 parts := strings.Split(repos, "@") 108 return parts[0], parts[1] 109 } 110 n = strings.LastIndex(repos, ":") 111 if n < 0 { 112 return repos, "" 113 } 114 if tag := repos[n+1:]; !strings.Contains(tag, "/") { 115 return repos[:n], tag 116 } 117 return repos, "" 118 } 119 120 // PartParser parses and validates the specified string (data) using the specified template 121 // e.g. ip:public:private -> 192.168.0.1:80:8000 122 func PartParser(template, data string) (map[string]string, error) { 123 // ip:public:private 124 var ( 125 templateParts = strings.Split(template, ":") 126 parts = strings.Split(data, ":") 127 out = make(map[string]string, len(templateParts)) 128 ) 129 if len(parts) != len(templateParts) { 130 return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template) 131 } 132 133 for i, t := range templateParts { 134 value := "" 135 if len(parts) > i { 136 value = parts[i] 137 } 138 out[t] = value 139 } 140 return out, nil 141 } 142 143 // ParseKeyValueOpt parses and validates the specified string as a key/value pair (key=value) 144 func ParseKeyValueOpt(opt string) (string, string, error) { 145 parts := strings.SplitN(opt, "=", 2) 146 if len(parts) != 2 { 147 return "", "", fmt.Errorf("Unable to parse key/value option: %s", opt) 148 } 149 return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil 150 } 151 152 // ParsePortRange parses and validates the specified string as a port-range (8000-9000) 153 func ParsePortRange(ports string) (uint64, uint64, error) { 154 if ports == "" { 155 return 0, 0, fmt.Errorf("Empty string specified for ports.") 156 } 157 if !strings.Contains(ports, "-") { 158 start, err := strconv.ParseUint(ports, 10, 16) 159 end := start 160 return start, end, err 161 } 162 163 parts := strings.Split(ports, "-") 164 start, err := strconv.ParseUint(parts[0], 10, 16) 165 if err != nil { 166 return 0, 0, err 167 } 168 end, err := strconv.ParseUint(parts[1], 10, 16) 169 if err != nil { 170 return 0, 0, err 171 } 172 if end < start { 173 return 0, 0, fmt.Errorf("Invalid range specified for the Port: %s", ports) 174 } 175 return start, end, nil 176 } 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 200 // ParseUintList parses and validates the specified string as the value 201 // found in some cgroup file (e.g. `cpuset.cpus`, `cpuset.mems`), which could be 202 // one of the formats below. Note that duplicates are actually allowed in the 203 // input string. It returns a `map[int]bool` with available elements from `val` 204 // set to `true`. 205 // Supported formats: 206 // 7 207 // 1-6 208 // 0,3-4,7,8-10 209 // 0-0,0,1-7 210 // 03,1-3 <- this is gonna get parsed as [1,2,3] 211 // 3,2,1 212 // 0-2,3,1 213 func ParseUintList(val string) (map[int]bool, error) { 214 if val == "" { 215 return map[int]bool{}, nil 216 } 217 218 availableInts := make(map[int]bool) 219 split := strings.Split(val, ",") 220 errInvalidFormat := fmt.Errorf("invalid format: %s", val) 221 222 for _, r := range split { 223 if !strings.Contains(r, "-") { 224 v, err := strconv.Atoi(r) 225 if err != nil { 226 return nil, errInvalidFormat 227 } 228 availableInts[v] = true 229 } else { 230 split := strings.SplitN(r, "-", 2) 231 min, err := strconv.Atoi(split[0]) 232 if err != nil { 233 return nil, errInvalidFormat 234 } 235 max, err := strconv.Atoi(split[1]) 236 if err != nil { 237 return nil, errInvalidFormat 238 } 239 if max < min { 240 return nil, errInvalidFormat 241 } 242 for i := min; i <= max; i++ { 243 availableInts[i] = true 244 } 245 } 246 } 247 return availableInts, nil 248 }