These filters are only use to interchange data between clients and daemons.
They don't belong to the parsers package.
Signed-off-by: David Calavera <david.calavera@gmail.com>
| ... | ... |
@@ -9,8 +9,8 @@ import ( |
| 9 | 9 |
|
| 10 | 10 |
"github.com/docker/docker/api/client/lib" |
| 11 | 11 |
"github.com/docker/docker/api/types" |
| 12 |
+ "github.com/docker/docker/api/types/filters" |
|
| 12 | 13 |
"github.com/docker/docker/api/types/registry" |
| 13 |
- "github.com/docker/docker/pkg/parsers/filters" |
|
| 14 | 14 |
"github.com/docker/docker/runconfig" |
| 15 | 15 |
) |
| 16 | 16 |
|
| ... | ... |
@@ -2,11 +2,11 @@ package client |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"github.com/docker/docker/api/types" |
| 5 |
+ "github.com/docker/docker/api/types/filters" |
|
| 5 | 6 |
Cli "github.com/docker/docker/cli" |
| 6 | 7 |
"github.com/docker/docker/opts" |
| 7 | 8 |
"github.com/docker/docker/pkg/jsonmessage" |
| 8 | 9 |
flag "github.com/docker/docker/pkg/mflag" |
| 9 |
- "github.com/docker/docker/pkg/parsers/filters" |
|
| 10 | 10 |
) |
| 11 | 11 |
|
| 12 | 12 |
// CmdEvents prints a live stream of real time events from the server. |
| ... | ... |
@@ -8,10 +8,10 @@ import ( |
| 8 | 8 |
|
| 9 | 9 |
"github.com/docker/distribution/reference" |
| 10 | 10 |
"github.com/docker/docker/api/types" |
| 11 |
+ "github.com/docker/docker/api/types/filters" |
|
| 11 | 12 |
Cli "github.com/docker/docker/cli" |
| 12 | 13 |
"github.com/docker/docker/opts" |
| 13 | 14 |
flag "github.com/docker/docker/pkg/mflag" |
| 14 |
- "github.com/docker/docker/pkg/parsers/filters" |
|
| 15 | 15 |
"github.com/docker/docker/pkg/stringid" |
| 16 | 16 |
"github.com/docker/docker/pkg/units" |
| 17 | 17 |
) |
| ... | ... |
@@ -6,8 +6,8 @@ import ( |
| 6 | 6 |
"time" |
| 7 | 7 |
|
| 8 | 8 |
"github.com/docker/docker/api/types" |
| 9 |
+ "github.com/docker/docker/api/types/filters" |
|
| 9 | 10 |
timetypes "github.com/docker/docker/api/types/time" |
| 10 |
- "github.com/docker/docker/pkg/parsers/filters" |
|
| 11 | 11 |
) |
| 12 | 12 |
|
| 13 | 13 |
// Events returns a stream of events in the daemon in a ReadCloser. |
| ... | ... |
@@ -3,10 +3,10 @@ package client |
| 3 | 3 |
import ( |
| 4 | 4 |
"github.com/docker/docker/api/client/ps" |
| 5 | 5 |
"github.com/docker/docker/api/types" |
| 6 |
+ "github.com/docker/docker/api/types/filters" |
|
| 6 | 7 |
Cli "github.com/docker/docker/cli" |
| 7 | 8 |
"github.com/docker/docker/opts" |
| 8 | 9 |
flag "github.com/docker/docker/pkg/mflag" |
| 9 |
- "github.com/docker/docker/pkg/parsers/filters" |
|
| 10 | 10 |
) |
| 11 | 11 |
|
| 12 | 12 |
// CmdPs outputs a list of Docker containers. |
| ... | ... |
@@ -5,10 +5,10 @@ import ( |
| 5 | 5 |
"text/tabwriter" |
| 6 | 6 |
|
| 7 | 7 |
"github.com/docker/docker/api/types" |
| 8 |
+ "github.com/docker/docker/api/types/filters" |
|
| 8 | 9 |
Cli "github.com/docker/docker/cli" |
| 9 | 10 |
"github.com/docker/docker/opts" |
| 10 | 11 |
flag "github.com/docker/docker/pkg/mflag" |
| 11 |
- "github.com/docker/docker/pkg/parsers/filters" |
|
| 12 | 12 |
) |
| 13 | 13 |
|
| 14 | 14 |
// CmdVolume is the parent subcommand for all volume commands |
| ... | ... |
@@ -10,9 +10,9 @@ import ( |
| 10 | 10 |
"github.com/Sirupsen/logrus" |
| 11 | 11 |
"github.com/docker/docker/api/server/httputils" |
| 12 | 12 |
"github.com/docker/docker/api/types" |
| 13 |
+ "github.com/docker/docker/api/types/filters" |
|
| 13 | 14 |
"github.com/docker/docker/api/types/network" |
| 14 | 15 |
"github.com/docker/docker/daemon" |
| 15 |
- "github.com/docker/docker/pkg/parsers/filters" |
|
| 16 | 16 |
"github.com/docker/docker/runconfig" |
| 17 | 17 |
"github.com/docker/libnetwork" |
| 18 | 18 |
) |
| ... | ... |
@@ -2,8 +2,8 @@ package system |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"github.com/docker/docker/api/types" |
| 5 |
+ "github.com/docker/docker/api/types/filters" |
|
| 5 | 6 |
"github.com/docker/docker/pkg/jsonmessage" |
| 6 |
- "github.com/docker/docker/pkg/parsers/filters" |
|
| 7 | 7 |
) |
| 8 | 8 |
|
| 9 | 9 |
// Backend is the methods that need to be implemented to provide |
| ... | ... |
@@ -9,10 +9,10 @@ import ( |
| 9 | 9 |
"github.com/docker/docker/api" |
| 10 | 10 |
"github.com/docker/docker/api/server/httputils" |
| 11 | 11 |
"github.com/docker/docker/api/types" |
| 12 |
+ "github.com/docker/docker/api/types/filters" |
|
| 12 | 13 |
timetypes "github.com/docker/docker/api/types/time" |
| 13 | 14 |
"github.com/docker/docker/pkg/ioutils" |
| 14 | 15 |
"github.com/docker/docker/pkg/jsonmessage" |
| 15 |
- "github.com/docker/docker/pkg/parsers/filters" |
|
| 16 | 16 |
"golang.org/x/net/context" |
| 17 | 17 |
) |
| 18 | 18 |
|
| 12 | 12 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,241 @@ |
| 0 |
+// Package filters provides helper function to parse and handle command line |
|
| 1 |
+// filter, used for example in docker ps or docker images commands. |
|
| 2 |
+package filters |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "encoding/json" |
|
| 6 |
+ "errors" |
|
| 7 |
+ "fmt" |
|
| 8 |
+ "regexp" |
|
| 9 |
+ "strings" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+// Args stores filter arguments as map key:{array of values}.
|
|
| 13 |
+// It contains a aggregation of the list of arguments (which are in the form |
|
| 14 |
+// of -f 'key=value') based on the key, and store values for the same key |
|
| 15 |
+// in an slice. |
|
| 16 |
+// e.g given -f 'label=label1=1' -f 'label=label2=2' -f 'image.name=ubuntu' |
|
| 17 |
+// the args will be {'label': {'label1=1','label2=2'}, 'image.name', {'ubuntu'}}
|
|
| 18 |
+type Args struct {
|
|
| 19 |
+ fields map[string]map[string]bool |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+// NewArgs initializes a new Args struct. |
|
| 23 |
+func NewArgs() Args {
|
|
| 24 |
+ return Args{fields: map[string]map[string]bool{}}
|
|
| 25 |
+} |
|
| 26 |
+ |
|
| 27 |
+// ParseFlag parses the argument to the filter flag. Like |
|
| 28 |
+// |
|
| 29 |
+// `docker ps -f 'created=today' -f 'image.name=ubuntu*'` |
|
| 30 |
+// |
|
| 31 |
+// If prev map is provided, then it is appended to, and returned. By default a new |
|
| 32 |
+// map is created. |
|
| 33 |
+func ParseFlag(arg string, prev Args) (Args, error) {
|
|
| 34 |
+ filters := prev |
|
| 35 |
+ if len(arg) == 0 {
|
|
| 36 |
+ return filters, nil |
|
| 37 |
+ } |
|
| 38 |
+ |
|
| 39 |
+ if !strings.Contains(arg, "=") {
|
|
| 40 |
+ return filters, ErrBadFormat |
|
| 41 |
+ } |
|
| 42 |
+ |
|
| 43 |
+ f := strings.SplitN(arg, "=", 2) |
|
| 44 |
+ |
|
| 45 |
+ name := strings.ToLower(strings.TrimSpace(f[0])) |
|
| 46 |
+ value := strings.TrimSpace(f[1]) |
|
| 47 |
+ |
|
| 48 |
+ filters.Add(name, value) |
|
| 49 |
+ |
|
| 50 |
+ return filters, nil |
|
| 51 |
+} |
|
| 52 |
+ |
|
| 53 |
+// ErrBadFormat is an error returned in case of bad format for a filter. |
|
| 54 |
+var ErrBadFormat = errors.New("bad format of filter (expected name=value)")
|
|
| 55 |
+ |
|
| 56 |
+// ToParam packs the Args into an string for easy transport from client to server. |
|
| 57 |
+func ToParam(a Args) (string, error) {
|
|
| 58 |
+ // this way we don't URL encode {}, just empty space
|
|
| 59 |
+ if a.Len() == 0 {
|
|
| 60 |
+ return "", nil |
|
| 61 |
+ } |
|
| 62 |
+ |
|
| 63 |
+ buf, err := json.Marshal(a.fields) |
|
| 64 |
+ if err != nil {
|
|
| 65 |
+ return "", err |
|
| 66 |
+ } |
|
| 67 |
+ return string(buf), nil |
|
| 68 |
+} |
|
| 69 |
+ |
|
| 70 |
+// FromParam unpacks the filter Args. |
|
| 71 |
+func FromParam(p string) (Args, error) {
|
|
| 72 |
+ if len(p) == 0 {
|
|
| 73 |
+ return NewArgs(), nil |
|
| 74 |
+ } |
|
| 75 |
+ |
|
| 76 |
+ r := strings.NewReader(p) |
|
| 77 |
+ d := json.NewDecoder(r) |
|
| 78 |
+ |
|
| 79 |
+ m := map[string]map[string]bool{}
|
|
| 80 |
+ if err := d.Decode(&m); err != nil {
|
|
| 81 |
+ r.Seek(0, 0) |
|
| 82 |
+ |
|
| 83 |
+ // Allow parsing old arguments in slice format. |
|
| 84 |
+ // Because other libraries might be sending them in this format. |
|
| 85 |
+ deprecated := map[string][]string{}
|
|
| 86 |
+ if deprecatedErr := d.Decode(&deprecated); deprecatedErr == nil {
|
|
| 87 |
+ m = deprecatedArgs(deprecated) |
|
| 88 |
+ } else {
|
|
| 89 |
+ return NewArgs(), err |
|
| 90 |
+ } |
|
| 91 |
+ } |
|
| 92 |
+ return Args{m}, nil
|
|
| 93 |
+} |
|
| 94 |
+ |
|
| 95 |
+// Get returns the list of values associates with a field. |
|
| 96 |
+// It returns a slice of strings to keep backwards compatibility with old code. |
|
| 97 |
+func (filters Args) Get(field string) []string {
|
|
| 98 |
+ values := filters.fields[field] |
|
| 99 |
+ if values == nil {
|
|
| 100 |
+ return make([]string, 0) |
|
| 101 |
+ } |
|
| 102 |
+ slice := make([]string, 0, len(values)) |
|
| 103 |
+ for key := range values {
|
|
| 104 |
+ slice = append(slice, key) |
|
| 105 |
+ } |
|
| 106 |
+ return slice |
|
| 107 |
+} |
|
| 108 |
+ |
|
| 109 |
+// Add adds a new value to a filter field. |
|
| 110 |
+func (filters Args) Add(name, value string) {
|
|
| 111 |
+ if _, ok := filters.fields[name]; ok {
|
|
| 112 |
+ filters.fields[name][value] = true |
|
| 113 |
+ } else {
|
|
| 114 |
+ filters.fields[name] = map[string]bool{value: true}
|
|
| 115 |
+ } |
|
| 116 |
+} |
|
| 117 |
+ |
|
| 118 |
+// Del removes a value from a filter field. |
|
| 119 |
+func (filters Args) Del(name, value string) {
|
|
| 120 |
+ if _, ok := filters.fields[name]; ok {
|
|
| 121 |
+ delete(filters.fields[name], value) |
|
| 122 |
+ } |
|
| 123 |
+} |
|
| 124 |
+ |
|
| 125 |
+// Len returns the number of fields in the arguments. |
|
| 126 |
+func (filters Args) Len() int {
|
|
| 127 |
+ return len(filters.fields) |
|
| 128 |
+} |
|
| 129 |
+ |
|
| 130 |
+// MatchKVList returns true if the values for the specified field matches the ones |
|
| 131 |
+// from the sources. |
|
| 132 |
+// e.g. given Args are {'label': {'label1=1','label2=1'}, 'image.name', {'ubuntu'}},
|
|
| 133 |
+// field is 'label' and sources are {'label1': '1', 'label2': '2'}
|
|
| 134 |
+// it returns true. |
|
| 135 |
+func (filters Args) MatchKVList(field string, sources map[string]string) bool {
|
|
| 136 |
+ fieldValues := filters.fields[field] |
|
| 137 |
+ |
|
| 138 |
+ //do not filter if there is no filter set or cannot determine filter |
|
| 139 |
+ if len(fieldValues) == 0 {
|
|
| 140 |
+ return true |
|
| 141 |
+ } |
|
| 142 |
+ |
|
| 143 |
+ if sources == nil || len(sources) == 0 {
|
|
| 144 |
+ return false |
|
| 145 |
+ } |
|
| 146 |
+ |
|
| 147 |
+ for name2match := range fieldValues {
|
|
| 148 |
+ testKV := strings.SplitN(name2match, "=", 2) |
|
| 149 |
+ |
|
| 150 |
+ v, ok := sources[testKV[0]] |
|
| 151 |
+ if !ok {
|
|
| 152 |
+ return false |
|
| 153 |
+ } |
|
| 154 |
+ if len(testKV) == 2 && testKV[1] != v {
|
|
| 155 |
+ return false |
|
| 156 |
+ } |
|
| 157 |
+ } |
|
| 158 |
+ |
|
| 159 |
+ return true |
|
| 160 |
+} |
|
| 161 |
+ |
|
| 162 |
+// Match returns true if the values for the specified field matches the source string |
|
| 163 |
+// e.g. given Args are {'label': {'label1=1','label2=1'}, 'image.name', {'ubuntu'}},
|
|
| 164 |
+// field is 'image.name' and source is 'ubuntu' |
|
| 165 |
+// it returns true. |
|
| 166 |
+func (filters Args) Match(field, source string) bool {
|
|
| 167 |
+ if filters.ExactMatch(field, source) {
|
|
| 168 |
+ return true |
|
| 169 |
+ } |
|
| 170 |
+ |
|
| 171 |
+ fieldValues := filters.fields[field] |
|
| 172 |
+ for name2match := range fieldValues {
|
|
| 173 |
+ match, err := regexp.MatchString(name2match, source) |
|
| 174 |
+ if err != nil {
|
|
| 175 |
+ continue |
|
| 176 |
+ } |
|
| 177 |
+ if match {
|
|
| 178 |
+ return true |
|
| 179 |
+ } |
|
| 180 |
+ } |
|
| 181 |
+ return false |
|
| 182 |
+} |
|
| 183 |
+ |
|
| 184 |
+// ExactMatch returns true if the source matches exactly one of the filters. |
|
| 185 |
+func (filters Args) ExactMatch(field, source string) bool {
|
|
| 186 |
+ fieldValues, ok := filters.fields[field] |
|
| 187 |
+ //do not filter if there is no filter set or cannot determine filter |
|
| 188 |
+ if !ok || len(fieldValues) == 0 {
|
|
| 189 |
+ return true |
|
| 190 |
+ } |
|
| 191 |
+ |
|
| 192 |
+ // try to march full name value to avoid O(N) regular expression matching |
|
| 193 |
+ if fieldValues[source] {
|
|
| 194 |
+ return true |
|
| 195 |
+ } |
|
| 196 |
+ return false |
|
| 197 |
+} |
|
| 198 |
+ |
|
| 199 |
+// Include returns true if the name of the field to filter is in the filters. |
|
| 200 |
+func (filters Args) Include(field string) bool {
|
|
| 201 |
+ _, ok := filters.fields[field] |
|
| 202 |
+ return ok |
|
| 203 |
+} |
|
| 204 |
+ |
|
| 205 |
+// Validate ensures that all the fields in the filter are valid. |
|
| 206 |
+// It returns an error as soon as it finds an invalid field. |
|
| 207 |
+func (filters Args) Validate(accepted map[string]bool) error {
|
|
| 208 |
+ for name := range filters.fields {
|
|
| 209 |
+ if !accepted[name] {
|
|
| 210 |
+ return fmt.Errorf("Invalid filter '%s'", name)
|
|
| 211 |
+ } |
|
| 212 |
+ } |
|
| 213 |
+ return nil |
|
| 214 |
+} |
|
| 215 |
+ |
|
| 216 |
+// WalkValues iterates over the list of filtered values for a field. |
|
| 217 |
+// It stops the iteration if it finds an error and it returns that error. |
|
| 218 |
+func (filters Args) WalkValues(field string, op func(value string) error) error {
|
|
| 219 |
+ if _, ok := filters.fields[field]; !ok {
|
|
| 220 |
+ return nil |
|
| 221 |
+ } |
|
| 222 |
+ for v := range filters.fields[field] {
|
|
| 223 |
+ if err := op(v); err != nil {
|
|
| 224 |
+ return err |
|
| 225 |
+ } |
|
| 226 |
+ } |
|
| 227 |
+ return nil |
|
| 228 |
+} |
|
| 229 |
+ |
|
| 230 |
+func deprecatedArgs(d map[string][]string) map[string]map[string]bool {
|
|
| 231 |
+ m := map[string]map[string]bool{}
|
|
| 232 |
+ for k, v := range d {
|
|
| 233 |
+ values := map[string]bool{}
|
|
| 234 |
+ for _, vv := range v {
|
|
| 235 |
+ values[vv] = true |
|
| 236 |
+ } |
|
| 237 |
+ m[k] = values |
|
| 238 |
+ } |
|
| 239 |
+ return m |
|
| 240 |
+} |
| 0 | 241 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,351 @@ |
| 0 |
+package filters |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "testing" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+func TestParseArgs(t *testing.T) {
|
|
| 8 |
+ // equivalent of `docker ps -f 'created=today' -f 'image.name=ubuntu*' -f 'image.name=*untu'` |
|
| 9 |
+ flagArgs := []string{
|
|
| 10 |
+ "created=today", |
|
| 11 |
+ "image.name=ubuntu*", |
|
| 12 |
+ "image.name=*untu", |
|
| 13 |
+ } |
|
| 14 |
+ var ( |
|
| 15 |
+ args = NewArgs() |
|
| 16 |
+ err error |
|
| 17 |
+ ) |
|
| 18 |
+ for i := range flagArgs {
|
|
| 19 |
+ args, err = ParseFlag(flagArgs[i], args) |
|
| 20 |
+ if err != nil {
|
|
| 21 |
+ t.Errorf("failed to parse %s: %s", flagArgs[i], err)
|
|
| 22 |
+ } |
|
| 23 |
+ } |
|
| 24 |
+ if len(args.Get("created")) != 1 {
|
|
| 25 |
+ t.Errorf("failed to set this arg")
|
|
| 26 |
+ } |
|
| 27 |
+ if len(args.Get("image.name")) != 2 {
|
|
| 28 |
+ t.Errorf("the args should have collapsed")
|
|
| 29 |
+ } |
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+func TestParseArgsEdgeCase(t *testing.T) {
|
|
| 33 |
+ var filters Args |
|
| 34 |
+ args, err := ParseFlag("", filters)
|
|
| 35 |
+ if err != nil {
|
|
| 36 |
+ t.Fatal(err) |
|
| 37 |
+ } |
|
| 38 |
+ if args.Len() != 0 {
|
|
| 39 |
+ t.Fatalf("Expected an empty Args (map), got %v", args)
|
|
| 40 |
+ } |
|
| 41 |
+ if args, err = ParseFlag("anything", args); err == nil || err != ErrBadFormat {
|
|
| 42 |
+ t.Fatalf("Expected ErrBadFormat, got %v", err)
|
|
| 43 |
+ } |
|
| 44 |
+} |
|
| 45 |
+ |
|
| 46 |
+func TestToParam(t *testing.T) {
|
|
| 47 |
+ fields := map[string]map[string]bool{
|
|
| 48 |
+ "created": {"today": true},
|
|
| 49 |
+ "image.name": {"ubuntu*": true, "*untu": true},
|
|
| 50 |
+ } |
|
| 51 |
+ a := Args{fields: fields}
|
|
| 52 |
+ |
|
| 53 |
+ _, err := ToParam(a) |
|
| 54 |
+ if err != nil {
|
|
| 55 |
+ t.Errorf("failed to marshal the filters: %s", err)
|
|
| 56 |
+ } |
|
| 57 |
+} |
|
| 58 |
+ |
|
| 59 |
+func TestFromParam(t *testing.T) {
|
|
| 60 |
+ invalids := []string{
|
|
| 61 |
+ "anything", |
|
| 62 |
+ "['a','list']", |
|
| 63 |
+ "{'key': 'value'}",
|
|
| 64 |
+ `{"key": "value"}`,
|
|
| 65 |
+ } |
|
| 66 |
+ valid := map[*Args][]string{
|
|
| 67 |
+ &Args{fields: map[string]map[string]bool{"key": {"value": true}}}: {
|
|
| 68 |
+ `{"key": ["value"]}`,
|
|
| 69 |
+ `{"key": {"value": true}}`,
|
|
| 70 |
+ }, |
|
| 71 |
+ &Args{fields: map[string]map[string]bool{"key": {"value1": true, "value2": true}}}: {
|
|
| 72 |
+ `{"key": ["value1", "value2"]}`,
|
|
| 73 |
+ `{"key": {"value1": true, "value2": true}}`,
|
|
| 74 |
+ }, |
|
| 75 |
+ &Args{fields: map[string]map[string]bool{"key1": {"value1": true}, "key2": {"value2": true}}}: {
|
|
| 76 |
+ `{"key1": ["value1"], "key2": ["value2"]}`,
|
|
| 77 |
+ `{"key1": {"value1": true}, "key2": {"value2": true}}`,
|
|
| 78 |
+ }, |
|
| 79 |
+ } |
|
| 80 |
+ |
|
| 81 |
+ for _, invalid := range invalids {
|
|
| 82 |
+ if _, err := FromParam(invalid); err == nil {
|
|
| 83 |
+ t.Fatalf("Expected an error with %v, got nothing", invalid)
|
|
| 84 |
+ } |
|
| 85 |
+ } |
|
| 86 |
+ |
|
| 87 |
+ for expectedArgs, matchers := range valid {
|
|
| 88 |
+ for _, json := range matchers {
|
|
| 89 |
+ args, err := FromParam(json) |
|
| 90 |
+ if err != nil {
|
|
| 91 |
+ t.Fatal(err) |
|
| 92 |
+ } |
|
| 93 |
+ if args.Len() != expectedArgs.Len() {
|
|
| 94 |
+ t.Fatalf("Expected %v, go %v", expectedArgs, args)
|
|
| 95 |
+ } |
|
| 96 |
+ for key, expectedValues := range expectedArgs.fields {
|
|
| 97 |
+ values := args.Get(key) |
|
| 98 |
+ |
|
| 99 |
+ if len(values) != len(expectedValues) {
|
|
| 100 |
+ t.Fatalf("Expected %v, go %v", expectedArgs, args)
|
|
| 101 |
+ } |
|
| 102 |
+ |
|
| 103 |
+ for _, v := range values {
|
|
| 104 |
+ if !expectedValues[v] {
|
|
| 105 |
+ t.Fatalf("Expected %v, go %v", expectedArgs, args)
|
|
| 106 |
+ } |
|
| 107 |
+ } |
|
| 108 |
+ } |
|
| 109 |
+ } |
|
| 110 |
+ } |
|
| 111 |
+} |
|
| 112 |
+ |
|
| 113 |
+func TestEmpty(t *testing.T) {
|
|
| 114 |
+ a := Args{}
|
|
| 115 |
+ v, err := ToParam(a) |
|
| 116 |
+ if err != nil {
|
|
| 117 |
+ t.Errorf("failed to marshal the filters: %s", err)
|
|
| 118 |
+ } |
|
| 119 |
+ v1, err := FromParam(v) |
|
| 120 |
+ if err != nil {
|
|
| 121 |
+ t.Errorf("%s", err)
|
|
| 122 |
+ } |
|
| 123 |
+ if a.Len() != v1.Len() {
|
|
| 124 |
+ t.Errorf("these should both be empty sets")
|
|
| 125 |
+ } |
|
| 126 |
+} |
|
| 127 |
+ |
|
| 128 |
+func TestArgsMatchKVListEmptySources(t *testing.T) {
|
|
| 129 |
+ args := NewArgs() |
|
| 130 |
+ if !args.MatchKVList("created", map[string]string{}) {
|
|
| 131 |
+ t.Fatalf("Expected true for (%v,created), got true", args)
|
|
| 132 |
+ } |
|
| 133 |
+ |
|
| 134 |
+ args = Args{map[string]map[string]bool{"created": {"today": true}}}
|
|
| 135 |
+ if args.MatchKVList("created", map[string]string{}) {
|
|
| 136 |
+ t.Fatalf("Expected false for (%v,created), got true", args)
|
|
| 137 |
+ } |
|
| 138 |
+} |
|
| 139 |
+ |
|
| 140 |
+func TestArgsMatchKVList(t *testing.T) {
|
|
| 141 |
+ // Not empty sources |
|
| 142 |
+ sources := map[string]string{
|
|
| 143 |
+ "key1": "value1", |
|
| 144 |
+ "key2": "value2", |
|
| 145 |
+ "key3": "value3", |
|
| 146 |
+ } |
|
| 147 |
+ |
|
| 148 |
+ matches := map[*Args]string{
|
|
| 149 |
+ &Args{}: "field",
|
|
| 150 |
+ &Args{map[string]map[string]bool{
|
|
| 151 |
+ "created": map[string]bool{"today": true},
|
|
| 152 |
+ "labels": map[string]bool{"key1": true}},
|
|
| 153 |
+ }: "labels", |
|
| 154 |
+ &Args{map[string]map[string]bool{
|
|
| 155 |
+ "created": map[string]bool{"today": true},
|
|
| 156 |
+ "labels": map[string]bool{"key1=value1": true}},
|
|
| 157 |
+ }: "labels", |
|
| 158 |
+ } |
|
| 159 |
+ |
|
| 160 |
+ for args, field := range matches {
|
|
| 161 |
+ if args.MatchKVList(field, sources) != true {
|
|
| 162 |
+ t.Fatalf("Expected true for %v on %v, got false", sources, args)
|
|
| 163 |
+ } |
|
| 164 |
+ } |
|
| 165 |
+ |
|
| 166 |
+ differs := map[*Args]string{
|
|
| 167 |
+ &Args{map[string]map[string]bool{
|
|
| 168 |
+ "created": map[string]bool{"today": true}},
|
|
| 169 |
+ }: "created", |
|
| 170 |
+ &Args{map[string]map[string]bool{
|
|
| 171 |
+ "created": map[string]bool{"today": true},
|
|
| 172 |
+ "labels": map[string]bool{"key4": true}},
|
|
| 173 |
+ }: "labels", |
|
| 174 |
+ &Args{map[string]map[string]bool{
|
|
| 175 |
+ "created": map[string]bool{"today": true},
|
|
| 176 |
+ "labels": map[string]bool{"key1=value3": true}},
|
|
| 177 |
+ }: "labels", |
|
| 178 |
+ } |
|
| 179 |
+ |
|
| 180 |
+ for args, field := range differs {
|
|
| 181 |
+ if args.MatchKVList(field, sources) != false {
|
|
| 182 |
+ t.Fatalf("Expected false for %v on %v, got true", sources, args)
|
|
| 183 |
+ } |
|
| 184 |
+ } |
|
| 185 |
+} |
|
| 186 |
+ |
|
| 187 |
+func TestArgsMatch(t *testing.T) {
|
|
| 188 |
+ source := "today" |
|
| 189 |
+ |
|
| 190 |
+ matches := map[*Args]string{
|
|
| 191 |
+ &Args{}: "field",
|
|
| 192 |
+ &Args{map[string]map[string]bool{
|
|
| 193 |
+ "created": map[string]bool{"today": true}},
|
|
| 194 |
+ }: "today", |
|
| 195 |
+ &Args{map[string]map[string]bool{
|
|
| 196 |
+ "created": map[string]bool{"to*": true}},
|
|
| 197 |
+ }: "created", |
|
| 198 |
+ &Args{map[string]map[string]bool{
|
|
| 199 |
+ "created": map[string]bool{"to(.*)": true}},
|
|
| 200 |
+ }: "created", |
|
| 201 |
+ &Args{map[string]map[string]bool{
|
|
| 202 |
+ "created": map[string]bool{"tod": true}},
|
|
| 203 |
+ }: "created", |
|
| 204 |
+ &Args{map[string]map[string]bool{
|
|
| 205 |
+ "created": map[string]bool{"anyting": true, "to*": true}},
|
|
| 206 |
+ }: "created", |
|
| 207 |
+ } |
|
| 208 |
+ |
|
| 209 |
+ for args, field := range matches {
|
|
| 210 |
+ if args.Match(field, source) != true {
|
|
| 211 |
+ t.Fatalf("Expected true for %v on %v, got false", source, args)
|
|
| 212 |
+ } |
|
| 213 |
+ } |
|
| 214 |
+ |
|
| 215 |
+ differs := map[*Args]string{
|
|
| 216 |
+ &Args{map[string]map[string]bool{
|
|
| 217 |
+ "created": map[string]bool{"tomorrow": true}},
|
|
| 218 |
+ }: "created", |
|
| 219 |
+ &Args{map[string]map[string]bool{
|
|
| 220 |
+ "created": map[string]bool{"to(day": true}},
|
|
| 221 |
+ }: "created", |
|
| 222 |
+ &Args{map[string]map[string]bool{
|
|
| 223 |
+ "created": map[string]bool{"tom(.*)": true}},
|
|
| 224 |
+ }: "created", |
|
| 225 |
+ &Args{map[string]map[string]bool{
|
|
| 226 |
+ "created": map[string]bool{"tom": true}},
|
|
| 227 |
+ }: "created", |
|
| 228 |
+ &Args{map[string]map[string]bool{
|
|
| 229 |
+ "created": map[string]bool{"today1": true},
|
|
| 230 |
+ "labels": map[string]bool{"today": true}},
|
|
| 231 |
+ }: "created", |
|
| 232 |
+ } |
|
| 233 |
+ |
|
| 234 |
+ for args, field := range differs {
|
|
| 235 |
+ if args.Match(field, source) != false {
|
|
| 236 |
+ t.Fatalf("Expected false for %v on %v, got true", source, args)
|
|
| 237 |
+ } |
|
| 238 |
+ } |
|
| 239 |
+} |
|
| 240 |
+ |
|
| 241 |
+func TestAdd(t *testing.T) {
|
|
| 242 |
+ f := NewArgs() |
|
| 243 |
+ f.Add("status", "running")
|
|
| 244 |
+ v := f.fields["status"] |
|
| 245 |
+ if len(v) != 1 || !v["running"] {
|
|
| 246 |
+ t.Fatalf("Expected to include a running status, got %v", v)
|
|
| 247 |
+ } |
|
| 248 |
+ |
|
| 249 |
+ f.Add("status", "paused")
|
|
| 250 |
+ if len(v) != 2 || !v["paused"] {
|
|
| 251 |
+ t.Fatalf("Expected to include a paused status, got %v", v)
|
|
| 252 |
+ } |
|
| 253 |
+} |
|
| 254 |
+ |
|
| 255 |
+func TestDel(t *testing.T) {
|
|
| 256 |
+ f := NewArgs() |
|
| 257 |
+ f.Add("status", "running")
|
|
| 258 |
+ f.Del("status", "running")
|
|
| 259 |
+ v := f.fields["status"] |
|
| 260 |
+ if v["running"] {
|
|
| 261 |
+ t.Fatalf("Expected to not include a running status filter, got true")
|
|
| 262 |
+ } |
|
| 263 |
+} |
|
| 264 |
+ |
|
| 265 |
+func TestLen(t *testing.T) {
|
|
| 266 |
+ f := NewArgs() |
|
| 267 |
+ if f.Len() != 0 {
|
|
| 268 |
+ t.Fatalf("Expected to not include any field")
|
|
| 269 |
+ } |
|
| 270 |
+ f.Add("status", "running")
|
|
| 271 |
+ if f.Len() != 1 {
|
|
| 272 |
+ t.Fatalf("Expected to include one field")
|
|
| 273 |
+ } |
|
| 274 |
+} |
|
| 275 |
+ |
|
| 276 |
+func TestExactMatch(t *testing.T) {
|
|
| 277 |
+ f := NewArgs() |
|
| 278 |
+ |
|
| 279 |
+ if !f.ExactMatch("status", "running") {
|
|
| 280 |
+ t.Fatalf("Expected to match `running` when there are no filters, got false")
|
|
| 281 |
+ } |
|
| 282 |
+ |
|
| 283 |
+ f.Add("status", "running")
|
|
| 284 |
+ f.Add("status", "pause*")
|
|
| 285 |
+ |
|
| 286 |
+ if !f.ExactMatch("status", "running") {
|
|
| 287 |
+ t.Fatalf("Expected to match `running` with one of the filters, got false")
|
|
| 288 |
+ } |
|
| 289 |
+ |
|
| 290 |
+ if f.ExactMatch("status", "paused") {
|
|
| 291 |
+ t.Fatalf("Expected to not match `paused` with one of the filters, got true")
|
|
| 292 |
+ } |
|
| 293 |
+} |
|
| 294 |
+ |
|
| 295 |
+func TestInclude(t *testing.T) {
|
|
| 296 |
+ f := NewArgs() |
|
| 297 |
+ if f.Include("status") {
|
|
| 298 |
+ t.Fatalf("Expected to not include a status key, got true")
|
|
| 299 |
+ } |
|
| 300 |
+ f.Add("status", "running")
|
|
| 301 |
+ if !f.Include("status") {
|
|
| 302 |
+ t.Fatalf("Expected to include a status key, got false")
|
|
| 303 |
+ } |
|
| 304 |
+} |
|
| 305 |
+ |
|
| 306 |
+func TestValidate(t *testing.T) {
|
|
| 307 |
+ f := NewArgs() |
|
| 308 |
+ f.Add("status", "running")
|
|
| 309 |
+ |
|
| 310 |
+ valid := map[string]bool{
|
|
| 311 |
+ "status": true, |
|
| 312 |
+ "dangling": true, |
|
| 313 |
+ } |
|
| 314 |
+ |
|
| 315 |
+ if err := f.Validate(valid); err != nil {
|
|
| 316 |
+ t.Fatal(err) |
|
| 317 |
+ } |
|
| 318 |
+ |
|
| 319 |
+ f.Add("bogus", "running")
|
|
| 320 |
+ if err := f.Validate(valid); err == nil {
|
|
| 321 |
+ t.Fatalf("Expected to return an error, got nil")
|
|
| 322 |
+ } |
|
| 323 |
+} |
|
| 324 |
+ |
|
| 325 |
+func TestWalkValues(t *testing.T) {
|
|
| 326 |
+ f := NewArgs() |
|
| 327 |
+ f.Add("status", "running")
|
|
| 328 |
+ f.Add("status", "paused")
|
|
| 329 |
+ |
|
| 330 |
+ f.WalkValues("status", func(value string) error {
|
|
| 331 |
+ if value != "running" && value != "paused" {
|
|
| 332 |
+ t.Fatalf("Unexpected value %s", value)
|
|
| 333 |
+ } |
|
| 334 |
+ return nil |
|
| 335 |
+ }) |
|
| 336 |
+ |
|
| 337 |
+ err := f.WalkValues("status", func(value string) error {
|
|
| 338 |
+ return fmt.Errorf("return")
|
|
| 339 |
+ }) |
|
| 340 |
+ if err == nil {
|
|
| 341 |
+ t.Fatalf("Expected to get an error, got nil")
|
|
| 342 |
+ } |
|
| 343 |
+ |
|
| 344 |
+ err = f.WalkValues("foo", func(value string) error {
|
|
| 345 |
+ return fmt.Errorf("return")
|
|
| 346 |
+ }) |
|
| 347 |
+ if err != nil {
|
|
| 348 |
+ t.Fatalf("Expected to not iterate when the field doesn't exist, got %v", err)
|
|
| 349 |
+ } |
|
| 350 |
+} |
| ... | ... |
@@ -22,6 +22,7 @@ import ( |
| 22 | 22 |
"github.com/docker/distribution/reference" |
| 23 | 23 |
"github.com/docker/docker/api" |
| 24 | 24 |
"github.com/docker/docker/api/types" |
| 25 |
+ "github.com/docker/docker/api/types/filters" |
|
| 25 | 26 |
registrytypes "github.com/docker/docker/api/types/registry" |
| 26 | 27 |
"github.com/docker/docker/container" |
| 27 | 28 |
"github.com/docker/docker/daemon/events" |
| ... | ... |
@@ -49,7 +50,6 @@ import ( |
| 49 | 49 |
"github.com/docker/docker/pkg/mount" |
| 50 | 50 |
"github.com/docker/docker/pkg/namesgenerator" |
| 51 | 51 |
"github.com/docker/docker/pkg/nat" |
| 52 |
- "github.com/docker/docker/pkg/parsers/filters" |
|
| 53 | 52 |
"github.com/docker/docker/pkg/progress" |
| 54 | 53 |
"github.com/docker/docker/pkg/signal" |
| 55 | 54 |
"github.com/docker/docker/pkg/streamformatter" |
| ... | ... |
@@ -2,8 +2,8 @@ package events |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"github.com/docker/distribution/reference" |
| 5 |
+ "github.com/docker/docker/api/types/filters" |
|
| 5 | 6 |
"github.com/docker/docker/pkg/jsonmessage" |
| 6 |
- "github.com/docker/docker/pkg/parsers/filters" |
|
| 7 | 7 |
) |
| 8 | 8 |
|
| 9 | 9 |
// Filter can filter out docker events from a stream |
| ... | ... |
@@ -7,9 +7,9 @@ import ( |
| 7 | 7 |
|
| 8 | 8 |
"github.com/docker/distribution/reference" |
| 9 | 9 |
"github.com/docker/docker/api/types" |
| 10 |
+ "github.com/docker/docker/api/types/filters" |
|
| 10 | 11 |
"github.com/docker/docker/image" |
| 11 | 12 |
"github.com/docker/docker/layer" |
| 12 |
- "github.com/docker/docker/pkg/parsers/filters" |
|
| 13 | 13 |
) |
| 14 | 14 |
|
| 15 | 15 |
var acceptedImageFilterTags = map[string]bool{
|
| ... | ... |
@@ -31,7 +31,7 @@ func (daemon *Daemon) Map() map[image.ID]*image.Image {
|
| 31 | 31 |
} |
| 32 | 32 |
|
| 33 | 33 |
// Images returns a filtered list of images. filterArgs is a JSON-encoded set |
| 34 |
-// of filter arguments which will be interpreted by pkg/parsers/filters. |
|
| 34 |
+// of filter arguments which will be interpreted by api/types/filters. |
|
| 35 | 35 |
// filter is a shell glob string applied to repository names. The argument |
| 36 | 36 |
// named all controls whether all images in the graph are filtered, or just |
| 37 | 37 |
// the heads. |
| ... | ... |
@@ -8,11 +8,11 @@ import ( |
| 8 | 8 |
|
| 9 | 9 |
"github.com/Sirupsen/logrus" |
| 10 | 10 |
"github.com/docker/docker/api/types" |
| 11 |
+ "github.com/docker/docker/api/types/filters" |
|
| 11 | 12 |
"github.com/docker/docker/container" |
| 12 | 13 |
"github.com/docker/docker/image" |
| 13 | 14 |
"github.com/docker/docker/pkg/graphdb" |
| 14 | 15 |
"github.com/docker/docker/pkg/nat" |
| 15 |
- "github.com/docker/docker/pkg/parsers/filters" |
|
| 16 | 16 |
) |
| 17 | 17 |
|
| 18 | 18 |
// iterationAction represents possible outcomes happening during the container iteration. |
| ... | ... |
@@ -9,9 +9,9 @@ import ( |
| 9 | 9 |
"strings" |
| 10 | 10 |
|
| 11 | 11 |
"github.com/docker/docker/api/types" |
| 12 |
+ "github.com/docker/docker/api/types/filters" |
|
| 12 | 13 |
"github.com/docker/docker/api/types/network" |
| 13 | 14 |
"github.com/docker/docker/pkg/integration/checker" |
| 14 |
- "github.com/docker/docker/pkg/parsers/filters" |
|
| 15 | 15 |
"github.com/go-check/check" |
| 16 | 16 |
) |
| 17 | 17 |
|
| 18 | 18 |
deleted file mode 100644 |
| ... | ... |
@@ -1,241 +0,0 @@ |
| 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 |
- |
|
| 13 |
-// Args stores filter arguments as map key:{array of values}.
|
|
| 14 |
-// It contains a aggregation of the list of arguments (which are in the form |
|
| 15 |
-// of -f 'key=value') based on the key, and store values for the same key |
|
| 16 |
-// in an slice. |
|
| 17 |
-// e.g given -f 'label=label1=1' -f 'label=label2=2' -f 'image.name=ubuntu' |
|
| 18 |
-// the args will be {'label': {'label1=1','label2=2'}, 'image.name', {'ubuntu'}}
|
|
| 19 |
-type Args struct {
|
|
| 20 |
- fields map[string]map[string]bool |
|
| 21 |
-} |
|
| 22 |
- |
|
| 23 |
-// NewArgs initializes a new Args struct. |
|
| 24 |
-func NewArgs() Args {
|
|
| 25 |
- return Args{fields: map[string]map[string]bool{}}
|
|
| 26 |
-} |
|
| 27 |
- |
|
| 28 |
-// ParseFlag parses the argument to the filter flag. Like |
|
| 29 |
-// |
|
| 30 |
-// `docker ps -f 'created=today' -f 'image.name=ubuntu*'` |
|
| 31 |
-// |
|
| 32 |
-// If prev map is provided, then it is appended to, and returned. By default a new |
|
| 33 |
-// map is created. |
|
| 34 |
-func ParseFlag(arg string, prev Args) (Args, error) {
|
|
| 35 |
- filters := prev |
|
| 36 |
- if len(arg) == 0 {
|
|
| 37 |
- return filters, nil |
|
| 38 |
- } |
|
| 39 |
- |
|
| 40 |
- if !strings.Contains(arg, "=") {
|
|
| 41 |
- return filters, ErrBadFormat |
|
| 42 |
- } |
|
| 43 |
- |
|
| 44 |
- f := strings.SplitN(arg, "=", 2) |
|
| 45 |
- |
|
| 46 |
- name := strings.ToLower(strings.TrimSpace(f[0])) |
|
| 47 |
- value := strings.TrimSpace(f[1]) |
|
| 48 |
- |
|
| 49 |
- filters.Add(name, value) |
|
| 50 |
- |
|
| 51 |
- return filters, nil |
|
| 52 |
-} |
|
| 53 |
- |
|
| 54 |
-// ErrBadFormat is an error returned in case of bad format for a filter. |
|
| 55 |
-var ErrBadFormat = errors.New("bad format of filter (expected name=value)")
|
|
| 56 |
- |
|
| 57 |
-// ToParam packs the Args into an string for easy transport from client to server. |
|
| 58 |
-func ToParam(a Args) (string, error) {
|
|
| 59 |
- // this way we don't URL encode {}, just empty space
|
|
| 60 |
- if a.Len() == 0 {
|
|
| 61 |
- return "", nil |
|
| 62 |
- } |
|
| 63 |
- |
|
| 64 |
- buf, err := json.Marshal(a.fields) |
|
| 65 |
- if err != nil {
|
|
| 66 |
- return "", err |
|
| 67 |
- } |
|
| 68 |
- return string(buf), nil |
|
| 69 |
-} |
|
| 70 |
- |
|
| 71 |
-// FromParam unpacks the filter Args. |
|
| 72 |
-func FromParam(p string) (Args, error) {
|
|
| 73 |
- if len(p) == 0 {
|
|
| 74 |
- return NewArgs(), nil |
|
| 75 |
- } |
|
| 76 |
- |
|
| 77 |
- r := strings.NewReader(p) |
|
| 78 |
- d := json.NewDecoder(r) |
|
| 79 |
- |
|
| 80 |
- m := map[string]map[string]bool{}
|
|
| 81 |
- if err := d.Decode(&m); err != nil {
|
|
| 82 |
- r.Seek(0, 0) |
|
| 83 |
- |
|
| 84 |
- // Allow parsing old arguments in slice format. |
|
| 85 |
- // Because other libraries might be sending them in this format. |
|
| 86 |
- deprecated := map[string][]string{}
|
|
| 87 |
- if deprecatedErr := d.Decode(&deprecated); deprecatedErr == nil {
|
|
| 88 |
- m = deprecatedArgs(deprecated) |
|
| 89 |
- } else {
|
|
| 90 |
- return NewArgs(), err |
|
| 91 |
- } |
|
| 92 |
- } |
|
| 93 |
- return Args{m}, nil
|
|
| 94 |
-} |
|
| 95 |
- |
|
| 96 |
-// Get returns the list of values associates with a field. |
|
| 97 |
-// It returns a slice of strings to keep backwards compatibility with old code. |
|
| 98 |
-func (filters Args) Get(field string) []string {
|
|
| 99 |
- values := filters.fields[field] |
|
| 100 |
- if values == nil {
|
|
| 101 |
- return make([]string, 0) |
|
| 102 |
- } |
|
| 103 |
- slice := make([]string, 0, len(values)) |
|
| 104 |
- for key := range values {
|
|
| 105 |
- slice = append(slice, key) |
|
| 106 |
- } |
|
| 107 |
- return slice |
|
| 108 |
-} |
|
| 109 |
- |
|
| 110 |
-// Add adds a new value to a filter field. |
|
| 111 |
-func (filters Args) Add(name, value string) {
|
|
| 112 |
- if _, ok := filters.fields[name]; ok {
|
|
| 113 |
- filters.fields[name][value] = true |
|
| 114 |
- } else {
|
|
| 115 |
- filters.fields[name] = map[string]bool{value: true}
|
|
| 116 |
- } |
|
| 117 |
-} |
|
| 118 |
- |
|
| 119 |
-// Del removes a value from a filter field. |
|
| 120 |
-func (filters Args) Del(name, value string) {
|
|
| 121 |
- if _, ok := filters.fields[name]; ok {
|
|
| 122 |
- delete(filters.fields[name], value) |
|
| 123 |
- } |
|
| 124 |
-} |
|
| 125 |
- |
|
| 126 |
-// Len returns the number of fields in the arguments. |
|
| 127 |
-func (filters Args) Len() int {
|
|
| 128 |
- return len(filters.fields) |
|
| 129 |
-} |
|
| 130 |
- |
|
| 131 |
-// MatchKVList returns true if the values for the specified field matches the ones |
|
| 132 |
-// from the sources. |
|
| 133 |
-// e.g. given Args are {'label': {'label1=1','label2=1'}, 'image.name', {'ubuntu'}},
|
|
| 134 |
-// field is 'label' and sources are {'label1': '1', 'label2': '2'}
|
|
| 135 |
-// it returns true. |
|
| 136 |
-func (filters Args) MatchKVList(field string, sources map[string]string) bool {
|
|
| 137 |
- fieldValues := filters.fields[field] |
|
| 138 |
- |
|
| 139 |
- //do not filter if there is no filter set or cannot determine filter |
|
| 140 |
- if len(fieldValues) == 0 {
|
|
| 141 |
- return true |
|
| 142 |
- } |
|
| 143 |
- |
|
| 144 |
- if sources == nil || len(sources) == 0 {
|
|
| 145 |
- return false |
|
| 146 |
- } |
|
| 147 |
- |
|
| 148 |
- for name2match := range fieldValues {
|
|
| 149 |
- testKV := strings.SplitN(name2match, "=", 2) |
|
| 150 |
- |
|
| 151 |
- v, ok := sources[testKV[0]] |
|
| 152 |
- if !ok {
|
|
| 153 |
- return false |
|
| 154 |
- } |
|
| 155 |
- if len(testKV) == 2 && testKV[1] != v {
|
|
| 156 |
- return false |
|
| 157 |
- } |
|
| 158 |
- } |
|
| 159 |
- |
|
| 160 |
- return true |
|
| 161 |
-} |
|
| 162 |
- |
|
| 163 |
-// Match returns true if the values for the specified field matches the source string |
|
| 164 |
-// e.g. given Args are {'label': {'label1=1','label2=1'}, 'image.name', {'ubuntu'}},
|
|
| 165 |
-// field is 'image.name' and source is 'ubuntu' |
|
| 166 |
-// it returns true. |
|
| 167 |
-func (filters Args) Match(field, source string) bool {
|
|
| 168 |
- if filters.ExactMatch(field, source) {
|
|
| 169 |
- return true |
|
| 170 |
- } |
|
| 171 |
- |
|
| 172 |
- fieldValues := filters.fields[field] |
|
| 173 |
- for name2match := range fieldValues {
|
|
| 174 |
- match, err := regexp.MatchString(name2match, source) |
|
| 175 |
- if err != nil {
|
|
| 176 |
- continue |
|
| 177 |
- } |
|
| 178 |
- if match {
|
|
| 179 |
- return true |
|
| 180 |
- } |
|
| 181 |
- } |
|
| 182 |
- return false |
|
| 183 |
-} |
|
| 184 |
- |
|
| 185 |
-// ExactMatch returns true if the source matches exactly one of the filters. |
|
| 186 |
-func (filters Args) ExactMatch(field, source string) bool {
|
|
| 187 |
- fieldValues, ok := filters.fields[field] |
|
| 188 |
- //do not filter if there is no filter set or cannot determine filter |
|
| 189 |
- if !ok || len(fieldValues) == 0 {
|
|
| 190 |
- return true |
|
| 191 |
- } |
|
| 192 |
- |
|
| 193 |
- // try to march full name value to avoid O(N) regular expression matching |
|
| 194 |
- if fieldValues[source] {
|
|
| 195 |
- return true |
|
| 196 |
- } |
|
| 197 |
- return false |
|
| 198 |
-} |
|
| 199 |
- |
|
| 200 |
-// Include returns true if the name of the field to filter is in the filters. |
|
| 201 |
-func (filters Args) Include(field string) bool {
|
|
| 202 |
- _, ok := filters.fields[field] |
|
| 203 |
- return ok |
|
| 204 |
-} |
|
| 205 |
- |
|
| 206 |
-// Validate ensures that all the fields in the filter are valid. |
|
| 207 |
-// It returns an error as soon as it finds an invalid field. |
|
| 208 |
-func (filters Args) Validate(accepted map[string]bool) error {
|
|
| 209 |
- for name := range filters.fields {
|
|
| 210 |
- if !accepted[name] {
|
|
| 211 |
- return fmt.Errorf("Invalid filter '%s'", name)
|
|
| 212 |
- } |
|
| 213 |
- } |
|
| 214 |
- return nil |
|
| 215 |
-} |
|
| 216 |
- |
|
| 217 |
-// WalkValues iterates over the list of filtered values for a field. |
|
| 218 |
-// It stops the iteration if it finds an error and it returns that error. |
|
| 219 |
-func (filters Args) WalkValues(field string, op func(value string) error) error {
|
|
| 220 |
- if _, ok := filters.fields[field]; !ok {
|
|
| 221 |
- return nil |
|
| 222 |
- } |
|
| 223 |
- for v := range filters.fields[field] {
|
|
| 224 |
- if err := op(v); err != nil {
|
|
| 225 |
- return err |
|
| 226 |
- } |
|
| 227 |
- } |
|
| 228 |
- return nil |
|
| 229 |
-} |
|
| 230 |
- |
|
| 231 |
-func deprecatedArgs(d map[string][]string) map[string]map[string]bool {
|
|
| 232 |
- m := map[string]map[string]bool{}
|
|
| 233 |
- for k, v := range d {
|
|
| 234 |
- values := map[string]bool{}
|
|
| 235 |
- for _, vv := range v {
|
|
| 236 |
- values[vv] = true |
|
| 237 |
- } |
|
| 238 |
- m[k] = values |
|
| 239 |
- } |
|
| 240 |
- return m |
|
| 241 |
-} |
| 242 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,351 +0,0 @@ |
| 1 |
-package filters |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "testing" |
|
| 6 |
-) |
|
| 7 |
- |
|
| 8 |
-func TestParseArgs(t *testing.T) {
|
|
| 9 |
- // equivalent of `docker ps -f 'created=today' -f 'image.name=ubuntu*' -f 'image.name=*untu'` |
|
| 10 |
- flagArgs := []string{
|
|
| 11 |
- "created=today", |
|
| 12 |
- "image.name=ubuntu*", |
|
| 13 |
- "image.name=*untu", |
|
| 14 |
- } |
|
| 15 |
- var ( |
|
| 16 |
- args = NewArgs() |
|
| 17 |
- err error |
|
| 18 |
- ) |
|
| 19 |
- for i := range flagArgs {
|
|
| 20 |
- args, err = ParseFlag(flagArgs[i], args) |
|
| 21 |
- if err != nil {
|
|
| 22 |
- t.Errorf("failed to parse %s: %s", flagArgs[i], err)
|
|
| 23 |
- } |
|
| 24 |
- } |
|
| 25 |
- if len(args.Get("created")) != 1 {
|
|
| 26 |
- t.Errorf("failed to set this arg")
|
|
| 27 |
- } |
|
| 28 |
- if len(args.Get("image.name")) != 2 {
|
|
| 29 |
- t.Errorf("the args should have collapsed")
|
|
| 30 |
- } |
|
| 31 |
-} |
|
| 32 |
- |
|
| 33 |
-func TestParseArgsEdgeCase(t *testing.T) {
|
|
| 34 |
- var filters Args |
|
| 35 |
- args, err := ParseFlag("", filters)
|
|
| 36 |
- if err != nil {
|
|
| 37 |
- t.Fatal(err) |
|
| 38 |
- } |
|
| 39 |
- if args.Len() != 0 {
|
|
| 40 |
- t.Fatalf("Expected an empty Args (map), got %v", args)
|
|
| 41 |
- } |
|
| 42 |
- if args, err = ParseFlag("anything", args); err == nil || err != ErrBadFormat {
|
|
| 43 |
- t.Fatalf("Expected ErrBadFormat, got %v", err)
|
|
| 44 |
- } |
|
| 45 |
-} |
|
| 46 |
- |
|
| 47 |
-func TestToParam(t *testing.T) {
|
|
| 48 |
- fields := map[string]map[string]bool{
|
|
| 49 |
- "created": {"today": true},
|
|
| 50 |
- "image.name": {"ubuntu*": true, "*untu": true},
|
|
| 51 |
- } |
|
| 52 |
- a := Args{fields: fields}
|
|
| 53 |
- |
|
| 54 |
- _, err := ToParam(a) |
|
| 55 |
- if err != nil {
|
|
| 56 |
- t.Errorf("failed to marshal the filters: %s", err)
|
|
| 57 |
- } |
|
| 58 |
-} |
|
| 59 |
- |
|
| 60 |
-func TestFromParam(t *testing.T) {
|
|
| 61 |
- invalids := []string{
|
|
| 62 |
- "anything", |
|
| 63 |
- "['a','list']", |
|
| 64 |
- "{'key': 'value'}",
|
|
| 65 |
- `{"key": "value"}`,
|
|
| 66 |
- } |
|
| 67 |
- valid := map[*Args][]string{
|
|
| 68 |
- &Args{fields: map[string]map[string]bool{"key": {"value": true}}}: {
|
|
| 69 |
- `{"key": ["value"]}`,
|
|
| 70 |
- `{"key": {"value": true}}`,
|
|
| 71 |
- }, |
|
| 72 |
- &Args{fields: map[string]map[string]bool{"key": {"value1": true, "value2": true}}}: {
|
|
| 73 |
- `{"key": ["value1", "value2"]}`,
|
|
| 74 |
- `{"key": {"value1": true, "value2": true}}`,
|
|
| 75 |
- }, |
|
| 76 |
- &Args{fields: map[string]map[string]bool{"key1": {"value1": true}, "key2": {"value2": true}}}: {
|
|
| 77 |
- `{"key1": ["value1"], "key2": ["value2"]}`,
|
|
| 78 |
- `{"key1": {"value1": true}, "key2": {"value2": true}}`,
|
|
| 79 |
- }, |
|
| 80 |
- } |
|
| 81 |
- |
|
| 82 |
- for _, invalid := range invalids {
|
|
| 83 |
- if _, err := FromParam(invalid); err == nil {
|
|
| 84 |
- t.Fatalf("Expected an error with %v, got nothing", invalid)
|
|
| 85 |
- } |
|
| 86 |
- } |
|
| 87 |
- |
|
| 88 |
- for expectedArgs, matchers := range valid {
|
|
| 89 |
- for _, json := range matchers {
|
|
| 90 |
- args, err := FromParam(json) |
|
| 91 |
- if err != nil {
|
|
| 92 |
- t.Fatal(err) |
|
| 93 |
- } |
|
| 94 |
- if args.Len() != expectedArgs.Len() {
|
|
| 95 |
- t.Fatalf("Expected %v, go %v", expectedArgs, args)
|
|
| 96 |
- } |
|
| 97 |
- for key, expectedValues := range expectedArgs.fields {
|
|
| 98 |
- values := args.Get(key) |
|
| 99 |
- |
|
| 100 |
- if len(values) != len(expectedValues) {
|
|
| 101 |
- t.Fatalf("Expected %v, go %v", expectedArgs, args)
|
|
| 102 |
- } |
|
| 103 |
- |
|
| 104 |
- for _, v := range values {
|
|
| 105 |
- if !expectedValues[v] {
|
|
| 106 |
- t.Fatalf("Expected %v, go %v", expectedArgs, args)
|
|
| 107 |
- } |
|
| 108 |
- } |
|
| 109 |
- } |
|
| 110 |
- } |
|
| 111 |
- } |
|
| 112 |
-} |
|
| 113 |
- |
|
| 114 |
-func TestEmpty(t *testing.T) {
|
|
| 115 |
- a := Args{}
|
|
| 116 |
- v, err := ToParam(a) |
|
| 117 |
- if err != nil {
|
|
| 118 |
- t.Errorf("failed to marshal the filters: %s", err)
|
|
| 119 |
- } |
|
| 120 |
- v1, err := FromParam(v) |
|
| 121 |
- if err != nil {
|
|
| 122 |
- t.Errorf("%s", err)
|
|
| 123 |
- } |
|
| 124 |
- if a.Len() != v1.Len() {
|
|
| 125 |
- t.Errorf("these should both be empty sets")
|
|
| 126 |
- } |
|
| 127 |
-} |
|
| 128 |
- |
|
| 129 |
-func TestArgsMatchKVListEmptySources(t *testing.T) {
|
|
| 130 |
- args := NewArgs() |
|
| 131 |
- if !args.MatchKVList("created", map[string]string{}) {
|
|
| 132 |
- t.Fatalf("Expected true for (%v,created), got true", args)
|
|
| 133 |
- } |
|
| 134 |
- |
|
| 135 |
- args = Args{map[string]map[string]bool{"created": {"today": true}}}
|
|
| 136 |
- if args.MatchKVList("created", map[string]string{}) {
|
|
| 137 |
- t.Fatalf("Expected false for (%v,created), got true", args)
|
|
| 138 |
- } |
|
| 139 |
-} |
|
| 140 |
- |
|
| 141 |
-func TestArgsMatchKVList(t *testing.T) {
|
|
| 142 |
- // Not empty sources |
|
| 143 |
- sources := map[string]string{
|
|
| 144 |
- "key1": "value1", |
|
| 145 |
- "key2": "value2", |
|
| 146 |
- "key3": "value3", |
|
| 147 |
- } |
|
| 148 |
- |
|
| 149 |
- matches := map[*Args]string{
|
|
| 150 |
- &Args{}: "field",
|
|
| 151 |
- &Args{map[string]map[string]bool{
|
|
| 152 |
- "created": map[string]bool{"today": true},
|
|
| 153 |
- "labels": map[string]bool{"key1": true}},
|
|
| 154 |
- }: "labels", |
|
| 155 |
- &Args{map[string]map[string]bool{
|
|
| 156 |
- "created": map[string]bool{"today": true},
|
|
| 157 |
- "labels": map[string]bool{"key1=value1": true}},
|
|
| 158 |
- }: "labels", |
|
| 159 |
- } |
|
| 160 |
- |
|
| 161 |
- for args, field := range matches {
|
|
| 162 |
- if args.MatchKVList(field, sources) != true {
|
|
| 163 |
- t.Fatalf("Expected true for %v on %v, got false", sources, args)
|
|
| 164 |
- } |
|
| 165 |
- } |
|
| 166 |
- |
|
| 167 |
- differs := map[*Args]string{
|
|
| 168 |
- &Args{map[string]map[string]bool{
|
|
| 169 |
- "created": map[string]bool{"today": true}},
|
|
| 170 |
- }: "created", |
|
| 171 |
- &Args{map[string]map[string]bool{
|
|
| 172 |
- "created": map[string]bool{"today": true},
|
|
| 173 |
- "labels": map[string]bool{"key4": true}},
|
|
| 174 |
- }: "labels", |
|
| 175 |
- &Args{map[string]map[string]bool{
|
|
| 176 |
- "created": map[string]bool{"today": true},
|
|
| 177 |
- "labels": map[string]bool{"key1=value3": true}},
|
|
| 178 |
- }: "labels", |
|
| 179 |
- } |
|
| 180 |
- |
|
| 181 |
- for args, field := range differs {
|
|
| 182 |
- if args.MatchKVList(field, sources) != false {
|
|
| 183 |
- t.Fatalf("Expected false for %v on %v, got true", sources, args)
|
|
| 184 |
- } |
|
| 185 |
- } |
|
| 186 |
-} |
|
| 187 |
- |
|
| 188 |
-func TestArgsMatch(t *testing.T) {
|
|
| 189 |
- source := "today" |
|
| 190 |
- |
|
| 191 |
- matches := map[*Args]string{
|
|
| 192 |
- &Args{}: "field",
|
|
| 193 |
- &Args{map[string]map[string]bool{
|
|
| 194 |
- "created": map[string]bool{"today": true}},
|
|
| 195 |
- }: "today", |
|
| 196 |
- &Args{map[string]map[string]bool{
|
|
| 197 |
- "created": map[string]bool{"to*": true}},
|
|
| 198 |
- }: "created", |
|
| 199 |
- &Args{map[string]map[string]bool{
|
|
| 200 |
- "created": map[string]bool{"to(.*)": true}},
|
|
| 201 |
- }: "created", |
|
| 202 |
- &Args{map[string]map[string]bool{
|
|
| 203 |
- "created": map[string]bool{"tod": true}},
|
|
| 204 |
- }: "created", |
|
| 205 |
- &Args{map[string]map[string]bool{
|
|
| 206 |
- "created": map[string]bool{"anyting": true, "to*": true}},
|
|
| 207 |
- }: "created", |
|
| 208 |
- } |
|
| 209 |
- |
|
| 210 |
- for args, field := range matches {
|
|
| 211 |
- if args.Match(field, source) != true {
|
|
| 212 |
- t.Fatalf("Expected true for %v on %v, got false", source, args)
|
|
| 213 |
- } |
|
| 214 |
- } |
|
| 215 |
- |
|
| 216 |
- differs := map[*Args]string{
|
|
| 217 |
- &Args{map[string]map[string]bool{
|
|
| 218 |
- "created": map[string]bool{"tomorrow": true}},
|
|
| 219 |
- }: "created", |
|
| 220 |
- &Args{map[string]map[string]bool{
|
|
| 221 |
- "created": map[string]bool{"to(day": true}},
|
|
| 222 |
- }: "created", |
|
| 223 |
- &Args{map[string]map[string]bool{
|
|
| 224 |
- "created": map[string]bool{"tom(.*)": true}},
|
|
| 225 |
- }: "created", |
|
| 226 |
- &Args{map[string]map[string]bool{
|
|
| 227 |
- "created": map[string]bool{"tom": true}},
|
|
| 228 |
- }: "created", |
|
| 229 |
- &Args{map[string]map[string]bool{
|
|
| 230 |
- "created": map[string]bool{"today1": true},
|
|
| 231 |
- "labels": map[string]bool{"today": true}},
|
|
| 232 |
- }: "created", |
|
| 233 |
- } |
|
| 234 |
- |
|
| 235 |
- for args, field := range differs {
|
|
| 236 |
- if args.Match(field, source) != false {
|
|
| 237 |
- t.Fatalf("Expected false for %v on %v, got true", source, args)
|
|
| 238 |
- } |
|
| 239 |
- } |
|
| 240 |
-} |
|
| 241 |
- |
|
| 242 |
-func TestAdd(t *testing.T) {
|
|
| 243 |
- f := NewArgs() |
|
| 244 |
- f.Add("status", "running")
|
|
| 245 |
- v := f.fields["status"] |
|
| 246 |
- if len(v) != 1 || !v["running"] {
|
|
| 247 |
- t.Fatalf("Expected to include a running status, got %v", v)
|
|
| 248 |
- } |
|
| 249 |
- |
|
| 250 |
- f.Add("status", "paused")
|
|
| 251 |
- if len(v) != 2 || !v["paused"] {
|
|
| 252 |
- t.Fatalf("Expected to include a paused status, got %v", v)
|
|
| 253 |
- } |
|
| 254 |
-} |
|
| 255 |
- |
|
| 256 |
-func TestDel(t *testing.T) {
|
|
| 257 |
- f := NewArgs() |
|
| 258 |
- f.Add("status", "running")
|
|
| 259 |
- f.Del("status", "running")
|
|
| 260 |
- v := f.fields["status"] |
|
| 261 |
- if v["running"] {
|
|
| 262 |
- t.Fatalf("Expected to not include a running status filter, got true")
|
|
| 263 |
- } |
|
| 264 |
-} |
|
| 265 |
- |
|
| 266 |
-func TestLen(t *testing.T) {
|
|
| 267 |
- f := NewArgs() |
|
| 268 |
- if f.Len() != 0 {
|
|
| 269 |
- t.Fatalf("Expected to not include any field")
|
|
| 270 |
- } |
|
| 271 |
- f.Add("status", "running")
|
|
| 272 |
- if f.Len() != 1 {
|
|
| 273 |
- t.Fatalf("Expected to include one field")
|
|
| 274 |
- } |
|
| 275 |
-} |
|
| 276 |
- |
|
| 277 |
-func TestExactMatch(t *testing.T) {
|
|
| 278 |
- f := NewArgs() |
|
| 279 |
- |
|
| 280 |
- if !f.ExactMatch("status", "running") {
|
|
| 281 |
- t.Fatalf("Expected to match `running` when there are no filters, got false")
|
|
| 282 |
- } |
|
| 283 |
- |
|
| 284 |
- f.Add("status", "running")
|
|
| 285 |
- f.Add("status", "pause*")
|
|
| 286 |
- |
|
| 287 |
- if !f.ExactMatch("status", "running") {
|
|
| 288 |
- t.Fatalf("Expected to match `running` with one of the filters, got false")
|
|
| 289 |
- } |
|
| 290 |
- |
|
| 291 |
- if f.ExactMatch("status", "paused") {
|
|
| 292 |
- t.Fatalf("Expected to not match `paused` with one of the filters, got true")
|
|
| 293 |
- } |
|
| 294 |
-} |
|
| 295 |
- |
|
| 296 |
-func TestInclude(t *testing.T) {
|
|
| 297 |
- f := NewArgs() |
|
| 298 |
- if f.Include("status") {
|
|
| 299 |
- t.Fatalf("Expected to not include a status key, got true")
|
|
| 300 |
- } |
|
| 301 |
- f.Add("status", "running")
|
|
| 302 |
- if !f.Include("status") {
|
|
| 303 |
- t.Fatalf("Expected to include a status key, got false")
|
|
| 304 |
- } |
|
| 305 |
-} |
|
| 306 |
- |
|
| 307 |
-func TestValidate(t *testing.T) {
|
|
| 308 |
- f := NewArgs() |
|
| 309 |
- f.Add("status", "running")
|
|
| 310 |
- |
|
| 311 |
- valid := map[string]bool{
|
|
| 312 |
- "status": true, |
|
| 313 |
- "dangling": true, |
|
| 314 |
- } |
|
| 315 |
- |
|
| 316 |
- if err := f.Validate(valid); err != nil {
|
|
| 317 |
- t.Fatal(err) |
|
| 318 |
- } |
|
| 319 |
- |
|
| 320 |
- f.Add("bogus", "running")
|
|
| 321 |
- if err := f.Validate(valid); err == nil {
|
|
| 322 |
- t.Fatalf("Expected to return an error, got nil")
|
|
| 323 |
- } |
|
| 324 |
-} |
|
| 325 |
- |
|
| 326 |
-func TestWalkValues(t *testing.T) {
|
|
| 327 |
- f := NewArgs() |
|
| 328 |
- f.Add("status", "running")
|
|
| 329 |
- f.Add("status", "paused")
|
|
| 330 |
- |
|
| 331 |
- f.WalkValues("status", func(value string) error {
|
|
| 332 |
- if value != "running" && value != "paused" {
|
|
| 333 |
- t.Fatalf("Unexpected value %s", value)
|
|
| 334 |
- } |
|
| 335 |
- return nil |
|
| 336 |
- }) |
|
| 337 |
- |
|
| 338 |
- err := f.WalkValues("status", func(value string) error {
|
|
| 339 |
- return fmt.Errorf("return")
|
|
| 340 |
- }) |
|
| 341 |
- if err == nil {
|
|
| 342 |
- t.Fatalf("Expected to get an error, got nil")
|
|
| 343 |
- } |
|
| 344 |
- |
|
| 345 |
- err = f.WalkValues("foo", func(value string) error {
|
|
| 346 |
- return fmt.Errorf("return")
|
|
| 347 |
- }) |
|
| 348 |
- if err != nil {
|
|
| 349 |
- t.Fatalf("Expected to not iterate when the field doesn't exist, got %v", err)
|
|
| 350 |
- } |
|
| 351 |
-} |