From 837aad724ff1f7f4cc3b125b8b3419af29cd4982 Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Mon, 30 Mar 2020 12:59:05 -0500 Subject: podmanv2 save image add ability to save an image for podman v2 Signed-off-by: Brent Baude --- pkg/api/handlers/libpod/images.go | 53 ++++++++++++++++++++++++++++--------- pkg/api/handlers/utils/handler.go | 7 +++++ pkg/api/server/register_images.go | 2 +- pkg/bindings/images/images.go | 7 ++--- pkg/domain/entities/engine_image.go | 1 + pkg/domain/entities/images.go | 7 +++++ pkg/domain/infra/abi/images.go | 8 ++++++ pkg/domain/infra/tunnel/images.go | 53 +++++++++++++++++++++++++++++++++++++ 8 files changed, 122 insertions(+), 16 deletions(-) (limited to 'pkg') diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index e7f20854c..850de4598 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -16,12 +16,14 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/image" image2 "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/util" + utils2 "github.com/containers/libpod/utils" "github.com/gorilla/schema" "github.com/pkg/errors" ) @@ -161,13 +163,16 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { } func ExportImage(w http.ResponseWriter, r *http.Request) { + var ( + output string + ) runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { Compress bool `schema:"compress"` Format string `schema:"format"` }{ - Format: "docker-archive", + Format: define.OCIArchive, } if err := decoder.Decode(&query, r.URL.Query()); err != nil { @@ -175,14 +180,27 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) return } - - tmpfile, err := ioutil.TempFile("", "api.tar") - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) - return - } - if err := tmpfile.Close(); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) + switch query.Format { + case define.OCIArchive, define.V2s2Archive: + tmpfile, err := ioutil.TempFile("", "api.tar") + if err != nil { + utils.Error(w, "unable to create tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) + return + } + output = tmpfile.Name() + if err := tmpfile.Close(); err != nil { + utils.Error(w, "unable to close tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) + return + } + case define.OCIManifestDir, define.V2s2ManifestDir: + tmpdir, err := ioutil.TempDir("", "save") + if err != nil { + utils.Error(w, "unable to create tmpdir", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempdir")) + return + } + output = tmpdir + default: + utils.Error(w, "unknown format", http.StatusInternalServerError, errors.Errorf("unknown format %q", query.Format)) return } name := utils.GetName(r) @@ -192,17 +210,28 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { return } - if err := newImage.Save(r.Context(), name, query.Format, tmpfile.Name(), []string{}, false, query.Compress); err != nil { + if err := newImage.Save(r.Context(), name, query.Format, output, []string{}, false, query.Compress); err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) return } - rdr, err := os.Open(tmpfile.Name()) + defer os.RemoveAll(output) + // if dir format, we need to tar it + if query.Format == "oci-dir" || query.Format == "docker-dir" { + rdr, err := utils2.Tar(output) + if err != nil { + utils.InternalServerError(w, err) + return + } + defer rdr.Close() + utils.WriteResponse(w, http.StatusOK, rdr) + return + } + rdr, err := os.Open(output) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) return } defer rdr.Close() - defer os.Remove(tmpfile.Name()) utils.WriteResponse(w, http.StatusOK, rdr) } diff --git a/pkg/api/handlers/utils/handler.go b/pkg/api/handlers/utils/handler.go index 32b8c5b0a..b5bd488fb 100644 --- a/pkg/api/handlers/utils/handler.go +++ b/pkg/api/handlers/utils/handler.go @@ -43,6 +43,13 @@ func WriteResponse(w http.ResponseWriter, code int, value interface{}) { w.Header().Set("Content-Type", "application/octet; charset=us-ascii") w.WriteHeader(code) + if _, err := io.Copy(w, v); err != nil { + logrus.Errorf("unable to copy to response: %q", err) + } + case io.Reader: + w.Header().Set("Content-Type", "application/x-tar") + w.WriteHeader(code) + if _, err := io.Copy(w, v); err != nil { logrus.Errorf("unable to copy to response: %q", err) } diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index e4e46025b..d45423096 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -955,7 +955,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // tags: // - images // summary: Export an image - // description: Export an image as a tarball + // description: Export an image // parameters: // - in: path // name: name:.* diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index dcb568d6b..1b3df609b 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -146,11 +146,12 @@ func Export(ctx context.Context, nameOrID string, w io.Writer, format *string, c if err != nil { return err } - if err := response.Process(nil); err != nil { + + if response.StatusCode/100 == 2 || response.StatusCode/100 == 3 { + _, err = io.Copy(w, response.Body) return err } - _, err = io.Copy(w, response.Body) - return err + return nil } // Prune removes unused images from local storage. The optional filters can be used to further diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go index 04b9d34e6..a28bfc548 100644 --- a/pkg/domain/entities/engine_image.go +++ b/pkg/domain/entities/engine_image.go @@ -17,4 +17,5 @@ type ImageEngine interface { Load(ctx context.Context, opts ImageLoadOptions) (*ImageLoadReport, error) Import(ctx context.Context, opts ImageImportOptions) (*ImageImportReport, error) Push(ctx context.Context, source string, destination string, opts ImagePushOptions) error + Save(ctx context.Context, nameOrId string, tags []string, options ImageSaveOptions) error } diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index d66de3c5e..bc8a34c13 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -234,3 +234,10 @@ type ImageImportOptions struct { type ImageImportReport struct { Id string } + +type ImageSaveOptions struct { + Compress bool + Format string + Output string + Quiet bool +} diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 94008f287..9d706a112 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -405,3 +405,11 @@ func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOpti } return &entities.ImageImportReport{Id: id}, nil } + +func (ir *ImageEngine) Save(ctx context.Context, nameOrId string, tags []string, options entities.ImageSaveOptions) error { + newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId) + if err != nil { + return err + } + return newImage.Save(ctx, nameOrId, options.Format, options.Output, tags, options.Quiet, options.Compress) +} diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 028603d98..516914a68 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -2,12 +2,14 @@ package tunnel import ( "context" + "io/ioutil" "os" "github.com/containers/image/v5/docker/reference" images "github.com/containers/libpod/pkg/bindings/images" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/domain/utils" + utils2 "github.com/containers/libpod/utils" "github.com/pkg/errors" ) @@ -188,3 +190,54 @@ func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOpti func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, options entities.ImagePushOptions) error { return images.Push(ir.ClientCxt, source, destination, options) } + +func (ir *ImageEngine) Save(ctx context.Context, nameOrId string, tags []string, options entities.ImageSaveOptions) error { + var ( + f *os.File + err error + ) + + switch options.Format { + case "oci-dir", "docker-dir": + f, err = ioutil.TempFile("", "podman_save") + if err == nil { + defer func() { _ = os.Remove(f.Name()) }() + } + default: + f, err = os.Create(options.Output) + } + if err != nil { + return err + } + + exErr := images.Export(ir.ClientCxt, nameOrId, f, &options.Format, &options.Compress) + if err := f.Close(); err != nil { + return err + } + if exErr != nil { + return exErr + } + + if options.Format != "oci-dir" && options.Format != "docker-dir" { + return nil + } + + f, err = os.Open(f.Name()) + if err != nil { + return err + } + info, err := os.Stat(options.Output) + switch { + case err == nil: + if info.Mode().IsRegular() { + return errors.Errorf("%q already exists as a regular file", options.Output) + } + case os.IsNotExist(err): + if err := os.Mkdir(options.Output, 0755); err != nil { + return err + } + default: + return err + } + return utils2.UntarToFileSystem(options.Output, f, nil) +} -- cgit v1.2.3-54-g00ecf