1
0
mirror of https://github.com/moby/moby.git synced 2025-08-01 05:47:11 +03:00

client: normalize and validate empty ID / name arguments to fail early

In situations where an empty ID was passed, the client would construct an
invalid API endpoint URL, which either resulted in the "not found" handler
being hit (resulting in a "page not found" error), or even the wrong endpoint
being hit if the client follows redirects.

For example, `/containers/<empty id>/json` (inspect) redirects to `/containers/json`
(docker ps))

Given that empty IDs should never be expected (especially if they're part of
the API URL path), we can validate these and return early.

Its worth noting that a few methods already had an error in place; those
methods were related to the situation mentioned above, where (e.g.) an
"inspect" would redirect to a "list" endpoint. The existing errors, for
convenience, mimicked a "not found" error; this patch changes such errors
to an "Invalid Parameter" instead, which is more correct, but it could be
a breaking change for some edge cases where users parsed the output;

    git grep 'objectNotFoundError{'
    client/config_inspect.go:        return swarm.Config{}, nil, objectNotFoundError{object: "config", id: id}
    client/container_inspect.go:     return container.InspectResponse{}, nil, objectNotFoundError{object: "container", id: containerID}
    client/container_inspect.go:     return container.InspectResponse{}, objectNotFoundError{object: "container", id: containerID}
    client/distribution_inspect.go:  return distributionInspect, objectNotFoundError{object: "distribution", id: imageRef}
    client/image_inspect.go:         return image.InspectResponse{}, nil, objectNotFoundError{object: "image", id: imageID}
    client/network_inspect.go:       return network.Inspect{}, nil, objectNotFoundError{object: "network", id: networkID}
    client/node_inspect.go:          return swarm.Node{}, nil, objectNotFoundError{object: "node", id: nodeID}
    client/plugin_inspect.go:        return nil, nil, objectNotFoundError{object: "plugin", id: name}
    client/secret_inspect.go:        return swarm.Secret{}, nil, objectNotFoundError{object: "secret", id: id}
    client/service_inspect.go:       return swarm.Service{}, nil, objectNotFoundError{object: "service", id: serviceID}
    client/task_inspect.go:          return swarm.Task{}, nil, objectNotFoundError{object: "task", id: taskID}
    client/volume_inspect.go:        return volume.Volume{}, nil, objectNotFoundError{object: "volume", id: volumeID}

Two such errors are still left, as "ID or name" would probably be confusing,
but perhaps we can use a more generic error to include those as well (e.g.
"invalid <object> reference: value is empty");

    client/distribution_inspect.go:  return distributionInspect, objectNotFoundError{object: "distribution", id: imageRef}
    client/image_inspect.go:         return image.InspectResponse{}, nil, objectNotFoundError{object: "image", id: imageID}

Before this patch:

    docker container start ""
    Error response from daemon: page not found
    Error: failed to start containers:

    docker container start " "
    Error response from daemon: No such container:
    Error: failed to start containers:

With this patch:

    docker container start ""
    invalid container name or ID: value is empty
    Error: failed to start containers:

    docker container start " "
    invalid container name or ID: value is empty
    Error: failed to start containers:

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-02-03 10:35:25 +01:00
parent 96ded2a1ba
commit 329b2a26f3
100 changed files with 706 additions and 70 deletions

View File

@ -7,8 +7,13 @@ import (
)
// CheckpointCreate creates a checkpoint from the given container with the given name
func (cli *Client) CheckpointCreate(ctx context.Context, container string, options checkpoint.CreateOptions) error {
resp, err := cli.post(ctx, "/containers/"+container+"/checkpoints", nil, options, nil)
func (cli *Client) CheckpointCreate(ctx context.Context, containerID string, options checkpoint.CreateOptions) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/checkpoints", nil, options, nil)
ensureReaderClosed(resp)
return err
}

View File

