Browse code

Client: always call ensureReaderClosed

Unlike a plain `net/http/client.Do()`, requests made through client/request
use the `sendRequest` function, which parses the server response, and may
convert non-transport errors into errors (through `cli.checkResponseErr()`).

This means that we cannot assume that no reader was opened if an error is
returned.

This patch changes various locations where `ensureReaderClosed` was only
called in the non-error situation, and uses a `defer` to make sure it's
always called.

`ensureReaderClosed` itself already checks if the response's body was set,
so in situations where the error was due to a transport error, calling
`ensureReaderClosed` should be a no-op.

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

Sebastiaan van Stijn authored on 2019/02/11 21:26:12
Showing 61 changed files
... ...
@@ -11,10 +11,6 @@ func (cli *Client) BuildCancel(ctx context.Context, id string) error {
11 11
 	query.Set("id", id)
12 12
 
13 13
 	serverResp, err := cli.post(ctx, "/build/cancel", query, nil, nil)
14
-	if err != nil {
15
-		return err
16
-	}
17
-	defer ensureReaderClosed(serverResp)
18
-
19
-	return nil
14
+	ensureReaderClosed(serverResp)
15
+	return err
20 16
 }
... ...
@@ -31,11 +31,11 @@ func (cli *Client) BuildCachePrune(ctx context.Context, opts types.BuildCachePru
31 31
 	query.Set("filters", filters)
32 32
 
33 33
 	serverResp, err := cli.post(ctx, "/build/prune", query, nil, nil)
34
+	defer ensureReaderClosed(serverResp)
34 35
 
35 36
 	if err != nil {
36 37
 		return nil, err
37 38
 	}
38
-	defer ensureReaderClosed(serverResp)
39 39
 
40 40
 	if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
41 41
 		return nil, fmt.Errorf("Error retrieving disk usage: %v", err)
... ...
@@ -18,11 +18,11 @@ func (cli *Client) CheckpointList(ctx context.Context, container string, options
18 18
 	}
19 19
 
20 20
 	resp, err := cli.get(ctx, "/containers/"+container+"/checkpoints", query, nil)
21
+	defer ensureReaderClosed(resp)
21 22
 	if err != nil {
22 23
 		return checkpoints, wrapResponseError(err, resp, "container", container)
23 24
 	}
24 25
 
25 26
 	err = json.NewDecoder(resp.body).Decode(&checkpoints)
26
-	ensureReaderClosed(resp)
27 27
 	return checkpoints, err
28 28
 }
... ...
@@ -15,11 +15,11 @@ func (cli *Client) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (t
15 15
 		return response, err
16 16
 	}
17 17
 	resp, err := cli.post(ctx, "/configs/create", nil, config, nil)
18
+	defer ensureReaderClosed(resp)
18 19
 	if err != nil {
19 20
 		return response, err
20 21
 	}
21 22
 
22 23
 	err = json.NewDecoder(resp.body).Decode(&response)
23
-	ensureReaderClosed(resp)
24 24
 	return response, err
25 25
 }
... ...
@@ -18,10 +18,10 @@ func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.C
18 18
 		return swarm.Config{}, nil, err
19 19
 	}
20 20
 	resp, err := cli.get(ctx, "/configs/"+id, nil, nil)
21
+	defer ensureReaderClosed(resp)
21 22
 	if err != nil {
22 23
 		return swarm.Config{}, nil, wrapResponseError(err, resp, "config", id)
23 24
 	}
24
-	defer ensureReaderClosed(resp)
25 25
 
26 26
 	body, err := ioutil.ReadAll(resp.body)
27 27
 	if err != nil {
... ...
@@ -27,12 +27,12 @@ func (cli *Client) ConfigList(ctx context.Context, options types.ConfigListOptio
27 27
 	}
28 28
 
29 29
 	resp, err := cli.get(ctx, "/configs", query, nil)
30
+	defer ensureReaderClosed(resp)
30 31
 	if err != nil {
31 32
 		return nil, err
32 33
 	}
33 34
 
34 35
 	var configs []swarm.Config
35 36
 	err = json.NewDecoder(resp.body).Decode(&configs)
36
-	ensureReaderClosed(resp)
37 37
 	return configs, err
38 38
 }
... ...
@@ -8,6 +8,6 @@ func (cli *Client) ConfigRemove(ctx context.Context, id string) error {
8 8
 		return err
9 9
 	}
10 10
 	resp, err := cli.delete(ctx, "/configs/"+id, nil, nil)
11
-	ensureReaderClosed(resp)
11
+	defer ensureReaderClosed(resp)
12 12
 	return wrapResponseError(err, resp, "config", id)
13 13
 }
