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:
Brent Baude
2020-03-27 08:20:13 -05:00
parent ccb9e579c4
commit 8a16674722
11 changed files with 707 additions and 7 deletions

View 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()
}

View 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()
}

View File

@ -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:

View File

@ -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()})
}

View File

@ -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
}

View 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)
}

View File

@ -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)

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}