From bb39051616fbce12b4cb3135a62c7747273ab0aa Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Tue, 31 Mar 2020 19:13:19 -0500 Subject: podmanv2 export add ability to export a container to a tarball Signed-off-by: Brent Baude --- pkg/api/handlers/compat/containers_export.go | 42 ++++++++++++++++++++++++++ pkg/api/server/register_containers.go | 45 ++++++++++++++++++++++++++++ pkg/bindings/containers/containers.go | 20 +++++++++++++ pkg/domain/entities/containers.go | 4 +++ pkg/domain/entities/engine_container.go | 1 + pkg/domain/infra/abi/containers.go | 8 +++++ pkg/domain/infra/tunnel/containers.go | 16 ++++++++++ 7 files changed, 136 insertions(+) create mode 100644 pkg/api/handlers/compat/containers_export.go (limited to 'pkg') diff --git a/pkg/api/handlers/compat/containers_export.go b/pkg/api/handlers/compat/containers_export.go new file mode 100644 index 000000000..37b9fbf2b --- /dev/null +++ b/pkg/api/handlers/compat/containers_export.go @@ -0,0 +1,42 @@ +package compat + +import ( + "io/ioutil" + "net/http" + "os" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/pkg/errors" +) + +func ExportContainer(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + name := utils.GetName(r) + con, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + tmpfile, err := ioutil.TempFile("", "api.tar") + if err != nil { + utils.Error(w, "unable to create tarball tempfile", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) + return + } + defer os.Remove(tmpfile.Name()) + if err := tmpfile.Close(); err != nil { + utils.Error(w, "unable to close tempfile", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) + return + } + if err := con.Export(tmpfile.Name()); err != nil { + utils.Error(w, "failed to save the image", http.StatusInternalServerError, errors.Wrap(err, "failed to save image")) + return + } + rdr, err := os.Open(tmpfile.Name()) + if err != nil { + utils.Error(w, "failed to read temp tarball", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) + return + } + defer rdr.Close() + utils.WriteResponse(w, http.StatusOK, rdr) +} diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index 08834ff01..145c054c0 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -587,6 +587,29 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { r.HandleFunc(VersionedPath("/containers/{name}/resize"), s.APIHandler(compat.ResizeContainer)).Methods(http.MethodPost) // Added non version path to URI to support docker non versioned paths r.HandleFunc("/containers/{name}/resize", s.APIHandler(compat.ResizeContainer)).Methods(http.MethodPost) + // swagger:operation GET /containers/{name}/export compat exportContainer + // --- + // tags: + // - containers (compat) + // summary: Export a container + // description: Export the contents of a container as a tarball. + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // 200: + // description: tarball is returned in body + // 404: + // $ref: "#/responses/NoSuchContainer" + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/containers/{name}/export"), s.APIHandler(compat.ExportContainer)).Methods(http.MethodGet) + r.HandleFunc("/containers/{name}/export", s.APIHandler(compat.ExportContainer)).Methods(http.MethodGet) /* libpod endpoints @@ -1237,5 +1260,27 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/InternalError" r.HandleFunc(VersionedPath("/libpod/containers/{name}/resize"), s.APIHandler(compat.ResizeContainer)).Methods(http.MethodPost) + // swagger:operation GET /libpod/containers/{name}/export libpod libpodExportContainer + // --- + // tags: + // - containers + // summary: Export a container + // description: Export the contents of a container as a tarball. + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // 200: + // description: tarball is returned in body + // 404: + // $ref: "#/responses/NoSuchContainer" + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/{name}/export"), s.APIHandler(compat.ExportContainer)).Methods(http.MethodGet) return nil } diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index bad1294f4..49a2dfd58 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -2,6 +2,7 @@ package containers import ( "context" + "io" "net/http" "net/url" "strconv" @@ -296,3 +297,22 @@ func Stop(ctx context.Context, nameOrID string, timeout *uint) error { } return response.Process(nil) } + +// Export creates a tarball of the given name or ID of a container. It +// requires an io.Writer be provided to write the tarball. +func Export(ctx context.Context, nameOrID string, w io.Writer) error { + params := url.Values{} + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/export", params, nameOrID) + if err != nil { + return err + } + if response.StatusCode/100 == 2 { + _, err = io.Copy(w, response.Body) + return err + } + return response.Process(nil) +} diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index b7f1cd812..d51124f55 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -117,3 +117,7 @@ type CommitOptions struct { type CommitReport struct { Id string } + +type ContainerExportOptions struct { + Output string +} diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index c5f46ccfc..a122857cd 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -10,6 +10,7 @@ type ContainerEngine interface { ContainerCommit(ctx context.Context, nameOrId string, options CommitOptions) (*CommitReport, error) ContainerExists(ctx context.Context, nameOrId string) (*BoolReport, error) ContainerInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]*ContainerInspectReport, error) + ContainerExport(ctx context.Context, nameOrId string, options ContainerExportOptions) error ContainerKill(ctx context.Context, namesOrIds []string, options KillOptions) ([]*KillReport, error) ContainerPause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error) ContainerRestart(ctx context.Context, namesOrIds []string, options RestartOptions) ([]*RestartReport, error) diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 172c7d1a3..d4c5ac311 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -325,3 +325,11 @@ func (ic *ContainerEngine) ContainerCommit(ctx context.Context, nameOrId string, } return &entities.CommitReport{Id: newImage.ID()}, nil } + +func (ic *ContainerEngine) ContainerExport(ctx context.Context, nameOrId string, options entities.ContainerExportOptions) error { + ctr, err := ic.Libpod.LookupContainer(nameOrId) + if err != nil { + return err + } + return ctr.Export(options.Output) +} diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index c1bade4ba..8885ae7c7 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -2,6 +2,8 @@ package tunnel import ( "context" + "io" + "os" "github.com/containers/image/v5/docker/reference" @@ -210,3 +212,17 @@ func (ic *ContainerEngine) ContainerCommit(ctx context.Context, nameOrId string, } return &entities.CommitReport{Id: response.ID}, nil } + +func (ic *ContainerEngine) ContainerExport(ctx context.Context, nameOrId string, options entities.ContainerExportOptions) error { + var ( + err error + w io.Writer + ) + if len(options.Output) > 0 { + w, err = os.Create(options.Output) + if err != nil { + return err + } + } + return containers.Export(ic.ClientCxt, nameOrId, w) +} -- cgit v1.2.3-54-g00ecf