... ...
@@ -45,11 +45,11 @@ func (cli *Client) ContainerCommit(ctx context.Context, container string, option
45 45
 
46 46
 	var response types.IDResponse
47 47
 	resp, err := cli.post(ctx, "/commit", query, options.Config, nil)
48
+	defer ensureReaderClosed(resp)
48 49
 	if err != nil {
49 50
 		return response, err
50 51
 	}
51 52
 
52 53
 	err = json.NewDecoder(resp.body).Decode(&response)
53
-	ensureReaderClosed(resp)
54 54
 	return response, err
55 55
 }
... ...
@@ -21,10 +21,10 @@ func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path stri
21 21
 
22 22
 	urlStr := "/containers/" + containerID + "/archive"
23 23
 	response, err := cli.head(ctx, urlStr, query, nil)
24
+	defer ensureReaderClosed(response)
24 25
 	if err != nil {
25 26
 		return types.ContainerPathStat{}, wrapResponseError(err, response, "container:path", containerID+":"+path)
26 27
 	}
27
-	defer ensureReaderClosed(response)
28 28
 	return getContainerPathStatFromHeader(response.header)
29 29
 }
30 30
 
... ...
@@ -45,10 +45,10 @@ func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath str
45 45
 	apiPath := "/containers/" + containerID + "/archive"
46 46
 
47 47
 	response, err := cli.putRaw(ctx, apiPath, query, content, nil)
48
+	defer ensureReaderClosed(response)
48 49
 	if err != nil {
49 50
 		return wrapResponseError(err, response, "container:path", containerID+":"+dstPath)
50 51
 	}
51
-	defer ensureReaderClosed(response)
52 52
 
53 53
 	// TODO this code converts non-error status-codes (e.g., "204 No Content") into an error; verify if this is the desired behavior
54 54
 	if response.statusCode != http.StatusOK {
... ...
@@ -42,11 +42,11 @@ func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config
42 42
 	}
43 43
 
44 44
 	serverResp, err := cli.post(ctx, "/containers/create", query, body, nil)
45
+	defer ensureReaderClosed(serverResp)
45 46
 	if err != nil {
46 47
 		return response, err
47 48
 	}
48 49
 
49 50
 	err = json.NewDecoder(serverResp.body).Decode(&response)
50
-	ensureReaderClosed(serverResp)
51 51
 	return response, err
52 52
 }
... ...
@@ -13,11 +13,11 @@ func (cli *Client) ContainerDiff(ctx context.Context, containerID string) ([]con
13 13
 	var changes []container.ContainerChangeResponseItem
14 14
 
15 15
 	serverResp, err := cli.get(ctx, "/containers/"+containerID+"/changes", url.Values{}, nil)
16
+	defer ensureReaderClosed(serverResp)
16 17
 	if err != nil {
17 18
 		return changes, err
18 19
 	}
19 20
 
20 21
 	err = json.NewDecoder(serverResp.body).Decode(&changes)
21
-	ensureReaderClosed(serverResp)
22 22
 	return changes, err
23 23
 }
... ...
@@ -16,11 +16,11 @@ func (cli *Client) ContainerExecCreate(ctx context.Context, container string, co
16 16
 	}
17 17
 
18 18
 	resp, err := cli.post(ctx, "/containers/"+container+"/exec", nil, config, nil)
19
+	defer ensureReaderClosed(resp)
19 20
 	if err != nil {
20 21
 		return response, err
21 22
 	}
22 23
 	err = json.NewDecoder(resp.body).Decode(&response)
23
-	ensureReaderClosed(resp)
24 24
 	return response, err
25 25
 }
26 26
 
... ...
@@ -16,10 +16,10 @@ func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (ty
16 16
 		return types.ContainerJSON{}, objectNotFoundError{object: "container", id: containerID}
17 17
 	}
18 18
 	serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil)
19
+	defer ensureReaderClosed(serverResp)
19 20
 	if err != nil {
20 21
 		return types.ContainerJSON{}, wrapResponseError(err, serverResp, "container", containerID)
21 22
 	}
22
-	defer ensureReaderClosed(serverResp)
23 23
 
24 24
 	var response types.ContainerJSON
25 25
 	err = json.NewDecoder(serverResp.body).Decode(&response)
... ...
@@ -36,10 +36,10 @@ func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID stri
36 36
 		query.Set("size", "1")
37 37
 	}
38 38
 	serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", query, nil)
39
+	defer ensureReaderClosed(serverResp)
39 40
 	if err != nil {
40 41
 		return types.ContainerJSON{}, nil, wrapResponseError(err, serverResp, "container", containerID)
41 42
 	}
42
-	defer ensureReaderClosed(serverResp)
43 43
 
44 44
 	body, err := ioutil.ReadAll(serverResp.body)
