Browse code

client/node: Wrap options and output

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>

Paweł Gronowski authored on 2025/10/22 19:01:37
Showing 22 changed files
... ...
@@ -137,10 +137,10 @@ type NetworkAPIClient interface {
137 137
 
138 138
 // NodeAPIClient defines API client methods for the nodes
139 139
 type NodeAPIClient interface {
140
-	NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error)
141
-	NodeList(ctx context.Context, options NodeListOptions) ([]swarm.Node, error)
142
-	NodeRemove(ctx context.Context, nodeID string, options NodeRemoveOptions) error
143
-	NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error
140
+	NodeInspect(ctx context.Context, nodeID string, options NodeInspectOptions) (NodeInspectResult, error)
141
+	NodeList(ctx context.Context, options NodeListOptions) (NodeListResult, error)
142
+	NodeRemove(ctx context.Context, nodeID string, options NodeRemoveOptions) (NodeRemoveResult, error)
143
+	NodeUpdate(ctx context.Context, nodeID string, options NodeUpdateOptions) (NodeUpdateResult, error)
144 144
 }
145 145
 
146 146
 // PluginAPIClient defines API client methods for the plugins
... ...
@@ -9,25 +9,30 @@ import (
9 9
 	"github.com/moby/moby/api/types/swarm"
10 10
 )
11 11
 
12
-// NodeInspectWithRaw returns the node information.
13
-func (cli *Client) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) {
12
+type NodeInspectResult struct {
13
+	Node swarm.Node
14
+	Raw  []byte
15
+}
16
+
17
+// NodeInspect returns the node information.
18
+func (cli *Client) NodeInspect(ctx context.Context, nodeID string, options NodeInspectOptions) (NodeInspectResult, error) {
14 19
 	nodeID, err := trimID("node", nodeID)
15 20
 	if err != nil {
16
-		return swarm.Node{}, nil, err
21
+		return NodeInspectResult{}, err
17 22
 	}
18 23
 	resp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil)
19 24
 	defer ensureReaderClosed(resp)
20 25
 	if err != nil {
21
-		return swarm.Node{}, nil, err
26
+		return NodeInspectResult{}, err
22 27
 	}
23 28
 
24 29
 	body, err := io.ReadAll(resp.Body)
25 30
 	if err != nil {
26
-		return swarm.Node{}, nil, err
31
+		return NodeInspectResult{}, err
27 32
 	}
28 33
 
29 34
 	var response swarm.Node
30 35
 	rdr := bytes.NewReader(body)
31 36
 	err = json.NewDecoder(rdr).Decode(&response)
32
-	return response, body, err
37
+	return NodeInspectResult{Node: response, Raw: body}, err
33 38
 }
... ...
@@ -19,7 +19,7 @@ func TestNodeInspectError(t *testing.T) {
19 19
 	client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
20 20
 	assert.NilError(t, err)
21 21
 
22
-	_, _, err = client.NodeInspectWithRaw(context.Background(), "nothing")
22
+	_, err = client.NodeInspect(context.Background(), "nothing", NodeInspectOptions{})
23 23
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
24 24
 }
25 25
 
... ...
@@ -27,7 +27,7 @@ func TestNodeInspectNodeNotFound(t *testing.T) {
27 27
 	client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusNotFound, "Server error")))
28 28
 	assert.NilError(t, err)
29 29
 
30
-	_, _, err = client.NodeInspectWithRaw(context.Background(), "unknown")
30
+	_, err = client.NodeInspect(context.Background(), "unknown", NodeInspectOptions{})
31 31
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
32 32
 }
33 33
 
... ...
@@ -36,11 +36,11 @@ func TestNodeInspectWithEmptyID(t *testing.T) {
36 36
 		return nil, errors.New("should not make request")
37 37
 	}))
38 38
 	assert.NilError(t, err)
39
-	_, _, err = client.NodeInspectWithRaw(context.Background(), "")
39
+	_, err = client.NodeInspect(context.Background(), "", NodeInspectOptions{})
40 40
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
41 41
 	assert.Check(t, is.ErrorContains(err, "value is empty"))
42 42
 
