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:
Cory Snider
2025-10-06 13:12:23 -04:00
parent c09d53a1dd
commit 778e5bfad3
58 changed files with 878 additions and 964 deletions

View File

@@ -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
}

View File

@@ -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))
})
}
}

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"
)

View File

@@ -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) {

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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"
)

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"
)

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View 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() {}

View 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
}

View 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))
})
}
}

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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{}

View File

@@ -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"

View File

@@ -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"
)

View File

@@ -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"
)

View File

@@ -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"
)

View File

@@ -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"

View File

@@ -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"
)

View File

@@ -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 {

View File

@@ -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"
)

View File

@@ -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"

View File

@@ -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"
)

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"
)

View File

@@ -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"

View File

@@ -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"
)

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"
)

View File

@@ -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"

View File

@@ -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"
)

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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"
)

View File

@@ -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"
)

View File

@@ -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"
)

View File

@@ -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"

View File

@@ -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"

View File

@@ -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
}