podman-remote load image

enable the ability to load an image into remote storage
using the remote client.

Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
baude
2019-02-19 13:36:42 -06:00
parent af922fb2c6
commit 71db80ddb1
11 changed files with 175 additions and 58 deletions

View File

@ -17,7 +17,6 @@ func getMainCommands() []*cobra.Command {
generateCommand.Command,
_containerKubeCommand,
_psCommand,
_loadCommand,
_loginCommand,
_logoutCommand,
_logsCommand,

View File

@ -24,6 +24,7 @@ var imageSubCommands = []*cobra.Command{
_imagesCommand,
_importCommand,
_inspectCommand,
_loadCommand,
_pruneImagesCommand,
_pullCommand,
_pushCommand,

View File

@ -6,12 +6,8 @@ import (
"io/ioutil"
"os"
"github.com/containers/image/directory"
dockerarchive "github.com/containers/image/docker/archive"
ociarchive "github.com/containers/image/oci/archive"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/libpod/adapter"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -56,14 +52,16 @@ func loadCmd(c *cliconfig.LoadValues) error {
return errors.New("too many arguments. Requires exactly 1")
}
runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand)
runtime, err := adapter.GetRuntime(&c.PodmanCommand)
if err != nil {
return errors.Wrapf(err, "could not get runtime")
}
defer runtime.Shutdown(false)
input := c.Input
if runtime.Remote && len(input) == 0 {
return errors.New("the remote client requires you to load via -i and a tarball")
}
if input == "/dev/stdin" {
fi, err := os.Stdin.Stat()
if err != nil {
@ -96,46 +94,10 @@ func loadCmd(c *cliconfig.LoadValues) error {
return err
}
var writer io.Writer
if !c.Quiet {
writer = os.Stderr
}
ctx := getContext()
var newImages []*image.Image
src, err := dockerarchive.ParseReference(input) // FIXME? We should add dockerarchive.NewReference()
if err == nil {
newImages, err = runtime.ImageRuntime().LoadFromArchiveReference(ctx, src, c.SignaturePolicy, writer)
}
names, err := runtime.LoadImage(getContext(), imageName, c)
if err != nil {
// generate full src name with specified image:tag
src, err := ociarchive.NewReference(input, imageName) // imageName may be ""
if err == nil {
newImages, err = runtime.ImageRuntime().LoadFromArchiveReference(ctx, src, c.SignaturePolicy, writer)
}
if err != nil {
src, err := directory.NewReference(input)
if err == nil {
newImages, err = runtime.ImageRuntime().LoadFromArchiveReference(ctx, src, c.SignaturePolicy, writer)
}
if err != nil {
return errors.Wrapf(err, "error pulling %q", input)
}
}
return err
}
fmt.Println("Loaded image(s): " + getImageNames(newImages))
fmt.Println("Loaded image(s): " + names)
return nil
}
func getImageNames(images []*image.Image) string {
var names string
for i := range images {
if i == 0 {
names = images[i].InputName
} else {
names += ", " + images[i].InputName
}
}
return names
}

View File

@ -45,6 +45,7 @@ var mainCommands = []*cobra.Command{
_infoCommand,
_inspectCommand,
_killCommand,
_loadCommand,
podCommand.Command,
_pullCommand,
_pushCommand,

View File

@ -1102,8 +1102,11 @@ method VolumesPrune() -> (prunedNames: []string, prunedErrors: []string)
method ImageSave(options: ImageSaveOptions) -> (reply: MoreResponse)
method GetPodsByContext(all: bool, latest: bool, args: []string) -> (pods: []string)
method LoadImage(name: string, inputFile: string, quiet: bool, deleteFile: bool) -> (reply: MoreResponse)
# ImageNotFound means the image could not be found by the provided name or ID in local storage.
error ImageNotFound (id: string)

View File

@ -322,3 +322,14 @@ func (r *LocalRuntime) SaveImage(ctx context.Context, c *cliconfig.SaveValues) e
}
return newImage.Save(ctx, source, c.Format, c.Output, additionalTags, c.Quiet, c.Compress)
}
// LoadImage is a wrapper function for libpod PruneVolumes
func (r *LocalRuntime) LoadImage(ctx context.Context, name string, cli *cliconfig.LoadValues) (string, error) {
var (
writer io.Writer
)
if !cli.Quiet {
writer = os.Stderr
}
return r.Runtime.LoadImage(ctx, name, cli.Input, writer, cli.SignaturePolicy)
}

View File

@ -486,7 +486,7 @@ func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, opti
if err != nil {
return err
}
buildinfo.ContextDir = strings.Replace(tempFile, ":", "", -1)
buildinfo.ContextDir = tempFile
reply, err := iopodman.BuildImage().Send(r.Conn, varlink.More, buildinfo)
if err != nil {
@ -549,7 +549,7 @@ func (r *LocalRuntime) SendFileOverVarlink(source string) (string, error) {
}
return tempFile, nil
return strings.Replace(tempFile, ":", "", -1), nil
}
// GetAllVolumes retrieves all the volumes
@ -763,3 +763,36 @@ func (r *LocalRuntime) SaveImage(ctx context.Context, c *cliconfig.SaveValues) e
}
return nil
}
// LoadImage loads a container image from a remote client's filesystem
func (r *LocalRuntime) LoadImage(ctx context.Context, name string, cli *cliconfig.LoadValues) (string, error) {
var names string
remoteTempFile, err := r.SendFileOverVarlink(cli.Input)
if err != nil {
return "", nil
}
more := varlink.More
if cli.Quiet {
more = 0
}
reply, err := iopodman.LoadImage().Send(r.Conn, uint64(more), name, remoteTempFile, cli.Quiet, true)
if err != nil {
return "", err
}
for {
responses, flags, err := reply()
if err != nil {
logrus.Error(err)
return "", err
}
for _, line := range responses.Logs {
fmt.Print(line)
}
names = responses.Id
if flags&varlink.Continues == 0 {
break
}
}
return names, nil
}