@ -26,6 +26,14 @@ func TestCheckpointCreateError(t *testing.T) {
})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.CheckpointCreate(context.Background(), "", checkpoint.CreateOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.CheckpointCreate(context.Background(), " ", checkpoint.CreateOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestCheckpointCreate(t *testing.T) {
@ -36,7 +44,7 @@ func TestCheckpointCreate(t *testing.T) {
client := &Client{
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)
return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, req.URL)
}
if req.Method != http.MethodPost {

View File

@ -9,6 +9,11 @@ import (
// CheckpointDelete deletes the checkpoint with the given name from the given container
func (cli *Client) CheckpointDelete(ctx context.Context, containerID string, options checkpoint.DeleteOptions) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
query := url.Values{}
if options.CheckpointDir != "" {
query.Set("dir", options.CheckpointDir)

View File

@ -25,6 +25,14 @@ func TestCheckpointDeleteError(t *testing.T) {
})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.CheckpointDelete(context.Background(), "", checkpoint.DeleteOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.CheckpointDelete(context.Background(), " ", checkpoint.DeleteOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestCheckpointDelete(t *testing.T) {

View File

@ -11,8 +11,9 @@ import (
// ConfigInspectWithRaw returns the config information with raw data
func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.Config, []byte, error) {
if id == "" {
return swarm.Config{}, nil, objectNotFoundError{object: "config", id: id}
id, err := trimID("contig", id)
if err != nil {
return swarm.Config{}, nil, err
}
if err := cli.NewVersionError(ctx, "1.30", "config inspect"); err != nil {
return swarm.Config{}, nil, err

View File

@ -33,7 +33,12 @@ func TestConfigInspectWithEmptyID(t *testing.T) {
}),
}
_, _, err := client.ConfigInspectWithRaw(context.Background(), "")
assert.Check(t, is.ErrorType(err, errdefs.IsNotFound))
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, _, err = client.ConfigInspectWithRaw(context.Background(), " ")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestConfigInspectUnsupported(t *testing.T) {

View File

@ -4,6 +4,10 @@ import "context"
// ConfigRemove removes a config.
func (cli *Client) ConfigRemove(ctx context.Context, id string) error {
id, err := trimID("config", id)
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.30", "config remove"); err != nil {
return err
}

View File

@ -31,6 +31,14 @@ func TestConfigRemoveError(t *testing.T) {
err := client.ConfigRemove(context.Background(), "config_id")
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.ConfigRemove(context.Background(), "")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.ConfigRemove(context.Background(), " ")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestConfigRemove(t *testing.T) {

View File

@ -9,6 +9,10 @@ import (
// ConfigUpdate attempts to update a config
func (cli *Client) ConfigUpdate(ctx context.Context, id string, version swarm.Version, config swarm.ConfigSpec) error {
id, err := trimID("config", id)
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.30", "config update"); err != nil {
return err
}

View File

@ -32,6 +32,14 @@ func TestConfigUpdateError(t *testing.T) {
err := client.ConfigUpdate(context.Background(), "config_id", swarm.Version{}, swarm.ConfigSpec{})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.ConfigUpdate(context.Background(), "", swarm.Version{}, swarm.ConfigSpec{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.ConfigUpdate(context.Background(), " ", swarm.Version{}, swarm.ConfigSpec{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestConfigUpdate(t *testing.T) {

View File

@ -33,7 +33,12 @@ import (
//
// You can use github.com/docker/docker/pkg/stdcopy.StdCopy to demultiplex this
// stream.
func (cli *Client) ContainerAttach(ctx context.Context, container string, options container.AttachOptions) (types.HijackedResponse, error) {
func (cli *Client) ContainerAttach(ctx context.Context, containerID string, options container.AttachOptions) (types.HijackedResponse, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return types.HijackedResponse{}, err
}
query := url.Values{}
if options.Stream {
query.Set("stream", "1")
@ -54,7 +59,7 @@ func (cli *Client) ContainerAttach(ctx context.Context, container string, option
query.Set("logs", "1")
}
return cli.postHijacked(ctx, "/containers/"+container+"/attach", query, nil, http.Header{
return cli.postHijacked(ctx, "/containers/"+containerID+"/attach", query, nil, http.Header{
"Content-Type": {"text/plain"},
})
}

View File

@ -12,7 +12,12 @@ import (
)
// ContainerCommit applies changes to a container and creates a new tagged image.
func (cli *Client) ContainerCommit(ctx context.Context, container string, options container.CommitOptions) (types.IDResponse, error) {
func (cli *Client) ContainerCommit(ctx context.Context, containerID string, options container.CommitOptions) (types.IDResponse, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return types.IDResponse{}, err
}
var repository, tag string
if options.Reference != "" {
ref, err := reference.ParseNormalizedNamed(options.Reference)
@ -32,7 +37,7 @@ func (cli *Client) ContainerCommit(ctx context.Context, container string, option
}
query := url.Values{}
query.Set("container", container)
query.Set("container", containerID)
query.Set("repo", repository)
query.Set("tag", tag)
query.Set("comment", options.Comment)

View File

@ -23,6 +23,14 @@ func TestContainerCommitError(t *testing.T) {
}
_, err := client.ContainerCommit(context.Background(), "nothing", container.CommitOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
_, err = client.ContainerCommit(context.Background(), "", container.CommitOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerCommit(context.Background(), " ", container.CommitOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestContainerCommit(t *testing.T) {

View File

@ -16,11 +16,15 @@ import (
// ContainerStatPath returns stat information about a path inside the container filesystem.
func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path string) (container.PathStat, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return container.PathStat{}, err
}
query := url.Values{}
query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
urlStr := "/containers/" + containerID + "/archive"
response, err := cli.head(ctx, urlStr, query, nil)
response, err := cli.head(ctx, "/containers/"+containerID+"/archive", query, nil)
defer ensureReaderClosed(response)
if err != nil {
return container.PathStat{}, err
@ -31,6 +35,11 @@ func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path stri
// CopyToContainer copies content into the container filesystem.
// Note that `content` must be a Reader for a TAR archive
func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath string, content io.Reader, options container.CopyToContainerOptions) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
query := url.Values{}
query.Set("path", filepath.ToSlash(dstPath)) // Normalize the paths used in the API.
// Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
@ -42,9 +51,7 @@ func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath str
query.Set("copyUIDGID", "true")
}
apiPath := "/containers/" + containerID + "/archive"
response, err := cli.putRaw(ctx, apiPath, query, content, nil)
response, err := cli.putRaw(ctx, "/containers/"+containerID+"/archive", query, content, nil)
defer ensureReaderClosed(response)
if err != nil {
return err
@ -56,11 +63,15 @@ func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath str
// CopyFromContainer gets the content from the container and returns it as a Reader
// for a TAR archive to manipulate it in the host. It's up to the caller to close the reader.
func (cli *Client) CopyFromContainer(ctx context.Context, containerID, srcPath string) (io.ReadCloser, container.PathStat, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return nil, container.PathStat{}, err
}
query := make(url.Values, 1)
query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
apiPath := "/containers/" + containerID + "/archive"
response, err := cli.get(ctx, apiPath, query, nil)
response, err := cli.get(ctx, "/containers/"+containerID+"/archive", query, nil)
if err != nil {
return nil, container.PathStat{}, err
}

View File

@ -23,6 +23,14 @@ func TestContainerStatPathError(t *testing.T) {
}
_, err := client.ContainerStatPath(context.Background(), "container_id", "path")
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
_, err = client.ContainerStatPath(context.Background(), "", "path")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerStatPath(context.Background(), " ", "path")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestContainerStatPathNotFoundError(t *testing.T) {
@ -99,6 +107,14 @@ func TestCopyToContainerError(t *testing.T) {
}
err := client.CopyToContainer(context.Background(), "container_id", "path/to/file", bytes.NewReader([]byte("")), container.CopyToContainerOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.CopyToContainer(context.Background(), "", "path/to/file", bytes.NewReader([]byte("")), container.CopyToContainerOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.CopyToContainer(context.Background(), " ", "path/to/file", bytes.NewReader([]byte("")), container.CopyToContainerOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestCopyToContainerNotFoundError(t *testing.T) {
@ -173,6 +189,14 @@ func TestCopyFromContainerError(t *testing.T) {
}
_, _, err := client.CopyFromContainer(context.Background(), "container_id", "path/to/file")
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
_, _, err = client.CopyFromContainer(context.Background(), "", "path/to/file")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, _, err = client.CopyFromContainer(context.Background(), " ", "path/to/file")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestCopyFromContainerNotFoundError(t *testing.T) {

View File

@ -10,14 +10,21 @@ import (
// ContainerDiff shows differences in a container filesystem since it was started.
func (cli *Client) ContainerDiff(ctx context.Context, containerID string) ([]container.FilesystemChange, error) {
var changes []container.FilesystemChange
containerID, err := trimID("container", containerID)
if err != nil {
return nil, err
}
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/changes", url.Values{}, nil)
defer ensureReaderClosed(serverResp)
if err != nil {
return changes, err
return nil, err
}
var changes []container.FilesystemChange
err = json.NewDecoder(serverResp.body).Decode(&changes)
if err != nil {
return nil, err
}
return changes, err
}

View File

@ -22,6 +22,14 @@ func TestContainerDiffError(t *testing.T) {
}
_, err := client.ContainerDiff(context.Background(), "nothing")
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
_, err = client.ContainerDiff(context.Background(), "")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerDiff(context.Background(), " ")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestContainerDiff(t *testing.T) {

View File

@ -11,8 +11,11 @@ import (
)
// ContainerExecCreate creates a new exec configuration to run an exec process.
func (cli *Client) ContainerExecCreate(ctx context.Context, container string, options container.ExecOptions) (types.IDResponse, error) {
var response types.IDResponse
func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string, options container.ExecOptions) (types.IDResponse, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return types.IDResponse{}, err
}
// Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options.
@ -20,21 +23,23 @@ func (cli *Client) ContainerExecCreate(ctx context.Context, container string, op
// Normally, version-negotiation (if enabled) would not happen until
// the API request is made.
if err := cli.checkVersion(ctx); err != nil {
return response, err
return types.IDResponse{}, err
}
if err := cli.NewVersionError(ctx, "1.25", "env"); len(options.Env) != 0 && err != nil {
return response, err
return types.IDResponse{}, err
}
if versions.LessThan(cli.ClientVersion(), "1.42") {
options.ConsoleSize = nil
}
resp, err := cli.post(ctx, "/containers/"+container+"/exec", nil, options, nil)
resp, err := cli.post(ctx, "/containers/"+containerID+"/exec", nil, options, nil)
defer ensureReaderClosed(resp)
if err != nil {
return response, err
return types.IDResponse{}, err
}
var response types.IDResponse
err = json.NewDecoder(resp.body).Decode(&response)
return response, err
}

View File

@ -21,8 +21,17 @@ func TestContainerExecCreateError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ContainerExecCreate(context.Background(), "container_id", container.ExecOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
_, err = client.ContainerExecCreate(context.Background(), "", container.ExecOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerExecCreate(context.Background(), " ", container.ExecOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
// TestContainerExecCreateConnectionError verifies that connection errors occurring
@ -33,7 +42,7 @@ func TestContainerExecCreateConnectionError(t *testing.T) {
client, err := NewClientWithOpts(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
_, err = client.ContainerExecCreate(context.Background(), "", container.ExecOptions{})
_, err = client.ContainerExecCreate(context.Background(), "container_id", container.ExecOptions{})
assert.Check(t, is.ErrorType(err, IsErrConnectionFailed))
}

View File

@ -10,6 +10,11 @@ import (
// and returns them as an io.ReadCloser. It's up to the caller
// to close the stream.
func (cli *Client) ContainerExport(ctx context.Context, containerID string) (io.ReadCloser, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return nil, err
}
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/export", url.Values{}, nil)
if err != nil {
return nil, err

View File

@ -20,6 +20,14 @@ func TestContainerExportError(t *testing.T) {
}
_, err := client.ContainerExport(context.Background(), "nothing")
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
_, err = client.ContainerExport(context.Background(), "")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerExport(context.Background(), " ")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestContainerExport(t *testing.T) {

View File

@ -12,9 +12,11 @@ import (
// ContainerInspect returns the container information.
func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (container.InspectResponse, error) {
if containerID == "" {
return container.InspectResponse{}, objectNotFoundError{object: "container", id: containerID}
containerID, err := trimID("container", containerID)
if err != nil {
return container.InspectResponse{}, err
}
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil)
defer ensureReaderClosed(serverResp)
if err != nil {
@ -28,9 +30,11 @@ func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (co
// ContainerInspectWithRaw returns the container information and its raw representation.
func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID string, getSize bool) (container.InspectResponse, []byte, error) {
if containerID == "" {
return container.InspectResponse{}, nil, objectNotFoundError{object: "container", id: containerID}
containerID, err := trimID("container", containerID)
if err != nil {
return container.InspectResponse{}, nil, err
}
query := url.Values{}
if getSize {
query.Set("size", "1")

View File

@ -24,6 +24,14 @@ func TestContainerInspectError(t *testing.T) {
_, err := client.ContainerInspect(context.Background(), "nothing")
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
_, err = client.ContainerInspect(context.Background(), "")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerInspect(context.Background(), " ")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestContainerInspectContainerNotFound(t *testing.T) {
@ -42,7 +50,12 @@ func TestContainerInspectWithEmptyID(t *testing.T) {
}),
}
_, _, err := client.ContainerInspectWithRaw(context.Background(), "", true)
assert.Check(t, is.ErrorType(err, errdefs.IsNotFound))
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, _, err = client.ContainerInspectWithRaw(context.Background(), " ", true)
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestContainerInspect(t *testing.T) {

View File

@ -7,6 +7,11 @@ import (
// ContainerKill terminates the container process but does not remove the container from the docker host.
func (cli *Client) ContainerKill(ctx context.Context, containerID, signal string) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
query := url.Values{}
if signal != "" {
query.Set("signal", signal)

View File

@ -20,6 +20,14 @@ func TestContainerKillError(t *testing.T) {
}
err := client.ContainerKill(context.Background(), "nothing", "SIGKILL")
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.ContainerKill(context.Background(), "", "")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.ContainerKill(context.Background(), " ", "")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestContainerKill(t *testing.T) {

View File

@ -33,7 +33,12 @@ import (
//
// You can use github.com/docker/docker/pkg/stdcopy.StdCopy to demultiplex this
// stream.
func (cli *Client) ContainerLogs(ctx context.Context, container string, options container.LogsOptions) (io.ReadCloser, error) {
func (cli *Client) ContainerLogs(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return nil, err
}
query := url.Values{}
if options.ShowStdout {
query.Set("stdout", "1")
@ -72,7 +77,7 @@ func (cli *Client) ContainerLogs(ctx context.Context, container string, options
}
query.Set("tail", options.Tail)
resp, err := cli.get(ctx, "/containers/"+container+"/logs", query, nil)
resp, err := cli.get(ctx, "/containers/"+containerID+"/logs", query, nil)
if err != nil {
return nil, err
}

View File

@ -24,6 +24,14 @@ func TestContainerLogsNotFoundError(t *testing.T) {
}
_, err := client.ContainerLogs(context.Background(), "container_id", container.LogsOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsNotFound))
_, err = client.ContainerLogs(context.Background(), "", container.LogsOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerLogs(context.Background(), " ", container.LogsOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestContainerLogsError(t *testing.T) {

View File

@ -4,6 +4,11 @@ import "context"
// ContainerPause pauses the main process of a given container without terminating it.
func (cli *Client) ContainerPause(ctx context.Context, containerID string) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/pause", nil, nil, nil)
ensureReaderClosed(resp)
return err

View File

@ -9,6 +9,11 @@ import (
// ContainerRemove kills and removes a container from the docker host.
func (cli *Client) ContainerRemove(ctx context.Context, containerID string, options container.RemoveOptions) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
query := url.Values{}
if options.RemoveVolumes {
query.Set("v", "1")

View File

@ -21,6 +21,14 @@ func TestContainerRemoveError(t *testing.T) {
}
err := client.ContainerRemove(context.Background(), "container_id", container.RemoveOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.ContainerRemove(context.Background(), "", container.RemoveOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.ContainerRemove(context.Background(), " ", container.RemoveOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestContainerRemoveNotFoundError(t *testing.T) {

View File

@ -7,6 +7,11 @@ import (
// ContainerRename changes the name of a given container.
func (cli *Client) ContainerRename(ctx context.Context, containerID, newContainerName string) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
query := url.Values{}
query.Set("name", newContainerName)
resp, err := cli.post(ctx, "/containers/"+containerID+"/rename", query, nil, nil)

View File

@ -20,6 +20,14 @@ func TestContainerRenameError(t *testing.T) {
}
err := client.ContainerRename(context.Background(), "nothing", "newNothing")
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.ContainerRename(context.Background(), "", "newNothing")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.ContainerRename(context.Background(), " ", "newNothing")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestContainerRename(t *testing.T) {

View File

@ -10,11 +10,19 @@ import (
// ContainerResize changes the size of the tty for a container.
func (cli *Client) ContainerResize(ctx context.Context, containerID string, options container.ResizeOptions) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
return cli.resize(ctx, "/containers/"+containerID, options.Height, options.Width)
}
// ContainerExecResize changes the size of the tty for an exec process running inside a container.
func (cli *Client) ContainerExecResize(ctx context.Context, execID string, options container.ResizeOptions) error {
execID, err := trimID("exec", execID)
if err != nil {
return err
}
return cli.resize(ctx, "/exec/"+execID, options.Height, options.Width)
}

View File

@ -20,6 +20,14 @@ func TestContainerResizeError(t *testing.T) {
}
err := client.ContainerResize(context.Background(), "container_id", container.ResizeOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.ContainerResize(context.Background(), "", container.ResizeOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.ContainerResize(context.Background(), " ", container.ResizeOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestContainerExecResizeError(t *testing.T) {

View File

@ -13,6 +13,11 @@ import (
// It makes the daemon wait for the container to be up again for
// a specific amount of time, given the timeout.
func (cli *Client) ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
query := url.Values{}
if options.Timeout != nil {
query.Set("t", strconv.Itoa(*options.Timeout))

View File

@ -21,6 +21,14 @@ func TestContainerRestartError(t *testing.T) {
}
err := client.ContainerRestart(context.Background(), "nothing", container.StopOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.ContainerRestart(context.Background(), "", container.StopOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.ContainerRestart(context.Background(), " ", container.StopOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
// TestContainerRestartConnectionError verifies that connection errors occurring

View File

@ -9,6 +9,11 @@ import (
// ContainerStart sends a request to the docker daemon to start a container.
func (cli *Client) ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
query := url.Values{}
if len(options.CheckpointID) != 0 {
query.Set("checkpoint", options.CheckpointID)

View File

@ -22,6 +22,14 @@ func TestContainerStartError(t *testing.T) {
}
err := client.ContainerStart(context.Background(), "nothing", container.StartOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.ContainerStart(context.Background(), "", container.StartOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.ContainerStart(context.Background(), " ", container.StartOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestContainerStart(t *testing.T) {

View File

@ -10,6 +10,11 @@ import (
// ContainerStats returns near realtime stats for a given container.
// It's up to the caller to close the io.ReadCloser returned.
func (cli *Client) ContainerStats(ctx context.Context, containerID string, stream bool) (container.StatsResponseReader, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return container.StatsResponseReader{}, err
}
query := url.Values{}
query.Set("stream", "0")
if stream {
@ -30,6 +35,11 @@ func (cli *Client) ContainerStats(ctx context.Context, containerID string, strea
// ContainerStatsOneShot gets a single stat entry from a container.
// It differs from `ContainerStats` in that the API should not wait to prime the stats
func (cli *Client) ContainerStatsOneShot(ctx context.Context, containerID string) (container.StatsResponseReader, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return container.StatsResponseReader{}, err
}
query := url.Values{}
query.Set("stream", "0")
query.Set("one-shot", "1")

View File

@ -20,6 +20,14 @@ func TestContainerStatsError(t *testing.T) {
}
_, err := client.ContainerStats(context.Background(), "nothing", false)
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
_, err = client.ContainerStats(context.Background(), "", false)
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerStats(context.Background(), " ", false)
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestContainerStats(t *testing.T) {

View File

@ -17,6 +17,11 @@ import (
// otherwise the engine default. A negative timeout value can be specified,
// meaning no timeout, i.e. no forceful termination is performed.
func (cli *Client) ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
query := url.Values{}
if options.Timeout != nil {
query.Set("t", strconv.Itoa(*options.Timeout))

View File

@ -19,8 +19,16 @@ func TestContainerStopError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.ContainerStop(context.Background(), "nothing", container.StopOptions{})
err := client.ContainerStop(context.Background(), "container_id", container.StopOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.ContainerStop(context.Background(), "", container.StopOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.ContainerStop(context.Background(), " ", container.StopOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
// TestContainerStopConnectionError verifies that connection errors occurring
@ -31,7 +39,7 @@ func TestContainerStopConnectionError(t *testing.T) {
client, err := NewClientWithOpts(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
err = client.ContainerStop(context.Background(), "nothing", container.StopOptions{})
err = client.ContainerStop(context.Background(), "container_id", container.StopOptions{})
assert.Check(t, is.ErrorType(err, IsErrConnectionFailed))
}
@ -40,7 +48,7 @@ func TestContainerStop(t *testing.T) {
client := &Client{
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)
return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, req.URL)
}
s := req.URL.Query().Get("signal")
if s != "SIGKILL" {

View File

@ -11,7 +11,11 @@ import (
// ContainerTop shows process information from within a container.
func (cli *Client) ContainerTop(ctx context.Context, containerID string, arguments []string) (container.ContainerTopOKBody, error) {
var response container.ContainerTopOKBody
containerID, err := trimID("container", containerID)
if err != nil {
return container.ContainerTopOKBody{}, err
}
query := url.Values{}
if len(arguments) > 0 {
query.Set("ps_args", strings.Join(arguments, " "))
@ -20,9 +24,10 @@ func (cli *Client) ContainerTop(ctx context.Context, containerID string, argumen
resp, err := cli.get(ctx, "/containers/"+containerID+"/top", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return response, err
return container.ContainerTopOKBody{}, err
}
var response container.ContainerTopOKBody
err = json.NewDecoder(resp.body).Decode(&response)
return response, err
}

View File

@ -23,6 +23,14 @@ func TestContainerTopError(t *testing.T) {
}
_, err := client.ContainerTop(context.Background(), "nothing", []string{})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
_, err = client.ContainerTop(context.Background(), "", []string{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerTop(context.Background(), " ", []string{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestContainerTop(t *testing.T) {

View File

@ -4,6 +4,11 @@ import "context"
// ContainerUnpause resumes the process execution within a container
func (cli *Client) ContainerUnpause(ctx context.Context, containerID string) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/unpause", nil, nil, nil)
ensureReaderClosed(resp)
return err

View File

@ -20,6 +20,14 @@ func TestContainerUnpauseError(t *testing.T) {
}
err := client.ContainerUnpause(context.Background(), "nothing")
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.ContainerUnpause(context.Background(), "")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.ContainerUnpause(context.Background(), " ")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestContainerUnpause(t *testing.T) {

View File

@ -9,13 +9,18 @@ import (
// ContainerUpdate updates the resources of a container.
func (cli *Client) ContainerUpdate(ctx context.Context, containerID string, updateConfig container.UpdateConfig) (container.ContainerUpdateOKBody, error) {
var response container.ContainerUpdateOKBody
containerID, err := trimID("container", containerID)
if err != nil {
return container.ContainerUpdateOKBody{}, err
}
serverResp, err := cli.post(ctx, "/containers/"+containerID+"/update", nil, updateConfig, nil)
defer ensureReaderClosed(serverResp)
if err != nil {
return response, err
return container.ContainerUpdateOKBody{}, err
}
var response container.ContainerUpdateOKBody
err = json.NewDecoder(serverResp.body).Decode(&response)
return response, err
}

View File

@ -22,6 +22,14 @@ func TestContainerUpdateError(t *testing.T) {
}
_, err := client.ContainerUpdate(context.Background(), "nothing", container.UpdateConfig{})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
_, err = client.ContainerUpdate(context.Background(), "", container.UpdateConfig{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerUpdate(context.Background(), " ", container.UpdateConfig{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestContainerUpdate(t *testing.T) {

View File

@ -33,6 +33,12 @@ func (cli *Client) ContainerWait(ctx context.Context, containerID string, condit
resultC := make(chan container.WaitResponse)
errC := make(chan error, 1)
containerID, err := trimID("container", containerID)
if err != nil {
errC <- err
return resultC, errC
}
// Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options.
//

View File

@ -8,6 +8,16 @@ import (
// NetworkConnect connects a container to an existent network in the docker host.
func (cli *Client) NetworkConnect(ctx context.Context, networkID, containerID string, config *network.EndpointSettings) error {
networkID, err := trimID("network", networkID)
if err != nil {
return err
}
containerID, err = trimID("container", containerID)
if err != nil {
return err
}
nc := network.ConnectOptions{
Container: containerID,
EndpointConfig: config,

View File

@ -23,6 +23,15 @@ func TestNetworkConnectError(t *testing.T) {
err := client.NetworkConnect(context.Background(), "network_id", "container_id", nil)
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
// Empty network ID or container ID
err = client.NetworkConnect(context.Background(), "", "container_id", nil)
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.NetworkConnect(context.Background(), "network_id", "", nil)
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestNetworkConnectEmptyNilEndpointSettings(t *testing.T) {

View File

@ -8,6 +8,16 @@ import (
// NetworkDisconnect disconnects a container from an existent network in the docker host.
func (cli *Client) NetworkDisconnect(ctx context.Context, networkID, containerID string, force bool) error {
networkID, err := trimID("network", networkID)
if err != nil {
return err
}
containerID, err = trimID("container", containerID)
if err != nil {
return err
}
nd := network.DisconnectOptions{
Container: containerID,
Force: force,

View File

@ -23,6 +23,15 @@ func TestNetworkDisconnectError(t *testing.T) {
err := client.NetworkDisconnect(context.Background(), "network_id", "container_id", false)
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
// Empty network ID or container ID
err = client.NetworkDisconnect(context.Background(), "", "container_id", false)
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.NetworkDisconnect(context.Background(), "network_id", "", false)
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestNetworkDisconnect(t *testing.T) {

View File

@ -18,8 +18,9 @@ func (cli *Client) NetworkInspect(ctx context.Context, networkID string, options
// NetworkInspectWithRaw returns the information for a specific network configured in the docker host and its raw representation.
func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string, options network.InspectOptions) (network.Inspect, []byte, error) {
if networkID == "" {
return network.Inspect{}, nil, objectNotFoundError{object: "network", id: networkID}
networkID, err := trimID("network", networkID)
if err != nil {
return network.Inspect{}, nil, err
}
query := url.Values{}
if options.Verbose {

View File

@ -69,7 +69,12 @@ func TestNetworkInspect(t *testing.T) {
t.Run("empty ID", func(t *testing.T) {
// verify that the client does not create a request if the network-ID/name is empty.
_, err := client.NetworkInspect(context.Background(), "", network.InspectOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsNotFound))
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.NetworkInspect(context.Background(), " ", network.InspectOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
})
t.Run("no options", func(t *testing.T) {
r, err := client.NetworkInspect(context.Background(), "network_id", network.InspectOptions{})

View File

@ -4,6 +4,10 @@ import "context"
// NetworkRemove removes an existent network from the docker host.
func (cli *Client) NetworkRemove(ctx context.Context, networkID string) error {
networkID, err := trimID("network", networkID)
if err != nil {
return err
}
resp, err := cli.delete(ctx, "/networks/"+networkID, nil, nil)
defer ensureReaderClosed(resp)
return err

View File

@ -21,6 +21,14 @@ func TestNetworkRemoveError(t *testing.T) {
err := client.NetworkRemove(context.Background(), "network_id")
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.NetworkRemove(context.Background(), "")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.NetworkRemove(context.Background(), " ")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestNetworkRemove(t *testing.T) {

View File

@ -11,8 +11,9 @@ import (
// NodeInspectWithRaw returns the node information.
func (cli *Client) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) {
if nodeID == "" {
return swarm.Node{}, nil, objectNotFoundError{object: "node", id: nodeID}
nodeID, err := trimID("node", nodeID)
if err != nil {
return swarm.Node{}, nil, err
}
serverResp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil)
defer ensureReaderClosed(serverResp)

View File

@ -42,7 +42,12 @@ func TestNodeInspectWithEmptyID(t *testing.T) {
}),
}
_, _, err := client.NodeInspectWithRaw(context.Background(), "")
assert.Check(t, is.ErrorType(err, errdefs.IsNotFound))
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, _, err = client.NodeInspectWithRaw(context.Background(), " ")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestNodeInspect(t *testing.T) {

View File

@ -9,6 +9,11 @@ import (
// NodeRemove removes a Node.
func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options types.NodeRemoveOptions) error {
nodeID, err := trimID("node", nodeID)
if err != nil {
return err
}
query := url.Values{}
if options.Force {
query.Set("force", "1")

View File

@ -22,6 +22,14 @@ func TestNodeRemoveError(t *testing.T) {
err := client.NodeRemove(context.Background(), "node_id", types.NodeRemoveOptions{Force: false})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.NodeRemove(context.Background(), "", types.NodeRemoveOptions{Force: false})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.NodeRemove(context.Background(), " ", types.NodeRemoveOptions{Force: false})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestNodeRemove(t *testing.T) {

View File

@ -9,6 +9,11 @@ import (
// NodeUpdate updates a Node.
func (cli *Client) NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error {
nodeID, err := trimID("node", nodeID)
if err != nil {
return err
}
query := url.Values{}
query.Set("version", version.String())
resp, err := cli.post(ctx, "/nodes/"+nodeID+"/update", query, node, nil)

View File

@ -22,6 +22,14 @@ func TestNodeUpdateError(t *testing.T) {
err := client.NodeUpdate(context.Background(), "node_id", swarm.Version{}, swarm.NodeSpec{})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.NodeUpdate(context.Background(), "", swarm.Version{}, swarm.NodeSpec{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.NodeUpdate(context.Background(), " ", swarm.Version{}, swarm.NodeSpec{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestNodeUpdate(t *testing.T) {

View File

@ -9,6 +9,10 @@ import (
// PluginDisable disables a plugin
func (cli *Client) PluginDisable(ctx context.Context, name string, options types.PluginDisableOptions) error {
name, err := trimID("plugin", name)
if err != nil {
return err
}
query := url.Values{}
if options.Force {
query.Set("force", "1")

View File

@ -22,6 +22,14 @@ func TestPluginDisableError(t *testing.T) {
err := client.PluginDisable(context.Background(), "plugin_name", types.PluginDisableOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.PluginDisable(context.Background(), "", types.PluginDisableOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.PluginDisable(context.Background(), " ", types.PluginDisableOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestPluginDisable(t *testing.T) {

View File

@ -10,6 +10,10 @@ import (
// PluginEnable enables a plugin
func (cli *Client) PluginEnable(ctx context.Context, name string, options types.PluginEnableOptions) error {
name, err := trimID("plugin", name)
if err != nil {
return err
}
query := url.Values{}
query.Set("timeout", strconv.Itoa(options.Timeout))

View File

@ -22,6 +22,14 @@ func TestPluginEnableError(t *testing.T) {
err := client.PluginEnable(context.Background(), "plugin_name", types.PluginEnableOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.PluginEnable(context.Background(), "", types.PluginEnableOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.PluginEnable(context.Background(), " ", types.PluginEnableOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestPluginEnable(t *testing.T) {

View File

@ -11,8 +11,9 @@ import (
// PluginInspectWithRaw inspects an existing plugin
func (cli *Client) PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) {
if name == "" {
return nil, nil, objectNotFoundError{object: "plugin", id: name}
name, err := trimID("plugin", name)
if err != nil {
return nil, nil, err
}
resp, err := cli.get(ctx, "/plugins/"+name+"/json", nil, nil)
defer ensureReaderClosed(resp)

View File

@ -33,7 +33,12 @@ func TestPluginInspectWithEmptyID(t *testing.T) {
}),
}
_, _, err := client.PluginInspectWithRaw(context.Background(), "")
assert.Check(t, is.ErrorType(err, errdefs.IsNotFound))
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, _, err = client.PluginInspectWithRaw(context.Background(), " ")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestPluginInspect(t *testing.T) {

View File

@ -10,6 +10,10 @@ import (
// PluginPush pushes a plugin to a registry
func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error) {
name, err := trimID("plugin", name)
if err != nil {
return nil, err
}
resp, err := cli.post(ctx, "/plugins/"+name+"/push", nil, nil, http.Header{
registry.AuthHeader: {registryAuth},
})

View File

@ -22,6 +22,14 @@ func TestPluginPushError(t *testing.T) {
_, err := client.PluginPush(context.Background(), "plugin_name", "")
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
_, err = client.PluginPush(context.Background(), "", "")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.PluginPush(context.Background(), " ", "")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestPluginPush(t *testing.T) {

View File

@ -9,6 +9,11 @@ import (
// PluginRemove removes a plugin
func (cli *Client) PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error {
name, err := trimID("plugin", name)
if err != nil {
return err
}
query := url.Values{}
if options.Force {
query.Set("force", "1")

View File

@ -22,6 +22,14 @@ func TestPluginRemoveError(t *testing.T) {
err := client.PluginRemove(context.Background(), "plugin_name", types.PluginRemoveOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.PluginRemove(context.Background(), "", types.PluginRemoveOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.PluginRemove(context.Background(), " ", types.PluginRemoveOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestPluginRemove(t *testing.T) {

View File

@ -6,6 +6,11 @@ import (
// PluginSet modifies settings for an existing plugin
func (cli *Client) PluginSet(ctx context.Context, name string, args []string) error {
name, err := trimID("plugin", name)
if err != nil {
return err
}
resp, err := cli.post(ctx, "/plugins/"+name+"/set", nil, args, nil)
ensureReaderClosed(resp)
return err

View File

@ -21,6 +21,14 @@ func TestPluginSetError(t *testing.T) {
err := client.PluginSet(context.Background(), "plugin_name", []string{})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.PluginSet(context.Background(), "", []string{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.PluginSet(context.Background(), " ", []string{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestPluginSet(t *testing.T) {

View File

@ -13,7 +13,12 @@ import (
)
// PluginUpgrade upgrades a plugin
func (cli *Client) PluginUpgrade(ctx context.Context, name string, options types.PluginInstallOptions) (rc io.ReadCloser, err error) {
func (cli *Client) PluginUpgrade(ctx context.Context, name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
name, err := trimID("plugin", name)
if err != nil {
return nil, err
}
if err := cli.NewVersionError(ctx, "1.26", "plugin upgrade"); err != nil {
return nil, err
}

View File

@ -11,11 +11,12 @@ 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(ctx, "1.25", "secret inspect"); err != nil {
id, err := trimID("secret", id)
if err != nil {
return swarm.Secret{}, nil, err
}
if id == "" {
return swarm.Secret{}, nil, objectNotFoundError{object: "secret", id: id}
if err := cli.NewVersionError(ctx, "1.25", "secret inspect"); err != nil {
return swarm.Secret{}, nil, err
}
resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
defer ensureReaderClosed(resp)

View File

@ -53,7 +53,12 @@ func TestSecretInspectWithEmptyID(t *testing.T) {
}),
}
_, _, err := client.SecretInspectWithRaw(context.Background(), "")
assert.Check(t, is.ErrorType(err, errdefs.IsNotFound))
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, _, err = client.SecretInspectWithRaw(context.Background(), " ")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestSecretInspect(t *testing.T) {

View File

@ -4,6 +4,10 @@ import "context"
// SecretRemove removes a secret.
func (cli *Client) SecretRemove(ctx context.Context, id string) error {
id, err := trimID("secret", id)
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.25", "secret remove"); err != nil {
return err
}

View File

@ -31,6 +31,14 @@ func TestSecretRemoveError(t *testing.T) {
err := client.SecretRemove(context.Background(), "secret_id")
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.SecretRemove(context.Background(), "")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.SecretRemove(context.Background(), " ")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestSecretRemove(t *testing.T) {

View File

@ -9,6 +9,10 @@ import (
// SecretUpdate attempts to update a secret.
func (cli *Client) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error {
id, err := trimID("secret", id)
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.25", "secret update"); err != nil {
return err
}

View File

@ -32,6 +32,14 @@ func TestSecretUpdateError(t *testing.T) {
err := client.SecretUpdate(context.Background(), "secret_id", swarm.Version{}, swarm.SecretSpec{})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.SecretUpdate(context.Background(), "", swarm.Version{}, swarm.SecretSpec{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.SecretUpdate(context.Background(), " ", swarm.Version{}, swarm.SecretSpec{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestSecretUpdate(t *testing.T) {
@ -41,7 +49,7 @@ func TestSecretUpdate(t *testing.T) {
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)
return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, req.URL)
}
if req.Method != http.MethodPost {
return nil, fmt.Errorf("expected POST method, got %s", req.Method)

View File

@ -14,9 +14,11 @@ import (
// ServiceInspectWithRaw returns the service information and the raw data.
func (cli *Client) ServiceInspectWithRaw(ctx context.Context, serviceID string, opts types.ServiceInspectOptions) (swarm.Service, []byte, error) {
if serviceID == "" {
return swarm.Service{}, nil, objectNotFoundError{object: "service", id: serviceID}
serviceID, err := trimID("service", serviceID)
if err != nil {
return swarm.Service{}, nil, err
}
query := url.Values{}
query.Set("insertDefaults", fmt.Sprintf("%v", opts.InsertDefaults))
serverResp, err := cli.get(ctx, "/services/"+serviceID, query, nil)

View File

@ -43,7 +43,12 @@ func TestServiceInspectWithEmptyID(t *testing.T) {
}),
}
_, _, err := client.ServiceInspectWithRaw(context.Background(), "", types.ServiceInspectOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsNotFound))
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, _, err = client.ServiceInspectWithRaw(context.Background(), " ", types.ServiceInspectOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestServiceInspect(t *testing.T) {

View File

@ -14,6 +14,11 @@ import (
// ServiceLogs returns the logs generated by a service in an io.ReadCloser.
// It's up to the caller to close the stream.
func (cli *Client) ServiceLogs(ctx context.Context, serviceID string, options container.LogsOptions) (io.ReadCloser, error) {
serviceID, err := trimID("service", serviceID)
if err != nil {
return nil, err
}
query := url.Values{}
if options.ShowStdout {
query.Set("stdout", "1")

View File

@ -29,6 +29,14 @@ func TestServiceLogsError(t *testing.T) {
Since: "2006-01-02TZ",
})
assert.Check(t, is.ErrorContains(err, `parsing time "2006-01-02TZ"`))
_, err = client.ServiceLogs(context.Background(), "", container.LogsOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ServiceLogs(context.Background(), " ", container.LogsOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestServiceLogs(t *testing.T) {

View File

@ -4,6 +4,11 @@ import "context"
// ServiceRemove kills and removes a service.
func (cli *Client) ServiceRemove(ctx context.Context, serviceID string) error {
serviceID, err := trimID("service", serviceID)
if err != nil {
return err
}
resp, err := cli.delete(ctx, "/services/"+serviceID, nil, nil)
defer ensureReaderClosed(resp)
return err

View File

@ -21,6 +21,14 @@ func TestServiceRemoveError(t *testing.T) {
err := client.ServiceRemove(context.Background(), "service_id")
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.ServiceRemove(context.Background(), "")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.ServiceRemove(context.Background(), " ")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestServiceRemoveNotFoundError(t *testing.T) {

View File

@ -16,7 +16,10 @@ import (
// It should be the value as set *before* the update. You can find this value in the Meta field
// of swarm.Service, which can be found using ServiceInspectWithRaw.
func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
response := swarm.ServiceUpdateResponse{}
serviceID, err := trimID("service", serviceID)
if err != nil {
return swarm.ServiceUpdateResponse{}, err
}
// Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options.
@ -24,7 +27,7 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
// Normally, version-negotiation (if enabled) would not happen until
// the API request is made.
if err := cli.checkVersion(ctx); err != nil {
return response, err
return swarm.ServiceUpdateResponse{}, err
}
query := url.Values{}
@ -39,7 +42,7 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
query.Set("version", version.String())
if err := validateServiceSpec(service); err != nil {
return response, err
return swarm.ServiceUpdateResponse{}, err
}
// ensure that the image is tagged
@ -74,9 +77,10 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers)
defer ensureReaderClosed(resp)
if err != nil {
return response, err
return swarm.ServiceUpdateResponse{}, err
}
response := swarm.ServiceUpdateResponse{}
err = json.NewDecoder(resp.body).Decode(&response)
if resolveWarning != "" {
response.Warnings = append(response.Warnings, resolveWarning)

View File

@ -23,6 +23,14 @@ func TestServiceUpdateError(t *testing.T) {
_, err := client.ServiceUpdate(context.Background(), "service_id", swarm.Version{}, swarm.ServiceSpec{}, types.ServiceUpdateOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
_, err = client.ServiceUpdate(context.Background(), "", swarm.Version{}, swarm.ServiceSpec{}, types.ServiceUpdateOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ServiceUpdate(context.Background(), " ", swarm.Version{}, swarm.ServiceSpec{}, types.ServiceUpdateOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
// TestServiceUpdateConnectionError verifies that connection errors occurring

View File

@ -11,9 +11,11 @@ import (
// TaskInspectWithRaw returns the task information and its raw representation.
func (cli *Client) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) {
if taskID == "" {
return swarm.Task{}, nil, objectNotFoundError{object: "task", id: taskID}
taskID, err := trimID("task", taskID)
if err != nil {
return swarm.Task{}, nil, err
}
serverResp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil)
defer ensureReaderClosed(serverResp)
if err != nil {

View File

@ -33,7 +33,12 @@ func TestTaskInspectWithEmptyID(t *testing.T) {
}),
}
_, _, err := client.TaskInspectWithRaw(context.Background(), "")
assert.Check(t, is.ErrorType(err, errdefs.IsNotFound))
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, _, err = client.TaskInspectWithRaw(context.Background(), " ")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestTaskInspect(t *testing.T) {

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/url"
"strings"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/errdefs"
@ -13,6 +14,23 @@ import (
var headerRegexp = lazyregexp.New(`\ADocker/.+\s\((.+)\)\z`)
type emptyIDError string
func (e emptyIDError) InvalidParameter() {}
func (e emptyIDError) Error() string {
return "invalid " + string(e) + " name or ID: value is empty"
}
// trimID trims the given object-ID / name, returning an error if it's empty.
func trimID(objType, id string) (string, error) {
id = strings.TrimSpace(id)
if len(id) == 0 {
return "", emptyIDError(objType)
}
return id, nil
}
// getDockerOS returns the operating system based on the server header from the daemon.
func getDockerOS(serverHeader string) string {
var osType string

View File

@ -17,8 +17,9 @@ func (cli *Client) VolumeInspect(ctx context.Context, volumeID string) (volume.V
// VolumeInspectWithRaw returns the information about a specific volume in the docker host and its raw representation
func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (volume.Volume, []byte, error) {
if volumeID == "" {
return volume.Volume{}, nil, objectNotFoundError{object: "volume", id: volumeID}
volumeID, err := trimID("volume", volumeID)
if err != nil {
return volume.Volume{}, nil, err
}
var vol volume.Volume

View File

@ -42,7 +42,12 @@ func TestVolumeInspectWithEmptyID(t *testing.T) {
}),
}
_, _, err := client.VolumeInspectWithRaw(context.Background(), "")
assert.Check(t, is.ErrorType(err, errdefs.IsNotFound))
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, _, err = client.VolumeInspectWithRaw(context.Background(), " ")
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestVolumeInspect(t *testing.T) {

View File

@ -9,6 +9,11 @@ import (
// VolumeRemove removes a volume from the docker host.
func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool) error {
volumeID, err := trimID("volume", volumeID)
if err != nil {
return err
}
query := url.Values{}
if force {
// Make sure we negotiated (if the client is configured to do so),

View File

@ -21,6 +21,14 @@ func TestVolumeRemoveError(t *testing.T) {
err := client.VolumeRemove(context.Background(), "volume_id", false)
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.VolumeRemove(context.Background(), "", false)
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.VolumeRemove(context.Background(), " ", false)
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
// TestVolumeRemoveConnectionError verifies that connection errors occurring

View File

@ -11,6 +11,10 @@ import (
// VolumeUpdate updates a volume. This only works for Cluster Volumes, and
// only some fields can be updated.
func (cli *Client) VolumeUpdate(ctx context.Context, volumeID string, version swarm.Version, options volume.UpdateOptions) error {
volumeID, err := trimID("volume", volumeID)
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.42", "volume update"); err != nil {
return err
}

View File

@ -21,8 +21,16 @@ func TestVolumeUpdateError(t *testing.T) {
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.VolumeUpdate(context.Background(), "", swarm.Version{}, volumetypes.UpdateOptions{})
err := client.VolumeUpdate(context.Background(), "volume", swarm.Version{}, volumetypes.UpdateOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
err = client.VolumeUpdate(context.Background(), "", swarm.Version{}, volumetypes.UpdateOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.VolumeUpdate(context.Background(), " ", swarm.Version{}, volumetypes.UpdateOptions{})
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestVolumeUpdate(t *testing.T) {

View File

@ -1391,7 +1391,8 @@ func (s *DockerAPISuite) TestContainerAPIDeleteWithEmptyName(c *testing.T) {
defer apiClient.Close()
err = apiClient.ContainerRemove(testutil.GetContext(c), "", container.RemoveOptions{})
assert.Check(c, errdefs.IsNotFound(err))
assert.Check(c, is.ErrorType(err, errdefs.IsInvalidParameter))
assert.Check(c, is.ErrorContains(err, "value is empty"))
}
func (s *DockerAPISuite) TestContainerAPIStatsWithNetworkDisabled(c *testing.T) {