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