Feature: export image using the Transfer API#7916
Feature: export image using the Transfer API#7916knight42 wants to merge 6 commits intocontainerd:mainfrom
Transfer API#7916Conversation
|
Hi @knight42. Thanks for your PR. I'm waiting for a containerd member to verify that this patch is reasonable to test. If it is, they should reply with Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
9848186 to
482f88d
Compare
pkg/transfer/archive/exporter.go
Outdated
| // NewImageExportStream returns a image importer via tar stream | ||
| // TODO: Add export options | ||
| func NewImageExportStream(stream io.WriteCloser, mediaType string) *ImageExportStream { | ||
| func NewImageExportStream(stream io.WriteCloser, mediaType string, opts ExportOptions) *ImageExportStream { |
There was a problem hiding this comment.
Would it be more idiomatic to use functional options pattern here with something like the following? It would be the same functionally but cleans up the callsites a bit imo.
type ImageExportStreamOpt func(*ImageExportStream)
func NewImageExportStream(stream io.WriteCloser, mediaType string, opts ...ImageExportStreamOpt) *ImageExportStream {
...
}Edit: Oh just seen your comment in PR description. Personally I think options pattern matches other areas of containerd + cleans up callsites. See what others thinks.
There was a problem hiding this comment.
OK I would like to change it later
There was a problem hiding this comment.
Second @austinvazquez. The importer implementation is a good reference.
With this, Export(ctx context.Context, is images.Store, cs content.Store) can be significantly simplified.
There was a problem hiding this comment.
I just realize there might be naming collision between the export and import options, regarding to the platform related ones. We might need to rename these options, like WithExportPlatforms(...), WithImportPlatforms, or simply put them into separate packages.
|
|
||
| err = is.Export(ctx, ts.images, ts.content) | ||
| if err != nil { | ||
| return err |
There was a problem hiding this comment.
Would we need to send an event to tops here that export has been cancelled due to error?
There was a problem hiding this comment.
I would like to, but I don't know what is the content that I should send. I have referred to the implementation of import, it does not send neither 🤔
| // Whether to include all platforms | ||
| bool all_platforms = 5; | ||
| // Skips the creation of the Docker compatible manifest.json file | ||
| bool skip_docker_manifest = 6; |
There was a problem hiding this comment.
Is this only required for compatibility with older versions of Docker now?
There was a problem hiding this comment.
Still no version of docker that supports OCI layout.
cmd/ctr/commands/images/export.go
Outdated
| } | ||
|
|
||
| err := client.Transfer(ctx, | ||
| image.NewStore(""), // a dummy image store |
There was a problem hiding this comment.
We can create a new type that represents multiple images. The idea is that the source could be anything, it could be a single image from the image store or in this case multiple images from the image store. It could also be sourced from a registry. The images shouldn't be used as export options though since that is information about the source.
There was a problem hiding this comment.
We can create a new type that represents multiple images
Yeah we could do that, shall we add a new type MultiImageStore with a GetImages method? like:
type MultiImageStore struct {
images [] string
}
func (s *MultiImageStore) GetImageNames() []string {
return s.images
}
then we could add an interface for it:
type ImageNamesGetter interface {
GetImageNames() []string
}
then we could pass it to the method exportStream of localTransferService to get the image names specified by user.
I am not sure if there is a better way to do this, from my perspective how we get image in export is similar to push, the only difference is we could export multiple images, and the current implementation of image.Store only takes single image into consideration.
I propose we could split the current image.Store into a source image store and a destionation image store, while we could get multiple images from a source image store, but could only save only single image to a destination image store. Therefore we could reuse the source image store in export and push(we might want to support pushing multiple images later :P) WDYT?
There was a problem hiding this comment.
We can likely re-use the ImageStore and provide an extra names array or extend to names. I think extra names may make sense so that it can implement a GetImage but optionally provide more image names. There could be cases in the future where we want to provide additional references on pull as well (such as store by tag@digest, a reference which might be created dynamically on store).
There was a problem hiding this comment.
I'm playing around with a change to the image store transfer interface and import logic that may make this cleaner.
There was a problem hiding this comment.
I'm playing around with a change to the image store transfer interface and import logic that may make this cleaner.
@dmcgowan That's great! I would like to help if there is anything I could do
pkg/transfer/archive/exporter.go
Outdated
| // NewImageExportStream returns a image importer via tar stream | ||
| // TODO: Add export options | ||
| func NewImageExportStream(stream io.WriteCloser, mediaType string) *ImageExportStream { | ||
| func NewImageExportStream(stream io.WriteCloser, mediaType string, opts ExportOptions) *ImageExportStream { |
There was a problem hiding this comment.
Second @austinvazquez. The importer implementation is a good reference.
With this, Export(ctx context.Context, is images.Store, cs content.Store) can be significantly simplified.
pkg/transfer/transfer.go
Outdated
|
|
||
| // ImageExporter exports images to a writer | ||
| type ImageExporter interface { | ||
| Export(ctx context.Context, is images.Store, cs content.Store) error |
There was a problem hiding this comment.
Suggest remove variable name which is not necessary Export(context.Context, images.Store, content.Store) error for consistency with other interface methods.
pkg/transfer/local/export.go
Outdated
| "github.com/containerd/containerd/pkg/transfer" | ||
| ) | ||
|
|
||
| func (ts *localTransferService) exportStream(ctx context.Context, is transfer.ImageExporter, tops *transfer.Config) error { |
There was a problem hiding this comment.
nit: variable name ie seems better for type transfer.ImageExporter
There was a problem hiding this comment.
The profile of exportSteam doesn't look right to me.
I think it should be exportStream(ctx context.Context, ig transfer.ImageGetter, ie transfer.ImageExporter, tops *transfer.Config)
pkg/transfer/local/transfer.go
Outdated
| case transfer.ImagePusher: | ||
| return ts.push(ctx, s, d, topts) | ||
| case transfer.ImageExporter: | ||
| return ts.exportStream(ctx, d, topts) |
There was a problem hiding this comment.
I think it should be return ts.exportStream(ctx, s, d, topts) t comply the Transfer API structure.
Signed-off-by: Jian Zeng <[email protected]>
Signed-off-by: Jian Zeng <[email protected]>
Signed-off-by: Jian Zeng <[email protected]>
Signed-off-by: Jian Zeng <[email protected]>
Signed-off-by: Jian Zeng <[email protected]>
025fb09 to
c0aea39
Compare
Signed-off-by: Jian Zeng <[email protected]>
c0aea39 to
54ff192
Compare
| // ListExportImageNames returns a list of image names to export | ||
| func (is *Store) ListExportImageNames() []string { | ||
| return is.exportImageNames | ||
| } |
There was a problem hiding this comment.
@dmcgowan I decided to save the names of images to be exported in the image store.
| type ExportOpt func(*ImageExportStream) | ||
|
|
||
| func WithSkipNonDistributable() ExportOpt { | ||
| return func(ies *ImageExportStream) { | ||
| ies.skipNonDistributable = true | ||
| } | ||
| } | ||
|
|
||
| func WithSkipDockerManifest() ExportOpt { | ||
| return func(ies *ImageExportStream) { | ||
| ies.skipDockerManifest = true | ||
| } | ||
| } | ||
|
|
||
| func WithAllPlatforms() ExportOpt { | ||
| return func(ies *ImageExportStream) { | ||
| ies.allPlatforms = true | ||
| } | ||
| } | ||
|
|
||
| func WithPlatforms(platforms ...v1.Platform) ExportOpt { | ||
| return func(ies *ImageExportStream) { | ||
| ies.platforms = platforms | ||
| } | ||
| } |
There was a problem hiding this comment.
conform to functional options pattern
|
Thanks @knight42! I'm still testing this locally and have a few changes in the process. I am planning on carrying those changes though after I finish testing to make sure we can get this in for 1.7. It'll keep your commit history though. I'll comment here when done, thanks! |
Got it, sorry for the late response. I guess I cloud close this PR then, thank you. |
Superseded by #8191
Ref #7592
Summary
Implement exporting image using the new
TransferAPI.Implementation Details
Invoke the
TransferAPI with a dummyimage.Storeas source andarchive.ImageExportStreamas destination.The reason why a dummy
image.Storeis chosen as source here is I learnt that the images to export are actually read on the containerd side, notctrand we need to distinguish the export scenario from the others, so I choose to send a dummyimage.Storeto tell the local transfer service about that.In addition, I have added some options to the
archive.ImageExportStreamto gain the same ability as "non-local" export. These options are hold in a config struct, does not follow the functional options pattern, b/c I think this would save me some typings. This might be opinionated, I could switch to functional options if that's necessary./cc @dmcgowan