Browse code

client: refactor task responses

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Austin Vazquez authored on 2025/10/21 04:30:20
Showing 17 changed files
... ...
@@ -168,8 +168,8 @@ type ServiceAPIClient interface {
168 168
 	ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
169 169
 	ServiceLogs(ctx context.Context, serviceID string, options ContainerLogsOptions) (io.ReadCloser, error)
170 170
 	TaskLogs(ctx context.Context, taskID string, options ContainerLogsOptions) (io.ReadCloser, error)
171
-	TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error)
172
-	TaskList(ctx context.Context, options TaskListOptions) ([]swarm.Task, error)
171
+	TaskInspect(ctx context.Context, taskID string) (TaskInspectResult, error)
172
+	TaskList(ctx context.Context, options TaskListOptions) (TaskListResult, error)
173 173
 }
174 174
 
175 175
 // SwarmAPIClient defines API client methods for the swarm
176 176
deleted file mode 100644
... ...
@@ -1,6 +0,0 @@
1
-package client
2
-
3
-// TaskListOptions holds parameters to list tasks with.
4
-type TaskListOptions struct {
5
-	Filters Filters
6
-}
... ...
@@ -1,34 +1,31 @@
1 1
 package client
2 2
 
3 3
 import (
4
-	"bytes"
5 4
 	"context"
6
-	"encoding/json"
7
-	"io"
8 5
 
9 6
 	"github.com/moby/moby/api/types/swarm"
10 7
 )
11 8
 
12
-// TaskInspectWithRaw returns the task information and its raw representation.
13
-func (cli *Client) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) {
9
+// TaskInspectResult contains the result of a task inspection.
10
+type TaskInspectResult struct {
11
+	Task swarm.Task
12
+	Raw  []byte
13
+}
14
+
15
+// TaskInspect returns the task information and its raw representation.
16
+func (cli *Client) TaskInspect(ctx context.Context, taskID string) (TaskInspectResult, error) {
14 17
 	taskID, err := trimID("task", taskID)
15 18
 	if err != nil {
16
-		return swarm.Task{}, nil, err
19
+		return TaskInspectResult{}, err
17 20
 	}
18 21
 
19 22
 	resp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil)
20 23
 	defer ensureReaderClosed(resp)
21 24
 	if err != nil {
22
-		return swarm.Task{}, nil, err
23
-	}
24
-
25
-	body, err := io.ReadAll(resp.Body)
26
-	if err != nil {
27
-		return swarm.Task{}, nil, err
25
+		return TaskInspectResult{}, err
28 26
 	}
29 27
 
30
-	var response swarm.Task
31
-	rdr := bytes.NewReader(body)
32
-	err = json.NewDecoder(rdr).Decode(&response)
33
-	return response, body, err
28
+	var out TaskInspectResult
29
+	out.Raw, err = decodeWithRaw(resp, &out.Task)
30
+	return out, err
34 31
 }
... ...
@@ -19,7 +19,7 @@ func TestTaskInspectError(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.TaskInspectWithRaw(context.Background(), "nothing")
22
+	_, err = client.TaskInspect(context.Background(), "nothing")
23 23
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
24 24
 }
25 25
 
... ...
@@ -28,11 +28,11 @@ func TestTaskInspectWithEmptyID(t *testing.T) {
28 28
 		return nil, errors.New("should not make request")
29 29
 	}))
30 30
 	assert.NilError(t, err)
31
-	_, _, err = client.TaskInspectWithRaw(context.Background(), "")
31
+	_, err = client.TaskInspect(context.Background(), "")
32 32
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
33 33
 	assert.Check(t, is.ErrorContains(err, "value is empty"))
34 34
 
35
-	_, _, err = client.TaskInspectWithRaw(context.Background(), "    ")
35
+	_, err = client.TaskInspect(context.Background(), "    ")
36 36
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
37 37
 	assert.Check(t, is.ErrorContains(err, "value is empty"))
38 38
 }
... ...
@@ -56,7 +56,7 @@ func TestTaskInspect(t *testing.T) {
56 56
 	}))
57 57
 	assert.NilError(t, err)
58 58
 
