Improves the current filtering implementation complixity.
Currently, the best case is O(N) and worst case O(N^2) for key-value filtering.
In the new implementation, the best case is O(1) and worst case O(N), again for key-value filtering.
Signed-off-by: David Calavera <david.calavera@gmail.com>
| ... | ... |
@@ -26,7 +26,7 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
| 26 | 26 |
|
| 27 | 27 |
var ( |
| 28 | 28 |
v = url.Values{}
|
| 29 |
- eventFilterArgs = filters.Args{}
|
|
| 29 |
+ eventFilterArgs = filters.NewArgs() |
|
| 30 | 30 |
) |
| 31 | 31 |
|
| 32 | 32 |
// Consolidate all filter flags, and sanity check them early. |
| ... | ... |
@@ -53,7 +53,7 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
| 53 | 53 |
} |
| 54 | 54 |
v.Set("until", ts)
|
| 55 | 55 |
} |
| 56 |
- if len(eventFilterArgs) > 0 {
|
|
| 56 |
+ if eventFilterArgs.Len() > 0 {
|
|
| 57 | 57 |
filterJSON, err := filters.ToParam(eventFilterArgs) |
| 58 | 58 |
if err != nil {
|
| 59 | 59 |
return err |
| ... | ... |
@@ -36,7 +36,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
| 36 | 36 |
|
| 37 | 37 |
// Consolidate all filter flags, and sanity check them early. |
| 38 | 38 |
// They'll get process in the daemon/server. |
| 39 |
- imageFilterArgs := filters.Args{}
|
|
| 39 |
+ imageFilterArgs := filters.NewArgs() |
|
| 40 | 40 |
for _, f := range flFilter.GetAll() {
|
| 41 | 41 |
var err error |
| 42 | 42 |
imageFilterArgs, err = filters.ParseFlag(f, imageFilterArgs) |
| ... | ... |
@@ -47,7 +47,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
| 47 | 47 |
|
| 48 | 48 |
matchName := cmd.Arg(0) |
| 49 | 49 |
v := url.Values{}
|
| 50 |
- if len(imageFilterArgs) > 0 {
|
|
| 50 |
+ if imageFilterArgs.Len() > 0 {
|
|
| 51 | 51 |
filterJSON, err := filters.ToParam(imageFilterArgs) |
| 52 | 52 |
if err != nil {
|
| 53 | 53 |
return err |
| ... | ... |
@@ -20,7 +20,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
| 20 | 20 |
var ( |
| 21 | 21 |
err error |
| 22 | 22 |
|
| 23 |
- psFilterArgs = filters.Args{}
|
|
| 23 |
+ psFilterArgs = filters.NewArgs() |
|
| 24 | 24 |
v = url.Values{}
|
| 25 | 25 |
|
| 26 | 26 |
cmd = Cli.Subcmd("ps", nil, Cli.DockerCommands["ps"].Description, true)
|
| ... | ... |
@@ -72,7 +72,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
| 72 | 72 |
} |
| 73 | 73 |
} |
| 74 | 74 |
|
| 75 |
- if len(psFilterArgs) > 0 {
|
|
| 75 |
+ if psFilterArgs.Len() > 0 {
|
|
| 76 | 76 |
filterJSON, err := filters.ToParam(psFilterArgs) |
| 77 | 77 |
if err != nil {
|
| 78 | 78 |
return err |
| ... | ... |
@@ -54,7 +54,7 @@ func (cli *DockerCli) CmdVolumeLs(args ...string) error {
|
| 54 | 54 |
cmd.Require(flag.Exact, 0) |
| 55 | 55 |
cmd.ParseFlags(args, true) |
| 56 | 56 |
|
| 57 |
- volFilterArgs := filters.Args{}
|
|
| 57 |
+ volFilterArgs := filters.NewArgs() |
|
| 58 | 58 |
for _, f := range flFilter.GetAll() {
|
| 59 | 59 |
var err error |
| 60 | 60 |
volFilterArgs, err = filters.ParseFlag(f, volFilterArgs) |
| ... | ... |
@@ -64,7 +64,7 @@ func (cli *DockerCli) CmdVolumeLs(args ...string) error {
|
| 64 | 64 |
} |
| 65 | 65 |
|
| 66 | 66 |
v := url.Values{}
|
| 67 |
- if len(volFilterArgs) > 0 {
|
|
| 67 |
+ if volFilterArgs.Len() > 0 {
|
|
| 68 | 68 |
filterJSON, err := filters.ToParam(volFilterArgs) |
| 69 | 69 |
if err != nil {
|
| 70 | 70 |
return err |
| ... | ... |
@@ -29,27 +29,23 @@ func (n *networkRouter) getNetworksList(ctx context.Context, w http.ResponseWrit |
| 29 | 29 |
} |
| 30 | 30 |
|
| 31 | 31 |
list := []*types.NetworkResource{}
|
| 32 |
- var nameFilter, idFilter bool |
|
| 33 |
- var names, ids []string |
|
| 34 |
- if names, nameFilter = netFilters["name"]; nameFilter {
|
|
| 35 |
- for _, name := range names {
|
|
| 36 |
- if nw, err := n.backend.GetNetwork(name, daemon.NetworkByName); err == nil {
|
|
| 37 |
- list = append(list, buildNetworkResource(nw)) |
|
| 38 |
- } else {
|
|
| 39 |
- logrus.Errorf("failed to get network for filter=%s : %v", name, err)
|
|
| 40 |
- } |
|
| 32 |
+ netFilters.WalkValues("name", func(name string) error {
|
|
| 33 |
+ if nw, err := n.backend.GetNetwork(name, daemon.NetworkByName); err == nil {
|
|
| 34 |
+ list = append(list, buildNetworkResource(nw)) |
|
| 35 |
+ } else {
|
|
| 36 |
+ logrus.Errorf("failed to get network for filter=%s : %v", name, err)
|
|
| 41 | 37 |
} |
| 42 |
- } |
|
| 38 |
+ return nil |
|
| 39 |
+ }) |
|
| 43 | 40 |
|
| 44 |
- if ids, idFilter = netFilters["id"]; idFilter {
|
|
| 45 |
- for _, id := range ids {
|
|
| 46 |
- for _, nw := range n.backend.GetNetworksByID(id) {
|
|
| 47 |
- list = append(list, buildNetworkResource(nw)) |
|
| 48 |
- } |
|
| 41 |
+ netFilters.WalkValues("id", func(id string) error {
|
|
| 42 |
+ for _, nw := range n.backend.GetNetworksByID(id) {
|
|
| 43 |
+ list = append(list, buildNetworkResource(nw)) |
|
| 49 | 44 |
} |
| 50 |
- } |
|
| 45 |
+ return nil |
|
| 46 |
+ }) |
|
| 51 | 47 |
|
| 52 |
- if !nameFilter && !idFilter {
|
|
| 48 |
+ if !netFilters.Include("name") && !netFilters.Include("id") {
|
|
| 53 | 49 |
nwList := n.backend.GetNetworksByID("")
|
| 54 | 50 |
for _, nw := range nwList {
|
| 55 | 51 |
list = append(list, buildNetworkResource(nw)) |
| ... | ... |
@@ -536,12 +536,11 @@ func (daemon *Daemon) GetByName(name string) (*Container, error) {
|
| 536 | 536 |
func (daemon *Daemon) GetEventFilter(filter filters.Args) *events.Filter {
|
| 537 | 537 |
// incoming container filter can be name, id or partial id, convert to |
| 538 | 538 |
// a full container id |
| 539 |
- for i, cn := range filter["container"] {
|
|
| 539 |
+ for _, cn := range filter.Get("container") {
|
|
| 540 | 540 |
c, err := daemon.Get(cn) |
| 541 |
- if err != nil {
|
|
| 542 |
- filter["container"][i] = "" |
|
| 543 |
- } else {
|
|
| 544 |
- filter["container"][i] = c.ID |
|
| 541 |
+ filter.Del("container", cn)
|
|
| 542 |
+ if err == nil {
|
|
| 543 |
+ filter.Add("container", c.ID)
|
|
| 545 | 544 |
} |
| 546 | 545 |
} |
| 547 | 546 |
return events.NewFilter(filter, daemon.GetLabels) |
| ... | ... |
@@ -19,14 +19,14 @@ func NewFilter(filter filters.Args, getLabels func(id string) map[string]string) |
| 19 | 19 |
|
| 20 | 20 |
// Include returns true when the event ev is included by the filters |
| 21 | 21 |
func (ef *Filter) Include(ev *jsonmessage.JSONMessage) bool {
|
| 22 |
- return isFieldIncluded(ev.Status, ef.filter["event"]) && |
|
| 23 |
- isFieldIncluded(ev.ID, ef.filter["container"]) && |
|
| 22 |
+ return ef.filter.ExactMatch("event", ev.Status) &&
|
|
| 23 |
+ ef.filter.ExactMatch("container", ev.ID) &&
|
|
| 24 | 24 |
ef.isImageIncluded(ev.ID, ev.From) && |
| 25 | 25 |
ef.isLabelFieldIncluded(ev.ID) |
| 26 | 26 |
} |
| 27 | 27 |
|
| 28 | 28 |
func (ef *Filter) isLabelFieldIncluded(id string) bool {
|
| 29 |
- if _, ok := ef.filter["label"]; !ok {
|
|
| 29 |
+ if !ef.filter.Include("label") {
|
|
| 30 | 30 |
return true |
| 31 | 31 |
} |
| 32 | 32 |
return ef.filter.MatchKVList("label", ef.getLabels(id))
|
| ... | ... |
@@ -37,31 +37,16 @@ func (ef *Filter) isLabelFieldIncluded(id string) bool {
|
| 37 | 37 |
// from an image will be included in the image events. Also compare both |
| 38 | 38 |
// against the stripped repo name without any tags. |
| 39 | 39 |
func (ef *Filter) isImageIncluded(eventID string, eventFrom string) bool {
|
| 40 |
- stripTag := func(image string) string {
|
|
| 41 |
- ref, err := reference.ParseNamed(image) |
|
| 42 |
- if err != nil {
|
|
| 43 |
- return image |
|
| 44 |
- } |
|
| 45 |
- return ref.Name() |
|
| 46 |
- } |
|
| 47 |
- |
|
| 48 |
- return isFieldIncluded(eventID, ef.filter["image"]) || |
|
| 49 |
- isFieldIncluded(eventFrom, ef.filter["image"]) || |
|
| 50 |
- isFieldIncluded(stripTag(eventID), ef.filter["image"]) || |
|
| 51 |
- isFieldIncluded(stripTag(eventFrom), ef.filter["image"]) |
|
| 40 |
+ return ef.filter.ExactMatch("image", eventID) ||
|
|
| 41 |
+ ef.filter.ExactMatch("image", eventFrom) ||
|
|
| 42 |
+ ef.filter.ExactMatch("image", stripTag(eventID)) ||
|
|
| 43 |
+ ef.filter.ExactMatch("image", stripTag(eventFrom))
|
|
| 52 | 44 |
} |
| 53 | 45 |
|
| 54 |
-func isFieldIncluded(field string, filter []string) bool {
|
|
| 55 |
- if len(field) == 0 {
|
|
| 56 |
- return true |
|
| 57 |
- } |
|
| 58 |
- if len(filter) == 0 {
|
|
| 59 |
- return true |
|
| 60 |
- } |
|
| 61 |
- for _, v := range filter {
|
|
| 62 |
- if v == field {
|
|
| 63 |
- return true |
|
| 64 |
- } |
|
| 46 |
+func stripTag(image string) string {
|
|
| 47 |
+ ref, err := reference.ParseNamed(image) |
|
| 48 |
+ if err != nil {
|
|
| 49 |
+ return image |
|
| 65 | 50 |
} |
| 66 |
- return false |
|
| 51 |
+ return ref.Name() |
|
| 67 | 52 |
} |
| ... | ... |
@@ -4,7 +4,6 @@ import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"path" |
| 6 | 6 |
"sort" |
| 7 |
- "strings" |
|
| 8 | 7 |
|
| 9 | 8 |
"github.com/docker/distribution/reference" |
| 10 | 9 |
"github.com/docker/docker/api/types" |
| ... | ... |
@@ -13,9 +12,9 @@ import ( |
| 13 | 13 |
"github.com/docker/docker/pkg/parsers/filters" |
| 14 | 14 |
) |
| 15 | 15 |
|
| 16 |
-var acceptedImageFilterTags = map[string]struct{}{
|
|
| 17 |
- "dangling": {},
|
|
| 18 |
- "label": {},
|
|
| 16 |
+var acceptedImageFilterTags = map[string]bool{
|
|
| 17 |
+ "dangling": true, |
|
| 18 |
+ "label": true, |
|
| 19 | 19 |
} |
| 20 | 20 |
|
| 21 | 21 |
// byCreated is a temporary type used to sort a list of images by creation |
| ... | ... |
@@ -47,19 +46,15 @@ func (daemon *Daemon) Images(filterArgs, filter string, all bool) ([]*types.Imag |
| 47 | 47 |
if err != nil {
|
| 48 | 48 |
return nil, err |
| 49 | 49 |
} |
| 50 |
- for name := range imageFilters {
|
|
| 51 |
- if _, ok := acceptedImageFilterTags[name]; !ok {
|
|
| 52 |
- return nil, fmt.Errorf("Invalid filter '%s'", name)
|
|
| 53 |
- } |
|
| 50 |
+ if err := imageFilters.Validate(acceptedImageFilterTags); err != nil {
|
|
| 51 |
+ return nil, err |
|
| 54 | 52 |
} |
| 55 | 53 |
|
| 56 |
- if i, ok := imageFilters["dangling"]; ok {
|
|
| 57 |
- for _, value := range i {
|
|
| 58 |
- if v := strings.ToLower(value); v == "true" {
|
|
| 59 |
- danglingOnly = true |
|
| 60 |
- } else if v != "false" {
|
|
| 61 |
- return nil, fmt.Errorf("Invalid filter 'dangling=%s'", v)
|
|
| 62 |
- } |
|
| 54 |
+ if imageFilters.Include("dangling") {
|
|
| 55 |
+ if imageFilters.ExactMatch("dangling", "true") {
|
|
| 56 |
+ danglingOnly = true |
|
| 57 |
+ } else if !imageFilters.ExactMatch("dangling", "false") {
|
|
| 58 |
+ return nil, fmt.Errorf("Invalid filter 'dangling=%s'", imageFilters.Get("dangling"))
|
|
| 63 | 59 |
} |
| 64 | 60 |
} |
| 65 | 61 |
|
| ... | ... |
@@ -82,9 +77,9 @@ func (daemon *Daemon) Images(filterArgs, filter string, all bool) ([]*types.Imag |
| 82 | 82 |
} |
| 83 | 83 |
|
| 84 | 84 |
for id, img := range allImages {
|
| 85 |
- if _, ok := imageFilters["label"]; ok {
|
|
| 85 |
+ if imageFilters.Include("label") {
|
|
| 86 |
+ // Very old image that do not have image.Config (or even labels) |
|
| 86 | 87 |
if img.Config == nil {
|
| 87 |
- // Very old image that do not have image.Config (or even labels) |
|
| 88 | 88 |
continue |
| 89 | 89 |
} |
| 90 | 90 |
// We are now sure image.Config is not nil |
| ... | ... |
@@ -8,7 +8,6 @@ import ( |
| 8 | 8 |
|
| 9 | 9 |
"github.com/Sirupsen/logrus" |
| 10 | 10 |
"github.com/docker/docker/api/types" |
| 11 |
- derr "github.com/docker/docker/errors" |
|
| 12 | 11 |
"github.com/docker/docker/image" |
| 13 | 12 |
"github.com/docker/docker/pkg/graphdb" |
| 14 | 13 |
"github.com/docker/docker/pkg/nat" |
| ... | ... |
@@ -136,64 +135,65 @@ func (daemon *Daemon) foldFilter(config *ContainersConfig) (*listContext, error) |
| 136 | 136 |
} |
| 137 | 137 |
|
| 138 | 138 |
var filtExited []int |
| 139 |
- if i, ok := psFilters["exited"]; ok {
|
|
| 140 |
- for _, value := range i {
|
|
| 141 |
- code, err := strconv.Atoi(value) |
|
| 142 |
- if err != nil {
|
|
| 143 |
- return nil, err |
|
| 144 |
- } |
|
| 145 |
- filtExited = append(filtExited, code) |
|
| 139 |
+ err = psFilters.WalkValues("exited", func(value string) error {
|
|
| 140 |
+ code, err := strconv.Atoi(value) |
|
| 141 |
+ if err != nil {
|
|
| 142 |
+ return err |
|
| 146 | 143 |
} |
| 144 |
+ filtExited = append(filtExited, code) |
|
| 145 |
+ return nil |
|
| 146 |
+ }) |
|
| 147 |
+ if err != nil {
|
|
| 148 |
+ return nil, err |
|
| 147 | 149 |
} |
| 148 | 150 |
|
| 149 |
- if i, ok := psFilters["status"]; ok {
|
|
| 150 |
- for _, value := range i {
|
|
| 151 |
- if !isValidStateString(value) {
|
|
| 152 |
- return nil, errors.New("Unrecognised filter value for status")
|
|
| 153 |
- } |
|
| 154 |
- |
|
| 155 |
- config.All = true |
|
| 151 |
+ err = psFilters.WalkValues("status", func(value string) error {
|
|
| 152 |
+ if !isValidStateString(value) {
|
|
| 153 |
+ return fmt.Errorf("Unrecognised filter value for status: %s", value)
|
|
| 156 | 154 |
} |
| 155 |
+ |
|
| 156 |
+ config.All = true |
|
| 157 |
+ return nil |
|
| 158 |
+ }) |
|
| 159 |
+ if err != nil {
|
|
| 160 |
+ return nil, err |
|
| 157 | 161 |
} |
| 158 | 162 |
|
| 159 | 163 |
var beforeContFilter, sinceContFilter *Container |
| 160 |
- if i, ok := psFilters["before"]; ok {
|
|
| 161 |
- for _, value := range i {
|
|
| 162 |
- beforeContFilter, err = daemon.Get(value) |
|
| 163 |
- if err != nil {
|
|
| 164 |
- return nil, err |
|
| 165 |
- } |
|
| 166 |
- } |
|
| 164 |
+ err = psFilters.WalkValues("before", func(value string) error {
|
|
| 165 |
+ beforeContFilter, err = daemon.Get(value) |
|
| 166 |
+ return err |
|
| 167 |
+ }) |
|
| 168 |
+ if err != nil {
|
|
| 169 |
+ return nil, err |
|
| 167 | 170 |
} |
| 168 | 171 |
|
| 169 |
- if i, ok := psFilters["since"]; ok {
|
|
| 170 |
- for _, value := range i {
|
|
| 171 |
- sinceContFilter, err = daemon.Get(value) |
|
| 172 |
- if err != nil {
|
|
| 173 |
- return nil, err |
|
| 174 |
- } |
|
| 175 |
- } |
|
| 172 |
+ err = psFilters.WalkValues("since", func(value string) error {
|
|
| 173 |
+ sinceContFilter, err = daemon.Get(value) |
|
| 174 |
+ return err |
|
| 175 |
+ }) |
|
| 176 |
+ if err != nil {
|
|
| 177 |
+ return nil, err |
|
| 176 | 178 |
} |
| 177 | 179 |
|
| 178 | 180 |
imagesFilter := map[image.ID]bool{}
|
| 179 | 181 |
var ancestorFilter bool |
| 180 |
- if ancestors, ok := psFilters["ancestor"]; ok {
|
|
| 182 |
+ if psFilters.Include("ancestor") {
|
|
| 181 | 183 |
ancestorFilter = true |
| 182 |
- // The idea is to walk the graph down the most "efficient" way. |
|
| 183 |
- for _, ancestor := range ancestors {
|
|
| 184 |
- // First, get the imageId of the ancestor filter (yay) |
|
| 184 |
+ psFilters.WalkValues("ancestor", func(ancestor string) error {
|
|
| 185 | 185 |
id, err := daemon.GetImageID(ancestor) |
| 186 | 186 |
if err != nil {
|
| 187 | 187 |
logrus.Warnf("Error while looking up for image %v", ancestor)
|
| 188 |
- continue |
|
| 188 |
+ return nil |
|
| 189 | 189 |
} |
| 190 | 190 |
if imagesFilter[id] {
|
| 191 | 191 |
// Already seen this ancestor, skip it |
| 192 |
- continue |
|
| 192 |
+ return nil |
|
| 193 | 193 |
} |
| 194 | 194 |
// Then walk down the graph and put the imageIds in imagesFilter |
| 195 | 195 |
populateImageFilterByParents(imagesFilter, id, daemon.imageStore.Children) |
| 196 |
- } |
|
| 196 |
+ return nil |
|
| 197 |
+ }) |
|
| 197 | 198 |
} |
| 198 | 199 |
|
| 199 | 200 |
names := make(map[string][]string) |
| ... | ... |
@@ -202,14 +202,14 @@ func (daemon *Daemon) foldFilter(config *ContainersConfig) (*listContext, error) |
| 202 | 202 |
return nil |
| 203 | 203 |
}, 1) |
| 204 | 204 |
|
| 205 |
- if config.Before != "" {
|
|
| 205 |
+ if config.Before != "" && beforeContFilter == nil {
|
|
| 206 | 206 |
beforeContFilter, err = daemon.Get(config.Before) |
| 207 | 207 |
if err != nil {
|
| 208 | 208 |
return nil, err |
| 209 | 209 |
} |
| 210 | 210 |
} |
| 211 | 211 |
|
| 212 |
- if config.Since != "" {
|
|
| 212 |
+ if config.Since != "" && sinceContFilter == nil {
|
|
| 213 | 213 |
sinceContFilter, err = daemon.Get(config.Since) |
| 214 | 214 |
if err != nil {
|
| 215 | 215 |
return nil, err |
| ... | ... |
@@ -397,17 +397,8 @@ func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, error) {
|
| 397 | 397 |
return nil, err |
| 398 | 398 |
} |
| 399 | 399 |
|
| 400 |
- filterUsed := false |
|
| 401 |
- if i, ok := volFilters["dangling"]; ok {
|
|
| 402 |
- if len(i) > 1 {
|
|
| 403 |
- return nil, derr.ErrorCodeDanglingOne |
|
| 404 |
- } |
|
| 405 |
- |
|
| 406 |
- filterValue := i[0] |
|
| 407 |
- if strings.ToLower(filterValue) == "true" || filterValue == "1" {
|
|
| 408 |
- filterUsed = true |
|
| 409 |
- } |
|
| 410 |
- } |
|
| 400 |
+ filterUsed := volFilters.Include("dangling") &&
|
|
| 401 |
+ (volFilters.ExactMatch("dangling", "true") || volFilters.ExactMatch("dangling", "1"))
|
|
| 411 | 402 |
|
| 412 | 403 |
volumes := daemon.volumes.List() |
| 413 | 404 |
for _, v := range volumes {
|
| ... | ... |
@@ -230,9 +230,9 @@ func isNetworkAvailable(c *check.C, name string) bool {
|
| 230 | 230 |
func getNetworkIDByName(c *check.C, name string) string {
|
| 231 | 231 |
var ( |
| 232 | 232 |
v = url.Values{}
|
| 233 |
- filterArgs = filters.Args{}
|
|
| 233 |
+ filterArgs = filters.NewArgs() |
|
| 234 | 234 |
) |
| 235 |
- filterArgs["name"] = []string{name}
|
|
| 235 |
+ filterArgs.Add("name", name)
|
|
| 236 | 236 |
filterJSON, err := filters.ToParam(filterArgs) |
| 237 | 237 |
c.Assert(err, checker.IsNil) |
| 238 | 238 |
v.Set("filters", filterJSON)
|
| ... | ... |
@@ -74,7 +74,7 @@ func (s *DockerSuite) TestImagesErrorWithInvalidFilterNameTest(c *check.C) {
|
| 74 | 74 |
c.Assert(out, checker.Contains, "Invalid filter") |
| 75 | 75 |
} |
| 76 | 76 |
|
| 77 |
-func (s *DockerSuite) TestImagesFilterLabel(c *check.C) {
|
|
| 77 |
+func (s *DockerSuite) TestImagesFilterLabelMatch(c *check.C) {
|
|
| 78 | 78 |
testRequires(c, DaemonIsLinux) |
| 79 | 79 |
imageName1 := "images_filter_test1" |
| 80 | 80 |
imageName2 := "images_filter_test2" |
| ... | ... |
@@ -5,6 +5,7 @@ package filters |
| 5 | 5 |
import ( |
| 6 | 6 |
"encoding/json" |
| 7 | 7 |
"errors" |
| 8 |
+ "fmt" |
|
| 8 | 9 |
"regexp" |
| 9 | 10 |
"strings" |
| 10 | 11 |
) |
| ... | ... |
@@ -15,7 +16,14 @@ import ( |
| 15 | 15 |
// in an slice. |
| 16 | 16 |
// e.g given -f 'label=label1=1' -f 'label=label2=2' -f 'image.name=ubuntu' |
| 17 | 17 |
// the args will be {'label': {'label1=1','label2=2'}, 'image.name', {'ubuntu'}}
|
| 18 |
-type Args map[string][]string |
|
| 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 |
+} |
|
| 19 | 26 |
|
| 20 | 27 |
// ParseFlag parses the argument to the filter flag. Like |
| 21 | 28 |
// |
| ... | ... |
@@ -25,9 +33,6 @@ type Args map[string][]string |
| 25 | 25 |
// map is created. |
| 26 | 26 |
func ParseFlag(arg string, prev Args) (Args, error) {
|
| 27 | 27 |
filters := prev |
| 28 |
- if prev == nil {
|
|
| 29 |
- filters = Args{}
|
|
| 30 |
- } |
|
| 31 | 28 |
if len(arg) == 0 {
|
| 32 | 29 |
return filters, nil |
| 33 | 30 |
} |
| ... | ... |
@@ -37,9 +42,11 @@ func ParseFlag(arg string, prev Args) (Args, error) {
|
| 37 | 37 |
} |
| 38 | 38 |
|
| 39 | 39 |
f := strings.SplitN(arg, "=", 2) |
| 40 |
+ |
|
| 40 | 41 |
name := strings.ToLower(strings.TrimSpace(f[0])) |
| 41 | 42 |
value := strings.TrimSpace(f[1]) |
| 42 |
- filters[name] = append(filters[name], value) |
|
| 43 |
+ |
|
| 44 |
+ filters.Add(name, value) |
|
| 43 | 45 |
|
| 44 | 46 |
return filters, nil |
| 45 | 47 |
} |
| ... | ... |
@@ -50,11 +57,11 @@ var ErrBadFormat = errors.New("bad format of filter (expected name=value)")
|
| 50 | 50 |
// ToParam packs the Args into an string for easy transport from client to server. |
| 51 | 51 |
func ToParam(a Args) (string, error) {
|
| 52 | 52 |
// this way we don't URL encode {}, just empty space
|
| 53 |
- if len(a) == 0 {
|
|
| 53 |
+ if a.Len() == 0 {
|
|
| 54 | 54 |
return "", nil |
| 55 | 55 |
} |
| 56 | 56 |
|
| 57 |
- buf, err := json.Marshal(a) |
|
| 57 |
+ buf, err := json.Marshal(a.fields) |
|
| 58 | 58 |
if err != nil {
|
| 59 | 59 |
return "", err |
| 60 | 60 |
} |
| ... | ... |
@@ -63,23 +70,71 @@ func ToParam(a Args) (string, error) {
|
| 63 | 63 |
|
| 64 | 64 |
// FromParam unpacks the filter Args. |
| 65 | 65 |
func FromParam(p string) (Args, error) {
|
| 66 |
- args := Args{}
|
|
| 67 | 66 |
if len(p) == 0 {
|
| 68 |
- return args, nil |
|
| 67 |
+ return NewArgs(), nil |
|
| 68 |
+ } |
|
| 69 |
+ |
|
| 70 |
+ r := strings.NewReader(p) |
|
| 71 |
+ d := json.NewDecoder(r) |
|
| 72 |
+ |
|
| 73 |
+ m := map[string]map[string]bool{}
|
|
| 74 |
+ if err := d.Decode(&m); err != nil {
|
|
| 75 |
+ r.Seek(0, 0) |
|
| 76 |
+ |
|
| 77 |
+ // Allow parsing old arguments in slice format. |
|
| 78 |
+ // Because other libraries might be sending them in this format. |
|
| 79 |
+ deprecated := map[string][]string{}
|
|
| 80 |
+ if deprecatedErr := d.Decode(&deprecated); deprecatedErr == nil {
|
|
| 81 |
+ m = deprecatedArgs(deprecated) |
|
| 82 |
+ } else {
|
|
| 83 |
+ return NewArgs(), err |
|
| 84 |
+ } |
|
| 85 |
+ } |
|
| 86 |
+ return Args{m}, nil
|
|
| 87 |
+} |
|
| 88 |
+ |
|
| 89 |
+// Get returns the list of values associates with a field. |
|
| 90 |
+// It returns a slice of strings to keep backwards compatibility with old code. |
|
| 91 |
+func (filters Args) Get(field string) []string {
|
|
| 92 |
+ values := filters.fields[field] |
|
| 93 |
+ if values == nil {
|
|
| 94 |
+ return make([]string, 0) |
|
| 69 | 95 |
} |
| 70 |
- if err := json.NewDecoder(strings.NewReader(p)).Decode(&args); err != nil {
|
|
| 71 |
- return nil, err |
|
| 96 |
+ slice := make([]string, 0, len(values)) |
|
| 97 |
+ for key := range values {
|
|
| 98 |
+ slice = append(slice, key) |
|
| 72 | 99 |
} |
| 73 |
- return args, nil |
|
| 100 |
+ return slice |
|
| 101 |
+} |
|
| 102 |
+ |
|
| 103 |
+// Add adds a new value to a filter field. |
|
| 104 |
+func (filters Args) Add(name, value string) {
|
|
| 105 |
+ if _, ok := filters.fields[name]; ok {
|
|
| 106 |
+ filters.fields[name][value] = true |
|
| 107 |
+ } else {
|
|
| 108 |
+ filters.fields[name] = map[string]bool{value: true}
|
|
| 109 |
+ } |
|
| 110 |
+} |
|
| 111 |
+ |
|
| 112 |
+// Del removes a value from a filter field. |
|
| 113 |
+func (filters Args) Del(name, value string) {
|
|
| 114 |
+ if _, ok := filters.fields[name]; ok {
|
|
| 115 |
+ delete(filters.fields[name], value) |
|
| 116 |
+ } |
|
| 117 |
+} |
|
| 118 |
+ |
|
| 119 |
+// Len returns the number of fields in the arguments. |
|
| 120 |
+func (filters Args) Len() int {
|
|
| 121 |
+ return len(filters.fields) |
|
| 74 | 122 |
} |
| 75 | 123 |
|
| 76 | 124 |
// MatchKVList returns true if the values for the specified field maches the ones |
| 77 | 125 |
// from the sources. |
| 78 | 126 |
// e.g. given Args are {'label': {'label1=1','label2=1'}, 'image.name', {'ubuntu'}},
|
| 79 |
-// field is 'label' and sources are {'label':{'label1=1','label2=2','label3=3'}}
|
|
| 127 |
+// field is 'label' and sources are {'label1': '1', 'label2': '2'}
|
|
| 80 | 128 |
// it returns true. |
| 81 | 129 |
func (filters Args) MatchKVList(field string, sources map[string]string) bool {
|
| 82 |
- fieldValues := filters[field] |
|
| 130 |
+ fieldValues := filters.fields[field] |
|
| 83 | 131 |
|
| 84 | 132 |
//do not filter if there is no filter set or cannot determine filter |
| 85 | 133 |
if len(fieldValues) == 0 {
|
| ... | ... |
@@ -90,21 +145,16 @@ func (filters Args) MatchKVList(field string, sources map[string]string) bool {
|
| 90 | 90 |
return false |
| 91 | 91 |
} |
| 92 | 92 |
|
| 93 |
-outer: |
|
| 94 |
- for _, name2match := range fieldValues {
|
|
| 93 |
+ for name2match := range fieldValues {
|
|
| 95 | 94 |
testKV := strings.SplitN(name2match, "=", 2) |
| 96 | 95 |
|
| 97 |
- for k, v := range sources {
|
|
| 98 |
- if len(testKV) == 1 {
|
|
| 99 |
- if k == testKV[0] {
|
|
| 100 |
- continue outer |
|
| 101 |
- } |
|
| 102 |
- } else if k == testKV[0] && v == testKV[1] {
|
|
| 103 |
- continue outer |
|
| 104 |
- } |
|
| 96 |
+ v, ok := sources[testKV[0]] |
|
| 97 |
+ if !ok {
|
|
| 98 |
+ return false |
|
| 99 |
+ } |
|
| 100 |
+ if len(testKV) == 2 && testKV[1] != v {
|
|
| 101 |
+ return false |
|
| 105 | 102 |
} |
| 106 |
- |
|
| 107 |
- return false |
|
| 108 | 103 |
} |
| 109 | 104 |
|
| 110 | 105 |
return true |
| ... | ... |
@@ -115,13 +165,12 @@ outer: |
| 115 | 115 |
// field is 'image.name' and source is 'ubuntu' |
| 116 | 116 |
// it returns true. |
| 117 | 117 |
func (filters Args) Match(field, source string) bool {
|
| 118 |
- fieldValues := filters[field] |
|
| 119 |
- |
|
| 120 |
- //do not filter if there is no filter set or cannot determine filter |
|
| 121 |
- if len(fieldValues) == 0 {
|
|
| 118 |
+ if filters.ExactMatch(field, source) {
|
|
| 122 | 119 |
return true |
| 123 | 120 |
} |
| 124 |
- for _, name2match := range fieldValues {
|
|
| 121 |
+ |
|
| 122 |
+ fieldValues := filters.fields[field] |
|
| 123 |
+ for name2match := range fieldValues {
|
|
| 125 | 124 |
match, err := regexp.MatchString(name2match, source) |
| 126 | 125 |
if err != nil {
|
| 127 | 126 |
continue |
| ... | ... |
@@ -132,3 +181,61 @@ func (filters Args) Match(field, source string) bool {
|
| 132 | 132 |
} |
| 133 | 133 |
return false |
| 134 | 134 |
} |
| 135 |
+ |
|
| 136 |
+// ExactMatch returns true if the source matches exactly one of the filters. |
|
| 137 |
+func (filters Args) ExactMatch(field, source string) bool {
|
|
| 138 |
+ fieldValues, ok := filters.fields[field] |
|
| 139 |
+ //do not filter if there is no filter set or cannot determine filter |
|
| 140 |
+ if !ok || len(fieldValues) == 0 {
|
|
| 141 |
+ return true |
|
| 142 |
+ } |
|
| 143 |
+ |
|
| 144 |
+ // try to march full name value to avoid O(N) regular expression matching |
|
| 145 |
+ if fieldValues[source] {
|
|
| 146 |
+ return true |
|
| 147 |
+ } |
|
| 148 |
+ return false |
|
| 149 |
+} |
|
| 150 |
+ |
|
| 151 |
+// Include returns true if the name of the field to filter is in the filters. |
|
| 152 |
+func (filters Args) Include(field string) bool {
|
|
| 153 |
+ _, ok := filters.fields[field] |
|
| 154 |
+ return ok |
|
| 155 |
+} |
|
| 156 |
+ |
|
| 157 |
+// Validate ensures that all the fields in the filter are valid. |
|
| 158 |
+// It returns an error as soon as it finds an invalid field. |
|
| 159 |
+func (filters Args) Validate(accepted map[string]bool) error {
|
|
| 160 |
+ for name := range filters.fields {
|
|
| 161 |
+ if !accepted[name] {
|
|
| 162 |
+ return fmt.Errorf("Invalid filter '%s'", name)
|
|
| 163 |
+ } |
|
| 164 |
+ } |
|
| 165 |
+ return nil |
|
| 166 |
+} |
|
| 167 |
+ |
|
| 168 |
+// WalkValues iterates over the list of filtered values for a field. |
|
| 169 |
+// It stops the iteration if it finds an error and it returns that error. |
|
| 170 |
+func (filters Args) WalkValues(field string, op func(value string) error) error {
|
|
| 171 |
+ if _, ok := filters.fields[field]; !ok {
|
|
| 172 |
+ return nil |
|
| 173 |
+ } |
|
| 174 |
+ for v := range filters.fields[field] {
|
|
| 175 |
+ if err := op(v); err != nil {
|
|
| 176 |
+ return err |
|
| 177 |
+ } |
|
| 178 |
+ } |
|
| 179 |
+ return nil |
|
| 180 |
+} |
|
| 181 |
+ |
|
| 182 |
+func deprecatedArgs(d map[string][]string) map[string]map[string]bool {
|
|
| 183 |
+ m := map[string]map[string]bool{}
|
|
| 184 |
+ for k, v := range d {
|
|
| 185 |
+ values := map[string]bool{}
|
|
| 186 |
+ for _, vv := range v {
|
|
| 187 |
+ values[vv] = true |
|
| 188 |
+ } |
|
| 189 |
+ m[k] = values |
|
| 190 |
+ } |
|
| 191 |
+ return m |
|
| 192 |
+} |
| ... | ... |
@@ -1,7 +1,7 @@ |
| 1 | 1 |
package filters |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "sort" |
|
| 4 |
+ "fmt" |
|
| 5 | 5 |
"testing" |
| 6 | 6 |
) |
| 7 | 7 |
|
| ... | ... |
@@ -13,7 +13,7 @@ func TestParseArgs(t *testing.T) {
|
| 13 | 13 |
"image.name=*untu", |
| 14 | 14 |
} |
| 15 | 15 |
var ( |
| 16 |
- args = Args{}
|
|
| 16 |
+ args = NewArgs() |
|
| 17 | 17 |
err error |
| 18 | 18 |
) |
| 19 | 19 |
for i := range flagArgs {
|
| ... | ... |
@@ -22,10 +22,10 @@ func TestParseArgs(t *testing.T) {
|
| 22 | 22 |
t.Errorf("failed to parse %s: %s", flagArgs[i], err)
|
| 23 | 23 |
} |
| 24 | 24 |
} |
| 25 |
- if len(args["created"]) != 1 {
|
|
| 25 |
+ if len(args.Get("created")) != 1 {
|
|
| 26 | 26 |
t.Errorf("failed to set this arg")
|
| 27 | 27 |
} |
| 28 |
- if len(args["image.name"]) != 2 {
|
|
| 28 |
+ if len(args.Get("image.name")) != 2 {
|
|
| 29 | 29 |
t.Errorf("the args should have collapsed")
|
| 30 | 30 |
} |
| 31 | 31 |
} |
| ... | ... |
@@ -36,7 +36,7 @@ func TestParseArgsEdgeCase(t *testing.T) {
|
| 36 | 36 |
if err != nil {
|
| 37 | 37 |
t.Fatal(err) |
| 38 | 38 |
} |
| 39 |
- if args == nil || len(args) != 0 {
|
|
| 39 |
+ if args.Len() != 0 {
|
|
| 40 | 40 |
t.Fatalf("Expected an empty Args (map), got %v", args)
|
| 41 | 41 |
} |
| 42 | 42 |
if args, err = ParseFlag("anything", args); err == nil || err != ErrBadFormat {
|
| ... | ... |
@@ -45,10 +45,11 @@ func TestParseArgsEdgeCase(t *testing.T) {
|
| 45 | 45 |
} |
| 46 | 46 |
|
| 47 | 47 |
func TestToParam(t *testing.T) {
|
| 48 |
- a := Args{
|
|
| 49 |
- "created": []string{"today"},
|
|
| 50 |
- "image.name": []string{"ubuntu*", "*untu"},
|
|
| 48 |
+ fields := map[string]map[string]bool{
|
|
| 49 |
+ "created": {"today": true},
|
|
| 50 |
+ "image.name": {"ubuntu*": true, "*untu": true},
|
|
| 51 | 51 |
} |
| 52 |
+ a := Args{fields: fields}
|
|
| 52 | 53 |
|
| 53 | 54 |
_, err := ToParam(a) |
| 54 | 55 |
if err != nil {
|
| ... | ... |
@@ -63,42 +64,48 @@ func TestFromParam(t *testing.T) {
|
| 63 | 63 |
"{'key': 'value'}",
|
| 64 | 64 |
`{"key": "value"}`,
|
| 65 | 65 |
} |
| 66 |
- valids := map[string]Args{
|
|
| 67 |
- `{"key": ["value"]}`: {
|
|
| 68 |
- "key": {"value"},
|
|
| 66 |
+ valid := map[*Args][]string{
|
|
| 67 |
+ &Args{fields: map[string]map[string]bool{"key": {"value": true}}}: {
|
|
| 68 |
+ `{"key": ["value"]}`,
|
|
| 69 |
+ `{"key": {"value": true}}`,
|
|
| 69 | 70 |
}, |
| 70 |
- `{"key": ["value1", "value2"]}`: {
|
|
| 71 |
- "key": {"value1", "value2"},
|
|
| 71 |
+ &Args{fields: map[string]map[string]bool{"key": {"value1": true, "value2": true}}}: {
|
|
| 72 |
+ `{"key": ["value1", "value2"]}`,
|
|
| 73 |
+ `{"key": {"value1": true, "value2": true}}`,
|
|
| 72 | 74 |
}, |
| 73 |
- `{"key1": ["value1"], "key2": ["value2"]}`: {
|
|
| 74 |
- "key1": {"value1"},
|
|
| 75 |
- "key2": {"value2"},
|
|
| 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}}`,
|
|
| 76 | 78 |
}, |
| 77 | 79 |
} |
| 80 |
+ |
|
| 78 | 81 |
for _, invalid := range invalids {
|
| 79 | 82 |
if _, err := FromParam(invalid); err == nil {
|
| 80 | 83 |
t.Fatalf("Expected an error with %v, got nothing", invalid)
|
| 81 | 84 |
} |
| 82 | 85 |
} |
| 83 |
- for json, expectedArgs := range valids {
|
|
| 84 |
- args, err := FromParam(json) |
|
| 85 |
- if err != nil {
|
|
| 86 |
- t.Fatal(err) |
|
| 87 |
- } |
|
| 88 |
- if len(args) != len(expectedArgs) {
|
|
| 89 |
- t.Fatalf("Expected %v, go %v", expectedArgs, args)
|
|
| 90 |
- } |
|
| 91 |
- for key, expectedValues := range expectedArgs {
|
|
| 92 |
- values := args[key] |
|
| 93 |
- sort.Strings(values) |
|
| 94 |
- sort.Strings(expectedValues) |
|
| 95 |
- if len(values) != len(expectedValues) {
|
|
| 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() {
|
|
| 96 | 94 |
t.Fatalf("Expected %v, go %v", expectedArgs, args)
|
| 97 | 95 |
} |
| 98 |
- for index, expectedValue := range expectedValues {
|
|
| 99 |
- if values[index] != expectedValue {
|
|
| 96 |
+ for key, expectedValues := range expectedArgs.fields {
|
|
| 97 |
+ values := args.Get(key) |
|
| 98 |
+ |
|
| 99 |
+ if len(values) != len(expectedValues) {
|
|
| 100 | 100 |
t.Fatalf("Expected %v, go %v", expectedArgs, args)
|
| 101 | 101 |
} |
| 102 |
+ |
|
| 103 |
+ for _, v := range values {
|
|
| 104 |
+ if !expectedValues[v] {
|
|
| 105 |
+ t.Fatalf("Expected %v, go %v", expectedArgs, args)
|
|
| 106 |
+ } |
|
| 107 |
+ } |
|
| 102 | 108 |
} |
| 103 | 109 |
} |
| 104 | 110 |
} |
| ... | ... |
@@ -114,54 +121,63 @@ func TestEmpty(t *testing.T) {
|
| 114 | 114 |
if err != nil {
|
| 115 | 115 |
t.Errorf("%s", err)
|
| 116 | 116 |
} |
| 117 |
- if len(a) != len(v1) {
|
|
| 117 |
+ if a.Len() != v1.Len() {
|
|
| 118 | 118 |
t.Errorf("these should both be empty sets")
|
| 119 | 119 |
} |
| 120 | 120 |
} |
| 121 | 121 |
|
| 122 |
-func TestArgsMatchKVList(t *testing.T) {
|
|
| 123 |
- // empty sources |
|
| 124 |
- args := Args{
|
|
| 125 |
- "created": []string{"today"},
|
|
| 122 |
+func TestArgsMatchKVListEmptySources(t *testing.T) {
|
|
| 123 |
+ args := NewArgs() |
|
| 124 |
+ if !args.MatchKVList("created", map[string]string{}) {
|
|
| 125 |
+ t.Fatalf("Expected true for (%v,created), got true", args)
|
|
| 126 | 126 |
} |
| 127 |
+ |
|
| 128 |
+ args = Args{map[string]map[string]bool{"created": {"today": true}}}
|
|
| 127 | 129 |
if args.MatchKVList("created", map[string]string{}) {
|
| 128 | 130 |
t.Fatalf("Expected false for (%v,created), got true", args)
|
| 129 | 131 |
} |
| 132 |
+} |
|
| 133 |
+ |
|
| 134 |
+func TestArgsMatchKVList(t *testing.T) {
|
|
| 130 | 135 |
// Not empty sources |
| 131 | 136 |
sources := map[string]string{
|
| 132 | 137 |
"key1": "value1", |
| 133 | 138 |
"key2": "value2", |
| 134 | 139 |
"key3": "value3", |
| 135 | 140 |
} |
| 141 |
+ |
|
| 136 | 142 |
matches := map[*Args]string{
|
| 137 | 143 |
&Args{}: "field",
|
| 138 |
- &Args{
|
|
| 139 |
- "created": []string{"today"},
|
|
| 140 |
- "labels": []string{"key1"},
|
|
| 144 |
+ &Args{map[string]map[string]bool{
|
|
| 145 |
+ "created": map[string]bool{"today": true},
|
|
| 146 |
+ "labels": map[string]bool{"key1": true}},
|
|
| 141 | 147 |
}: "labels", |
| 142 |
- &Args{
|
|
| 143 |
- "created": []string{"today"},
|
|
| 144 |
- "labels": []string{"key1=value1"},
|
|
| 145 |
- }: "labels", |
|
| 146 |
- } |
|
| 147 |
- differs := map[*Args]string{
|
|
| 148 |
- &Args{
|
|
| 149 |
- "created": []string{"today"},
|
|
| 150 |
- }: "created", |
|
| 151 |
- &Args{
|
|
| 152 |
- "created": []string{"today"},
|
|
| 153 |
- "labels": []string{"key4"},
|
|
| 154 |
- }: "labels", |
|
| 155 |
- &Args{
|
|
| 156 |
- "created": []string{"today"},
|
|
| 157 |
- "labels": []string{"key1=value3"},
|
|
| 148 |
+ &Args{map[string]map[string]bool{
|
|
| 149 |
+ "created": map[string]bool{"today": true},
|
|
| 150 |
+ "labels": map[string]bool{"key1=value1": true}},
|
|
| 158 | 151 |
}: "labels", |
| 159 | 152 |
} |
| 153 |
+ |
|
| 160 | 154 |
for args, field := range matches {
|
| 161 | 155 |
if args.MatchKVList(field, sources) != true {
|
| 162 | 156 |
t.Fatalf("Expected true for %v on %v, got false", sources, args)
|
| 163 | 157 |
} |
| 164 | 158 |
} |
| 159 |
+ |
|
| 160 |
+ differs := map[*Args]string{
|
|
| 161 |
+ &Args{map[string]map[string]bool{
|
|
| 162 |
+ "created": map[string]bool{"today": true}},
|
|
| 163 |
+ }: "created", |
|
| 164 |
+ &Args{map[string]map[string]bool{
|
|
| 165 |
+ "created": map[string]bool{"today": true},
|
|
| 166 |
+ "labels": map[string]bool{"key4": true}},
|
|
| 167 |
+ }: "labels", |
|
| 168 |
+ &Args{map[string]map[string]bool{
|
|
| 169 |
+ "created": map[string]bool{"today": true},
|
|
| 170 |
+ "labels": map[string]bool{"key1=value3": true}},
|
|
| 171 |
+ }: "labels", |
|
| 172 |
+ } |
|
| 173 |
+ |
|
| 165 | 174 |
for args, field := range differs {
|
| 166 | 175 |
if args.MatchKVList(field, sources) != false {
|
| 167 | 176 |
t.Fatalf("Expected false for %v on %v, got true", sources, args)
|
| ... | ... |
@@ -171,48 +187,165 @@ func TestArgsMatchKVList(t *testing.T) {
|
| 171 | 171 |
|
| 172 | 172 |
func TestArgsMatch(t *testing.T) {
|
| 173 | 173 |
source := "today" |
| 174 |
+ |
|
| 174 | 175 |
matches := map[*Args]string{
|
| 175 | 176 |
&Args{}: "field",
|
| 176 |
- &Args{
|
|
| 177 |
- "created": []string{"today"},
|
|
| 178 |
- "labels": []string{"key1"},
|
|
| 177 |
+ &Args{map[string]map[string]bool{
|
|
| 178 |
+ "created": map[string]bool{"today": true}},
|
|
| 179 | 179 |
}: "today", |
| 180 |
- &Args{
|
|
| 181 |
- "created": []string{"to*"},
|
|
| 180 |
+ &Args{map[string]map[string]bool{
|
|
| 181 |
+ "created": map[string]bool{"to*": true}},
|
|
| 182 | 182 |
}: "created", |
| 183 |
- &Args{
|
|
| 184 |
- "created": []string{"to(.*)"},
|
|
| 183 |
+ &Args{map[string]map[string]bool{
|
|
| 184 |
+ "created": map[string]bool{"to(.*)": true}},
|
|
| 185 | 185 |
}: "created", |
| 186 |
- &Args{
|
|
| 187 |
- "created": []string{"tod"},
|
|
| 186 |
+ &Args{map[string]map[string]bool{
|
|
| 187 |
+ "created": map[string]bool{"tod": true}},
|
|
| 188 | 188 |
}: "created", |
| 189 |
- &Args{
|
|
| 190 |
- "created": []string{"anything", "to*"},
|
|
| 189 |
+ &Args{map[string]map[string]bool{
|
|
| 190 |
+ "created": map[string]bool{"anyting": true, "to*": true}},
|
|
| 191 | 191 |
}: "created", |
| 192 | 192 |
} |
| 193 |
+ |
|
| 194 |
+ for args, field := range matches {
|
|
| 195 |
+ if args.Match(field, source) != true {
|
|
| 196 |
+ t.Fatalf("Expected true for %v on %v, got false", source, args)
|
|
| 197 |
+ } |
|
| 198 |
+ } |
|
| 199 |
+ |
|
| 193 | 200 |
differs := map[*Args]string{
|
| 194 |
- &Args{
|
|
| 195 |
- "created": []string{"tomorrow"},
|
|
| 201 |
+ &Args{map[string]map[string]bool{
|
|
| 202 |
+ "created": map[string]bool{"tomorrow": true}},
|
|
| 196 | 203 |
}: "created", |
| 197 |
- &Args{
|
|
| 198 |
- "created": []string{"to(day"},
|
|
| 204 |
+ &Args{map[string]map[string]bool{
|
|
| 205 |
+ "created": map[string]bool{"to(day": true}},
|
|
| 199 | 206 |
}: "created", |
| 200 |
- &Args{
|
|
| 201 |
- "created": []string{"tom(.*)"},
|
|
| 207 |
+ &Args{map[string]map[string]bool{
|
|
| 208 |
+ "created": map[string]bool{"tom(.*)": true}},
|
|
| 202 | 209 |
}: "created", |
| 203 |
- &Args{
|
|
| 204 |
- "created": []string{"today1"},
|
|
| 205 |
- "labels": []string{"today"},
|
|
| 210 |
+ &Args{map[string]map[string]bool{
|
|
| 211 |
+ "created": map[string]bool{"tom": true}},
|
|
| 212 |
+ }: "created", |
|
| 213 |
+ &Args{map[string]map[string]bool{
|
|
| 214 |
+ "created": map[string]bool{"today1": true},
|
|
| 215 |
+ "labels": map[string]bool{"today": true}},
|
|
| 206 | 216 |
}: "created", |
| 207 | 217 |
} |
| 208 |
- for args, field := range matches {
|
|
| 209 |
- if args.Match(field, source) != true {
|
|
| 210 |
- t.Fatalf("Expected true for %v on %v, got false", source, args)
|
|
| 211 |
- } |
|
| 212 |
- } |
|
| 218 |
+ |
|
| 213 | 219 |
for args, field := range differs {
|
| 214 | 220 |
if args.Match(field, source) != false {
|
| 215 | 221 |
t.Fatalf("Expected false for %v on %v, got true", source, args)
|
| 216 | 222 |
} |
| 217 | 223 |
} |
| 218 | 224 |
} |
| 225 |
+ |
|
| 226 |
+func TestAdd(t *testing.T) {
|
|
| 227 |
+ f := NewArgs() |
|
| 228 |
+ f.Add("status", "running")
|
|
| 229 |
+ v := f.fields["status"] |
|
| 230 |
+ if len(v) != 1 || !v["running"] {
|
|
| 231 |
+ t.Fatalf("Expected to include a running status, got %v", v)
|
|
| 232 |
+ } |
|
| 233 |
+ |
|
| 234 |
+ f.Add("status", "paused")
|
|
| 235 |
+ if len(v) != 2 || !v["paused"] {
|
|
| 236 |
+ t.Fatalf("Expected to include a paused status, got %v", v)
|
|
| 237 |
+ } |
|
| 238 |
+} |
|
| 239 |
+ |
|
| 240 |
+func TestDel(t *testing.T) {
|
|
| 241 |
+ f := NewArgs() |
|
| 242 |
+ f.Add("status", "running")
|
|
| 243 |
+ f.Del("status", "running")
|
|
| 244 |
+ v := f.fields["status"] |
|
| 245 |
+ if v["running"] {
|
|
| 246 |
+ t.Fatalf("Expected to not include a running status filter, got true")
|
|
| 247 |
+ } |
|
| 248 |
+} |
|
| 249 |
+ |
|
| 250 |
+func TestLen(t *testing.T) {
|
|
| 251 |
+ f := NewArgs() |
|
| 252 |
+ if f.Len() != 0 {
|
|
| 253 |
+ t.Fatalf("Expected to not include any field")
|
|
| 254 |
+ } |
|
| 255 |
+ f.Add("status", "running")
|
|
| 256 |
+ if f.Len() != 1 {
|
|
| 257 |
+ t.Fatalf("Expected to include one field")
|
|
| 258 |
+ } |
|
| 259 |
+} |
|
| 260 |
+ |
|
| 261 |
+func TestExactMatch(t *testing.T) {
|
|
| 262 |
+ f := NewArgs() |
|
| 263 |
+ |
|
| 264 |
+ if !f.ExactMatch("status", "running") {
|
|
| 265 |
+ t.Fatalf("Expected to match `running` when there are no filters, got false")
|
|
| 266 |
+ } |
|
| 267 |
+ |
|
| 268 |
+ f.Add("status", "running")
|
|
| 269 |
+ f.Add("status", "pause*")
|
|
| 270 |
+ |
|
| 271 |
+ if !f.ExactMatch("status", "running") {
|
|
| 272 |
+ t.Fatalf("Expected to match `running` with one of the filters, got false")
|
|
| 273 |
+ } |
|
| 274 |
+ |
|
| 275 |
+ if f.ExactMatch("status", "paused") {
|
|
| 276 |
+ t.Fatalf("Expected to not match `paused` with one of the filters, got true")
|
|
| 277 |
+ } |
|
| 278 |
+} |
|
| 279 |
+ |
|
| 280 |
+func TestInclude(t *testing.T) {
|
|
| 281 |
+ f := NewArgs() |
|
| 282 |
+ if f.Include("status") {
|
|
| 283 |
+ t.Fatalf("Expected to not include a status key, got true")
|
|
| 284 |
+ } |
|
| 285 |
+ f.Add("status", "running")
|
|
| 286 |
+ if !f.Include("status") {
|
|
| 287 |
+ t.Fatalf("Expected to include a status key, got false")
|
|
| 288 |
+ } |
|
| 289 |
+} |
|
| 290 |
+ |
|
| 291 |
+func TestValidate(t *testing.T) {
|
|
| 292 |
+ f := NewArgs() |
|
| 293 |
+ f.Add("status", "running")
|
|
| 294 |
+ |
|
| 295 |
+ valid := map[string]bool{
|
|
| 296 |
+ "status": true, |
|
| 297 |
+ "dangling": true, |
|
| 298 |
+ } |
|
| 299 |
+ |
|
| 300 |
+ if err := f.Validate(valid); err != nil {
|
|
| 301 |
+ t.Fatal(err) |
|
| 302 |
+ } |
|
| 303 |
+ |
|
| 304 |
+ f.Add("bogus", "running")
|
|
| 305 |
+ if err := f.Validate(valid); err == nil {
|
|
| 306 |
+ t.Fatalf("Expected to return an error, got nil")
|
|
| 307 |
+ } |
|
| 308 |
+} |
|
| 309 |
+ |
|
| 310 |
+func TestWalkValues(t *testing.T) {
|
|
| 311 |
+ f := NewArgs() |
|
| 312 |
+ f.Add("status", "running")
|
|
| 313 |
+ f.Add("status", "paused")
|
|
| 314 |
+ |
|
| 315 |
+ f.WalkValues("status", func(value string) error {
|
|
| 316 |
+ if value != "running" && value != "paused" {
|
|
| 317 |
+ t.Fatalf("Unexpected value %s", value)
|
|
| 318 |
+ } |
|
| 319 |
+ return nil |
|
| 320 |
+ }) |
|
| 321 |
+ |
|
| 322 |
+ err := f.WalkValues("status", func(value string) error {
|
|
| 323 |
+ return fmt.Errorf("return")
|
|
| 324 |
+ }) |
|
| 325 |
+ if err == nil {
|
|
| 326 |
+ t.Fatalf("Expected to get an error, got nil")
|
|
| 327 |
+ } |
|
| 328 |
+ |
|
| 329 |
+ err = f.WalkValues("foo", func(value string) error {
|
|
| 330 |
+ return fmt.Errorf("return")
|
|
| 331 |
+ }) |
|
| 332 |
+ if err != nil {
|
|
| 333 |
+ t.Fatalf("Expected to not iterate when the field doesn't exist, got %v", err)
|
|
| 334 |
+ } |
|
| 335 |
+} |