Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 8c1a909

Browse filesBrowse files
authored
Merge pull request #51148 from ndeloof/decode-JSONMessage-push
introduce ImagePushResponse
2 parents 8a211de + 2d1429c commit 8c1a909
Copy full SHA for 8c1a909

10 files changed

+223-140Lines changed: 223 additions & 140 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎client/client_interfaces.go‎

Copy file name to clipboardExpand all lines: client/client_interfaces.go
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ type ImageAPIClient interface {
113113

114114
ImageList(ctx context.Context, options ImageListOptions) ([]image.Summary, error)
115115
ImagePull(ctx context.Context, ref string, options ImagePullOptions) (ImagePullResponse, error)
116-
ImagePush(ctx context.Context, ref string, options ImagePushOptions) (io.ReadCloser, error)
116+
ImagePush(ctx context.Context, ref string, options ImagePushOptions) (ImagePushResponse, error)
117117
ImageRemove(ctx context.Context, image string, options ImageRemoveOptions) ([]image.DeleteResponse, error)
118118
ImageSearch(ctx context.Context, term string, options ImageSearchOptions) ([]registry.SearchResult, error)
119119
ImageTag(ctx context.Context, image, ref string) error
Collapse file

‎client/image_pull.go‎

Copy file name to clipboardExpand all lines: client/image_pull.go
+13-65Lines changed: 13 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,83 +2,31 @@ package client
22

33
import (
44
"context"
5-
"encoding/json"
6-
"errors"
75
"io"
86
"iter"
97
"net/url"
108
"strings"
11-
"sync"
129

1310
cerrdefs "github.com/containerd/errdefs"
1411
"github.com/distribution/reference"
12+
"github.com/moby/moby/client/internal"
1513
"github.com/moby/moby/client/pkg/jsonmessage"
1614
)
1715

18-
func newImagePullResponse(rc io.ReadCloser) ImagePullResponse {
19-
if rc == nil {
20-
panic("nil io.ReadCloser")
21-
}
22-
return ImagePullResponse{
23-
rc: rc,
24-
close: sync.OnceValue(rc.Close),
25-
}
26-
}
27-
28-
type ImagePullResponse struct {
29-
rc io.ReadCloser
30-
close func() error
31-
}
32-
33-
// Read implements io.ReadCloser
34-
func (r ImagePullResponse) Read(p []byte) (n int, err error) {
35-
if r.rc == nil {
36-
return 0, io.EOF
37-
}
38-
return r.rc.Read(p)
39-
}
40-
41-
// Close implements io.ReadCloser
42-
func (r ImagePullResponse) Close() error {
43-
if r.close == nil {
44-
return nil
45-
}
46-
return r.close()
47-
}
48-
49-
// JSONMessages decodes the response stream as a sequence of JSONMessages.
50-
// if stream ends or context is cancelled, the underlying [io.Reader] is closed.
51-
func (r ImagePullResponse) JSONMessages(ctx context.Context) iter.Seq2[jsonmessage.JSONMessage, error] {
52-
context.AfterFunc(ctx, func() {
53-
_ = r.Close()
54-
})
55-
dec := json.NewDecoder(r)
56-
return func(yield func(jsonmessage.JSONMessage, error) bool) {
57-
defer r.Close()
58-
for {
59-
var jm jsonmessage.JSONMessage
60-
err := dec.Decode(&jm)
61-
if errors.Is(err, io.EOF) {
62-
break
63-
}
64-
if ctx.Err() != nil {
65-
yield(jm, ctx.Err())
66-
return
67-
}
68-
if !yield(jm, err) {
69-
return
70-
}
71-
}
72-
}
16+
type ImagePullResponse interface {
17+
io.ReadCloser
18+
JSONMessages(ctx context.Context) iter.Seq2[jsonmessage.JSONMessage, error]
19+
Wait(ctx context.Context) error
7320
}
7421

7522
// ImagePull requests the docker host to pull an image from a remote registry.
7623
// It executes the privileged function if the operation is unauthorized
7724
// and it tries one more time.
78-
// Callers can use [ImagePullResponse.JSONMessages] to monitor pull progress as
79-
// a sequence of JSONMessages, [ImagePullResponse.Close] does not need to be
80-
// called in this case. Or, use the [io.Reader] interface and call
81-
// [ImagePullResponse.Close] after processing.
25+
// Callers can:
26+
// - use [ImagePullResponse.Wait] to wait for pull to complete
27+
// - use [ImagePullResponse.JSONMessages] to monitor pull progress as a sequence
28+
// of JSONMessages, [ImagePullResponse.Close] does not need to be called in this case.
29+
// - use the [io.Reader] interface and call [ImagePullResponse.Close] after processing.
8230
func (cli *Client) ImagePull(ctx context.Context, refStr string, options ImagePullOptions) (ImagePullResponse, error) {
8331
// FIXME(vdemeester): there is currently used in a few way in docker/docker
8432
// - if not in trusted content, ref is used to pass the whole reference, and tag is empty
@@ -88,7 +36,7 @@ func (cli *Client) ImagePull(ctx context.Context, refStr string, options ImagePu
8836

8937
ref, err := reference.ParseNormalizedNamed(refStr)
9038
if err != nil {
91-
return ImagePullResponse{}, err
39+
return nil, err
9240
}
9341

9442
query := url.Values{}
@@ -105,10 +53,10 @@ func (cli *Client) ImagePull(ctx context.Context, refStr string, options ImagePu
10553
resp, err = cli.tryImageCreate(ctx, query, options.PrivilegeFunc)
10654
}
10755
if err != nil {
108-
return ImagePullResponse{}, err
56+
return nil, err
10957
}
11058

111-
return newImagePullResponse(resp.Body), nil
59+
return internal.NewJSONMessageStream(resp.Body), nil
11260
}
11361

11462
// getAPITagFromNamedRef returns a tag from the specified reference.
Collapse file

‎client/image_pull_test.go‎

Copy file name to clipboardExpand all lines: client/image_pull_test.go
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
cerrdefs "github.com/containerd/errdefs"
1414
"github.com/moby/moby/api/types/registry"
15+
"github.com/moby/moby/client/internal"
1516
"github.com/moby/moby/client/pkg/jsonmessage"
1617
"gotest.tools/v3/assert"
1718
is "gotest.tools/v3/assert/cmp"
@@ -199,7 +200,7 @@ func TestImagePullWithoutErrors(t *testing.T) {
199200

200201
func TestImagePullResponse(t *testing.T) {
201202
r, w := io.Pipe()
202-
response := newImagePullResponse(r)
203+
response := internal.NewJSONMessageStream(r)
203204
ctx, cancel := context.WithCancel(context.TODO())
204205
messages := response.JSONMessages(ctx)
205206
c := make(chan jsonmessage.JSONMessage)
Collapse file

‎client/image_push.go‎

Copy file name to clipboardExpand all lines: client/image_push.go
+16-3Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,32 @@ import (
66
"errors"
77
"fmt"
88
"io"
9+
"iter"
910
"net/http"
1011
"net/url"
1112

1213
cerrdefs "github.com/containerd/errdefs"
1314
"github.com/distribution/reference"
1415
"github.com/moby/moby/api/types/registry"
16+
"github.com/moby/moby/client/internal"
17+
"github.com/moby/moby/client/pkg/jsonmessage"
1518
)
1619

20+
type ImagePushResponse interface {
21+
io.ReadCloser
22+
JSONMessages(ctx context.Context) iter.Seq2[jsonmessage.JSONMessage, error]
23+
Wait(ctx context.Context) error
24+
}
25+
1726
// ImagePush requests the docker host to push an image to a remote registry.
1827
// It executes the privileged function if the operation is unauthorized
1928
// and it tries one more time.
20-
// It's up to the caller to handle the [io.ReadCloser] and close it.
21-
func (cli *Client) ImagePush(ctx context.Context, image string, options ImagePushOptions) (io.ReadCloser, error) {
29+
// Callers can
30+
// - use [ImagePushResponse.Wait] to wait for push to complete
31+
// - use [ImagePushResponse.JSONMessages] to monitor pull progress as a sequence
32+
// of JSONMessages, [ImagePushResponse.Close] does not need to be called in this case.
33+
// - use the [io.Reader] interface and call [ImagePushResponse.Close] after processing.
34+
func (cli *Client) ImagePush(ctx context.Context, image string, options ImagePushOptions) (ImagePushResponse, error) {
2235
ref, err := reference.ParseNormalizedNamed(image)
2336
if err != nil {
2437
return nil, err
@@ -57,7 +70,7 @@ func (cli *Client) ImagePush(ctx context.Context, image string, options ImagePus
5770
if err != nil {
5871
return nil, err
5972
}
60-
return resp.Body, nil
73+
return internal.NewJSONMessageStream(resp.Body), nil
6174
}
6275

6376
func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, resolveAuth registry.RequestAuthConfig) (*http.Response, error) {
Collapse file

‎client/internal/jsonmessages.go‎

Copy file name to clipboard
+79Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package internal
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"errors"
7+
"io"
8+
"iter"
9+
"sync"
10+
11+
"github.com/moby/moby/client/pkg/jsonmessage"
12+
)
13+
14+
func NewJSONMessageStream(rc io.ReadCloser) stream {
15+
if rc == nil {
16+
panic("nil io.ReadCloser")
17+
}
18+
return stream{
19+
rc: rc,
20+
close: sync.OnceValue(rc.Close),
21+
}
22+
}
23+
24+
type stream struct {
25+
rc io.ReadCloser
26+
close func() error
27+
}
28+
29+
// Read implements io.ReadCloser
30+
func (r stream) Read(p []byte) (n int, err error) {
31+
if r.rc == nil {
32+
return 0, io.EOF
33+
}
34+
return r.rc.Read(p)
35+
}
36+
37+
// Close implements io.ReadCloser
38+
func (r stream) Close() error {
39+
if r.close == nil {
40+
return nil
41+
}
42+
return r.close()
43+
}
44+
45+
// JSONMessages decodes the response stream as a sequence of JSONMessages.
46+
// if stream ends or context is cancelled, the underlying [io.Reader] is closed.
47+
func (r stream) JSONMessages(ctx context.Context) iter.Seq2[jsonmessage.JSONMessage, error] {
48+
context.AfterFunc(ctx, func() {
49+
_ = r.Close()
50+
})
51+
dec := json.NewDecoder(r)
52+
return func(yield func(jsonmessage.JSONMessage, error) bool) {
53+
defer r.Close()
54+
for {
55+
var jm jsonmessage.JSONMessage
56+
err := dec.Decode(&jm)
57+
if errors.Is(err, io.EOF) {
58+
break
59+
}
60+
if ctx.Err() != nil {
61+
yield(jm, ctx.Err())
62+
return
63+
}
64+
if !yield(jm, err) {
65+
return
66+
}
67+
}
68+
}
69+
}
70+
71+
// Wait waits for operation to complete and detects errors reported as JSONMessage
72+
func (r stream) Wait(ctx context.Context) error {
73+
for _, err := range r.JSONMessages(ctx) {
74+
if err != nil {
75+
return err
76+
}
77+
}
78+
return nil
79+
}
Collapse file

‎integration/image/pull_test.go‎

Copy file name to clipboardExpand all lines: integration/image/pull_test.go
+3-1Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,9 @@ func TestImagePullStoredDigestForOtherRepo(t *testing.T) {
153153

154154
// Now, pull a totally different repo with a the same digest
155155
rdr, err = apiClient.ImagePull(ctx, path.Join(registry.DefaultURL, "other:image@"+desc.Digest.String()), client.ImagePullOptions{})
156-
assert.Check(t, rdr.Close())
156+
if rdr != nil {
157+
assert.Check(t, rdr.Close())
158+
}
157159
assert.Assert(t, err != nil, "Expected error, got none: %v", err)
158160
assert.Assert(t, cerrdefs.IsNotFound(err), err)
159161
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
Collapse file

‎vendor/github.com/moby/moby/client/client_interfaces.go‎

Copy file name to clipboardExpand all lines: vendor/github.com/moby/moby/client/client_interfaces.go
+1-1Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.