43
-	_, _, err = client.NodeInspectWithRaw(context.Background(), "    ")
43
+	_, err = client.NodeInspect(context.Background(), "    ", NodeInspectOptions{})
44 44
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
45 45
 	assert.Check(t, is.ErrorContains(err, "value is empty"))
46 46
 }
... ...
@@ -64,7 +64,7 @@ func TestNodeInspect(t *testing.T) {
64 64
 	}))
65 65
 	assert.NilError(t, err)
66 66
 
67
-	nodeInspect, _, err := client.NodeInspectWithRaw(context.Background(), "node_id")
67
+	result, err := client.NodeInspect(context.Background(), "node_id", NodeInspectOptions{})
68 68
 	assert.NilError(t, err)
69
-	assert.Check(t, is.Equal(nodeInspect.ID, "node_id"))
69
+	assert.Check(t, is.Equal(result.Node.ID, "node_id"))
70 70
 }
... ...
@@ -8,17 +8,21 @@ import (
8 8
 	"github.com/moby/moby/api/types/swarm"
9 9
 )
10 10
 
11
+type NodeListResult struct {
12
+	Items []swarm.Node
13
+}
14
+
11 15
 // NodeList returns the list of nodes.
12
-func (cli *Client) NodeList(ctx context.Context, options NodeListOptions) ([]swarm.Node, error) {
16
+func (cli *Client) NodeList(ctx context.Context, options NodeListOptions) (NodeListResult, error) {
13 17
 	query := url.Values{}
14 18
 	options.Filters.updateURLValues(query)
15 19
 	resp, err := cli.get(ctx, "/nodes", query, nil)
16 20
 	defer ensureReaderClosed(resp)
17 21
 	if err != nil {
18
-		return nil, err
22
+		return NodeListResult{}, err
19 23
 	}
20 24
 
21 25
 	var nodes []swarm.Node
22 26
 	err = json.NewDecoder(resp.Body).Decode(&nodes)
23
-	return nodes, err
27
+	return NodeListResult{Items: nodes}, err
24 28
 }
... ...
@@ -76,8 +76,8 @@ func TestNodeList(t *testing.T) {
76 76
 		}))
77 77
 		assert.NilError(t, err)
78 78
 
79
-		nodes, err := client.NodeList(context.Background(), listCase.options)
79
+		result, err := client.NodeList(context.Background(), listCase.options)
80 80
 		assert.NilError(t, err)
81
-		assert.Check(t, is.Len(nodes, 2))
81
+		assert.Check(t, is.Len(result.Items, 2))
82 82
 	}
83 83
 }
... ...
@@ -5,11 +5,14 @@ import (
5 5
 	"net/url"
6 6
 )
7 7
 
8
+type NodeRemoveResult struct {
9
+}
10
+
8 11
 // NodeRemove removes a Node.
9
-func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options NodeRemoveOptions) error {
12
+func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options NodeRemoveOptions) (NodeRemoveResult, error) {
10 13
 	nodeID, err := trimID("node", nodeID)
11 14
 	if err != nil {
12
-		return err
15
+		return NodeRemoveResult{}, err
13 16
 	}
14 17
 
15 18
 	query := url.Values{}
... ...
@@ -19,5 +22,5 @@ func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options NodeRe
19 19
 
20 20
 	resp, err := cli.delete(ctx, "/nodes/"+nodeID, query, nil)
21 21
 	defer ensureReaderClosed(resp)
22
-	return err
22
+	return NodeRemoveResult{}, err
23 23
 }
... ...
@@ -17,14 +17,14 @@ func TestNodeRemoveError(t *testing.T) {
17 17
 	client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
18 18
 	assert.NilError(t, err)
19 19
 
20
-	err = client.NodeRemove(context.Background(), "node_id", NodeRemoveOptions{Force: false})
20
+	_, err = client.NodeRemove(context.Background(), "node_id", NodeRemoveOptions{Force: false})
21 21
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
22 22
 
23
-	err = client.NodeRemove(context.Background(), "", NodeRemoveOptions{Force: false})
23
+	_, err = client.NodeRemove(context.Background(), "", NodeRemoveOptions{Force: false})
24 24
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
25 25
 	assert.Check(t, is.ErrorContains(err, "value is empty"))
26 26
 
27
-	err = client.NodeRemove(context.Background(), "    ", NodeRemoveOptions{Force: false})
27
+	_, err = client.NodeRemove(context.Background(), "    ", NodeRemoveOptions{Force: false})
28 28
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
29 29
 	assert.Check(t, is.ErrorContains(err, "value is empty"))
30 30
 }
