Browse code

daemon: prune networks using network.Filter

Construct a network.Filter from the filters.Args only once per API
request so we don't waste cycles re-validating an already validated
filter. Since (*Daemon).NetworksPrune is implemented in terms of
(Cluster).GetNetworks, that method now accepts a network.Filter instead
of a filter.Args. Change the signature of (*Daemon).GetNetworks for
consistency as both of the GetNetworks methods are used by network API
route handlers.

Signed-off-by: Cory Snider <csnider@mirantis.com>

Cory Snider authored on 2025/08/30 08:28:35
Showing 9 changed files
... ...
@@ -1,9 +1,9 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
-	"github.com/moby/moby/api/types/filters"
5 4
 	"github.com/moby/moby/api/types/network"
6 5
 	lncluster "github.com/moby/moby/v2/daemon/libnetwork/cluster"
6
+	dnetwork "github.com/moby/moby/v2/daemon/network"
7 7
 )
8 8
 
9 9
 // Cluster is the interface for [github.com/moby/moby/v2/daemon/cluster.Cluster].
... ...
@@ -22,6 +22,6 @@ type ClusterStatus interface {
22 22
 // NetworkManager provides methods to manage networks
23 23
 type NetworkManager interface {
24 24
 	GetNetwork(input string) (network.Inspect, error)
25
-	GetNetworks(filters.Args) ([]network.Inspect, error)
25
+	GetNetworks(dnetwork.Filter) ([]network.Inspect, error)
26 26
 	RemoveNetwork(input string) error
27 27
 }
... ...
@@ -2,6 +2,7 @@ package convert
2 2
 
3 3
 import (
4 4
 	"strings"
5
+	"time"
5 6
 
6 7
 	gogotypes "github.com/gogo/protobuf/types"
7 8
 	"github.com/moby/moby/api/types/network"
... ...
@@ -270,6 +271,11 @@ func (nw FilterNetwork) Scope() string {
270 270
 	return scope.Swarm
271 271
 }
272 272
 
273
+func (nw FilterNetwork) Created() time.Time {
274
+	t, _ := gogotypes.TimestampFromProto(nw.N.Meta.CreatedAt)
275
+	return t
276
+}
277
+
273 278
 func (nw FilterNetwork) HasContainerAttachments() bool {
274 279
 	// Not tracked in swarmkit
275 280
 	return false
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"fmt"
6 6
 
7 7
 	"github.com/containerd/log"
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/network"
10 9
 	types "github.com/moby/moby/api/types/swarm"
11 10
 	"github.com/moby/moby/v2/daemon/cluster/convert"
... ...
@@ -16,25 +15,11 @@ import (
16 16
 )
17 17
 
18 18
 // GetNetworks returns all current cluster managed networks.
19
-func (c *Cluster) GetNetworks(filter filters.Args) ([]network.Inspect, error) {
20
-	flt, err := networkSettings.NewFilter(filter)
21
-	if err != nil {
22
-		return nil, err
23
-	}
24
-
25
-	var f *swarmapi.ListNetworksRequest_Filters
26
-
27
-	if filter.Len() > 0 {
28
-		f = &swarmapi.ListNetworksRequest_Filters{}
29
-
30
-		if filter.Contains("name") {
31
-			f.Names = filter.Get("name")
32
-			f.NamePrefixes = filter.Get("name")
33
-		}
34
-
35
-		if filter.Contains("id") {
36
-			f.IDPrefixes = filter.Get("id")
37
-		}
19
+func (c *Cluster) GetNetworks(filter networkSettings.Filter) ([]network.Inspect, error) {
20
+	f := &swarmapi.ListNetworksRequest_Filters{
21
+		Names:        filter.Get("name"),
22
+		NamePrefixes: filter.Get("name"),
23
+		IDPrefixes:   filter.Get("id"),
38 24
 	}
39 25
 
40 26
 	list, err := c.listNetworks(context.TODO(), f)
... ...
@@ -46,7 +31,7 @@ func (c *Cluster) GetNetworks(filter filters.Args) ([]network.Inspect, error) {
46 46
 		if n.Spec.Annotations.Labels["com.docker.swarm.predefined"] == "true" {
47 47
 			continue
48 48
 		}
49
-		if flt.Matches(convert.FilterNetwork{N: n}) {
49
+		if filter.Matches(convert.FilterNetwork{N: n}) {
50 50
 			filtered = append(filtered, convert.BasicNetworkFromGRPC(*n))
51 51
 		}
52 52
 	}
... ...
@@ -17,7 +17,6 @@ import (
17 17
 	"github.com/docker/go-connections/nat"
18 18
 	containertypes "github.com/moby/moby/api/types/container"
19 19
 	"github.com/moby/moby/api/types/events"
20
-	"github.com/moby/moby/api/types/filters"
21 20
 	networktypes "github.com/moby/moby/api/types/network"
22 21
 	clustertypes "github.com/moby/moby/v2/daemon/cluster/provider"
23 22
 	"github.com/moby/moby/v2/daemon/config"
... ...
@@ -591,16 +590,11 @@ func (daemon *Daemon) deleteNetwork(nw *libnetwork.Network, dynamic bool) error
591 591
 }
592 592
 
593 593
 // GetNetworks returns a list of all networks
594
-func (daemon *Daemon) GetNetworks(filter filters.Args, config backend.NetworkListConfig) ([]networktypes.Inspect, error) {
595
-	flt, err := network.NewFilter(filter)
596
-	if err != nil {
597
-		return nil, err
598
-	}
599
-
594
+func (daemon *Daemon) GetNetworks(filter network.Filter, config backend.NetworkListConfig) ([]networktypes.Inspect, error) {
600 595
 	allNetworks := daemon.getAllNetworks()
601 596
 	networks := make([]networktypes.Inspect, 0, len(allNetworks))
602 597
 	for _, n := range allNetworks {
603
-		if flt.Matches(n) {
598
+		if filter.Matches(n) {
604 599
 			nr := buildNetworkResource(n)
605 600
 			if config.Detailed {
606 601
 				nr.Containers = buildContainerAttachments(n)
... ...
@@ -1,7 +1,10 @@
1 1
 package network
2 2
 
3 3
 import (
4
+	"time"
5
+
4 6
 	"github.com/moby/moby/api/types/filters"
7
+	"github.com/moby/moby/v2/daemon/internal/timestamp"
5 8
 	"github.com/moby/moby/v2/errdefs"
6 9
 	"github.com/pkg/errors"
7 10
 )
... ...
@@ -10,6 +13,7 @@ type Filter struct {
10 10
 	args filters.Args
11 11
 
12 12
 	filterByUse, danglingOnly bool
13
+	until                     time.Time
13 14
 }
14 15
 
15 16
 type FilterNetwork interface {
... ...
@@ -18,6 +22,7 @@ type FilterNetwork interface {
18 18
 	ID() string
19 19
 	Labels() map[string]string
20 20
 	Scope() string
21
+	Created() time.Time
21 22
 	HasContainerAttachments() bool
22 23
 	HasServiceAttachments() bool
23 24
 }
... ...
@@ -46,7 +51,31 @@ func NewFilter(args filters.Args) (Filter, error) {
46 46
 	if err := args.WalkValues("type", validateNetworkTypeFilter); err != nil {
47 47
 		return Filter{}, err
48 48
 	}
49
-	return Filter{args: args, filterByUse: filterByUse, danglingOnly: danglingOnly}, nil
49
+	until := time.Time{}
50
+	if untilFilters := args.Get("until"); len(untilFilters) > 0 {
51
+		if len(untilFilters) > 1 {
52
+			return Filter{}, errdefs.InvalidParameter(errors.New("more than one until filter specified"))
53
+		}
54
+		ts, err := timestamp.GetTimestamp(untilFilters[0], time.Now())
55
+		if err != nil {
56
+			return Filter{}, errdefs.InvalidParameter(err)
57
+		}
58
+		seconds, nanoseconds, err := timestamp.ParseTimestamps(ts, 0)
59
+		if err != nil {
60
+			return Filter{}, errdefs.InvalidParameter(err)
61
+		}
62
+		until = time.Unix(seconds, nanoseconds)
63
+	}
64
+	return Filter{
65
+		args:         args,
66
+		filterByUse:  filterByUse,
67
+		danglingOnly: danglingOnly,
68
+		until:        until,
69
+	}, nil
70
+}
71
+
72
+func (f Filter) Get(key string) []string {
73
+	return f.args.Get(key)
50 74
 }
51 75
 
52 76
 // Matches returns true if nw satisfies the filter criteria.
... ...
@@ -67,6 +96,10 @@ func (f Filter) Matches(nw FilterNetwork) bool {
67 67
 		!f.args.MatchKVList("label", nw.Labels()) {
68 68
 		return false
69 69
 	}
70
+	if f.args.Contains("label!") &&
71
+		f.args.MatchKVList("label!", nw.Labels()) {
72
+		return false
73
+	}
70 74
 	if f.args.Contains("scope") &&
71 75
 		!f.args.ExactMatch("scope", nw.Scope()) {
72 76
 		return false
... ...
@@ -84,6 +117,10 @@ func (f Filter) Matches(nw FilterNetwork) bool {
84 84
 		!matchesType(netTypes, nw) {
85 85
 		return false
86 86
 	}
87
+	if !f.until.IsZero() &&
88
+		nw.Created().After(f.until) {
89
+		return false
90
+	}
87 91
 	return true
88 92
 }
89 93
 
... ...
@@ -4,6 +4,7 @@ package network
4 4
 
5 5
 import (
6 6
 	"testing"
7
+	"time"
7 8
 
8 9
 	"gotest.tools/v3/assert"
9 10
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -20,6 +21,7 @@ type mockFilterNetwork struct {
20 20
 	id         string
21 21
 	labels     map[string]string
22 22
 	scope      string
23
+	created    time.Time
23 24
 	containers bool
24 25
 	services   bool
25 26
 }
... ...
@@ -44,6 +46,10 @@ func (n mockFilterNetwork) Scope() string {
44 44
 	return n.scope
45 45
 }
46 46
 
47
+func (n mockFilterNetwork) Created() time.Time {
48
+	return n.created
49
+}
50
+
47 51
 func (n mockFilterNetwork) HasContainerAttachments() bool {
48 52
 	return n.containers
49 53
 }
... ...
@@ -55,41 +61,47 @@ func (n mockFilterNetwork) HasServiceAttachments() bool {
55 55
 func TestFilter(t *testing.T) {
56 56
 	networks := []mockFilterNetwork{
57 57
 		{
58
-			name:   network.NetworkHost,
59
-			id:     "ubfg", // ROT13(name)
60
-			driver: "host",
61
-			scope:  "local",
58
+			name:    network.NetworkHost,
59
+			id:      "ubfg", // ROT13(name)
60
+			driver:  "host",
61
+			scope:   "local",
62
+			created: time.Date(2025, time.June, 1, 0, 0, 0, 0, time.Local),
62 63
 		},
63 64
 		{
64
-			name:   network.NetworkBridge,
65
-			id:     "oevqtr",
66
-			driver: "bridge",
67
-			scope:  "local",
65
+			name:    network.NetworkBridge,
66
+			id:      "oevqtr",
67
+			driver:  "bridge",
68
+			scope:   "local",
69
+			created: time.Date(2025, time.June, 1, 0, 0, 0, 0, time.Local),
68 70
 		},
69 71
 		{
70
-			name:   network.NetworkNone,
71
-			id:     "abar",
72
-			driver: "null",
73
-			scope:  "local",
72
+			name:    network.NetworkNone,
73
+			id:      "abar",
74
+			driver:  "null",
75
+			scope:   "local",
76
+			created: time.Date(2025, time.June, 1, 0, 0, 0, 0, time.Local),
74 77
 		},
75 78
 		{
76
-			name:   "myoverlay",
77
-			id:     "zlbireynl",
78
-			driver: "overlay",
79
-			scope:  "swarm",
79
+			name:    "myoverlay",
80
+			id:      "zlbireynl",
81
+			driver:  "overlay",
82
+			scope:   "swarm",
83
+			created: time.Date(2024, time.June, 1, 12, 0, 0, 0, time.Local),
80 84
 		},
81 85
 		{
82
-			name:   "mydrivernet",
83
-			id:     "zlqevirearg",
84
-			driver: "mydriver",
85
-			scope:  "local",
86
-			labels: map[string]string{"foo": "bar"},
86
+			name:    "mydrivernet",
87
+			id:      "zlqevirearg",
88
+			driver:  "mydriver",
89
+			scope:   "local",
90
+			labels:  map[string]string{"foo": "bar"},
91
+			created: time.Date(2024, time.December, 1, 2, 0, 0, 0, time.Local),
87 92
 		},
88 93
 		{
89
-			name:   "mykvnet",
90
-			id:     "zlxiarg",
91
-			driver: "mykvdriver",
92
-			scope:  "global",
94
+			name:    "mykvnet",
95
+			id:      "zlxiarg",
96
+			driver:  "mykvdriver",
97
+			scope:   "global",
98
+			created: time.Date(2025, time.January, 1, 0, 0, 0, 0, time.Local),
93 99
 		},
94 100
 		{
95 101
 			name:       "networkwithcontainer",
... ...
@@ -97,6 +109,7 @@ func TestFilter(t *testing.T) {
97 97
 			driver:     "nwc",
98 98
 			scope:      "local",
99 99
 			containers: true,
100
+			created:    time.Date(2025, time.June, 1, 0, 0, 0, 0, time.Local),
100 101
 		},
101 102
 		{
102 103
 			name:     "networkwithservice",
... ...
@@ -104,6 +117,14 @@ func TestFilter(t *testing.T) {
104 104
 			driver:   "nwc",
105 105
 			scope:    "local",
106 106
 			services: true,
107
+			created:  time.Date(2025, time.June, 1, 0, 0, 0, 0, time.Local),
108
+		},
109
+		{
110
+			name:    "idoverlap",
111
+			id:      "aaaaa0my0bbbbbb",
112
+			driver:  "nwc",
113
+			scope:   "local",
114
+			created: time.Date(2025, time.February, 1, 0, 0, 0, 0, time.Local),
107 115
 		},
108 116
 	}
109 117
 
... ...
@@ -142,7 +163,7 @@ func TestFilter(t *testing.T) {
142 142
 		{
143 143
 			subtest: "type=custom",
144 144
 			filter:  filters.NewArgs(filters.Arg("type", "custom")),
145
-			results: []string{"myoverlay", "mydrivernet", "mykvnet", "networkwithcontainer", "networkwithservice"},
145
+			results: []string{"myoverlay", "mydrivernet", "mykvnet", "networkwithcontainer", "networkwithservice", "idoverlap"},
146 146
 		},
147 147
 		{
148 148
 			subtest: "type=builtin",
... ...
@@ -157,7 +178,7 @@ func TestFilter(t *testing.T) {
157 157
 		{
158 158
 			subtest: "scope=local",
159 159
 			filter:  filters.NewArgs(filters.Arg("scope", "local")),
160
-			results: []string{network.NetworkHost, network.NetworkBridge, network.NetworkNone, "mydrivernet", "networkwithcontainer", "networkwithservice"},
160
+			results: []string{network.NetworkHost, network.NetworkBridge, network.NetworkNone, "mydrivernet", "networkwithcontainer", "networkwithservice", "idoverlap"},
161 161
 		},
162 162
 		{
163 163
 			subtest: "scope=swarm",
... ...
@@ -172,12 +193,12 @@ func TestFilter(t *testing.T) {
172 172
 		{
173 173
 			subtest: "dangling=true",
174 174
 			filter:  filters.NewArgs(filters.Arg("dangling", "true")),
175
-			results: []string{"myoverlay", "mydrivernet", "mykvnet"},
175
+			results: []string{"myoverlay", "mydrivernet", "mykvnet", "idoverlap"},
176 176
 		},
177 177
 		{
178 178
 			subtest: "dangling=1",
179 179
 			filter:  filters.NewArgs(filters.Arg("dangling", "1")),
180
-			results: []string{"myoverlay", "mydrivernet", "mykvnet"},
180
+			results: []string{"myoverlay", "mydrivernet", "mykvnet", "idoverlap"},
181 181
 		},
182 182
 		{
183 183
 			subtest: "dangling=false",
... ...
@@ -227,6 +248,31 @@ func TestFilter(t *testing.T) {
227 227
 			filter:  filters.NewArgs(filters.Arg("id", "argjbex")),
228 228
 			results: []string{"networkwithcontainer", "networkwithservice"},
229 229
 		},
230
+		{
231
+			subtest: "id=my",
232
+			filter:  filters.NewArgs(filters.Arg("id", "my")),
233
+			results: []string{"idoverlap"},
234
+		},
235
+		{
236
+			subtest: "label!=foo",
237
+			filter:  filters.NewArgs(filters.Arg("label!", "foo")),
238
+			results: []string{network.NetworkHost, network.NetworkBridge, network.NetworkNone, "myoverlay", "mykvnet", "networkwithcontainer", "networkwithservice", "idoverlap"},
239
+		},
240
+		{
241
+			subtest: "until=2025-01-01",
242
+			filter:  filters.NewArgs(filters.Arg("until", "2025-01-01")),
243
+			results: []string{"myoverlay", "mydrivernet", "mykvnet"},
244
+		},
245
+		{
246
+			subtest: "until=2024-12-01T01:00:00",
247
+			filter:  filters.NewArgs(filters.Arg("until", "2024-12-01T01:00:00")),
248
+			results: []string{"myoverlay"},
249
+		},
250
+		{
251
+			subtest: "MultipleTerms=until",
252
+			filter:  filters.NewArgs(filters.Arg("until", "2024-12-01T01:00:00"), filters.Arg("until", "2h")),
253
+			err:     "more than one until filter specified",
254
+		},
230 255
 	}
231 256
 
232 257
 	for _, testCase := range testCases {
... ...
@@ -13,6 +13,7 @@ import (
13 13
 	"github.com/moby/moby/v2/daemon/internal/lazyregexp"
14 14
 	"github.com/moby/moby/v2/daemon/internal/timestamp"
15 15
 	"github.com/moby/moby/v2/daemon/libnetwork"
16
+	dnetwork "github.com/moby/moby/v2/daemon/network"
16 17
 	"github.com/moby/moby/v2/daemon/server/backend"
17 18
 	"github.com/moby/moby/v2/errdefs"
18 19
 	"github.com/pkg/errors"
... ...
@@ -96,11 +97,9 @@ func (daemon *Daemon) ContainersPrune(ctx context.Context, pruneFilters filters.
96 96
 }
97 97
 
98 98
 // localNetworksPrune removes unused local networks
99
-func (daemon *Daemon) localNetworksPrune(ctx context.Context, pruneFilters filters.Args) *network.PruneReport {
99
+func (daemon *Daemon) localNetworksPrune(ctx context.Context, pruneFilters dnetwork.Filter) *network.PruneReport {
100 100
 	rep := &network.PruneReport{}
101 101
 
102
-	until, _ := getUntilFromPruneFilters(pruneFilters)
103
-
104 102
 	// When the function returns true, the walk will stop.
105 103
 	daemon.netController.WalkNetworks(func(nw *libnetwork.Network) bool {
106 104
 		select {
... ...
@@ -112,10 +111,7 @@ func (daemon *Daemon) localNetworksPrune(ctx context.Context, pruneFilters filte
112 112
 		if nw.ConfigOnly() {
113 113
 			return false
114 114
 		}
115
-		if !until.IsZero() && nw.Created().After(until) {
116
-			return false
117
-		}
118
-		if !matchLabels(pruneFilters, nw.Labels()) {
115
+		if !pruneFilters.Matches(nw) {
119 116
 			return false
120 117
 		}
121 118
 		if !nw.IsPruneable() {
... ...
@@ -137,11 +133,9 @@ func (daemon *Daemon) localNetworksPrune(ctx context.Context, pruneFilters filte
137 137
 var networkIsInUse = lazyregexp.New(`network ([[:alnum:]]+) is in use`)
138 138
 
139 139
 // clusterNetworksPrune removes unused cluster networks
140
-func (daemon *Daemon) clusterNetworksPrune(ctx context.Context, pruneFilters filters.Args) (*network.PruneReport, error) {
140
+func (daemon *Daemon) clusterNetworksPrune(ctx context.Context, pruneFilters dnetwork.Filter) (*network.PruneReport, error) {
141 141
 	rep := &network.PruneReport{}
142 142
 
143
-	until, _ := getUntilFromPruneFilters(pruneFilters)
144
-
145 143
 	cluster := daemon.GetCluster()
146 144
 
147 145
 	if !cluster.IsManager() {
... ...
@@ -162,12 +156,6 @@ func (daemon *Daemon) clusterNetworksPrune(ctx context.Context, pruneFilters fil
162 162
 				// Routing-mesh network removal has to be explicitly invoked by user
163 163
 				continue
164 164
 			}
165
-			if !until.IsZero() && nw.Created.After(until) {
166
-				continue
167
-			}
168
-			if !matchLabels(pruneFilters, nw.Labels) {
169
-				continue
170
-			}
171 165
 			// https://github.com/moby/moby/issues/24186
172 166
 			// `docker network inspect` unfortunately displays ONLY those containers that are local to that node.
173 167
 			// So we try to remove it anyway and check the error
... ...
@@ -187,19 +175,20 @@ func (daemon *Daemon) clusterNetworksPrune(ctx context.Context, pruneFilters fil
187 187
 }
188 188
 
189 189
 // NetworksPrune removes unused networks
190
-func (daemon *Daemon) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (*network.PruneReport, error) {
190
+func (daemon *Daemon) NetworksPrune(ctx context.Context, filterArgs filters.Args) (*network.PruneReport, error) {
191 191
 	if !daemon.pruneRunning.CompareAndSwap(false, true) {
192 192
 		return nil, errPruneRunning
193 193
 	}
194 194
 	defer daemon.pruneRunning.Store(false)
195 195
 
196 196
 	// make sure that only accepted filters have been received
197
-	err := pruneFilters.Validate(networksAcceptedFilters)
197
+	err := filterArgs.Validate(networksAcceptedFilters)
198 198
 	if err != nil {
199 199
 		return nil, err
200 200
 	}
201 201
 
202
-	if _, err := getUntilFromPruneFilters(pruneFilters); err != nil {
202
+	pruneFilters, err := dnetwork.NewFilter(filterArgs)
203
+	if err != nil {
203 204
 		return nil, err
204 205
 	}
205 206
 
... ...
@@ -5,13 +5,14 @@ import (
5 5
 
6 6
 	"github.com/moby/moby/api/types/filters"
7 7
 	"github.com/moby/moby/api/types/network"
8
+	dnetwork "github.com/moby/moby/v2/daemon/network"
8 9
 	"github.com/moby/moby/v2/daemon/server/backend"
9 10
 )
10 11
 
11 12
 // Backend is all the methods that need to be implemented
12 13
 // to provide network specific functionality.
13 14
 type Backend interface {
14
-	GetNetworks(filters.Args, backend.NetworkListConfig) ([]network.Inspect, error)
15
+	GetNetworks(dnetwork.Filter, backend.NetworkListConfig) ([]network.Inspect, error)
15 16
 	CreateNetwork(ctx context.Context, nc network.CreateRequest) (*network.CreateResponse, error)
16 17
 	ConnectContainerToNetwork(ctx context.Context, containerName, networkName string, endpointConfig *network.EndpointSettings) error
17 18
 	DisconnectContainerFromNetwork(containerName string, networkName string, force bool) error
... ...
@@ -22,7 +23,7 @@ type Backend interface {
22 22
 // ClusterBackend is all the methods that need to be implemented
23 23
 // to provide cluster network specific functionality.
24 24
 type ClusterBackend interface {
25
-	GetNetworks(filters.Args) ([]network.Inspect, error)
25
+	GetNetworks(dnetwork.Filter) ([]network.Inspect, error)
26 26
 	GetNetwork(name string) (network.Inspect, error)
27 27
 	GetNetworksByName(name string) ([]network.Inspect, error)
28 28
 	CreateNetwork(nc network.CreateRequest) (string, error)
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	"github.com/moby/moby/api/types/versions"
12 12
 	"github.com/moby/moby/v2/daemon/libnetwork"
13 13
 	"github.com/moby/moby/v2/daemon/libnetwork/scope"
14
+	dnetwork "github.com/moby/moby/v2/daemon/network"
14 15
 	"github.com/moby/moby/v2/daemon/server/backend"
15 16
 	"github.com/moby/moby/v2/daemon/server/httputils"
16 17
 	"github.com/moby/moby/v2/daemon/server/networkbackend"
... ...
@@ -23,12 +24,17 @@ func (n *networkRouter) getNetworksList(ctx context.Context, w http.ResponseWrit
23 23
 		return err
24 24
 	}
25 25
 
26
-	filter, err := filters.FromJSON(r.Form.Get("filters"))
26
+	filterArgs, err := filters.FromJSON(r.Form.Get("filters"))
27 27
 	if err != nil {
28 28
 		return err
29 29
 	}
30 30
 
31
-	if err := network.ValidateFilters(filter); err != nil {
31
+	if err := network.ValidateFilters(filterArgs); err != nil {
32
+		return err
33
+	}
34
+
35
+	filter, err := dnetwork.NewFilter(filterArgs)
36
+	if err != nil {
32 37
 		return err
33 38
 	}
34 39
 
... ...
@@ -114,10 +120,15 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
114 114
 
115 115
 	// TODO(@cpuguy83): All this logic for figuring out which network to return does not belong here
116 116
 	// Instead there should be a backend function to just get one network.
117
-	filter := filters.NewArgs(filters.Arg("idOrName", term))
117
+	filterArgs := filters.NewArgs(filters.Arg("idOrName", term))
118 118
 	if networkScope != "" {
119
-		filter.Add("scope", networkScope)
119
+		filterArgs.Add("scope", networkScope)
120
+	}
121
+	filter, err := dnetwork.NewFilter(filterArgs)
122
+	if err != nil {
123
+		return err
120 124
 	}
125
+
121 126
 	networks, _ := n.backend.GetNetworks(filter, backend.NetworkListConfig{Detailed: true, Verbose: verbose})
122 127
 	for _, nw := range networks {
123 128
 		if nw.ID == term {
... ...
@@ -322,7 +333,10 @@ func (n *networkRouter) findUniqueNetwork(term string) (network.Inspect, error)
322 322
 	listByFullName := map[string]network.Inspect{}
323 323
 	listByPartialID := map[string]network.Inspect{}
324 324
 
325
-	filter := filters.NewArgs(filters.Arg("idOrName", term))
325
+	filter, err := dnetwork.NewFilter(filters.NewArgs(filters.Arg("idOrName", term)))
326
+	if err != nil {
327
+		return network.Inspect{}, err
328
+	}
326 329
 	networks, _ := n.backend.GetNetworks(filter, backend.NetworkListConfig{Detailed: true})
327 330
 	for _, nw := range networks {
328 331
 		if nw.ID == term {