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 <pawel.gronowski@docker.com>
This commit is contained in:
parent
1da783894b
commit
61a7c4999c
@ -37,6 +37,7 @@ type importOpts struct {
|
|||||||
platformMatcher platforms.MatchComparer
|
platformMatcher platforms.MatchComparer
|
||||||
compress bool
|
compress bool
|
||||||
discardLayers bool
|
discardLayers bool
|
||||||
|
skipMissing bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImportOpt allows the caller to specify import specific options
|
// ImportOpt allows the caller to specify import specific options
|
||||||
@ -113,6 +114,15 @@ func WithDiscardUnpackedLayers() ImportOpt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithSkipMissing allows to import an archive which doesn't contain all the
|
||||||
|
// referenced blobs.
|
||||||
|
func WithSkipMissing() ImportOpt {
|
||||||
|
return func(c *importOpts) error {
|
||||||
|
c.skipMissing = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Import imports an image from a Tar stream using reader.
|
// Import imports an image from a Tar stream using reader.
|
||||||
// Caller needs to specify importer. Future version may use oci.v1 as the default.
|
// Caller needs to specify importer. Future version may use oci.v1 as the default.
|
||||||
// Note that unreferenced blobs may be imported to the content store as well.
|
// Note that unreferenced blobs may be imported to the content store as well.
|
||||||
@ -162,7 +172,12 @@ func (c *Client) Import(ctx context.Context, reader io.Reader, opts ...ImportOpt
|
|||||||
var handler images.HandlerFunc = func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
var handler images.HandlerFunc = func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||||
// Only save images at top level
|
// Only save images at top level
|
||||||
if desc.Digest != index.Digest {
|
if desc.Digest != index.Digest {
|
||||||
return images.Children(ctx, cs, desc)
|
// Don't set labels on missing content.
|
||||||
|
children, err := images.Children(ctx, cs, desc)
|
||||||
|
if iopts.skipMissing && errdefs.IsNotFound(err) {
|
||||||
|
return nil, images.ErrSkipDesc
|
||||||
|
}
|
||||||
|
return children, err
|
||||||
}
|
}
|
||||||
|
|
||||||
idx, err := decodeIndex(ctx, cs, desc)
|
idx, err := decodeIndex(ctx, cs, desc)
|
||||||
|
@ -320,3 +320,14 @@ func copyWithBuffer(dst io.Writer, src io.Reader) (written int64, err error) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exists returns whether an attempt to access the content would not error out
|
||||||
|
// with an ErrNotFound error. It will return an encountered error if it was
|
||||||
|
// different than ErrNotFound.
|
||||||
|
func Exists(ctx context.Context, provider InfoProvider, desc ocispec.Descriptor) (bool, error) {
|
||||||
|
_, err := provider.Info(ctx, desc.Digest)
|
||||||
|
if errdefs.IsNotFound(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return err == nil, err
|
||||||
|
}
|
||||||
|
@ -140,6 +140,45 @@ func WithSkipNonDistributableBlobs() ExportOpt {
|
|||||||
return WithBlobFilter(f)
|
return WithBlobFilter(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithSkipMissing excludes blobs referenced by manifests if not all blobs
|
||||||
|
// would be included in the archive.
|
||||||
|
// The manifest itself is excluded only if it's not present locally.
|
||||||
|
// This allows to export multi-platform images if not all platforms are present
|
||||||
|
// while still persisting the multi-platform index.
|
||||||
|
func WithSkipMissing(store ContentProvider) ExportOpt {
|
||||||
|
return func(ctx context.Context, o *exportOptions) error {
|
||||||
|
o.blobRecordOptions.childrenHandler = images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) {
|
||||||
|
children, err := images.Children(ctx, store, desc)
|
||||||
|
if !images.IsManifestType(desc.MediaType) {
|
||||||
|
return children, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// If manifest itself is missing, skip it from export.
|
||||||
|
if errdefs.IsNotFound(err) {
|
||||||
|
return nil, images.ErrSkipDesc
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't export manifest descendants if any of them doesn't exist.
|
||||||
|
for _, child := range children {
|
||||||
|
exists, err := content.Exists(ctx, store, child)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any child is missing, only export the manifest, but don't export its descendants.
|
||||||
|
if !exists {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return children, nil
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func addNameAnnotation(name string, base map[string]string) map[string]string {
|
func addNameAnnotation(name string, base map[string]string) map[string]string {
|
||||||
annotations := map[string]string{}
|
annotations := map[string]string{}
|
||||||
for k, v := range base {
|
for k, v := range base {
|
||||||
@ -152,8 +191,14 @@ func addNameAnnotation(name string, base map[string]string) map[string]string {
|
|||||||
return annotations
|
return annotations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContentProvider provides both content and info about content
|
||||||
|
type ContentProvider interface {
|
||||||
|
content.Provider
|
||||||
|
content.InfoProvider
|
||||||
|
}
|
||||||
|
|
||||||
// Export implements Exporter.
|
// Export implements Exporter.
|
||||||
func Export(ctx context.Context, store content.Provider, writer io.Writer, opts ...ExportOpt) error {
|
func Export(ctx context.Context, store ContentProvider, writer io.Writer, opts ...ExportOpt) error {
|
||||||
var eo exportOptions
|
var eo exportOptions
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
if err := opt(ctx, &eo); err != nil {
|
if err := opt(ctx, &eo); err != nil {
|
||||||
@ -291,7 +336,10 @@ func getRecords(ctx context.Context, store content.Provider, desc ocispec.Descri
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
childrenHandler := images.ChildrenHandler(store)
|
childrenHandler := brOpts.childrenHandler
|
||||||
|
if childrenHandler == nil {
|
||||||
|
childrenHandler = images.ChildrenHandler(store)
|
||||||
|
}
|
||||||
|
|
||||||
handlers := images.Handlers(
|
handlers := images.Handlers(
|
||||||
childrenHandler,
|
childrenHandler,
|
||||||
@ -313,7 +361,8 @@ type tarRecord struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type blobRecordOptions struct {
|
type blobRecordOptions struct {
|
||||||
blobFilter BlobFilter
|
blobFilter BlobFilter
|
||||||
|
childrenHandler images.HandlerFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func blobRecord(cs content.Provider, desc ocispec.Descriptor, opts *blobRecordOptions) tarRecord {
|
func blobRecord(cs content.Provider, desc ocispec.Descriptor, opts *blobRecordOptions) tarRecord {
|
||||||
|
Loading…
Reference in New Issue
Block a user