diff --git a/pkg/image/fetcher.go b/pkg/image/fetcher.go index ff9b2d18..e95fe796 100644 --- a/pkg/image/fetcher.go +++ b/pkg/image/fetcher.go @@ -257,6 +257,17 @@ func (f *Fetcher) FetchForPlatform(ctx context.Context, name string, options Fet return nil, err } + platformStr := options.Target.ValuesAsPlatform() + + // When PullPolicy is PullNever, skip platform-specific digest resolution as it requires + // network access to fetch the manifest list. Instead, use the image as-is from the daemon. + // Note: This may cause issues with containerd storage. Users should pre-pull the platform-specific + // digest if they encounter errors. + if options.Daemon && options.PullPolicy == PullNever { + f.logger.Debugf("Using lifecycle %s with platform %s (skipping digest resolution due to --pull-policy never)", name, platformStr) + return f.Fetch(ctx, name, options) + } + // Build platform and registry settings from options platform := imgutil.Platform{ OS: options.Target.OS, @@ -275,7 +286,6 @@ func (f *Fetcher) FetchForPlatform(ctx context.Context, name string, options Fet } // Log the resolution for visibility - platformStr := options.Target.ValuesAsPlatform() f.logger.Debugf("Using lifecycle %s; pulling digest %s for platform %s", name, resolvedName, platformStr) return f.Fetch(ctx, resolvedName, options) diff --git a/pkg/image/fetcher_test.go b/pkg/image/fetcher_test.go index 012914e0..2e6ff7c2 100644 --- a/pkg/image/fetcher_test.go +++ b/pkg/image/fetcher_test.go @@ -732,6 +732,49 @@ func testFetcher(t *testing.T, when spec.G, it spec.S) { h.AssertError(t, err, "") }) }) + + when("pull policy is PullNever with daemon", func() { + var localImageName string + + it.Before(func() { + // Use a different name for the local image to avoid conflicts + localImageName = "pack.local/test-" + h.RandString(10) + + // Create a local daemon image with platform information + // Use osType (daemon OS) instead of runtime.GOOS to handle cases where + // Windows runner is running Linux containers + img, err := local.NewImage(localImageName, docker, local.WithDefaultPlatform(imgutil.Platform{ + OS: osType, + Architecture: runtime.GOARCH, + })) + h.AssertNil(t, err) + h.AssertNil(t, img.Save()) + }) + + it.After(func() { + h.DockerRmi(docker, localImageName) + }) + + it("skips platform-specific digest resolution and uses tag directly", func() { + target := dist.Target{ + OS: osType, + Arch: runtime.GOARCH, + } + + fetchedImg, err := imageFetcher.FetchForPlatform(context.TODO(), localImageName, image.FetchOptions{ + Daemon: true, + PullPolicy: image.PullNever, + Target: &target, + }) + + // Should succeed without network access (digest resolution skipped) + h.AssertNil(t, err) + h.AssertNotNil(t, fetchedImg) + + // Verify debug message about skipping digest resolution + h.AssertContains(t, outBuf.String(), "skipping digest resolution due to --pull-policy never") + }) + }) }) when("#CheckReadAccess", func() {