45 45
 	if err != nil {
... ...
@@ -45,12 +45,12 @@ func (cli *Client) ContainerList(ctx context.Context, options types.ContainerLis
45 45
 	}
46 46
 
47 47
 	resp, err := cli.get(ctx, "/containers/json", query, nil)
48
+	defer ensureReaderClosed(resp)
48 49
 	if err != nil {
49 50
 		return nil, err
50 51
 	}
51 52
 
52 53
 	var containers []types.Container
53 54
 	err = json.NewDecoder(resp.body).Decode(&containers)
54
-	ensureReaderClosed(resp)
55 55
 	return containers, err
56 56
 }
... ...
@@ -23,10 +23,10 @@ func (cli *Client) ContainersPrune(ctx context.Context, pruneFilters filters.Arg
23 23
 	}
24 24
 
25 25
 	serverResp, err := cli.post(ctx, "/containers/prune", query, nil, nil)
26
+	defer ensureReaderClosed(serverResp)
26 27
 	if err != nil {
27 28
 		return report, err
28 29
 	}
29
-	defer ensureReaderClosed(serverResp)
30 30
 
31 31
 	if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
32 32
 		return report, fmt.Errorf("Error retrieving disk usage: %v", err)
... ...
@@ -22,6 +22,6 @@ func (cli *Client) ContainerRemove(ctx context.Context, containerID string, opti
22 22
 	}
23 23
 
24 24
 	resp, err := cli.delete(ctx, "/containers/"+containerID, query, nil)
25
-	ensureReaderClosed(resp)
25
+	defer ensureReaderClosed(resp)
26 26
 	return wrapResponseError(err, resp, "container", containerID)
27 27
 }
... ...
@@ -18,11 +18,11 @@ func (cli *Client) ContainerTop(ctx context.Context, containerID string, argumen
18 18
 	}
19 19
 
20 20
 	resp, err := cli.get(ctx, "/containers/"+containerID+"/top", query, nil)
21
+	defer ensureReaderClosed(resp)
21 22
 	if err != nil {
22 23
 		return response, err
23 24
 	}
24 25
 
25 26
 	err = json.NewDecoder(resp.body).Decode(&response)
26
-	ensureReaderClosed(resp)
27 27
 	return response, err
28 28
 }
