Browse code

Add API version checks to client

The Docker CLI already performs version-checks when
running commands, but other clients consuming the API
client may not do so.

This patch adds a version check to various
client functions.

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

Sebastiaan van Stijn authored on 2017/06/08 01:09:07
Showing 24 changed files
... ...
@@ -11,6 +11,9 @@ import (
11 11
 // ConfigCreate creates a new Config.
12 12
 func (cli *Client) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (types.ConfigCreateResponse, error) {
13 13
 	var response types.ConfigCreateResponse
14
+	if err := cli.NewVersionError("1.30", "config create"); err != nil {
15
+		return response, err
16
+	}
14 17
 	resp, err := cli.post(ctx, "/configs/create", nil, config, nil)
15 18
 	if err != nil {
16 19
 		return response, err
... ...
@@ -11,12 +11,23 @@ import (
11 11
 
12 12
 	"github.com/docker/docker/api/types"
13 13
 	"github.com/docker/docker/api/types/swarm"
14
+	"github.com/stretchr/testify/assert"
14 15
 	"golang.org/x/net/context"
15 16
 )
16 17
 
18
+func TestConfigCreateUnsupported(t *testing.T) {
19
+	client := &Client{
20
+		version: "1.29",
21
+		client:  &http.Client{},
22
+	}
23
+	_, err := client.ConfigCreate(context.Background(), swarm.ConfigSpec{})
24
+	assert.EqualError(t, err, `"config create" requires API version 1.30, but the Docker daemon API version is 1.29`)
25
+}
26
+
17 27
 func TestConfigCreateError(t *testing.T) {
18 28
 	client := &Client{
19
-		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
29
+		version: "1.30",
30
+		client:  newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
20 31
 	}
21 32
 	_, err := client.ConfigCreate(context.Background(), swarm.ConfigSpec{})
22 33
 	if err == nil || err.Error() != "Error response from daemon: Server error" {
... ...
@@ -25,8 +36,9 @@ func TestConfigCreateError(t *testing.T) {
25 25
 }
26 26
 
27 27
 func TestConfigCreate(t *testing.T) {
28
-	expectedURL := "/configs/create"
28
+	expectedURL := "/v1.30/configs/create"
29 29
 	client := &Client{
30
+		version: "1.30",
30 31
 		client: newMockClient(func(req *http.Request) (*http.Response, error) {
31 32
 			if !strings.HasPrefix(req.URL.Path, expectedURL) {
32 33
 				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
... ...
@@ -12,6 +12,9 @@ import (
12 12
 
13 13
 // ConfigInspectWithRaw returns the config information with raw data
14 14
 func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.Config, []byte, error) {
15
+	if err := cli.NewVersionError("1.30", "config inspect"); err != nil {
16
+		return swarm.Config{}, nil, err
17
+	}
15 18
 	resp, err := cli.get(ctx, "/configs/"+id, nil, nil)
16 19
 	if err != nil {
17 20
 		if resp.statusCode == http.StatusNotFound {
... ...
@@ -10,12 +10,23 @@ import (
10 10
 	"testing"
11 11
 
12 12
 	"github.com/docker/docker/api/types/swarm"
13
+	"github.com/stretchr/testify/assert"
13 14
 	"golang.org/x/net/context"
14 15
 )
15 16
 
17
+func TestConfigInspectUnsupported(t *testing.T) {
18
+	client := &Client{
19
+		version: "1.29",
20
+		client:  &http.Client{},
21
+	}
22
+	_, _, err := client.ConfigInspectWithRaw(context.Background(), "nothing")
23
+	assert.EqualError(t, err, `"config inspect" requires API version 1.30, but the Docker daemon API version is 1.29`)
24
+}
25
+
16 26
 func TestConfigInspectError(t *testing.T) {
17 27
 	client := &Client{
18
-		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
28
+		version: "1.30",
29
+		client:  newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
19 30
 	}
20 31
 
21 32
 	_, _, err := client.ConfigInspectWithRaw(context.Background(), "nothing")
... ...
@@ -26,7 +37,8 @@ func TestConfigInspectError(t *testing.T) {
26 26
 
27 27
 func TestConfigInspectConfigNotFound(t *testing.T) {
28 28
 	client := &Client{
29
-		client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
29
+		version: "1.30",
30
+		client:  newMockClient(errorMock(http.StatusNotFound, "Server error")),
30 31
 	}
31 32
 
32 33
 	_, _, err := client.ConfigInspectWithRaw(context.Background(), "unknown")
... ...
@@ -36,8 +48,9 @@ func TestConfigInspectConfigNotFound(t *testing.T) {
36 36
 }
37 37
 
38 38
 func TestConfigInspect(t *testing.T) {
39
-	expectedURL := "/configs/config_id"
39
+	expectedURL := "/v1.30/configs/config_id"
40 40
 	client := &Client{
41
+		version: "1.30",
41 42
 		client: newMockClient(func(req *http.Request) (*http.Response, error) {
42 43
 			if !strings.HasPrefix(req.URL.Path, expectedURL) {
43 44
 				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
... ...
@@ -12,6 +12,9 @@ import (
12 12
 
13 13
 // ConfigList returns the list of configs.
14 14
 func (cli *Client) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) {
15
+	if err := cli.NewVersionError("1.30", "config list"); err != nil {
16
+		return nil, err
17
+	}
15 18
 	query := url.Values{}
16 19
 
17 20
 	if options.Filters.Len() > 0 {
... ...
@@ -12,12 +12,23 @@ import (
12 12
 	"github.com/docker/docker/api/types"
13 13
 	"github.com/docker/docker/api/types/filters"
14 14
 	"github.com/docker/docker/api/types/swarm"
15
+	"github.com/stretchr/testify/assert"
15 16
 	"golang.org/x/net/context"
16 17
 )
17 18
 
19
+func TestConfigListUnsupported(t *testing.T) {
20
+	client := &Client{
21
+		version: "1.29",
22
+		client:  &http.Client{},
23
+	}
24
+	_, err := client.ConfigList(context.Background(), types.ConfigListOptions{})
25
+	assert.EqualError(t, err, `"config list" requires API version 1.30, but the Docker daemon API version is 1.29`)
26
+}
27
+
18 28
 func TestConfigListError(t *testing.T) {
19 29
 	client := &Client{
20
-		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
30
+		version: "1.30",
31
+		client:  newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
21 32
 	}
22 33
 
23 34
 	_, err := client.ConfigList(context.Background(), types.ConfigListOptions{})
... ...
@@ -27,7 +38,7 @@ func TestConfigListError(t *testing.T) {
27 27
 }
28 28
 
29 29
 func TestConfigList(t *testing.T) {
30
-	expectedURL := "/configs"
30
+	expectedURL := "/v1.30/configs"
31 31
 
32 32
 	filters := filters.NewArgs()
33 33
 	filters.Add("label", "label1")
... ...
@@ -54,6 +65,7 @@ func TestConfigList(t *testing.T) {
54 54
 	}
55 55
 	for _, listCase := range listCases {
56 56
 		client := &Client{
57
+			version: "1.30",
57 58
 			client: newMockClient(func(req *http.Request) (*http.Response, error) {
58 59
 				if !strings.HasPrefix(req.URL.Path, expectedURL) {
59 60
 					return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
... ...
@@ -4,6 +4,9 @@ import "golang.org/x/net/context"
4 4
 
5 5
 // ConfigRemove removes a Config.
6 6
 func (cli *Client) ConfigRemove(ctx context.Context, id string) error {
7
+	if err := cli.NewVersionError("1.30", "config remove"); err != nil {
8
+		return err
9
+	}
7 10
 	resp, err := cli.delete(ctx, "/configs/"+id, nil, nil)
8 11
 	ensureReaderClosed(resp)
9 12
 	return err
... ...
@@ -8,12 +8,23 @@ import (
8 8
 	"strings"
9 9
 	"testing"
10 10
 
11
+	"github.com/stretchr/testify/assert"
11 12
 	"golang.org/x/net/context"
12 13
 )
13 14
 
15
+func TestConfigRemoveUnsupported(t *testing.T) {
16
+	client := &Client{
17
+		version: "1.29",
18
+		client:  &http.Client{},
19
+	}
20
+	err := client.ConfigRemove(context.Background(), "config_id")
21
+	assert.EqualError(t, err, `"config remove" requires API version 1.30, but the Docker daemon API version is 1.29`)
22
+}
23
+
14 24
 func TestConfigRemoveError(t *testing.T) {
15 25
 	client := &Client{
16
-		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
26
+		version: "1.30",
27
+		client:  newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
17 28
 	}
18 29
 
19 30
 	err := client.ConfigRemove(context.Background(), "config_id")
... ...
@@ -23,9 +34,10 @@ func TestConfigRemoveError(t *testing.T) {
23 23
 }
24 24
 
25 25
 func TestConfigRemove(t *testing.T) {
26
-	expectedURL := "/configs/config_id"
26
+	expectedURL := "/v1.30/configs/config_id"
27 27
 
28 28
 	client := &Client{
29
+		version: "1.30",
29 30
 		client: newMockClient(func(req *http.Request) (*http.Response, error) {
30 31
 			if !strings.HasPrefix(req.URL.Path, expectedURL) {
31 32
 				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
... ...
@@ -10,6 +10,9 @@ import (
10 10
 
11 11
 // ConfigUpdate attempts to update a Config
12 12
 func (cli *Client) ConfigUpdate(ctx context.Context, id string, version swarm.Version, config swarm.ConfigSpec) error {
13
+	if err := cli.NewVersionError("1.30", "config update"); err != nil {
14
+		return err
15
+	}
13 16
 	query := url.Values{}
14 17
 	query.Set("version", strconv.FormatUint(version.Index, 10))
15 18
 	resp, err := cli.post(ctx, "/configs/"+id+"/update", query, config, nil)
... ...
@@ -8,14 +8,24 @@ import (
8 8
 	"strings"
9 9
 	"testing"
10 10
 
11
-	"golang.org/x/net/context"
12
-
13 11
 	"github.com/docker/docker/api/types/swarm"
12
+	"github.com/stretchr/testify/assert"
13
+	"golang.org/x/net/context"
14 14
 )
15 15
 
16
+func TestConfigUpdateUnsupported(t *testing.T) {
17
+	client := &Client{
18
+		version: "1.29",
19
+		client:  &http.Client{},
20
+	}
21
+	err := client.ConfigUpdate(context.Background(), "config_id", swarm.Version{}, swarm.ConfigSpec{})
22
+	assert.EqualError(t, err, `"config update" requires API version 1.30, but the Docker daemon API version is 1.29`)
23
+}
24
+
16 25
 func TestConfigUpdateError(t *testing.T) {
17 26
 	client := &Client{
18
-		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
27
+		version: "1.30",
28
+		client:  newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
19 29
 	}
20 30
 
21 31
 	err := client.ConfigUpdate(context.Background(), "config_id", swarm.Version{}, swarm.ConfigSpec{})
... ...
@@ -25,9 +35,10 @@ func TestConfigUpdateError(t *testing.T) {
25 25
 }
26 26
 
27 27
 func TestConfigUpdate(t *testing.T) {
28
-	expectedURL := "/configs/config_id/update"
28
+	expectedURL := "/v1.30/configs/config_id/update"
29 29
 
30 30
 	client := &Client{
31
+		version: "1.30",
31 32
 		client: newMockClient(func(req *http.Request) (*http.Response, error) {
32 33
 			if !strings.HasPrefix(req.URL.Path, expectedURL) {
33 34
 				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
... ...
@@ -10,6 +10,12 @@ import (
10 10
 
11 11
 // DistributionInspect returns the image digest with full Manifest
12 12
 func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegistryAuth string) (registrytypes.DistributionInspect, error) {
13
+	// Contact the registry to retrieve digest and platform information
14
+	var distributionInspect registrytypes.DistributionInspect
15
+
16
+	if err := cli.NewVersionError("1.30", "distribution inspect"); err != nil {
17
+		return distributionInspect, err
18
+	}
13 19
 	var headers map[string][]string
14 20
 
15 21
 	if encodedRegistryAuth != "" {
... ...
@@ -18,8 +24,6 @@ func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegist
18 18
 		}
19 19
 	}
20 20
 
21
-	// Contact the registry to retrieve digest and platform information
22
-	var distributionInspect registrytypes.DistributionInspect
23 21
 	resp, err := cli.get(ctx, "/distribution/"+image+"/json", url.Values{}, headers)
24 22
 	if err != nil {
25 23
 		return distributionInspect, err
26 24
new file mode 100644
... ...
@@ -0,0 +1,18 @@
0
+package client
1
+
2
+import (
3
+	"net/http"
4
+	"testing"
5
+
6
+	"github.com/stretchr/testify/assert"
7
+	"golang.org/x/net/context"
8
+)
9
+
10
+func TestDistributionInspectUnsupported(t *testing.T) {
11
+	client := &Client{
12
+		version: "1.29",
13
+		client:  &http.Client{},
14
+	}
15
+	_, err := client.DistributionInspect(context.Background(), "foobar:1.0", "")
16
+	assert.EqualError(t, err, `"distribution inspect" requires API version 1.30, but the Docker daemon API version is 1.29`)
17
+}
... ...
@@ -12,6 +12,9 @@ import (
12 12
 
13 13
 // PluginUpgrade upgrades a plugin
14 14
 func (cli *Client) PluginUpgrade(ctx context.Context, name string, options types.PluginInstallOptions) (rc io.ReadCloser, err error) {
15
+	if err := cli.NewVersionError("1.26", "plugin upgrade"); err != nil {
16
+		return nil, err
17
+	}
15 18
 	query := url.Values{}
16 19
 	if _, err := reference.ParseNormalizedNamed(options.RemoteRef); err != nil {
17 20
 		return nil, errors.Wrap(err, "invalid remote reference")
... ...
@@ -11,6 +11,9 @@ import (
11 11
 // SecretCreate creates a new Secret.
12 12
 func (cli *Client) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (types.SecretCreateResponse, error) {
13 13
 	var response types.SecretCreateResponse
14
+	if err := cli.NewVersionError("1.25", "secret create"); err != nil {
15
+		return response, err
16
+	}
14 17
 	resp, err := cli.post(ctx, "/secrets/create", nil, secret, nil)
15 18
 	if err != nil {
16 19
 		return response, err
... ...
@@ -11,12 +11,23 @@ import (
11 11
 
12 12
 	"github.com/docker/docker/api/types"
13 13
 	"github.com/docker/docker/api/types/swarm"
14
+	"github.com/stretchr/testify/assert"
14 15
 	"golang.org/x/net/context"
15 16
 )
16 17
 
18
+func TestSecretCreateUnsupported(t *testing.T) {
19
+	client := &Client{
20
+		version: "1.24",
21
+		client:  &http.Client{},
22
+	}
23
+	_, err := client.SecretCreate(context.Background(), swarm.SecretSpec{})
24
+	assert.EqualError(t, err, `"secret create" requires API version 1.25, but the Docker daemon API version is 1.24`)
25
+}
26
+
17 27
 func TestSecretCreateError(t *testing.T) {
18 28
 	client := &Client{
19
-		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
29
+		version: "1.25",
30
+		client:  newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
20 31
 	}
21 32
 	_, err := client.SecretCreate(context.Background(), swarm.SecretSpec{})
22 33
 	if err == nil || err.Error() != "Error response from daemon: Server error" {
... ...
@@ -25,8 +36,9 @@ func TestSecretCreateError(t *testing.T) {
25 25
 }
26 26
 
27 27
 func TestSecretCreate(t *testing.T) {
28
-	expectedURL := "/secrets/create"
28
+	expectedURL := "/v1.25/secrets/create"
29 29
 	client := &Client{
30
+		version: "1.25",
30 31
 		client: newMockClient(func(req *http.Request) (*http.Response, error) {
31 32
 			if !strings.HasPrefix(req.URL.Path, expectedURL) {
32 33
 				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
... ...
@@ -12,6 +12,9 @@ import (
12 12
 
13 13
 // SecretInspectWithRaw returns the secret information with raw data
14 14
 func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.Secret, []byte, error) {
15
+	if err := cli.NewVersionError("1.25", "secret inspect"); err != nil {
16
+		return swarm.Secret{}, nil, err
17
+	}
15 18
 	resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
16 19
 	if err != nil {
17 20
 		if resp.statusCode == http.StatusNotFound {
... ...
@@ -10,12 +10,23 @@ import (
10 10
 	"testing"
11 11
 
12 12
 	"github.com/docker/docker/api/types/swarm"
13
+	"github.com/stretchr/testify/assert"
13 14
 	"golang.org/x/net/context"
14 15
 )
15 16
 
17
+func TestSecretInspectUnsupported(t *testing.T) {
18
+	client := &Client{
19
+		version: "1.24",
20
+		client:  &http.Client{},
21
+	}
22
+	_, _, err := client.SecretInspectWithRaw(context.Background(), "nothing")
23
+	assert.EqualError(t, err, `"secret inspect" requires API version 1.25, but the Docker daemon API version is 1.24`)
24
+}
25
+
16 26
 func TestSecretInspectError(t *testing.T) {
17 27
 	client := &Client{
18
-		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
28
+		version: "1.25",
29
+		client:  newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
19 30
 	}
20 31
 
21 32
 	_, _, err := client.SecretInspectWithRaw(context.Background(), "nothing")
... ...
@@ -26,7 +37,8 @@ func TestSecretInspectError(t *testing.T) {
26 26
 
27 27
 func TestSecretInspectSecretNotFound(t *testing.T) {
28 28
 	client := &Client{
29
-		client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
29
+		version: "1.25",
30
+		client:  newMockClient(errorMock(http.StatusNotFound, "Server error")),
30 31
 	}
31 32
 
32 33
 	_, _, err := client.SecretInspectWithRaw(context.Background(), "unknown")
... ...
@@ -36,8 +48,9 @@ func TestSecretInspectSecretNotFound(t *testing.T) {
36 36
 }
37 37
 
38 38
 func TestSecretInspect(t *testing.T) {
39
-	expectedURL := "/secrets/secret_id"
39
+	expectedURL := "/v1.25/secrets/secret_id"
40 40
 	client := &Client{
41
+		version: "1.25",
41 42
 		client: newMockClient(func(req *http.Request) (*http.Response, error) {
42 43
 			if !strings.HasPrefix(req.URL.Path, expectedURL) {
43 44
 				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
... ...
@@ -12,6 +12,9 @@ import (
12 12
 
13 13
 // SecretList returns the list of secrets.
14 14
 func (cli *Client) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) {
15
+	if err := cli.NewVersionError("1.25", "secret list"); err != nil {
16
+		return nil, err
17
+	}
15 18
 	query := url.Values{}
16 19
 
17 20
 	if options.Filters.Len() > 0 {
... ...
@@ -12,12 +12,23 @@ import (
12 12
 	"github.com/docker/docker/api/types"
13 13
 	"github.com/docker/docker/api/types/filters"
14 14
 	"github.com/docker/docker/api/types/swarm"
15
+	"github.com/stretchr/testify/assert"
15 16
 	"golang.org/x/net/context"
16 17
 )
17 18
 
19
+func TestSecretListUnsupported(t *testing.T) {
20
+	client := &Client{
21
+		version: "1.24",
22
+		client:  &http.Client{},
23
+	}
24
+	_, err := client.SecretList(context.Background(), types.SecretListOptions{})
25
+	assert.EqualError(t, err, `"secret list" requires API version 1.25, but the Docker daemon API version is 1.24`)
26
+}
27
+
18 28
 func TestSecretListError(t *testing.T) {
19 29
 	client := &Client{
20
-		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
30
+		version: "1.25",
31
+		client:  newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
21 32
 	}
22 33
 
23 34
 	_, err := client.SecretList(context.Background(), types.SecretListOptions{})
... ...
@@ -27,7 +38,7 @@ func TestSecretListError(t *testing.T) {
27 27
 }
28 28
 
29 29
 func TestSecretList(t *testing.T) {
30
-	expectedURL := "/secrets"
30
+	expectedURL := "/v1.25/secrets"
31 31
 
32 32
 	filters := filters.NewArgs()
33 33
 	filters.Add("label", "label1")
... ...
@@ -54,6 +65,7 @@ func TestSecretList(t *testing.T) {
54 54
 	}
55 55
 	for _, listCase := range listCases {
56 56
 		client := &Client{
57
+			version: "1.25",
57 58
 			client: newMockClient(func(req *http.Request) (*http.Response, error) {
58 59
 				if !strings.HasPrefix(req.URL.Path, expectedURL) {
59 60
 					return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
... ...
@@ -4,6 +4,9 @@ import "golang.org/x/net/context"
4 4
 
5 5
 // SecretRemove removes a Secret.
6 6
 func (cli *Client) SecretRemove(ctx context.Context, id string) error {
7
+	if err := cli.NewVersionError("1.25", "secret remove"); err != nil {
8
+		return err
9
+	}
7 10
 	resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil)
8 11
 	ensureReaderClosed(resp)
9 12
 	return err
... ...
@@ -8,12 +8,23 @@ import (
8 8
 	"strings"
9 9
 	"testing"
10 10
 
11
+	"github.com/stretchr/testify/assert"
11 12
 	"golang.org/x/net/context"
12 13
 )
13 14
 
15
+func TestSecretRemoveUnsupported(t *testing.T) {
16
+	client := &Client{
17
+		version: "1.24",
18
+		client:  &http.Client{},
19
+	}
20
+	err := client.SecretRemove(context.Background(), "secret_id")
21
+	assert.EqualError(t, err, `"secret remove" requires API version 1.25, but the Docker daemon API version is 1.24`)
22
+}
23
+
14 24
 func TestSecretRemoveError(t *testing.T) {
15 25
 	client := &Client{
16
-		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
26
+		version: "1.25",
27
+		client:  newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
17 28
 	}
18 29
 
19 30
 	err := client.SecretRemove(context.Background(), "secret_id")
... ...
@@ -23,9 +34,10 @@ func TestSecretRemoveError(t *testing.T) {
23 23
 }
24 24
 
25 25
 func TestSecretRemove(t *testing.T) {
26
-	expectedURL := "/secrets/secret_id"
26
+	expectedURL := "/v1.25/secrets/secret_id"
27 27
 
28 28
 	client := &Client{
29
+		version: "1.25",
29 30
 		client: newMockClient(func(req *http.Request) (*http.Response, error) {
30 31
 			if !strings.HasPrefix(req.URL.Path, expectedURL) {
31 32
 				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
... ...
@@ -10,6 +10,9 @@ import (
10 10
 
11 11
 // SecretUpdate attempts to update a Secret
12 12
 func (cli *Client) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error {
13
+	if err := cli.NewVersionError("1.25", "secret update"); err != nil {
14
+		return err
15
+	}
13 16
 	query := url.Values{}
14 17
 	query.Set("version", strconv.FormatUint(version.Index, 10))
15 18
 	resp, err := cli.post(ctx, "/secrets/"+id+"/update", query, secret, nil)
... ...
@@ -8,14 +8,24 @@ import (
8 8
 	"strings"
9 9
 	"testing"
10 10
 
11
-	"golang.org/x/net/context"
12
-
13 11
 	"github.com/docker/docker/api/types/swarm"
12
+	"github.com/stretchr/testify/assert"
13
+	"golang.org/x/net/context"
14 14
 )
15 15
 
16
+func TestSecretUpdateUnsupported(t *testing.T) {
17
+	client := &Client{
18
+		version: "1.24",
19
+		client:  &http.Client{},
20
+	}
21
+	err := client.SecretUpdate(context.Background(), "secret_id", swarm.Version{}, swarm.SecretSpec{})
22
+	assert.EqualError(t, err, `"secret update" requires API version 1.25, but the Docker daemon API version is 1.24`)
23
+}
24
+
16 25
 func TestSecretUpdateError(t *testing.T) {
17 26
 	client := &Client{
18
-		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
27
+		version: "1.25",
28
+		client:  newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
19 29
 	}
20 30
 
21 31
 	err := client.SecretUpdate(context.Background(), "secret_id", swarm.Version{}, swarm.SecretSpec{})
... ...
@@ -25,9 +35,10 @@ func TestSecretUpdateError(t *testing.T) {
25 25
 }
26 26
 
27 27
 func TestSecretUpdate(t *testing.T) {
28
-	expectedURL := "/secrets/secret_id/update"
28
+	expectedURL := "/v1.25/secrets/secret_id/update"
29 29
 
30 30
 	client := &Client{
31
+		version: "1.25",
31 32
 		client: newMockClient(func(req *http.Request) (*http.Response, error) {
32 33
 			if !strings.HasPrefix(req.URL.Path, expectedURL) {
33 34
 				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
... ...
@@ -68,8 +68,9 @@ func TestServiceCreateCompatiblePlatforms(t *testing.T) {
68 68
 	)
69 69
 
70 70
 	client := &Client{
71
+		version: "1.30",
71 72
 		client: newMockClient(func(req *http.Request) (*http.Response, error) {
72
-			if strings.HasPrefix(req.URL.Path, "/services/create") {
73
+			if strings.HasPrefix(req.URL.Path, "/v1.30/services/create") {
73 74
 				// check if the /distribution endpoint returned correct output
74 75
 				err := json.NewDecoder(distributionInspectBody).Decode(&distributionInspect)
75 76
 				if err != nil {
... ...
@@ -89,7 +90,7 @@ func TestServiceCreateCompatiblePlatforms(t *testing.T) {
89 89
 					StatusCode: http.StatusOK,
90 90
 					Body:       ioutil.NopCloser(bytes.NewReader(b)),
91 91
 				}, nil
92
-			} else if strings.HasPrefix(req.URL.Path, "/distribution/") {
92
+			} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/") {
93 93
 				platforms = []v1.Platform{
94 94
 					{
95 95
 						Architecture: "amd64",
... ...
@@ -146,8 +147,9 @@ func TestServiceCreateDigestPinning(t *testing.T) {
146 146
 	}
147 147
 
148 148
 	client := &Client{
149
+		version: "1.30",
149 150
 		client: newMockClient(func(req *http.Request) (*http.Response, error) {
150
-			if strings.HasPrefix(req.URL.Path, "/services/create") {
151
+			if strings.HasPrefix(req.URL.Path, "/v1.30/services/create") {
151 152
 				// reset and set image received by the service create endpoint
152 153
 				serviceCreateImage = ""
153 154
 				var service swarm.ServiceSpec
... ...
@@ -166,10 +168,10 @@ func TestServiceCreateDigestPinning(t *testing.T) {
166 166
 					StatusCode: http.StatusOK,
167 167
 					Body:       ioutil.NopCloser(bytes.NewReader(b)),
168 168
 				}, nil
169
-			} else if strings.HasPrefix(req.URL.Path, "/distribution/cannotresolve") {
169
+			} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/cannotresolve") {
170 170
 				// unresolvable image
171 171
 				return nil, fmt.Errorf("cannot resolve image")
172
-			} else if strings.HasPrefix(req.URL.Path, "/distribution/") {
172
+			} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/") {
173 173
 				// resolvable images
174 174
 				b, err := json.Marshal(registrytypes.DistributionInspect{
175 175
 					Descriptor: v1.Descriptor{