mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
api/types/filters: move to daemon/internal
Most of the code in the filters package relates to the unmarshaling, validation and application of filters from client requests. None of this is necessary or particularly useful for Go SDK users. Move the full-fat filters package into daemon/internal and switch all the daemon code to import that package so we are free to iterate upon the code without worrying about source-code interface compatibility. Signed-off-by: Cory Snider <csnider@mirantis.com>
This commit is contained in:
@@ -6,8 +6,6 @@ package filters
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Args stores a mapping of keys to a set of multiple values.
|
||||
@@ -35,15 +33,6 @@ func NewArgs(initialArgs ...KeyValuePair) Args {
|
||||
return args
|
||||
}
|
||||
|
||||
// Keys returns all the keys in list of Args
|
||||
func (args Args) Keys() []string {
|
||||
keys := make([]string, 0, len(args.fields))
|
||||
for k := range args.fields {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte representation of the Args
|
||||
func (args Args) MarshalJSON() ([]byte, error) {
|
||||
if len(args.fields) == 0 {
|
||||
@@ -61,48 +50,6 @@ func ToJSON(a Args) (string, error) {
|
||||
return string(buf), err
|
||||
}
|
||||
|
||||
// FromJSON decodes a JSON encoded string into Args
|
||||
func FromJSON(p string) (Args, error) {
|
||||
args := NewArgs()
|
||||
|
||||
if p == "" {
|
||||
return args, nil
|
||||
}
|
||||
|
||||
raw := []byte(p)
|
||||
err := json.Unmarshal(raw, &args)
|
||||
if err == nil {
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// Fallback to parsing arguments in the legacy slice format
|
||||
deprecated := map[string][]string{}
|
||||
if legacyErr := json.Unmarshal(raw, &deprecated); legacyErr != nil {
|
||||
return args, &invalidFilter{}
|
||||
}
|
||||
|
||||
args.fields = deprecatedArgs(deprecated)
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON populates the Args from JSON encode bytes
|
||||
func (args Args) UnmarshalJSON(raw []byte) error {
|
||||
return json.Unmarshal(raw, &args.fields)
|
||||
}
|
||||
|
||||
// Get returns the list of values associated with the key
|
||||
func (args Args) Get(key string) []string {
|
||||
values := args.fields[key]
|
||||
if values == nil {
|
||||
return make([]string, 0)
|
||||
}
|
||||
slice := make([]string, 0, len(values))
|
||||
for key := range values {
|
||||
slice = append(slice, key)
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// Add a new value to the set of values
|
||||
func (args Args) Add(key, value string) {
|
||||
if _, ok := args.fields[key]; ok {
|
||||
@@ -112,191 +59,7 @@ func (args Args) Add(key, value string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Del removes a value from the set
|
||||
func (args Args) Del(key, value string) {
|
||||
if _, ok := args.fields[key]; ok {
|
||||
delete(args.fields[key], value)
|
||||
if len(args.fields[key]) == 0 {
|
||||
delete(args.fields, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the number of keys in the mapping
|
||||
func (args Args) Len() int {
|
||||
return len(args.fields)
|
||||
}
|
||||
|
||||
// MatchKVList returns true if all the pairs in sources exist as key=value
|
||||
// pairs in the mapping at key, or if there are no values at key.
|
||||
func (args Args) MatchKVList(key string, sources map[string]string) bool {
|
||||
fieldValues := args.fields[key]
|
||||
|
||||
// do not filter if there is no filter set or cannot determine filter
|
||||
if len(fieldValues) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(sources) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for value := range fieldValues {
|
||||
testK, testV, hasValue := strings.Cut(value, "=")
|
||||
|
||||
v, ok := sources[testK]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if hasValue && testV != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Match returns true if any of the values at key match the source string
|
||||
func (args Args) Match(field, source string) bool {
|
||||
if args.ExactMatch(field, source) {
|
||||
return true
|
||||
}
|
||||
|
||||
fieldValues := args.fields[field]
|
||||
for name2match := range fieldValues {
|
||||
match, err := regexp.MatchString(name2match, source)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if match {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetBoolOrDefault returns a boolean value of the key if the key is present
|
||||
// and is interpretable as a boolean value. Otherwise the default value is returned.
|
||||
// Error is not nil only if the filter values are not valid boolean or are conflicting.
|
||||
func (args Args) GetBoolOrDefault(key string, defaultValue bool) (bool, error) {
|
||||
fieldValues, ok := args.fields[key]
|
||||
if !ok {
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
if len(fieldValues) == 0 {
|
||||
return defaultValue, &invalidFilter{key, nil}
|
||||
}
|
||||
|
||||
isFalse := fieldValues["0"] || fieldValues["false"]
|
||||
isTrue := fieldValues["1"] || fieldValues["true"]
|
||||
if isFalse == isTrue {
|
||||
// Either no or conflicting truthy/falsy value were provided
|
||||
return defaultValue, &invalidFilter{key, args.Get(key)}
|
||||
}
|
||||
return isTrue, nil
|
||||
}
|
||||
|
||||
// ExactMatch returns true if the source matches exactly one of the values.
|
||||
func (args Args) ExactMatch(key, source string) bool {
|
||||
fieldValues, ok := args.fields[key]
|
||||
// do not filter if there is no filter set or cannot determine filter
|
||||
if !ok || len(fieldValues) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// try to match full name value to avoid O(N) regular expression matching
|
||||
return fieldValues[source]
|
||||
}
|
||||
|
||||
// UniqueExactMatch returns true if there is only one value and the source
|
||||
// matches exactly the value.
|
||||
func (args Args) UniqueExactMatch(key, source string) bool {
|
||||
fieldValues := args.fields[key]
|
||||
// do not filter if there is no filter set or cannot determine filter
|
||||
if len(fieldValues) == 0 {
|
||||
return true
|
||||
}
|
||||
if len(args.fields[key]) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
// try to match full name value to avoid O(N) regular expression matching
|
||||
return fieldValues[source]
|
||||
}
|
||||
|
||||
// FuzzyMatch returns true if the source matches exactly one value, or the
|
||||
// source has one of the values as a prefix.
|
||||
func (args Args) FuzzyMatch(key, source string) bool {
|
||||
if args.ExactMatch(key, source) {
|
||||
return true
|
||||
}
|
||||
|
||||
fieldValues := args.fields[key]
|
||||
for prefix := range fieldValues {
|
||||
if strings.HasPrefix(source, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Contains returns true if the key exists in the mapping
|
||||
func (args Args) Contains(field string) bool {
|
||||
_, ok := args.fields[field]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Validate compared the set of accepted keys against the keys in the mapping.
|
||||
// An error is returned if any mapping keys are not in the accepted set.
|
||||
func (args Args) Validate(accepted map[string]bool) error {
|
||||
for name := range args.fields {
|
||||
if !accepted[name] {
|
||||
return &invalidFilter{name, nil}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WalkValues iterates over the list of values for a key in the mapping and calls
|
||||
// op() for each value. If op returns an error the iteration stops and the
|
||||
// error is returned.
|
||||
func (args Args) WalkValues(field string, op func(value string) error) error {
|
||||
if _, ok := args.fields[field]; !ok {
|
||||
return nil
|
||||
}
|
||||
for v := range args.fields[field] {
|
||||
if err := op(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone returns a copy of args.
|
||||
func (args Args) Clone() (newArgs Args) {
|
||||
newArgs.fields = make(map[string]map[string]bool, len(args.fields))
|
||||
for k, m := range args.fields {
|
||||
var mm map[string]bool
|
||||
if m != nil {
|
||||
mm = make(map[string]bool, len(m))
|
||||
for kk, v := range m {
|
||||
mm[kk] = v
|
||||
}
|
||||
}
|
||||
newArgs.fields[k] = mm
|
||||
}
|
||||
return newArgs
|
||||
}
|
||||
|
||||
func deprecatedArgs(d map[string][]string) map[string]map[string]bool {
|
||||
m := map[string]map[string]bool{}
|
||||
for k, v := range d {
|
||||
values := map[string]bool{}
|
||||
for _, vv := range v {
|
||||
values[vv] = true
|
||||
}
|
||||
m[k] = values
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package filters
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
@@ -43,457 +41,22 @@ func TestToJSON(t *testing.T) {
|
||||
assert.Check(t, is.Equal(s, expected))
|
||||
}
|
||||
|
||||
func TestFromJSON(t *testing.T) {
|
||||
invalids := []string{
|
||||
"anything",
|
||||
"['a','list']",
|
||||
"{'key': 'value'}",
|
||||
`{"key": "value"}`,
|
||||
}
|
||||
valid := map[*Args][]string{
|
||||
{fields: map[string]map[string]bool{"key": {"value": true}}}: {
|
||||
`{"key": ["value"]}`,
|
||||
`{"key": {"value": true}}`,
|
||||
},
|
||||
{fields: map[string]map[string]bool{"key": {"value1": true, "value2": true}}}: {
|
||||
`{"key": ["value1", "value2"]}`,
|
||||
`{"key": {"value1": true, "value2": true}}`,
|
||||
},
|
||||
{fields: map[string]map[string]bool{"key1": {"value1": true}, "key2": {"value2": true}}}: {
|
||||
`{"key1": ["value1"], "key2": ["value2"]}`,
|
||||
`{"key1": {"value1": true}, "key2": {"value2": true}}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, invalid := range invalids {
|
||||
t.Run(invalid, func(t *testing.T) {
|
||||
_, err := FromJSON(invalid)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected an error with %v, got nothing", invalid)
|
||||
}
|
||||
var invalidFilterError *invalidFilter
|
||||
assert.Check(t, is.ErrorType(err, invalidFilterError))
|
||||
wrappedErr := fmt.Errorf("something went wrong: %w", err)
|
||||
assert.Check(t, is.ErrorIs(wrappedErr, err))
|
||||
})
|
||||
}
|
||||
|
||||
for expectedArgs, matchers := range valid {
|
||||
for _, jsonString := range matchers {
|
||||
args, err := FromJSON(jsonString)
|
||||
assert.Check(t, err)
|
||||
assert.Check(t, is.Equal(args.Len(), expectedArgs.Len()))
|
||||
for key, expectedValues := range expectedArgs.fields {
|
||||
values := args.Get(key)
|
||||
assert.Check(t, is.Len(values, len(expectedValues)), expectedArgs)
|
||||
|
||||
for _, v := range values {
|
||||
if !expectedValues[v] {
|
||||
t.Errorf("Expected %v, go %v", expectedArgs, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
a := Args{}
|
||||
assert.Check(t, is.Equal(a.Len(), 0))
|
||||
v, err := ToJSON(a)
|
||||
assert.Check(t, err)
|
||||
v1, err := FromJSON(v)
|
||||
assert.Check(t, err)
|
||||
assert.Check(t, is.Equal(v1.Len(), 0))
|
||||
}
|
||||
|
||||
func TestArgsMatchKVListEmptySources(t *testing.T) {
|
||||
args := NewArgs()
|
||||
if !args.MatchKVList("created", map[string]string{}) {
|
||||
t.Fatalf("Expected true for (%v,created), got true", args)
|
||||
}
|
||||
|
||||
args = Args{map[string]map[string]bool{"created": {"today": true}}}
|
||||
if args.MatchKVList("created", map[string]string{}) {
|
||||
t.Fatalf("Expected false for (%v,created), got true", args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArgsMatchKVList(t *testing.T) {
|
||||
// Not empty sources
|
||||
sources := map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
}
|
||||
|
||||
matches := map[*Args]string{
|
||||
{}: "field",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"today": true},
|
||||
"labels": {"key1": true},
|
||||
},
|
||||
}: "labels",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"today": true},
|
||||
"labels": {"key1=value1": true},
|
||||
},
|
||||
}: "labels",
|
||||
}
|
||||
|
||||
for args, field := range matches {
|
||||
if !args.MatchKVList(field, sources) {
|
||||
t.Errorf("Expected true for %v on %v, got false", sources, args)
|
||||
}
|
||||
}
|
||||
|
||||
differs := map[*Args]string{
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"today": true},
|
||||
},
|
||||
}: "created",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"today": true},
|
||||
"labels": {"key4": true},
|
||||
},
|
||||
}: "labels",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"today": true},
|
||||
"labels": {"key1=value3": true},
|
||||
},
|
||||
}: "labels",
|
||||
}
|
||||
|
||||
for args, field := range differs {
|
||||
if args.MatchKVList(field, sources) {
|
||||
t.Errorf("Expected false for %v on %v, got true", sources, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestArgsMatch(t *testing.T) {
|
||||
source := "today"
|
||||
|
||||
matches := map[*Args]string{
|
||||
{}: "field",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"today": true},
|
||||
},
|
||||
}: "today",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"to*": true},
|
||||
},
|
||||
}: "created",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"to(.*)": true},
|
||||
},
|
||||
}: "created",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"tod": true},
|
||||
},
|
||||
}: "created",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"anything": true, "to*": true},
|
||||
},
|
||||
}: "created",
|
||||
}
|
||||
|
||||
for args, field := range matches {
|
||||
assert.Check(t, args.Match(field, source), "Expected field %s to match %s", field, source)
|
||||
}
|
||||
|
||||
differs := map[*Args]string{
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"tomorrow": true},
|
||||
},
|
||||
}: "created",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"to(day": true},
|
||||
},
|
||||
}: "created",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"tom(.*)": true},
|
||||
},
|
||||
}: "created",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"tom": true},
|
||||
},
|
||||
}: "created",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"today1": true},
|
||||
"labels": {"today": true},
|
||||
},
|
||||
}: "created",
|
||||
}
|
||||
|
||||
for args, field := range differs {
|
||||
assert.Check(t, !args.Match(field, source), "Expected field %s to not match %s", field, source)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
f := NewArgs()
|
||||
f.Add("status", "running")
|
||||
v := f.Get("status")
|
||||
v := f.fields["status"]
|
||||
assert.Check(t, is.DeepEqual(v, []string{"running"}))
|
||||
|
||||
f.Add("status", "paused")
|
||||
v = f.Get("status")
|
||||
v = f.fields["status"]
|
||||
assert.Check(t, is.Len(v, 2))
|
||||
assert.Check(t, is.Contains(v, "running"))
|
||||
assert.Check(t, is.Contains(v, "paused"))
|
||||
}
|
||||
|
||||
func TestDel(t *testing.T) {
|
||||
f := NewArgs()
|
||||
f.Add("status", "running")
|
||||
f.Del("status", "running")
|
||||
assert.Check(t, is.Equal(f.Len(), 0))
|
||||
assert.Check(t, is.DeepEqual(f.Get("status"), []string{}))
|
||||
}
|
||||
|
||||
func TestLen(t *testing.T) {
|
||||
f := NewArgs()
|
||||
assert.Check(t, is.Equal(f.Len(), 0))
|
||||
f.Add("status", "running")
|
||||
assert.Check(t, is.Equal(f.Len(), 1))
|
||||
}
|
||||
|
||||
func TestExactMatch(t *testing.T) {
|
||||
f := NewArgs()
|
||||
|
||||
assert.Check(t, f.ExactMatch("status", "running"), "Expected to match `running` when there are no filters")
|
||||
|
||||
f.Add("status", "running")
|
||||
f.Add("status", "pause*")
|
||||
|
||||
assert.Check(t, f.ExactMatch("status", "running"), "Expected to match `running` with one of the filters")
|
||||
assert.Check(t, !f.ExactMatch("status", "paused"), "Expected to not match `paused` with one of the filters")
|
||||
}
|
||||
|
||||
func TestOnlyOneExactMatch(t *testing.T) {
|
||||
f := NewArgs()
|
||||
|
||||
assert.Check(t, f.ExactMatch("status", "running"), "Expected to match `running` when there are no filters")
|
||||
|
||||
f.Add("status", "running")
|
||||
assert.Check(t, f.ExactMatch("status", "running"), "Expected to match `running` with one of the filters")
|
||||
assert.Check(t, !f.UniqueExactMatch("status", "paused"), "Expected to not match `paused` with one of the filters")
|
||||
|
||||
f.Add("status", "pause")
|
||||
assert.Check(t, !f.UniqueExactMatch("status", "running"), "Expected to not match only `running` with two filters")
|
||||
}
|
||||
|
||||
func TestContains(t *testing.T) {
|
||||
f := NewArgs()
|
||||
assert.Check(t, !f.Contains("status"))
|
||||
|
||||
f.Add("status", "running")
|
||||
assert.Check(t, f.Contains("status"))
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
f := NewArgs()
|
||||
f.Add("status", "running")
|
||||
|
||||
valid := map[string]bool{
|
||||
"status": true,
|
||||
"dangling": true,
|
||||
}
|
||||
|
||||
assert.Check(t, f.Validate(valid))
|
||||
|
||||
f.Add("bogus", "running")
|
||||
err := f.Validate(valid)
|
||||
var invalidFilterError *invalidFilter
|
||||
assert.Check(t, is.ErrorType(err, invalidFilterError))
|
||||
wrappedErr := fmt.Errorf("something went wrong: %w", err)
|
||||
assert.Check(t, is.ErrorIs(wrappedErr, err))
|
||||
}
|
||||
|
||||
func TestWalkValues(t *testing.T) {
|
||||
f := NewArgs()
|
||||
f.Add("status", "running")
|
||||
f.Add("status", "paused")
|
||||
|
||||
err := f.WalkValues("status", func(value string) error {
|
||||
if value != "running" && value != "paused" {
|
||||
t.Fatalf("Unexpected value %s", value)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
assert.Check(t, err)
|
||||
|
||||
loops1 := 0
|
||||
err = f.WalkValues("status", func(value string) error {
|
||||
loops1++
|
||||
return nil
|
||||
})
|
||||
assert.Check(t, err)
|
||||
assert.Check(t, is.Equal(loops1, 2), "Expected to not iterate when the field doesn't exist")
|
||||
|
||||
loops2 := 0
|
||||
err = f.WalkValues("unknown-key", func(value string) error {
|
||||
loops2++
|
||||
return nil
|
||||
})
|
||||
assert.Check(t, err)
|
||||
assert.Check(t, is.Equal(loops2, 0), "Expected to not iterate when the field doesn't exist")
|
||||
}
|
||||
|
||||
func TestFuzzyMatch(t *testing.T) {
|
||||
f := NewArgs()
|
||||
f.Add("container", "foo")
|
||||
|
||||
cases := map[string]bool{
|
||||
"foo": true,
|
||||
"foobar": true,
|
||||
"barfoo": false,
|
||||
"bar": false,
|
||||
}
|
||||
for source, match := range cases {
|
||||
got := f.FuzzyMatch("container", source)
|
||||
if got != match {
|
||||
t.Errorf("Expected %v, got %v: %s", match, got, source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClone(t *testing.T) {
|
||||
f := NewArgs()
|
||||
f.Add("foo", "bar")
|
||||
f2 := f.Clone()
|
||||
f2.Add("baz", "qux")
|
||||
assert.Check(t, is.Len(f.Get("baz"), 0))
|
||||
}
|
||||
|
||||
func TestGetBoolOrDefault(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
args map[string][]string
|
||||
defValue bool
|
||||
expectedErr error
|
||||
expectedValue bool
|
||||
}{
|
||||
{
|
||||
name: "single true",
|
||||
args: map[string][]string{
|
||||
"dangling": {"true"},
|
||||
},
|
||||
defValue: false,
|
||||
expectedErr: nil,
|
||||
expectedValue: true,
|
||||
},
|
||||
{
|
||||
name: "single false",
|
||||
args: map[string][]string{
|
||||
"dangling": {"false"},
|
||||
},
|
||||
defValue: true,
|
||||
expectedErr: nil,
|
||||
expectedValue: false,
|
||||
},
|
||||
{
|
||||
name: "single bad value",
|
||||
args: map[string][]string{
|
||||
"dangling": {"potato"},
|
||||
},
|
||||
defValue: true,
|
||||
expectedErr: &invalidFilter{Filter: "dangling", Value: []string{"potato"}},
|
||||
expectedValue: true,
|
||||
},
|
||||
{
|
||||
name: "two bad values",
|
||||
args: map[string][]string{
|
||||
"dangling": {"banana", "potato"},
|
||||
},
|
||||
defValue: true,
|
||||
expectedErr: &invalidFilter{Filter: "dangling", Value: []string{"banana", "potato"}},
|
||||
expectedValue: true,
|
||||
},
|
||||
{
|
||||
name: "two conflicting values",
|
||||
args: map[string][]string{
|
||||
"dangling": {"false", "true"},
|
||||
},
|
||||
defValue: false,
|
||||
expectedErr: &invalidFilter{Filter: "dangling", Value: []string{"false", "true"}},
|
||||
expectedValue: false,
|
||||
},
|
||||
{
|
||||
name: "multiple conflicting values",
|
||||
args: map[string][]string{
|
||||
"dangling": {"false", "true", "1"},
|
||||
},
|
||||
defValue: true,
|
||||
expectedErr: &invalidFilter{Filter: "dangling", Value: []string{"false", "true", "1"}},
|
||||
expectedValue: true,
|
||||
},
|
||||
{
|
||||
name: "1 means true",
|
||||
args: map[string][]string{
|
||||
"dangling": {"1"},
|
||||
},
|
||||
defValue: false,
|
||||
expectedErr: nil,
|
||||
expectedValue: true,
|
||||
},
|
||||
{
|
||||
name: "0 means false",
|
||||
args: map[string][]string{
|
||||
"dangling": {"0"},
|
||||
},
|
||||
defValue: true,
|
||||
expectedErr: nil,
|
||||
expectedValue: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
a := NewArgs()
|
||||
|
||||
for key, values := range tc.args {
|
||||
for _, value := range values {
|
||||
a.Add(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
value, err := a.GetBoolOrDefault("dangling", tc.defValue)
|
||||
|
||||
if tc.expectedErr == nil {
|
||||
assert.Check(t, is.Nil(err))
|
||||
} else {
|
||||
assert.Check(t, is.ErrorType(err, tc.expectedErr))
|
||||
|
||||
// Check if error is the same.
|
||||
expected := tc.expectedErr.(*invalidFilter)
|
||||
actual := err.(*invalidFilter)
|
||||
|
||||
assert.Check(t, is.Equal(expected.Filter, actual.Filter))
|
||||
|
||||
sort.Strings(expected.Value)
|
||||
sort.Strings(actual.Value)
|
||||
assert.Check(t, is.DeepEqual(expected.Value, actual.Value))
|
||||
|
||||
wrappedErr := fmt.Errorf("something went wrong: %w", err)
|
||||
assert.Check(t, is.ErrorIs(wrappedErr, err), "Expected a wrapped error to be detected as invalidFilter")
|
||||
}
|
||||
|
||||
assert.Check(t, is.Equal(tc.expectedValue, value))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/docker/distribution"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/events"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
"github.com/moby/moby/api/types/swarm"
|
||||
@@ -16,6 +15,7 @@ import (
|
||||
"github.com/moby/moby/api/types/volume"
|
||||
clustertypes "github.com/moby/moby/v2/daemon/cluster/provider"
|
||||
containerpkg "github.com/moby/moby/v2/daemon/container"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/image"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/cluster"
|
||||
|
||||
@@ -15,13 +15,13 @@ import (
|
||||
gogotypes "github.com/gogo/protobuf/types"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/events"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
enginemount "github.com/moby/moby/api/types/mount"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/api/types/volume"
|
||||
"github.com/moby/moby/v2/daemon/cluster/convert"
|
||||
executorpkg "github.com/moby/moby/v2/daemon/cluster/executor"
|
||||
clustertypes "github.com/moby/moby/v2/daemon/cluster/provider"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/netiputil"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/scope"
|
||||
"github.com/moby/moby/v2/internal/sliceutil"
|
||||
|
||||
@@ -8,13 +8,13 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
swarmtypes "github.com/moby/moby/api/types/swarm"
|
||||
"github.com/moby/moby/v2/daemon/cluster/controllers/plugin"
|
||||
"github.com/moby/moby/v2/daemon/cluster/convert"
|
||||
executorpkg "github.com/moby/moby/v2/daemon/cluster/executor"
|
||||
clustertypes "github.com/moby/moby/v2/daemon/cluster/provider"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork"
|
||||
networktypes "github.com/moby/moby/v2/daemon/libnetwork/types"
|
||||
"github.com/moby/swarmkit/v2/agent"
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
swarmapi "github.com/moby/swarmkit/v2/api"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package cluster
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
)
|
||||
|
||||
func TestNewListSecretsFilters(t *testing.T) {
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
types "github.com/moby/moby/api/types/swarm"
|
||||
"github.com/moby/moby/v2/daemon/cluster/convert"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/stack"
|
||||
"github.com/moby/moby/v2/daemon/pkg/opts"
|
||||
"github.com/moby/moby/v2/daemon/server/backend"
|
||||
|
||||
@@ -3,9 +3,9 @@ package cluster
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
types "github.com/moby/moby/api/types/swarm"
|
||||
"github.com/moby/moby/v2/daemon/cluster/convert"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/server/swarmbackend"
|
||||
swarmapi "github.com/moby/swarmkit/v2/api"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
bkconfig "github.com/moby/buildkit/cmd/buildkitd/config"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
)
|
||||
|
||||
// BuilderGCRule represents a GC rule for buildkit cache
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/fs"
|
||||
)
|
||||
|
||||
@@ -18,8 +18,8 @@ import (
|
||||
"github.com/distribution/reference"
|
||||
"github.com/moby/buildkit/util/attestation"
|
||||
dockerspec "github.com/moby/docker-image-spec/specs-go/v1"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
imagetypes "github.com/moby/moby/api/types/image"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/timestamp"
|
||||
"github.com/moby/moby/v2/daemon/server/imagebackend"
|
||||
"github.com/moby/moby/v2/errdefs"
|
||||
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
"github.com/containerd/log"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/moby/moby/api/types/events"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/image"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/image"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/server/backend"
|
||||
"github.com/moby/moby/v2/daemon/server/imagebackend"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"github.com/containerd/log"
|
||||
gogotypes "github.com/gogo/protobuf/types"
|
||||
"github.com/moby/moby/api/types/events"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/v2/daemon/container"
|
||||
daemonevents "github.com/moby/moby/v2/daemon/events"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork"
|
||||
swarmapi "github.com/moby/swarmkit/v2/api"
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ package events
|
||||
import (
|
||||
"github.com/distribution/reference"
|
||||
"github.com/moby/moby/api/types/events"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
)
|
||||
|
||||
// Filter can filter out docker events from a stream
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"github.com/distribution/reference"
|
||||
"github.com/moby/go-archive"
|
||||
"github.com/moby/moby/api/types/events"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
imagetype "github.com/moby/moby/api/types/image"
|
||||
"github.com/moby/moby/v2/daemon/builder"
|
||||
"github.com/moby/moby/v2/daemon/container"
|
||||
"github.com/moby/moby/v2/daemon/images"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/image"
|
||||
"github.com/moby/moby/v2/daemon/internal/layer"
|
||||
"github.com/moby/moby/v2/daemon/server/backend"
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/containerd/log"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/moby/moby/api/types/events"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
imagetypes "github.com/moby/moby/api/types/image"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/image"
|
||||
"github.com/moby/moby/v2/daemon/internal/layer"
|
||||
"github.com/moby/moby/v2/daemon/internal/timestamp"
|
||||
|
||||
@@ -45,7 +45,6 @@ import (
|
||||
"github.com/moby/buildkit/worker"
|
||||
"github.com/moby/buildkit/worker/containerd"
|
||||
"github.com/moby/buildkit/worker/label"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/v2/daemon/config"
|
||||
"github.com/moby/moby/v2/daemon/graphdriver"
|
||||
"github.com/moby/moby/v2/daemon/internal/builder-next/adapters/containerimage"
|
||||
@@ -55,6 +54,7 @@ import (
|
||||
"github.com/moby/moby/v2/daemon/internal/builder-next/imagerefchecker"
|
||||
mobyworker "github.com/moby/moby/v2/daemon/internal/builder-next/worker"
|
||||
wlabel "github.com/moby/moby/v2/daemon/internal/builder-next/worker/label"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/server/buildbackend"
|
||||
"github.com/pkg/errors"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
|
||||
24
daemon/internal/filters/errors.go
Normal file
24
daemon/internal/filters/errors.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package filters
|
||||
|
||||
import "fmt"
|
||||
|
||||
// invalidFilter indicates that the provided filter or its value is invalid
|
||||
type invalidFilter struct {
|
||||
Filter string
|
||||
Value []string
|
||||
}
|
||||
|
||||
func (e invalidFilter) Error() string {
|
||||
msg := "invalid filter"
|
||||
if e.Filter != "" {
|
||||
msg += " '" + e.Filter
|
||||
if e.Value != nil {
|
||||
msg = fmt.Sprintf("%s=%s", msg, e.Value)
|
||||
}
|
||||
msg += "'"
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// InvalidParameter marks this error as ErrInvalidParameter
|
||||
func (e invalidFilter) InvalidParameter() {}
|
||||
302
daemon/internal/filters/parse.go
Normal file
302
daemon/internal/filters/parse.go
Normal file
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
Package filters provides tools for encoding a mapping of keys to a set of
|
||||
multiple values.
|
||||
*/
|
||||
package filters
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Args stores a mapping of keys to a set of multiple values.
|
||||
type Args struct {
|
||||
fields map[string]map[string]bool
|
||||
}
|
||||
|
||||
// KeyValuePair are used to initialize a new Args
|
||||
type KeyValuePair struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
// Arg creates a new KeyValuePair for initializing Args
|
||||
func Arg(key, value string) KeyValuePair {
|
||||
return KeyValuePair{Key: key, Value: value}
|
||||
}
|
||||
|
||||
// NewArgs returns a new Args populated with the initial args
|
||||
func NewArgs(initialArgs ...KeyValuePair) Args {
|
||||
args := Args{fields: map[string]map[string]bool{}}
|
||||
for _, arg := range initialArgs {
|
||||
args.Add(arg.Key, arg.Value)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// Keys returns all the keys in list of Args
|
||||
func (args Args) Keys() []string {
|
||||
keys := make([]string, 0, len(args.fields))
|
||||
for k := range args.fields {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte representation of the Args
|
||||
func (args Args) MarshalJSON() ([]byte, error) {
|
||||
if len(args.fields) == 0 {
|
||||
return []byte("{}"), nil
|
||||
}
|
||||
return json.Marshal(args.fields)
|
||||
}
|
||||
|
||||
// ToJSON returns the Args as a JSON encoded string
|
||||
func ToJSON(a Args) (string, error) {
|
||||
if a.Len() == 0 {
|
||||
return "", nil
|
||||
}
|
||||
buf, err := json.Marshal(a)
|
||||
return string(buf), err
|
||||
}
|
||||
|
||||
// FromJSON decodes a JSON encoded string into Args
|
||||
func FromJSON(p string) (Args, error) {
|
||||
args := NewArgs()
|
||||
|
||||
if p == "" {
|
||||
return args, nil
|
||||
}
|
||||
|
||||
raw := []byte(p)
|
||||
err := json.Unmarshal(raw, &args)
|
||||
if err == nil {
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// Fallback to parsing arguments in the legacy slice format
|
||||
deprecated := map[string][]string{}
|
||||
if legacyErr := json.Unmarshal(raw, &deprecated); legacyErr != nil {
|
||||
return args, &invalidFilter{}
|
||||
}
|
||||
|
||||
args.fields = deprecatedArgs(deprecated)
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON populates the Args from JSON encode bytes
|
||||
func (args Args) UnmarshalJSON(raw []byte) error {
|
||||
return json.Unmarshal(raw, &args.fields)
|
||||
}
|
||||
|
||||
// Get returns the list of values associated with the key
|
||||
func (args Args) Get(key string) []string {
|
||||
values := args.fields[key]
|
||||
if values == nil {
|
||||
return make([]string, 0)
|
||||
}
|
||||
slice := make([]string, 0, len(values))
|
||||
for key := range values {
|
||||
slice = append(slice, key)
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// Add a new value to the set of values
|
||||
func (args Args) Add(key, value string) {
|
||||
if _, ok := args.fields[key]; ok {
|
||||
args.fields[key][value] = true
|
||||
} else {
|
||||
args.fields[key] = map[string]bool{value: true}
|
||||
}
|
||||
}
|
||||
|
||||
// Del removes a value from the set
|
||||
func (args Args) Del(key, value string) {
|
||||
if _, ok := args.fields[key]; ok {
|
||||
delete(args.fields[key], value)
|
||||
if len(args.fields[key]) == 0 {
|
||||
delete(args.fields, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the number of keys in the mapping
|
||||
func (args Args) Len() int {
|
||||
return len(args.fields)
|
||||
}
|
||||
|
||||
// MatchKVList returns true if all the pairs in sources exist as key=value
|
||||
// pairs in the mapping at key, or if there are no values at key.
|
||||
func (args Args) MatchKVList(key string, sources map[string]string) bool {
|
||||
fieldValues := args.fields[key]
|
||||
|
||||
// do not filter if there is no filter set or cannot determine filter
|
||||
if len(fieldValues) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(sources) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for value := range fieldValues {
|
||||
testK, testV, hasValue := strings.Cut(value, "=")
|
||||
|
||||
v, ok := sources[testK]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if hasValue && testV != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Match returns true if any of the values at key match the source string
|
||||
func (args Args) Match(field, source string) bool {
|
||||
if args.ExactMatch(field, source) {
|
||||
return true
|
||||
}
|
||||
|
||||
fieldValues := args.fields[field]
|
||||
for name2match := range fieldValues {
|
||||
match, err := regexp.MatchString(name2match, source)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if match {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetBoolOrDefault returns a boolean value of the key if the key is present
|
||||
// and is interpretable as a boolean value. Otherwise the default value is returned.
|
||||
// Error is not nil only if the filter values are not valid boolean or are conflicting.
|
||||
func (args Args) GetBoolOrDefault(key string, defaultValue bool) (bool, error) {
|
||||
fieldValues, ok := args.fields[key]
|
||||
if !ok {
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
if len(fieldValues) == 0 {
|
||||
return defaultValue, &invalidFilter{key, nil}
|
||||
}
|
||||
|
||||
isFalse := fieldValues["0"] || fieldValues["false"]
|
||||
isTrue := fieldValues["1"] || fieldValues["true"]
|
||||
if isFalse == isTrue {
|
||||
// Either no or conflicting truthy/falsy value were provided
|
||||
return defaultValue, &invalidFilter{key, args.Get(key)}
|
||||
}
|
||||
return isTrue, nil
|
||||
}
|
||||
|
||||
// ExactMatch returns true if the source matches exactly one of the values.
|
||||
func (args Args) ExactMatch(key, source string) bool {
|
||||
fieldValues, ok := args.fields[key]
|
||||
// do not filter if there is no filter set or cannot determine filter
|
||||
if !ok || len(fieldValues) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// try to match full name value to avoid O(N) regular expression matching
|
||||
return fieldValues[source]
|
||||
}
|
||||
|
||||
// UniqueExactMatch returns true if there is only one value and the source
|
||||
// matches exactly the value.
|
||||
func (args Args) UniqueExactMatch(key, source string) bool {
|
||||
fieldValues := args.fields[key]
|
||||
// do not filter if there is no filter set or cannot determine filter
|
||||
if len(fieldValues) == 0 {
|
||||
return true
|
||||
}
|
||||
if len(args.fields[key]) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
// try to match full name value to avoid O(N) regular expression matching
|
||||
return fieldValues[source]
|
||||
}
|
||||
|
||||
// FuzzyMatch returns true if the source matches exactly one value, or the
|
||||
// source has one of the values as a prefix.
|
||||
func (args Args) FuzzyMatch(key, source string) bool {
|
||||
if args.ExactMatch(key, source) {
|
||||
return true
|
||||
}
|
||||
|
||||
fieldValues := args.fields[key]
|
||||
for prefix := range fieldValues {
|
||||
if strings.HasPrefix(source, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Contains returns true if the key exists in the mapping
|
||||
func (args Args) Contains(field string) bool {
|
||||
_, ok := args.fields[field]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Validate compared the set of accepted keys against the keys in the mapping.
|
||||
// An error is returned if any mapping keys are not in the accepted set.
|
||||
func (args Args) Validate(accepted map[string]bool) error {
|
||||
for name := range args.fields {
|
||||
if !accepted[name] {
|
||||
return &invalidFilter{name, nil}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WalkValues iterates over the list of values for a key in the mapping and calls
|
||||
// op() for each value. If op returns an error the iteration stops and the
|
||||
// error is returned.
|
||||
func (args Args) WalkValues(field string, op func(value string) error) error {
|
||||
if _, ok := args.fields[field]; !ok {
|
||||
return nil
|
||||
}
|
||||
for v := range args.fields[field] {
|
||||
if err := op(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone returns a copy of args.
|
||||
func (args Args) Clone() (newArgs Args) {
|
||||
newArgs.fields = make(map[string]map[string]bool, len(args.fields))
|
||||
for k, m := range args.fields {
|
||||
var mm map[string]bool
|
||||
if m != nil {
|
||||
mm = make(map[string]bool, len(m))
|
||||
for kk, v := range m {
|
||||
mm[kk] = v
|
||||
}
|
||||
}
|
||||
newArgs.fields[k] = mm
|
||||
}
|
||||
return newArgs
|
||||
}
|
||||
|
||||
func deprecatedArgs(d map[string][]string) map[string]map[string]bool {
|
||||
m := map[string]map[string]bool{}
|
||||
for k, v := range d {
|
||||
values := map[string]bool{}
|
||||
for _, vv := range v {
|
||||
values[vv] = true
|
||||
}
|
||||
m[k] = values
|
||||
}
|
||||
return m
|
||||
}
|
||||
499
daemon/internal/filters/parse_test.go
Normal file
499
daemon/internal/filters/parse_test.go
Normal file
@@ -0,0 +1,499 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestMarshalJSON(t *testing.T) {
|
||||
a := NewArgs(
|
||||
Arg("created", "today"),
|
||||
Arg("image.name", "ubuntu*"),
|
||||
Arg("image.name", "*untu"),
|
||||
)
|
||||
|
||||
s, err := a.MarshalJSON()
|
||||
assert.Check(t, err)
|
||||
const expected = `{"created":{"today":true},"image.name":{"*untu":true,"ubuntu*":true}}`
|
||||
assert.Check(t, is.Equal(string(s), expected))
|
||||
}
|
||||
|
||||
func TestMarshalJSONWithEmpty(t *testing.T) {
|
||||
s, err := json.Marshal(NewArgs())
|
||||
assert.Check(t, err)
|
||||
const expected = `{}`
|
||||
assert.Check(t, is.Equal(string(s), expected))
|
||||
}
|
||||
|
||||
func TestToJSON(t *testing.T) {
|
||||
a := NewArgs(
|
||||
Arg("created", "today"),
|
||||
Arg("image.name", "ubuntu*"),
|
||||
Arg("image.name", "*untu"),
|
||||
)
|
||||
|
||||
s, err := ToJSON(a)
|
||||
assert.Check(t, err)
|
||||
const expected = `{"created":{"today":true},"image.name":{"*untu":true,"ubuntu*":true}}`
|
||||
assert.Check(t, is.Equal(s, expected))
|
||||
}
|
||||
|
||||
func TestFromJSON(t *testing.T) {
|
||||
invalids := []string{
|
||||
"anything",
|
||||
"['a','list']",
|
||||
"{'key': 'value'}",
|
||||
`{"key": "value"}`,
|
||||
}
|
||||
valid := map[*Args][]string{
|
||||
{fields: map[string]map[string]bool{"key": {"value": true}}}: {
|
||||
`{"key": ["value"]}`,
|
||||
`{"key": {"value": true}}`,
|
||||
},
|
||||
{fields: map[string]map[string]bool{"key": {"value1": true, "value2": true}}}: {
|
||||
`{"key": ["value1", "value2"]}`,
|
||||
`{"key": {"value1": true, "value2": true}}`,
|
||||
},
|
||||
{fields: map[string]map[string]bool{"key1": {"value1": true}, "key2": {"value2": true}}}: {
|
||||
`{"key1": ["value1"], "key2": ["value2"]}`,
|
||||
`{"key1": {"value1": true}, "key2": {"value2": true}}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, invalid := range invalids {
|
||||
t.Run(invalid, func(t *testing.T) {
|
||||
_, err := FromJSON(invalid)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected an error with %v, got nothing", invalid)
|
||||
}
|
||||
var invalidFilterError *invalidFilter
|
||||
assert.Check(t, is.ErrorType(err, invalidFilterError))
|
||||
wrappedErr := fmt.Errorf("something went wrong: %w", err)
|
||||
assert.Check(t, is.ErrorIs(wrappedErr, err))
|
||||
})
|
||||
}
|
||||
|
||||
for expectedArgs, matchers := range valid {
|
||||
for _, jsonString := range matchers {
|
||||
args, err := FromJSON(jsonString)
|
||||
assert.Check(t, err)
|
||||
assert.Check(t, is.Equal(args.Len(), expectedArgs.Len()))
|
||||
for key, expectedValues := range expectedArgs.fields {
|
||||
values := args.Get(key)
|
||||
assert.Check(t, is.Len(values, len(expectedValues)), expectedArgs)
|
||||
|
||||
for _, v := range values {
|
||||
if !expectedValues[v] {
|
||||
t.Errorf("Expected %v, go %v", expectedArgs, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
a := Args{}
|
||||
assert.Check(t, is.Equal(a.Len(), 0))
|
||||
v, err := ToJSON(a)
|
||||
assert.Check(t, err)
|
||||
v1, err := FromJSON(v)
|
||||
assert.Check(t, err)
|
||||
assert.Check(t, is.Equal(v1.Len(), 0))
|
||||
}
|
||||
|
||||
func TestArgsMatchKVListEmptySources(t *testing.T) {
|
||||
args := NewArgs()
|
||||
if !args.MatchKVList("created", map[string]string{}) {
|
||||
t.Fatalf("Expected true for (%v,created), got true", args)
|
||||
}
|
||||
|
||||
args = Args{map[string]map[string]bool{"created": {"today": true}}}
|
||||
if args.MatchKVList("created", map[string]string{}) {
|
||||
t.Fatalf("Expected false for (%v,created), got true", args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArgsMatchKVList(t *testing.T) {
|
||||
// Not empty sources
|
||||
sources := map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
}
|
||||
|
||||
matches := map[*Args]string{
|
||||
{}: "field",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"today": true},
|
||||
"labels": {"key1": true},
|
||||
},
|
||||
}: "labels",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"today": true},
|
||||
"labels": {"key1=value1": true},
|
||||
},
|
||||
}: "labels",
|
||||
}
|
||||
|
||||
for args, field := range matches {
|
||||
if !args.MatchKVList(field, sources) {
|
||||
t.Errorf("Expected true for %v on %v, got false", sources, args)
|
||||
}
|
||||
}
|
||||
|
||||
differs := map[*Args]string{
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"today": true},
|
||||
},
|
||||
}: "created",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"today": true},
|
||||
"labels": {"key4": true},
|
||||
},
|
||||
}: "labels",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"today": true},
|
||||
"labels": {"key1=value3": true},
|
||||
},
|
||||
}: "labels",
|
||||
}
|
||||
|
||||
for args, field := range differs {
|
||||
if args.MatchKVList(field, sources) {
|
||||
t.Errorf("Expected false for %v on %v, got true", sources, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestArgsMatch(t *testing.T) {
|
||||
source := "today"
|
||||
|
||||
matches := map[*Args]string{
|
||||
{}: "field",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"today": true},
|
||||
},
|
||||
}: "today",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"to*": true},
|
||||
},
|
||||
}: "created",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"to(.*)": true},
|
||||
},
|
||||
}: "created",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"tod": true},
|
||||
},
|
||||
}: "created",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"anything": true, "to*": true},
|
||||
},
|
||||
}: "created",
|
||||
}
|
||||
|
||||
for args, field := range matches {
|
||||
assert.Check(t, args.Match(field, source), "Expected field %s to match %s", field, source)
|
||||
}
|
||||
|
||||
differs := map[*Args]string{
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"tomorrow": true},
|
||||
},
|
||||
}: "created",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"to(day": true},
|
||||
},
|
||||
}: "created",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"tom(.*)": true},
|
||||
},
|
||||
}: "created",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"tom": true},
|
||||
},
|
||||
}: "created",
|
||||
{
|
||||
map[string]map[string]bool{
|
||||
"created": {"today1": true},
|
||||
"labels": {"today": true},
|
||||
},
|
||||
}: "created",
|
||||
}
|
||||
|
||||
for args, field := range differs {
|
||||
assert.Check(t, !args.Match(field, source), "Expected field %s to not match %s", field, source)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
f := NewArgs()
|
||||
f.Add("status", "running")
|
||||
v := f.Get("status")
|
||||
assert.Check(t, is.DeepEqual(v, []string{"running"}))
|
||||
|
||||
f.Add("status", "paused")
|
||||
v = f.Get("status")
|
||||
assert.Check(t, is.Len(v, 2))
|
||||
assert.Check(t, is.Contains(v, "running"))
|
||||
assert.Check(t, is.Contains(v, "paused"))
|
||||
}
|
||||
|
||||
func TestDel(t *testing.T) {
|
||||
f := NewArgs()
|
||||
f.Add("status", "running")
|
||||
f.Del("status", "running")
|
||||
assert.Check(t, is.Equal(f.Len(), 0))
|
||||
assert.Check(t, is.DeepEqual(f.Get("status"), []string{}))
|
||||
}
|
||||
|
||||
func TestLen(t *testing.T) {
|
||||
f := NewArgs()
|
||||
assert.Check(t, is.Equal(f.Len(), 0))
|
||||
f.Add("status", "running")
|
||||
assert.Check(t, is.Equal(f.Len(), 1))
|
||||
}
|
||||
|
||||
func TestExactMatch(t *testing.T) {
|
||||
f := NewArgs()
|
||||
|
||||
assert.Check(t, f.ExactMatch("status", "running"), "Expected to match `running` when there are no filters")
|
||||
|
||||
f.Add("status", "running")
|
||||
f.Add("status", "pause*")
|
||||
|
||||
assert.Check(t, f.ExactMatch("status", "running"), "Expected to match `running` with one of the filters")
|
||||
assert.Check(t, !f.ExactMatch("status", "paused"), "Expected to not match `paused` with one of the filters")
|
||||
}
|
||||
|
||||
func TestOnlyOneExactMatch(t *testing.T) {
|
||||
f := NewArgs()
|
||||
|
||||
assert.Check(t, f.ExactMatch("status", "running"), "Expected to match `running` when there are no filters")
|
||||
|
||||
f.Add("status", "running")
|
||||
assert.Check(t, f.ExactMatch("status", "running"), "Expected to match `running` with one of the filters")
|
||||
assert.Check(t, !f.UniqueExactMatch("status", "paused"), "Expected to not match `paused` with one of the filters")
|
||||
|
||||
f.Add("status", "pause")
|
||||
assert.Check(t, !f.UniqueExactMatch("status", "running"), "Expected to not match only `running` with two filters")
|
||||
}
|
||||
|
||||
func TestContains(t *testing.T) {
|
||||
f := NewArgs()
|
||||
assert.Check(t, !f.Contains("status"))
|
||||
|
||||
f.Add("status", "running")
|
||||
assert.Check(t, f.Contains("status"))
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
f := NewArgs()
|
||||
f.Add("status", "running")
|
||||
|
||||
valid := map[string]bool{
|
||||
"status": true,
|
||||
"dangling": true,
|
||||
}
|
||||
|
||||
assert.Check(t, f.Validate(valid))
|
||||
|
||||
f.Add("bogus", "running")
|
||||
err := f.Validate(valid)
|
||||
var invalidFilterError *invalidFilter
|
||||
assert.Check(t, is.ErrorType(err, invalidFilterError))
|
||||
wrappedErr := fmt.Errorf("something went wrong: %w", err)
|
||||
assert.Check(t, is.ErrorIs(wrappedErr, err))
|
||||
}
|
||||
|
||||
func TestWalkValues(t *testing.T) {
|
||||
f := NewArgs()
|
||||
f.Add("status", "running")
|
||||
f.Add("status", "paused")
|
||||
|
||||
err := f.WalkValues("status", func(value string) error {
|
||||
if value != "running" && value != "paused" {
|
||||
t.Fatalf("Unexpected value %s", value)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
assert.Check(t, err)
|
||||
|
||||
loops1 := 0
|
||||
err = f.WalkValues("status", func(value string) error {
|
||||
loops1++
|
||||
return nil
|
||||
})
|
||||
assert.Check(t, err)
|
||||
assert.Check(t, is.Equal(loops1, 2), "Expected to not iterate when the field doesn't exist")
|
||||
|
||||
loops2 := 0
|
||||
err = f.WalkValues("unknown-key", func(value string) error {
|
||||
loops2++
|
||||
return nil
|
||||
})
|
||||
assert.Check(t, err)
|
||||
assert.Check(t, is.Equal(loops2, 0), "Expected to not iterate when the field doesn't exist")
|
||||
}
|
||||
|
||||
func TestFuzzyMatch(t *testing.T) {
|
||||
f := NewArgs()
|
||||
f.Add("container", "foo")
|
||||
|
||||
cases := map[string]bool{
|
||||
"foo": true,
|
||||
"foobar": true,
|
||||
"barfoo": false,
|
||||
"bar": false,
|
||||
}
|
||||
for source, match := range cases {
|
||||
got := f.FuzzyMatch("container", source)
|
||||
if got != match {
|
||||
t.Errorf("Expected %v, got %v: %s", match, got, source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClone(t *testing.T) {
|
||||
f := NewArgs()
|
||||
f.Add("foo", "bar")
|
||||
f2 := f.Clone()
|
||||
f2.Add("baz", "qux")
|
||||
assert.Check(t, is.Len(f.Get("baz"), 0))
|
||||
}
|
||||
|
||||
func TestGetBoolOrDefault(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
args map[string][]string
|
||||
defValue bool
|
||||
expectedErr error
|
||||
expectedValue bool
|
||||
}{
|
||||
{
|
||||
name: "single true",
|
||||
args: map[string][]string{
|
||||
"dangling": {"true"},
|
||||
},
|
||||
defValue: false,
|
||||
expectedErr: nil,
|
||||
expectedValue: true,
|
||||
},
|
||||
{
|
||||
name: "single false",
|
||||
args: map[string][]string{
|
||||
"dangling": {"false"},
|
||||
},
|
||||
defValue: true,
|
||||
expectedErr: nil,
|
||||
expectedValue: false,
|
||||
},
|
||||
{
|
||||
name: "single bad value",
|
||||
args: map[string][]string{
|
||||
"dangling": {"potato"},
|
||||
},
|
||||
defValue: true,
|
||||
expectedErr: &invalidFilter{Filter: "dangling", Value: []string{"potato"}},
|
||||
expectedValue: true,
|
||||
},
|
||||
{
|
||||
name: "two bad values",
|
||||
args: map[string][]string{
|
||||
"dangling": {"banana", "potato"},
|
||||
},
|
||||
defValue: true,
|
||||
expectedErr: &invalidFilter{Filter: "dangling", Value: []string{"banana", "potato"}},
|
||||
expectedValue: true,
|
||||
},
|
||||
{
|
||||
name: "two conflicting values",
|
||||
args: map[string][]string{
|
||||
"dangling": {"false", "true"},
|
||||
},
|
||||
defValue: false,
|
||||
expectedErr: &invalidFilter{Filter: "dangling", Value: []string{"false", "true"}},
|
||||
expectedValue: false,
|
||||
},
|
||||
{
|
||||
name: "multiple conflicting values",
|
||||
args: map[string][]string{
|
||||
"dangling": {"false", "true", "1"},
|
||||
},
|
||||
defValue: true,
|
||||
expectedErr: &invalidFilter{Filter: "dangling", Value: []string{"false", "true", "1"}},
|
||||
expectedValue: true,
|
||||
},
|
||||
{
|
||||
name: "1 means true",
|
||||
args: map[string][]string{
|
||||
"dangling": {"1"},
|
||||
},
|
||||
defValue: false,
|
||||
expectedErr: nil,
|
||||
expectedValue: true,
|
||||
},
|
||||
{
|
||||
name: "0 means false",
|
||||
args: map[string][]string{
|
||||
"dangling": {"0"},
|
||||
},
|
||||
defValue: true,
|
||||
expectedErr: nil,
|
||||
expectedValue: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
a := NewArgs()
|
||||
|
||||
for key, values := range tc.args {
|
||||
for _, value := range values {
|
||||
a.Add(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
value, err := a.GetBoolOrDefault("dangling", tc.defValue)
|
||||
|
||||
if tc.expectedErr == nil {
|
||||
assert.Check(t, is.Nil(err))
|
||||
} else {
|
||||
assert.Check(t, is.ErrorType(err, tc.expectedErr))
|
||||
|
||||
// Check if error is the same.
|
||||
expected := tc.expectedErr.(*invalidFilter)
|
||||
actual := err.(*invalidFilter)
|
||||
|
||||
assert.Check(t, is.Equal(expected.Filter, actual.Filter))
|
||||
|
||||
sort.Strings(expected.Value)
|
||||
sort.Strings(actual.Value)
|
||||
assert.Check(t, is.DeepEqual(expected.Value, actual.Value))
|
||||
|
||||
wrappedErr := fmt.Errorf("something went wrong: %w", err)
|
||||
assert.Check(t, is.ErrorIs(wrappedErr, err), "Expected a wrapped error to be detected as invalidFilter")
|
||||
}
|
||||
|
||||
assert.Check(t, is.Equal(tc.expectedValue, value))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
"github.com/containerd/log"
|
||||
containertypes "github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/v2/daemon/container"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/image"
|
||||
"github.com/moby/moby/v2/daemon/server/backend"
|
||||
"github.com/moby/moby/v2/daemon/server/imagebackend"
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
containertypes "github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/v2/daemon/container"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/image"
|
||||
"github.com/moby/moby/v2/daemon/server/backend"
|
||||
"github.com/opencontainers/go-digest"
|
||||
|
||||
@@ -3,7 +3,7 @@ package network
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/timestamp"
|
||||
"github.com/moby/moby/v2/errdefs"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
)
|
||||
|
||||
var _ FilterNetwork = mockFilterNetwork{}
|
||||
|
||||
@@ -25,10 +25,10 @@ import (
|
||||
"github.com/moby/moby/api/pkg/progress"
|
||||
"github.com/moby/moby/api/types"
|
||||
"github.com/moby/moby/api/types/events"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/plugin"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
"github.com/moby/moby/v2/daemon/internal/containerfs"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/stringid"
|
||||
v2 "github.com/moby/moby/v2/daemon/pkg/plugin/v2"
|
||||
"github.com/moby/moby/v2/daemon/server/backend"
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/plugin"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/server/backend"
|
||||
)
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/distribution/registry/client/auth"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
"github.com/docker/distribution/registry/client/transport"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"github.com/containerd/log"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/events"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/lazyregexp"
|
||||
"github.com/moby/moby/v2/daemon/internal/timestamp"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork"
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
"github.com/moby/moby/api/types/build"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
)
|
||||
|
||||
type CachePruneOptions struct {
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
imagetypes "github.com/moby/moby/api/types/image"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ import (
|
||||
"github.com/moby/moby/api/pkg/streamformatter"
|
||||
"github.com/moby/moby/api/types/build"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
"github.com/moby/moby/api/types/versions"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/server/buildbackend"
|
||||
"github.com/moby/moby/v2/daemon/server/httputils"
|
||||
"github.com/moby/moby/v2/pkg/ioutils"
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
|
||||
"github.com/moby/go-archive"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
containerpkg "github.com/moby/moby/v2/daemon/container"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/server/backend"
|
||||
"github.com/moby/moby/v2/pkg/sysinfo"
|
||||
)
|
||||
|
||||
@@ -14,10 +14,10 @@ import (
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/moby/moby/api/types"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/mount"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/api/types/versions"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/runconfig"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/netlabel"
|
||||
networkSettings "github.com/moby/moby/v2/daemon/network"
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/image"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
dockerimage "github.com/moby/moby/v2/daemon/internal/image"
|
||||
"github.com/moby/moby/v2/daemon/server/imagebackend"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
||||
@@ -15,11 +15,11 @@ import (
|
||||
"github.com/moby/moby/api/pkg/authconfig"
|
||||
"github.com/moby/moby/api/pkg/progress"
|
||||
"github.com/moby/moby/api/pkg/streamformatter"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
"github.com/moby/moby/api/types/versions"
|
||||
"github.com/moby/moby/v2/daemon/builder/remotecontext"
|
||||
"github.com/moby/moby/v2/daemon/internal/compat"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/image"
|
||||
"github.com/moby/moby/v2/daemon/server/httputils"
|
||||
"github.com/moby/moby/v2/daemon/server/imagebackend"
|
||||
|
||||
@@ -3,8 +3,8 @@ package network
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
dnetwork "github.com/moby/moby/v2/daemon/network"
|
||||
"github.com/moby/moby/v2/daemon/server/backend"
|
||||
)
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/api/types/versions"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/scope"
|
||||
dnetwork "github.com/moby/moby/v2/daemon/network"
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
plugintypes "github.com/moby/moby/api/types/plugin"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/pkg/plugin"
|
||||
"github.com/moby/moby/v2/daemon/server/backend"
|
||||
)
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"github.com/distribution/reference"
|
||||
"github.com/moby/moby/api/pkg/authconfig"
|
||||
"github.com/moby/moby/api/pkg/streamformatter"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/plugin"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/server/backend"
|
||||
"github.com/moby/moby/v2/daemon/server/httputils"
|
||||
"github.com/moby/moby/v2/pkg/ioutils"
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
types "github.com/moby/moby/api/types/swarm"
|
||||
"github.com/moby/moby/api/types/versions"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/server/backend"
|
||||
"github.com/moby/moby/v2/daemon/server/httputils"
|
||||
"github.com/moby/moby/v2/daemon/server/swarmbackend"
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"github.com/moby/moby/api/types"
|
||||
"github.com/moby/moby/api/types/build"
|
||||
"github.com/moby/moby/api/types/events"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
"github.com/moby/moby/api/types/swarm"
|
||||
"github.com/moby/moby/api/types/system"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/server/backend"
|
||||
)
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ import (
|
||||
"github.com/moby/moby/api/types"
|
||||
buildtypes "github.com/moby/moby/api/types/build"
|
||||
"github.com/moby/moby/api/types/events"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
"github.com/moby/moby/api/types/swarm"
|
||||
"github.com/moby/moby/api/types/system"
|
||||
"github.com/moby/moby/api/types/versions"
|
||||
"github.com/moby/moby/v2/daemon/internal/compat"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/timestamp"
|
||||
"github.com/moby/moby/v2/daemon/server/backend"
|
||||
"github.com/moby/moby/v2/daemon/server/httputils"
|
||||
|
||||
@@ -3,8 +3,8 @@ package volume
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/volume"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/server/volumebackend"
|
||||
"github.com/moby/moby/v2/daemon/volume/service/opts"
|
||||
)
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
"github.com/containerd/log"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/versions"
|
||||
"github.com/moby/moby/api/types/volume"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/server/httputils"
|
||||
"github.com/moby/moby/v2/daemon/server/volumebackend"
|
||||
"github.com/moby/moby/v2/daemon/volume/service/opts"
|
||||
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/volume"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/server/httputils"
|
||||
"github.com/moby/moby/v2/daemon/server/volumebackend"
|
||||
"github.com/moby/moby/v2/daemon/volume/service/opts"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package swarmbackend
|
||||
|
||||
import "github.com/moby/moby/api/types/filters"
|
||||
import "github.com/moby/moby/v2/daemon/internal/filters"
|
||||
|
||||
type ConfigListOptions struct {
|
||||
Filters filters.Args
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package volumebackend
|
||||
|
||||
import "github.com/moby/moby/api/types/filters"
|
||||
import "github.com/moby/moby/v2/daemon/internal/filters"
|
||||
|
||||
type ListOptions struct {
|
||||
Filters filters.Args
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/volume"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
volumetypes "github.com/moby/moby/api/types/volume"
|
||||
"github.com/moby/moby/v2/daemon/internal/directory"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/volume"
|
||||
"github.com/moby/moby/v2/errdefs"
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ package service
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/moby/moby/api/types/events"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
volumetypes "github.com/moby/moby/api/types/volume"
|
||||
"github.com/moby/moby/v2/daemon/internal/directory"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/idtools"
|
||||
"github.com/moby/moby/v2/daemon/internal/stringid"
|
||||
"github.com/moby/moby/v2/daemon/volume"
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
"github.com/moby/moby/api/types/events"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/volume"
|
||||
volumedrivers "github.com/moby/moby/v2/daemon/volume/drivers"
|
||||
"github.com/moby/moby/v2/daemon/volume/service/opts"
|
||||
|
||||
237
vendor/github.com/moby/moby/api/types/filters/parse.go
generated
vendored
237
vendor/github.com/moby/moby/api/types/filters/parse.go
generated
vendored
@@ -6,8 +6,6 @@ package filters
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Args stores a mapping of keys to a set of multiple values.
|
||||
@@ -35,15 +33,6 @@ func NewArgs(initialArgs ...KeyValuePair) Args {
|
||||
return args
|
||||
}
|
||||
|
||||
// Keys returns all the keys in list of Args
|
||||
func (args Args) Keys() []string {
|
||||
keys := make([]string, 0, len(args.fields))
|
||||
for k := range args.fields {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte representation of the Args
|
||||
func (args Args) MarshalJSON() ([]byte, error) {
|
||||
if len(args.fields) == 0 {
|
||||
@@ -61,48 +50,6 @@ func ToJSON(a Args) (string, error) {
|
||||
return string(buf), err
|
||||
}
|
||||
|
||||
// FromJSON decodes a JSON encoded string into Args
|
||||
func FromJSON(p string) (Args, error) {
|
||||
args := NewArgs()
|
||||
|
||||
if p == "" {
|
||||
return args, nil
|
||||
}
|
||||
|
||||
raw := []byte(p)
|
||||
err := json.Unmarshal(raw, &args)
|
||||
if err == nil {
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// Fallback to parsing arguments in the legacy slice format
|
||||
deprecated := map[string][]string{}
|
||||
if legacyErr := json.Unmarshal(raw, &deprecated); legacyErr != nil {
|
||||
return args, &invalidFilter{}
|
||||
}
|
||||
|
||||
args.fields = deprecatedArgs(deprecated)
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON populates the Args from JSON encode bytes
|
||||
func (args Args) UnmarshalJSON(raw []byte) error {
|
||||
return json.Unmarshal(raw, &args.fields)
|
||||
}
|
||||
|
||||
// Get returns the list of values associated with the key
|
||||
func (args Args) Get(key string) []string {
|
||||
values := args.fields[key]
|
||||
if values == nil {
|
||||
return make([]string, 0)
|
||||
}
|
||||
slice := make([]string, 0, len(values))
|
||||
for key := range values {
|
||||
slice = append(slice, key)
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// Add a new value to the set of values
|
||||
func (args Args) Add(key, value string) {
|
||||
if _, ok := args.fields[key]; ok {
|
||||
@@ -112,191 +59,7 @@ func (args Args) Add(key, value string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Del removes a value from the set
|
||||
func (args Args) Del(key, value string) {
|
||||
if _, ok := args.fields[key]; ok {
|
||||
delete(args.fields[key], value)
|
||||
if len(args.fields[key]) == 0 {
|
||||
delete(args.fields, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the number of keys in the mapping
|
||||
func (args Args) Len() int {
|
||||
return len(args.fields)
|
||||
}
|
||||
|
||||
// MatchKVList returns true if all the pairs in sources exist as key=value
|
||||
// pairs in the mapping at key, or if there are no values at key.
|
||||
func (args Args) MatchKVList(key string, sources map[string]string) bool {
|
||||
fieldValues := args.fields[key]
|
||||
|
||||
// do not filter if there is no filter set or cannot determine filter
|
||||
if len(fieldValues) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(sources) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for value := range fieldValues {
|
||||
testK, testV, hasValue := strings.Cut(value, "=")
|
||||
|
||||
v, ok := sources[testK]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if hasValue && testV != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Match returns true if any of the values at key match the source string
|
||||
func (args Args) Match(field, source string) bool {
|
||||
if args.ExactMatch(field, source) {
|
||||
return true
|
||||
}
|
||||
|
||||
fieldValues := args.fields[field]
|
||||
for name2match := range fieldValues {
|
||||
match, err := regexp.MatchString(name2match, source)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if match {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetBoolOrDefault returns a boolean value of the key if the key is present
|
||||
// and is interpretable as a boolean value. Otherwise the default value is returned.
|
||||
// Error is not nil only if the filter values are not valid boolean or are conflicting.
|
||||
func (args Args) GetBoolOrDefault(key string, defaultValue bool) (bool, error) {
|
||||
fieldValues, ok := args.fields[key]
|
||||
if !ok {
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
if len(fieldValues) == 0 {
|
||||
return defaultValue, &invalidFilter{key, nil}
|
||||
}
|
||||
|
||||
isFalse := fieldValues["0"] || fieldValues["false"]
|
||||
isTrue := fieldValues["1"] || fieldValues["true"]
|
||||
if isFalse == isTrue {
|
||||
// Either no or conflicting truthy/falsy value were provided
|
||||
return defaultValue, &invalidFilter{key, args.Get(key)}
|
||||
}
|
||||
return isTrue, nil
|
||||
}
|
||||
|
||||
// ExactMatch returns true if the source matches exactly one of the values.
|
||||
func (args Args) ExactMatch(key, source string) bool {
|
||||
fieldValues, ok := args.fields[key]
|
||||
// do not filter if there is no filter set or cannot determine filter
|
||||
if !ok || len(fieldValues) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// try to match full name value to avoid O(N) regular expression matching
|
||||
return fieldValues[source]
|
||||
}
|
||||
|
||||
// UniqueExactMatch returns true if there is only one value and the source
|
||||
// matches exactly the value.
|
||||
func (args Args) UniqueExactMatch(key, source string) bool {
|
||||
fieldValues := args.fields[key]
|
||||
// do not filter if there is no filter set or cannot determine filter
|
||||
if len(fieldValues) == 0 {
|
||||
return true
|
||||
}
|
||||
if len(args.fields[key]) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
// try to match full name value to avoid O(N) regular expression matching
|
||||
return fieldValues[source]
|
||||
}
|
||||
|
||||
// FuzzyMatch returns true if the source matches exactly one value, or the
|
||||
// source has one of the values as a prefix.
|
||||
func (args Args) FuzzyMatch(key, source string) bool {
|
||||
if args.ExactMatch(key, source) {
|
||||
return true
|
||||
}
|
||||
|
||||
fieldValues := args.fields[key]
|
||||
for prefix := range fieldValues {
|
||||
if strings.HasPrefix(source, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Contains returns true if the key exists in the mapping
|
||||
func (args Args) Contains(field string) bool {
|
||||
_, ok := args.fields[field]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Validate compared the set of accepted keys against the keys in the mapping.
|
||||
// An error is returned if any mapping keys are not in the accepted set.
|
||||
func (args Args) Validate(accepted map[string]bool) error {
|
||||
for name := range args.fields {
|
||||
if !accepted[name] {
|
||||
return &invalidFilter{name, nil}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WalkValues iterates over the list of values for a key in the mapping and calls
|
||||
// op() for each value. If op returns an error the iteration stops and the
|
||||
// error is returned.
|
||||
func (args Args) WalkValues(field string, op func(value string) error) error {
|
||||
if _, ok := args.fields[field]; !ok {
|
||||
return nil
|
||||
}
|
||||
for v := range args.fields[field] {
|
||||
if err := op(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone returns a copy of args.
|
||||
func (args Args) Clone() (newArgs Args) {
|
||||
newArgs.fields = make(map[string]map[string]bool, len(args.fields))
|
||||
for k, m := range args.fields {
|
||||
var mm map[string]bool
|
||||
if m != nil {
|
||||
mm = make(map[string]bool, len(m))
|
||||
for kk, v := range m {
|
||||
mm[kk] = v
|
||||
}
|
||||
}
|
||||
newArgs.fields[k] = mm
|
||||
}
|
||||
return newArgs
|
||||
}
|
||||
|
||||
func deprecatedArgs(d map[string][]string) map[string]map[string]bool {
|
||||
m := map[string]map[string]bool{}
|
||||
for k, v := range d {
|
||||
values := map[string]bool{}
|
||||
for _, vv := range v {
|
||||
values[vv] = true
|
||||
}
|
||||
m[k] = values
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user