From 96ef787f115fe19c4d8fcbea991a1e87cb21da96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=81=E6=98=8E=E6=99=9310296073?= Date: Thu, 7 May 2026 21:03:08 +0800 Subject: [PATCH] fix: fix load image failed Signed-off-by: ningmingxiao --- cmd/nerdctl/image/image_save.go | 12 ++- pkg/imgutil/load/load.go | 28 ++----- pkg/transferutil/progress.go | 127 +++++++++++++++++++++++++++++++- 3 files changed, 142 insertions(+), 25 deletions(-) diff --git a/cmd/nerdctl/image/image_save.go b/cmd/nerdctl/image/image_save.go index 79c0c9cfd0a..89a1eac1e5a 100644 --- a/cmd/nerdctl/image/image_save.go +++ b/cmd/nerdctl/image/image_save.go @@ -17,12 +17,15 @@ package image import ( + "context" "fmt" "os" "github.com/mattn/go-isatty" "github.com/spf13/cobra" + "github.com/containerd/log" + "github.com/containerd/nerdctl/v2/cmd/nerdctl/completion" "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" "github.com/containerd/nerdctl/v2/pkg/api/types" @@ -91,7 +94,14 @@ func saveAction(cmd *cobra.Command, args []string) error { return err } output = f - defer f.Close() + defer func() { + if err := f.Sync(); err != nil { + f.Close() + log.G(context.Background()).Error(err) + return + } + f.Close() + }() } else if out, ok := output.(*os.File); ok && isatty.IsTerminal(out.Fd()) { return fmt.Errorf("cowardly refusing to save to a terminal. Use the -o flag or redirect") } diff --git a/pkg/imgutil/load/load.go b/pkg/imgutil/load/load.go index c8b4c629a56..883d6015378 100644 --- a/pkg/imgutil/load/load.go +++ b/pkg/imgutil/load/load.go @@ -22,6 +22,7 @@ import ( "fmt" "os" "strings" + "time" containerd "github.com/containerd/containerd/v2/client" "github.com/containerd/containerd/v2/core/images" @@ -82,35 +83,16 @@ func FromArchive(ctx context.Context, client *containerd.Client, options types.I storeOpts = append(storeOpts, transferimage.WithPlatforms(platUnpack)) } storeOpts = append(storeOpts, transferimage.WithUnpack(platUnpack, options.GOptions.Snapshotter)) - storeOpts = append(storeOpts, transferimage.WithDigestRef("import", true, true)) - - var loadedImages []images.Image - pf, done := transferutil.ProgressHandler(ctx, options.Stdout) + storeOpts = append(storeOpts, transferimage.WithNamedPrefix(fmt.Sprintf("import-%s", time.Now().Format("2006-01-02")), true)) + pf, done, loadedImages := transferutil.ProgressHandlerLoadImage(ctx, client, beforeSet, options) err = client.Transfer(ctx, tarchive.NewImageImportStream(options.Stdin, ""), transferimage.NewStore("", storeOpts...), - transfer.WithProgress(func(p transfer.Progress) { - if p.Event == "saved" { - if img, err := imageService.Get(ctx, p.Name); err == nil { - if !beforeSet[img.Name] { - loadedImages = append(loadedImages, img) - } - } - } - pf(p) - }), + transfer.WithProgress(pf), ) - done() - - if !options.Quiet { - for _, img := range loadedImages { - fmt.Fprintf(options.Stdout, "Loaded image: %s\n", img.Name) - } - } - - return loadedImages, err + return *loadedImages, err } // FromOCIArchive loads and unpacks the images from the OCI formatted archive at the provided file system path. diff --git a/pkg/transferutil/progress.go b/pkg/transferutil/progress.go index 15baf5d508d..a84aa97aaef 100644 --- a/pkg/transferutil/progress.go +++ b/pkg/transferutil/progress.go @@ -25,8 +25,12 @@ import ( ocispec "github.com/opencontainers/image-spec/specs-go/v1" + containerd "github.com/containerd/containerd/v2/client" + "github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/v2/core/transfer" "github.com/containerd/containerd/v2/pkg/progress" + + "github.com/containerd/nerdctl/v2/pkg/api/types" ) // From https://github.com/containerd/containerd/blob/v2.2.0-rc.0/cmd/ctr/commands/image/pull.go#L240-L473 @@ -156,6 +160,127 @@ func ProgressHandler(ctx context.Context, out io.Writer) (transfer.ProgressFunc, return progressFn, done } +func ProgressHandlerLoadImage(ctx context.Context, client *containerd.Client, beforeSet map[string]bool, options types.ImageLoadOptions) (transfer.ProgressFunc, func(), *[]images.Image) { + ctx, cancel := context.WithCancel(ctx) + var ( + fw = progress.NewWriter(options.Stdout) + start = time.Now() + statuses = map[string]*progressNode{} + roots = []*progressNode{} + pc = make(chan transfer.Progress, 5) + status string + closeC = make(chan struct{}) + loadedImages []images.Image + imagesDisplay []string + ) + + result := &loadedImages + progressFn := func(p transfer.Progress) { + select { + case pc <- p: + case <-ctx.Done(): + } + } + + done := func() { + cancel() + <-closeC + if !options.Quiet { + for _, img := range imagesDisplay { + fmt.Fprintf(options.Stdout, "Loaded image: %s\n", img) + } + } + } + + go func() { + defer close(closeC) + for { + select { + case p := <-pc: + if p.Name == "" { + status = p.Event + continue + } + if p.Event == "saved" { + if img, err := client.ImageService().Get(ctx, p.Name); err == nil { + if !beforeSet[img.Name] { + loadedImages = append(loadedImages, img) + } + imagesDisplay = append(imagesDisplay, p.Name) + } + } + if node, ok := statuses[p.Name]; !ok { + node = &progressNode{ + Progress: p, + root: true, + } + if len(p.Parents) == 0 { + roots = append(roots, node) + } else { + var parents []string + for _, parent := range p.Parents { + pStatus, ok := statuses[parent] + if ok { + parents = append(parents, parent) + pStatus.children = append(pStatus.children, node) + node.root = false + } + } + node.Progress.Parents = parents + if node.root { + roots = append(roots, node) + } + } + statuses[p.Name] = node + } else { + if len(node.Progress.Parents) != len(p.Parents) { + var parents []string + var removeRoot bool + for _, parent := range p.Parents { + pStatus, ok := statuses[parent] + if ok { + parents = append(parents, parent) + var found bool + for _, child := range pStatus.children { + if child.Progress.Name == p.Name { + found = true + break + } + } + if !found { + pStatus.children = append(pStatus.children, node) + } + if node.root { + removeRoot = true + } + node.root = false + } + } + p.Parents = parents + // Check if needs to remove from root + if removeRoot { + for i := range roots { + if roots[i] == node { + roots = append(roots[:i], roots[i+1:]...) + break + } + } + } + } + node.Progress = p + } + + displayHierarchy(fw, status, roots, start) + fw.Flush() + + case <-ctx.Done(): + return + } + } + }() + return progressFn, done, result +} + func displayHierarchy(w io.Writer, status string, roots []*progressNode, start time.Time) { total := displayNode(w, "", roots) for _, r := range roots { @@ -207,7 +332,7 @@ func displayNode(w io.Writer, prefix string, nodes []*progressNode) int64 { status.Event, bar) default: - fmt.Fprintf(w, "%-40.40s\t%s\t\n", + fmt.Fprintf(w, "%s\t%s\t\n", name, status.Event) }