59
-	taskInspect, _, err := client.TaskInspectWithRaw(context.Background(), "task_id")
59
+	result, err := client.TaskInspect(context.Background(), "task_id")
60 60
 	assert.NilError(t, err)
61
-	assert.Check(t, is.Equal(taskInspect.ID, "task_id"))
61
+	assert.Check(t, is.Equal(result.Task.ID, "task_id"))
62 62
 }
... ...
@@ -8,8 +8,18 @@ import (
8 8
 	"github.com/moby/moby/api/types/swarm"
9 9
 )
10 10
 
11
+// TaskListOptions holds parameters to list tasks with.
12
+type TaskListOptions struct {
13
+	Filters Filters
14
+}
15
+
16
+// TaskListResult contains the result of a task list operation.
17
+type TaskListResult struct {
18
+	Tasks []swarm.Task
19
+}
20
+
11 21
 // TaskList returns the list of tasks.
12
-func (cli *Client) TaskList(ctx context.Context, options TaskListOptions) ([]swarm.Task, error) {
22
+func (cli *Client) TaskList(ctx context.Context, options TaskListOptions) (TaskListResult, error) {
13 23
 	query := url.Values{}
14 24
 
15 25
 	options.Filters.updateURLValues(query)
... ...
@@ -17,10 +27,10 @@ func (cli *Client) TaskList(ctx context.Context, options TaskListOptions) ([]swa
17 17
 	resp, err := cli.get(ctx, "/tasks", query, nil)
18 18
 	defer ensureReaderClosed(resp)
19 19
 	if err != nil {
20
-		return nil, err
20
+		return TaskListResult{}, err
21 21
 	}
22 22
 
23 23
 	var tasks []swarm.Task
24 24
 	err = json.NewDecoder(resp.Body).Decode(&tasks)
25
-	return tasks, err
25
+	return TaskListResult{Tasks: tasks}, err
26 26
 }
... ...
@@ -75,8 +75,8 @@ func TestTaskList(t *testing.T) {
75 75
 		}))
76 76
 		assert.NilError(t, err)
77 77
 
78
-		tasks, err := client.TaskList(context.Background(), listCase.options)
78
+		result, err := client.TaskList(context.Background(), listCase.options)
79 79
 		assert.NilError(t, err)
80
-		assert.Check(t, is.Len(tasks, 2))
80
+		assert.Check(t, is.Len(result.Tasks, 2))
81 81
 	}
82 82
 }
