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 }