Browse code

client: remove support for API < v1.22 filter format

The format for filters changed in 93d1dd8036d57f5cf1e5cbbbad875ae9a6fa6180
(docker v1.10 / API v1.22). As part of that implementation, the daemon
would parse the new format, and fall back to parsing the old format if
this failed. This fallback was not based on API version, so any version
of the API released since would continue to accept both the legacy and
curent format.

For the client, the change in format caused a regression when connecting
to an older daemon; a `ToParamWithVersion` utility was introduced in
[docker/engine-api@81388f0] to produce the old format when the client was
connected to a docker v1.9 or older daemon, using an old API version.

Given that any version of docker 1.10 or above would support both formats,
regardless of the API version used, and API v1.22 is no longer supported,
it should be safe to assume we can drop the version-specific format in the
client. Even if the client would be using API v1.22 (or older), the format
would only be necessary for an actual docker v1.9 daemon, which would be
very unlikely, and a daemon that's 9 Years old.

[docker/engine-api@81388f0]: https://github.com/docker-archive-public/docker.engine-api//commit/81388f00dde756d73eaf3bc653be4bf567547a98

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2025/09/16 04:27:58
Showing 15 changed files
... ...
@@ -8,7 +8,6 @@ import (
8 8
 
9 9
 	"github.com/moby/moby/api/types/container"
10 10
 	"github.com/moby/moby/api/types/filters"
11
-	"github.com/moby/moby/api/types/versions"
12 11
 )
13 12
 
14 13
 // ContainerListOptions holds parameters to list containers with.
... ...
@@ -47,27 +46,10 @@ func (cli *Client) ContainerList(ctx context.Context, options ContainerListOptio
47 47
 	}
48 48
 
49 49
 	if options.Filters.Len() > 0 {
50
-		// Make sure we negotiated (if the client is configured to do so),
51
-		// as code below contains API-version specific handling of options.
52
-		//
53
-		// Normally, version-negotiation (if enabled) would not happen until
54
-		// the API request is made.
55
-		if err := cli.checkVersion(ctx); err != nil {
56
-			return nil, err
57
-		}
58
-
59 50
 		filterJSON, err := filters.ToJSON(options.Filters)
60 51
 		if err != nil {
61 52
 			return nil, err
62 53
 		}
63
-		if cli.version != "" && versions.LessThan(cli.version, "1.22") {
64
-			legacyFormat, err := encodeLegacyFilters(filterJSON)
65
-			if err != nil {
66
-				return nil, err
67
-			}
68
-			filterJSON = legacyFormat
69
-		}
70
-
71 54
 		query.Set("filters", filterJSON)
72 55
 	}
73 56
 
... ...
@@ -43,13 +43,6 @@ func (cli *Client) ImageList(ctx context.Context, options ImageListOptions) ([]i
43 43
 		if err != nil {
44 44
 			return images, err
45 45
 		}
46
-		if cli.version != "" && versions.LessThan(cli.version, "1.22") {
47
-			legacyFormat, err := encodeLegacyFilters(filterJSON)
48
-			if err != nil {
49
-				return nil, err
50
-			}
51
-			filterJSON = legacyFormat
52
-		}
53 46
 		query.Set("filters", filterJSON)
54 47
 	}
55 48
 	if options.All {
... ...
@@ -7,7 +7,6 @@ import (
7 7
 
8 8
 	"github.com/moby/moby/api/types/filters"
9 9
 	"github.com/moby/moby/api/types/network"
10
-	"github.com/moby/moby/api/types/versions"
11 10
 )
12 11
 
13 12
 // NetworkList returns the list of networks configured in the docker host.
... ...
@@ -18,14 +17,6 @@ func (cli *Client) NetworkList(ctx context.Context, options NetworkListOptions)
18 18
 		if err != nil {
19 19
 			return nil, err
20 20
 		}
21
-		if cli.version != "" && versions.LessThan(cli.version, "1.22") {
22
-			legacyFormat, err := encodeLegacyFilters(filterJSON)
23
-			if err != nil {
24
-				return nil, err
25
-			}
26
-			filterJSON = legacyFormat
27
-		}
28
-
29 21
 		query.Set("filters", filterJSON)
30 22
 	}