... ...
@@ -103,13 +103,13 @@ func (d *Daemon) CheckRunningTaskNetworks(ctx context.Context) func(t *testing.T
103 103
 		cli := d.NewClientT(t)
104 104
 		defer cli.Close()
105 105
 
106
-		tasks, err := cli.TaskList(ctx, client.TaskListOptions{
106
+		taskResult, err := cli.TaskList(ctx, client.TaskListOptions{
107 107
 			Filters: make(client.Filters).Add("desired-state", "running"),
108 108
 		})
109 109
 		assert.NilError(t, err)
110 110
 
111 111
 		result := make(map[string]int)
112
-		for _, task := range tasks {
112
+		for _, task := range taskResult.Tasks {
113 113
 			for _, network := range task.Spec.Networks {
114 114
 				result[network.Target]++
115 115
 			}
... ...
@@ -124,13 +124,13 @@ func (d *Daemon) CheckRunningTaskImages(ctx context.Context) func(t *testing.T)
124 124
 		cli := d.NewClientT(t)
125 125
 		defer cli.Close()
126 126
 
127
-		tasks, err := cli.TaskList(ctx, client.TaskListOptions{
127
+		taskResult, err := cli.TaskList(ctx, client.TaskListOptions{
128 128
 			Filters: make(client.Filters).Add("desired-state", "running"),
129 129
 		})
130 130
 		assert.NilError(t, err)
131 131
 
132 132
 		result := make(map[string]int)
133
-		for _, task := range tasks {
133
+		for _, task := range taskResult.Tasks {
134 134
 			if task.Status.State == swarm.TaskStateRunning && task.Spec.ContainerSpec != nil {
135 135
 				result[task.Spec.ContainerSpec.Image]++
136 136
 			}
... ...
@@ -204,14 +204,14 @@ func ServiceWithPidsLimit(limit int64) ServiceSpecOpt {
204 204
 func GetRunningTasks(ctx context.Context, t *testing.T, c client.ServiceAPIClient, serviceID string) []swarmtypes.Task {
205 205
 	t.Helper()
206 206
 
207
-	tasks, err := c.TaskList(ctx, client.TaskListOptions{
207
+	result, err := c.TaskList(ctx, client.TaskListOptions{
208 208
 		Filters: make(client.Filters).
209 209
 			Add("service", serviceID).
210 210
 			Add("desired-state", "running"),
211 211
 	})
212 212
 
213 213
 	assert.NilError(t, err)
214
-	return tasks
214
+	return result.Tasks
215 215
 }
216 216
 
217 217
 // ExecTask runs the passed in exec config on the given task
... ...
@@ -12,15 +12,15 @@ import (
12 12
 // NoTasksForService verifies that there are no more tasks for the given service
13 13
 func NoTasksForService(ctx context.Context, apiClient client.ServiceAPIClient, serviceID string) func(log poll.LogT) poll.Result {
14 14
 	return func(log poll.LogT) poll.Result {
15
-		tasks, err := apiClient.TaskList(ctx, client.TaskListOptions{
15
+		result, err := apiClient.TaskList(ctx, client.TaskListOptions{
16 16
 			Filters: make(client.Filters).Add("service", serviceID),
17 17
 		})
18 18
 		if err == nil {
19
-			if len(tasks) == 0 {
19
+			if len(result.Tasks) == 0 {
20 20
 				return poll.Success()
21 21
 			}
22
-			if len(tasks) > 0 {
23
-				return poll.Continue("task count for service %s at %d waiting for 0", serviceID, len(tasks))
22
+			if len(result.Tasks) > 0 {
23
+				return poll.Continue("task count for service %s at %d waiting for 0", serviceID, len(result.Tasks))
24 24
 			}
25 25
 			return poll.Continue("waiting for tasks for service %s to be deleted", serviceID)
26 26
 		}
... ...
@@ -32,14 +32,14 @@ func NoTasksForService(ctx context.Context, apiClient client.ServiceAPIClient, s
32 32
 // NoTasks verifies that all tasks are gone
33 33
 func NoTasks(ctx context.Context, apiClient client.ServiceAPIClient) func(log poll.LogT) poll.Result {
34 34
 	return func(log poll.LogT) poll.Result {
35
-		tasks, err := apiClient.TaskList(ctx, client.TaskListOptions{})
35
+		result, err := apiClient.TaskList(ctx, client.TaskListOptions{})
36 36
 		switch {
37 37
 		case err != nil:
38 38
 			return poll.Error(err)
39
-		case len(tasks) == 0:
39
+		case len(result.Tasks) == 0:
40 40
 			return poll.Success()
41 41
 		default:
42
-			return poll.Continue("waiting for all tasks to be removed: task count at %d", len(tasks))
42
+			return poll.Continue("waiting for all tasks to be removed: task count at %d", len(result.Tasks))
43 43
 		}
44 44
 	}
45 45
 }
... ...
@@ -47,12 +47,12 @@ func NoTasks(ctx context.Context, apiClient client.ServiceAPIClient) func(log po
47 47
 // RunningTasksCount verifies there are `instances` tasks running for `serviceID`
48 48
 func RunningTasksCount(ctx context.Context, apiClient client.ServiceAPIClient, serviceID string, instances uint64) func(log poll.LogT) poll.Result {
49 49
 	return func(log poll.LogT) poll.Result {
50
-		tasks, err := apiClient.TaskList(ctx, client.TaskListOptions{
50
+		result, err := apiClient.TaskList(ctx, client.TaskListOptions{
51 51
 			Filters: make(client.Filters).Add("service", serviceID),
52 52
 		})
53 53
 		var running int
54 54
 		var taskError string
55
-		for _, task := range tasks {
55
+		for _, task := range result.Tasks {
56 56
 			switch task.Status.State {
57 57
 			case swarmtypes.TaskStateRunning:
58 58
 				running++
... ...
@@ -76,7 +76,7 @@ func RunningTasksCount(ctx context.Context, apiClient client.ServiceAPIClient, s
76 76
 		case running == int(instances):
77 77
 			return poll.Success()
78 78
 		default:
79
-			return poll.Continue("running task count at %d waiting for %d (total tasks: %d)", running, instances, len(tasks))
79
+			return poll.Continue("running task count at %d waiting for %d (total tasks: %d)", running, instances, len(result.Tasks))
80 80
 		}
81 81
 	}
82 82
 }
... ...
@@ -97,7 +97,7 @@ func JobComplete(ctx context.Context, apiClient client.ServiceAPIClient, service
97 97
 	previousResult := ""
98 98
 
99 99
 	return func(log poll.LogT) poll.Result {
100
-		tasks, err := apiClient.TaskList(ctx, client.TaskListOptions{
100
+		result, err := apiClient.TaskList(ctx, client.TaskListOptions{
101 101
 			Filters: filter,
102 102
 		})
103 103
 		if err != nil {
... ...
@@ -110,7 +110,7 @@ func JobComplete(ctx context.Context, apiClient client.ServiceAPIClient, service
110 110
 		var runningSlot []int
111 111
 		var runningID []string
112 112
 
113
-		for _, task := range tasks {
113
+		for _, task := range result.Tasks {
114 114
 			// make sure the task has the same job iteration
115 115
 			if task.JobIteration == nil || task.JobIteration.Index != jobIteration.Index {
116 116
 				continue
... ...
@@ -368,19 +368,19 @@ func TestCreateServiceSysctls(t *testing.T) {
368 368
 		// more complex)
369 369
 
370 370
 		// get all tasks of the service, so we can get the container
371
-		tasks, err := apiClient.TaskList(ctx, client.TaskListOptions{
371
+		taskResult, err := apiClient.TaskList(ctx, client.TaskListOptions{
372 372
 			Filters: make(client.Filters).Add("service", serviceID),
373 373
 		})
374 374
 		assert.NilError(t, err)
375
-		assert.Check(t, is.Equal(len(tasks), 1))
375
+		assert.Check(t, is.Equal(len(taskResult.Tasks), 1))
376 376
 
377 377
 		// verify that the container has the sysctl option set
378
-		ctnr, err := apiClient.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID)
378
+		ctnr, err := apiClient.ContainerInspect(ctx, taskResult.Tasks[0].Status.ContainerStatus.ContainerID)
379 379
 		assert.NilError(t, err)
380 380
 		assert.DeepEqual(t, ctnr.HostConfig.Sysctls, expectedSysctls)
381 381
 
382 382
 		// verify that the task has the sysctl option set in the task object
383
-		assert.DeepEqual(t, tasks[0].Spec.ContainerSpec.Sysctls, expectedSysctls)
383
+		assert.DeepEqual(t, taskResult.Tasks[0].Spec.ContainerSpec.Sysctls, expectedSysctls)
384 384
 
385 385
 		// verify that the service also has the sysctl set in the spec.
386 386
 		service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, client.ServiceInspectOptions{})
... ...
@@ -438,21 +438,21 @@ func TestCreateServiceCapabilities(t *testing.T) {
438 438
 	// level has been tested elsewhere.
439 439
 
440 440
 	// get all tasks of the service, so we can get the container
441
-	tasks, err := apiClient.TaskList(ctx, client.TaskListOptions{
441
+	taskResult, err := apiClient.TaskList(ctx, client.TaskListOptions{
442 442
 		Filters: make(client.Filters).Add("service", serviceID),
443 443
 	})
444 444
 	assert.NilError(t, err)
445
-	assert.Check(t, is.Equal(len(tasks), 1))
445
+	assert.Check(t, is.Equal(len(taskResult.Tasks), 1))
446 446
 
447 447
 	// verify that the container has the capabilities option set
448
-	ctnr, err := apiClient.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID)
448
+	ctnr, err := apiClient.ContainerInspect(ctx, taskResult.Tasks[0].Status.ContainerStatus.ContainerID)
449 449
 	assert.NilError(t, err)
450 450
 	assert.DeepEqual(t, ctnr.HostConfig.CapAdd, capAdd)
451 451
 	assert.DeepEqual(t, ctnr.HostConfig.CapDrop, capDrop)
452 452
 
453 453
 	// verify that the task has the capabilities option set in the task object
454
-	assert.DeepEqual(t, tasks[0].Spec.ContainerSpec.CapabilityAdd, capAdd)
455
-	assert.DeepEqual(t, tasks[0].Spec.ContainerSpec.CapabilityDrop, capDrop)
454
+	assert.DeepEqual(t, taskResult.Tasks[0].Spec.ContainerSpec.CapabilityAdd, capAdd)
455
+	assert.DeepEqual(t, taskResult.Tasks[0].Spec.ContainerSpec.CapabilityDrop, capDrop)
456 456
 
457 457
 	// verify that the service also has the capabilities set in the spec.
458 458
 	service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, client.ServiceInspectOptions{})
... ...
@@ -52,12 +52,12 @@ func TestServiceListWithStatuses(t *testing.T) {
52 52
 		// serviceContainerCount function does not do. instead, we'll use a
53 53
 		// bespoke closure right here.
54 54
 		poll.WaitOn(t, func(log poll.LogT) poll.Result {
55
-			tasks, err := apiClient.TaskList(ctx, client.TaskListOptions{
55
+			taskResult, err := apiClient.TaskList(ctx, client.TaskListOptions{
56 56
 				Filters: make(client.Filters).Add("service", id),
57 57
 			})
58 58
 
59 59
 			running := 0
60
-			for _, task := range tasks {
60
+			for _, task := range taskResult.Tasks {
61 61
 				if task.Status.State == swarmtypes.TaskStateRunning {
62 62
 					running++
63 63
 				}
... ...
@@ -71,7 +71,7 @@ func TestServiceListWithStatuses(t *testing.T) {
71 71
 			default:
72 72
 				return poll.Continue(
73 73
 					"running task count %d (%d total), waiting for %d",
74
-					running, len(tasks), i+1,
74
+					running, len(taskResult.Tasks), i+1,
75 75
 				)
76 76
 			}
77 77
 		})
... ...
@@ -325,13 +325,13 @@ func TestServiceUpdatePidsLimit(t *testing.T) {
325 325
 
326 326
 func getServiceTaskContainer(ctx context.Context, t *testing.T, cli client.APIClient, serviceID string) container.InspectResponse {
327 327
 	t.Helper()
328
-	tasks, err := cli.TaskList(ctx, client.TaskListOptions{
328
+	taskResult, err := cli.TaskList(ctx, client.TaskListOptions{
329 329
 		Filters: make(client.Filters).Add("service", serviceID).Add("desired-state", "running"),
330 330
 	})
331 331
 	assert.NilError(t, err)
332
-	assert.Assert(t, len(tasks) > 0)
332
+	assert.Assert(t, len(taskResult.Tasks) > 0)
333 333
 
334
-	ctr, err := cli.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID)
334
+	ctr, err := cli.ContainerInspect(ctx, taskResult.Tasks[0].Status.ContainerStatus.ContainerID)
335 335
 	assert.NilError(t, err)
336 336
 	assert.Equal(t, ctr.State.Running, true)
337 337
 	return ctr
... ...
@@ -70,9 +70,9 @@ func (d *Daemon) GetServiceTasksWithFilters(ctx context.Context, t testing.TB, s
70 70
 		Filters: filterArgs,
71 71
 	}
72 72
 
73
-	tasks, err := cli.TaskList(ctx, options)
73
+	result, err := cli.TaskList(ctx, options)
74 74
 	assert.NilError(t, err)
75
-	return tasks
75
+	return result.Tasks
76 76
 }
77 77
 
78 78
 // UpdateService updates a swarm service with the specified service constructor
... ...
@@ -116,7 +116,7 @@ func (d *Daemon) GetTask(ctx context.Context, t testing.TB, id string) swarm.Tas
116 116
 	cli := d.NewClientT(t)
117 117
 	defer cli.Close()
118 118
 
119
-	task, _, err := cli.TaskInspectWithRaw(ctx, id)
119
+	result, err := cli.TaskInspect(ctx, id)
120 120
 	assert.NilError(t, err)
121
-	return task
121
+	return result.Task
122 122
 }
... ...
@@ -168,8 +168,8 @@ type ServiceAPIClient interface {
168 168
 	ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
169 169
 	ServiceLogs(ctx context.Context, serviceID string, options ContainerLogsOptions) (io.ReadCloser, error)
170 170
 	TaskLogs(ctx context.Context, taskID string, options ContainerLogsOptions) (io.ReadCloser, error)
171
-	TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error)
172
-	TaskList(ctx context.Context, options TaskListOptions) ([]swarm.Task, error)
171
+	TaskInspect(ctx context.Context, taskID string) (TaskInspectResult, error)
172
+	TaskList(ctx context.Context, options TaskListOptions) (TaskListResult, error)
173 173
 }
174 174
 
175 175
 // SwarmAPIClient defines API client methods for the swarm
176 176
deleted file mode 100644
... ...
@@ -1,6 +0,0 @@
1
-package client
2
-
3
-// TaskListOptions holds parameters to list tasks with.
4
-type TaskListOptions struct {
5
-	Filters Filters
6
-}
... ...
@@ -1,34 +1,31 @@
1 1
 package client
2 2
 
3 3
 import (
4
-	"bytes"
5 4
 	"context"
6
-	"encoding/json"
7
-	"io"
8 5
 
9 6
 	"github.com/moby/moby/api/types/swarm"
10 7
 )
11 8
 
12
-// TaskInspectWithRaw returns the task information and its raw representation.
13
-func (cli *Client) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) {
9
+// TaskInspectResult contains the result of a task inspection.
10
+type TaskInspectResult struct {
11
+	Task swarm.Task
12
+	Raw  []byte
13
+}
14
+
15
+// TaskInspect returns the task information and its raw representation.
16
+func (cli *Client) TaskInspect(ctx context.Context, taskID string) (TaskInspectResult, error) {
14 17
 	taskID, err := trimID("task", taskID)
15 18
 	if err != nil {
16
-		return swarm.Task{}, nil, err
19
+		return TaskInspectResult{}, err
17 20
 	}
18 21
 
19 22
 	resp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil)
20 23
 	defer ensureReaderClosed(resp)
21 24
 	if err != nil {
22
-		return swarm.Task{}, nil, err
23
-	}
24
-
25
-	body, err := io.ReadAll(resp.Body)
26
-	if err != nil {
27
-		return swarm.Task{}, nil, err
25
+		return TaskInspectResult{}, err
28 26
 	}
29 27
 
30
-	var response swarm.Task
31
-	rdr := bytes.NewReader(body)
32
-	err = json.NewDecoder(rdr).Decode(&response)
33
-	return response, body, err
28
+	var out TaskInspectResult
29
+	out.Raw, err = decodeWithRaw(resp, &out.Task)
30
+	return out, err
34 31
 }
... ...
@@ -8,8 +8,18 @@ import (
8 8
 	"github.com/moby/moby/api/types/swarm"
9 9
 )
10 10
 
11
+// TaskListOptions holds parameters to list tasks with.
12
+type TaskListOptions struct {
13
+	Filters Filters
14
+}
15
+
16
+// TaskListResult contains the result of a task list operation.
17
+type TaskListResult struct {
18
+	Tasks []swarm.Task
19
+}
20
+
11 21
 // TaskList returns the list of tasks.
12
-func (cli *Client) TaskList(ctx context.Context, options TaskListOptions) ([]swarm.Task, error) {
22
+func (cli *Client) TaskList(ctx context.Context, options TaskListOptions) (TaskListResult, error) {
13 23
 	query := url.Values{}
14 24
 
15 25
 	options.Filters.updateURLValues(query)
... ...
@@ -17,10 +27,10 @@ func (cli *Client) TaskList(ctx context.Context, options TaskListOptions) ([]swa
17 17
 	resp, err := cli.get(ctx, "/tasks", query, nil)
18 18
 	defer ensureReaderClosed(resp)
19 19
 	if err != nil {
20
-		return nil, err
20
+		return TaskListResult{}, err
21 21
 	}
22 22
 
23 23
 	var tasks []swarm.Task
24 24
 	err = json.NewDecoder(resp.Body).Decode(&tasks)
25
-	return tasks, err
25
+	return TaskListResult{Tasks: tasks}, err
26 26
 }