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:
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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"},
|
||||
})
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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")
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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")
|
||||
|
@ -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) {
|
||||
|
@ -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))
|
||||
|
@ -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" {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
//
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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{})
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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")
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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")
|
||||
|
@ -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) {
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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},
|
||||
})
|
||||
|
@ -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) {
|
||||
|
@ -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")
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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")
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
Reference in New Issue
Block a user