Skip to content
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

Add reader/writer for oci-archive multi image support #1381

Closed
wants to merge 1 commit into from

Conversation

umohnani8
Copy link
Member

Add reader/writer with helpers to allow podman save/load multi oci-archive images.
Allow read oci-archive using source_index to point to the index from oci-archive manifest.
Also reimplement ociArchiveImage{Source,Destination} to support this.

Taking over #1072
Fixes #1116

Signed-off-by: Qi Wang [email protected]
Signed-off-by: Urvashi Mohnani [email protected]

@umohnani8
Copy link
Member Author

@vrothberg @mtrmac PTAL
Looked through #1072 and most of the reviews were addressed, did some fixes for the few comments that weren't already fixed - please let me know if I missed any.
Will open up a podman vendor + patch PR once this is in.

@vrothberg
Copy link
Member

Will look into it now. Note that it will require some massaging into c/common/libimage prior to Podman.

Copy link
Member

@vrothberg vrothberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before merging, we need some tests somewhere. For multi-image docker archives, we added them to Podman and those have since been moved to libimage (see https://github.com/containers/common/blob/main/libimage/save_test.go and https://github.com/containers/common/blob/main/libimage/load_test.go and https://github.com/containers/common/tree/main/libimage/testdata).

I suggest opening a PR against c/common vendoring in this PR. Once everything's green, we can merge here, then merge into libimage and then let it bubble up into Podman.

docs/containers-transports.5.md Outdated Show resolved Hide resolved
oci/internal/oci_util_test.go Show resolved Hide resolved
oci/archive/oci_transport.go Outdated Show resolved Hide resolved
oci/archive/oci_transport.go Outdated Show resolved Hide resolved
oci/archive/oci_transport.go Outdated Show resolved Hide resolved
oci/archive/oci_dest.go Outdated Show resolved Hide resolved
oci/archive/reader.go Outdated Show resolved Hide resolved
oci/archive/reader.go Outdated Show resolved Hide resolved
Copy link
Collaborator

@mtrmac mtrmac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WIP, so far I’ve just read parts of oci/archive.Reader.

Structurally it seems clean to me for oci/archive.ociArchiveImageSource to be always built on top of an oci/archive.Reader, and similarly for destination/Writer. I do feel somewhat strongly about this, OTOH the current approach around the existing tempDirOCIRef is probably less churn and easier to review, for now.

oci/archive/reader.go Outdated Show resolved Hide resolved
oci/archive/reader.go Show resolved Hide resolved
}
succeeded = true
return &reader, nil
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docker/archive.Reader needs to provide a NewReaderForReference for libimage, AFAICS to turn an user-supplied docker-archive:…@0 into a tag used in c/storage on podman load.

Will something like that be necessary here? (Cc: @vrothberg )

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I update NewReader to take in a reference, if that is wrong I can break them apart tot two different functions NewReader and NewReaderForReference`.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would definitely make sense to provide a NewReader that accepts a path, so that direct usage of a Reader is simpler.

As to the NewReaderForReference, that requires looking at what c/common needs. The docker-archive variant requires NewReaderForReference also to return a reader-bound equivalent of the input reference (because we don’t otherwise expose ref.index and ref.sourceIndex, so the caller can’t do that itself), and Reader.ManifestTagsForReference. Looking at the nameFromAnnotations call in c/common, something vaguely like that might be necessary here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM

oci/archive/reader.go Outdated Show resolved Hide resolved
oci/archive/reader.go Outdated Show resolved Hide resolved
oci/archive/oci_transport.go Outdated Show resolved Hide resolved
oci/archive/oci_src.go Outdated Show resolved Hide resolved
oci/archive/oci_dest.go Outdated Show resolved Hide resolved
oci/layout/oci_transport.go Outdated Show resolved Hide resolved
oci/archive/oci_dest.go Outdated Show resolved Hide resolved
oci/archive/reader.go Outdated Show resolved Hide resolved
@vrothberg
Copy link
Member

As noted by @mtrmac, this PR should have a similar logic as in containers/common#853 (review) to account for archives generated with buildkit.

@umohnani8
Copy link
Member Author

@vrothberg @mtrmac I think I addressed most of the comments here, please let me know if I accidentally missed any.

The c/common PR for this is containers/common#921

@umohnani8 umohnani8 force-pushed the multi-manifest branch 3 times, most recently from e1b0dbd to e4964c0 Compare February 8, 2022 15:17
@umohnani8 umohnani8 force-pushed the multi-manifest branch 3 times, most recently from 1165cd5 to 460f04d Compare February 21, 2022 17:55
@umohnani8
Copy link
Member Author

Can we please re-run the failed tests - failure looks like a flake.

@vrothberg
Copy link
Member

Can we please re-run the failed tests - failure looks like a flake.

Retrigger ✔️

@umohnani8
Copy link
Member Author

This is ready for review, c/common PR is green containers/common#921.
@vrothberg @mtrmac PTAL

Also test failure looks like a flake again

@vrothberg
Copy link
Member

oci/archive/oci_dest.go Outdated Show resolved Hide resolved
}
succeeded = true
return &reader, nil
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM

oci/archive/reader.go Outdated Show resolved Hide resolved
oci/layout/oci_transport.go Outdated Show resolved Hide resolved
oci/layout/oci_transport.go Outdated Show resolved Hide resolved
var d *imgspecv1.Descriptor

if ref.sourceIndex != -1 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: WIth three cases, this could be a switch (and perhaps with an explicit “internal error” case if both image and sourceIndex are set).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do in a follow up PR

Comment on lines +221 to +219
d = &index.Manifests[ref.sourceIndex]
return *d, nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn’t this just return index.Manifests[ref.sourceIndex]?

(Overall the d variable seems basically unnecessary — everything can just return a value, and the d == nil case only possible after the for loop`. OTOH cleaning up the pre-existing code is not really in scope, this PR is big enough.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do in a follow up PR

