Skip to content

Commit 005bb71

Browse files
authored
Add remote.Reuse for Pusher/Puller (#1672)
* code organization: split fetcher from descriptor * Refactor everything to go through Puller * crane: use puller/pusher in copy to drop legacy This package was only used to copy schema 1 images, which we don't need anymore. * Add remote.Reuse for Pusher/Puller This is a big change, but it helps callers out a lot. The Pusher/Puller interfaces allow us to deduplicate a bunch of work (largely, ping an auth), but they only work if you actually use them. It's a huge pain to migrate callers from remote.{Image,Index,...} interfaces to start using Pusher/Puller because the remote functions can be called from anywhere, which means plumbing pushers and pullers around the entire callgraph, which is super painful. Happily, though, most callers already plumb remote.Options around the callgraph so that they can set their options in one place and have them be consistent throughout their application. This change takes advantage of that fact by introducing remote.Reuse, which takes either a Puller or a Pusher, and calls equivalent methods on said pusher/puller whenever you pass remote.Reuse into a remote function. The end result is that we can get almost all of the performance wins of using Puller/Pusher directly with a 3 line change to most applications rather than refactoring everything. * Address review feedback
1 parent 58bd35b commit 005bb71

File tree

22 files changed

+697
-1105
lines changed

22 files changed

+697
-1105
lines changed

internal/legacy/copy.go

Lines changed: 0 additions & 57 deletions
This file was deleted.

internal/legacy/copy_test.go

Lines changed: 0 additions & 97 deletions
This file was deleted.

pkg/crane/copy.go

Lines changed: 15 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@ package crane
1717
import (
1818
"fmt"
1919

20-
"github.com/google/go-containerregistry/internal/legacy"
2120
"github.com/google/go-containerregistry/pkg/logs"
2221
"github.com/google/go-containerregistry/pkg/name"
2322
"github.com/google/go-containerregistry/pkg/v1/remote"
24-
"github.com/google/go-containerregistry/pkg/v1/types"
2523
)
2624

2725
// Copy copies a remote image or index from src to dst.
@@ -37,52 +35,30 @@ func Copy(src, dst string, opt ...Option) error {
3735
return fmt.Errorf("parsing reference for %q: %w", dst, err)
3836
}
3937

40-
logs.Progress.Printf("Copying from %v to %v", srcRef, dstRef)
41-
desc, err := remote.Get(srcRef, o.Remote...)
38+
pusher, err := remote.NewPusher(o.Remote...)
4239
if err != nil {
43-
return fmt.Errorf("fetching %q: %w", src, err)
40+
return err
4441
}
4542

46-
switch desc.MediaType {
47-
case types.OCIImageIndex, types.DockerManifestList:
48-
// Handle indexes separately.
49-
if o.Platform != nil {
50-
// If platform is explicitly set, don't copy the whole index, just the appropriate image.
51-
if err := copyImage(desc, dstRef, o); err != nil {
52-
return fmt.Errorf("failed to copy image: %w", err)
53-
}
54-
} else {
55-
if err := copyIndex(desc, dstRef, o); err != nil {
56-
return fmt.Errorf("failed to copy index: %w", err)
57-
}
58-
}
59-
case types.DockerManifestSchema1, types.DockerManifestSchema1Signed:
60-
// Handle schema 1 images separately.
61-
if err := legacy.CopySchema1(desc, srcRef, dstRef, o.Remote...); err != nil {
62-
return fmt.Errorf("failed to copy schema 1 image: %w", err)
63-
}
64-
default:
65-
// Assume anything else is an image, since some registries don't set mediaTypes properly.
66-
if err := copyImage(desc, dstRef, o); err != nil {
67-
return fmt.Errorf("failed to copy image: %w", err)
68-
}
43+
puller, err := remote.NewPuller(o.Remote...)
44+
if err != nil {
45+
return err
6946
}
7047

71-
return nil
72-
}
73-
74-
func copyImage(desc *remote.Descriptor, dstRef name.Reference, o Options) error {
75-
img, err := desc.Image()
48+
logs.Progress.Printf("Copying from %v to %v", srcRef, dstRef)
49+
desc, err := puller.Get(o.ctx, srcRef)
7650
if err != nil {
77-
return err
51+
return fmt.Errorf("fetching %q: %w", src, err)
7852
}
79-
return remote.Write(dstRef, img, o.Remote...)
80-
}
8153

82-
func copyIndex(desc *remote.Descriptor, dstRef name.Reference, o Options) error {
83-
idx, err := desc.ImageIndex()
54+
if o.Platform == nil {
55+
return pusher.Push(o.ctx, dstRef, desc)
56+
}
57+
58+
// If platform is explicitly set, don't copy the whole index, just the appropriate image.
59+
img, err := desc.Image()
8460
if err != nil {
8561
return err
8662
}
87-
return remote.WriteIndex(dstRef, idx, o.Remote...)
63+
return pusher.Push(o.ctx, dstRef, img)
8864
}

pkg/crane/options.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type Options struct {
3434

3535
transport http.RoundTripper
3636
insecure bool
37+
ctx context.Context
3738
}
3839

3940
// GetOptions exposes the underlying []remote.Option, []name.Option, and
@@ -50,6 +51,7 @@ func makeOptions(opts ...Option) Options {
5051
remote.WithAuthFromKeychain(authn.DefaultKeychain),
5152
},
5253
Keychain: authn.DefaultKeychain,
54+
ctx: context.Background(),
5355
}
5456

