summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
authorBrent Baude <bbaude@redhat.com>2020-03-30 12:59:05 -0500
committerBrent Baude <bbaude@redhat.com>2020-04-03 13:07:35 -0500
commit837aad724ff1f7f4cc3b125b8b3419af29cd4982 (patch)
tree25ad8d10028a7d742e9fd61d1c96d87fbc342090 /pkg
parenta89d62ac3a445a5d76bd72d4b8e1bb9262ba74c6 (diff)
downloadpodman-837aad724ff1f7f4cc3b125b8b3419af29cd4982.tar.gz
podman-837aad724ff1f7f4cc3b125b8b3419af29cd4982.tar.bz2
podman-837aad724ff1f7f4cc3b125b8b3419af29cd4982.zip
podmanv2 save image
add ability to save an image for podman v2 Signed-off-by: Brent Baude <bbaude@redhat.com>
Diffstat (limited to 'pkg')
-rw-r--r--pkg/api/handlers/libpod/images.go53
-rw-r--r--pkg/api/handlers/utils/handler.go7
-rw-r--r--pkg/api/server/register_images.go2
-rw-r--r--pkg/bindings/images/images.go7
-rw-r--r--pkg/domain/entities/engine_image.go1
-rw-r--r--pkg/domain/entities/images.go7
-rw-r--r--pkg/domain/infra/abi/images.go8
-rw-r--r--pkg/domain/infra/tunnel/images.go53
8 files changed, 122 insertions, 16 deletions
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
@@ -46,6 +46,13 @@ func WriteResponse(w http.ResponseWriter, code int, value interface{}) {
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)
+ }
default:
WriteJSON(w, code, value)
}
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)
+}