oci/layout/oci_transport_test.go Show resolved Hide resolved
docs/containers-transports.5.md Outdated Show resolved Hide resolved
docs/containers-transports.5.md Outdated Show resolved Hide resolved
@@ -144,7 +178,7 @@ func (ref ociArchiveReference) NewImageDestination(ctx context.Context, sys *typ

// DeleteImage deletes the named image from the registry, if supported.
func (ref ociArchiveReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
return errors.Errorf("Deleting images not implemented for oci: images")
return fmt.Errorf("Deleting images not implemented for oci: images")
}

// struct to store the ociReference and temporary directory returned by createOCIRef
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(If LoadManifestDescriptor* is reimplemented) I imagine no users of tempDirOCIRef should remain — just Readers and Writers, now that they “own” their temporary directories. Maybe with a shared helper to create an oci/layout reference from a (temp dir, archive reference).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do in a follow up PR

oci/archive/reader.go Show resolved Hide resolved
oci/archive/oci_src.go Outdated Show resolved Hide resolved
oci/archive/reader.go Outdated Show resolved Hide resolved
@umohnani8 umohnani8 force-pushed the multi-manifest branch 2 times, most recently from 63283ce to 6ce3216 Compare March 3, 2022 01:19
@umohnani8 umohnani8 force-pushed the multi-manifest branch 2 times, most recently from 2de595a to e6ece2b Compare March 9, 2022 15:39
@umohnani8
Copy link
Member Author

@mtrmac addressed all your comments PTAL

Copy link
Collaborator

@mtrmac mtrmac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Looks great overall.

The one real blocker is #1381 (comment) / containers/common#921 (comment) .

oci/archive/oci_dest.go Outdated Show resolved Hide resolved
oci/archive/oci_dest.go Outdated Show resolved Hide resolved
oci/archive/oci_dest.go Show resolved Hide resolved
oci/archive/oci_dest.go Outdated Show resolved Hide resolved
oci/archive/oci_dest.go Outdated Show resolved Hide resolved
oci/archive/oci_src.go Outdated Show resolved Hide resolved
oci/archive/oci_src.go Show resolved Hide resolved
oci/archive/oci_src.go Outdated Show resolved Hide resolved
oci/archive/oci_src.go Outdated Show resolved Hide resolved
oci/archive/reader.go Outdated Show resolved Hide resolved
@tanguofu
Copy link

tanguofu commented Jun 5, 2022

@mtrmac addressed all your comments PTAL

@umohnani8 could u has some update here? we look for this API canbe published, many thanks,

Add reader/writer with helpers to allow podman save/load multi oci-archive images.
Allow read oci-archive using source_index to point to the index from oci-archive manifest.
Also reimplement ociArchiveImage{Source,Destination} to support this.

Signed-off-by: Qi Wang <[email protected]>
Signed-off-by: Urvashi Mohnani <[email protected]>
@bea231
Copy link

bea231 commented Oct 5, 2022

Hi, any updates here?)

Copy link
Collaborator

