@@ -19,6 +19,7 @@ package taskutil
1919import (
2020 "context"
2121 "errors"
22+ "fmt"
2223 "io"
2324 "net/url"
2425 "os"
@@ -27,13 +28,20 @@ import (
2728 "strings"
2829 "sync"
2930 "syscall"
31+ "time"
3032
3133 "github.com/Masterminds/semver/v3"
34+ "github.com/opencontainers/go-digest"
3235 "golang.org/x/term"
3336
3437 "github.com/containerd/console"
38+ "github.com/containerd/containerd/api/types"
3539 containerd "github.com/containerd/containerd/v2/client"
40+ "github.com/containerd/containerd/v2/core/content"
41+ "github.com/containerd/containerd/v2/core/images"
42+ "github.com/containerd/containerd/v2/pkg/archive"
3643 "github.com/containerd/containerd/v2/pkg/cio"
44+ "github.com/containerd/errdefs"
3745 "github.com/containerd/log"
3846
3947 "github.com/containerd/nerdctl/v2/pkg/cioutil"
@@ -43,9 +51,50 @@ import (
4351
4452// NewTask is from https://github.com/containerd/containerd/blob/v1.4.3/cmd/ctr/commands/tasks/tasks_unix.go#L70-L108
4553func NewTask (ctx context.Context , client * containerd.Client , container containerd.Container ,
46- attachStreamOpt []string , isInteractive , isTerminal , isDetach bool , con console.Console , logURI , detachKeys , namespace string , detachC chan <- struct {}) (containerd.Task , error ) {
54+ attachStreamOpt []string , isInteractive , isTerminal , isDetach bool , con console.Console , logURI , detachKeys , namespace string , detachC chan <- struct {}, checkpointDir string ) (containerd.Task , error ) {
55+ var (
56+ checkpoint * types.Descriptor
57+ t containerd.Task
58+ err error
59+ )
4760
48- var t containerd.Task
61+ if checkpointDir != "" {
62+ tar := archive .Diff (ctx , "" , checkpointDir )
63+ cs := client .ContentStore ()
64+ writer , err := cs .Writer (ctx , content .WithRef (checkpointDir ))
65+ if err != nil {
66+ return nil , err
67+ }
68+ defer writer .Close ()
69+ size , err := io .Copy (writer , tar )
70+ if err != nil {
71+ return nil , err
72+ }
73+ labels := map [string ]string {
74+ "containerd.io/gc.root" : time .Now ().UTC ().Format (time .RFC3339 ),
75+ }
76+ if err = writer .Commit (ctx , size , "" , content .WithLabels (labels )); err != nil {
77+ if ! errors .Is (err , errdefs .ErrAlreadyExists ) {
78+ return nil , err
79+ }
80+ }
81+ checkpoint = & types.Descriptor {
82+ MediaType : images .MediaTypeContainerd1Checkpoint ,
83+ Digest : writer .Digest ().String (),
84+ Size : size ,
85+ }
86+ defer func () {
87+ if checkpoint != nil {
88+ _ = cs .Delete (ctx , digest .Digest (checkpoint .Digest ))
89+ }
90+ }()
91+ if err = tar .Close (); err != nil {
92+ return nil , fmt .Errorf ("failed to close checkpoint tar stream: %w" , err )
93+ }
94+ if err != nil {
95+ return nil , fmt .Errorf ("failed to upload checkpoint to containerd: %w" , err )
96+ }
97+ }
4998 closer := func () {
5099 if detachC != nil {
51100 detachC <- struct {}{}
@@ -158,7 +207,15 @@ func NewTask(ctx context.Context, client *containerd.Client, container container
158207 }
159208 ioCreator = cioutil .NewContainerIO (namespace , logURI , false , in , os .Stdout , os .Stderr )
160209 }
161- t , err := container .NewTask (ctx , ioCreator )
210+
211+ taskOpts := []containerd.NewTaskOpts {
212+ func (_ context.Context , _ * containerd.Client , info * containerd.TaskInfo ) error {
213+ info .Checkpoint = checkpoint
214+ return nil
215+ },
216+ }
217+
218+ t , err = container .NewTask (ctx , ioCreator , taskOpts ... )
162219 if err != nil {
163220 return nil , err
164221 }
0 commit comments