From 3e6bbefd268f51755be5af0644995297a71a05d7 Mon Sep 17 00:00:00 2001 From: Emil Davtyan Date: Tue, 30 Jan 2018 13:35:22 +0100 Subject: [PATCH] Produce errors when empty ids are passed into inspect calls. If a blank nodeID was previously passed in it resulted in a node list request. The response would then fail to umarshal into a `Node` type returning a JSON error. This adds an extra validation to all inspect calls to check that the ID that is required is provided and if not return an error. Signed-off-by: Emil Davtyan --- client/config_inspect.go | 3 +++ client/config_inspect_test.go | 24 ++++++++++++++++++++++++ client/container_inspect.go | 6 ++++++ client/container_inspect_test.go | 13 +++++++++++++ client/distribution_inspect.go | 3 +++ client/distribution_inspect_test.go | 13 +++++++++++++ client/image_inspect.go | 3 +++ client/image_inspect_test.go | 13 +++++++++++++ client/network_inspect.go | 3 +++ client/network_inspect_test.go | 13 +++++++++++++ client/node_inspect.go | 3 +++ client/node_inspect_test.go | 13 +++++++++++++ client/plugin_inspect.go | 3 +++ client/plugin_inspect_test.go | 13 +++++++++++++ client/secret_inspect.go | 3 +++ client/secret_inspect_test.go | 13 +++++++++++++ client/service_inspect.go | 3 +++ client/service_inspect_test.go | 13 +++++++++++++ client/task_inspect.go | 3 +++ client/task_inspect_test.go | 13 +++++++++++++ client/volume_inspect.go | 6 +----- client/volume_inspect_test.go | 16 ++++++---------- 22 files changed, 181 insertions(+), 15 deletions(-) diff --git a/client/config_inspect.go b/client/config_inspect.go index b44d6fdd7e..a03b46d411 100644 --- a/client/config_inspect.go +++ b/client/config_inspect.go @@ -11,6 +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} + } if err := cli.NewVersionError("1.30", "config inspect"); err != nil { return swarm.Config{}, nil, err } diff --git a/client/config_inspect_test.go b/client/config_inspect_test.go index fab0c286f5..5a188d056c 100644 --- a/client/config_inspect_test.go +++ b/client/config_inspect_test.go @@ -10,10 +10,34 @@ import ( "testing" "github.com/docker/docker/api/types/swarm" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "golang.org/x/net/context" ) +func TestConfigInspectNotFound(t *testing.T) { + client := &Client{ + client: newMockClient(errorMock(http.StatusNotFound, "Server error")), + } + + _, _, err := client.ConfigInspectWithRaw(context.Background(), "unknown") + if err == nil || !IsErrNotFound(err) { + t.Fatalf("expected a NotFoundError error, got %v", err) + } +} + +func TestConfigInspectWithEmptyID(t *testing.T) { + client := &Client{ + client: newMockClient(func(req *http.Request) (*http.Response, error) { + return nil, errors.New("should not make request") + }), + } + _, _, err := client.ConfigInspectWithRaw(context.Background(), "") + if !IsErrNotFound(err) { + t.Fatalf("Expected NotFoundError, got %v", err) + } +} + func TestConfigInspectUnsupported(t *testing.T) { client := &Client{ version: "1.29", diff --git a/client/container_inspect.go b/client/container_inspect.go index a15db14be8..2da90a43df 100644 --- a/client/container_inspect.go +++ b/client/container_inspect.go @@ -12,6 +12,9 @@ import ( // ContainerInspect returns the container information. func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) { + if containerID == "" { + return types.ContainerJSON{}, objectNotFoundError{object: "container", id: containerID} + } serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil) if err != nil { return types.ContainerJSON{}, wrapResponseError(err, serverResp, "container", containerID) @@ -25,6 +28,9 @@ func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (ty // ContainerInspectWithRaw returns the container information and its raw representation. func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID string, getSize bool) (types.ContainerJSON, []byte, error) { + if containerID == "" { + return types.ContainerJSON{}, nil, objectNotFoundError{object: "container", id: containerID} + } query := url.Values{} if getSize { query.Set("size", "1") diff --git a/client/container_inspect_test.go b/client/container_inspect_test.go index 37259e40bd..9915a89239 100644 --- a/client/container_inspect_test.go +++ b/client/container_inspect_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/docker/docker/api/types" + "github.com/pkg/errors" "golang.org/x/net/context" ) @@ -35,6 +36,18 @@ func TestContainerInspectContainerNotFound(t *testing.T) { } } +func TestContainerInspectWithEmptyID(t *testing.T) { + client := &Client{ + client: newMockClient(func(req *http.Request) (*http.Response, error) { + return nil, errors.New("should not make request") + }), + } + _, _, err := client.ContainerInspectWithRaw(context.Background(), "", true) + if !IsErrNotFound(err) { + t.Fatalf("Expected NotFoundError, got %v", err) + } +} + func TestContainerInspect(t *testing.T) { expectedURL := "/containers/container_id/json" client := &Client{ diff --git a/client/distribution_inspect.go b/client/distribution_inspect.go index aa5bc6a6c6..b2cf02e0c7 100644 --- a/client/distribution_inspect.go +++ b/client/distribution_inspect.go @@ -12,6 +12,9 @@ import ( func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegistryAuth string) (registrytypes.DistributionInspect, error) { // Contact the registry to retrieve digest and platform information var distributionInspect registrytypes.DistributionInspect + if image == "" { + return distributionInspect, objectNotFoundError{object: "distribution", id: image} + } if err := cli.NewVersionError("1.30", "distribution inspect"); err != nil { return distributionInspect, err diff --git a/client/distribution_inspect_test.go b/client/distribution_inspect_test.go index eff28d7ca7..4da49bcea8 100644 --- a/client/distribution_inspect_test.go +++ b/client/distribution_inspect_test.go @@ -4,6 +4,7 @@ import ( "net/http" "testing" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "golang.org/x/net/context" ) @@ -16,3 +17,15 @@ func TestDistributionInspectUnsupported(t *testing.T) { _, err := client.DistributionInspect(context.Background(), "foobar:1.0", "") assert.EqualError(t, err, `"distribution inspect" requires API version 1.30, but the Docker daemon API version is 1.29`) } + +func TestDistributionInspectWithEmptyID(t *testing.T) { + client := &Client{ + client: newMockClient(func(req *http.Request) (*http.Response, error) { + return nil, errors.New("should not make request") + }), + } + _, err := client.DistributionInspect(context.Background(), "", "") + if !IsErrNotFound(err) { + t.Fatalf("Expected NotFoundError, got %v", err) + } +} diff --git a/client/image_inspect.go b/client/image_inspect.go index 1bc5919907..42a0884023 100644 --- a/client/image_inspect.go +++ b/client/image_inspect.go @@ -11,6 +11,9 @@ import ( // ImageInspectWithRaw returns the image information and its raw representation. func (cli *Client) ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error) { + if imageID == "" { + return types.ImageInspect{}, nil, objectNotFoundError{object: "image", id: imageID} + } serverResp, err := cli.get(ctx, "/images/"+imageID+"/json", nil, nil) if err != nil { return types.ImageInspect{}, nil, wrapResponseError(err, serverResp, "image", imageID) diff --git a/client/image_inspect_test.go b/client/image_inspect_test.go index b5721f39fe..b578a4a6ae 100644 --- a/client/image_inspect_test.go +++ b/client/image_inspect_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/docker/docker/api/types" + "github.com/pkg/errors" "golang.org/x/net/context" ) @@ -36,6 +37,18 @@ func TestImageInspectImageNotFound(t *testing.T) { } } +func TestImageInspectWithEmptyID(t *testing.T) { + client := &Client{ + client: newMockClient(func(req *http.Request) (*http.Response, error) { + return nil, errors.New("should not make request") + }), + } + _, _, err := client.ImageInspectWithRaw(context.Background(), "") + if !IsErrNotFound(err) { + t.Fatalf("Expected NotFoundError, got %v", err) + } +} + func TestImageInspect(t *testing.T) { expectedURL := "/images/image_id/json" expectedTags := []string{"tag1", "tag2"} diff --git a/client/network_inspect.go b/client/network_inspect.go index afabe65970..a37c60402d 100644 --- a/client/network_inspect.go +++ b/client/network_inspect.go @@ -18,6 +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 types.NetworkInspectOptions) (types.NetworkResource, []byte, error) { + if networkID == "" { + return types.NetworkResource{}, nil, objectNotFoundError{object: "network", id: networkID} + } var ( networkResource types.NetworkResource resp serverResponse diff --git a/client/network_inspect_test.go b/client/network_inspect_test.go index 56399c7bc2..c134be5fec 100644 --- a/client/network_inspect_test.go +++ b/client/network_inspect_test.go @@ -11,6 +11,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/network" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "golang.org/x/net/context" ) @@ -34,6 +35,18 @@ func TestNetworkInspectNotFoundError(t *testing.T) { assert.True(t, IsErrNotFound(err)) } +func TestNetworkInspectWithEmptyID(t *testing.T) { + client := &Client{ + client: newMockClient(func(req *http.Request) (*http.Response, error) { + return nil, errors.New("should not make request") + }), + } + _, _, err := client.NetworkInspectWithRaw(context.Background(), "", types.NetworkInspectOptions{}) + if !IsErrNotFound(err) { + t.Fatalf("Expected NotFoundError, got %v", err) + } +} + func TestNetworkInspect(t *testing.T) { expectedURL := "/networks/network_id" client := &Client{ diff --git a/client/node_inspect.go b/client/node_inspect.go index 791d2c0066..bcd391153a 100644 --- a/client/node_inspect.go +++ b/client/node_inspect.go @@ -11,6 +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} + } serverResp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil) if err != nil { return swarm.Node{}, nil, wrapResponseError(err, serverResp, "node", nodeID) diff --git a/client/node_inspect_test.go b/client/node_inspect_test.go index 6260da517d..e5e315b144 100644 --- a/client/node_inspect_test.go +++ b/client/node_inspect_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/docker/docker/api/types/swarm" + "github.com/pkg/errors" "golang.org/x/net/context" ) @@ -35,6 +36,18 @@ func TestNodeInspectNodeNotFound(t *testing.T) { } } +func TestNodeInspectWithEmptyID(t *testing.T) { + client := &Client{ + client: newMockClient(func(req *http.Request) (*http.Response, error) { + return nil, errors.New("should not make request") + }), + } + _, _, err := client.NodeInspectWithRaw(context.Background(), "") + if !IsErrNotFound(err) { + t.Fatalf("Expected NotFoundError, got %v", err) + } +} + func TestNodeInspect(t *testing.T) { expectedURL := "/nodes/node_id" client := &Client{ diff --git a/client/plugin_inspect.go b/client/plugin_inspect.go index 6a6fc18dfe..0b9e07ebdc 100644 --- a/client/plugin_inspect.go +++ b/client/plugin_inspect.go @@ -11,6 +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} + } resp, err := cli.get(ctx, "/plugins/"+name+"/json", nil, nil) if err != nil { return nil, nil, wrapResponseError(err, resp, "plugin", name) diff --git a/client/plugin_inspect_test.go b/client/plugin_inspect_test.go index fae407eb9b..066e00797d 100644 --- a/client/plugin_inspect_test.go +++ b/client/plugin_inspect_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/docker/docker/api/types" + "github.com/pkg/errors" "golang.org/x/net/context" ) @@ -24,6 +25,18 @@ func TestPluginInspectError(t *testing.T) { } } +func TestPluginInspectWithEmptyID(t *testing.T) { + client := &Client{ + client: newMockClient(func(req *http.Request) (*http.Response, error) { + return nil, errors.New("should not make request") + }), + } + _, _, err := client.PluginInspectWithRaw(context.Background(), "") + if !IsErrNotFound(err) { + t.Fatalf("Expected NotFoundError, got %v", err) + } +} + func TestPluginInspect(t *testing.T) { expectedURL := "/plugins/plugin_name" client := &Client{ diff --git a/client/secret_inspect.go b/client/secret_inspect.go index 6927ea96fa..11d8434eb1 100644 --- a/client/secret_inspect.go +++ b/client/secret_inspect.go @@ -14,6 +14,9 @@ func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.S if err := cli.NewVersionError("1.25", "secret inspect"); err != nil { return swarm.Secret{}, nil, err } + if id == "" { + return swarm.Secret{}, nil, objectNotFoundError{object: "secret", id: id} + } resp, err := cli.get(ctx, "/secrets/"+id, nil, nil) if err != nil { return swarm.Secret{}, nil, wrapResponseError(err, resp, "secret", id) diff --git a/client/secret_inspect_test.go b/client/secret_inspect_test.go index 66dbb48d83..4b56d97190 100644 --- a/client/secret_inspect_test.go +++ b/client/secret_inspect_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/docker/docker/api/types/swarm" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "golang.org/x/net/context" ) @@ -47,6 +48,18 @@ func TestSecretInspectSecretNotFound(t *testing.T) { } } +func TestSecretInspectWithEmptyID(t *testing.T) { + client := &Client{ + client: newMockClient(func(req *http.Request) (*http.Response, error) { + return nil, errors.New("should not make request") + }), + } + _, _, err := client.SecretInspectWithRaw(context.Background(), "") + if !IsErrNotFound(err) { + t.Fatalf("Expected NotFoundError, got %v", err) + } +} + func TestSecretInspect(t *testing.T) { expectedURL := "/v1.25/secrets/secret_id" client := &Client{ diff --git a/client/service_inspect.go b/client/service_inspect.go index 3e9699e5e0..52792efbb9 100644 --- a/client/service_inspect.go +++ b/client/service_inspect.go @@ -14,6 +14,9 @@ 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} + } query := url.Values{} query.Set("insertDefaults", fmt.Sprintf("%v", opts.InsertDefaults)) serverResp, err := cli.get(ctx, "/services/"+serviceID, query, nil) diff --git a/client/service_inspect_test.go b/client/service_inspect_test.go index ade62535ca..8fab3a1dbb 100644 --- a/client/service_inspect_test.go +++ b/client/service_inspect_test.go @@ -11,6 +11,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" + "github.com/pkg/errors" "golang.org/x/net/context" ) @@ -36,6 +37,18 @@ func TestServiceInspectServiceNotFound(t *testing.T) { } } +func TestServiceInspectWithEmptyID(t *testing.T) { + client := &Client{ + client: newMockClient(func(req *http.Request) (*http.Response, error) { + return nil, errors.New("should not make request") + }), + } + _, _, err := client.ServiceInspectWithRaw(context.Background(), "", types.ServiceInspectOptions{}) + if !IsErrNotFound(err) { + t.Fatalf("Expected NotFoundError, got %v", err) + } +} + func TestServiceInspect(t *testing.T) { expectedURL := "/services/service_id" client := &Client{ diff --git a/client/task_inspect.go b/client/task_inspect.go index dc08cedb96..f7afa6c11e 100644 --- a/client/task_inspect.go +++ b/client/task_inspect.go @@ -11,6 +11,9 @@ 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} + } serverResp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil) if err != nil { return swarm.Task{}, nil, wrapResponseError(err, serverResp, "task", taskID) diff --git a/client/task_inspect_test.go b/client/task_inspect_test.go index 148cdad3a7..910006731b 100644 --- a/client/task_inspect_test.go +++ b/client/task_inspect_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/docker/docker/api/types/swarm" + "github.com/pkg/errors" "golang.org/x/net/context" ) @@ -24,6 +25,18 @@ func TestTaskInspectError(t *testing.T) { } } +func TestTaskInspectWithEmptyID(t *testing.T) { + client := &Client{ + client: newMockClient(func(req *http.Request) (*http.Response, error) { + return nil, errors.New("should not make request") + }), + } + _, _, err := client.TaskInspectWithRaw(context.Background(), "") + if !IsErrNotFound(err) { + t.Fatalf("Expected NotFoundError, got %v", err) + } +} + func TestTaskInspect(t *testing.T) { expectedURL := "/tasks/task_id" client := &Client{ diff --git a/client/volume_inspect.go b/client/volume_inspect.go index 9889343849..684bcf513f 100644 --- a/client/volume_inspect.go +++ b/client/volume_inspect.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/json" "io/ioutil" - "path" "github.com/docker/docker/api/types" "golang.org/x/net/context" @@ -18,15 +17,12 @@ func (cli *Client) VolumeInspect(ctx context.Context, volumeID string) (types.Vo // 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) (types.Volume, []byte, error) { - // The empty ID needs to be handled here because with an empty ID the - // request url will not contain a trailing / which calls the volume list API - // instead of volume inspect if volumeID == "" { return types.Volume{}, nil, objectNotFoundError{object: "volume", id: volumeID} } var volume types.Volume - resp, err := cli.get(ctx, path.Join("/volumes", volumeID), nil, nil) + resp, err := cli.get(ctx, "/volumes/"+volumeID, nil, nil) if err != nil { return volume, nil, wrapResponseError(err, resp, "volume", volumeID) } diff --git a/client/volume_inspect_test.go b/client/volume_inspect_test.go index 7d01f44ed2..6ac6f681db 100644 --- a/client/volume_inspect_test.go +++ b/client/volume_inspect_test.go @@ -11,6 +11,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/internal/testutil" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/net/context" @@ -35,20 +36,15 @@ func TestVolumeInspectNotFound(t *testing.T) { } func TestVolumeInspectWithEmptyID(t *testing.T) { - expectedURL := "/volumes/" - client := &Client{ client: newMockClient(func(req *http.Request) (*http.Response, error) { - assert.Equal(t, req.URL.Path, expectedURL) - return &http.Response{ - StatusCode: http.StatusNotFound, - Body: ioutil.NopCloser(bytes.NewReader(nil)), - }, nil + return nil, errors.New("should not make request") }), } - _, err := client.VolumeInspect(context.Background(), "") - testutil.ErrorContains(t, err, "No such volume: ") - + _, _, err := client.VolumeInspectWithRaw(context.Background(), "") + if !IsErrNotFound(err) { + t.Fatalf("Expected NotFoundError, got %v", err) + } } func TestVolumeInspect(t *testing.T) {