github.com/demonoid81/containerd@v1.3.4/task.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package containerd
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/json"
    23  	"fmt"
    24  	"io"
    25  	goruntime "runtime"
    26  	"strings"
    27  	"syscall"
    28  	"time"
    29  
    30  	"github.com/containerd/containerd/api/services/tasks/v1"
    31  	"github.com/containerd/containerd/api/types"
    32  	"github.com/containerd/containerd/cio"
    33  	"github.com/containerd/containerd/content"
    34  	"github.com/containerd/containerd/diff"
    35  	"github.com/containerd/containerd/errdefs"
    36  	"github.com/containerd/containerd/images"
    37  	"github.com/containerd/containerd/mount"
    38  	"github.com/containerd/containerd/plugin"
    39  	"github.com/containerd/containerd/rootfs"
    40  	"github.com/containerd/containerd/runtime/linux/runctypes"
    41  	"github.com/containerd/containerd/runtime/v2/runc/options"
    42  	"github.com/containerd/typeurl"
    43  	google_protobuf "github.com/gogo/protobuf/types"
    44  	digest "github.com/opencontainers/go-digest"
    45  	is "github.com/opencontainers/image-spec/specs-go"
    46  	v1 "github.com/opencontainers/image-spec/specs-go/v1"
    47  	specs "github.com/opencontainers/runtime-spec/specs-go"
    48  	"github.com/pkg/errors"
    49  )
    50  
    51  // UnknownExitStatus is returned when containerd is unable to
    52  // determine the exit status of a process. This can happen if the process never starts
    53  // or if an error was encountered when obtaining the exit status, it is set to 255.
    54  const UnknownExitStatus = 255
    55  
    56  const (
    57  	checkpointDateFormat = "01-02-2006-15:04:05"
    58  	checkpointNameFormat = "containerd.io/checkpoint/%s:%s"
    59  )
    60  
    61  // Status returns process status and exit information
    62  type Status struct {
    63  	// Status of the process
    64  	Status ProcessStatus
    65  	// ExitStatus returned by the process
    66  	ExitStatus uint32
    67  	// ExitedTime is the time at which the process died
    68  	ExitTime time.Time
    69  }
    70  
    71  // ProcessInfo provides platform specific process information
    72  type ProcessInfo struct {
    73  	// Pid is the process ID
    74  	Pid uint32
    75  	// Info includes additional process information
    76  	// Info varies by platform
    77  	Info *google_protobuf.Any
    78  }
    79  
    80  // ProcessStatus returns a human readable status for the Process representing its current status
    81  type ProcessStatus string
    82  
    83  const (
    84  	// Running indicates the process is currently executing
    85  	Running ProcessStatus = "running"
    86  	// Created indicates the process has been created within containerd but the
    87  	// user's defined process has not started
    88  	Created ProcessStatus = "created"
    89  	// Stopped indicates that the process has ran and exited
    90  	Stopped ProcessStatus = "stopped"
    91  	// Paused indicates that the process is currently paused
    92  	Paused ProcessStatus = "paused"
    93  	// Pausing indicates that the process is currently switching from a
    94  	// running state into a paused state
    95  	Pausing ProcessStatus = "pausing"
    96  	// Unknown indicates that we could not determine the status from the runtime
    97  	Unknown ProcessStatus = "unknown"
    98  )
    99  
   100  // IOCloseInfo allows specific io pipes to be closed on a process
   101  type IOCloseInfo struct {
   102  	Stdin bool
   103  }
   104  
   105  // IOCloserOpts allows the caller to set specific pipes as closed on a process
   106  type IOCloserOpts func(*IOCloseInfo)
   107  
   108  // WithStdinCloser closes the stdin of a process
   109  func WithStdinCloser(r *IOCloseInfo) {
   110  	r.Stdin = true
   111  }
   112  
   113  // CheckpointTaskInfo allows specific checkpoint information to be set for the task
   114  type CheckpointTaskInfo struct {
   115  	Name string
   116  	// ParentCheckpoint is the digest of a parent checkpoint
   117  	ParentCheckpoint digest.Digest
   118  	// Options hold runtime specific settings for checkpointing a task
   119  	Options interface{}
   120  
   121  	runtime string
   122  }
   123  
   124  // Runtime name for the container
   125  func (i *CheckpointTaskInfo) Runtime() string {
   126  	return i.runtime
   127  }
   128  
   129  // CheckpointTaskOpts allows the caller to set checkpoint options
   130  type CheckpointTaskOpts func(*CheckpointTaskInfo) error
   131  
   132  // TaskInfo sets options for task creation
   133  type TaskInfo struct {
   134  	// Checkpoint is the Descriptor for an existing checkpoint that can be used
   135  	// to restore a task's runtime and memory state
   136  	Checkpoint *types.Descriptor
   137  	// RootFS is a list of mounts to use as the task's root filesystem
   138  	RootFS []mount.Mount
   139  	// Options hold runtime specific settings for task creation
   140  	Options interface{}
   141  	runtime string
   142  }
   143  
   144  // Runtime name for the container
   145  func (i *TaskInfo) Runtime() string {
   146  	return i.runtime
   147  }
   148  
   149  // Task is the executable object within containerd
   150  type Task interface {
   151  	Process
   152  
   153  	// Pause suspends the execution of the task
   154  	Pause(context.Context) error
   155  	// Resume the execution of the task
   156  	Resume(context.Context) error
   157  	// Exec creates a new process inside the task
   158  	Exec(context.Context, string, *specs.Process, cio.Creator) (Process, error)
   159  	// Pids returns a list of system specific process ids inside the task
   160  	Pids(context.Context) ([]ProcessInfo, error)
   161  	// Checkpoint serializes the runtime and memory information of a task into an
   162  	// OCI Index that can be push and pulled from a remote resource.
   163  	//
   164  	// Additional software like CRIU maybe required to checkpoint and restore tasks
   165  	// NOTE: Checkpoint supports to dump task information to a directory, in this way,
   166  	// an empty OCI Index will be returned.
   167  	Checkpoint(context.Context, ...CheckpointTaskOpts) (Image, error)
   168  	// Update modifies executing tasks with updated settings
   169  	Update(context.Context, ...UpdateTaskOpts) error
   170  	// LoadProcess loads a previously created exec'd process
   171  	LoadProcess(context.Context, string, cio.Attach) (Process, error)
   172  	// Metrics returns task metrics for runtime specific metrics
   173  	//
   174  	// The metric types are generic to containerd and change depending on the runtime
   175  	// For the built in Linux runtime, github.com/containerd/cgroups.Metrics
   176  	// are returned in protobuf format
   177  	Metrics(context.Context) (*types.Metric, error)
   178  }
   179  
   180  var _ = (Task)(&task{})
   181  
   182  type task struct {
   183  	client *Client
   184  
   185  	io  cio.IO
   186  	id  string
   187  	pid uint32
   188  }
   189  
   190  // ID of the task
   191  func (t *task) ID() string {
   192  	return t.id
   193  }
   194  
   195  // Pid returns the pid or process id for the task
   196  func (t *task) Pid() uint32 {
   197  	return t.pid
   198  }
   199  
   200  func (t *task) Start(ctx context.Context) error {
   201  	r, err := t.client.TaskService().Start(ctx, &tasks.StartRequest{
   202  		ContainerID: t.id,
   203  	})
   204  	if err != nil {
   205  		if t.io != nil {
   206  			t.io.Cancel()
   207  			t.io.Close()
   208  		}
   209  		return errdefs.FromGRPC(err)
   210  	}
   211  	t.pid = r.Pid
   212  	return nil
   213  }
   214  
   215  func (t *task) Kill(ctx context.Context, s syscall.Signal, opts ...KillOpts) error {
   216  	var i KillInfo
   217  	for _, o := range opts {
   218  		if err := o(ctx, &i); err != nil {
   219  			return err
   220  		}
   221  	}
   222  	_, err := t.client.TaskService().Kill(ctx, &tasks.KillRequest{
   223  		Signal:      uint32(s),
   224  		ContainerID: t.id,
   225  		ExecID:      i.ExecID,
   226  		All:         i.All,
   227  	})
   228  	if err != nil {
   229  		return errdefs.FromGRPC(err)
   230  	}
   231  	return nil
   232  }
   233  
   234  func (t *task) Pause(ctx context.Context) error {
   235  	_, err := t.client.TaskService().Pause(ctx, &tasks.PauseTaskRequest{
   236  		ContainerID: t.id,
   237  	})
   238  	return errdefs.FromGRPC(err)
   239  }
   240  
   241  func (t *task) Resume(ctx context.Context) error {
   242  	_, err := t.client.TaskService().Resume(ctx, &tasks.ResumeTaskRequest{
   243  		ContainerID: t.id,
   244  	})
   245  	return errdefs.FromGRPC(err)
   246  }
   247  
   248  func (t *task) Status(ctx context.Context) (Status, error) {
   249  	r, err := t.client.TaskService().Get(ctx, &tasks.GetRequest{
   250  		ContainerID: t.id,
   251  	})
   252  	if err != nil {
   253  		return Status{}, errdefs.FromGRPC(err)
   254  	}
   255  	return Status{
   256  		Status:     ProcessStatus(strings.ToLower(r.Process.Status.String())),
   257  		ExitStatus: r.Process.ExitStatus,
   258  		ExitTime:   r.Process.ExitedAt,
   259  	}, nil
   260  }
   261  
   262  func (t *task) Wait(ctx context.Context) (<-chan ExitStatus, error) {
   263  	c := make(chan ExitStatus, 1)
   264  	go func() {
   265  		defer close(c)
   266  		r, err := t.client.TaskService().Wait(ctx, &tasks.WaitRequest{
   267  			ContainerID: t.id,
   268  		})
   269  		if err != nil {
   270  			c <- ExitStatus{
   271  				code: UnknownExitStatus,
   272  				err:  err,
   273  			}
   274  			return
   275  		}
   276  		c <- ExitStatus{
   277  			code:     r.ExitStatus,
   278  			exitedAt: r.ExitedAt,
   279  		}
   280  	}()
   281  	return c, nil
   282  }
   283  
   284  // Delete deletes the task and its runtime state
   285  // it returns the exit status of the task and any errors that were encountered
   286  // during cleanup
   287  func (t *task) Delete(ctx context.Context, opts ...ProcessDeleteOpts) (*ExitStatus, error) {
   288  	for _, o := range opts {
   289  		if err := o(ctx, t); err != nil {
   290  			return nil, err
   291  		}
   292  	}
   293  	status, err := t.Status(ctx)
   294  	if err != nil && errdefs.IsNotFound(err) {
   295  		return nil, err
   296  	}
   297  	switch status.Status {
   298  	case Stopped, Unknown, "":
   299  	case Created:
   300  		if t.client.runtime == fmt.Sprintf("%s.%s", plugin.RuntimePlugin, "windows") {
   301  			// On windows Created is akin to Stopped
   302  			break
   303  		}
   304  		fallthrough
   305  	default:
   306  		return nil, errors.Wrapf(errdefs.ErrFailedPrecondition, "task must be stopped before deletion: %s", status.Status)
   307  	}
   308  	if t.io != nil {
   309  		t.io.Cancel()
   310  		t.io.Wait()
   311  	}
   312  	r, err := t.client.TaskService().Delete(ctx, &tasks.DeleteTaskRequest{
   313  		ContainerID: t.id,
   314  	})
   315  	if err != nil {
   316  		return nil, errdefs.FromGRPC(err)
   317  	}
   318  	// Only cleanup the IO after a successful Delete
   319  	if t.io != nil {
   320  		t.io.Close()
   321  	}
   322  	return &ExitStatus{code: r.ExitStatus, exitedAt: r.ExitedAt}, nil
   323  }
   324  
   325  func (t *task) Exec(ctx context.Context, id string, spec *specs.Process, ioCreate cio.Creator) (_ Process, err error) {
   326  	if id == "" {
   327  		return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "exec id must not be empty")
   328  	}
   329  	i, err := ioCreate(id)
   330  	if err != nil {
   331  		return nil, err
   332  	}
   333  	defer func() {
   334  		if err != nil && i != nil {
   335  			i.Cancel()
   336  			i.Close()
   337  		}
   338  	}()
   339  	any, err := typeurl.MarshalAny(spec)
   340  	if err != nil {
   341  		return nil, err
   342  	}
   343  	cfg := i.Config()
   344  	request := &tasks.ExecProcessRequest{
   345  		ContainerID: t.id,
   346  		ExecID:      id,
   347  		Terminal:    cfg.Terminal,
   348  		Stdin:       cfg.Stdin,
   349  		Stdout:      cfg.Stdout,
   350  		Stderr:      cfg.Stderr,
   351  		Spec:        any,
   352  	}
   353  	if _, err := t.client.TaskService().Exec(ctx, request); err != nil {
   354  		i.Cancel()
   355  		i.Wait()
   356  		i.Close()
   357  		return nil, errdefs.FromGRPC(err)
   358  	}
   359  	return &process{
   360  		id:   id,
   361  		task: t,
   362  		io:   i,
   363  	}, nil
   364  }
   365  
   366  func (t *task) Pids(ctx context.Context) ([]ProcessInfo, error) {
   367  	response, err := t.client.TaskService().ListPids(ctx, &tasks.ListPidsRequest{
   368  		ContainerID: t.id,
   369  	})
   370  	if err != nil {
   371  		return nil, errdefs.FromGRPC(err)
   372  	}
   373  	var processList []ProcessInfo
   374  	for _, p := range response.Processes {
   375  		processList = append(processList, ProcessInfo{
   376  			Pid:  p.Pid,
   377  			Info: p.Info,
   378  		})
   379  	}
   380  	return processList, nil
   381  }
   382  
   383  func (t *task) CloseIO(ctx context.Context, opts ...IOCloserOpts) error {
   384  	r := &tasks.CloseIORequest{
   385  		ContainerID: t.id,
   386  	}
   387  	var i IOCloseInfo
   388  	for _, o := range opts {
   389  		o(&i)
   390  	}
   391  	r.Stdin = i.Stdin
   392  	_, err := t.client.TaskService().CloseIO(ctx, r)
   393  	return errdefs.FromGRPC(err)
   394  }
   395  
   396  func (t *task) IO() cio.IO {
   397  	return t.io
   398  }
   399  
   400  func (t *task) Resize(ctx context.Context, w, h uint32) error {
   401  	_, err := t.client.TaskService().ResizePty(ctx, &tasks.ResizePtyRequest{
   402  		ContainerID: t.id,
   403  		Width:       w,
   404  		Height:      h,
   405  	})
   406  	return errdefs.FromGRPC(err)
   407  }
   408  
   409  // NOTE: Checkpoint supports to dump task information to a directory, in this way, an empty
   410  // OCI Index will be returned.
   411  func (t *task) Checkpoint(ctx context.Context, opts ...CheckpointTaskOpts) (Image, error) {
   412  	ctx, done, err := t.client.WithLease(ctx)
   413  	if err != nil {
   414  		return nil, err
   415  	}
   416  	defer done(ctx)
   417  	cr, err := t.client.ContainerService().Get(ctx, t.id)
   418  	if err != nil {
   419  		return nil, err
   420  	}
   421  
   422  	request := &tasks.CheckpointTaskRequest{
   423  		ContainerID: t.id,
   424  	}
   425  	i := CheckpointTaskInfo{
   426  		runtime: cr.Runtime.Name,
   427  	}
   428  	for _, o := range opts {
   429  		if err := o(&i); err != nil {
   430  			return nil, err
   431  		}
   432  	}
   433  	// set a default name
   434  	if i.Name == "" {
   435  		i.Name = fmt.Sprintf(checkpointNameFormat, t.id, time.Now().Format(checkpointDateFormat))
   436  	}
   437  	request.ParentCheckpoint = i.ParentCheckpoint
   438  	if i.Options != nil {
   439  		any, err := typeurl.MarshalAny(i.Options)
   440  		if err != nil {
   441  			return nil, err
   442  		}
   443  		request.Options = any
   444  	}
   445  	// make sure we pause it and resume after all other filesystem operations are completed
   446  	if err := t.Pause(ctx); err != nil {
   447  		return nil, err
   448  	}
   449  	defer t.Resume(ctx)
   450  	index := v1.Index{
   451  		Versioned: is.Versioned{
   452  			SchemaVersion: 2,
   453  		},
   454  		Annotations: make(map[string]string),
   455  	}
   456  	if err := t.checkpointTask(ctx, &index, request); err != nil {
   457  		return nil, err
   458  	}
   459  	// if checkpoint image path passed, jump checkpoint image,
   460  	// return an empty image
   461  	if isCheckpointPathExist(cr.Runtime.Name, i.Options) {
   462  		return NewImage(t.client, images.Image{}), nil
   463  	}
   464  
   465  	if cr.Image != "" {
   466  		if err := t.checkpointImage(ctx, &index, cr.Image); err != nil {
   467  			return nil, err
   468  		}
   469  		index.Annotations["image.name"] = cr.Image
   470  	}
   471  	if cr.SnapshotKey != "" {
   472  		if err := t.checkpointRWSnapshot(ctx, &index, cr.Snapshotter, cr.SnapshotKey); err != nil {
   473  			return nil, err
   474  		}
   475  	}
   476  	desc, err := t.writeIndex(ctx, &index)
   477  	if err != nil {
   478  		return nil, err
   479  	}
   480  	im := images.Image{
   481  		Name:   i.Name,
   482  		Target: desc,
   483  		Labels: map[string]string{
   484  			"containerd.io/checkpoint": "true",
   485  		},
   486  	}
   487  	if im, err = t.client.ImageService().Create(ctx, im); err != nil {
   488  		return nil, err
   489  	}
   490  	return NewImage(t.client, im), nil
   491  }
   492  
   493  // UpdateTaskInfo allows updated specific settings to be changed on a task
   494  type UpdateTaskInfo struct {
   495  	// Resources updates a tasks resource constraints
   496  	Resources interface{}
   497  }
   498  
   499  // UpdateTaskOpts allows a caller to update task settings
   500  type UpdateTaskOpts func(context.Context, *Client, *UpdateTaskInfo) error
   501  
   502  func (t *task) Update(ctx context.Context, opts ...UpdateTaskOpts) error {
   503  	request := &tasks.UpdateTaskRequest{
   504  		ContainerID: t.id,
   505  	}
   506  	var i UpdateTaskInfo
   507  	for _, o := range opts {
   508  		if err := o(ctx, t.client, &i); err != nil {
   509  			return err
   510  		}
   511  	}
   512  	if i.Resources != nil {
   513  		any, err := typeurl.MarshalAny(i.Resources)
   514  		if err != nil {
   515  			return err
   516  		}
   517  		request.Resources = any
   518  	}
   519  	_, err := t.client.TaskService().Update(ctx, request)
   520  	return errdefs.FromGRPC(err)
   521  }
   522  
   523  func (t *task) LoadProcess(ctx context.Context, id string, ioAttach cio.Attach) (Process, error) {
   524  	if id == t.id && ioAttach == nil {
   525  		return t, nil
   526  	}
   527  	response, err := t.client.TaskService().Get(ctx, &tasks.GetRequest{
   528  		ContainerID: t.id,
   529  		ExecID:      id,
   530  	})
   531  	if err != nil {
   532  		err = errdefs.FromGRPC(err)
   533  		if errdefs.IsNotFound(err) {
   534  			return nil, errors.Wrapf(err, "no running process found")
   535  		}
   536  		return nil, err
   537  	}
   538  	var i cio.IO
   539  	if ioAttach != nil {
   540  		if i, err = attachExistingIO(response, ioAttach); err != nil {
   541  			return nil, err
   542  		}
   543  	}
   544  	return &process{
   545  		id:   id,
   546  		task: t,
   547  		io:   i,
   548  	}, nil
   549  }
   550  
   551  func (t *task) Metrics(ctx context.Context) (*types.Metric, error) {
   552  	response, err := t.client.TaskService().Metrics(ctx, &tasks.MetricsRequest{
   553  		Filters: []string{
   554  			"id==" + t.id,
   555  		},
   556  	})
   557  	if err != nil {
   558  		return nil, errdefs.FromGRPC(err)
   559  	}
   560  
   561  	if response.Metrics == nil {
   562  		_, err := t.Status(ctx)
   563  		if err != nil && errdefs.IsNotFound(err) {
   564  			return nil, err
   565  		}
   566  		return nil, errors.New("no metrics received")
   567  	}
   568  
   569  	return response.Metrics[0], nil
   570  }
   571  
   572  func (t *task) checkpointTask(ctx context.Context, index *v1.Index, request *tasks.CheckpointTaskRequest) error {
   573  	response, err := t.client.TaskService().Checkpoint(ctx, request)
   574  	if err != nil {
   575  		return errdefs.FromGRPC(err)
   576  	}
   577  	// NOTE: response.Descriptors can be an empty slice if checkpoint image is jumped
   578  	// add the checkpoint descriptors to the index
   579  	for _, d := range response.Descriptors {
   580  		index.Manifests = append(index.Manifests, v1.Descriptor{
   581  			MediaType: d.MediaType,
   582  			Size:      d.Size_,
   583  			Digest:    d.Digest,
   584  			Platform: &v1.Platform{
   585  				OS:           goruntime.GOOS,
   586  				Architecture: goruntime.GOARCH,
   587  			},
   588  			Annotations: d.Annotations,
   589  		})
   590  	}
   591  	return nil
   592  }
   593  
   594  func (t *task) checkpointRWSnapshot(ctx context.Context, index *v1.Index, snapshotterName string, id string) error {
   595  	opts := []diff.Opt{
   596  		diff.WithReference(fmt.Sprintf("checkpoint-rw-%s", id)),
   597  	}
   598  	rw, err := rootfs.CreateDiff(ctx, id, t.client.SnapshotService(snapshotterName), t.client.DiffService(), opts...)
   599  	if err != nil {
   600  		return err
   601  	}
   602  	rw.Platform = &v1.Platform{
   603  		OS:           goruntime.GOOS,
   604  		Architecture: goruntime.GOARCH,
   605  	}
   606  	index.Manifests = append(index.Manifests, rw)
   607  	return nil
   608  }
   609  
   610  func (t *task) checkpointImage(ctx context.Context, index *v1.Index, image string) error {
   611  	if image == "" {
   612  		return fmt.Errorf("cannot checkpoint image with empty name")
   613  	}
   614  	ir, err := t.client.ImageService().Get(ctx, image)
   615  	if err != nil {
   616  		return err
   617  	}
   618  	index.Manifests = append(index.Manifests, ir.Target)
   619  	return nil
   620  }
   621  
   622  func (t *task) writeIndex(ctx context.Context, index *v1.Index) (d v1.Descriptor, err error) {
   623  	labels := map[string]string{}
   624  	for i, m := range index.Manifests {
   625  		labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = m.Digest.String()
   626  	}
   627  	buf := bytes.NewBuffer(nil)
   628  	if err := json.NewEncoder(buf).Encode(index); err != nil {
   629  		return v1.Descriptor{}, err
   630  	}
   631  	return writeContent(ctx, t.client.ContentStore(), v1.MediaTypeImageIndex, t.id, buf, content.WithLabels(labels))
   632  }
   633  
   634  func writeContent(ctx context.Context, store content.Ingester, mediaType, ref string, r io.Reader, opts ...content.Opt) (d v1.Descriptor, err error) {
   635  	writer, err := store.Writer(ctx, content.WithRef(ref))
   636  	if err != nil {
   637  		return d, err
   638  	}
   639  	defer writer.Close()
   640  	size, err := io.Copy(writer, r)
   641  	if err != nil {
   642  		return d, err
   643  	}
   644  
   645  	if err := writer.Commit(ctx, size, "", opts...); err != nil {
   646  		if !errdefs.IsAlreadyExists(err) {
   647  			return d, err
   648  		}
   649  	}
   650  	return v1.Descriptor{
   651  		MediaType: mediaType,
   652  		Digest:    writer.Digest(),
   653  		Size:      size,
   654  	}, nil
   655  }
   656  
   657  // isCheckpointPathExist only suitable for runc runtime now
   658  func isCheckpointPathExist(runtime string, v interface{}) bool {
   659  	if v == nil {
   660  		return false
   661  	}
   662  
   663  	switch runtime {
   664  	case plugin.RuntimeRuncV1, plugin.RuntimeRuncV2:
   665  		if opts, ok := v.(*options.CheckpointOptions); ok && opts.ImagePath != "" {
   666  			return true
   667  		}
   668  
   669  	case plugin.RuntimeLinuxV1:
   670  		if opts, ok := v.(*runctypes.CheckpointOptions); ok && opts.ImagePath != "" {
   671  			return true
   672  		}
   673  	}
   674  
   675  	return false
   676  }