31 23
 	var networkResources []network.Summary
... ...
@@ -7,7 +7,6 @@ import (
7 7
 
8 8
 	"github.com/moby/moby/api/types/filters"
9 9
 	"github.com/moby/moby/api/types/plugin"
10
-	"github.com/moby/moby/api/types/versions"
11 10
 )
12 11
 
13 12
 // PluginList returns the installed plugins
... ...
@@ -20,13 +19,6 @@ func (cli *Client) PluginList(ctx context.Context, filter filters.Args) (plugin.
20 20
 		if err != nil {
21 21
 			return plugins, err
22 22
 		}
23
-		if cli.version != "" && versions.LessThan(cli.version, "1.22") {
24
-			legacyFormat, err := encodeLegacyFilters(filterJSON)
25
-			if err != nil {
26
-				return plugins, err
27
-			}
28
-			filterJSON = legacyFormat
29
-		}
30 23
 		query.Set("filters", filterJSON)
31 24
 	}
32 25
 	resp, err := cli.get(ctx, "/plugins", query, nil)
... ...
@@ -8,7 +8,6 @@ import (
8 8
 
9 9
 	"github.com/moby/moby/api/types/events"
10 10
 	"github.com/moby/moby/api/types/filters"
11
-	"github.com/moby/moby/api/types/versions"
12 11
 	"github.com/moby/moby/client/internal/timestamp"
13 12
 )
14 13
 
