diff --git a/cmd/nerdctl/image/image_save.go b/cmd/nerdctl/image/image_save.go index 79c0c9cfd0a..16c8ee30951 100644 --- a/cmd/nerdctl/image/image_save.go +++ b/cmd/nerdctl/image/image_save.go @@ -42,6 +42,7 @@ func SaveCommand() *cobra.Command { SilenceErrors: true, } cmd.Flags().StringP("output", "o", "", "Write to a file, instead of STDOUT") + cmd.Flags().Bool("skip-verify", false, "Skip verification of remote layers. Use this when the original registry is unavailable but you have all layers locally.") // #region platform flags // platform is defined as StringSlice, not StringArray, to allow specifying "--platform=amd64,arm64" @@ -67,11 +68,16 @@ func saveOptions(cmd *cobra.Command) (types.ImageSaveOptions, error) { if err != nil { return types.ImageSaveOptions{}, err } + skipVerify, err := cmd.Flags().GetBool("skip-verify") + if err != nil { + return types.ImageSaveOptions{}, err + } return types.ImageSaveOptions{ GOptions: globalOptions, AllPlatforms: allPlatforms, Platform: platform, + SkipVerify: skipVerify, }, err } diff --git a/cmd/nerdctl/image/image_save_test.go b/cmd/nerdctl/image/image_save_test.go index 7b97f523761..391c0a10f9b 100644 --- a/cmd/nerdctl/image/image_save_test.go +++ b/cmd/nerdctl/image/image_save_test.go @@ -66,6 +66,23 @@ func TestSaveContent(t *testing.T) { testCase.Run(t) } +func TestSaveSkipVerify(t *testing.T) { + nerdtest.Setup() + + testCase := &test.Case{ + Require: require.Not(require.Windows), + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("pull", "--quiet", testutil.CommonImage) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("save", "--skip-verify", "-o", filepath.Join(data.Temp().Path(), "out.tar"), testutil.CommonImage) + }, + Expected: test.Expects(0, nil, nil), + } + + testCase.Run(t) +} + func TestSave(t *testing.T) { testCase := nerdtest.Setup() diff --git a/pkg/api/types/image_types.go b/pkg/api/types/image_types.go index 0ceb3148896..64f228f25d4 100644 --- a/pkg/api/types/image_types.go +++ b/pkg/api/types/image_types.go @@ -274,6 +274,8 @@ type ImageSaveOptions struct { AllPlatforms bool // Export content for a specific platform Platform []string + // Skip verification of remote layers + SkipVerify bool } // ImageSignOptions contains options for signing an image. It contains options from diff --git a/pkg/cmd/image/save.go b/pkg/cmd/image/save.go index 0a499b3f135..37ba30b8cfb 100644 --- a/pkg/cmd/image/save.go +++ b/pkg/cmd/image/save.go @@ -22,6 +22,7 @@ import ( containerd "github.com/containerd/containerd/v2/client" "github.com/containerd/containerd/v2/core/images/archive" + "github.com/containerd/log" "github.com/containerd/nerdctl/v2/pkg/api/types" "github.com/containerd/nerdctl/v2/pkg/idutil/imagewalker" @@ -50,9 +51,13 @@ func Save(ctx context.Context, client *containerd.Client, images []string, optio } // Ensure all the layers are here: https://github.com/containerd/nerdctl/issues/3425 - err = EnsureAllContent(ctx, client, found.Image.Name, platMC, options.GOptions) - if err != nil { - return err + if !options.SkipVerify { + err = EnsureAllContent(ctx, client, found.Image.Name, platMC, options.GOptions) + if err != nil { + return err + } + } else { + log.G(ctx).Info("Skipping remote layer verification (--skip-verify enabled)") } imgName := found.Image.Name diff --git a/pkg/cmd/image/tag.go b/pkg/cmd/image/tag.go index 60ab191d4f7..5301f8231b1 100644 --- a/pkg/cmd/image/tag.go +++ b/pkg/cmd/image/tag.go @@ -24,7 +24,6 @@ import ( "github.com/containerd/containerd/v2/core/images" "github.com/containerd/errdefs" "github.com/containerd/log" - "github.com/containerd/nerdctl/v2/pkg/api/types" "github.com/containerd/nerdctl/v2/pkg/idutil/imagewalker" "github.com/containerd/nerdctl/v2/pkg/platformutil" @@ -70,8 +69,8 @@ func Tag(ctx context.Context, client *containerd.Client, options types.ImageTagO err = EnsureAllContent(ctx, client, srcName, platMC, options.GOptions) if err != nil { - log.G(ctx).Warn("Unable to fetch missing layers before committing. " + - "If you try to save or push this image, it might fail. See https://github.com/containerd/nerdctl/issues/3439.") + log.G(ctx).Warn("Unable to verify all image layers are present locally (this does not affect the tag operation). " + + "If you later save or push this image, some layers may need to be re-downloaded. See https://github.com/containerd/nerdctl/issues/3439.") } img, err := imageService.Get(ctx, srcName) @@ -92,5 +91,8 @@ func Tag(ctx context.Context, client *containerd.Client, options types.ImageTagO return err } } + + // Log successful tag operation + log.G(ctx).Infof("Successfully tagged %s", parsedReference.String()) return nil }