Skip to content

Commit 9894189

Browse files
committed
imagetools: support oci-layout refs
Add oci-layout:// source and target support to imagetools create and inspect while keeping merge, filter, and referrer logic shared. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
1 parent 71edf28 commit 9894189

File tree

13 files changed

+865
-188
lines changed

13 files changed

+865
-188
lines changed

build/build.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -805,11 +805,10 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
805805

806806
itpull := imagetools.New(imageopt)
807807

808-
ref, err := reference.ParseNormalizedNamed(names[0])
808+
ref, err := imagetools.ParseLocation(names[0])
809809
if err != nil {
810810
return err
811811
}
812-
ref = reference.TagNameOnly(ref)
813812

814813
srcs := make([]*imagetools.Source, len(descs))
815814
for i, desc := range descs {
@@ -832,7 +831,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
832831
itpush := imagetools.New(imageopt)
833832

834833
for _, n := range names {
835-
nn, err := reference.ParseNormalizedNamed(n)
834+
nn, err := imagetools.ParseLocation(n)
836835
if err != nil {
837836
return err
838837
}

build/opt.go

Lines changed: 6 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/docker/buildx/util/buildflags"
2828
"github.com/docker/buildx/util/confutil"
2929
"github.com/docker/buildx/util/dockerutil"
30+
"github.com/docker/buildx/util/ocilayout"
3031
"github.com/docker/buildx/util/osutil"
3132
"github.com/docker/buildx/util/progress"
3233
"github.com/docker/buildx/util/sourcemeta"
@@ -913,7 +914,11 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro
913914

914915
// handle OCI layout
915916
if localPath, ok := strings.CutPrefix(v.Path, "oci-layout://"); ok {
916-
localPath, dig, tag := parseOCILayoutPath(localPath)
917+
ref, _, err := ocilayout.Parse("oci-layout://" + localPath)
918+
if err != nil {
919+
return nil, err
920+
}
921+
localPath, dig, tag := ref.Path, ref.Digest.String(), ref.Tag
917922
if dig == "" {
918923
dig, err = resolveDigest(localPath, tag)
919924
if err != nil {
@@ -1402,38 +1407,6 @@ func isActive(ce *client.CacheOptionsEntry) bool {
14021407
return ce.Attrs["token"] != "" && (ce.Attrs["url"] != "" || ce.Attrs["url_v2"] != "")
14031408
}
14041409

1405-
// parseOCILayoutPath handles the oci-layout url accepted by buildx.
1406-
func parseOCILayoutPath(s string) (localPath, dgst, tag string) {
1407-
localPath = s
1408-
1409-
// Look for the digest reference. There might be multiple @ symbols
1410-
// in the path and the @ symbol may be part of the path or part of
1411-
// the digest. If we find the @ symbol, verify that it's a valid
1412-
// digest reference instead of just assuming it is because it
1413-
// might be part of the file path.
1414-
if i := strings.LastIndex(localPath, "@"); i >= 0 {
1415-
after := localPath[i+1:]
1416-
if reference.DigestRegexp.MatchString(after) {
1417-
localPath, dgst = localPath[:i], after
1418-
}
1419-
}
1420-
1421-
// Do the same with the tag. This isn't as necessary since colons
1422-
// aren't valid as file paths on Linux/Unix systems, but they are valid
1423-
// on Windows systems so we might as well just be safe.
1424-
if i := strings.LastIndex(localPath, ":"); i >= 0 {
1425-
after := localPath[i+1:]
1426-
if reference.TagRegexp.MatchString(after) {
1427-
localPath, tag = localPath[:i], after
1428-
}
1429-
}
1430-
1431-
if tag == "" {
1432-
tag = "latest"
1433-
}
1434-
return
1435-
}
1436-
14371410
func defaultPlatform(bopts gateway.BuildOpts) *ocispecs.Platform {
14381411
pl := bopts.Workers[0].Platforms
14391412
if len(pl) == 0 {

build/opt_test.go

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -40,48 +40,6 @@ func TestCacheOptions_DerivedVars(t *testing.T) {
4040
}, CreateCaches(cacheFrom))
4141
}
4242

43-
func TestParseOCILayoutPath(t *testing.T) {
44-
for _, tt := range []struct {
45-
s string
46-
path string
47-
dgst string
48-
tag string
49-
}{
50-
{
51-
s: "/path/to/oci/layout",
52-
path: "/path/to/oci/layout",
53-
tag: "latest",
54-
},
55-
{
56-
s: "/path/to/oci/layout:1.3",
57-
path: "/path/to/oci/layout",
58-
tag: "1.3",
59-
},
60-
{
61-
s: "/path/to/oci/layout@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
62-
path: "/path/to/oci/layout",
63-
dgst: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
64-
tag: "latest",
65-
},
66-
{
67-
s: "/path/to/oci/@/layout@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
68-
path: "/path/to/oci/@/layout",
69-
dgst: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
70-
tag: "latest",
71-
},
72-
{
73-
s: "/path/to/oci/@/layout",
74-
path: "/path/to/oci/@/layout",
75-
tag: "latest",
76-
},
77-
} {
78-
path, dgst, tag := parseOCILayoutPath(tt.s)
79-
assert.Equal(t, tt.path, path, "comparing path: %s", tt.s)
80-
assert.Equal(t, tt.dgst, dgst, "comparing digest: %s", tt.s)
81-
assert.Equal(t, tt.tag, tag, "comparing tag: %s", tt.s)
82-
}
83-
}
84-
8543
func TestCreateExports_RegistryUnpack(t *testing.T) {
8644
tests := []struct {
8745
name string

commands/imagetools/create.go

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010

1111
"github.com/containerd/containerd/v2/core/remotes"
1212
"github.com/containerd/platforms"
13-
"github.com/distribution/reference"
1413
"github.com/docker/buildx/builder"
1514
"github.com/docker/buildx/util/buildflags"
1615
"github.com/docker/buildx/util/cobrautil/completion"
@@ -60,7 +59,7 @@ func runCreate(ctx context.Context, dockerCli command.Cli, in createOptions, arg
6059

6160
args = append(fileArgs, args...)
6261

63-
tags, err := parseRefs(in.tags)
62+
tags, err := parseLocations(in.tags)
6463
if err != nil {
6564
return err
6665
}
@@ -102,10 +101,16 @@ func runCreate(ctx context.Context, dockerCli command.Cli, in createOptions, arg
102101
return errors.Errorf("no repositories specified, please set a reference in tag or source")
103102
}
104103

105-
var defaultRepo *string
104+
var defaultRepo *imagetools.Location
106105
if len(repos) == 1 {
107-
for repo := range repos {
108-
defaultRepo = &repo
106+
for _, src := range srcs {
107+
if src.Ref != nil {
108+
defaultRepo = src.Ref
109+
break
110+
}
111+
}
112+
if defaultRepo == nil && len(tags) > 0 {
113+
defaultRepo = tags[0]
109114
}
110115
}
111116

@@ -114,19 +119,19 @@ func runCreate(ctx context.Context, dockerCli command.Cli, in createOptions, arg
114119
if defaultRepo == nil {
115120
return errors.Errorf("multiple repositories specified, cannot infer repository for %q", args[i])
116121
}
117-
n, err := reference.ParseNormalizedNamed(*defaultRepo)
118-
if err != nil {
119-
return err
120-
}
121122
if s.Desc.MediaType == "" && s.Desc.Digest != "" {
122-
r, err := reference.WithDigest(n, s.Desc.Digest)
123+
r, err := defaultRepo.WithDigest(s.Desc.Digest)
123124
if err != nil {
124125
return err
125126
}
126127
srcs[i].Ref = r
127128
sourceRefs = true
128129
} else {
129-
srcs[i].Ref = reference.TagNameOnly(n)
130+
r, err := defaultRepo.TagNameOnly()
131+
if err != nil {
132+
return err
133+
}
134+
srcs[i].Ref = r
130135
}
131136
}
132137
}
@@ -209,7 +214,7 @@ func runCreate(ctx context.Context, dockerCli command.Cli, in createOptions, arg
209214
eg, _ := errgroup.WithContext(ctx)
210215
pw := progress.WithPrefix(printer, "internal", true)
211216

212-
tagsByRepo := map[string][]reference.Named{}
217+
tagsByRepo := map[string][]*imagetools.Location{}
213218
for _, t := range tags {
214219
repo := t.Name()
215220
tagsByRepo[repo] = append(tagsByRepo[repo], t)
@@ -224,10 +229,14 @@ func runCreate(ctx context.Context, dockerCli command.Cli, in createOptions, arg
224229
for _, desc := range manifests {
225230
eg2.Go(func() error {
226231
sub.Log(1, fmt.Appendf(nil, "copying %s from %s to %s\n", desc.Digest.String(), desc.Source.Ref.String(), repo))
227-
return r.Copy(ctx, &imagetools.Source{
232+
err := r.Copy(ctx, &imagetools.Source{
228233
Ref: desc.Source.Ref,
229234
Desc: desc.Descriptor,
230235
}, seed)
236+
if err != nil {
237+
return errors.Wrapf(err, "copy %s from %s to %s", desc.Digest.String(), desc.Source.Ref.String(), seed.String())
238+
}
239+
return nil
231240
})
232241
}
233242
if err := eg2.Wait(); err != nil {
@@ -236,7 +245,7 @@ func runCreate(ctx context.Context, dockerCli command.Cli, in createOptions, arg
236245
for _, t := range repoTags {
237246
sub.Log(1, fmt.Appendf(nil, "pushing %s to %s\n", desc.Digest.String(), t.String()))
238247
if err := r.Push(ctx, t, desc, dt); err != nil {
239-
return err
248+
return errors.Wrapf(err, "publish %s to %s", desc.Digest.String(), t.String())
240249
}
241250
}
242251
return nil
@@ -302,10 +311,10 @@ func withMediaTypeKeyPrefix(ctx context.Context) context.Context {
302311
return ctx
303312
}
304313

305-
func parseRefs(in []string) ([]reference.Named, error) {
306-
refs := make([]reference.Named, len(in))
314+
func parseLocations(in []string) ([]*imagetools.Location, error) {
315+
refs := make([]*imagetools.Location, len(in))
307316
for i, in := range in {
308-
n, err := reference.ParseNormalizedNamed(in)
317+
n, err := imagetools.ParseLocation(in)
309318
if err != nil {
310319
return nil, err
311320
}
@@ -327,10 +336,10 @@ func parseSource(in string) (*imagetools.Source, error) {
327336
return nil, err
328337
}
329338

330-
ref, err := reference.ParseNormalizedNamed(in)
339+
loc, err := imagetools.ParseLocation(in)
331340
if err == nil {
332341
return &imagetools.Source{
333-
Ref: ref,
342+
Ref: loc,
334343
}, nil
335344
} else if !strings.HasPrefix(in, "{") {
336345
return nil, err

0 commit comments

Comments
 (0)