@@ -25,8 +25,12 @@ import (
2525
2626 ocispec "github.com/opencontainers/image-spec/specs-go/v1"
2727
28+ containerd "github.com/containerd/containerd/v2/client"
29+ "github.com/containerd/containerd/v2/core/images"
2830 "github.com/containerd/containerd/v2/core/transfer"
2931 "github.com/containerd/containerd/v2/pkg/progress"
32+
33+ "github.com/containerd/nerdctl/v2/pkg/api/types"
3034)
3135
3236// From https://github.com/containerd/containerd/blob/v2.2.0-rc.0/cmd/ctr/commands/image/pull.go#L240-L473
@@ -156,6 +160,125 @@ func ProgressHandler(ctx context.Context, out io.Writer) (transfer.ProgressFunc,
156160 return progressFn , done
157161}
158162
163+ func ProgressHandlerLoadImage (ctx context.Context , client * containerd.Client , beforeSet map [string ]bool , options types.ImageLoadOptions ) (transfer.ProgressFunc , func (), []images.Image ) {
164+ ctx , cancel := context .WithCancel (ctx )
165+ var (
166+ fw = progress .NewWriter (options .Stdout )
167+ start = time .Now ()
168+ statuses = map [string ]* progressNode {}
169+ roots = []* progressNode {}
170+ pc = make (chan transfer.Progress , 5 )
171+ status string
172+ closeC = make (chan struct {})
173+ loadedImages []images.Image
174+ imagesDisplay []string
175+ )
176+ progressFn := func (p transfer.Progress ) {
177+ select {
178+ case pc <- p :
179+ case <- ctx .Done ():
180+ }
181+ }
182+
183+ done := func () {
184+ cancel ()
185+ <- closeC
186+ if ! options .Quiet {
187+ for _ , img := range imagesDisplay {
188+ fmt .Fprintf (options .Stdout , "Loaded image: %s\n " , img )
189+ }
190+ }
191+ }
192+
193+ go func () {
194+ defer close (closeC )
195+ for {
196+ select {
197+ case p := <- pc :
198+ if p .Name == "" {
199+ status = p .Event
200+ continue
201+ }
202+ if p .Event == "saved" {
203+ if img , err := client .ImageService ().Get (ctx , p .Name ); err == nil {
204+ if ! beforeSet [img .Name ] {
205+ loadedImages = append (loadedImages , img )
206+ }
207+ imagesDisplay = append (imagesDisplay , img .Name )
208+ }
209+ }
210+ if node , ok := statuses [p .Name ]; ! ok {
211+ node = & progressNode {
212+ Progress : p ,
213+ root : true ,
214+ }
215+ if len (p .Parents ) == 0 {
216+ roots = append (roots , node )
217+ } else {
218+ var parents []string
219+ for _ , parent := range p .Parents {
220+ pStatus , ok := statuses [parent ]
221+ if ok {
222+ parents = append (parents , parent )
223+ pStatus .children = append (pStatus .children , node )
224+ node .root = false
225+ }
226+ }
227+ node .Progress .Parents = parents
228+ if node .root {
229+ roots = append (roots , node )
230+ }
231+ }
232+ statuses [p .Name ] = node
233+ } else {
234+ if len (node .Progress .Parents ) != len (p .Parents ) {
235+ var parents []string
236+ var removeRoot bool
237+ for _ , parent := range p .Parents {
238+ pStatus , ok := statuses [parent ]
239+ if ok {
240+ parents = append (parents , parent )
241+ var found bool
242+ for _ , child := range pStatus .children {
243+ if child .Progress .Name == p .Name {
244+ found = true
245+ break
246+ }
247+ }
248+ if ! found {
249+ pStatus .children = append (pStatus .children , node )
250+ }
251+ if node .root {
252+ removeRoot = true
253+ }
254+ node .root = false
255+ }
256+ }
257+ p .Parents = parents
258+ // Check if needs to remove from root
259+ if removeRoot {
260+ for i := range roots {
261+ if roots [i ] == node {
262+ roots = append (roots [:i ], roots [i + 1 :]... )
263+ break
264+ }
265+ }
266+ }
267+ }
268+ node .Progress = p
269+ }
270+
271+ displayHierarchy (fw , status , roots , start )
272+ fw .Flush ()
273+
274+ case <- ctx .Done ():
275+ return
276+ }
277+ }
278+ }()
279+ return progressFn , done , loadedImages
280+ }
281+
159282func displayHierarchy (w io.Writer , status string , roots []* progressNode , start time.Time ) {
160283 total := displayNode (w , "" , roots )
161284 for _ , r := range roots {
0 commit comments