github.com/shopify/docker@v1.13.1/api/types/filters/parse.go (about)

     1  // Package filters provides helper function to parse and handle command line
     2  // filter, used for example in docker ps or docker images commands.
     3  package filters
     4  
     5  import (
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"regexp"
    10  	"strings"
    11  
    12  	"github.com/docker/docker/api/types/versions"
    13  )
    14  
    15  // Args stores filter arguments as map key:{map key: bool}.
    16  // It contains an aggregation of the map of arguments (which are in the form
    17  // of -f 'key=value') based on the key, and stores values for the same key
    18  // in a map with string keys and boolean values.
    19  // e.g given -f 'label=label1=1' -f 'label=label2=2' -f 'image.name=ubuntu'
    20  // the args will be {"image.name":{"ubuntu":true},"label":{"label1=1":true,"label2=2":true}}
    21  type Args struct {
    22  	fields map[string]map[string]bool
    23  }
    24  
    25  // NewArgs initializes a new Args struct.
    26  func NewArgs() Args {
    27  	return Args{fields: map[string]map[string]bool{}}
    28  }
    29  
    30  // ParseFlag parses the argument to the filter flag. Like
    31  //
    32  //   `docker ps -f 'created=today' -f 'image.name=ubuntu*'`
    33  //
    34  // If prev map is provided, then it is appended to, and returned. By default a new
    35  // map is created.
    36  func ParseFlag(arg string, prev Args) (Args, error) {
    37  	filters := prev
    38  	if len(arg) == 0 {
    39  		return filters, nil
    40  	}
    41  
    42  	if !strings.Contains(arg, "=") {
    43  		return filters, ErrBadFormat
    44  	}
    45  
    46  	f := strings.SplitN(arg, "=", 2)
    47  
    48  	name := strings.ToLower(strings.TrimSpace(f[0]))
    49  	value := strings.TrimSpace(f[1])
    50  
    51  	filters.Add(name, value)
    52  
    53  	return filters, nil
    54  }
    55  
    56  // ErrBadFormat is an error returned in case of bad format for a filter.
    57  var ErrBadFormat = errors.New("bad format of filter (expected name=value)")
    58  
    59  // ToParam packs the Args into a string for easy transport from client to server.
    60  func ToParam(a Args) (string, error) {
    61  	// this way we don't URL encode {}, just empty space
    62  	if a.Len() == 0 {
    63  		return "", nil
    64  	}
    65  
    66  	buf, err := json.Marshal(a.fields)
    67  	if err != nil {
    68  		return "", err
    69  	}
    70  	return string(buf), nil
    71  }
    72  
    73  // ToParamWithVersion packs the Args into a string for easy transport from client to server.
    74  // The generated string will depend on the specified version (corresponding to the API version).
    75  func ToParamWithVersion(version string, a Args) (string, error) {
    76  	// this way we don't URL encode {}, just empty space
    77  	if a.Len() == 0 {
    78  		return "", nil
    79  	}
    80  
    81  	// for daemons older than v1.10, filter must be of the form map[string][]string
    82  	buf := []byte{}
    83  	err := errors.New("")
    84  	if version != "" && versions.LessThan(version, "1.22") {
    85  		buf, err = json.Marshal(convertArgsToSlice(a.fields))
    86  	} else {
    87  		buf, err = json.Marshal(a.fields)
    88  	}
    89  	if err != nil {
    90  		return "", err
    91  	}
    92  	return string(buf), nil
    93  }
    94  
    95  // FromParam unpacks the filter Args.
    96  func FromParam(p string) (Args, error) {
    97  	if len(p) == 0 {
    98  		return NewArgs(), nil
    99  	}
   100  
   101  	r := strings.NewReader(p)
   102  	d := json.NewDecoder(r)
   103  
   104  	m := map[string]map[string]bool{}
   105  	if err := d.Decode(&m); err != nil {
   106  		r.Seek(0, 0)
   107  
   108  		// Allow parsing old arguments in slice format.
   109  		// Because other libraries might be sending them in this format.
   110  		deprecated := map[string][]string{}
   111  		if deprecatedErr := d.Decode(&deprecated); deprecatedErr == nil {
   112  			m = deprecatedArgs(deprecated)
   113  		} else {
   114  			return NewArgs(), err
   115  		}
   116  	}
   117  	return Args{m}, nil
   118  }
   119  
   120  // Get returns the list of values associates with a field.
   121  // It returns a slice of strings to keep backwards compatibility with old code.
   122  func (filters Args) Get(field string) []string {
   123  	values := filters.fields[field]
   124  	if values == nil {
   125  		return make([]string, 0)
   126  	}
   127  	slice := make([]string, 0, len(values))
   128  	for key := range values {
   129  		slice = append(slice, key)
   130  	}
   131  	return slice
   132  }
   133  
   134  // Add adds a new value to a filter field.
   135  func (filters Args) Add(name, value string) {
   136  	if _, ok := filters.fields[name]; ok {
   137  		filters.fields[name][value] = true
   138  	} else {
   139  		filters.fields[name] = map[string]bool{value: true}
   140  	}
   141  }
   142  
   143  // Del removes a value from a filter field.
   144  func (filters Args) Del(name, value string) {
   145  	if _, ok := filters.fields[name]; ok {
   146  		delete(filters.fields[name], value)
   147  		if len(filters.fields[name]) == 0 {
   148  			delete(filters.fields, name)
   149  		}
   150  	}
   151  }
   152  
   153  // Len returns the number of fields in the arguments.
   154  func (filters Args) Len() int {
   155  	return len(filters.fields)
   156  }
   157  
   158  // MatchKVList returns true if the values for the specified field matches the ones
   159  // from the sources.
   160  // e.g. given Args are {'label': {'label1=1','label2=1'}, 'image.name', {'ubuntu'}},
   161  //      field is 'label' and sources are {'label1': '1', 'label2': '2'}
   162  //      it returns true.
   163  func (filters Args) MatchKVList(field string, sources map[string]string) bool {
   164  	fieldValues := filters.fields[field]
   165  
   166  	//do not filter if there is no filter set or cannot determine filter
   167  	if len(fieldValues) == 0 {
   168  		return true
   169  	}
   170  
   171  	if len(sources) == 0 {
   172  		return false
   173  	}
   174  
   175  	for name2match := range fieldValues {
   176  		testKV := strings.SplitN(name2match, "=", 2)
   177  
   178  		v, ok := sources[testKV[0]]
   179  		if !ok {
   180  			return false
   181  		}
   182  		if len(testKV) == 2 && testKV[1] != v {
   183  			return false
   184  		}
   185  	}
   186  
   187  	return true
   188  }
   189  
   190  // Match returns true if the values for the specified field matches the source string
   191  // e.g. given Args are {'label': {'label1=1','label2=1'}, 'image.name', {'ubuntu'}},
   192  //      field is 'image.name' and source is 'ubuntu'
   193  //      it returns true.
   194  func (filters Args) Match(field, source string) bool {
   195  	if filters.ExactMatch(field, source) {
   196  		return true
   197  	}
   198  
   199  	fieldValues := filters.fields[field]
   200  	for name2match := range fieldValues {
   201  		match, err := regexp.MatchString(name2match, source)
   202  		if err != nil {
   203  			continue
   204  		}
   205  		if match {
   206  			return true
   207  		}
   208  	}
   209  	return false
   210  }
   211  
   212  // ExactMatch returns true if the source matches exactly one of the filters.
   213  func (filters Args) ExactMatch(field, source string) bool {
   214  	fieldValues, ok := filters.fields[field]
   215  	//do not filter if there is no filter set or cannot determine filter
   216  	if !ok || len(fieldValues) == 0 {
   217  		return true
   218  	}
   219  
   220  	// try to match full name value to avoid O(N) regular expression matching
   221  	return fieldValues[source]
   222  }
   223  
   224  // UniqueExactMatch returns true if there is only one filter and the source matches exactly this one.
   225  func (filters Args) UniqueExactMatch(field, source string) bool {
   226  	fieldValues := filters.fields[field]
   227  	//do not filter if there is no filter set or cannot determine filter
   228  	if len(fieldValues) == 0 {
   229  		return true
   230  	}
   231  	if len(filters.fields[field]) != 1 {
   232  		return false
   233  	}
   234  
   235  	// try to match full name value to avoid O(N) regular expression matching
   236  	return fieldValues[source]
   237  }
   238  
   239  // FuzzyMatch returns true if the source matches exactly one of the filters,
   240  // or the source has one of the filters as a prefix.
   241  func (filters Args) FuzzyMatch(field, source string) bool {
   242  	if filters.ExactMatch(field, source) {
   243  		return true
   244  	}
   245  
   246  	fieldValues := filters.fields[field]
   247  	for prefix := range fieldValues {
   248  		if strings.HasPrefix(source, prefix) {
   249  			return true
   250  		}
   251  	}
   252  	return false
   253  }
   254  
   255  // Include returns true if the name of the field to filter is in the filters.
   256  func (filters Args) Include(field string) bool {
   257  	_, ok := filters.fields[field]
   258  	return ok
   259  }
   260  
   261  // Validate ensures that all the fields in the filter are valid.
   262  // It returns an error as soon as it finds an invalid field.
   263  func (filters Args) Validate(accepted map[string]bool) error {
   264  	for name := range filters.fields {
   265  		if !accepted[name] {
   266  			return fmt.Errorf("Invalid filter '%s'", name)
   267  		}
   268  	}
   269  	return nil
   270  }
   271  
   272  // WalkValues iterates over the list of filtered values for a field.
   273  // It stops the iteration if it finds an error and it returns that error.
   274  func (filters Args) WalkValues(field string, op func(value string) error) error {
   275  	if _, ok := filters.fields[field]; !ok {
   276  		return nil
   277  	}
   278  	for v := range filters.fields[field] {
   279  		if err := op(v); err != nil {
   280  			return err
   281  		}
   282  	}
   283  	return nil
   284  }
   285  
   286  func deprecatedArgs(d map[string][]string) map[string]map[string]bool {
   287  	m := map[string]map[string]bool{}
   288  	for k, v := range d {
   289  		values := map[string]bool{}
   290  		for _, vv := range v {
   291  			values[vv] = true
   292  		}
   293  		m[k] = values
   294  	}
   295  	return m
   296  }
   297  
   298  func convertArgsToSlice(f map[string]map[string]bool) map[string][]string {
   299  	m := map[string][]string{}
   300  	for k, v := range f {
   301  		values := []string{}
   302  		for kk := range v {
   303  			if v[kk] {
   304  				values = append(values, kk)
   305  			}
   306  		}
   307  		m[k] = values
   308  	}
   309  	return m
   310  }