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 /libpod | |
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>
Diffstat (limited to 'libpod')
-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 |
5 files changed, 264 insertions, 0 deletions
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 +} |