... ...
@@ -11,12 +11,11 @@ import (
11 11
 func (cli *Client) ContainerUpdate(ctx context.Context, containerID string, updateConfig container.UpdateConfig) (container.ContainerUpdateOKBody, error) {
12 12
 	var response container.ContainerUpdateOKBody
13 13
 	serverResp, err := cli.post(ctx, "/containers/"+containerID+"/update", nil, updateConfig, nil)
14
+	defer ensureReaderClosed(serverResp)
14 15
 	if err != nil {
15 16
 		return response, err
16 17
 	}
17 18
 
18 19
 	err = json.NewDecoder(serverResp.body).Decode(&response)
19
-
20
-	ensureReaderClosed(serverResp)
21 20
 	return response, err
22 21
 }
... ...
@@ -13,10 +13,10 @@ func (cli *Client) DiskUsage(ctx context.Context) (types.DiskUsage, error) {
13 13
 	var du types.DiskUsage
14 14
 
15 15
 	serverResp, err := cli.get(ctx, "/system/df", nil, nil)
16
+	defer ensureReaderClosed(serverResp)
16 17
 	if err != nil {
17 18
 		return du, err
18 19
 	}
19
-	defer ensureReaderClosed(serverResp)
20 20
 
21 21
 	if err := json.NewDecoder(serverResp.body).Decode(&du); err != nil {
22 22
 		return du, fmt.Errorf("Error retrieving disk usage: %v", err)
... ...
@@ -28,11 +28,11 @@ func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegist
28 28
 	}
29 29
 
30 30
 	resp, err := cli.get(ctx, "/distribution/"+image+"/json", url.Values{}, headers)
31
+	defer ensureReaderClosed(resp)
31 32
 	if err != nil {
32 33
 		return distributionInspect, err
33 34
 	}
34 35
 
35 36
 	err = json.NewDecoder(resp.body).Decode(&distributionInspect)
36
-	ensureReaderClosed(resp)
37 37
 	return distributionInspect, err
38 38
 }
... ...
@@ -12,11 +12,11 @@ import (
12 12
 func (cli *Client) ImageHistory(ctx context.Context, imageID string) ([]image.HistoryResponseItem, error) {
13 13
 	var history []image.HistoryResponseItem
14 14
 	serverResp, err := cli.get(ctx, "/images/"+imageID+"/history", url.Values{}, nil)
15
+	defer ensureReaderClosed(serverResp)
15 16
 	if err != nil {
16 17
 		return history, err
17 18
 	}
18 19
 
19 20
 	err = json.NewDecoder(serverResp.body).Decode(&history)
20
-	ensureReaderClosed(serverResp)
21 21
 	return history, err
22 22
 }
... ...
@@ -15,10 +15,10 @@ func (cli *Client) ImageInspectWithRaw(ctx context.Context, imageID string) (typ
15 15
 		return types.ImageInspect{}, nil, objectNotFoundError{object: "image", id: imageID}
16 16
 	}
17 17
 	serverResp, err := cli.get(ctx, "/images/"+imageID+"/json", nil, nil)
18
+	defer ensureReaderClosed(serverResp)
18 19
 	if err != nil {
19 20
 		return types.ImageInspect{}, nil, wrapResponseError(err, serverResp, "image", imageID)
20 21
 	}
21
-	defer ensureReaderClosed(serverResp)
22 22
 
23 23
 	body, err := ioutil.ReadAll(serverResp.body)
24 24
 	if err != nil {
... ...
@@ -35,11 +35,11 @@ func (cli *Client) ImageList(ctx context.Context, options types.ImageListOptions
35 35
 	}
36 36
 
37 37
 	serverResp, err := cli.get(ctx, "/images/json", query, nil)
38
+	defer ensureReaderClosed(serverResp)
38 39
 	if err != nil {
39 40
 		return images, err
40 41
 	}
41 42
 
42 43
 	err = json.NewDecoder(serverResp.body).Decode(&images)
43
-	ensureReaderClosed(serverResp)
44 44
 	return images, err
45 45
 }
... ...
@@ -23,10 +23,10 @@ func (cli *Client) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (
23 23
 	}
24 24
 
25 25
 	serverResp, err := cli.post(ctx, "/images/prune", query, nil, nil)
26
+	defer ensureReaderClosed(serverResp)
26 27
 	if err != nil {
27 28
 		return report, err
28 29
 	}
29
-	defer ensureReaderClosed(serverResp)
30 30
 
31 31
 	if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
32 32
 		return report, fmt.Errorf("Error retrieving disk usage: %v", err)
... ...
@@ -21,11 +21,11 @@ func (cli *Client) ImageRemove(ctx context.Context, imageID string, options type
21 21
 
22 22
 	var dels []types.ImageDeleteResponseItem
23 23
 	resp, err := cli.delete(ctx, "/images/"+imageID, query, nil)
24
+	defer ensureReaderClosed(resp)
24 25
 	if err != nil {
25 26
 		return dels, wrapResponseError(err, resp, "image", imageID)
26 27
 	}
27 28
 
28 29
 	err = json.NewDecoder(resp.body).Decode(&dels)
29
-	ensureReaderClosed(resp)
30 30
 	return dels, err
31 31
 }
... ...
@@ -29,6 +29,7 @@ func (cli *Client) ImageSearch(ctx context.Context, term string, options types.I
29 29
 	}
30 30
 
31 31
 	resp, err := cli.tryImageSearch(ctx, query, options.RegistryAuth)
32
+	defer ensureReaderClosed(resp)
32 33
 	if errdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil {
33 34
 		newAuthHeader, privilegeErr := options.PrivilegeFunc()
34 35
 		if privilegeErr != nil {
... ...
@@ -41,7 +42,6 @@ func (cli *Client) ImageSearch(ctx context.Context, term string, options types.I
41 41
 	}
42 42
 
43 43
 	err = json.NewDecoder(resp.body).Decode(&results)
44
-	ensureReaderClosed(resp)
45 44
 	return results, err
46 45
 }
47 46
 
... ...
@@ -13,10 +13,10 @@ import (
13 13
 func (cli *Client) Info(ctx context.Context) (types.Info, error) {
14 14
 	var info types.Info
15 15
 	serverResp, err := cli.get(ctx, "/info", url.Values{}, nil)
16
+	defer ensureReaderClosed(serverResp)
16 17
 	if err != nil {
17 18
 		return info, err
18 19
 	}
19
-	defer ensureReaderClosed(serverResp)
20 20
 
21 21
 	if err := json.NewDecoder(serverResp.body).Decode(&info); err != nil {
22 22
 		return info, fmt.Errorf("Error reading remote info: %v", err)
... ...
@@ -13,6 +13,7 @@ import (
13 13
 // It returns unauthorizedError when the authentication fails.
14 14
 func (cli *Client) RegistryLogin(ctx context.Context, auth types.AuthConfig) (registry.AuthenticateOKBody, error) {
15 15
 	resp, err := cli.post(ctx, "/auth", url.Values{}, auth, nil)
16
+	defer ensureReaderClosed(resp)
16 17
 
17 18
 	if err != nil {
18 19
 		return registry.AuthenticateOKBody{}, err
... ...
@@ -20,6 +21,5 @@ func (cli *Client) RegistryLogin(ctx context.Context, auth types.AuthConfig) (re
20 20
 
21 21
 	var response registry.AuthenticateOKBody
22 22
 	err = json.NewDecoder(resp.body).Decode(&response)
23
-	ensureReaderClosed(resp)
24 23
 	return response, err
25 24
 }
... ...
@@ -15,11 +15,11 @@ func (cli *Client) NetworkCreate(ctx context.Context, name string, options types
15 15
 	}
16 16
 	var response types.NetworkCreateResponse
17 17
 	serverResp, err := cli.post(ctx, "/networks/create", nil, networkCreateRequest, nil)
18
+	defer ensureReaderClosed(serverResp)
18 19
 	if err != nil {
19 20
 		return response, err
20 21
 	}
21 22
 
22
-	json.NewDecoder(serverResp.body).Decode(&response)
23
-	ensureReaderClosed(serverResp)
23
+	err = json.NewDecoder(serverResp.body).Decode(&response)
24 24
 	return response, err
25 25
 }
... ...
@@ -34,10 +34,10 @@ func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string,
34 34
 		query.Set("scope", options.Scope)
35 35
 	}
36 36
 	resp, err = cli.get(ctx, "/networks/"+networkID, query, nil)
37
+	defer ensureReaderClosed(resp)
37 38
 	if err != nil {
38 39
 		return networkResource, nil, wrapResponseError(err, resp, "network", networkID)
39 40
 	}
40
-	defer ensureReaderClosed(resp)
41 41
 
42 42
 	body, err := ioutil.ReadAll(resp.body)
43 43
 	if err != nil {
... ...
@@ -22,10 +22,10 @@ func (cli *Client) NetworkList(ctx context.Context, options types.NetworkListOpt
22 22
 	}
23 23
 	var networkResources []types.NetworkResource
24 24
 	resp, err := cli.get(ctx, "/networks", query, nil)
25
+	defer ensureReaderClosed(resp)
25 26
 	if err != nil {
26 27
 		return networkResources, err
27 28
 	}
28 29
 	err = json.NewDecoder(resp.body).Decode(&networkResources)
29
-	ensureReaderClosed(resp)
30 30
 	return networkResources, err
31 31
 }
... ...
@@ -23,10 +23,10 @@ func (cli *Client) NetworksPrune(ctx context.Context, pruneFilters filters.Args)
23 23
 	}
24 24
 
25 25
 	serverResp, err := cli.post(ctx, "/networks/prune", query, nil, nil)
26
+	defer ensureReaderClosed(serverResp)
26 27
 	if err != nil {
27 28
 		return report, err
28 29
 	}
29
-	defer ensureReaderClosed(serverResp)
30 30
 
31 31
 	if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
32 32
 		return report, fmt.Errorf("Error retrieving network prune report: %v", err)
... ...
@@ -5,6 +5,6 @@ import "context"
5 5
 // NetworkRemove removes an existent network from the docker host.
6 6
 func (cli *Client) NetworkRemove(ctx context.Context, networkID string) error {
7 7
 	resp, err := cli.delete(ctx, "/networks/"+networkID, nil, nil)
8
-	ensureReaderClosed(resp)
8
+	defer ensureReaderClosed(resp)
9 9
 	return wrapResponseError(err, resp, "network", networkID)
10 10
 }
... ...
@@ -15,10 +15,10 @@ func (cli *Client) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm
15 15
 		return swarm.Node{}, nil, objectNotFoundError{object: "node", id: nodeID}
16 16
 	}
17 17
 	serverResp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil)
18
+	defer ensureReaderClosed(serverResp)
18 19
 	if err != nil {
19 20
 		return swarm.Node{}, nil, wrapResponseError(err, serverResp, "node", nodeID)
20 21
 	}
21
-	defer ensureReaderClosed(serverResp)
22 22
 
23 23
 	body, err := ioutil.ReadAll(serverResp.body)
24 24
 	if err != nil {
... ...
@@ -25,12 +25,12 @@ func (cli *Client) NodeList(ctx context.Context, options types.NodeListOptions)
25 25
 	}
26 26
 
27 27
 	resp, err := cli.get(ctx, "/nodes", query, nil)
28
+	defer ensureReaderClosed(resp)
28 29
 	if err != nil {
29 30
 		return nil, err
30 31
 	}
31 32
 
32 33
 	var nodes []swarm.Node
33 34
 	err = json.NewDecoder(resp.body).Decode(&nodes)
34
-	ensureReaderClosed(resp)
35 35
 	return nodes, err
36 36
 }
... ...
@@ -15,6 +15,6 @@ func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options types.
15 15
 	}
16 16
 
17 17
 	resp, err := cli.delete(ctx, "/nodes/"+nodeID, query, nil)
18
-	ensureReaderClosed(resp)
18
+	defer ensureReaderClosed(resp)
19 19
 	return wrapResponseError(err, resp, "node", nodeID)
20 20
 }
... ...
@@ -38,10 +38,10 @@ func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
38 38
 		return ping, err
39 39
 	}
