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  }