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>
This commit is contained in:
Sebastiaan van Stijn 2017-06-07 18:09:07 +02:00
parent 4741c02605
commit 1401342f46
24 changed files with 210 additions and 33 deletions

View File

@ -11,6 +11,9 @@ import (
// ConfigCreate creates a new Config.
func (cli *Client) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (types.ConfigCreateResponse, error) {
var response types.ConfigCreateResponse
if err := cli.NewVersionError("1.30", "config create"); err != nil {
return response, err
}
resp, err := cli.post(ctx, "/configs/create", nil, config, nil)
if err != nil {
return response, err

View File

@ -11,12 +11,23 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestConfigCreateUnsupported(t *testing.T) {
client := &Client{
version: "1.29",
client: &http.Client{},
}
_, err := client.ConfigCreate(context.Background(), swarm.ConfigSpec{})
assert.EqualError(t, err, `"config create" requires API version 1.30, but the Docker daemon API version is 1.29`)
}
func TestConfigCreateError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
version: "1.30",
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ConfigCreate(context.Background(), swarm.ConfigSpec{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
@ -25,8 +36,9 @@ func TestConfigCreateError(t *testing.T) {
}
func TestConfigCreate(t *testing.T) {
expectedURL := "/configs/create"
expectedURL := "/v1.30/configs/create"
client := &Client{
version: "1.30",
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)

View File

@ -12,6 +12,9 @@ import (
// ConfigInspectWithRaw returns the config information with raw data
func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.Config, []byte, error) {
if err := cli.NewVersionError("1.30", "config inspect"); err != nil {
return swarm.Config{}, nil, err
}
resp, err := cli.get(ctx, "/configs/"+id, nil, nil)
if err != nil {
if resp.statusCode == http.StatusNotFound {

View File

@ -10,12 +10,23 @@ import (
"testing"
"github.com/docker/docker/api/types/swarm"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestConfigInspectUnsupported(t *testing.T) {
client := &Client{
version: "1.29",
client: &http.Client{},
}
_, _, err := client.ConfigInspectWithRaw(context.Background(), "nothing")
assert.EqualError(t, err, `"config inspect" requires API version 1.30, but the Docker daemon API version is 1.29`)
}
func TestConfigInspectError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
version: "1.30",
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
_, _, err := client.ConfigInspectWithRaw(context.Background(), "nothing")
@ -26,7 +37,8 @@ func TestConfigInspectError(t *testing.T) {
func TestConfigInspectConfigNotFound(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
version: "1.30",
client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
}
_, _, err := client.ConfigInspectWithRaw(context.Background(), "unknown")
@ -36,8 +48,9 @@ func TestConfigInspectConfigNotFound(t *testing.T) {
}
func TestConfigInspect(t *testing.T) {
expectedURL := "/configs/config_id"
expectedURL := "/v1.30/configs/config_id"
client := &Client{
version: "1.30",
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)

View File

@ -12,6 +12,9 @@ import (
// ConfigList returns the list of configs.
func (cli *Client) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) {
if err := cli.NewVersionError("1.30", "config list"); err != nil {
return nil, err
}
query := url.Values{}
if options.Filters.Len() > 0 {

View File

@ -12,12 +12,23 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestConfigListUnsupported(t *testing.T) {
client := &Client{
version: "1.29",
client: &http.Client{},
}
_, err := client.ConfigList(context.Background(), types.ConfigListOptions{})
assert.EqualError(t, err, `"config list" requires API version 1.30, but the Docker daemon API version is 1.29`)
}
func TestConfigListError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
version: "1.30",
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ConfigList(context.Background(), types.ConfigListOptions{})
@ -27,7 +38,7 @@ func TestConfigListError(t *testing.T) {
}
func TestConfigList(t *testing.T) {
expectedURL := "/configs"
expectedURL := "/v1.30/configs"
filters := filters.NewArgs()
filters.Add("label", "label1")
@ -54,6 +65,7 @@ func TestConfigList(t *testing.T) {
}
for _, listCase := range listCases {
client := &Client{
version: "1.30",
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)

View File

@ -4,6 +4,9 @@ import "golang.org/x/net/context"
// ConfigRemove removes a Config.
func (cli *Client) ConfigRemove(ctx context.Context, id string) error {
if err := cli.NewVersionError("1.30", "config remove"); err != nil {
return err
}
resp, err := cli.delete(ctx, "/configs/"+id, nil, nil)
ensureReaderClosed(resp)
return err

View File

@ -8,12 +8,23 @@ import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestConfigRemoveUnsupported(t *testing.T) {
client := &Client{
version: "1.29",
client: &http.Client{},
}
err := client.ConfigRemove(context.Background(), "config_id")
assert.EqualError(t, err, `"config remove" requires API version 1.30, but the Docker daemon API version is 1.29`)
}
func TestConfigRemoveError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
version: "1.30",
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.ConfigRemove(context.Background(), "config_id")
@ -23,9 +34,10 @@ func TestConfigRemoveError(t *testing.T) {
}
func TestConfigRemove(t *testing.T) {
expectedURL := "/configs/config_id"
expectedURL := "/v1.30/configs/config_id"
client := &Client{
version: "1.30",
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)

View File

@ -10,6 +10,9 @@ import (
// ConfigUpdate attempts to update a Config
func (cli *Client) ConfigUpdate(ctx context.Context, id string, version swarm.Version, config swarm.ConfigSpec) error {
if err := cli.NewVersionError("1.30", "config update"); err != nil {
return err
}
query := url.Values{}
query.Set("version", strconv.FormatUint(version.Index, 10))
resp, err := cli.post(ctx, "/configs/"+id+"/update", query, config, nil)

View File

@ -8,14 +8,24 @@ import (
"strings"
"testing"
"golang.org/x/net/context"
"github.com/docker/docker/api/types/swarm"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestConfigUpdateUnsupported(t *testing.T) {
client := &Client{
version: "1.29",
client: &http.Client{},
}
err := client.ConfigUpdate(context.Background(), "config_id", swarm.Version{}, swarm.ConfigSpec{})
assert.EqualError(t, err, `"config update" requires API version 1.30, but the Docker daemon API version is 1.29`)
}
func TestConfigUpdateError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
version: "1.30",
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.ConfigUpdate(context.Background(), "config_id", swarm.Version{}, swarm.ConfigSpec{})
@ -25,9 +35,10 @@ func TestConfigUpdateError(t *testing.T) {
}
func TestConfigUpdate(t *testing.T) {
expectedURL := "/configs/config_id/update"
expectedURL := "/v1.30/configs/config_id/update"
client := &Client{
version: "1.30",
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)

View File

@ -10,6 +10,12 @@ import (
// DistributionInspect returns the image digest with full Manifest
func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegistryAuth string) (registrytypes.DistributionInspect, error) {
// Contact the registry to retrieve digest and platform information
var distributionInspect registrytypes.DistributionInspect
if err := cli.NewVersionError("1.30", "distribution inspect"); err != nil {
return distributionInspect, err
}
var headers map[string][]string
if encodedRegistryAuth != "" {
@ -18,8 +24,6 @@ func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegist
}
}
// Contact the registry to retrieve digest and platform information
var distributionInspect registrytypes.DistributionInspect
resp, err := cli.get(ctx, "/distribution/"+image+"/json", url.Values{}, headers)
if err != nil {
return distributionInspect, err

View File

@ -0,0 +1,18 @@
package client
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestDistributionInspectUnsupported(t *testing.T) {
client := &Client{
version: "1.29",
client: &http.Client{},
}
_, err := client.DistributionInspect(context.Background(), "foobar:1.0", "")
assert.EqualError(t, err, `"distribution inspect" requires API version 1.30, but the Docker daemon API version is 1.29`)
}

View File

@ -12,6 +12,9 @@ import (
// PluginUpgrade upgrades a plugin
func (cli *Client) PluginUpgrade(ctx context.Context, name string, options types.PluginInstallOptions) (rc io.ReadCloser, err error) {
if err := cli.NewVersionError("1.26", "plugin upgrade"); err != nil {
return nil, err
}
query := url.Values{}
if _, err := reference.ParseNormalizedNamed(options.RemoteRef); err != nil {
return nil, errors.Wrap(err, "invalid remote reference")

View File

@ -11,6 +11,9 @@ import (
// SecretCreate creates a new Secret.
func (cli *Client) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (types.SecretCreateResponse, error) {
var response types.SecretCreateResponse
if err := cli.NewVersionError("1.25", "secret create"); err != nil {
return response, err
}
resp, err := cli.post(ctx, "/secrets/create", nil, secret, nil)
if err != nil {
return response, err

View File

@ -11,12 +11,23 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestSecretCreateUnsupported(t *testing.T) {
client := &Client{
version: "1.24",
client: &http.Client{},
}
_, err := client.SecretCreate(context.Background(), swarm.SecretSpec{})
assert.EqualError(t, err, `"secret create" requires API version 1.25, but the Docker daemon API version is 1.24`)
}
func TestSecretCreateError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
version: "1.25",
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.SecretCreate(context.Background(), swarm.SecretSpec{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
@ -25,8 +36,9 @@ func TestSecretCreateError(t *testing.T) {
}
func TestSecretCreate(t *testing.T) {
expectedURL := "/secrets/create"
expectedURL := "/v1.25/secrets/create"
client := &Client{
version: "1.25",
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)

View File

@ -12,6 +12,9 @@ import (
// SecretInspectWithRaw returns the secret information with raw data
func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.Secret, []byte, error) {
if err := cli.NewVersionError("1.25", "secret inspect"); err != nil {
return swarm.Secret{}, nil, err
}
resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
if err != nil {
if resp.statusCode == http.StatusNotFound {

View File

@ -10,12 +10,23 @@ import (
"testing"
"github.com/docker/docker/api/types/swarm"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestSecretInspectUnsupported(t *testing.T) {
client := &Client{
version: "1.24",
client: &http.Client{},
}
_, _, err := client.SecretInspectWithRaw(context.Background(), "nothing")
assert.EqualError(t, err, `"secret inspect" requires API version 1.25, but the Docker daemon API version is 1.24`)
}
func TestSecretInspectError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
version: "1.25",
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
_, _, err := client.SecretInspectWithRaw(context.Background(), "nothing")
@ -26,7 +37,8 @@ func TestSecretInspectError(t *testing.T) {
func TestSecretInspectSecretNotFound(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
version: "1.25",
client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
}
_, _, err := client.SecretInspectWithRaw(context.Background(), "unknown")
@ -36,8 +48,9 @@ func TestSecretInspectSecretNotFound(t *testing.T) {
}
func TestSecretInspect(t *testing.T) {
expectedURL := "/secrets/secret_id"
expectedURL := "/v1.25/secrets/secret_id"
client := &Client{
version: "1.25",
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)

View File

@ -12,6 +12,9 @@ import (
// SecretList returns the list of secrets.
func (cli *Client) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) {
if err := cli.NewVersionError("1.25", "secret list"); err != nil {
return nil, err
}
query := url.Values{}
if options.Filters.Len() > 0 {

View File

@ -12,12 +12,23 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestSecretListUnsupported(t *testing.T) {
client := &Client{
version: "1.24",
client: &http.Client{},
}
_, err := client.SecretList(context.Background(), types.SecretListOptions{})
assert.EqualError(t, err, `"secret list" requires API version 1.25, but the Docker daemon API version is 1.24`)
}
func TestSecretListError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
version: "1.25",
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.SecretList(context.Background(), types.SecretListOptions{})
@ -27,7 +38,7 @@ func TestSecretListError(t *testing.T) {
}
func TestSecretList(t *testing.T) {
expectedURL := "/secrets"
expectedURL := "/v1.25/secrets"
filters := filters.NewArgs()
filters.Add("label", "label1")
@ -54,6 +65,7 @@ func TestSecretList(t *testing.T) {
}
for _, listCase := range listCases {
client := &Client{
version: "1.25",
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)

View File

@ -4,6 +4,9 @@ import "golang.org/x/net/context"
// SecretRemove removes a Secret.
func (cli *Client) SecretRemove(ctx context.Context, id string) error {
if err := cli.NewVersionError("1.25", "secret remove"); err != nil {
return err
}
resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil)
ensureReaderClosed(resp)
return err

View File

@ -8,12 +8,23 @@ import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestSecretRemoveUnsupported(t *testing.T) {
client := &Client{
version: "1.24",
client: &http.Client{},
}
err := client.SecretRemove(context.Background(), "secret_id")
assert.EqualError(t, err, `"secret remove" requires API version 1.25, but the Docker daemon API version is 1.24`)
}
func TestSecretRemoveError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
version: "1.25",
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.SecretRemove(context.Background(), "secret_id")
@ -23,9 +34,10 @@ func TestSecretRemoveError(t *testing.T) {
}
func TestSecretRemove(t *testing.T) {
expectedURL := "/secrets/secret_id"
expectedURL := "/v1.25/secrets/secret_id"
client := &Client{
version: "1.25",
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)

View File

@ -10,6 +10,9 @@ import (
// SecretUpdate attempts to update a Secret
func (cli *Client) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error {
if err := cli.NewVersionError("1.25", "secret update"); err != nil {
return err
}
query := url.Values{}
query.Set("version", strconv.FormatUint(version.Index, 10))
resp, err := cli.post(ctx, "/secrets/"+id+"/update", query, secret, nil)

View File

@ -8,14 +8,24 @@ import (
"strings"
"testing"
"golang.org/x/net/context"
"github.com/docker/docker/api/types/swarm"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestSecretUpdateUnsupported(t *testing.T) {
client := &Client{
version: "1.24",
client: &http.Client{},
}
err := client.SecretUpdate(context.Background(), "secret_id", swarm.Version{}, swarm.SecretSpec{})
assert.EqualError(t, err, `"secret update" requires API version 1.25, but the Docker daemon API version is 1.24`)
}
func TestSecretUpdateError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
version: "1.25",
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.SecretUpdate(context.Background(), "secret_id", swarm.Version{}, swarm.SecretSpec{})
@ -25,9 +35,10 @@ func TestSecretUpdateError(t *testing.T) {
}
func TestSecretUpdate(t *testing.T) {
expectedURL := "/secrets/secret_id/update"
expectedURL := "/v1.25/secrets/secret_id/update"
client := &Client{
version: "1.25",
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)

View File

@ -68,8 +68,9 @@ func TestServiceCreateCompatiblePlatforms(t *testing.T) {
)
client := &Client{
version: "1.30",
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if strings.HasPrefix(req.URL.Path, "/services/create") {
if strings.HasPrefix(req.URL.Path, "/v1.30/services/create") {
// check if the /distribution endpoint returned correct output
err := json.NewDecoder(distributionInspectBody).Decode(&distributionInspect)
if err != nil {
@ -89,7 +90,7 @@ func TestServiceCreateCompatiblePlatforms(t *testing.T) {
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(b)),
}, nil
} else if strings.HasPrefix(req.URL.Path, "/distribution/") {
} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/") {
platforms = []v1.Platform{
{
Architecture: "amd64",
@ -146,8 +147,9 @@ func TestServiceCreateDigestPinning(t *testing.T) {
}
client := &Client{
version: "1.30",
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if strings.HasPrefix(req.URL.Path, "/services/create") {
if strings.HasPrefix(req.URL.Path, "/v1.30/services/create") {
// reset and set image received by the service create endpoint
serviceCreateImage = ""
var service swarm.ServiceSpec
@ -166,10 +168,10 @@ func TestServiceCreateDigestPinning(t *testing.T) {
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(b)),
}, nil
} else if strings.HasPrefix(req.URL.Path, "/distribution/cannotresolve") {
} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/cannotresolve") {
// unresolvable image
return nil, fmt.Errorf("cannot resolve image")
} else if strings.HasPrefix(req.URL.Path, "/distribution/") {
} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/") {
// resolvable images
b, err := json.Marshal(registrytypes.DistributionInspect{
Descriptor: v1.Descriptor{