40 40
 	serverResp, err = cli.doRequest(ctx, req)
41
+	defer ensureReaderClosed(serverResp)
41 42
 	if err != nil {
42 43
 		return ping, err
43 44
 	}
44
-	defer ensureReaderClosed(serverResp)
45 45
 	return parsePingResponse(cli, serverResp)
46 46
 }
47 47
 
... ...
@@ -18,9 +18,6 @@ func (cli *Client) PluginCreate(ctx context.Context, createContext io.Reader, cr
18 18
 	query.Set("name", createOptions.RepoName)
19 19
 
20 20
 	resp, err := cli.postRaw(ctx, "/plugins/create", query, createContext, headers)
21
-	if err != nil {
22
-		return err
23
-	}
24 21
 	ensureReaderClosed(resp)
25 22
 	return err
26 23
 }
... ...
@@ -15,11 +15,11 @@ func (cli *Client) PluginInspectWithRaw(ctx context.Context, name string) (*type
15 15
 		return nil, nil, objectNotFoundError{object: "plugin", id: name}
16 16
 	}
17 17
 	resp, err := cli.get(ctx, "/plugins/"+name+"/json", nil, nil)
18
+	defer ensureReaderClosed(resp)
18 19
 	if err != nil {
19 20
 		return nil, nil, wrapResponseError(err, resp, "plugin", name)
20 21
 	}
