-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Feature: export image using the Transfer API
#7916
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK I would like to change it later
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this only required for compatibility with older versions of Docker now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR.
The top level question I have is the design of Transfer API for export feature, why is dummy image.Store used as source? Shouldn't ImageGetter be used which gets an image from images.store similar to push?
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: variable name ie seems better for type transfer.ImageExporter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
knight42
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dmcgowan Hi I have refactored the implementation a bit, PTAL
| // ListExportImageNames returns a list of image names to export | ||
| func (is *Store) ListExportImageNames() []string { | ||
| return is.exportImageNames | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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