mirror of
https://github.com/containers/podman.git
synced 2025-05-20 16:47:39 +08:00
podmanv2 checkpoint and restore
add the ability to checkpoint and restore containers on v2podman Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
79
cmd/podmanV2/containers/checkpoint.go
Normal file
79
cmd/podmanV2/containers/checkpoint.go
Normal file
@ -0,0 +1,79 @@
|
||||
package containers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/libpod/cmd/podmanV2/parse"
|
||||
"github.com/containers/libpod/cmd/podmanV2/registry"
|
||||
"github.com/containers/libpod/cmd/podmanV2/utils"
|
||||
"github.com/containers/libpod/pkg/domain/entities"
|
||||
"github.com/containers/libpod/pkg/rootless"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
checkpointDescription = `
|
||||
podman container checkpoint
|
||||
|
||||
Checkpoints one or more running containers. The container name or ID can be used.
|
||||
`
|
||||
checkpointCommand = &cobra.Command{
|
||||
Use: "checkpoint [flags] CONTAINER [CONTAINER...]",
|
||||
Short: "Checkpoints one or more containers",
|
||||
Long: checkpointDescription,
|
||||
RunE: checkpoint,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
return parse.CheckAllLatestAndCIDFile(cmd, args, false, false)
|
||||
},
|
||||
Example: `podman container checkpoint --keep ctrID
|
||||
podman container checkpoint --all
|
||||
podman container checkpoint --leave-running --latest`,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
checkpointOptions entities.CheckpointOptions
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
|
||||
Command: checkpointCommand,
|
||||
Parent: containerCmd,
|
||||
})
|
||||
flags := checkpointCommand.Flags()
|
||||
flags.BoolVarP(&checkpointOptions.Keep, "keep", "k", false, "Keep all temporary checkpoint files")
|
||||
flags.BoolVarP(&checkpointOptions.LeaveRuninng, "leave-running", "R", false, "Leave the container running after writing checkpoint to disk")
|
||||
flags.BoolVar(&checkpointOptions.TCPEstablished, "tcp-established", false, "Checkpoint a container with established TCP connections")
|
||||
flags.BoolVarP(&checkpointOptions.All, "all", "a", false, "Checkpoint all running containers")
|
||||
flags.BoolVarP(&checkpointOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
|
||||
flags.StringVarP(&checkpointOptions.Export, "export", "e", "", "Export the checkpoint image to a tar.gz")
|
||||
flags.BoolVar(&checkpointOptions.IgnoreRootFS, "ignore-rootfs", false, "Do not include root file-system changes when exporting")
|
||||
if registry.IsRemote() {
|
||||
_ = flags.MarkHidden("latest")
|
||||
}
|
||||
}
|
||||
|
||||
func checkpoint(cmd *cobra.Command, args []string) error {
|
||||
var errs utils.OutputErrors
|
||||
if rootless.IsRootless() {
|
||||
return errors.New("checkpointing a container requires root")
|
||||
}
|
||||
if checkpointOptions.Export == "" && checkpointOptions.IgnoreRootFS {
|
||||
return errors.Errorf("--ignore-rootfs can only be used with --export")
|
||||
}
|
||||
responses, err := registry.ContainerEngine().ContainerCheckpoint(context.Background(), args, checkpointOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range responses {
|
||||
if r.Err == nil {
|
||||
fmt.Println(r.Id)
|
||||
} else {
|
||||
errs = append(errs, r.Err)
|
||||
}
|
||||
}
|
||||
return errs.PrintErrors()
|
||||
}
|
104
cmd/podmanV2/containers/restore.go
Normal file
104
cmd/podmanV2/containers/restore.go
Normal file
@ -0,0 +1,104 @@
|
||||
package containers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/libpod/cmd/podmanV2/parse"
|
||||
"github.com/containers/libpod/cmd/podmanV2/registry"
|
||||
"github.com/containers/libpod/cmd/podmanV2/utils"
|
||||
"github.com/containers/libpod/pkg/domain/entities"
|
||||
"github.com/containers/libpod/pkg/rootless"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
restoreDescription = `
|
||||
podman container restore
|
||||
|
||||
Restores a container from a checkpoint. The container name or ID can be used.
|
||||
`
|
||||
restoreCommand = &cobra.Command{
|
||||
Use: "restore [flags] CONTAINER [CONTAINER...]",
|
||||
Short: "Restores one or more containers from a checkpoint",
|
||||
Long: restoreDescription,
|
||||
RunE: restore,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
return parse.CheckAllLatestAndCIDFile(cmd, args, true, false)
|
||||
},
|
||||
Example: `podman container restore ctrID
|
||||
podman container restore --latest
|
||||
podman container restore --all`,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
restoreOptions entities.RestoreOptions
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
|
||||
Command: restoreCommand,
|
||||
Parent: containerCmd,
|
||||
})
|
||||
flags := restoreCommand.Flags()
|
||||
flags.BoolVarP(&restoreOptions.All, "all", "a", false, "Restore all checkpointed containers")
|
||||
flags.BoolVarP(&restoreOptions.Keep, "keep", "k", false, "Keep all temporary checkpoint files")
|
||||
flags.BoolVarP(&restoreOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
|
||||
flags.BoolVar(&restoreOptions.TCPEstablished, "tcp-established", false, "Restore a container with established TCP connections")
|
||||
flags.StringVarP(&restoreOptions.Import, "import", "i", "", "Restore from exported checkpoint archive (tar.gz)")
|
||||
flags.StringVarP(&restoreOptions.Name, "name", "n", "", "Specify new name for container restored from exported checkpoint (only works with --import)")
|
||||
flags.BoolVar(&restoreOptions.IgnoreRootFS, "ignore-rootfs", false, "Do not apply root file-system changes when importing from exported checkpoint")
|
||||
flags.BoolVar(&restoreOptions.IgnoreStaticIP, "ignore-static-ip", false, "Ignore IP address set via --static-ip")
|
||||
flags.BoolVar(&restoreOptions.IgnoreStaticMAC, "ignore-static-mac", false, "Ignore MAC address set via --mac-address")
|
||||
if registry.IsRemote() {
|
||||
_ = flags.MarkHidden("latest")
|
||||
}
|
||||
}
|
||||
|
||||
func restore(cmd *cobra.Command, args []string) error {
|
||||
var errs utils.OutputErrors
|
||||
if rootless.IsRootless() {
|
||||
return errors.New("restoring a container requires root")
|
||||
}
|
||||
if restoreOptions.Import == "" && restoreOptions.IgnoreRootFS {
|
||||
return errors.Errorf("--ignore-rootfs can only be used with --import")
|
||||
}
|
||||
if restoreOptions.Import == "" && restoreOptions.Name != "" {
|
||||
return errors.Errorf("--name can only be used with --import")
|
||||
}
|
||||
if restoreOptions.Name != "" && restoreOptions.TCPEstablished {
|
||||
return errors.Errorf("--tcp-established cannot be used with --name")
|
||||
}
|
||||
|
||||
argLen := len(args)
|
||||
if restoreOptions.Import != "" {
|
||||
if restoreOptions.All || restoreOptions.Latest {
|
||||
return errors.Errorf("Cannot use --import with --all or --latest")
|
||||
}
|
||||
if argLen > 0 {
|
||||
return errors.Errorf("Cannot use --import with positional arguments")
|
||||
}
|
||||
}
|
||||
if (restoreOptions.All || restoreOptions.Latest) && argLen > 0 {
|
||||
return errors.Errorf("no arguments are needed with --all or --latest")
|
||||
}
|
||||
if argLen < 1 && !restoreOptions.All && !restoreOptions.Latest && restoreOptions.Import == "" {
|
||||
return errors.Errorf("you must provide at least one name or id")
|
||||
}
|
||||
responses, err := registry.ContainerEngine().ContainerRestore(context.Background(), args, restoreOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range responses {
|
||||
if r.Err == nil {
|
||||
fmt.Println(r.Id)
|
||||
} else {
|
||||
errs = append(errs, r.Err)
|
||||
}
|
||||
}
|
||||
return errs.PrintErrors()
|
||||
|
||||
}
|
@ -26,6 +26,7 @@ import (
|
||||
"github.com/containers/libpod/libpod/image"
|
||||
"github.com/containers/libpod/libpod/logs"
|
||||
"github.com/containers/libpod/pkg/adapter/shortcuts"
|
||||
"github.com/containers/libpod/pkg/checkpoint"
|
||||
envLib "github.com/containers/libpod/pkg/env"
|
||||
"github.com/containers/libpod/pkg/systemd/generate"
|
||||
"github.com/containers/storage"
|
||||
@ -625,7 +626,7 @@ func (r *LocalRuntime) Restore(ctx context.Context, c *cliconfig.RestoreValues)
|
||||
|
||||
switch {
|
||||
case c.Import != "":
|
||||
containers, err = crImportCheckpoint(ctx, r.Runtime, c.Import, c.Name)
|
||||
containers, err = checkpoint.CRImportCheckpoint(ctx, r.Runtime, c.Import, c.Name)
|
||||
case c.All:
|
||||
containers, err = r.GetContainers(filterFuncs...)
|
||||
default:
|
||||
|
@ -1,16 +1,21 @@
|
||||
package libpod
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/containers/libpod/pkg/api/handlers/compat"
|
||||
|
||||
"github.com/containers/libpod/cmd/podman/shared"
|
||||
"github.com/containers/libpod/libpod"
|
||||
"github.com/containers/libpod/libpod/define"
|
||||
"github.com/containers/libpod/pkg/api/handlers/utils"
|
||||
"github.com/containers/libpod/pkg/domain/entities"
|
||||
"github.com/gorilla/schema"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -325,3 +330,129 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts shared.P
|
||||
}
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
func Checkpoint(w http.ResponseWriter, r *http.Request) {
|
||||
var targetFile string
|
||||
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
||||
query := struct {
|
||||
Keep bool `schema:"keep"`
|
||||
LeaveRunning bool `schema:"leaveRunning"`
|
||||
TCPEstablished bool `schema:"tcpEstablished"`
|
||||
Export bool `schema:"export"`
|
||||
IgnoreRootFS bool `schema:"ignoreRootFS"`
|
||||
}{
|
||||
// override any golang type defaults
|
||||
}
|
||||
|
||||
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
||||
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
|
||||
errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
|
||||
return
|
||||
}
|
||||
name := utils.GetName(r)
|
||||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||||
ctr, err := runtime.LookupContainer(name)
|
||||
if err != nil {
|
||||
utils.ContainerNotFound(w, name, err)
|
||||
return
|
||||
}
|
||||
if query.Export {
|
||||
tmpFile, err := ioutil.TempFile("", "checkpoint")
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(tmpFile.Name())
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
targetFile = tmpFile.Name()
|
||||
}
|
||||
options := libpod.ContainerCheckpointOptions{
|
||||
Keep: query.Keep,
|
||||
KeepRunning: query.LeaveRunning,
|
||||
TCPEstablished: query.TCPEstablished,
|
||||
IgnoreRootfs: query.IgnoreRootFS,
|
||||
}
|
||||
if query.Export {
|
||||
options.TargetFile = targetFile
|
||||
}
|
||||
err = ctr.Checkpoint(r.Context(), options)
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
if query.Export {
|
||||
f, err := os.Open(targetFile)
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
utils.WriteResponse(w, http.StatusOK, f)
|
||||
return
|
||||
}
|
||||
utils.WriteResponse(w, http.StatusOK, entities.CheckpointReport{Id: ctr.ID()})
|
||||
}
|
||||
|
||||
func Restore(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
targetFile string
|
||||
)
|
||||
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
||||
query := struct {
|
||||
Keep bool `schema:"keep"`
|
||||
TCPEstablished bool `schema:"tcpEstablished"`
|
||||
Import bool `schema:"import"`
|
||||
Name string `schema:"name"`
|
||||
IgnoreRootFS bool `schema:"ignoreRootFS"`
|
||||
IgnoreStaticIP bool `schema:"ignoreStaticIP"`
|
||||
IgnoreStaticMAC bool `schema:"ignoreStaticMAC"`
|
||||
}{
|
||||
// override any golang type defaults
|
||||
}
|
||||
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
||||
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
|
||||
errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
|
||||
return
|
||||
}
|
||||
name := utils.GetName(r)
|
||||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||||
ctr, err := runtime.LookupContainer(name)
|
||||
if err != nil {
|
||||
utils.ContainerNotFound(w, name, err)
|
||||
return
|
||||
}
|
||||
if query.Import {
|
||||
t, err := ioutil.TempFile("", "restore")
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
defer t.Close()
|
||||
if err := compat.SaveFromBody(t, r); err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
targetFile = t.Name()
|
||||
}
|
||||
|
||||
options := libpod.ContainerCheckpointOptions{
|
||||
Keep: query.Keep,
|
||||
TCPEstablished: query.TCPEstablished,
|
||||
IgnoreRootfs: query.IgnoreRootFS,
|
||||
IgnoreStaticIP: query.IgnoreStaticIP,
|
||||
IgnoreStaticMAC: query.IgnoreStaticMAC,
|
||||
}
|
||||
if query.Import {
|
||||
options.TargetFile = targetFile
|
||||
options.Name = query.Name
|
||||
}
|
||||
err = ctr.Restore(r.Context(), options)
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
utils.WriteResponse(w, http.StatusOK, entities.RestoreReport{Id: ctr.ID()})
|
||||
}
|
||||
|
@ -1282,5 +1282,100 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
|
||||
// 500:
|
||||
// $ref: "#/responses/InternalError"
|
||||
r.HandleFunc(VersionedPath("/libpod/containers/{name}/export"), s.APIHandler(compat.ExportContainer)).Methods(http.MethodGet)
|
||||
// swagger:operation GET /libpod/containers/{name}/checkout libpod libpodCheckpointContainer
|
||||
// ---
|
||||
// tags:
|
||||
// - containers
|
||||
// summary: Checkpoint a container
|
||||
// parameters:
|
||||
// - in: path
|
||||
// name: name
|
||||
// type: string
|
||||
// required: true
|
||||
// description: the name or ID of the container
|
||||
// - in: query
|
||||
// name: keep
|
||||
// type: boolean
|
||||
// description: keep all temporary checkpoint files
|
||||
// - in: query
|
||||
// name: leaveRunning
|
||||
// type: boolean
|
||||
// description: leave the container running after writing checkpoint to disk
|
||||
// - in: query
|
||||
// name: tcpEstablished
|
||||
// type: boolean
|
||||
// description: checkpoint a container with established TCP connections
|
||||
// - in: query
|
||||
// name: export
|
||||
// type: boolean
|
||||
// description: export the checkpoint image to a tar.gz
|
||||
// - in: query
|
||||
// name: ignoreRootFS
|
||||
// type: boolean
|
||||
// description: do not include root file-system changes when exporting
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// 200:
|
||||
// description: tarball is returned in body if exported
|
||||
// 404:
|
||||
// $ref: "#/responses/NoSuchContainer"
|
||||
// 500:
|
||||
// $ref: "#/responses/InternalError"
|
||||
r.HandleFunc(VersionedPath("/libpod/containers/{name}/checkpoint"), s.APIHandler(libpod.Checkpoint)).Methods(http.MethodPost)
|
||||
// swagger:operation GET /libpod/containers/{name} restore libpod libpodRestoreContainer
|
||||
// ---
|
||||
// tags:
|
||||
// - containers
|
||||
// summary: Restore a container
|
||||
// description: Restore a container from a checkpoint.
|
||||
// parameters:
|
||||
// - in: path
|
||||
// name: name
|
||||
// type: string
|
||||
// required: true
|
||||
// description: the name or id of the container
|
||||
// - in: query
|
||||
// name: name
|
||||
// type: string
|
||||
// description: the name of the container when restored from a tar. can only be used with import
|
||||
// - in: query
|
||||
// name: keep
|
||||
// type: boolean
|
||||
// description: keep all temporary checkpoint files
|
||||
// - in: query
|
||||
// name: leaveRunning
|
||||
// type: boolean
|
||||
// description: leave the container running after writing checkpoint to disk
|
||||
// - in: query
|
||||
// name: tcpEstablished
|
||||
// type: boolean
|
||||
// description: checkpoint a container with established TCP connections
|
||||
// - in: query
|
||||
// name: import
|
||||
// type: boolean
|
||||
// description: import the restore from a checkpoint tar.gz
|
||||
// - in: query
|
||||
// name: ignoreRootFS
|
||||
// type: boolean
|
||||
// description: do not include root file-system changes when exporting
|
||||
// - in: query
|
||||
// name: ignoreStaticIP
|
||||
// type: boolean
|
||||
// description: ignore IP address if set statically
|
||||
// - in: query
|
||||
// name: ignoreStaticMAC
|
||||
// type: boolean
|
||||
// description: ignore MAC address if set statically
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// 200:
|
||||
// description: tarball is returned in body if exported
|
||||
// 404:
|
||||
// $ref: "#/responses/NoSuchContainer"
|
||||
// 500:
|
||||
// $ref: "#/responses/InternalError"
|
||||
r.HandleFunc(VersionedPath("/libpod/containers/{name}/restore"), s.APIHandler(libpod.Restore)).Methods(http.MethodPost)
|
||||
return nil
|
||||
}
|
||||
|
79
pkg/bindings/containers/checkpoint.go
Normal file
79
pkg/bindings/containers/checkpoint.go
Normal file
@ -0,0 +1,79 @@
|
||||
package containers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/containers/libpod/pkg/bindings"
|
||||
"github.com/containers/libpod/pkg/domain/entities"
|
||||
)
|
||||
|
||||
// Checkpoint checkpoints the given container (identified by nameOrId). All additional
|
||||
// options are options and allow for more fine grained control of the checkpoint process.
|
||||
func Checkpoint(ctx context.Context, nameOrId string, keep, leaveRunning, tcpEstablished, ignoreRootFS *bool, export *string) (*entities.CheckpointReport, error) {
|
||||
var report entities.CheckpointReport
|
||||
conn, err := bindings.GetClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params := url.Values{}
|
||||
if keep != nil {
|
||||
params.Set("keep", strconv.FormatBool(*keep))
|
||||
}
|
||||
if leaveRunning != nil {
|
||||
params.Set("leaveRunning", strconv.FormatBool(*leaveRunning))
|
||||
}
|
||||
if tcpEstablished != nil {
|
||||
params.Set("TCPestablished", strconv.FormatBool(*tcpEstablished))
|
||||
}
|
||||
if ignoreRootFS != nil {
|
||||
params.Set("ignoreRootFS", strconv.FormatBool(*ignoreRootFS))
|
||||
}
|
||||
if export != nil {
|
||||
params.Set("export", *export)
|
||||
}
|
||||
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/checkpoint", params, nameOrId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &report, response.Process(&report)
|
||||
}
|
||||
|
||||
// Restore restores a checkpointed container to running. The container is identified by the nameOrId option. All
|
||||
// additional options are optional and allow finer control of the restore processs.
|
||||
func Restore(ctx context.Context, nameOrId string, keep, tcpEstablished, ignoreRootFS, ignoreStaticIP, ignoreStaticMAC *bool, name, importArchive *string) (*entities.RestoreReport, error) {
|
||||
var report entities.RestoreReport
|
||||
conn, err := bindings.GetClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params := url.Values{}
|
||||
if keep != nil {
|
||||
params.Set("keep", strconv.FormatBool(*keep))
|
||||
}
|
||||
if tcpEstablished != nil {
|
||||
params.Set("TCPestablished", strconv.FormatBool(*tcpEstablished))
|
||||
}
|
||||
if ignoreRootFS != nil {
|
||||
params.Set("ignoreRootFS", strconv.FormatBool(*ignoreRootFS))
|
||||
}
|
||||
if ignoreStaticIP != nil {
|
||||
params.Set("ignoreStaticIP", strconv.FormatBool(*ignoreStaticIP))
|
||||
}
|
||||
if ignoreStaticMAC != nil {
|
||||
params.Set("ignoreStaticMAC", strconv.FormatBool(*ignoreStaticMAC))
|
||||
}
|
||||
if name != nil {
|
||||
params.Set("name", *name)
|
||||
}
|
||||
if importArchive != nil {
|
||||
params.Set("import", *importArchive)
|
||||
}
|
||||
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restore", params, nameOrId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &report, response.Process(&report)
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
// +build !remoteclient
|
||||
|
||||
package adapter
|
||||
package checkpoint
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -42,9 +40,9 @@ func crImportFromJSON(filePath string, v interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// crImportCheckpoint it the function which imports the information
|
||||
// CRImportCheckpoint it the function which imports the information
|
||||
// from checkpoint tarball and re-creates the container from that information
|
||||
func crImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input string, name string) ([]*libpod.Container, error) {
|
||||
func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input string, name string) ([]*libpod.Container, error) {
|
||||
// First get the container definition from the
|
||||
// tarball to a temporary directory
|
||||
archiveFile, err := os.Open(input)
|
@ -121,3 +121,35 @@ type CommitReport struct {
|
||||
type ContainerExportOptions struct {
|
||||
Output string
|
||||
}
|
||||
|
||||
type CheckpointOptions struct {
|
||||
All bool
|
||||
Export string
|
||||
IgnoreRootFS bool
|
||||
Keep bool
|
||||
Latest bool
|
||||
LeaveRuninng bool
|
||||
TCPEstablished bool
|
||||
}
|
||||
|
||||
type CheckpointReport struct {
|
||||
Err error
|
||||
Id string
|
||||
}
|
||||
|
||||
type RestoreOptions struct {
|
||||
All bool
|
||||
IgnoreRootFS bool
|
||||
IgnoreStaticIP bool
|
||||
IgnoreStaticMAC bool
|
||||
Import string
|
||||
Keep bool
|
||||
Latest bool
|
||||
Name string
|
||||
TCPEstablished bool
|
||||
}
|
||||
|
||||
type RestoreReport struct {
|
||||
Err error
|
||||
Id string
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ import (
|
||||
|
||||
type ContainerEngine interface {
|
||||
ContainerCommit(ctx context.Context, nameOrId string, options CommitOptions) (*CommitReport, error)
|
||||
ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error)
|
||||
ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, 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
|
||||
|
@ -13,12 +13,42 @@ import (
|
||||
"github.com/containers/libpod/libpod/define"
|
||||
"github.com/containers/libpod/libpod/image"
|
||||
"github.com/containers/libpod/pkg/adapter/shortcuts"
|
||||
"github.com/containers/libpod/pkg/checkpoint"
|
||||
"github.com/containers/libpod/pkg/domain/entities"
|
||||
"github.com/containers/libpod/pkg/signal"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// getContainersByContext gets pods whether all, latest, or a slice of names/ids
|
||||
// is specified.
|
||||
func getContainersByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, err error) {
|
||||
var ctr *libpod.Container
|
||||
ctrs = []*libpod.Container{}
|
||||
|
||||
switch {
|
||||
case all:
|
||||
ctrs, err = runtime.GetAllContainers()
|
||||
case latest:
|
||||
ctr, err = runtime.GetLatestContainer()
|
||||
ctrs = append(ctrs, ctr)
|
||||
default:
|
||||
for _, n := range names {
|
||||
ctr, e := runtime.LookupContainer(n)
|
||||
if e != nil {
|
||||
// Log all errors here, so callers don't need to.
|
||||
logrus.Debugf("Error looking up container %q: %v", n, e)
|
||||
if err == nil {
|
||||
err = e
|
||||
}
|
||||
} else {
|
||||
ctrs = append(ctrs, ctr)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Should return *entities.ContainerExistsReport, error
|
||||
func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) {
|
||||
_, err := ic.Libpod.LookupContainer(nameOrId)
|
||||
@ -333,3 +363,82 @@ func (ic *ContainerEngine) ContainerExport(ctx context.Context, nameOrId string,
|
||||
}
|
||||
return ctr.Export(options.Output)
|
||||
}
|
||||
|
||||
func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds []string, options entities.CheckpointOptions) ([]*entities.CheckpointReport, error) {
|
||||
var (
|
||||
err error
|
||||
cons []*libpod.Container
|
||||
reports []*entities.CheckpointReport
|
||||
)
|
||||
checkOpts := libpod.ContainerCheckpointOptions{
|
||||
Keep: options.Keep,
|
||||
TCPEstablished: options.TCPEstablished,
|
||||
TargetFile: options.Export,
|
||||
IgnoreRootfs: options.IgnoreRootFS,
|
||||
}
|
||||
|
||||
if options.All {
|
||||
running := func(c *libpod.Container) bool {
|
||||
state, _ := c.State()
|
||||
return state == define.ContainerStateRunning
|
||||
}
|
||||
cons, err = ic.Libpod.GetContainers(running)
|
||||
} else {
|
||||
cons, err = getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, con := range cons {
|
||||
err = con.Checkpoint(ctx, checkOpts)
|
||||
reports = append(reports, &entities.CheckpointReport{
|
||||
Err: err,
|
||||
Id: con.ID(),
|
||||
})
|
||||
}
|
||||
return reports, nil
|
||||
}
|
||||
|
||||
func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []string, options entities.RestoreOptions) ([]*entities.RestoreReport, error) {
|
||||
var (
|
||||
cons []*libpod.Container
|
||||
err error
|
||||
filterFuncs []libpod.ContainerFilter
|
||||
reports []*entities.RestoreReport
|
||||
)
|
||||
|
||||
restoreOptions := libpod.ContainerCheckpointOptions{
|
||||
Keep: options.Keep,
|
||||
TCPEstablished: options.TCPEstablished,
|
||||
TargetFile: options.Import,
|
||||
Name: options.Name,
|
||||
IgnoreRootfs: options.IgnoreRootFS,
|
||||
IgnoreStaticIP: options.IgnoreStaticIP,
|
||||
IgnoreStaticMAC: options.IgnoreStaticMAC,
|
||||
}
|
||||
|
||||
filterFuncs = append(filterFuncs, func(c *libpod.Container) bool {
|
||||
state, _ := c.State()
|
||||
return state == define.ContainerStateExited
|
||||
})
|
||||
|
||||
switch {
|
||||
case options.Import != "":
|
||||
cons, err = checkpoint.CRImportCheckpoint(ctx, ic.Libpod, options.Import, options.Name)
|
||||
case options.All:
|
||||
cons, err = ic.Libpod.GetContainers(filterFuncs...)
|
||||
default:
|
||||
cons, err = getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, con := range cons {
|
||||
err := con.Restore(ctx, restoreOptions)
|
||||
reports = append(reports, &entities.RestoreReport{
|
||||
Err: err,
|
||||
Id: con.ID(),
|
||||
})
|
||||
}
|
||||
return reports, nil
|
||||
}
|
||||
|
@ -6,7 +6,8 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
|
||||
"github.com/containers/libpod/libpod/define"
|
||||
"github.com/containers/libpod/pkg/api/handlers/libpod"
|
||||
"github.com/containers/libpod/pkg/bindings/containers"
|
||||
"github.com/containers/libpod/pkg/domain/entities"
|
||||
"github.com/pkg/errors"
|
||||
@ -226,3 +227,72 @@ func (ic *ContainerEngine) ContainerExport(ctx context.Context, nameOrId string,
|
||||
}
|
||||
return containers.Export(ic.ClientCxt, nameOrId, w)
|
||||
}
|
||||
|
||||
func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds []string, options entities.CheckpointOptions) ([]*entities.CheckpointReport, error) {
|
||||
var (
|
||||
reports []*entities.CheckpointReport
|
||||
err error
|
||||
ctrs []libpod.ListContainer
|
||||
)
|
||||
|
||||
if options.All {
|
||||
allCtrs, err := getContainersByContext(ic.ClientCxt, true, []string{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// narrow the list to running only
|
||||
for _, c := range allCtrs {
|
||||
if c.State == define.ContainerStateRunning.String() {
|
||||
ctrs = append(ctrs, c)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
ctrs, err = getContainersByContext(ic.ClientCxt, false, namesOrIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for _, c := range ctrs {
|
||||
report, err := containers.Checkpoint(ic.ClientCxt, c.ID, &options.Keep, &options.LeaveRuninng, &options.TCPEstablished, &options.IgnoreRootFS, &options.Export)
|
||||
if err != nil {
|
||||
reports = append(reports, &entities.CheckpointReport{Id: c.ID, Err: err})
|
||||
}
|
||||
reports = append(reports, report)
|
||||
}
|
||||
return reports, nil
|
||||
}
|
||||
|
||||
func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []string, options entities.RestoreOptions) ([]*entities.RestoreReport, error) {
|
||||
var (
|
||||
reports []*entities.RestoreReport
|
||||
err error
|
||||
ctrs []libpod.ListContainer
|
||||
)
|
||||
if options.All {
|
||||
allCtrs, err := getContainersByContext(ic.ClientCxt, true, []string{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// narrow the list to exited only
|
||||
for _, c := range allCtrs {
|
||||
if c.State == define.ContainerStateExited.String() {
|
||||
ctrs = append(ctrs, c)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
ctrs, err = getContainersByContext(ic.ClientCxt, false, namesOrIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for _, c := range ctrs {
|
||||
report, err := containers.Restore(ic.ClientCxt, c.ID, &options.Keep, &options.TCPEstablished, &options.IgnoreRootFS, &options.IgnoreStaticIP, &options.IgnoreStaticMAC, &options.Name, &options.Import)
|
||||
if err != nil {
|
||||
reports = append(reports, &entities.RestoreReport{Id: c.ID, Err: err})
|
||||
}
|
||||
reports = append(reports, report)
|
||||
}
|
||||
return reports, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user