21 22
 
22
-	defer ensureReaderClosed(resp)
23 23
 	body, err := ioutil.ReadAll(resp.body)
24 24
 	if err != nil {
25 25
 		return nil, nil, err
... ...
@@ -22,11 +22,11 @@ func (cli *Client) PluginList(ctx context.Context, filter filters.Args) (types.P
22 22
 		query.Set("filters", filterJSON)
23 23
 	}
24 24
 	resp, err := cli.get(ctx, "/plugins", query, nil)
25
+	defer ensureReaderClosed(resp)
25 26
 	if err != nil {
26 27
 		return plugins, wrapResponseError(err, resp, "plugin", "")
27 28
 	}
28 29
 
29 30
 	err = json.NewDecoder(resp.body).Decode(&plugins)
30
-	ensureReaderClosed(resp)
31 31
 	return plugins, err
32 32
 }
... ...
@@ -15,6 +15,6 @@ func (cli *Client) PluginRemove(ctx context.Context, name string, options types.
15 15
 	}
16 16
 
17 17
 	resp, err := cli.delete(ctx, "/plugins/"+name, query, nil)
18
-	ensureReaderClosed(resp)
18
+	defer ensureReaderClosed(resp)
19 19
 	return wrapResponseError(err, resp, "plugin", name)
20 20
 }
... ...
@@ -15,11 +15,11 @@ func (cli *Client) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (t
15 15
 		return response, err
16 16
 	}
17 17
 	resp, err := cli.post(ctx, "/secrets/create", nil, secret, nil)
18
+	defer ensureReaderClosed(resp)
18 19
 	if err != nil {
19 20
 		return response, err
20 21
 	}
21 22
 
22 23
 	err = json.NewDecoder(resp.body).Decode(&response)
23
-	ensureReaderClosed(resp)
24 24
 	return response, err
25 25
 }
... ...
@@ -18,10 +18,10 @@ func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.S
18 18
 		return swarm.Secret{}, nil, objectNotFoundError{object: "secret", id: id}
19 19
 	}
20 20
 	resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
21
+	defer ensureReaderClosed(resp)
21 22
 	if err != nil {
22 23
 		return swarm.Secret{}, nil, wrapResponseError(err, resp, "secret", id)
23 24
 	}
24
-	defer ensureReaderClosed(resp)
25 25
 
26 26
 	body, err := ioutil.ReadAll(resp.body)
27 27
 	if err != nil {
... ...
@@ -27,12 +27,12 @@ func (cli *Client) SecretList(ctx context.Context, options types.SecretListOptio
27 27
 	}
28 28
 
29 29
 	resp, err := cli.get(ctx, "/secrets", query, nil)
30
+	defer ensureReaderClosed(resp)
30 31
 	if err != nil {
31 32
 		return nil, err
32 33
 	}
33 34
 
34 35
 	var secrets []swarm.Secret
35 36
 	err = json.NewDecoder(resp.body).Decode(&secrets)
36
-	ensureReaderClosed(resp)
37 37
 	return secrets, err
38 38
 }
... ...
@@ -8,6 +8,6 @@ func (cli *Client) SecretRemove(ctx context.Context, id string) error {
8 8
 		return err
9 9
 	}
10 10
 	resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil)
11
-	ensureReaderClosed(resp)
11
+	defer ensureReaderClosed(resp)
12 12
 	return wrapResponseError(err, resp, "secret", id)
13 13
 }
... ...
@@ -72,6 +72,7 @@ func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec,
72 72
 
73 73
 	var response types.ServiceCreateResponse
74 74
 	resp, err := cli.post(ctx, "/services/create", nil, service, headers)
