diff options
author | baude <bbaude@redhat.com> | 2019-02-19 10:08:43 -0600 |
---|---|---|
committer | baude <bbaude@redhat.com> | 2019-02-20 12:58:05 -0600 |
commit | 711ac9305185e645f2970d09ff76c2761132202a (patch) | |
tree | e8c7c83be19cc075446b20ea49a88c529dda69f1 | |
parent | 4de0bf9c74624de8a2cab1e5cbebc0beaa67339a (diff) | |
download | podman-711ac9305185e645f2970d09ff76c2761132202a.tar.gz podman-711ac9305185e645f2970d09ff76c2761132202a.tar.bz2 podman-711ac9305185e645f2970d09ff76c2761132202a.zip |
podman-remote save [image]
Add the ability to save an image from the remote-host to the
remote-client.
Signed-off-by: baude <bbaude@redhat.com>
-rwxr-xr-x | API.md | 36 | ||||
-rw-r--r-- | cmd/podman/commands.go | 2 | ||||
-rw-r--r-- | cmd/podman/image.go | 1 | ||||
-rw-r--r-- | cmd/podman/main.go | 1 | ||||
-rw-r--r-- | cmd/podman/save.go | 106 | ||||
-rw-r--r-- | cmd/podman/varlink/io.podman.varlink | 12 | ||||
-rw-r--r-- | libpod/adapter/runtime.go | 12 | ||||
-rw-r--r-- | libpod/adapter/runtime_remote.go | 95 | ||||
-rw-r--r-- | libpod/image/image.go | 67 | ||||
-rw-r--r-- | libpod/image/utils.go | 26 | ||||
-rw-r--r-- | pkg/varlinkapi/images.go | 99 | ||||
-rw-r--r-- | test/e2e/save_test.go | 2 | ||||
-rw-r--r-- | utils/utils.go | 30 |
13 files changed, 361 insertions, 128 deletions
@@ -59,6 +59,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func ImageExists(name: string) int](#ImageExists) +[func ImageSave(options: ImageSaveOptions) MoreResponse](#ImageSave) + [func ImagesPrune(all: bool) []string](#ImagesPrune) [func ImportImage(source: string, reference: string, message: string, changes: []string, delete: bool) string](#ImportImage) @@ -107,7 +109,7 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func RestartPod(name: string) string](#RestartPod) -[func SearchImages(query: string, limit: int, tlsVerify: ?bool, filter: ImageSearchFilter) ImageSearchResult](#SearchImages) +[func SearchImages(query: string, limit: , tlsVerify: , filter: ImageSearchFilter) ImageSearchResult](#SearchImages) [func SendFile(type: string, length: int) string](#SendFile) @@ -163,6 +165,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [type ImageHistory](#ImageHistory) +[type ImageSaveOptions](#ImageSaveOptions) + [type ImageSearchFilter](#ImageSearchFilter) [type ImageSearchResult](#ImageSearchResult) @@ -556,6 +560,11 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.ImageExists '{"name": "im "exists": 1 } ~~~ +### <a name="ImageSave"></a>func ImageSave +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method ImageSave(options: [ImageSaveOptions](#ImageSaveOptions)) [MoreResponse](#MoreResponse)</div> + ### <a name="ImagesPrune"></a>func ImagesPrune <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -847,7 +856,7 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.RestartPod '{"name": "135 ### <a name="SearchImages"></a>func SearchImages <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> -method SearchImages(query: [string](https://godoc.org/builtin#string), limit: [](#), tlsVerify: [](#)) [ImageSearchResult](#ImageSearchResult)</div> +method SearchImages(query: [string](https://godoc.org/builtin#string), limit: [](#), tlsVerify: [](#), filter: [ImageSearchFilter](#ImageSearchFilter)) [ImageSearchResult](#ImageSearchResult)</div> SearchImages searches available registries for images that contain the contents of "query" in their name. If "limit" is given, limits the amount of search results per registry. @@ -1410,13 +1419,30 @@ tags [[]string](#[]string) size [int](https://godoc.org/builtin#int) comment [string](https://godoc.org/builtin#string) +### <a name="ImageSaveOptions"></a>type ImageSaveOptions + + + +name [string](https://godoc.org/builtin#string) + +format [string](https://godoc.org/builtin#string) + +output [string](https://godoc.org/builtin#string) + +outputType [string](https://godoc.org/builtin#string) + +moreTags [[]string](#[]string) + +quiet [bool](https://godoc.org/builtin#bool) + +compress [bool](https://godoc.org/builtin#bool) ### <a name="ImageSearchFilter"></a>type ImageSearchFilter -Represents a filter for SearchImages -is_official [bool](https://godoc.org/builtin#bool) -is_automated [bool](https://godoc.org/builtin#bool) +is_official [](#) + +is_automated [](#) star_count [int](https://godoc.org/builtin#int) ### <a name="ImageSearchResult"></a>type ImageSearchResult diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go index 27ac342ba..387e35767 100644 --- a/cmd/podman/commands.go +++ b/cmd/podman/commands.go @@ -30,7 +30,6 @@ func getMainCommands() []*cobra.Command { _restoreCommand, _rmCommand, _runCommand, - _saveCommand, _searchCommand, _signCommand, _startCommand, @@ -53,7 +52,6 @@ func getMainCommands() []*cobra.Command { func getImageSubCommands() []*cobra.Command { return []*cobra.Command{ _loadCommand, - _saveCommand, _signCommand, } } diff --git a/cmd/podman/image.go b/cmd/podman/image.go index 4f9c7cd6a..3c8942ef5 100644 --- a/cmd/podman/image.go +++ b/cmd/podman/image.go @@ -28,6 +28,7 @@ var imageSubCommands = []*cobra.Command{ _pullCommand, _pushCommand, _rmiCommand, + _saveCommand, _tagCommand, } diff --git a/cmd/podman/main.go b/cmd/podman/main.go index ecb72f58b..5fa6cf233 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -48,6 +48,7 @@ var mainCommands = []*cobra.Command{ _pullCommand, _pushCommand, _rmiCommand, + _saveCommand, _tagCommand, _versionCommand, imageCommand.Command, diff --git a/cmd/podman/save.go b/cmd/podman/save.go index ff4a22453..ba5209f34 100644 --- a/cmd/podman/save.go +++ b/cmd/podman/save.go @@ -1,21 +1,10 @@ package main import ( - "fmt" - "io" "os" - "strings" - "github.com/containers/image/directory" - dockerarchive "github.com/containers/image/docker/archive" - "github.com/containers/image/docker/reference" - "github.com/containers/image/manifest" - ociarchive "github.com/containers/image/oci/archive" - "github.com/containers/image/types" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - libpodImage "github.com/containers/libpod/libpod/image" - imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/containers/libpod/libpod/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -52,7 +41,7 @@ func init() { saveCommand.SetUsageTemplate(UsageTemplate()) flags := saveCommand.Flags() flags.BoolVar(&saveCommand.Compress, "compress", false, "Compress tarball image layers when saving to a directory using the 'dir' transport. (default is same compression type as source)") - flags.StringVar(&saveCommand.Format, "format", "", "Save image to oci-archive, oci-dir (directory with oci manifest type), docker-dir (directory with v2s2 manifest type)") + flags.StringVar(&saveCommand.Format, "format", "docker-archive", "Save image to oci-archive, oci-dir (directory with oci manifest type), docker-dir (directory with v2s2 manifest type)") flags.StringVarP(&saveCommand.Output, "output", "o", "/dev/stdout", "Write to a file, default is STDOUT") flags.BoolVarP(&saveCommand.Quiet, "quiet", "q", false, "Suppress the output") } @@ -64,7 +53,7 @@ func saveCmd(c *cliconfig.SaveValues) error { return errors.Errorf("need at least 1 argument") } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not create runtime") } @@ -74,11 +63,6 @@ func saveCmd(c *cliconfig.SaveValues) error { return errors.Errorf("--compress can only be set when --format is either 'oci-dir' or 'docker-dir'") } - var writer io.Writer - if !c.Quiet { - writer = os.Stderr - } - output := c.Output if output == "/dev/stdout" { fi := os.Stdout @@ -89,87 +73,5 @@ func saveCmd(c *cliconfig.SaveValues) error { if err := validateFileName(output); err != nil { return err } - - source := args[0] - newImage, err := runtime.ImageRuntime().NewFromLocal(source) - if err != nil { - return err - } - - var destRef types.ImageReference - var manifestType string - switch c.Format { - case "oci-archive": - destImageName := imageNameForSaveDestination(newImage, source) - destRef, err = ociarchive.NewReference(output, destImageName) // destImageName may be "" - if err != nil { - return errors.Wrapf(err, "error getting OCI archive ImageReference for (%q, %q)", output, destImageName) - } - case "oci-dir": - destRef, err = directory.NewReference(output) - if err != nil { - return errors.Wrapf(err, "error getting directory ImageReference for %q", output) - } - manifestType = imgspecv1.MediaTypeImageManifest - case "docker-dir": - destRef, err = directory.NewReference(output) - if err != nil { - return errors.Wrapf(err, "error getting directory ImageReference for %q", output) - } - manifestType = manifest.DockerV2Schema2MediaType - case "docker-archive", "": - dst := output - destImageName := imageNameForSaveDestination(newImage, source) - if destImageName != "" { - dst = fmt.Sprintf("%s:%s", dst, destImageName) - } - destRef, err = dockerarchive.ParseReference(dst) // FIXME? Add dockerarchive.NewReference - if err != nil { - return errors.Wrapf(err, "error getting Docker archive ImageReference for %q", dst) - } - default: - return errors.Errorf("unknown format option %q", c.String("format")) - } - - // supports saving multiple tags to the same tar archive - var additionaltags []reference.NamedTagged - if len(args) > 1 { - additionaltags, err = libpodImage.GetAdditionalTags(args[1:]) - if err != nil { - return err - } - } - if err := newImage.PushImageToReference(getContext(), destRef, manifestType, "", "", writer, c.Bool("compress"), libpodImage.SigningOptions{}, &libpodImage.DockerRegistryOptions{}, additionaltags); err != nil { - if err2 := os.Remove(output); err2 != nil { - logrus.Errorf("error deleting %q: %v", output, err) - } - return errors.Wrapf(err, "unable to save %q", args) - } - - return nil -} - -// imageNameForSaveDestination returns a Docker-like reference appropriate for saving img, -// which the user referred to as imgUserInput; or an empty string, if there is no appropriate -// reference. -func imageNameForSaveDestination(img *libpodImage.Image, imgUserInput string) string { - if strings.Contains(img.ID(), imgUserInput) { - return "" - } - - prepend := "" - localRegistryPrefix := fmt.Sprintf("%s/", libpodImage.DefaultLocalRegistry) - if !strings.HasPrefix(imgUserInput, localRegistryPrefix) { - // we need to check if localhost was added to the image name in NewFromLocal - for _, name := range img.Names() { - // If the user is saving an image in the localhost registry, getLocalImage need - // a name that matches the format localhost/<tag1>:<tag2> or localhost/<tag>:latest to correctly - // set up the manifest and save. - if strings.HasPrefix(name, localRegistryPrefix) && (strings.HasSuffix(name, imgUserInput) || strings.HasSuffix(name, fmt.Sprintf("%s:latest", imgUserInput))) { - prepend = localRegistryPrefix - break - } - } - } - return fmt.Sprintf("%s%s", prepend, imgUserInput) + return runtime.SaveImage(getContext(), c) } diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index c53a5454a..cae77e5b3 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -26,6 +26,16 @@ type ContainerChanges ( deleted: []string ) +type ImageSaveOptions ( + name: string, + format: string, + output: string, + outputType: string, + moreTags: []string, + quiet: bool, + compress: bool +) + type VolumeCreateOpts ( volumeName: string, driver: string, @@ -1090,6 +1100,8 @@ method GetVolumes(args: []string, all: bool) -> (volumes: []Volume) # VolumesPrune removes unused volumes on the host method VolumesPrune() -> (prunedNames: []string, prunedErrors: []string) +method ImageSave(options: ImageSaveOptions) -> (reply: MoreResponse) + # ImageNotFound means the image could not be found by the provided name or ID in local storage. error ImageNotFound (id: string) diff --git a/libpod/adapter/runtime.go b/libpod/adapter/runtime.go index 02ef9af07..b12f63cdc 100644 --- a/libpod/adapter/runtime.go +++ b/libpod/adapter/runtime.go @@ -310,3 +310,15 @@ func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, opti func (r *LocalRuntime) PruneVolumes(ctx context.Context) ([]string, []error) { return r.Runtime.PruneVolumes(ctx) } + +// SaveImage is a wrapper function for saving an image to the local filesystem +func (r *LocalRuntime) SaveImage(ctx context.Context, c *cliconfig.SaveValues) error { + source := c.InputArgs[0] + additionalTags := c.InputArgs[1:] + + newImage, err := r.Runtime.ImageRuntime().NewFromLocal(source) + if err != nil { + return err + } + return newImage.Save(ctx, source, c.Format, c.Output, additionalTags, c.Quiet, c.Compress) +} diff --git a/libpod/adapter/runtime_remote.go b/libpod/adapter/runtime_remote.go index b1d4d4d25..a79f93079 100644 --- a/libpod/adapter/runtime_remote.go +++ b/libpod/adapter/runtime_remote.go @@ -20,6 +20,7 @@ import ( "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/utils" "github.com/containers/storage/pkg/archive" "github.com/opencontainers/go-digest" "github.com/pkg/errors" @@ -385,8 +386,11 @@ func (r *LocalRuntime) Export(name string, path string) error { if err != nil { return err } + return r.GetFileFromRemoteHost(tempPath, path, true) +} - outputFile, err := os.Create(path) +func (r *LocalRuntime) GetFileFromRemoteHost(remoteFilePath, outputPath string, delete bool) error { + outputFile, err := os.Create(outputPath) if err != nil { return err } @@ -395,7 +399,7 @@ func (r *LocalRuntime) Export(name string, path string) error { writer := bufio.NewWriter(outputFile) defer writer.Flush() - reply, err := iopodman.ReceiveFile().Send(r.Conn, varlink.Upgrade, tempPath, true) + reply, err := iopodman.ReceiveFile().Send(r.Conn, varlink.Upgrade, remoteFilePath, delete) if err != nil { return err } @@ -409,7 +413,6 @@ func (r *LocalRuntime) Export(name string, path string) error { if _, err := io.CopyN(writer, reader, length); err != nil { return errors.Wrap(err, "file transer failed") } - return nil } @@ -467,28 +470,17 @@ func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, opti Squash: options.Squash, } // tar the file - logrus.Debugf("creating tarball of context dir %s", options.ContextDirectory) - input, err := archive.Tar(options.ContextDirectory, archive.Uncompressed) - if err != nil { - return errors.Wrapf(err, "unable to create tarball of context dir %s", options.ContextDirectory) - } - - // Write the tarball to the fs - // TODO we might considering sending this without writing to the fs for the sake of performance - // under given conditions like memory availability. outputFile, err := ioutil.TempFile("", "varlink_tar_send") if err != nil { return err } defer outputFile.Close() - logrus.Debugf("writing context dir tarball to %s", outputFile.Name()) + defer os.Remove(outputFile.Name()) - _, err = io.Copy(outputFile, input) - if err != nil { + // Create the tarball of the context dir to a tempfile + if err := utils.TarToFilesystem(options.ContextDirectory, outputFile); err != nil { return err } - - logrus.Debugf("completed writing context dir tarball %s", outputFile.Name()) // Send the context dir tarball over varlink. tempFile, err := r.SendFileOverVarlink(outputFile.Name()) if err != nil { @@ -702,3 +694,72 @@ func (r *LocalRuntime) PruneVolumes(ctx context.Context) ([]string, []error) { } return prunedNames, errs } + +// SaveImage is a wrapper function for saving an image to the local filesystem +func (r *LocalRuntime) SaveImage(ctx context.Context, c *cliconfig.SaveValues) error { + source := c.InputArgs[0] + additionalTags := c.InputArgs[1:] + + options := iopodman.ImageSaveOptions{ + Name: source, + Format: c.Format, + Output: c.Output, + MoreTags: additionalTags, + Quiet: c.Quiet, + Compress: c.Compress, + } + reply, err := iopodman.ImageSave().Send(r.Conn, varlink.More, options) + if err != nil { + return err + } + + var fetchfile string + for { + responses, flags, err := reply() + if err != nil { + return err + } + if len(responses.Id) > 0 { + fetchfile = responses.Id + } + for _, line := range responses.Logs { + fmt.Print(line) + } + if flags&varlink.Continues == 0 { + break + } + + } + if err != nil { + return err + } + + outputToDir := false + outfile := c.Output + var outputFile *os.File + // If the result is supposed to be a dir, then we need to put the tarfile + // from the host in a temporary file + if options.Format != "oci-archive" && options.Format != "docker-archive" { + outputToDir = true + outputFile, err = ioutil.TempFile("", "saveimage_tempfile") + if err != nil { + return err + } + outfile = outputFile.Name() + defer outputFile.Close() + defer os.Remove(outputFile.Name()) + } + // We now need to fetch the tarball result back to the more system + if err := r.GetFileFromRemoteHost(fetchfile, outfile, true); err != nil { + return err + } + + // If the result is a tarball, we're done + // If it is a dir, we need to untar the temporary file into the dir + if outputToDir { + if err := utils.UntarToFileSystem(c.Output, outputFile, &archive.TarOptions{}); err != nil { + return err + } + } + return nil +} diff --git a/libpod/image/image.go b/libpod/image/image.go index 028a795ea..547fb8994 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -5,14 +5,18 @@ import ( "encoding/json" "fmt" "io" + "os" "strings" "syscall" "time" types2 "github.com/containernetworking/cni/pkg/types" cp "github.com/containers/image/copy" + "github.com/containers/image/directory" + dockerarchive "github.com/containers/image/docker/archive" "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" + ociarchive "github.com/containers/image/oci/archive" is "github.com/containers/image/storage" "github.com/containers/image/tarball" "github.com/containers/image/transports" @@ -26,6 +30,7 @@ import ( "github.com/containers/storage" "github.com/containers/storage/pkg/reexec" digest "github.com/opencontainers/go-digest" + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" ociv1 "github.com/opencontainers/image-spec/specs-go/v1" opentracing "github.com/opentracing/opentracing-go" "github.com/pkg/errors" @@ -1084,3 +1089,65 @@ func (i *Image) Comment(ctx context.Context, manifestType string) (string, error } return ociv1Img.History[0].Comment, nil } + +// Save writes a container image to the filesystem +func (i *Image) Save(ctx context.Context, source, format, output string, moreTags []string, quiet, compress bool) error { + var ( + writer io.Writer + destRef types.ImageReference + manifestType string + err error + ) + + if quiet { + writer = os.Stderr + } + switch format { + case "oci-archive": + destImageName := imageNameForSaveDestination(i, source) + destRef, err = ociarchive.NewReference(output, destImageName) // destImageName may be "" + if err != nil { + return errors.Wrapf(err, "error getting OCI archive ImageReference for (%q, %q)", output, destImageName) + } + case "oci-dir": + destRef, err = directory.NewReference(output) + if err != nil { + return errors.Wrapf(err, "error getting directory ImageReference for %q", output) + } + manifestType = imgspecv1.MediaTypeImageManifest + case "docker-dir": + destRef, err = directory.NewReference(output) + if err != nil { + return errors.Wrapf(err, "error getting directory ImageReference for %q", output) + } + manifestType = manifest.DockerV2Schema2MediaType + case "docker-archive", "": + dst := output + destImageName := imageNameForSaveDestination(i, source) + if destImageName != "" { + dst = fmt.Sprintf("%s:%s", dst, destImageName) + } + destRef, err = dockerarchive.ParseReference(dst) // FIXME? Add dockerarchive.NewReference + if err != nil { + return errors.Wrapf(err, "error getting Docker archive ImageReference for %q", dst) + } + default: + return errors.Errorf("unknown format option %q", format) + } + // supports saving multiple tags to the same tar archive + var additionaltags []reference.NamedTagged + if len(moreTags) > 0 { + additionaltags, err = GetAdditionalTags(moreTags) + if err != nil { + return err + } + } + if err := i.PushImageToReference(ctx, destRef, manifestType, "", "", writer, compress, SigningOptions{}, &DockerRegistryOptions{}, additionaltags); err != nil { + if err2 := os.Remove(output); err2 != nil { + logrus.Errorf("error deleting %q: %v", output, err) + } + return errors.Wrapf(err, "unable to save %q", source) + } + + return nil +} diff --git a/libpod/image/utils.go b/libpod/image/utils.go index 3585428ad..544796a4b 100644 --- a/libpod/image/utils.go +++ b/libpod/image/utils.go @@ -1,6 +1,7 @@ package image import ( + "fmt" "io" "net/url" "regexp" @@ -148,3 +149,28 @@ func IsValidImageURI(imguri string) (bool, error) { } return true, nil } + +// imageNameForSaveDestination returns a Docker-like reference appropriate for saving img, +// which the user referred to as imgUserInput; or an empty string, if there is no appropriate +// reference. +func imageNameForSaveDestination(img *Image, imgUserInput string) string { + if strings.Contains(img.ID(), imgUserInput) { + return "" + } + + prepend := "" + localRegistryPrefix := fmt.Sprintf("%s/", DefaultLocalRegistry) + if !strings.HasPrefix(imgUserInput, localRegistryPrefix) { + // we need to check if localhost was added to the image name in NewFromLocal + for _, name := range img.Names() { + // If the user is saving an image in the localhost registry, getLocalImage need + // a name that matches the format localhost/<tag1>:<tag2> or localhost/<tag>:latest to correctly + // set up the manifest and save. + if strings.HasPrefix(name, localRegistryPrefix) && (strings.HasSuffix(name, imgUserInput) || strings.HasSuffix(name, fmt.Sprintf("%s:latest", imgUserInput))) { + prepend = localRegistryPrefix + break + } + } + } + return fmt.Sprintf("%s%s", prepend, imgUserInput) +} diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index d12ab97ab..77df77a29 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -736,3 +736,102 @@ func (i *LibpodAPI) ImagesPrune(call iopodman.VarlinkCall, all bool) error { } return call.ReplyImagesPrune(prunedImages) } + +// ImageSave .... +func (i *LibpodAPI) ImageSave(call iopodman.VarlinkCall, options iopodman.ImageSaveOptions) error { + newImage, err := i.Runtime.ImageRuntime().NewFromLocal(options.Name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + + // Determine if we are dealing with a tarball or dir + var output string + outputToDir := false + if options.Format == "oci-archive" || options.Format == "docker-archive" { + tempfile, err := ioutil.TempFile("", "varlink_send") + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + output = tempfile.Name() + tempfile.Close() + } else { + var err error + outputToDir = true + output, err = ioutil.TempDir("", "varlink_send") + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + } + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + if call.WantsMore() { + call.Continues = true + } + + saveOutput := bytes.NewBuffer([]byte{}) + c := make(chan error) + go func() { + err := newImage.Save(getContext(), options.Name, options.Format, output, options.MoreTags, options.Quiet, options.Compress) + c <- err + close(c) + }() + + // TODO When pull output gets fixed for the remote client, we need to look into how we can turn below + // into something re-usable. it is in build too + var log []string + done := false + for { + line, err := saveOutput.ReadString('\n') + if err == nil { + log = append(log, line) + continue + } else if err == io.EOF { + select { + case err := <-c: + if err != nil { + logrus.Errorf("reading of output during save failed for %s", newImage.ID()) + return call.ReplyErrorOccurred(err.Error()) + } + done = true + default: + if !call.WantsMore() { + time.Sleep(1 * time.Second) + break + } + br := iopodman.MoreResponse{ + Logs: log, + } + call.ReplyImageSave(br) + log = []string{} + } + } else { + return call.ReplyErrorOccurred(err.Error()) + } + if done { + break + } + } + call.Continues = false + + sendfile := output + // Image has been saved to `output` + if outputToDir { + // If the output is a directory, we need to tar up the directory to send it back + //Create a tempfile for the directory tarball + outputFile, err := ioutil.TempFile("", "varlink_save_dir") + if err != nil { + return err + } + defer outputFile.Close() + if err := utils.TarToFilesystem(output, outputFile); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + sendfile = outputFile.Name() + } + br := iopodman.MoreResponse{ + Logs: log, + Id: sendfile, + } + return call.ReplyPushImage(br) +} diff --git a/test/e2e/save_test.go b/test/e2e/save_test.go index b354492b8..9f64e49a7 100644 --- a/test/e2e/save_test.go +++ b/test/e2e/save_test.go @@ -1,5 +1,3 @@ -// +build !remoteclient - package integration import ( diff --git a/utils/utils.go b/utils/utils.go index 4a91b304f..33b0eb1c5 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -4,12 +4,15 @@ import ( "bytes" "fmt" "io" + "os" "os/exec" "strings" + "github.com/containers/storage/pkg/archive" systemdDbus "github.com/coreos/go-systemd/dbus" "github.com/godbus/dbus" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) // ExecCmd executes a command with args and returns its output as a string along @@ -139,3 +142,30 @@ func CopyDetachable(dst io.Writer, src io.Reader, keys []byte) (written int64, e } return written, err } + +// UntarToFileSystem untars an os.file of a tarball to a destination in the filesystem +func UntarToFileSystem(dest string, tarball *os.File, options *archive.TarOptions) error { + logrus.Debugf("untarring %s", tarball.Name()) + return archive.Untar(tarball, dest, options) +} + +// TarToFilesystem creates a tarball from source and writes to an os.file +// provided +func TarToFilesystem(source string, tarball *os.File) error { + tb, err := Tar(source) + if err != nil { + return err + } + _, err = io.Copy(tarball, tb) + if err != nil { + return err + } + logrus.Debugf("wrote tarball file %s", tarball.Name()) + return nil +} + +// Tar creates a tarball from source and returns a readcloser of it +func Tar(source string) (io.ReadCloser, error) { + logrus.Debugf("creating tarball of %s", source) + return archive.Tar(source, archive.Uncompressed) +} |