client: add Import() and Export() for importing/exporting image in OCI format#1013
Conversation
Codecov Report
@@ Coverage Diff @@
## master #1013 +/- ##
==========================================
- Coverage 24.69% 23.64% -1.06%
==========================================
Files 38 40 +2
Lines 4317 4509 +192
==========================================
Hits 1066 1066
- Misses 3049 3241 +192
Partials 202 202
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Just don't add this until its implemented
There was a problem hiding this comment.
same here, don't add options until you are going to implement them
There was a problem hiding this comment.
Instead of taking a path, would it be better to have export work over a reader?
Export() (io.ReadCloser, error)?
This way, clients have more flexibility for how they handle the exported content, they can push it over a stream or write it to disk at their path. Right now this client is not very flexible when you take a path like this
There was a problem hiding this comment.
If we are going to use TAR (correct me if misunderstanding) for Export(),
Import() would use TAR as well: Import(ctx context.Context, ref string, tarr io.ReadCloser, opts ...ImportOpt)(Image, error).
However, the implementation for Import() is likely to be inefficient, because TAR cannot be read randomly.
So my suggestion is to use directory by default across both Export() and Import().
We can still add WithOCITarExportation(tarr io.Reader) ExportOpt for tar support.
WDYT?
There was a problem hiding this comment.
Also, directory representation might be useful for Export() as well, because we can export blobs in parallel. (Unlike generic filesystem IO operation, this would be CPU-intensive for digest computation and good for multi-threading, but I didn't do benchmark 😅 )
There was a problem hiding this comment.
What is the usecase you are solving for because I think you are underestimating how inefficient the on disk format is.
There was a problem hiding this comment.
Image distribution using NFS/S3/SSHFS/IPFS/whatever.
tar cf /mnt/nfs-or-whatever/some-image | dist import foo/some-image:some-ref-name --ref-name some-ref-name - would cause I/O for unneeded blobs, unless tar is aware of OCI format and the ref name being used.
Also, dist cannot read index.json without scanning whole the "tape".
|
Implemented both Import/Export for both directory representation and TAR representation. @crosbymichael |
|
Will review, thanks! |
|
In your changes you didn't address the feedback. You are still outputting files to disk instead of returning/consuming an |
|
@crosbymichael I can remove extra WDYT? |
|
That is fine for the cli tools but I meant the method signatures for the
This way the client is useful by anyone, local, over a socket, etc and the CLI can save the tar, extract to a directory, etc. |
|
Its extremely hard to mock, test, use generally when packages write to files on disk internally. |
18a3168 to
e5482bd
Compare
|
OK, changed API as follows: // Import imports an image from a Tar stream using reader.
// OCI format is assumed by default.
//
// Note that unreferenced blobs are imported to the content store as well.
func (c *Client) Import(ctx context.Context, ref string, reader io.Reader, opts ...ImportOpt) (Image, error)// ImportDirectory imports an image from a directory using opener.
// OCI format is assumed by default.
//
// Unlike Import(), only blobs referenced by the manifest are imported to the content store.
//
// opener is typically `func(relPath string)(io.ReadCloser, error){ return os.Open(filepath.Join(someDir, relPath)) }`,
// but it does not need to use real filesystem.
func (c *Client) ImportDirectory(ctx context.Context, ref string, opener func(string) (io.ReadCloser, error), opts ...ImportOpt) (Image, error)// Export exports an image to a Tar stream.
// OCI format is used by default.
// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc.
func (c *Client) Export(ctx context.Context, desc ocispec.Descriptor, opts ...ExportOpt) (io.ReadCloser, error)// No `ExportDirectory` |
|
|
||
| // Directory is ImageDriver for directory representation of an OCI image. | ||
| // The directory must not exist before calling Init function of this structure. | ||
| func Directory(path string) ImageDriver { |
There was a problem hiding this comment.
Just note that this directory ImageDriver is not used at all in containerd, but can be used for 3rd party projects.
(My https://github.com/AkihiroSuda/filegrain is going to vendor this)
There was a problem hiding this comment.
I thought we were removing this.
It would be good to put together a proposal for directory based layouts. This use case really doesn't make sense. The OCI layout is meant for an archive.
| // | ||
| // img directory must not exist before calling this function. | ||
| // BlobWriter writes an OCI blob and returns a digest when closed. | ||
| type BlobWriter interface { |
There was a problem hiding this comment.
This looks eerily similar to content.Writer. Let's avoid duplication.
There was a problem hiding this comment.
The content package is internal detail of containerd while oci package is specific to OCI format.
So I'm not sure we should deduplicate, but may I try that in follow-up PR?
There was a problem hiding this comment.
e.g. future content package may use non-filesystem blob store, or just use sharded directory (blobs/sha256/de/deadbeef)
Then it could not be deduplicated
There was a problem hiding this comment.
@AkihiroSuda The content package is the premiere approach for doing io on OCI oriented content. Use the interface definition, don't define a new one that is slightly different.
| // | ||
| // opener is typically `func(relPath string)(io.ReadCloser, error){ return os.Open(filepath.Join(someDir, relPath)) }`, | ||
| // but it does not need to use real filesystem. | ||
| func (c *Client) ImportDirectory(ctx context.Context, ref string, opener func(string) (io.ReadCloser, error), opts ...ImportOpt) (Image, error) { |
There was a problem hiding this comment.
I'm not sure that this directory layout format is optimal, nor am I sure that handling directories and tarfiles with the same code path is the best for consistent implementations.
Is there any way we could split Import/Export and ImportDirectory into two PRs?
|
Eliminated I'll open follow-up PR for reviving |
|
Removed However, I do still think OCI is not limited to be used solely for archival purpose, let me discuss that in follow-up PR:
|
|
@AkihiroSuda Thanks! That document is making a leap to say that the layout can be used for "shared filesystem environments (e.g. nfs), or networked file fetching (e.g. http, ftp, rsync)." The layout makes no provisions for concurrency, updates or security in such environments. |
|
@aaronlehmann Updated PR |
|
@AkihiroSuda Looks like this needs another rebase. |
|
Rebased. Now CLI commands are |
|
@AkihiroSuda Should be |
|
done @stevvooe |
There was a problem hiding this comment.
Shouldn't format be resolved by the incoming file?
There was a problem hiding this comment.
It could be easily resolved if we can add support for directories.
However, for tar, we can't resolve the format until reading whole the "tape" to find oci-layout.
There was a problem hiding this comment.
I don't think directories solves that problem. Would it make sense to pre-unpack the format then resolve the version?
There was a problem hiding this comment.
Does it work with a foreign format other than OCI v1?
There was a problem hiding this comment.
For the most part, any archival format should be safe for pre-unpack, to some degree. However, what happens when there is a new oci layout format?
There was a problem hiding this comment.
Future OCI would need to be specified manually with WithOCIv2ImportFormat(), if it is incompatible to v1
There was a problem hiding this comment.
As you know Docker will very soonly support save/load in OCI format. moby/moby#33355
Do you want the classic save/load support in containerd?
(Can be another PR?)
There was a problem hiding this comment.
Do you want the classic save/load support in containerd?
Yes. OCI save/load support may not be available for a few versions.
There was a problem hiding this comment.
OK, but let me do that in a follow-up PR 😅
|
LGTM PTAL @dmcgowan |
|
rebased, @dmcgowan @crosbymichael @mlaventure PTAL? |
…I format
Export as a tar (Note: "-" can be used for stdout):
$ ctr images export /tmp/oci-busybox.tar docker.io/library/busybox:latest
Import a tar (Note: "-" can be used for stdin):
$ ctr images import foo/new:latest /tmp/oci-busybox.tar
Note: media types are not converted at the moment: e.g.
application/vnd.docker.image.rootfs.diff.tar.gzip
-> application/vnd.oci.image.layer.v1.tar+gzip
Signed-off-by: Akihiro Suda <[email protected]>
|
CI green |
Part of #831
Export as a tar (Note: "-" can be used for stdout):
Import a tar (Note: "-" can be used for stdin):
Note:
Signed-off-by: Akihiro Suda [email protected]