Browse code

client: make TaskLogsResult an interface

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

Sebastiaan van Stijn authored on 2025/11/04 02:23:57
Showing 2 changed files
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"context"
5 5
 	"io"
6 6
 	"net/url"
7
-	"sync"
8 7
 	"time"
9 8
 
10 9
 	"github.com/moby/moby/client/internal/timestamp"
... ...
@@ -24,9 +23,8 @@ type TaskLogsOptions struct {
24 24
 
25 25
 // TaskLogsResult holds the result of a task logs operation.
26 26
 // It implements [io.ReadCloser].
27
-type TaskLogsResult struct {
28
-	rc    io.ReadCloser
29
-	close func() error
27
+type TaskLogsResult interface {
28
+	io.ReadCloser
30 29
 }
31 30
 
32 31
 // TaskLogs returns the logs generated by a task.
... ...
@@ -44,7 +42,7 @@ func (cli *Client) TaskLogs(ctx context.Context, taskID string, options TaskLogs
44 44
 	if options.Since != "" {
45 45
 		ts, err := timestamp.GetTimestamp(options.Since, time.Now())
46 46
 		if err != nil {
47
-			return TaskLogsResult{}, err
47
+			return nil, err
48 48
 		}
49 49
 		query.Set("since", ts)
50 50
 	}
... ...
@@ -64,33 +62,33 @@ func (cli *Client) TaskLogs(ctx context.Context, taskID string, options TaskLogs
64 64
 
65 65
 	resp, err := cli.get(ctx, "/tasks/"+taskID+"/logs", query, nil)
66 66
 	if err != nil {
67
-		return TaskLogsResult{}, err
67
+		return nil, err
68 68
 	}
69
-	return newTaskLogsResult(resp.Body), nil
69
+	return &taskLogsResult{
70
+		body: resp.Body,
71
+	}, nil
70 72
 }
71 73
 
72
-func newTaskLogsResult(rc io.ReadCloser) TaskLogsResult {
73
-	if rc == nil {
74
-		panic("nil io.ReadCloser")
75
-	}
76
-	return TaskLogsResult{
77
-		rc:    rc,
78
-		close: sync.OnceValue(rc.Close),
79
-	}
74
+type taskLogsResult struct {
75
+	// body must be closed to avoid a resource leak
76
+	body io.ReadCloser
80 77
 }
81 78
 
82
-// Read implements [io.ReadCloser] for LogsResult.
83
-func (r TaskLogsResult) Read(p []byte) (n int, err error) {
84
-	if r.rc == nil {
79
+var (
80
+	_ io.ReadCloser       = (*taskLogsResult)(nil)
81
+	_ ContainerLogsResult = (*taskLogsResult)(nil)
82
+)
83
+
84
+func (r *taskLogsResult) Read(p []byte) (int, error) {
85
+	if r == nil || r.body == nil {
85 86
 		return 0, io.EOF
86 87
 	}
87
-	return r.rc.Read(p)
88
+	return r.body.Read(p)
88 89
 }
89 90
 
90
-// Close implements [io.ReadCloser] for LogsResult.
91
-func (r TaskLogsResult) Close() error {
92
-	if r.close == nil {
91
+func (r *taskLogsResult) Close() error {
92
+	if r == nil || r.body == nil {
93 93
 		return nil
94 94
 	}
95
-	return r.close()
95
+	return r.body.Close()
96 96
 }
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"context"
5 5
 	"io"
6 6
 	"net/url"
7
-	"sync"
8 7
 	"time"
9 8
 
10 9
 	"github.com/moby/moby/client/internal/timestamp"
... ...
@@ -24,9 +23,8 @@ type TaskLogsOptions struct {
24 24
 
25 25
 // TaskLogsResult holds the result of a task logs operation.
26 26
 // It implements [io.ReadCloser].
27
-type TaskLogsResult struct {
28
-	rc    io.ReadCloser
29
-	close func() error
27
+type TaskLogsResult interface {
28
+	io.ReadCloser
30 29
 }
31 30
 
32 31
 // TaskLogs returns the logs generated by a task.
... ...
@@ -44,7 +42,7 @@ func (cli *Client) TaskLogs(ctx context.Context, taskID string, options TaskLogs
44 44
 	if options.Since != "" {
45 45
 		ts, err := timestamp.GetTimestamp(options.Since, time.Now())
46 46
 		if err != nil {
47
-			return TaskLogsResult{}, err
47
+			return nil, err
48 48
 		}
49 49
 		query.Set("since", ts)
50 50
 	}
... ...
@@ -64,33 +62,33 @@ func (cli *Client) TaskLogs(ctx context.Context, taskID string, options TaskLogs
64 64
 
65 65
 	resp, err := cli.get(ctx, "/tasks/"+taskID+"/logs", query, nil)
66 66
 	if err != nil {
67
-		return TaskLogsResult{}, err
67
+		return nil, err
68 68
 	}
69
-	return newTaskLogsResult(resp.Body), nil
69
+	return &taskLogsResult{
70
+		body: resp.Body,
71
+	}, nil
70 72
 }
71 73
 
72
-func newTaskLogsResult(rc io.ReadCloser) TaskLogsResult {
73
-	if rc == nil {
74
-		panic("nil io.ReadCloser")
75
-	}
76
-	return TaskLogsResult{
77
-		rc:    rc,
78
-		close: sync.OnceValue(rc.Close),
79
-	}
74
+type taskLogsResult struct {
75
+	// body must be closed to avoid a resource leak
76
+	body io.ReadCloser
80 77
 }
81 78
 
82
-// Read implements [io.ReadCloser] for LogsResult.
83
-func (r TaskLogsResult) Read(p []byte) (n int, err error) {
84
-	if r.rc == nil {
79
+var (
80
+	_ io.ReadCloser       = (*taskLogsResult)(nil)
81
+	_ ContainerLogsResult = (*taskLogsResult)(nil)
82
+)
83
+
84
+func (r *taskLogsResult) Read(p []byte) (int, error) {
85
+	if r == nil || r.body == nil {
85 86
 		return 0, io.EOF
86 87
 	}
87
-	return r.rc.Read(p)
88
+	return r.body.Read(p)
88 89
 }
89 90
 
90
-// Close implements [io.ReadCloser] for LogsResult.
91
-func (r TaskLogsResult) Close() error {
92
-	if r.close == nil {
91
+func (r *taskLogsResult) Close() error {
92
+	if r == nil || r.body == nil {
93 93
 		return nil
94 94
 	}
95
-	return r.close()
95
+	return r.body.Close()
96 96
 }