View File

@ -32,7 +32,7 @@ import (
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/opentracing/opentracing-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

View File

@ -3,7 +3,6 @@ package libpod
import (
"context"
"fmt"
"github.com/opencontainers/image-spec/specs-go/v1"
"io"
"io/ioutil"
"net/http"
@ -15,6 +14,11 @@ import (
"github.com/containers/libpod/pkg/util"
"github.com/containers/storage"
"github.com/pkg/errors"
"github.com/containers/image/directory"
dockerarchive "github.com/containers/image/docker/archive"
ociarchive "github.com/containers/image/oci/archive"
"github.com/opencontainers/image-spec/specs-go/v1"
)
// Runtime API
@ -211,3 +215,41 @@ func downloadFromURL(source string) (string, error) {
return outFile.Name(), nil
}
// LoadImage loads a container image into local storage
func (r *Runtime) LoadImage(ctx context.Context, name, inputFile string, writer io.Writer, signaturePolicy string) (string, error) {
var newImages []*image.Image
src, err := dockerarchive.ParseReference(inputFile) // FIXME? We should add dockerarchive.NewReference()
if err == nil {
newImages, err = r.ImageRuntime().LoadFromArchiveReference(ctx, src, signaturePolicy, writer)
}
if err != nil {
// generate full src name with specified image:tag
src, err := ociarchive.NewReference(inputFile, name) // imageName may be ""
if err == nil {
newImages, err = r.ImageRuntime().LoadFromArchiveReference(ctx, src, signaturePolicy, writer)
}
if err != nil {
src, err := directory.NewReference(inputFile)
if err == nil {
newImages, err = r.ImageRuntime().LoadFromArchiveReference(ctx, src, signaturePolicy, writer)
}
if err != nil {
return "", errors.Wrapf(err, "error pulling %q", name)
}
}
}
return getImageNames(newImages), nil
}
func getImageNames(images []*image.Image) string {
var names string
for i := range images {
if i == 0 {
names = images[i].InputName
} else {
names += ", " + images[i].InputName
}
}
return names
}

View File

@ -776,9 +776,6 @@ func (i *LibpodAPI) ImageSave(call iopodman.VarlinkCall, options iopodman.ImageS
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 {
@ -835,3 +832,73 @@ func (i *LibpodAPI) ImageSave(call iopodman.VarlinkCall, options iopodman.ImageS
}
return call.ReplyPushImage(br)
}
// LoadImage ...
func (i *LibpodAPI) LoadImage(call iopodman.VarlinkCall, name, inputFile string, deleteInputFile, quiet bool) error {
var (
names string
writer io.Writer
err error
)
if !quiet {
writer = os.Stderr
}
if call.WantsMore() {
call.Continues = true
}
output := bytes.NewBuffer([]byte{})
c := make(chan error)
go func() {
names, err = i.Runtime.LoadImage(getContext(), name, inputFile, writer, "")
c <- err
close(c)
}()
var log []string
done := false
for {
line, err := output.ReadString('\n')
if err == nil {
log = append(log, line)
continue
} else if err == io.EOF {
select {
case err := <-c:
if err != nil {
logrus.Error(err)
return call.ReplyErrorOccurred(err.Error())
}
done = true
default:
if !call.WantsMore() {
time.Sleep(1 * time.Second)
break
}
br := iopodman.MoreResponse{
Logs: log,
}
call.ReplyLoadImage(br)
log = []string{}
}
} else {
return call.ReplyErrorOccurred(err.Error())
}
if done {
break
}
}
call.Continues = false
br := iopodman.MoreResponse{
Logs: log,
Id: names,
}
if deleteInputFile {
if err := os.Remove(inputFile); err != nil {
logrus.Errorf("unable to delete input file %s", inputFile)
}
}
return call.ReplyLoadImage(br)
}

View File

@ -199,11 +199,9 @@ var _ = Describe("Podman load", func() {
It("podman load localhost registry from scratch and :latest", func() {
outfile := filepath.Join(podmanTest.TempDir, "load_test.tar.gz")
setup := podmanTest.Podman([]string{"pull", fedoraMinimal})
setup.WaitWithDefaultTimeout()
Expect(setup.ExitCode()).To(Equal(0))
podmanTest.RestoreArtifact("fedora-minimal:latest")
setup = podmanTest.Podman([]string{"tag", "fedora-minimal", "hello"})
setup := podmanTest.Podman([]string{"tag", "fedora-minimal", "hello"})
setup.WaitWithDefaultTimeout()
Expect(setup.ExitCode()).To(Equal(0))