support multi-image (docker) archives

Support loading and saving tarballs with more than one image.
Add a new `/libpod/images/export` endpoint to the rest API to
allow for exporting/saving multiple images into an archive.

Note that a non-release version of containers/image is vendored.
A release version must be vendored before cutting a new Podman
release.  We force the containers/image version via a replace in
the go.mod file; this way go won't try to match the versions.

Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
Valentin Rothberg
2020-07-31 09:27:21 +02:00
parent be7778df6c
commit 7fea46752c
115 changed files with 3790 additions and 1680 deletions

View File

@@ -3,9 +3,8 @@ package archive
import (
"context"
"io"
"os"
"github.com/containers/image/v5/docker/tarfile"
"github.com/containers/image/v5/docker/internal/tarfile"
"github.com/containers/image/v5/types"
"github.com/pkg/errors"
)
@@ -13,37 +12,38 @@ import (
type archiveImageDestination struct {
*tarfile.Destination // Implements most of types.ImageDestination
ref archiveReference
writer io.Closer
archive *tarfile.Writer // Should only be closed if writer != nil
writer io.Closer // May be nil if the archive is shared
}
func newImageDestination(sys *types.SystemContext, ref archiveReference) (types.ImageDestination, error) {
// ref.path can be either a pipe or a regular file
// in the case of a pipe, we require that we can open it for write
// in the case of a regular file, we don't want to overwrite any pre-existing file
// so we check for Size() == 0 below (This is racy, but using O_EXCL would also be racy,
// only in a different way. Either way, its up to the user to not have two writers to the same path.)
fh, err := os.OpenFile(ref.path, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return nil, errors.Wrapf(err, "error opening file %q", ref.path)
if ref.sourceIndex != -1 {
return nil, errors.Errorf("Destination reference must not contain a manifest index @%d", ref.sourceIndex)
}
fhStat, err := fh.Stat()
if err != nil {
return nil, errors.Wrapf(err, "error statting file %q", ref.path)
}
var archive *tarfile.Writer
var writer io.Closer
if ref.archiveWriter != nil {
archive = ref.archiveWriter
writer = nil
} else {
fh, err := openArchiveForWriting(ref.path)
if err != nil {
return nil, err
}
if fhStat.Mode().IsRegular() && fhStat.Size() != 0 {
return nil, errors.New("docker-archive doesn't support modifying existing images")
archive = tarfile.NewWriter(fh)
writer = fh
}
tarDest := tarfile.NewDestinationWithContext(sys, fh, ref.destinationRef)
tarDest := tarfile.NewDestination(sys, archive, ref.ref)
if sys != nil && sys.DockerArchiveAdditionalTags != nil {
tarDest.AddRepoTags(sys.DockerArchiveAdditionalTags)
}
return &archiveImageDestination{
Destination: tarDest,
ref: ref,
writer: fh,
archive: archive,
writer: writer,
}, nil
}
@@ -60,7 +60,10 @@ func (d *archiveImageDestination) Reference() types.ImageReference {
// Close removes resources associated with an initialized ImageDestination, if any.
func (d *archiveImageDestination) Close() error {
return d.writer.Close()
if d.writer != nil {
return d.writer.Close()
}
return nil
}
// Commit marks the process of storing the image as successful and asks for the image to be persisted.
@@ -68,5 +71,8 @@ func (d *archiveImageDestination) Close() error {
// - Uploaded data MAY be visible to others before Commit() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
func (d *archiveImageDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error {
return d.Destination.Commit(ctx)
if d.writer != nil {
return d.archive.Close()
}
return nil
}