@mtrmac mtrmac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good, there is now a NewReaderForReference — but it would be useful to have a at least a proof of concept in containers/common#921 before definitely committing to the API.

Let me just try to sketch what that would look like…

if err := tempDirRef.deleteTempDir(); err != nil {
return nil, perrors.Wrapf(err, "deleting temp directory %q", tempDirRef.tempDirectory)
}
archive.Close()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should only close individualWriterOrNil if it is set, not the longer-term ref.archiveWriter.

(in both cases.)

}

// newImageDestination returns an ImageDestination for writing to an existing directory.
func newImageDestination(ctx context.Context, sys *types.SystemContext, ref ociArchiveReference) (private.ImageDestination, error) {
tempDirRef, err := createOCIRef(sys, ref.image)
func newImageDestination(ctx context.Context, sys *types.SystemContext, ref ociArchiveReference) (types.ImageDestination, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should continue to return private.ID, not types.ID. (We do, ultimately, have a test for the return value implementing private.ID, but having it explicit here would be a bit more convenient.)

} else {
layoutRef, err = layout.NewReference(archive.tempDirectory, ref.image)
if err != nil {
archive.Close()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

archive should only be closed if it is individualReaderOrNil. (in all cases.)

func createOCIRef(sys *types.SystemContext, image string) (tempDirOCIRef, error) {
dir, err := os.MkdirTemp(tmpdir.TemporaryDirectoryForBigFiles(sys), "oci")
func createOCIRef(sys *types.SystemContext, image string, sourceIndex int) (tempDirOCIRef, error) {
dir, err := ioutil.TempDir(tmpdir.TemporaryDirectoryForBigFiles(sys), "oci")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Merge problem) This should use the newer os.MkdirTemp name.

Comment on lines +175 to +179
// The caller should
// defer os.RemoveAll(tmpDir)
func refToTempOCI(t *testing.T, sourceIndex bool) (ref types.ImageReference, tmpDir string) {
tmpDir, err := ioutil.TempDir("", "oci-transport-test")
require.NoError(t, err)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Merge problem) The use of t.TempDir makes the caller cleaning up unnecessary.

Comment on lines +90 to +102
if err := archive.NewDefaultArchiver().Untar(arch, dst, &archive.TarOptions{NoLchown: true}); err != nil {
return nil, perrors.Wrapf(err, "error untarring file %q", dst)
}

indexJSON, err := os.Open(filepath.Join(dst, "index.json"))
if err != nil {
return nil, err
}
defer indexJSON.Close()
reader.manifest = &imgspecv1.Index{}
if err := json.NewDecoder(indexJSON).Decode(reader.manifest); err != nil {
return nil, err
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On these failures, the temporary directory should be removed.

Comment on lines +178 to +181
if refName != "" {
index = 1
}
ref, err := newReference(r.path, refName, index, r, nil)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks rather incorrect:

  • It is setting index exactly in the case where it is not needed (and newReference would reject such an input)
  • The index needs to actually point at the right manifest, not a hard-coded 1.

Comment on lines +193 to +197
// The caller should
// defer os.RemoveAll(tmpDir)
func refToTempOCI(t *testing.T, sourceIndex bool) (ref types.ImageReference, tmpDir string) {
tmpDir, err := ioutil.TempDir("", "oci-transport-test")
require.NoError(t, err)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Merge problem) The use of t.TempDir() means callers don’t need to worry about cleanup.

@@ -160,32 +178,5 @@ func (d *ociArchiveImageDestination) Commit(ctx context.Context, unparsedTopleve
if err := d.unpackedDest.Commit(ctx, unparsedToplevel); err != nil {
return perrors.Wrapf(err, "storing image %q", d.ref.image)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pkg/errors are no longer used (throughout).

archive = ref.archiveReader
individualReaderOrNil = nil
} else {
archive, _, err = NewReaderForReference(ctx, sys, ref)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t see any benefit to calling NewReaderForReference if we are going to throw away that reference.

@mtrmac
Copy link
Collaborator

mtrmac commented Oct 6, 2022

Prototyping this results in #1677 + containers/common#1178 . Note that the “pull” case still needs either LoadManifestDescriptor being able to benefit from a Reader-based reference (the way #1677 now does), or some other Reader API, to be able to correctly name the pulled image.

@TomSweeneyRedHat
Copy link
Member

Needs a rebase at this point.
@umohnani8 are you going to push this forward or should we close?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/feature A request for, or a PR adding, new functionality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support saving multiple images to oci-archive:
7 participants