... ...
@@ -62,7 +62,7 @@ func TestNodeRemove(t *testing.T) {
62 62
 		}))
63 63
 		assert.NilError(t, err)
64 64
 
65
-		err = client.NodeRemove(context.Background(), "node_id", NodeRemoveOptions{Force: removeCase.force})
65
+		_, err = client.NodeRemove(context.Background(), "node_id", NodeRemoveOptions{Force: removeCase.force})
66 66
 		assert.NilError(t, err)
67 67
 	}
68 68
 }
... ...
@@ -3,20 +3,21 @@ package client
3 3
 import (
4 4
 	"context"
5 5
 	"net/url"
6
-
7
-	"github.com/moby/moby/api/types/swarm"
8 6
 )
9 7
 
8
+type NodeUpdateResult struct {
9
+}
10
+
10 11
 // NodeUpdate updates a Node.
11
-func (cli *Client) NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error {
12
+func (cli *Client) NodeUpdate(ctx context.Context, nodeID string, options NodeUpdateOptions) (NodeUpdateResult, error) {
12 13
 	nodeID, err := trimID("node", nodeID)
13 14
 	if err != nil {
14
-		return err
15
+		return NodeUpdateResult{}, err
15 16
 	}
16 17
 
17 18
 	query := url.Values{}
18
-	query.Set("version", version.String())
19
-	resp, err := cli.post(ctx, "/nodes/"+nodeID+"/update", query, node, nil)
19
+	query.Set("version", options.Version.String())
20
+	resp, err := cli.post(ctx, "/nodes/"+nodeID+"/update", query, options.Node, nil)
20 21
 	defer ensureReaderClosed(resp)
21
-	return err
22
+	return NodeUpdateResult{}, err
22 23
 }
... ...
@@ -17,14 +17,23 @@ func TestNodeUpdateError(t *testing.T) {
17 17
 	client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
18 18
 	assert.NilError(t, err)
19 19
 
20
-	err = client.NodeUpdate(context.Background(), "node_id", swarm.Version{}, swarm.NodeSpec{})
20
+	_, err = client.NodeUpdate(context.Background(), "node_id", NodeUpdateOptions{
21
+		Version: swarm.Version{},
22
+		Node:    swarm.NodeSpec{},
23
+	})
21 24
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
22 25
 
23
-	err = client.NodeUpdate(context.Background(), "", swarm.Version{}, swarm.NodeSpec{})
26
+	_, err = client.NodeUpdate(context.Background(), "", NodeUpdateOptions{
27
+		Version: swarm.Version{},
28
+		Node:    swarm.NodeSpec{},
29
+	})
24 30
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
25 31
 	assert.Check(t, is.ErrorContains(err, "value is empty"))
26 32
 
27
-	err = client.NodeUpdate(context.Background(), "    ", swarm.Version{}, swarm.NodeSpec{})
33
+	_, err = client.NodeUpdate(context.Background(), "    ", NodeUpdateOptions{
34
+		Version: swarm.Version{},
35
+		Node:    swarm.NodeSpec{},
36
+	})
28 37
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
29 38
 	assert.Check(t, is.ErrorContains(err, "value is empty"))
30 39
 }
... ...
@@ -43,6 +52,9 @@ func TestNodeUpdate(t *testing.T) {
43 43
 	}))
44 44
 	assert.NilError(t, err)
45 45
 
46
-	err = client.NodeUpdate(context.Background(), "node_id", swarm.Version{}, swarm.NodeSpec{})
46
+	_, err = client.NodeUpdate(context.Background(), "node_id", NodeUpdateOptions{
47
+		Version: swarm.Version{},
48
+		Node:    swarm.NodeSpec{},
49
+	})
47 50
 	assert.NilError(t, err)
