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

podman save use named pipe #7073

Merged
merged 1 commit into from
Aug 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions cmd/podman/images/save.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ import (
"os"
"strings"

"github.com/containers/podman/v2/libpod/define"

"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/util"
"github.com/pkg/errors"
Expand Down Expand Up @@ -83,9 +82,10 @@ func saveFlags(flags *pflag.FlagSet) {

}

func save(cmd *cobra.Command, args []string) error {
func save(cmd *cobra.Command, args []string) (finalErr error) {
var (
tags []string
tags []string
succeeded = false
)
if cmd.Flag("compress").Changed && (saveOpts.Format != define.OCIManifestDir && saveOpts.Format != define.V2s2ManifestDir && saveOpts.Format == "") {
return errors.Errorf("--compress can only be set when --format is either 'oci-dir' or 'docker-dir'")
Expand All @@ -95,13 +95,32 @@ func save(cmd *cobra.Command, args []string) error {
if terminal.IsTerminal(int(fi.Fd())) {
mtrmac marked this conversation as resolved.
Show resolved Hide resolved
return errors.Errorf("refusing to save to terminal. Use -o flag or redirect")
}
saveOpts.Output = "/dev/stdout"
pipePath, cleanup, err := setupPipe()
if err != nil {
return err
}
if cleanup != nil {
defer func() {
errc := cleanup()
if succeeded {
writeErr := <-errc
if writeErr != nil && finalErr == nil {
finalErr = writeErr
}
}
}()
}
saveOpts.Output = pipePath
}
if err := parse.ValidateFileName(saveOpts.Output); err != nil {
return err
}
if len(args) > 1 {
tags = args[1:]
}
return registry.ImageEngine().Save(context.Background(), args[0], tags, saveOpts)
err := registry.ImageEngine().Save(context.Background(), args[0], tags, saveOpts)
if err == nil {
succeeded = true
}
return err
}
47 changes: 47 additions & 0 deletions cmd/podman/images/utils_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package images

import (
"io"
"io/ioutil"
"os"
"path/filepath"

"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)

// setupPipe for fixing https://github.com/containers/podman/issues/7017
// uses named pipe since containers/image EvalSymlinks fails with /dev/stdout
// the caller should use the returned function to clean up the pipeDir
func setupPipe() (string, func() <-chan error, error) {
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: The semantics of the cleanup callback is now complex enough that it might be worth documenting. (OTOH it’s not that much code and it has a single user.)

errc := make(chan error)
pipeDir, err := ioutil.TempDir(os.TempDir(), "pipeDir")
if err != nil {
return "", nil, err
}
pipePath := filepath.Join(pipeDir, "saveio")
err = unix.Mkfifo(pipePath, 0600)
if err != nil {
if e := os.RemoveAll(pipeDir); e != nil {
logrus.Errorf("error removing named pipe: %q", e)
}
return "", nil, errors.Wrapf(err, "error creating named pipe")
mtrmac marked this conversation as resolved.
Show resolved Hide resolved
}
go func() {
fpipe, err := os.Open(pipePath)
if err != nil {
errc <- err
return
}
_, err = io.Copy(os.Stdout, fpipe)
fpipe.Close()
errc <- err
}()
return pipePath, func() <-chan error {
if e := os.RemoveAll(pipeDir); e != nil {
logrus.Errorf("error removing named pipe: %q", e)
}
return errc
mtrmac marked this conversation as resolved.
Show resolved Hide resolved
}, nil
}
7 changes: 7 additions & 0 deletions cmd/podman/images/utils_unsupported.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// +build !linux

package images

func setupPipe() (string, func() <-chan error, error) {
return "/dev/stdout", nil, nil
}
10 changes: 10 additions & 0 deletions test/system/120-load.bats
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ verify_iid_and_name() {
is "$new_img_name" "$1" "Name & tag of restored image"
}

@test "podman save to pipe and load" {
# We can't use run_podman because that uses the BATS 'run' function
# which redirects stdout and stderr. Here we need to guarantee
# that podman's stdout is a pipe, not any other form of redirection
$PODMAN save --format oci-archive $IMAGE | cat >$PODMAN_TMPDIR/test.tar
[ $status -eq 0 ]

run_podman load -i $PODMAN_TMPDIR/test.tar
}


@test "podman load - by image ID" {
# FIXME: how to build a simple archive instead?
Expand Down