diff options
author | baude <bbaude@redhat.com> | 2019-01-27 09:57:44 -0600 |
---|---|---|
committer | baude <bbaude@redhat.com> | 2019-02-05 10:05:41 -0600 |
commit | 64c8fb7c2460eb561c8496f781f26d65443eea59 (patch) | |
tree | 19a7bae6cd3bc117fa7b2e9dfe44d3534b4f75ed | |
parent | 3554bfce98bc643bd4724340bf2abbaa6373e70c (diff) | |
download | podman-64c8fb7c2460eb561c8496f781f26d65443eea59.tar.gz podman-64c8fb7c2460eb561c8496f781f26d65443eea59.tar.bz2 podman-64c8fb7c2460eb561c8496f781f26d65443eea59.zip |
podman-remote import|export
addition of import and export for the podman-remote client. This includes
the ability to send and receive files between the remote-client and the
"podman" host using an upgraded varlink connection.
Signed-off-by: baude <bbaude@redhat.com>
-rw-r--r-- | cmd/podman/commands.go | 2 | ||||
-rw-r--r-- | cmd/podman/container.go | 1 | ||||
-rw-r--r-- | cmd/podman/export.go | 51 | ||||
-rw-r--r-- | cmd/podman/image.go | 1 | ||||
-rw-r--r-- | cmd/podman/import.go | 77 | ||||
-rw-r--r-- | cmd/podman/main.go | 2 | ||||
-rw-r--r-- | cmd/podman/varlink/io.podman.varlink | 5 | ||||
-rw-r--r-- | libpod/adapter/client.go | 11 | ||||
-rw-r--r-- | libpod/adapter/runtime.go | 51 | ||||
-rw-r--r-- | libpod/adapter/runtime_remote.go | 80 | ||||
-rw-r--r-- | libpod/runtime_ctr.go | 43 | ||||
-rw-r--r-- | libpod/runtime_img.go | 79 | ||||
-rw-r--r-- | pkg/varlinkapi/containers.go | 17 | ||||
-rw-r--r-- | pkg/varlinkapi/images.go | 8 | ||||
-rw-r--r-- | pkg/varlinkapi/transfers.go | 75 | ||||
-rw-r--r-- | test/e2e/export_test.go | 3 | ||||
-rw-r--r-- | vendor/github.com/varlink/go/varlink/bridge.go | 21 | ||||
-rw-r--r-- | vendor/github.com/varlink/go/varlink/bridge_windows.go | 17 | ||||
-rw-r--r-- | vendor/github.com/varlink/go/varlink/call.go | 21 | ||||
-rw-r--r-- | vendor/github.com/varlink/go/varlink/connection.go | 84 | ||||
-rw-r--r-- | vendor/github.com/varlink/go/varlink/orgvarlinkservice.go | 53 | ||||
-rw-r--r-- | vendor/github.com/varlink/go/varlink/service.go | 14 |
22 files changed, 542 insertions, 174 deletions
diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go index 718717009..d8fdd556f 100644 --- a/cmd/podman/commands.go +++ b/cmd/podman/commands.go @@ -12,8 +12,6 @@ func getAppCommands() []cli.Command { createCommand, diffCommand, execCommand, - exportCommand, - importCommand, killCommand, kubeCommand, loadCommand, diff --git a/cmd/podman/container.go b/cmd/podman/container.go index acbcbb644..29300a6a4 100644 --- a/cmd/podman/container.go +++ b/cmd/podman/container.go @@ -8,6 +8,7 @@ import ( var ( containerSubCommands = []cli.Command{ + exportCommand, inspectCommand, } containerDescription = "Manage containers" diff --git a/cmd/podman/export.go b/cmd/podman/export.go index c0e63bd2a..eaa4e38a2 100644 --- a/cmd/podman/export.go +++ b/cmd/podman/export.go @@ -1,12 +1,9 @@ package main import ( - "io/ioutil" "os" - "strconv" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/adapter" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -43,7 +40,7 @@ func exportCmd(c *cli.Context) error { rootless.SetSkipStorageSetup(true) } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -58,52 +55,18 @@ func exportCmd(c *cli.Context) error { } output := c.String("output") + if runtime.Remote && (output == "/dev/stdout" || len(output) == 0) { + return errors.New("remote client usage must specify an output file (-o)") + } if output == "/dev/stdout" { file := os.Stdout if logrus.IsTerminal(file) { return errors.Errorf("refusing to export to terminal. Use -o flag or redirect") } } + if err := validateFileName(output); err != nil { return err } - - ctr, err := runtime.LookupContainer(args[0]) - if err != nil { - return errors.Wrapf(err, "error looking up container %q", args[0]) - } - - if os.Geteuid() != 0 { - state, err := ctr.State() - if err != nil { - return errors.Wrapf(err, "cannot read container state %q", ctr.ID()) - } - if state == libpod.ContainerStateRunning || state == libpod.ContainerStatePaused { - data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) - if err != nil { - return errors.Wrapf(err, "cannot read conmon PID file %q", ctr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return errors.Wrapf(err, "cannot parse PID %q", data) - } - became, ret, err := rootless.JoinDirectUserAndMountNS(uint(conmonPid)) - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } else { - became, ret, err := rootless.BecomeRootInUserNS() - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } - } - - return ctr.Export(output) + return runtime.Export(args[0], c.String("output")) } diff --git a/cmd/podman/image.go b/cmd/podman/image.go index a51a90b0e..6b451a1ca 100644 --- a/cmd/podman/image.go +++ b/cmd/podman/image.go @@ -8,6 +8,7 @@ import ( var ( imageSubCommands = []cli.Command{ + importCommand, historyCommand, imageExistsCommand, inspectCommand, diff --git a/cmd/podman/import.go b/cmd/podman/import.go index 144354fa6..661bd5a65 100644 --- a/cmd/podman/import.go +++ b/cmd/podman/import.go @@ -2,16 +2,8 @@ package main import ( "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "os" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/util" - "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/containers/libpod/libpod/adapter" "github.com/pkg/errors" "github.com/urfave/cli" ) @@ -51,7 +43,7 @@ func importCmd(c *cli.Context) error { return err } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -60,7 +52,6 @@ func importCmd(c *cli.Context) error { var ( source string reference string - writer io.Writer ) args := c.Args() @@ -80,67 +71,13 @@ func importCmd(c *cli.Context) error { return err } - changes := v1.ImageConfig{} - if c.IsSet("change") || c.IsSet("c") { - changes, err = util.GetImageConfig(c.StringSlice("change")) - if err != nil { - return errors.Wrapf(err, "error adding config changes to image %q", source) - } + quiet := c.Bool("quiet") + if runtime.Remote { + quiet = false } - - history := []v1.History{ - {Comment: c.String("message")}, - } - - config := v1.Image{ - Config: changes, - History: history, - } - - writer = nil - if !c.Bool("quiet") { - writer = os.Stderr - } - - // if source is a url, download it and save to a temp file - u, err := url.ParseRequestURI(source) - if err == nil && u.Scheme != "" { - file, err := downloadFromURL(source) - if err != nil { - return err - } - defer os.Remove(file) - source = file - } - - newImage, err := runtime.ImageRuntime().Import(getContext(), source, reference, writer, image.SigningOptions{}, config) + iid, err := runtime.Import(getContext(), source, reference, c.StringSlice("change"), c.String("message"), quiet) if err == nil { - fmt.Println(newImage.ID()) + fmt.Println(iid) } return err } - -// donwloadFromURL downloads an image in the format "https:/example.com/myimage.tar" -// and temporarily saves in it /var/tmp/importxyz, which is deleted after the image is imported -func downloadFromURL(source string) (string, error) { - fmt.Printf("Downloading from %q\n", source) - - outFile, err := ioutil.TempFile("/var/tmp", "import") - if err != nil { - return "", errors.Wrap(err, "error creating file") - } - defer outFile.Close() - - response, err := http.Get(source) - if err != nil { - return "", errors.Wrapf(err, "error downloading %q", source) - } - defer response.Body.Close() - - _, err = io.Copy(outFile, response.Body) - if err != nil { - return "", errors.Wrapf(err, "error saving %s to %s", source, outFile.Name()) - } - - return outFile.Name(), nil -} diff --git a/cmd/podman/main.go b/cmd/podman/main.go index c10590006..1ca8882eb 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -88,9 +88,11 @@ func main() { app.Commands = []cli.Command{ containerCommand, + exportCommand, historyCommand, imageCommand, imagesCommand, + importCommand, infoCommand, inspectCommand, pullCommand, diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 8b02057a1..101232b0c 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -688,7 +688,7 @@ method Commit(name: string, image_name: string, changes: []string, author: strin # ImportImage imports an image from a source (like tarball) into local storage. The image can have additional # descriptions added to it using the message and changes options. See also [ExportImage](ExportImage). -method ImportImage(source: string, reference: string, message: string, changes: []string) -> (image: string) +method ImportImage(source: string, reference: string, message: string, changes: []string, delete: bool) -> (image: string) # ExportImage takes the name or ID of an image and exports it to a destination like a tarball. There is also # a booleon option to force compression. It also takes in a string array of tags to be able to save multiple @@ -1050,6 +1050,9 @@ method ContainerInspectData(name: string) -> (config: string) # development of Podman only and generally should not be used. method ContainerStateData(name: string) -> (config: string) +method SendFile(type: string, length: int) -> (file_handle: string) +method ReceiveFile(path: string, delete: bool) -> (len: int) + # ImageNotFound means the image could not be found by the provided name or ID in local storage. error ImageNotFound (name: string) diff --git a/libpod/adapter/client.go b/libpod/adapter/client.go index b3bb9acae..6512a5952 100644 --- a/libpod/adapter/client.go +++ b/libpod/adapter/client.go @@ -34,3 +34,14 @@ func (r RemoteRuntime) Connect() (*varlink.Connection, error) { } return connection, nil } + +// RefreshConnection is used to replace the current r.Conn after things like +// using an upgraded varlink connection +func (r RemoteRuntime) RefreshConnection() error { + newConn, err := r.Connect() + if err != nil { + return err + } + r.Conn = newConn + return nil +} diff --git a/libpod/adapter/runtime.go b/libpod/adapter/runtime.go index 007257714..2c408dd2f 100644 --- a/libpod/adapter/runtime.go +++ b/libpod/adapter/runtime.go @@ -4,12 +4,17 @@ package adapter import ( "context" + "github.com/pkg/errors" "io" + "io/ioutil" + "os" + "strconv" "github.com/containers/image/types" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/rootless" "github.com/urfave/cli" ) @@ -104,3 +109,49 @@ func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) { func (r *LocalRuntime) PruneImages(all bool) ([]string, error) { return r.ImageRuntime().PruneImages(all) } + +// Export is a wrapper to container export to a tarfile +func (r *LocalRuntime) Export(name string, path string) error { + ctr, err := r.Runtime.LookupContainer(name) + if err != nil { + return errors.Wrapf(err, "error looking up container %q", name) + } + if os.Geteuid() != 0 { + state, err := ctr.State() + if err != nil { + return errors.Wrapf(err, "cannot read container state %q", ctr.ID()) + } + if state == libpod.ContainerStateRunning || state == libpod.ContainerStatePaused { + data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) + if err != nil { + return errors.Wrapf(err, "cannot read conmon PID file %q", ctr.Config().ConmonPidFile) + } + conmonPid, err := strconv.Atoi(string(data)) + if err != nil { + return errors.Wrapf(err, "cannot parse PID %q", data) + } + became, ret, err := rootless.JoinDirectUserAndMountNS(uint(conmonPid)) + if err != nil { + return err + } + if became { + os.Exit(ret) + } + } else { + became, ret, err := rootless.BecomeRootInUserNS() + if err != nil { + return err + } + if became { + os.Exit(ret) + } + } + } + + return ctr.Export(path) +} + +// Import is a wrapper to import a container image +func (r *LocalRuntime) Import(ctx context.Context, source, reference string, changes []string, history string, quiet bool) (string, error) { + return r.Runtime.Import(ctx, source, reference, changes, history, quiet) +} diff --git a/libpod/adapter/runtime_remote.go b/libpod/adapter/runtime_remote.go index 0633c036d..07c786184 100644 --- a/libpod/adapter/runtime_remote.go +++ b/libpod/adapter/runtime_remote.go @@ -3,11 +3,13 @@ package adapter import ( + "bufio" "context" "encoding/json" "fmt" "github.com/pkg/errors" "io" + "os" "strings" "time" @@ -324,6 +326,84 @@ func (r *LocalRuntime) Config(name string) *libpod.ContainerConfig { return &data } + +// PruneImages is the wrapper call for a remote-client to prune images func (r *LocalRuntime) PruneImages(all bool) ([]string, error) { return iopodman.ImagesPrune().Call(r.Conn, all) } + +// Export is a wrapper to container export to a tarfile +func (r *LocalRuntime) Export(name string, path string) error { + tempPath, err := iopodman.ExportContainer().Call(r.Conn, name, "") + if err != nil { + return err + } + + outputFile, err := os.Create(path) + if err != nil { + return err + } + defer outputFile.Close() + + writer := bufio.NewWriter(outputFile) + defer writer.Flush() + + reply, err := iopodman.ReceiveFile().Send(r.Conn, varlink.Upgrade, tempPath, true) + if err != nil { + return err + } + + length, _, err := reply() + if err != nil { + return errors.Wrap(err, "unable to get file length for transfer") + } + + reader := r.Conn.Reader + if _, err := io.CopyN(writer, reader, length); err != nil { + return errors.Wrap(err, "file transer failed") + } + + return nil +} + +// Import implements the remote calls required to import a container image to the store +func (r *LocalRuntime) Import(ctx context.Context, source, reference string, changes []string, history string, quiet bool) (string, error) { + // First we send the file to the host + fs, err := os.Open(source) + if err != nil { + return "", err + } + + fileInfo, err := fs.Stat() + if err != nil { + return "", err + } + reply, err := iopodman.SendFile().Send(r.Conn, varlink.Upgrade, "", int64(fileInfo.Size())) + if err != nil { + return "", err + } + _, _, err = reply() + if err != nil { + return "", err + } + + reader := bufio.NewReader(fs) + _, err = reader.WriteTo(r.Conn.Writer) + if err != nil { + return "", err + } + r.Conn.Writer.Flush() + + // All was sent, wait for the ACK from the server + tempFile, err := r.Conn.Reader.ReadString(':') + if err != nil { + return "", err + } + + // r.Conn is kaput at this point due to the upgrade + if err := r.RemoteRuntime.RefreshConnection(); err != nil { + return "", err + + } + return iopodman.ImportImage().Call(r.Conn, strings.TrimRight(tempFile, ":"), reference, history, changes, true) +} diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 6d5ce5a7e..9afdef7b6 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -2,9 +2,11 @@ package libpod import ( "context" + "io/ioutil" "os" "path" "path/filepath" + "strconv" "strings" "time" @@ -521,3 +523,44 @@ func isNamedVolume(volName string) bool { } return false } + +// Export is the libpod portion of exporting a container to a tar file +func (r *Runtime) Export(name string, path string) error { + ctr, err := r.LookupContainer(name) + if err != nil { + return err + } + if os.Geteuid() != 0 { + state, err := ctr.State() + if err != nil { + return errors.Wrapf(err, "cannot read container state %q", ctr.ID()) + } + if state == ContainerStateRunning || state == ContainerStatePaused { + data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) + if err != nil { + return errors.Wrapf(err, "cannot read conmon PID file %q", ctr.Config().ConmonPidFile) + } + conmonPid, err := strconv.Atoi(string(data)) + if err != nil { + return errors.Wrapf(err, "cannot parse PID %q", data) + } + became, ret, err := rootless.JoinDirectUserAndMountNS(uint(conmonPid)) + if err != nil { + return err + } + if became { + os.Exit(ret) + } + } else { + became, ret, err := rootless.BecomeRootInUserNS() + if err != nil { + return err + } + if became { + os.Exit(ret) + } + } + } + return ctr.Export(path) + +} diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index 66844bb31..c20aa77a3 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -3,9 +3,16 @@ package libpod import ( "context" "fmt" + "github.com/opencontainers/image-spec/specs-go/v1" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" "github.com/containers/buildah/imagebuildah" "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/util" "github.com/containers/storage" "github.com/pkg/errors" ) @@ -132,3 +139,75 @@ func (r *Runtime) Build(ctx context.Context, options imagebuildah.BuildOptions, _, _, err := imagebuildah.BuildDockerfiles(ctx, r.store, options, dockerfiles...) return err } + +// Import is called as an intermediary to the image library Import +func (r *Runtime) Import(ctx context.Context, source string, reference string, changes []string, history string, quiet bool) (string, error) { + var ( + writer io.Writer + err error + ) + + ic := v1.ImageConfig{} + if len(changes) > 0 { + ic, err = util.GetImageConfig(changes) + if err != nil { + return "", errors.Wrapf(err, "error adding config changes to image %q", source) + } + } + + hist := []v1.History{ + {Comment: history}, + } + + config := v1.Image{ + Config: ic, + History: hist, + } + + writer = nil + if !quiet { + writer = os.Stderr + } + + // if source is a url, download it and save to a temp file + u, err := url.ParseRequestURI(source) + if err == nil && u.Scheme != "" { + file, err := downloadFromURL(source) + if err != nil { + return "", err + } + defer os.Remove(file) + source = file + } + + newImage, err := r.imageRuntime.Import(ctx, source, reference, writer, image.SigningOptions{}, config) + if err != nil { + return "", err + } + return newImage.ID(), nil +} + +// donwloadFromURL downloads an image in the format "https:/example.com/myimage.tar" +// and temporarily saves in it /var/tmp/importxyz, which is deleted after the image is imported +func downloadFromURL(source string) (string, error) { + fmt.Printf("Downloading from %q\n", source) + + outFile, err := ioutil.TempFile("/var/tmp", "import") + if err != nil { + return "", errors.Wrap(err, "error creating file") + } + defer outFile.Close() + + response, err := http.Get(source) + if err != nil { + return "", errors.Wrapf(err, "error downloading %q", source) + } + defer response.Body.Close() + + _, err = io.Copy(outFile, response.Body) + if err != nil { + return "", errors.Wrapf(err, "error saving %s to %s", source, outFile.Name()) + } + + return outFile.Name(), nil +} diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index a01e3cc2b..737e2dd96 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" "os" "syscall" "time" @@ -194,15 +195,25 @@ func (i *LibpodAPI) ListContainerChanges(call iopodman.VarlinkCall, name string) } // ExportContainer ... -func (i *LibpodAPI) ExportContainer(call iopodman.VarlinkCall, name, path string) error { +func (i *LibpodAPI) ExportContainer(call iopodman.VarlinkCall, name, outPath string) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name) } - if err := ctr.Export(path); err != nil { + outputFile, err := ioutil.TempFile("", "varlink_recv") + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + + defer outputFile.Close() + if outPath == "" { + outPath = outputFile.Name() + } + if err := ctr.Export(outPath); err != nil { return call.ReplyErrorOccurred(err.Error()) } - return call.ReplyExportContainer(path) + return call.ReplyExportContainer(outPath) + } // GetContainerStats ... diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index 0ab645802..5e0889645 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -500,7 +500,7 @@ func (i *LibpodAPI) Commit(call iopodman.VarlinkCall, name, imageName string, ch } // ImportImage imports an image from a tarball to the image store -func (i *LibpodAPI) ImportImage(call iopodman.VarlinkCall, source, reference, message string, changes []string) error { +func (i *LibpodAPI) ImportImage(call iopodman.VarlinkCall, source, reference, message string, changes []string, delete bool) error { configChanges, err := util.GetImageConfig(changes) if err != nil { return call.ReplyErrorOccurred(err.Error()) @@ -516,6 +516,12 @@ func (i *LibpodAPI) ImportImage(call iopodman.VarlinkCall, source, reference, me if err != nil { return call.ReplyErrorOccurred(err.Error()) } + if delete { + if err := os.Remove(source); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + } + return call.ReplyImportImage(newImage.ID()) } diff --git a/pkg/varlinkapi/transfers.go b/pkg/varlinkapi/transfers.go new file mode 100644 index 000000000..0cb7e5e2e --- /dev/null +++ b/pkg/varlinkapi/transfers.go @@ -0,0 +1,75 @@ +package varlinkapi + +import ( + "bufio" + "fmt" + "io" + "io/ioutil" + "os" + + "github.com/containers/libpod/cmd/podman/varlink" +) + +// SendFile allows a client to send a file to the varlink server +func (i *LibpodAPI) SendFile(call iopodman.VarlinkCall, ftype string, length int64) error { + if !call.WantsUpgrade() { + return call.ReplyErrorOccurred("client must use upgraded connection to send files") + } + + outputFile, err := ioutil.TempFile("", "varlink_send") + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + defer outputFile.Close() + + if err = call.ReplySendFile(outputFile.Name()); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + + writer := bufio.NewWriter(outputFile) + defer writer.Flush() + + reader := call.Call.Reader + if _, err := io.CopyN(writer, reader, length); err != nil { + return err + } + + // Send an ACK to the client + call.Call.Writer.WriteString(fmt.Sprintf("%s:", outputFile.Name())) + call.Call.Writer.Flush() + return nil + +} + +// ReceiveFile allows the varlink server to send a file to a client +func (i *LibpodAPI) ReceiveFile(call iopodman.VarlinkCall, filepath string, delete bool) error { + if !call.WantsUpgrade() { + return call.ReplyErrorOccurred("client must use upgraded connection to send files") + } + fs, err := os.Open(filepath) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + fileInfo, err := fs.Stat() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + + // Send the file length down to client + // Varlink connection upraded + if err = call.ReplyReceiveFile(fileInfo.Size()); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + + reader := bufio.NewReader(fs) + _, err = reader.WriteTo(call.Writer) + if err != nil { + return err + } + if delete { + if err := os.Remove(filepath); err != nil { + return err + } + } + return call.Writer.Flush() +} diff --git a/test/e2e/export_test.go b/test/e2e/export_test.go index de3f23667..dba0a2255 100644 --- a/test/e2e/export_test.go +++ b/test/e2e/export_test.go @@ -1,5 +1,3 @@ -// +build !remoteclient - package integration import ( @@ -37,6 +35,7 @@ var _ = Describe("Podman export", func() { }) It("podman export output flag", func() { + SkipIfRemote() _, ec, cid := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) diff --git a/vendor/github.com/varlink/go/varlink/bridge.go b/vendor/github.com/varlink/go/varlink/bridge.go index b20c0925a..0ea5de682 100644 --- a/vendor/github.com/varlink/go/varlink/bridge.go +++ b/vendor/github.com/varlink/go/varlink/bridge.go @@ -5,13 +5,13 @@ package varlink import ( "bufio" "io" - "log" "net" "os/exec" ) type PipeCon struct { net.Conn + cmd *exec.Cmd reader *io.ReadCloser writer *io.WriteCloser } @@ -25,6 +25,8 @@ func (p PipeCon) Close() error { if err2 != nil { return err2 } + p.cmd.Wait() + return nil } @@ -42,18 +44,15 @@ func NewBridge(bridge string) (*Connection, error) { if err != nil { return nil, err } - c.conn = PipeCon{nil, &r, &w} + c.conn = PipeCon{nil, cmd, &r, &w} c.address = "" - c.reader = bufio.NewReader(r) - c.writer = bufio.NewWriter(w) + c.Reader = bufio.NewReader(r) + c.Writer = bufio.NewWriter(w) - go func() { - err := cmd.Run() - if err != nil { - log.Fatal(err) - } - }() + err = cmd.Start() + if err != nil { + return nil, err + } return &c, nil } - diff --git a/vendor/github.com/varlink/go/varlink/bridge_windows.go b/vendor/github.com/varlink/go/varlink/bridge_windows.go index 692367a1a..220ae3156 100644 --- a/vendor/github.com/varlink/go/varlink/bridge_windows.go +++ b/vendor/github.com/varlink/go/varlink/bridge_windows.go @@ -3,13 +3,13 @@ package varlink import ( "bufio" "io" - "log" "net" "os/exec" ) type PipeCon struct { net.Conn + cmd *exec.Cmd reader *io.ReadCloser writer *io.WriteCloser } @@ -23,6 +23,8 @@ func (p PipeCon) Close() error { if err2 != nil { return err2 } + p.cmd.Wait() + return nil } @@ -40,18 +42,15 @@ func NewBridge(bridge string) (*Connection, error) { if err != nil { return nil, err } - c.conn = PipeCon{nil, &r, &w} + c.conn = PipeCon{nil, cmd, &r, &w} c.address = "" c.reader = bufio.NewReader(r) c.writer = bufio.NewWriter(w) - go func() { - err := cmd.Run() - if err != nil { - log.Fatal(err) - } - }() + err = cmd.Start() + if err != nil { + return nil, err + } return &c, nil } - diff --git a/vendor/github.com/varlink/go/varlink/call.go b/vendor/github.com/varlink/go/varlink/call.go index 5e9249c0e..d6e046f1d 100644 --- a/vendor/github.com/varlink/go/varlink/call.go +++ b/vendor/github.com/varlink/go/varlink/call.go @@ -4,6 +4,7 @@ import ( "bufio" "encoding/json" "fmt" + "io" "strings" ) @@ -11,9 +12,11 @@ import ( // client can be terminated by returning an error from the call instead // of sending a reply or error reply. type Call struct { - writer *bufio.Writer + *bufio.Reader + *bufio.Writer in *serviceCall Continues bool + Upgrade bool } // WantsMore indicates if the calling client accepts more than one reply to this method call. @@ -21,6 +24,11 @@ func (c *Call) WantsMore() bool { return c.in.More } +// WantsUpgrade indicates that the calling client wants the connection to be upgraded. +func (c *Call) WantsUpgrade() bool { + return c.in.Upgrade +} + // IsOneway indicate that the calling client does not expect a reply. func (c *Call) IsOneway() bool { return c.in.Oneway @@ -45,11 +53,18 @@ func (c *Call) sendMessage(r *serviceReply) error { } b = append(b, 0) - _, e = c.writer.Write(b) + _, e = c.Writer.Write(b) if e != nil { + if e == io.EOF { + return io.ErrUnexpectedEOF + } return e } - return c.writer.Flush() + e = c.Writer.Flush() + if e == io.EOF { + return io.ErrUnexpectedEOF + } + return e } // Reply sends a reply to this method call. diff --git a/vendor/github.com/varlink/go/varlink/connection.go b/vendor/github.com/varlink/go/varlink/connection.go index ac9542408..596caa825 100644 --- a/vendor/github.com/varlink/go/varlink/connection.go +++ b/vendor/github.com/varlink/go/varlink/connection.go @@ -4,6 +4,7 @@ import ( "bufio" "encoding/json" "fmt" + "io" "net" "strings" ) @@ -15,15 +16,59 @@ const ( More = 1 << iota Oneway = 1 << iota Continues = 1 << iota + Upgrade = 1 << iota ) // Error is a varlink error returned from a method call. type Error struct { - error Name string Parameters interface{} } +func (e *Error) DispatchError() error { + errorRawParameters := e.Parameters.(*json.RawMessage) + + switch e.Name { + case "org.varlink.service.InterfaceNotFound": + var param InterfaceNotFound + if errorRawParameters != nil { + err := json.Unmarshal(*errorRawParameters, ¶m) + if err != nil { + return e + } + } + return ¶m + case "org.varlink.service.MethodNotFound": + var param MethodNotFound + if errorRawParameters != nil { + err := json.Unmarshal(*errorRawParameters, ¶m) + if err != nil { + return e + } + } + return ¶m + case "org.varlink.service.MethodNotImplemented": + var param MethodNotImplemented + if errorRawParameters != nil { + err := json.Unmarshal(*errorRawParameters, ¶m) + if err != nil { + return e + } + } + return ¶m + case "org.varlink.service.InvalidParameter": + var param InvalidParameter + if errorRawParameters != nil { + err := json.Unmarshal(*errorRawParameters, ¶m) + if err != nil { + return e + } + } + return ¶m + } + return e +} + // Error returns the fully-qualified varlink error name. func (e *Error) Error() string { return e.Name @@ -31,10 +76,11 @@ func (e *Error) Error() string { // Connection is a connection from a client to a service. type Connection struct { + io.Closer address string conn net.Conn - reader *bufio.Reader - writer *bufio.Writer + Reader *bufio.Reader + Writer *bufio.Writer } // Send sends a method call. It returns a receive() function which is called to retrieve the method reply. @@ -46,6 +92,7 @@ func (c *Connection) Send(method string, parameters interface{}, flags uint64) ( Parameters interface{} `json:"parameters,omitempty"` More bool `json:"more,omitempty"` Oneway bool `json:"oneway,omitempty"` + Upgrade bool `json:"upgrade,omitempty"` } if (flags&More != 0) && (flags&Oneway != 0) { @@ -55,11 +102,19 @@ func (c *Connection) Send(method string, parameters interface{}, flags uint64) ( } } + if (flags&More != 0) && (flags&Upgrade != 0) { + return nil, &Error{ + Name: "org.varlink.InvalidParameter", + Parameters: "more", + } + } + m := call{ Method: method, Parameters: parameters, More: flags&More != 0, Oneway: flags&Oneway != 0, + Upgrade: flags&Upgrade != 0, } b, err := json.Marshal(m) if err != nil { @@ -67,13 +122,19 @@ func (c *Connection) Send(method string, parameters interface{}, flags uint64) ( } b = append(b, 0) - _, err = c.writer.Write(b) + _, err = c.Writer.Write(b) if err != nil { + if err == io.EOF { + return nil, io.ErrUnexpectedEOF + } return nil, err } - err = c.writer.Flush() + err = c.Writer.Flush() if err != nil { + if err == io.EOF { + return nil, io.ErrUnexpectedEOF + } return nil, err } @@ -84,8 +145,11 @@ func (c *Connection) Send(method string, parameters interface{}, flags uint64) ( Error string `json:"error"` } - out, err := c.reader.ReadBytes('\x00') + out, err := c.Reader.ReadBytes('\x00') if err != nil { + if err == io.EOF { + return 0, io.ErrUnexpectedEOF + } return 0, err } @@ -96,11 +160,11 @@ func (c *Connection) Send(method string, parameters interface{}, flags uint64) ( } if m.Error != "" { - err = &Error{ + e := &Error{ Name: m.Error, Parameters: m.Parameters, } - return 0, err + return 0, e.DispatchError() } if m.Parameters != nil { @@ -220,8 +284,8 @@ func NewConnection(address string) (*Connection, error) { } c.address = address - c.reader = bufio.NewReader(c.conn) - c.writer = bufio.NewWriter(c.conn) + c.Reader = bufio.NewReader(c.conn) + c.Writer = bufio.NewWriter(c.conn) return &c, nil } diff --git a/vendor/github.com/varlink/go/varlink/orgvarlinkservice.go b/vendor/github.com/varlink/go/varlink/orgvarlinkservice.go index 39f843c31..600c99d34 100644 --- a/vendor/github.com/varlink/go/varlink/orgvarlinkservice.go +++ b/vendor/github.com/varlink/go/varlink/orgvarlinkservice.go @@ -1,5 +1,42 @@ package varlink +// The requested interface was not found. +type InterfaceNotFound struct { + Interface string `json:"interface"` +} + +func (e InterfaceNotFound) Error() string { + return "org.varlink.service.InterfaceNotFound" +} + +// The requested method was not found +type MethodNotFound struct { + Method string `json:"method"` +} + +func (e MethodNotFound) Error() string { + return "org.varlink.service.MethodNotFound" +} + +// The interface defines the requested method, but the service does not +// implement it. +type MethodNotImplemented struct { + Method string `json:"method"` +} + +func (e MethodNotImplemented) Error() string { + return "org.varlink.service.MethodNotImplemented" +} + +// One of the passed parameters is invalid. +type InvalidParameter struct { + Parameter string `json:"parameter"` +} + +func (e InvalidParameter) Error() string { + return "org.varlink.service.InvalidParameter" +} + func doReplyError(c *Call, name string, parameters interface{}) error { return c.sendMessage(&serviceReply{ Error: name, @@ -9,36 +46,28 @@ func doReplyError(c *Call, name string, parameters interface{}) error { // ReplyInterfaceNotFound sends a org.varlink.service errror reply to this method call func (c *Call) ReplyInterfaceNotFound(interfaceA string) error { - var out struct { - Interface string `json:"interface,omitempty"` - } + var out InterfaceNotFound out.Interface = interfaceA return doReplyError(c, "org.varlink.service.InterfaceNotFound", &out) } // ReplyMethodNotFound sends a org.varlink.service errror reply to this method call func (c *Call) ReplyMethodNotFound(method string) error { - var out struct { - Method string `json:"method,omitempty"` - } + var out MethodNotFound out.Method = method return doReplyError(c, "org.varlink.service.MethodNotFound", &out) } // ReplyMethodNotImplemented sends a org.varlink.service errror reply to this method call func (c *Call) ReplyMethodNotImplemented(method string) error { - var out struct { - Method string `json:"method,omitempty"` - } + var out MethodNotImplemented out.Method = method return doReplyError(c, "org.varlink.service.MethodNotImplemented", &out) } // ReplyInvalidParameter sends a org.varlink.service errror reply to this method call func (c *Call) ReplyInvalidParameter(parameter string) error { - var out struct { - Parameter string `json:"parameter,omitempty"` - } + var out InvalidParameter out.Parameter = parameter return doReplyError(c, "org.varlink.service.InvalidParameter", &out) } diff --git a/vendor/github.com/varlink/go/varlink/service.go b/vendor/github.com/varlink/go/varlink/service.go index cb461f917..abccffe6a 100644 --- a/vendor/github.com/varlink/go/varlink/service.go +++ b/vendor/github.com/varlink/go/varlink/service.go @@ -22,6 +22,7 @@ type serviceCall struct { Parameters *json.RawMessage `json:"parameters,omitempty"` More bool `json:"more,omitempty"` Oneway bool `json:"oneway,omitempty"` + Upgrade bool `json:"upgrade,omitempty"` } type serviceReply struct { @@ -50,7 +51,7 @@ type Service struct { } // ServiceTimoutError helps API users to special-case timeouts. -type ServiceTimeoutError struct {} +type ServiceTimeoutError struct{} func (ServiceTimeoutError) Error() string { return "service timeout" @@ -73,7 +74,7 @@ func (s *Service) getInterfaceDescription(c Call, name string) error { return c.replyGetInterfaceDescription(description) } -func (s *Service) handleMessage(writer *bufio.Writer, request []byte) error { +func (s *Service) handleMessage(reader *bufio.Reader, writer *bufio.Writer, request []byte) error { var in serviceCall err := json.Unmarshal(request, &in) @@ -83,7 +84,8 @@ func (s *Service) handleMessage(writer *bufio.Writer, request []byte) error { } c := Call{ - writer: writer, + Reader: reader, + Writer: writer, in: &in, } @@ -129,7 +131,7 @@ func (s *Service) handleConnection(conn net.Conn, wg *sync.WaitGroup) { break } - err = s.handleMessage(writer, request[:len(request)-1]) + err = s.handleMessage(reader, writer, request[:len(request)-1]) if err != nil { // FIXME: report error //fmt.Fprintf(os.Stderr, "handleMessage: %v", err) @@ -201,11 +203,11 @@ func getListener(protocol string, address string) (net.Listener, error) { func (s *Service) refreshTimeout(timeout time.Duration) error { switch l := s.listener.(type) { case *net.UnixListener: - if err:= l.SetDeadline(time.Now().Add(timeout)); err != nil { + if err := l.SetDeadline(time.Now().Add(timeout)); err != nil { return err } case *net.TCPListener: - if err:= l.SetDeadline(time.Now().Add(timeout)); err != nil { + if err := l.SetDeadline(time.Now().Add(timeout)); err != nil { return err } |