diff --git a/client/client_test.go b/client/client_test.go index 8f59bed92..da193c303 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -256,6 +256,7 @@ var allTests = []func(t *testing.T, sb integration.Sandbox){ testHTTPPruneAfterResolveMeta, testHTTPResolveMetaReuse, testHTTPResolveMultiBuild, + testImageResolveConfigDefaultLocalFallback, testGitResolveMutatedSource, testGitBundleRoundTrip, testGitBundleRoundTripRegistry, @@ -13107,6 +13108,80 @@ func testHTTPResolveSourceMetadata(t *testing.T, sb integration.Sandbox) { require.NoError(t, err) } +func testImageResolveConfigDefaultLocalFallback(t *testing.T, sb integration.Sandbox) { + workers.CheckFeatureCompat(t, sb, workers.FeatureImageExporter) + + cdAddress := sb.ContainerdAddress() + if cdAddress == "" { + t.Skip("test requires containerd worker") + } + + ctx := sb.Context() + c, err := New(ctx, sb.Address()) + require.NoError(t, err) + defer c.Close() + + cdClient, err := newContainerd(cdAddress) + require.NoError(t, err) + defer cdClient.Close() + + registry, err := sb.NewRegistry() + if errors.Is(err, integration.ErrRequirements) { + t.Skip(err.Error()) + } + require.NoError(t, err) + + target := registry + "/buildkit/default-local-fallback:" + identity.NewID() + st := integration.UnixOrWindows( + llb.Scratch(), + llb.Image("nanoserver:latest"), + ) + def, err := st.File(llb.Mkfile("/fallback", 0600, []byte("local"))).Marshal(ctx) + require.NoError(t, err) + + _, err = c.Solve(ctx, def, SolveOpt{ + Exports: []ExportEntry{ + { + Type: ExporterImage, + Attrs: map[string]string{ + "name": target, + "store": "true", + }, + }, + }, + }, nil) + require.NoError(t, err) + + imageService := cdClient.ImageService() + imageStoreCtx := namespaces.WithNamespace(ctx, "buildkit") + img, err := imageService.Get(imageStoreCtx, target) + require.NoError(t, err) + + _, err = c.Build(ctx, SolveOpt{}, "buildkit_test", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) { + ref, dgst, config, err := c.ResolveImageConfig(ctx, target, sourceresolver.Opt{ + ImageOpt: &sourceresolver.ResolveImageOpt{ + ResolveMode: pb.AttrImageResolveModeDefault, + }, + }) + if err != nil { + return nil, err + } + require.Equal(t, target, ref) + require.Equal(t, img.Target.Digest, dgst) + require.NotEmpty(t, config) + + var ociimg ocispecs.Image + require.NoError(t, json.Unmarshal(config, &ociimg)) + require.NotEmpty(t, ociimg.OS) + require.NotEmpty(t, ociimg.Architecture) + + return gateway.NewResult(), nil + }, nil) + require.NoError(t, err) + + checkAllReleasable(t, c, sb, true) +} + func testImageResolveAttestationChainRequiresNetwork(t *testing.T, sb integration.Sandbox) { workers.CheckFeatureCompat(t, sb, workers.FeatureMultiPlatform, workers.FeatureProvenance) // this test temporarily requires direct registry access as the integration test diff --git a/source/containerimage/source.go b/source/containerimage/source.go index b752dc75b..ae3138643 100644 --- a/source/containerimage/source.go +++ b/source/containerimage/source.go @@ -189,7 +189,18 @@ func (is *Source) ResolveImageMetadata(ctx context.Context, id *ImageIdentifier, res, err := is.gImageRes.Do(ctx, key, func(ctx context.Context) (*resolveImageResult, error) { dgst, dt, err := imageutil.Config(ctx, ref, rslvr, is.ContentStore, is.LeaseManager, opt.Platform) if err != nil { - return nil, err + if rm != resolver.ResolveModeDefault || is.ImageStore == nil { + return nil, err + } + localRslvr := rslvr.WithImageStore(is.ImageStore, resolver.ResolveModePreferLocal) + if _, _, localErr := localRslvr.ResolveLocal(ctx, ref); localErr != nil { + return nil, err + } + localDgst, localDt, localErr := imageutil.Config(ctx, ref, localRslvr, is.ContentStore, is.LeaseManager, opt.Platform) + if localErr != nil { + return nil, err + } + dgst, dt = localDgst, localDt } return &resolveImageResult{dgst: dgst, dt: dt}, nil }) diff --git a/util/resolver/pool.go b/util/resolver/pool.go index 06d8be655..4cfbd0cf1 100644 --- a/util/resolver/pool.go +++ b/util/resolver/pool.go @@ -230,6 +230,18 @@ func (r *Resolver) WithImageStore(is images.Store, mode ResolveMode) *Resolver { return &r2 } +// ResolveLocal attempts to resolve the reference from the local image store. +func (r *Resolver) ResolveLocal(ctx context.Context, ref string) (string, ocispecs.Descriptor, error) { + if r.is == nil { + return "", ocispecs.Descriptor{}, errors.WithStack(cerrdefs.ErrNotFound) + } + img, err := getImageByRef(ctx, r.is, ref) + if err != nil { + return "", ocispecs.Descriptor{}, err + } + return ref, img.Target, nil +} + // Fetcher returns a new fetcher for the provided reference. func (r *Resolver) Fetcher(ctx context.Context, ref string) (remotes.Fetcher, error) { if r.handler.counter.Load() == 0 { @@ -241,8 +253,8 @@ func (r *Resolver) Fetcher(ctx context.Context, ref string) (remotes.Fetcher, er // Resolve attempts to resolve the reference into a name and descriptor. func (r *Resolver) Resolve(ctx context.Context, ref string) (string, ocispecs.Descriptor, error) { if r.mode == ResolveModePreferLocal && r.is != nil { - if img, err := getImageByRef(ctx, r.is, ref); err == nil { - return ref, img.Target, nil + if ref, desc, err := r.ResolveLocal(ctx, ref); err == nil { + return ref, desc, nil } } @@ -253,8 +265,8 @@ func (r *Resolver) Resolve(ctx context.Context, ref string) (string, ocispecs.De } if r.mode == ResolveModeDefault && r.is != nil { - if img, err := getImageByRef(ctx, r.is, ref); err == nil { - return ref, img.Target, nil + if ref, desc, err := r.ResolveLocal(ctx, ref); err == nil { + return ref, desc, nil } }