75
+	defer ensureReaderClosed(resp)
75 76
 	if err != nil {
76 77
 		return response, err
77 78
 	}
... ...
@@ -82,7 +83,6 @@ func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec,
82 82
 		response.Warnings = append(response.Warnings, digestWarning(service.TaskTemplate.ContainerSpec.Image))
83 83
 	}
84 84
 
85
-	ensureReaderClosed(resp)
86 85
 	return response, err
87 86
 }
88 87
 
... ...
@@ -20,10 +20,10 @@ func (cli *Client) ServiceInspectWithRaw(ctx context.Context, serviceID string,
20 20
 	query := url.Values{}
21 21
 	query.Set("insertDefaults", fmt.Sprintf("%v", opts.InsertDefaults))
22 22
 	serverResp, err := cli.get(ctx, "/services/"+serviceID, query, nil)
23
+	defer ensureReaderClosed(serverResp)
23 24
 	if err != nil {
24 25
 		return swarm.Service{}, nil, wrapResponseError(err, serverResp, "service", serviceID)
25 26
 	}
26
-	defer ensureReaderClosed(serverResp)
27 27
 
28 28
 	body, err := ioutil.ReadAll(serverResp.body)
29 29
 	if err != nil {
... ...
@@ -24,12 +24,12 @@ func (cli *Client) ServiceList(ctx context.Context, options types.ServiceListOpt
24 24
 	}
25 25
 
26 26
 	resp, err := cli.get(ctx, "/services", query, nil)
27
+	defer ensureReaderClosed(resp)
27 28
 	if err != nil {
28 29
 		return nil, err
29 30
 	}
30 31
 
31 32
 	var services []swarm.Service
32 33
 	err = json.NewDecoder(resp.body).Decode(&services)
33
-	ensureReaderClosed(resp)
34 34
 	return services, err
35 35
 }
... ...
@@ -5,6 +5,6 @@ import "context"
5 5
 // ServiceRemove kills and removes a service.
6 6
 func (cli *Client) ServiceRemove(ctx context.Context, serviceID string) error {
7 7
 	resp, err := cli.delete(ctx, "/services/"+serviceID, nil, nil)
8
-	ensureReaderClosed(resp)
8
+	defer ensureReaderClosed(resp)
9 9
 	return wrapResponseError(err, resp, "service", serviceID)
10 10
 }
... ...
@@ -79,6 +79,7 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
79 79
 
80 80
 	var response types.ServiceUpdateResponse
81 81
 	resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers)
82
+	defer ensureReaderClosed(resp)
82 83
 	if err != nil {
83 84
 		return response, err
84 85
 	}
... ...
@@ -89,6 +90,5 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
89 89
 		response.Warnings = append(response.Warnings, digestWarning(service.TaskTemplate.ContainerSpec.Image))
90 90
 	}
91 91
 
92
-	ensureReaderClosed(resp)
93 92
 	return response, err
94 93
 }
... ...
@@ -10,12 +10,12 @@ import (
10 10
 // SwarmGetUnlockKey retrieves the swarm's unlock key.
11 11
 func (cli *Client) SwarmGetUnlockKey(ctx context.Context) (types.SwarmUnlockKeyResponse, error) {
12 12
 	serverResp, err := cli.get(ctx, "/swarm/unlockkey", nil, nil)
13
+	defer ensureReaderClosed(serverResp)
13 14
 	if err != nil {
14 15
 		return types.SwarmUnlockKeyResponse{}, err
15 16
 	}
16 17
 
17 18
 	var response types.SwarmUnlockKeyResponse
18 19
 	err = json.NewDecoder(serverResp.body).Decode(&response)
19
-	ensureReaderClosed(serverResp)
20 20
 	return response, err
21 21
 }
... ...
@@ -10,12 +10,12 @@ import (
10 10
 // SwarmInit initializes the swarm.
11 11
 func (cli *Client) SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error) {
12 12
 	serverResp, err := cli.post(ctx, "/swarm/init", nil, req, nil)
13
+	defer ensureReaderClosed(serverResp)
13 14
 	if err != nil {
14 15
 		return "", err
15 16
 	}
16 17
 
17 18
 	var response string
18 19
 	err = json.NewDecoder(serverResp.body).Decode(&response)
19
-	ensureReaderClosed(serverResp)
20 20
 	return response, err
21 21
 }
... ...
@@ -10,12 +10,12 @@ import (
10 10
 // SwarmInspect inspects the swarm.
11 11
 func (cli *Client) SwarmInspect(ctx context.Context) (swarm.Swarm, error) {
12 12
 	serverResp, err := cli.get(ctx, "/swarm", nil, nil)
13
+	defer ensureReaderClosed(serverResp)
13 14
 	if err != nil {
14 15
 		return swarm.Swarm{}, err
15 16
 	}
16 17
 
17 18
 	var response swarm.Swarm
18 19
 	err = json.NewDecoder(serverResp.body).Decode(&response)
19
-	ensureReaderClosed(serverResp)
20 20
 	return response, err
21 21
 }
... ...
@@ -15,10 +15,10 @@ func (cli *Client) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm
15 15
 		return swarm.Task{}, nil, objectNotFoundError{object: "task", id: taskID}
16 16
 	}
17 17
 	serverResp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil)
