Skip to content

Commit 735b0e5

Browse files
committed
Add push object
Split resolver to only return a name with separate methods for getting a fetcher and pusher. Add implementation for push. Signed-off-by: Derek McGowan <derek@mcgstyle.net>
1 parent 8f3b89c commit 735b0e5

File tree

12 files changed

+517
-136
lines changed

12 files changed

+517
-136
lines changed

cmd/dist/fetch.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@ Most of this is experimental and there are few leaps to make this work.`,
7272
resolved := make(chan struct{})
7373
eg.Go(func() error {
7474
ongoing.add(ref)
75-
name, desc, fetcher, err := resolver.Resolve(ctx, ref)
75+
name, desc, err := resolver.Resolve(ctx, ref)
76+
if err != nil {
77+
return err
78+
}
79+
fetcher, err := resolver.Fetcher(ctx, name)
7680
if err != nil {
7781
return err
7882
}

cmd/dist/fetchobject.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ var fetchObjectCommand = cli.Command{
3333
ctx = log.WithLogger(ctx, log.G(ctx).WithField("ref", ref))
3434

3535
log.G(ctx).Infof("resolving")
36-
_, desc, fetcher, err := resolver.Resolve(ctx, ref)
36+
name, desc, err := resolver.Resolve(ctx, ref)
37+
if err != nil {
38+
return err
39+
}
40+
fetcher, err := resolver.Fetcher(ctx, name)
3741
if err != nil {
3842
return err
3943
}

cmd/dist/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ distribution tool
7777
fetchObjectCommand,
7878
applyCommand,
7979
rootfsCommand,
80+
pushObjectCommand,
8081
}
8182
app.Before = func(context *cli.Context) error {
8283
timeout = context.GlobalDuration("timeout")

cmd/dist/pull.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,16 @@ command. As part of this process, we do the following:
6464
resolved := make(chan struct{})
6565
eg.Go(func() error {
6666
ongoing.add(ref)
67-
name, desc, fetcher, err := resolver.Resolve(ctx, ref)
67+
name, desc, err := resolver.Resolve(ctx, ref)
6868
if err != nil {
6969
log.G(ctx).WithError(err).Error("failed to resolve")
7070
return err
7171
}
72+
fetcher, err := resolver.Fetcher(ctx, name)
73+
if err != nil {
74+
return err
75+
}
76+
7277
log.G(ctx).WithField("image", name).Debug("fetching")
7378
resolvedImageName = name
7479
close(resolved)

cmd/dist/pushobject.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/containerd/containerd/log"
7+
digest "github.com/opencontainers/go-digest"
8+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
9+
"github.com/urfave/cli"
10+
)
11+
12+
var pushObjectCommand = cli.Command{
13+
Name: "push-object",
14+
Usage: "pushes an object to a remote",
15+
ArgsUsage: "[flags] <remote> <object> <type>",
16+
Description: `Push objects by identifier to a remote.`,
17+
Flags: registryFlags,
18+
Action: func(clicontext *cli.Context) error {
19+
var (
20+
ref = clicontext.Args().Get(0)
21+
object = clicontext.Args().Get(1)
22+
media = clicontext.Args().Get(2)
23+
)
24+
dgst, err := digest.Parse(object)
25+
if err != nil {
26+
return err
27+
}
28+
29+
ctx, cancel := appContext()
30+
defer cancel()
31+
32+
resolver, err := getResolver(ctx, clicontext)
33+
if err != nil {
34+
return err
35+
}
36+
37+
ctx = log.WithLogger(ctx, log.G(ctx).WithField("ref", ref))
38+
39+
log.G(ctx).Infof("resolving")
40+
pusher, err := resolver.Pusher(ctx, ref)
41+
if err != nil {
42+
return err
43+
}
44+
45+
cs, err := resolveContentStore(clicontext)
46+
if err != nil {
47+
return err
48+
}
49+
50+
info, err := cs.Info(ctx, dgst)
51+
if err != nil {
52+
return err
53+
}
54+
desc := ocispec.Descriptor{
55+
MediaType: media,
56+
Digest: dgst,
57+
Size: info.Size,
58+
}
59+
60+
rc, err := cs.Reader(ctx, dgst)
61+
if err != nil {
62+
return err
63+
}
64+
defer rc.Close()
65+
66+
// TODO: Progress reader
67+
if err = pusher.Push(ctx, desc, rc); err != nil {
68+
return err
69+
}
70+
71+
fmt.Printf("Pushed %s %s\n", desc.Digest, desc.MediaType)
72+
73+
return nil
74+
},
75+
}

reference/reference.go

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -79,20 +79,20 @@ func Parse(s string) (Spec, error) {
7979
return Spec{}, ErrHostnameRequired
8080
}
8181

82-
parts := splitRe.Split(u.Path, 2)
83-
if len(parts) < 2 {
84-
return Spec{}, ErrObjectRequired
85-
}
86-
87-
// This allows us to retain the @ to signify digests or shortend digests in
88-
// the object.
89-
object := u.Path[len(parts[0]):]
90-
if object[:1] == ":" {
91-
object = object[1:]
82+
var object string
83+
84+
if idx := splitRe.FindStringIndex(u.Path); idx != nil {
85+
// This allows us to retain the @ to signify digests or shortend digests in
86+
// the object.
87+
object = u.Path[idx[0]:]
88+
if object[:1] == ":" {
89+
object = object[1:]
90+
}
91+
u.Path = u.Path[:idx[0]]
9292
}
9393

9494
return Spec{
95-
Locator: path.Join(u.Host, parts[0]),
95+
Locator: path.Join(u.Host, u.Path),
9696
Object: object,
9797
}, nil
9898
}
@@ -119,6 +119,9 @@ func (r Spec) Digest() digest.Digest {
119119

120120
// String returns the normalized string for the ref.
121121
func (r Spec) String() string {
122+
if r.Object == "" {
123+
return r.Locator
124+
}
122125
if r.Object[:1] == "@" {
123126
return fmt.Sprintf("%v%v", r.Locator, r.Object)
124127
}

reference/reference_test.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,14 @@ func TestReferenceParser(t *testing.T) {
7878
Err: ErrHostnameRequired,
7979
},
8080
{
81-
Name: "ErrObjectRequired",
82-
Input: "docker.io/library/redis?fooo=asdf",
83-
Err: ErrObjectRequired,
81+
Name: "ErrObjectRequired",
82+
Input: "docker.io/library/redis?fooo=asdf",
83+
Hostname: "docker.io",
84+
Normalized: "docker.io/library/redis",
85+
Expected: Spec{
86+
Locator: "docker.io/library/redis",
87+
Object: "",
88+
},
8489
},
8590
{
8691
Name: "Subdomain",

remotes/docker/fetcher.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package docker
2+
3+
import (
4+
"context"
5+
"io"
6+
"net/http"
7+
"path"
8+
"strings"
9+
10+
"github.com/Sirupsen/logrus"
11+
"github.com/containerd/containerd/images"
12+
"github.com/containerd/containerd/log"
13+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
14+
"github.com/pkg/errors"
15+
)
16+
17+
type dockerFetcher struct {
18+
*dockerBase
19+
}
20+
21+
func (r dockerFetcher) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error) {
22+
ctx = log.WithLogger(ctx, log.G(ctx).WithFields(
23+
logrus.Fields{
24+
"base": r.base.String(),
25+
"digest": desc.Digest,
26+
},
27+
))
28+
29+
paths, err := getV2URLPaths(desc)
30+
if err != nil {
31+
return nil, err
32+
}
33+
34+
for _, path := range paths {
35+
u := r.url(path)
36+
37+
req, err := http.NewRequest(http.MethodGet, u, nil)
38+
if err != nil {
39+
return nil, err
40+
}
41+
42+
req.Header.Set("Accept", strings.Join([]string{desc.MediaType, `*`}, ", "))
43+
resp, err := r.doRequestWithRetries(ctx, req, nil)
44+
if err != nil {
45+
return nil, err
46+
}
47+
48+
if resp.StatusCode > 299 {
49+
if resp.StatusCode == http.StatusNotFound {
50+
continue // try one of the other urls.
51+
}
52+
resp.Body.Close()
53+
return nil, errors.Errorf("unexpected status code %v: %v", u, resp.Status)
54+
}
55+
56+
return resp.Body, nil
57+
}
58+
59+
return nil, errors.New("not found")
60+
}
61+
62+
// getV2URLPaths generates the candidate urls paths for the object based on the
63+
// set of hints and the provided object id. URLs are returned in the order of
64+
// most to least likely succeed.
65+
func getV2URLPaths(desc ocispec.Descriptor) ([]string, error) {
66+
var urls []string
67+
68+
switch desc.MediaType {
69+
case images.MediaTypeDockerSchema2Manifest, images.MediaTypeDockerSchema2ManifestList,
70+
ocispec.MediaTypeImageManifest, ocispec.MediaTypeImageIndex:
71+
urls = append(urls, path.Join("manifests", desc.Digest.String()))
72+
}
73+
74+
// always fallback to attempting to get the object out of the blobs store.
75+
urls = append(urls, path.Join("blobs", desc.Digest.String()))
76+
77+
return urls, nil
78+
}

0 commit comments

Comments
 (0)