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