Skip to content

Commit 9609f04

Browse files
committed
import/export: Support references to missing content
Allow importing/exporting archives which doesn't have all the referenced blobs. This allows to export/import an image with only some of the platforms available locally while still persisting the full index. > The blobs directory MAY be missing referenced blobs, in which case the missing blobs SHOULD be fulfilled by an external blob store. https://github.com/opencontainers/image-spec/blob/v1.0/image-layout.md#blobs Signed-off-by: Paweł Gronowski <[email protected]> (cherry picked from commit 61a7c49) Signed-off-by: Paweł Gronowski <[email protected]>
1 parent 42b60d8 commit 9609f04

3 files changed

Lines changed: 79 additions & 4 deletions

File tree

content/helpers.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,3 +332,14 @@ func copyWithBuffer(dst io.Writer, src io.Reader) (written int64, err error) {
332332
}
333333
return
334334
}
335+
336+
// Exists returns whether an attempt to access the content would not error out
337+
// with an ErrNotFound error. It will return an encountered error if it was
338+
// different than ErrNotFound.
339+
func Exists(ctx context.Context, provider InfoProvider, desc ocispec.Descriptor) (bool, error) {
340+
_, err := provider.Info(ctx, desc.Digest)
341+
if errdefs.IsNotFound(err) {
342+
return false, nil
343+
}
344+
return err == nil, err
345+
}

images/archive/exporter.go

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,45 @@ func WithSkipNonDistributableBlobs() ExportOpt {
140140
return WithBlobFilter(f)
141141
}
142142

143+
// WithSkipMissing excludes blobs referenced by manifests if not all blobs
144+
// would be included in the archive.
145+
// The manifest itself is excluded only if it's not present locally.
146+
// This allows to export multi-platform images if not all platforms are present
147+
// while still persisting the multi-platform index.
148+
func WithSkipMissing(store ContentProvider) ExportOpt {
149+
return func(ctx context.Context, o *exportOptions) error {
150+
o.blobRecordOptions.childrenHandler = images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) {
151+
children, err := images.Children(ctx, store, desc)
152+
if !images.IsManifestType(desc.MediaType) {
153+
return children, err
154+
}
155+
156+
if err != nil {
157+
// If manifest itself is missing, skip it from export.
158+
if errdefs.IsNotFound(err) {
159+
return nil, images.ErrSkipDesc
160+
}
161+
return nil, err
162+
}
163+
164+
// Don't export manifest descendants if any of them doesn't exist.
165+
for _, child := range children {
166+
exists, err := content.Exists(ctx, store, child)
167+
if err != nil {
168+
return nil, err
169+
}
170+
171+
// If any child is missing, only export the manifest, but don't export its descendants.
172+
if !exists {
173+
return nil, nil
174+
}
175+
}
176+
return children, nil
177+
})
178+
return nil
179+
}
180+
}
181+
143182
func addNameAnnotation(name string, base map[string]string) map[string]string {
144183
annotations := map[string]string{}
145184
for k, v := range base {
@@ -152,8 +191,14 @@ func addNameAnnotation(name string, base map[string]string) map[string]string {
152191
return annotations
153192
}
154193

194+
// ContentProvider provides both content and info about content
195+
type ContentProvider interface {
196+
content.Provider
197+
content.InfoProvider
198+
}
199+
155200
// Export implements Exporter.
156-
func Export(ctx context.Context, store content.Provider, writer io.Writer, opts ...ExportOpt) error {
201+
func Export(ctx context.Context, store ContentProvider, writer io.Writer, opts ...ExportOpt) error {
157202
var eo exportOptions
158203
for _, opt := range opts {
159204
if err := opt(ctx, &eo); err != nil {
@@ -291,7 +336,10 @@ func getRecords(ctx context.Context, store content.Provider, desc ocispec.Descri
291336
return nil, nil
292337
}
293338

294-
childrenHandler := images.ChildrenHandler(store)
339+
childrenHandler := brOpts.childrenHandler
340+
if childrenHandler == nil {
341+
childrenHandler = images.ChildrenHandler(store)
342+
}
295343

296344
handlers := images.Handlers(
297345
childrenHandler,
@@ -313,7 +361,8 @@ type tarRecord struct {
313361
}
314362

315363
type blobRecordOptions struct {
316-
blobFilter BlobFilter
364+
blobFilter BlobFilter
365+
childrenHandler images.HandlerFunc
317366
}
318367

319368
func blobRecord(cs content.Provider, desc ocispec.Descriptor, opts *blobRecordOptions) tarRecord {

import.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type importOpts struct {
3939
platformMatcher platforms.MatchComparer
4040
compress bool
4141
discardLayers bool
42+
skipMissing bool
4243
}
4344

4445
// ImportOpt allows the caller to specify import specific options
@@ -115,6 +116,15 @@ func WithDiscardUnpackedLayers() ImportOpt {
115116
}
116117
}
117118

119+
// WithSkipMissing allows to import an archive which doesn't contain all the
120+
// referenced blobs.
121+
func WithSkipMissing() ImportOpt {
122+
return func(c *importOpts) error {
123+
c.skipMissing = true
124+
return nil
125+
}
126+
}
127+
118128
// Import imports an image from a Tar stream using reader.
119129
// Caller needs to specify importer. Future version may use oci.v1 as the default.
120130
// Note that unreferenced blobs may be imported to the content store as well.
@@ -164,7 +174,12 @@ func (c *Client) Import(ctx context.Context, reader io.Reader, opts ...ImportOpt
164174
var handler images.HandlerFunc = func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
165175
// Only save images at top level
166176
if desc.Digest != index.Digest {
167-
return images.Children(ctx, cs, desc)
177+
// Don't set labels on missing content.
178+
children, err := images.Children(ctx, cs, desc)
179+
if iopts.skipMissing && errdefs.IsNotFound(err) {
180+
return nil, images.ErrSkipDesc
181+
}
182+
return children, err
168183
}
169184

170185
p, err := content.ReadBlob(ctx, cs, desc)

0 commit comments

Comments
 (0)