5557
for _, o := range opts {
@@ -144,6 +146,7 @@ func WithNondistributable() Option {
144146
// WithContext is a functional option for setting the context.
145147
func WithContext(ctx context.Context) Option {
146148
return func(o *Options) {
149+
o.ctx = ctx
147150
o.Remote = append(o.Remote, remote.WithContext(ctx))
148151
}
149152
}

pkg/v1/remote/catalog.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ func CatalogPage(target name.Registry, last string, n int, options ...Option) ([
3636
if err != nil {
3737
return nil, err
3838
}
39-
f, err := makeFetcher(o.context, target, o)
39+
40+
f, err := newPuller(o).fetcher(o.context, target)
4041
if err != nil {
4142
return nil, err
4243
}
@@ -82,18 +83,18 @@ func Catalog(ctx context.Context, target name.Registry, options ...Option) ([]st
8283
ctx = o.context
8384
}
8485

85-
return newPuller(o).Catalog(ctx, target)
86+
return newPuller(o).catalog(ctx, target, o.pageSize)
8687
}
8788

88-
func (f *fetcher) catalogPage(ctx context.Context, reg name.Registry, next string) (*Catalogs, error) {
89+
func (f *fetcher) catalogPage(ctx context.Context, reg name.Registry, next string, pageSize int) (*Catalogs, error) {
8990
if next == "" {
9091
uri := &url.URL{
9192
Scheme: reg.Scheme(),
9293
Host: reg.RegistryStr(),
9394
Path: "/v2/_catalog",
9495
}
95-
if f.pageSize > 0 {
96-
uri.RawQuery = fmt.Sprintf("n=%d", f.pageSize)
96+
if pageSize > 0 {
97+
uri.RawQuery = fmt.Sprintf("n=%d", pageSize)
9798
}
9899
next = uri.String()
99100
}
@@ -134,8 +135,9 @@ func (f *fetcher) catalogPage(ctx context.Context, reg name.Registry, next strin
134135
}
135136

136137
type Catalogger struct {
137-
f *fetcher
138-
reg name.Registry
138+
f *fetcher
139+
reg name.Registry
140+
pageSize int
139141

140142
page *Catalogs
141143
err error
@@ -145,7 +147,7 @@ type Catalogger struct {
145147

146148
func (l *Catalogger) Next(ctx context.Context) (*Catalogs, error) {
147149
if l.needMore {
148-
l.page, l.err = l.f.catalogPage(ctx, l.reg, l.page.Next)
150+
l.page, l.err = l.f.catalogPage(ctx, l.reg, l.page.Next, l.pageSize)
149151
} else {
150152
l.needMore = true
151153
}

pkg/v1/remote/delete.go

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,7 @@
1515
package remote
1616

1717
import (
18-
"fmt"
19-
"net/http"
20-
"net/url"
21-
2218
"github.com/google/go-containerregistry/pkg/name"
23-
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
2419
)
2520

2621
// Delete removes the specified image reference from the remote registry.
@@ -29,32 +24,5 @@ func Delete(ref name.Reference, options ...Option) error {
2924
if err != nil {
3025
return err
3126
}
32-
w, err := makeWriter(o.context, ref.Context(), nil, o)
33-
if err != nil {
34-
return err
35-
}
36-
c := w.client
37-
38-
u := url.URL{
39-
Scheme: ref.Context().Registry.Scheme(),
40-
Host: ref.Context().RegistryStr(),
41-
Path: fmt.Sprintf("/v2/%s/manifests/%s", ref.Context().RepositoryStr(), ref.Identifier()),
42-
}
43-
44-
req, err := http.NewRequest(http.MethodDelete, u.String(), nil)
45-
if err != nil {
46-
return err
47-
}
48-
49-
resp, err := c.Do(req.WithContext(o.context))
50-
if err != nil {
51-
return err
52-
}
53-
defer resp.Body.Close()
54-
55-
return transport.CheckError(resp, http.StatusOK, http.StatusAccepted)
56-
57-
// TODO(jason): If the manifest had a `subject`, and if the registry
58-
// doesn't support Referrers, update the index pointed to by the
59-
// subject's fallback tag to remove the descriptor for this manifest.
27+
return newPusher(o).Delete(o.context, ref)
6028
}

0 commit comments

Comments
 (0)