... ...
@@ -31,17 +30,7 @@ func (cli *Client) Events(ctx context.Context, options EventsListOptions) (<-cha
31 31
 	go func() {
32 32
 		defer close(errs)
33 33
 
34
-		// Make sure we negotiated (if the client is configured to do so),
35
-		// as code below contains API-version specific handling of options.
36
-		//
37
-		// Normally, version-negotiation (if enabled) would not happen until
38
-		// the API request is made.
39
-		if err := cli.checkVersion(ctx); err != nil {
40
-			close(started)
41
-			errs <- err
42
-			return
43
-		}
44
-		query, err := buildEventsQueryParams(cli.version, options)
34
+		query, err := buildEventsQueryParams(options)
45 35
 		if err != nil {
46 36
 			close(started)
47 37
 			errs <- err
... ...
@@ -85,7 +74,7 @@ func (cli *Client) Events(ctx context.Context, options EventsListOptions) (<-cha
85 85
 	return messages, errs
86 86
 }
87 87
 
88
-func buildEventsQueryParams(cliVersion string, options EventsListOptions) (url.Values, error) {
88
+func buildEventsQueryParams(options EventsListOptions) (url.Values, error) {
89 89
 	query := url.Values{}
90 90
 	ref := time.Now()
91 91
 
... ...
@@ -110,13 +99,6 @@ func buildEventsQueryParams(cliVersion string, options EventsListOptions) (url.V
110 110
 		if err != nil {
111 111
 			return nil, err
112 112
 		}
113
-		if cliVersion != "" && versions.LessThan(cliVersion, "1.22") {
114
-			legacyFormat, err := encodeLegacyFilters(filterJSON)
115
-			if err != nil {
116
-				return nil, err
117
-			}
118
-			filterJSON = legacyFormat
119
-		}
120 113
 		query.Set("filters", filterJSON)
121 114
 	}
122 115
 
... ...
@@ -81,41 +81,3 @@ func encodePlatform(platform *ocispec.Platform) (string, error) {
81 81
 	}
82 82
 	return string(p), nil
83 83
 }
84
-
85
-// encodeLegacyFilters encodes Args in the legacy format as used in API v1.21 and older.
86
-// where values are a list of strings, instead of a set.
87
-//
88
-// Don't use in any new code; use [filters.ToJSON]] instead.
89
-func encodeLegacyFilters(currentFormat string) (string, error) {
90
-	// The Args.fields field is not exported, but used to marshal JSON,
91
-	// so we'll marshal to the new format, then unmarshal to get the
92
-	// fields, and marshal again.
93
-	//
94
-	// This is far from optimal, but this code is only used for deprecated
95
-	// API versions, so should not be hit commonly.
96
-	var argsFields map[string]map[string]bool
97
-	err := json.Unmarshal([]byte(currentFormat), &argsFields)
98
-	if err != nil {
99
-		return "", err
100
-	}
101
-
102
-	buf, err := json.Marshal(convertArgsToSlice(argsFields))
103
-	if err != nil {
104
-		return "", err
105
-	}
106
-	return string(buf), nil
107
-}
108
-
109
-func convertArgsToSlice(f map[string]map[string]bool) map[string][]string {
110
-	m := map[string][]string{}
111
-	for k, v := range f {
112
-		values := []string{}
113
-		for kk := range v {
114
-			if v[kk] {
115
-				values = append(values, kk)
116
-			}
117
-		}
118
-		m[k] = values
119
-	}
120
-	return m
121
-}
... ...
@@ -3,7 +3,6 @@ package client
3 3
 import (
4 4
 	"testing"
5 5
 
6
-	"github.com/moby/moby/api/types/filters"
7 6
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
8 7
 	"gotest.tools/v3/assert"
9 8
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -56,22 +55,3 @@ func TestEncodePlatforms(t *testing.T) {
56 56
 		})
57 57
 	}
58 58
 }
59
-
60
-func TestEncodeLegacyFilters(t *testing.T) {
61
-	a := filters.NewArgs(
62
-		filters.Arg("created", "today"),
63
-		filters.Arg("image.name", "ubuntu*"),
64
-		filters.Arg("image.name", "*untu"),
65
-	)
66
-
67
-	currentFormat, err := filters.ToJSON(a)
68
-	assert.NilError(t, err)
69
-
70
-	// encode in the API v1.21 (and older) format
71
-	str1, err := encodeLegacyFilters(currentFormat)
72
-	assert.Check(t, err)
73
-	if str1 != `{"created":["today"],"image.name":["*untu","ubuntu*"]}` &&
74
-		str1 != `{"created":["today"],"image.name":["ubuntu*","*untu"]}` {
75
-		t.Errorf("incorrectly marshaled the filters: %s", str1)
76
-	}
77
-}
... ...
@@ -6,7 +6,6 @@ import (
6 6
 	"net/url"
7 7
 
8 8
 	"github.com/moby/moby/api/types/filters"
9
-	"github.com/moby/moby/api/types/versions"
10 9
 	"github.com/moby/moby/api/types/volume"
11 10
 )
12 11
 
... ...
@@ -19,13 +18,6 @@ func (cli *Client) VolumeList(ctx context.Context, options VolumeListOptions) (v
19 19
 		if err != nil {
20 20
 			return volume.ListResponse{}, err
21 21
 		}
22
-		if cli.version != "" && versions.LessThan(cli.version, "1.22") {
23
-			legacyFormat, err := encodeLegacyFilters(filterJSON)
24
-			if err != nil {
25
-				return volume.ListResponse{}, err
26
-			}
27
-			filterJSON = legacyFormat
28
-		}
29 22
 		query.Set("filters", filterJSON)
30 23
 	}
31 24
 	resp, err := cli.get(ctx, "/volumes", query, nil)
... ...
@@ -8,7 +8,6 @@ import (
8 8
 
9 9
 	"github.com/moby/moby/api/types/container"
10 10
 	"github.com/moby/moby/api/types/filters"
11
-	"github.com/moby/moby/api/types/versions"
12 11
 )
13 12
 
14 13
 // ContainerListOptions holds parameters to list containers with.
... ...
@@ -47,27 +46,10 @@ func (cli *Client) ContainerList(ctx context.Context, options ContainerListOptio
47 47
 	}
48 48
 
49 49
 	if options.Filters.Len() > 0 {
50
-		// Make sure we negotiated (if the client is configured to do so),
51
-		// as code below contains API-version specific handling of options.
52
-		//
53
-		// Normally, version-negotiation (if enabled) would not happen until
54
-		// the API request is made.
55
-		if err := cli.checkVersion(ctx); err != nil {
56
-			return nil, err
57
-		}
58
-
59 50
 		filterJSON, err := filters.ToJSON(options.Filters)
60 51
 		if err != nil {
61 52
 			return nil, err
62 53
 		}
63
-		if cli.version != "" && versions.LessThan(cli.version, "1.22") {
64
-			legacyFormat, err := encodeLegacyFilters(filterJSON)
65
-			if err != nil {
66
-				return nil, err
67
-			}
68
-			filterJSON = legacyFormat
69
-		}
70
-
71 54
 		query.Set("filters", filterJSON)
72 55
 	}
73 56
 
... ...
@@ -43,13 +43,6 @@ func (cli *Client) ImageList(ctx context.Context, options ImageListOptions) ([]i
43 43
 		if err != nil {
44 44
 			return images, err
45 45
 		}
46
-		if cli.version != "" && versions.LessThan(cli.version, "1.22") {
47
-			legacyFormat, err := encodeLegacyFilters(filterJSON)
48
-			if err != nil {
49
-				return nil, err
50
-			}
51
-			filterJSON = legacyFormat
52
-		}
53 46
 		query.Set("filters", filterJSON)
54 47
 	}
55 48
 	if options.All {
... ...
@@ -7,7 +7,6 @@ import (
7 7
 
8 8
 	"github.com/moby/moby/api/types/filters"
9 9
 	"github.com/moby/moby/api/types/network"
10
-	"github.com/moby/moby/api/types/versions"
11 10
 )
12 11
 
13 12
 // NetworkList returns the list of networks configured in the docker host.
... ...
@@ -18,14 +17,6 @@ func (cli *Client) NetworkList(ctx context.Context, options NetworkListOptions)
18 18
 		if err != nil {
19 19
 			return nil, err
20 20
 		}
21
-		if cli.version != "" && versions.LessThan(cli.version, "1.22") {
22
-			legacyFormat, err := encodeLegacyFilters(filterJSON)
23
-			if err != nil {
24
-				return nil, err
25
-			}
26
-			filterJSON = legacyFormat
27
-		}
28
-
29 21
 		query.Set("filters", filterJSON)
30 22
 	}
31 23
 	var networkResources []network.Summary
... ...
@@ -7,7 +7,6 @@ import (
7 7
 
8 8
 	"github.com/moby/moby/api/types/filters"
9 9
 	"github.com/moby/moby/api/types/plugin"
10
-	"github.com/moby/moby/api/types/versions"
11 10
 )
12 11
 
13 12
 // PluginList returns the installed plugins
... ...
@@ -20,13 +19,6 @@ func (cli *Client) PluginList(ctx context.Context, filter filters.Args) (plugin.
20 20
 		if err != nil {
21 21
 			return plugins, err
22 22
 		}
23
-		if cli.version != "" && versions.LessThan(cli.version, "1.22") {
24
-			legacyFormat, err := encodeLegacyFilters(filterJSON)
25
-			if err != nil {
26
-				return plugins, err
27
-			}
28
-			filterJSON = legacyFormat
29
-		}
30 23
 		query.Set("filters", filterJSON)
31 24
 	}
32 25
 	resp, err := cli.get(ctx, "/plugins", query, nil)
... ...
@@ -8,7 +8,6 @@ import (
8 8
 
9 9
 	"github.com/moby/moby/api/types/events"
10 10
 	"github.com/moby/moby/api/types/filters"
11
-	"github.com/moby/moby/api/types/versions"
12 11
 	"github.com/moby/moby/client/internal/timestamp"
13 12
 )
14 13
 
... ...
@@ -31,17 +30,7 @@ func (cli *Client) Events(ctx context.Context, options EventsListOptions) (<-cha
31 31
 	go func() {
32 32
 		defer close(errs)
33 33
 
34
-		// Make sure we negotiated (if the client is configured to do so),
35
-		// as code below contains API-version specific handling of options.
36
-		//
37
-		// Normally, version-negotiation (if enabled) would not happen until
38
-		// the API request is made.
39
-		if err := cli.checkVersion(ctx); err != nil {
40
-			close(started)
41
-			errs <- err
42
-			return
43
-		}
44
-		query, err := buildEventsQueryParams(cli.version, options)
34
+		query, err := buildEventsQueryParams(options)
45 35
 		if err != nil {
46 36
 			close(started)
47 37
 			errs <- err
... ...
@@ -85,7 +74,7 @@ func (cli *Client) Events(ctx context.Context, options EventsListOptions) (<-cha
85 85
 	return messages, errs
86 86
 }
87 87
 
88
-func buildEventsQueryParams(cliVersion string, options EventsListOptions) (url.Values, error) {
88
+func buildEventsQueryParams(options EventsListOptions) (url.Values, error) {
89 89
 	query := url.Values{}
90 90
 	ref := time.Now()
91 91
 
... ...
@@ -110,13 +99,6 @@ func buildEventsQueryParams(cliVersion string, options EventsListOptions) (url.V
110 110
 		if err != nil {
111 111
 			return nil, err
112 112
 		}
113
-		if cliVersion != "" && versions.LessThan(cliVersion, "1.22") {
114
-			legacyFormat, err := encodeLegacyFilters(filterJSON)
115
-			if err != nil {
116
-				return nil, err
117
-			}
118
-			filterJSON = legacyFormat
119
-		}
120 113
 		query.Set("filters", filterJSON)
121 114
 	}
122 115
 
... ...
@@ -81,41 +81,3 @@ func encodePlatform(platform *ocispec.Platform) (string, error) {
81 81
 	}
82 82
 	return string(p), nil
83 83
 }
84
-
85
-// encodeLegacyFilters encodes Args in the legacy format as used in API v1.21 and older.
86
-// where values are a list of strings, instead of a set.
87
-//
88
-// Don't use in any new code; use [filters.ToJSON]] instead.
89
-func encodeLegacyFilters(currentFormat string) (string, error) {
90
-	// The Args.fields field is not exported, but used to marshal JSON,
91
-	// so we'll marshal to the new format, then unmarshal to get the
92
-	// fields, and marshal again.
93
-	//
94
-	// This is far from optimal, but this code is only used for deprecated
95
-	// API versions, so should not be hit commonly.
96
-	var argsFields map[string]map[string]bool
97
-	err := json.Unmarshal([]byte(currentFormat), &argsFields)
98
-	if err != nil {
99
-		return "", err
100
-	}
101
-
102
-	buf, err := json.Marshal(convertArgsToSlice(argsFields))
103
-	if err != nil {
104
-		return "", err
105
-	}
106
-	return string(buf), nil
107
-}
108
-
109
-func convertArgsToSlice(f map[string]map[string]bool) map[string][]string {
110
-	m := map[string][]string{}
111
-	for k, v := range f {
112
-		values := []string{}
113
-		for kk := range v {
114
-			if v[kk] {
115
-				values = append(values, kk)
116
-			}
117
-		}
118
-		m[k] = values
119
-	}
120
-	return m
121
-}
... ...
@@ -6,7 +6,6 @@ import (
6 6
 	"net/url"
7 7
 
8 8
 	"github.com/moby/moby/api/types/filters"
9
-	"github.com/moby/moby/api/types/versions"
10 9
 	"github.com/moby/moby/api/types/volume"
11 10
 )
12 11
 
... ...
@@ -19,13 +18,6 @@ func (cli *Client) VolumeList(ctx context.Context, options VolumeListOptions) (v
19 19
 		if err != nil {
20 20
 			return volume.ListResponse{}, err
21 21
 		}
22
-		if cli.version != "" && versions.LessThan(cli.version, "1.22") {
23
-			legacyFormat, err := encodeLegacyFilters(filterJSON)
24
-			if err != nil {
25
-				return volume.ListResponse{}, err
26
-			}
27
-			filterJSON = legacyFormat
28
-		}
29 22
 		query.Set("filters", filterJSON)
30 23
 	}
31 24
 	resp, err := cli.get(ctx, "/volumes", query, nil)