18
+	defer ensureReaderClosed(serverResp)
18 19
 	if err != nil {
19 20
 		return swarm.Task{}, nil, wrapResponseError(err, serverResp, "task", taskID)
20 21
 	}
21
-	defer ensureReaderClosed(serverResp)
22 22
 
23 23
 	body, err := ioutil.ReadAll(serverResp.body)
24 24
 	if err != nil {
... ...
@@ -24,12 +24,12 @@ func (cli *Client) TaskList(ctx context.Context, options types.TaskListOptions)
24 24
 	}
25 25
 
26 26
 	resp, err := cli.get(ctx, "/tasks", query, nil)
27
+	defer ensureReaderClosed(resp)
27 28
 	if err != nil {
28 29
 		return nil, err
29 30
 	}
30 31
 
31 32
 	var tasks []swarm.Task
32 33
 	err = json.NewDecoder(resp.body).Decode(&tasks)
33
-	ensureReaderClosed(resp)
34 34
 	return tasks, err
35 35
 }
... ...
@@ -10,12 +10,12 @@ import (
10 10
 // ServerVersion returns information of the docker client and server host.
11 11
 func (cli *Client) ServerVersion(ctx context.Context) (types.Version, error) {
12 12
 	resp, err := cli.get(ctx, "/version", nil, nil)
13
+	defer ensureReaderClosed(resp)
13 14
 	if err != nil {
14 15
 		return types.Version{}, err
15 16
 	}
16 17
 
17 18
 	var server types.Version
18 19
 	err = json.NewDecoder(resp.body).Decode(&server)
19
-	ensureReaderClosed(resp)
20 20
 	return server, err
21 21
 }
... ...
@@ -12,10 +12,10 @@ import (
12 12
 func (cli *Client) VolumeCreate(ctx context.Context, options volumetypes.VolumeCreateBody) (types.Volume, error) {
13 13
 	var volume types.Volume
14 14
 	resp, err := cli.post(ctx, "/volumes/create", nil, options, nil)
15
+	defer ensureReaderClosed(resp)
15 16
 	if err != nil {
16 17
 		return volume, err
17 18
 	}
18 19
 	err = json.NewDecoder(resp.body).Decode(&volume)
19
-	ensureReaderClosed(resp)
20 20
 	return volume, err
21 21
 }
... ...
@@ -23,10 +23,10 @@ func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (t
23 23
 
24 24
 	var volume types.Volume
25 25
 	resp, err := cli.get(ctx, "/volumes/"+volumeID, nil, nil)
26
+	defer ensureReaderClosed(resp)
26 27
 	if err != nil {
27 28
 		return volume, nil, wrapResponseError(err, resp, "volume", volumeID)
28 29
 	}
29
-	defer ensureReaderClosed(resp)
30 30
 
31 31
 	body, err := ioutil.ReadAll(resp.body)
32 32
 	if err != nil {
... ...
@@ -22,11 +22,11 @@ func (cli *Client) VolumeList(ctx context.Context, filter filters.Args) (volumet
22 22
 		query.Set("filters", filterJSON)
23 23
 	}
24 24
 	resp, err := cli.get(ctx, "/volumes", query, nil)
25
+	defer ensureReaderClosed(resp)
25 26
 	if err != nil {
26 27
 		return volumes, err
27 28
 	}
28 29
 
29 30
 	err = json.NewDecoder(resp.body).Decode(&volumes)
30
-	ensureReaderClosed(resp)
31 31
 	return volumes, err
32 32
 }
... ...
@@ -23,10 +23,10 @@ func (cli *Client) VolumesPrune(ctx context.Context, pruneFilters filters.Args)
23 23
 	}
24 24
 
25 25
 	serverResp, err := cli.post(ctx, "/volumes/prune", query, nil, nil)
26
+	defer ensureReaderClosed(serverResp)
26 27
 	if err != nil {
27 28
 		return report, err
28 29
 	}
29
-	defer ensureReaderClosed(serverResp)
30 30
 
31 31
 	if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
32 32
 		return report, fmt.Errorf("Error retrieving volume prune report: %v", err)
... ...
@@ -16,6 +16,6 @@ func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool
16 16
 		}
17 17
 	}
18 18
 	resp, err := cli.delete(ctx, "/volumes/"+volumeID, query, nil)
19
-	ensureReaderClosed(resp)
19
+	defer ensureReaderClosed(resp)
20 20
 	return wrapResponseError(err, resp, "volume", volumeID)
21 21
 }