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