Skip to content

Commit 9a4bbf0

Browse files
committed
Add multi oci manifest support to load and save
Signed-off-by: Urvashi Mohnani <[email protected]>
1 parent cba555c commit 9a4bbf0

File tree

6 files changed

+212
-67
lines changed

6 files changed

+212
-67
lines changed

libimage/load.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func (r *Runtime) Load(ctx context.Context, path string, options *LoadOptions) (
5353
if err != nil {
5454
return nil, ociArchiveTransport.Transport.Name(), err
5555
}
56-
images, err := r.copyFromDefault(ctx, ref, &options.CopyOptions)
56+
images, err := r.loadMultiImageOCIArchive(ctx, ref, &options.CopyOptions)
5757
return images, ociArchiveTransport.Transport.Name(), err
5858
},
5959

@@ -131,3 +131,22 @@ func (r *Runtime) loadMultiImageDockerArchive(ctx context.Context, ref types.Ima
131131

132132
return copiedImages, nil
133133
}
134+
135+
func (r *Runtime) loadMultiImageOCIArchive(ctx context.Context, ref types.ImageReference, options *CopyOptions) ([]string, error) {
136+
reader, err := ociArchiveTransport.NewReader(ctx, r.systemContextCopy(), ref)
137+
if err != nil {
138+
return nil, err
139+
}
140+
defer func() {
141+
if err := reader.Close(); err != nil {
142+
logrus.Errorf(err.Error())
143+
}
144+
}()
145+
146+
copiedImages, err := r.copyFromOCIArchiveReader(ctx, reader, options)
147+
if err != nil {
148+
return nil, err
149+
}
150+
151+
return copiedImages, nil
152+
}

