Skip to content

Conversation

@knight42
Copy link
Contributor

@knight42 knight42 commented Jan 4, 2023

Superseded by #8191

Ref #7592

Summary

Implement exporting image using the new Transfer API.

Implementation Details

Invoke the Transfer API with a dummy image.Store as source and archive.ImageExportStream as destination.

The reason why a dummy image.Store is chosen as source here is I learnt that the images to export are actually read on the containerd side, not ctr and we need to distinguish the export scenario from the others, so I choose to send a dummy image.Store to tell the local transfer service about that.

In addition, I have added some options to the archive.ImageExportStream to 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

@k8s-ci-robot k8s-ci-robot requested a review from dmcgowan January 4, 2023 09:11
@k8s-ci-robot
Copy link

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 /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions 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.

// 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 {
Copy link
Member

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.

Copy link
Contributor Author

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

Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Member

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?

Copy link
Contributor Author

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 🤔

@dmcgowan dmcgowan self-assigned this Jan 4, 2023
// Whether to include all platforms
bool all_platforms = 5;
// Skips the creation of the Docker compatible manifest.json file
bool skip_docker_manifest = 6;
Copy link
Member

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?

Copy link
Member

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.

}

err := client.Transfer(ctx,
image.NewStore(""), // a dummy image store
Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

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).

Copy link
Member

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.

Copy link
Contributor Author

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

Copy link
Contributor

@fangn2 fangn2 left a 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?

// 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 {
Copy link
Contributor

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.


// ImageExporter exports images to a writer
type ImageExporter interface {
Export(ctx context.Context, is images.Store, cs content.Store) error
Copy link
Contributor

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.

"github.com/containerd/containerd/pkg/transfer"
)

func (ts *localTransferService) exportStream(ctx context.Context, is transfer.ImageExporter, tops *transfer.Config) error {
Copy link
Contributor

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

Copy link
Contributor

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)

case transfer.ImagePusher:
return ts.push(ctx, s, d, topts)
case transfer.ImageExporter:
return ts.exportStream(ctx, d, topts)
Copy link
Contributor

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.

@dmcgowan dmcgowan added this to the 1.7 milestone Jan 21, 2023
@knight42 knight42 force-pushed the feat/transfer-export-image branch from 025fb09 to c0aea39 Compare February 28, 2023 17:20
@knight42 knight42 force-pushed the feat/transfer-export-image branch from c0aea39 to 54ff192 Compare February 28, 2023 17:23
Copy link
Contributor Author

@knight42 knight42 left a 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

Comment on lines +338 to +341
// ListExportImageNames returns a list of image names to export
func (is *Store) ListExportImageNames() []string {
return is.exportImageNames
}
Copy link
Contributor Author

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.

Comment on lines +51 to +75
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
}
}
Copy link
Contributor Author

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

@dmcgowan
Copy link
Member

dmcgowan commented Mar 1, 2023

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!

@dmcgowan dmcgowan mentioned this pull request Mar 1, 2023
@knight42
Copy link
Contributor Author

knight42 commented Mar 2, 2023

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.

@knight42 knight42 closed this Mar 2, 2023
@knight42 knight42 deleted the feat/transfer-export-image branch March 2, 2023 02:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

6 participants