48 51
 }
49 52
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+package client
1
+
2
+// NodeInspectOptions holds parameters to inspect nodes with.
3
+type NodeInspectOptions struct {
4
+}
0 5
new file mode 100644
... ...
@@ -0,0 +1,9 @@
0
+package client
1
+
2
+import "github.com/moby/moby/api/types/swarm"
3
+
4
+// NodeUpdateOptions holds parameters to update nodes with.
5
+type NodeUpdateOptions struct {
6
+	Version swarm.Version
7
+	Node    swarm.NodeSpec
8
+}
... ...
@@ -178,12 +178,12 @@ func (d *Daemon) CheckLeader(ctx context.Context) func(t *testing.T) (any, strin
178 178
 
179 179
 		errList := "could not get node list"
180 180
 
181
-		ls, err := cli.NodeList(ctx, client.NodeListOptions{})
181
+		result, err := cli.NodeList(ctx, client.NodeListOptions{})
182 182
 		if err != nil {
183 183
 			return err, errList
184 184
 		}
185 185
 
186
-		for _, node := range ls {
186
+		for _, node := range result.Items {
187 187
 			if node.ManagerStatus != nil && node.ManagerStatus.Leader {
188 188
 				return nil, ""
189 189
 			}
... ...
@@ -159,13 +159,13 @@ func JobComplete(ctx context.Context, apiClient client.ServiceAPIClient, service
159 159
 
160 160
 func HasLeader(ctx context.Context, apiClient client.NodeAPIClient) func(log poll.LogT) poll.Result {
161 161
 	return func(log poll.LogT) poll.Result {
162
-		nodes, err := apiClient.NodeList(ctx, client.NodeListOptions{
162
+		result, err := apiClient.NodeList(ctx, client.NodeListOptions{
163 163
 			Filters: make(client.Filters).Add("role", "manager"),
164 164
 		})
165 165
 		if err != nil {
166 166
 			return poll.Error(err)
167 167
 		}
168
-		for _, node := range nodes {
168
+		for _, node := range result.Items {
169 169
 			if node.ManagerStatus != nil && node.ManagerStatus.Leader {
170 170
 				return poll.Success()
171 171
 			}
... ...
@@ -165,11 +165,11 @@ func TestInspectNetwork(t *testing.T) {
165 165
 	t.Run("BeforeLeaderChange", checkNetworkInspect)
166 166
 
167 167
 	leaderID := func() string {
168
-		ls, err := c1.NodeList(ctx, client.NodeListOptions{
168
+		result, err := c1.NodeList(ctx, client.NodeListOptions{
169 169
 			Filters: make(client.Filters).Add("role", "manager"),
170 170
 		})
171 171
 		assert.NilError(t, err)
172
-		for _, node := range ls {
172
+		for _, node := range result.Items {
173 173
 			if node.ManagerStatus != nil && node.ManagerStatus.Leader {
174 174
 				return node.ID
175 175
 			}
... ...
@@ -20,7 +20,7 @@ func (d *Daemon) GetNode(ctx context.Context, t testing.TB, id string, errCheck
20 20
 	cli := d.NewClientT(t)
21 21
 	defer cli.Close()
22 22
 
23
-	node, _, err := cli.NodeInspectWithRaw(ctx, id)
23
+	result, err := cli.NodeInspect(ctx, id, client.NodeInspectOptions{})
24 24
 	if err != nil {
25 25
 		for _, f := range errCheck {
26 26
 			if f(err) {
... ...
@@ -28,9 +28,9 @@ func (d *Daemon) GetNode(ctx context.Context, t testing.TB, id string, errCheck
28 28
 			}
29 29
 		}
30 30
 	}
31
-	assert.NilError(t, err, "[%s] (*Daemon).GetNode: NodeInspectWithRaw(%q) failed", d.id, id)
32
-	assert.Check(t, node.ID == id)
33
-	return &node
31
+	assert.NilError(t, err, "[%s] (*Daemon).GetNode: NodeInspect(%q) failed", d.id, id)
32
+	assert.Check(t, result.Node.ID == id)
33
+	return &result.Node
34 34
 }
35 35
 
36 36
 // RemoveNode removes the specified node
... ...
@@ -42,7 +42,7 @@ func (d *Daemon) RemoveNode(ctx context.Context, t testing.TB, id string, force
42 42
 	options := client.NodeRemoveOptions{
43 43
 		Force: force,
44 44
 	}
45
-	err := cli.NodeRemove(ctx, id, options)
45
+	_, err := cli.NodeRemove(ctx, id, options)
46 46
 	assert.NilError(t, err)
47 47
 }
48 48
 
... ...
@@ -58,7 +58,10 @@ func (d *Daemon) UpdateNode(ctx context.Context, t testing.TB, id string, f ...N
58 58
 			fn(node)
59 59
 		}
60 60
 
61
-		err := cli.NodeUpdate(ctx, node.ID, node.Version, node.Spec)
61
+		_, err := cli.NodeUpdate(ctx, node.ID, client.NodeUpdateOptions{
62
+			Version: node.Version,
63
+			Node:    node.Spec,
64
+		})
62 65
 		if i < 10 && err != nil && strings.Contains(err.Error(), "update out of sequence") {
63 66
 			time.Sleep(100 * time.Millisecond)
64 67
 			continue
... ...
@@ -74,8 +77,8 @@ func (d *Daemon) ListNodes(ctx context.Context, t testing.TB) []swarm.Node {
74 74
 	cli := d.NewClientT(t)
75 75
 	defer cli.Close()
76 76
 
77
-	nodes, err := cli.NodeList(ctx, client.NodeListOptions{})
77
+	result, err := cli.NodeList(ctx, client.NodeListOptions{})
78 78
 	assert.NilError(t, err)
79 79
 
80
-	return nodes
80
+	return result.Items
81 81
 }
... ...
@@ -137,10 +137,10 @@ type NetworkAPIClient interface {
137 137
 
138 138
 // NodeAPIClient defines API client methods for the nodes
139 139
 type NodeAPIClient interface {
140
-	NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error)
141
-	NodeList(ctx context.Context, options NodeListOptions) ([]swarm.Node, error)
142
-	NodeRemove(ctx context.Context, nodeID string, options NodeRemoveOptions) error
143
-	NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error
140
+	NodeInspect(ctx context.Context, nodeID string, options NodeInspectOptions) (NodeInspectResult, error)
141
+	NodeList(ctx context.Context, options NodeListOptions) (NodeListResult, error)
142
+	NodeRemove(ctx context.Context, nodeID string, options NodeRemoveOptions) (NodeRemoveResult, error)
143
+	NodeUpdate(ctx context.Context, nodeID string, options NodeUpdateOptions) (NodeUpdateResult, error)
144 144
 }
145 145
 
146 146
 // PluginAPIClient defines API client methods for the plugins
... ...
@@ -9,25 +9,30 @@ import (
9 9
 	"github.com/moby/moby/api/types/swarm"
10 10
 )
11 11
 
12
-// NodeInspectWithRaw returns the node information.
13
-func (cli *Client) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) {
12
+type NodeInspectResult struct {
13
+	Node swarm.Node
14
+	Raw  []byte
15
+}
16
+
17
+// NodeInspect returns the node information.
18
+func (cli *Client) NodeInspect(ctx context.Context, nodeID string, options NodeInspectOptions) (NodeInspectResult, error) {
14 19
 	nodeID, err := trimID("node", nodeID)
15 20
 	if err != nil {
16
-		return swarm.Node{}, nil, err
21
+		return NodeInspectResult{}, err
17 22
 	}
18 23
 	resp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil)
19 24
 	defer ensureReaderClosed(resp)
20 25
 	if err != nil {
21
-		return swarm.Node{}, nil, err
26
+		return NodeInspectResult{}, err
22 27
 	}
23 28
 
24 29
 	body, err := io.ReadAll(resp.Body)
25 30
 	if err != nil {
26
-		return swarm.Node{}, nil, err
31
+		return NodeInspectResult{}, err
27 32
 	}
28 33
 
29 34
 	var response swarm.Node
30 35
 	rdr := bytes.NewReader(body)
31 36
 	err = json.NewDecoder(rdr).Decode(&response)
32
-	return response, body, err
37
+	return NodeInspectResult{Node: response, Raw: body}, err
33 38
 }
... ...
@@ -8,17 +8,21 @@ import (
8 8
 	"github.com/moby/moby/api/types/swarm"
9 9
 )
10 10
 
11
+type NodeListResult struct {
12
+	Items []swarm.Node
13
+}
14
+
11 15
 // NodeList returns the list of nodes.
12
-func (cli *Client) NodeList(ctx context.Context, options NodeListOptions) ([]swarm.Node, error) {
16
+func (cli *Client) NodeList(ctx context.Context, options NodeListOptions) (NodeListResult, error) {
13 17
 	query := url.Values{}
14 18
 	options.Filters.updateURLValues(query)
15 19
 	resp, err := cli.get(ctx, "/nodes", query, nil)
16 20
 	defer ensureReaderClosed(resp)
17 21
 	if err != nil {
18
-		return nil, err
22
+		return NodeListResult{}, err
19 23
 	}
20 24
 
21 25
 	var nodes []swarm.Node
22 26
 	err = json.NewDecoder(resp.Body).Decode(&nodes)
23
-	return nodes, err
27
+	return NodeListResult{Items: nodes}, err
24 28
 }
... ...
@@ -5,11 +5,14 @@ import (
5 5
 	"net/url"
6 6
 )
7 7
 
8
+type NodeRemoveResult struct {
9
+}
10
+
8 11
 // NodeRemove removes a Node.
9
-func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options NodeRemoveOptions) error {
12
+func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options NodeRemoveOptions) (NodeRemoveResult, error) {
10 13
 	nodeID, err := trimID("node", nodeID)
11 14
 	if err != nil {
12
-		return err
15
+		return NodeRemoveResult{}, err
13 16
 	}
14 17
 
15 18
 	query := url.Values{}
... ...
@@ -19,5 +22,5 @@ func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options NodeRe
19 19
 
20 20
 	resp, err := cli.delete(ctx, "/nodes/"+nodeID, query, nil)
21 21
 	defer ensureReaderClosed(resp)
22
-	return err
22
+	return NodeRemoveResult{}, err
23 23
 }
... ...
@@ -3,20 +3,21 @@ package client
3 3
 import (
4 4
 	"context"
5 5
 	"net/url"
6
-
7
-	"github.com/moby/moby/api/types/swarm"
8 6
 )
9 7
 
8
+type NodeUpdateResult struct {
9
+}
10
+
10 11
 // NodeUpdate updates a Node.
11
-func (cli *Client) NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error {
12
+func (cli *Client) NodeUpdate(ctx context.Context, nodeID string, options NodeUpdateOptions) (NodeUpdateResult, error) {
12 13
 	nodeID, err := trimID("node", nodeID)
13 14
 	if err != nil {
14
-		return err
15
+		return NodeUpdateResult{}, err
15 16
 	}
16 17
 
17 18
 	query := url.Values{}
18
-	query.Set("version", version.String())
19
-	resp, err := cli.post(ctx, "/nodes/"+nodeID+"/update", query, node, nil)
19
+	query.Set("version", options.Version.String())
20
+	resp, err := cli.post(ctx, "/nodes/"+nodeID+"/update", query, options.Node, nil)
20 21
 	defer ensureReaderClosed(resp)
21
-	return err
22
+	return NodeUpdateResult{}, err
22 23
 }
23 24
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+package client
1
+
2
+// NodeInspectOptions holds parameters to inspect nodes with.
3
+type NodeInspectOptions struct {
4
+}
0 5
new file mode 100644
... ...
@@ -0,0 +1,9 @@
0
+package client
1
+
2
+import "github.com/moby/moby/api/types/swarm"
3
+
4
+// NodeUpdateOptions holds parameters to update nodes with.
5
+type NodeUpdateOptions struct {
6
+	Version swarm.Version
7
+	Node    swarm.NodeSpec
8
+}