libimage/load_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ func TestLoad(t *testing.T) {
3333
{"testdata/oci-name-only.tar.gz", false, 1, []string{"localhost/pretty-empty:latest"}},
3434
{"testdata/oci-non-docker-name.tar.gz", true, 0, nil},
3535
{"testdata/oci-registry-name.tar.gz", false, 1, []string{"example.com/empty:latest"}},
36+
// {"testdata/oci-two-names.tar.xz", false, 1, []string{"localhost/pretty-empty:latest", "example.com/empty:latest"}},
37+
{"testdata/oci-two-images.tar.xz", false, 2, []string{"example.com/empty:latest", "example.com/empty/but:different"}},
3638
{"testdata/oci-unnamed.tar.gz", false, 1, []string{"sha256:5c8aca8137ac47e84c69ae93ce650ce967917cc001ba7aad5494073fac75b8b6"}},
3739
{"testdata/buildkit-oci.tar", false, 1, []string{"github.com/buildkit/archive:oci"}},
3840
} {

libimage/pull.go

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ func (r *Runtime) Pull(ctx context.Context, name string, pullPolicy config.PullP
150150
case dockerArchiveTransport.Transport.Name():
151151
pulledImages, pullError = r.copyFromDockerArchive(ctx, ref, &options.CopyOptions)
152152

153+
// OCI ARCHIVE
154+
case ociArchiveTransport.Transport.Name():
155+
pulledImages, pullError = r.copyFromOCIArchive(ctx, ref, &options.CopyOptions)
156+
153157
// ALL OTHER TRANSPORTS
154158
default:
155159
pulledImages, pullError = r.copyFromDefault(ctx, ref, &options.CopyOptions)
@@ -212,29 +216,6 @@ func (r *Runtime) copyFromDefault(ctx context.Context, ref types.ImageReference,
212216
storageName = toLocalImageName(split[0])
213217
imageName = storageName
214218

215-
case ociArchiveTransport.Transport.Name():
216-
manifestDescriptor, err := ociArchiveTransport.LoadManifestDescriptor(ref)
217-
if err != nil {
218-
return nil, err
219-
}
220-
storageName = nameFromAnnotations(manifestDescriptor.Annotations)
221-
switch len(storageName) {
222-
case 0:
223-
// If there's no reference name in the annotations, compute an ID.
224-
storageName, err = getImageID(ctx, ref, nil)
225-
if err != nil {
226-
return nil, err
227-
}
228-
imageName = "sha256:" + storageName[1:]
229-
default:
230-
named, err := NormalizeName(storageName)
231-
if err != nil {
232-
return nil, err
233-
}
234-
imageName = named.String()
235-
storageName = imageName
236-
}
237-
238219
case storageTransport.Transport.Name():
239220
storageName = ref.StringWithinTransport()
240221
named := ref.DockerReference()
@@ -343,6 +324,68 @@ func (r *Runtime) copyFromDockerArchiveReaderReference(ctx context.Context, read
343324
return destNames, nil
344325
}
345326

327+
func (r *Runtime) copyFromOCIArchive(ctx context.Context, readerRef types.ImageReference, options *CopyOptions) ([]string, error) {
328+
reader, err := ociArchiveTransport.NewReader(ctx, &r.systemContext, readerRef)
329+
if err != nil {
330+
return nil, err
331+
}
332+
return r.copyFromOCIArchiveReader(ctx, reader, options)
333+
}
334+
335+
func (r *Runtime) copyFromOCIArchiveReader(ctx context.Context, reader *ociArchiveTransport.Reader, options *CopyOptions) ([]string, error) {
336+
c, err := r.newCopier(options)
337+
if err != nil {
338+
return nil, err
339+
}
340+
defer c.close()
341+
342+
var (
343+
storageName, imageName string
344+
names []string
345+
)
346+
347+
list, err := reader.List()
348+
if err != nil {
349+
return nil, err
350+
}
351+
352+
for _, l := range list {
353+
storageName = nameFromAnnotations(l.ManifestDescriptor.Annotations)
354+
switch len(storageName) {
355+
case 0:
356+
// If there's no reference name in the annotations, compute an ID.
357+
storageName, err = getImageID(ctx, l.ImageRef, nil)
358+
if err != nil {
359+
return nil, err
360+
}
361+
imageName = "sha256:" + storageName[1:]
362+
default:
363+
named, err := NormalizeName(storageName)
364+
if err != nil {
365+
return nil, err
366+
}
367+
imageName = named.String()
368+
storageName = imageName
369+
}
370+
371+
// Create a storage reference.
372+
destRef, err := storageTransport.Transport.ParseStoreReference(r.store, storageName)
373+
if err != nil {
374+
return nil, errors.Wrapf(err, "parsing %q", storageName)
375+
}
376+
377+
if _, err := c.copy(ctx, l.ImageRef, destRef); err != nil {
378+
return nil, err
379+
}
380+
381+
names = append(names, imageName)
382+
383+
}
384+
385+
return names, nil
386+
387+
}
388+
346389
// copyFromRegistry pulls the specified, possibly unqualified, name from a
347390
// registry. On successful pull it returns the ID of the image in local
348391
// storage.

libimage/save.go

Lines changed: 100 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
dockerArchiveTransport "github.com/containers/image/v5/docker/archive"
1010
"github.com/containers/image/v5/docker/reference"
1111
"github.com/containers/image/v5/manifest"
12-
ociArchiveTransport "github.com/containers/image/v5/oci/archive"
12+
"github.com/containers/image/v5/oci/archive"
1313
ociTransport "github.com/containers/image/v5/oci/layout"
1414
"github.com/containers/image/v5/types"
1515
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
@@ -46,8 +46,8 @@ func (r *Runtime) Save(ctx context.Context, names []string, format, path string,
4646
case 1:
4747
// All formats support saving 1.
4848
default:
49-
if format != "docker-archive" {
50-
return errors.Errorf("unsupported format %q for saving multiple images (only docker-archive)", format)
49+
if format != "docker-archive" && format != "oci-archive" {
50+
return errors.Errorf("unsupported format %q for saving multiple images (only docker-archive and oci-archive)", format)
5151
}
5252
if len(options.AdditionalTags) > 0 {
5353
return errors.Errorf("cannot save multiple images with multiple tags")
@@ -56,15 +56,17 @@ func (r *Runtime) Save(ctx context.Context, names []string, format, path string,
5656

5757
// Dispatch the save operations.
5858
switch format {
59-
case "oci-archive", "oci-dir", "docker-dir":
59+
case "oci-dir", "docker-dir":
6060
if len(names) > 1 {
6161
return errors.Errorf("%q does not support saving multiple images (%v)", format, names)
6262
}
6363
return r.saveSingleImage(ctx, names[0], format, path, options)
64-
6564
case "docker-archive":
6665
options.ManifestMIMEType = manifest.DockerV2Schema2MediaType
67-
return r.saveDockerArchive(ctx, names, path, options)
66+
return r.saveArchive(ctx, names, format, path, options)
67+
case "oci-archive":
68+
options.ManifestMIMEType = ociv1.MediaTypeImageManifest
69+
return r.saveArchive(ctx, names, format, path, options)
6870
}
6971

7072
return errors.Errorf("unsupported format %q for saving images", format)
@@ -98,9 +100,6 @@ func (r *Runtime) saveSingleImage(ctx context.Context, name, format, path string
98100
// Prepare the destination reference.
99101
var destRef types.ImageReference
100102
switch format {
101-
case "oci-archive":
102-
destRef, err = ociArchiveTransport.NewReference(path, tag)
103-
104103
case "oci-dir":
105104
destRef, err = ociTransport.NewReference(path, tag)
106105
options.ManifestMIMEType = ociv1.MediaTypeImageManifest
@@ -132,10 +131,11 @@ func (r *Runtime) saveSingleImage(ctx context.Context, name, format, path string
132131
// data needed to properly save images. Since multiple names could refer to
133132
// the *same* image, we need to dance a bit and store additional "names".
134133
// Those can then be used as additional tags when copying.
135-
func (r *Runtime) saveDockerArchive(ctx context.Context, names []string, path string, options *SaveOptions) error {
134+
func (r *Runtime) saveArchive(ctx context.Context, names []string, format, path string, options *SaveOptions) (finalErr error) {
136135
type localImage struct {
137-
image *Image
138-
tags []reference.NamedTagged
136+
image *Image
137+
tags []reference.NamedTagged
138+
destNames []string
139139
}
140140

141141
additionalTags := []reference.NamedTagged{}
@@ -180,47 +180,112 @@ func (r *Runtime) saveDockerArchive(ctx context.Context, names []string, path st
180180
if withTag {
181181
local.tags = append(local.tags, tagged)
182182
}
183+
local.destNames = append(local.destNames, tagged.String())
183184
}
184185
localImages[image.ID()] = local
185186
if r.eventChannel != nil {
186187
defer r.writeEvent(&Event{ID: image.ID(), Name: path, Time: time.Now(), Type: EventTypeImageSave})
187188
}
188189
}
189190

190-
writer, err := dockerArchiveTransport.NewWriter(r.systemContextCopy(), path)
191-
if err != nil {
192-
return err
193-
}
194-
defer writer.Close()
195-
196-
for _, id := range orderedIDs {
197-
local, exists := localImages[id]
198-
if !exists {
199-
return errors.Errorf("internal error: saveDockerArchive: ID %s not found in local map", id)
200-
}
201-
202-
copyOpts := options.CopyOptions
203-
copyOpts.dockerArchiveAdditionalTags = local.tags
204-
205-
c, err := r.newCopier(&copyOpts)
191+
switch format {
192+
case "docker-archive":
193+
writer, err := dockerArchiveTransport.NewWriter(r.systemContextCopy(), path)
206194
if err != nil {
207195
return err
208196
}
209-
defer c.close()
197+
defer func() {
198+
err := writer.Close()
199+
if err == nil {
200+
return
201+
}
202+
if finalErr == nil {
203+
finalErr = err
204+
return
205+
}
206+
finalErr = errors.Wrap(finalErr, err.Error())
207+
}()
210208

211-
destRef, err := writer.NewReference(nil)
212-
if err != nil {
213-
return err
209+
for _, id := range orderedIDs {
210+
local, exists := localImages[id]
211+
if !exists {
212+
return errors.Errorf("internal error: saveDockerArchive: ID %s not found in local map", id)
213+
}
214+
215+
copyOpts := options.CopyOptions
216+
copyOpts.dockerArchiveAdditionalTags = local.tags
217+
218+
c, err := r.newCopier(&copyOpts)
219+
if err != nil {
220+
return err
221+
}
222+
defer c.close()
223+
224+
destRef, err := writer.NewReference(nil)
225+
if err != nil {
226+
return err
227+
}
228+
229+
srcRef, err := local.image.StorageReference()
230+
if err != nil {
231+
return err
232+
}
233+
234+
if _, err := c.copy(ctx, srcRef, destRef); err != nil {
235+
return err
236+
}
214237
}
215238

216-
srcRef, err := local.image.StorageReference()
239+
case "oci-archive":
240+
writer, err := archive.NewWriter(ctx, r.systemContextCopy(), path)
217241
if err != nil {
218242
return err
219243
}
244+
defer func() {
245+
err := writer.Close()
246+
if err == nil {
247+
return
248+
}
249+
if finalErr == nil {
250+
finalErr = err
251+
}
252+
finalErr = errors.Wrap(finalErr, err.Error())
253+
}()
254+
255+
for _, id := range orderedIDs {
256+
local, exists := localImages[id]
257+
if !exists {
258+
return errors.Errorf("internal error: saveOCIArchive: ID %s not found in local map", id)
259+
}
260+
261+
copyOpts := options.CopyOptions
262+
263+
c, err := r.newCopier(&copyOpts)
264+
if err != nil {
265+
return err
266+
}
267+
defer c.close()
268+
269+
for _, destName := range local.destNames {
270+
destRef, err := writer.NewReference(destName)
271+
if err != nil {
272+
return err
273+
}
274+
275+
srcRef, err := local.image.StorageReference()
276+
if err != nil {
277+
return err
278+
}
279+
280+
if _, err := c.copy(ctx, srcRef, destRef); err != nil {
281+
return err
282+
}
283+
}
220284

221-
if _, err := c.copy(ctx, srcRef, destRef); err != nil {
222-
return err
223285
}
286+
default:
287+
return errors.Errorf("some error occurred %q", format)
288+
224289
}
225290

226291
return nil

0 commit comments

Comments
 (0)