Skip to content

Commit e6d9fcd

Browse files
authored
Merge pull request #6293 from tonistiigi/add-resolvercache
solver: add ResolverCache support
2 parents bc3666b + 1cff633 commit e6d9fcd

File tree

9 files changed

+858
-20
lines changed

9 files changed

+858
-20
lines changed

client/client_test.go

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,9 @@ var allTests = []func(t *testing.T, sb integration.Sandbox){
246246
testHTTPResolveSourceMetadata,
247247
testHTTPPruneAfterCacheKey,
248248
testHTTPPruneAfterResolveMeta,
249+
testHTTPResolveMetaReuse,
250+
testHTTPResolveMultiBuild,
251+
testGitResolveMutatedSource,
249252
}
250253

251254
func TestIntegration(t *testing.T) {
@@ -12270,6 +12273,265 @@ func testHTTPPruneAfterResolveMeta(t *testing.T, sb integration.Sandbox) {
1227012273
checkAllReleasable(t, c, sb, false)
1227112274
}
1227212275

12276+
func testHTTPResolveMetaReuse(t *testing.T, sb integration.Sandbox) {
12277+
// the difference with testHTTPPruneAfterResolveMeta is that here we change content with the etag on the server
12278+
// but because the URL was already resolved once, the new content should not be seen
12279+
ctx := sb.Context()
12280+
c, err := New(ctx, sb.Address())
12281+
require.NoError(t, err)
12282+
defer c.Close()
12283+
12284+
resp := &httpserver.Response{
12285+
Etag: identity.NewID(),
12286+
Content: []byte("content1"),
12287+
}
12288+
server := httpserver.NewTestServer(map[string]*httpserver.Response{
12289+
"/foo": resp,
12290+
})
12291+
defer server.Close()
12292+
12293+
dest := t.TempDir()
12294+
_, err = c.Build(ctx, SolveOpt{
12295+
Exports: []ExportEntry{
12296+
{
12297+
Type: ExporterLocal,
12298+
OutputDir: dest,
12299+
},
12300+
},
12301+
}, "test", func(ctx context.Context, gc gateway.Client) (*gateway.Result, error) {
12302+
id := server.URL + "/foo"
12303+
md, err := gc.ResolveSourceMetadata(ctx, &pb.SourceOp{
12304+
Identifier: id,
12305+
}, sourceresolver.Opt{})
12306+
if err != nil {
12307+
return nil, err
12308+
}
12309+
require.NotNil(t, md.HTTP)
12310+
12311+
resp.Etag = identity.NewID()
12312+
resp.Content = []byte("content2") // etag changed so new content would be returned if re-resolving
12313+
12314+
st := llb.Scratch().File(llb.Copy(llb.HTTP(id), "foo", "bar"))
12315+
def, err := st.Marshal(sb.Context())
12316+
if err != nil {
12317+
return nil, err
12318+
}
12319+
return gc.Solve(ctx, gateway.SolveRequest{
12320+
Definition: def.ToPB(),
12321+
})
12322+
}, nil)
12323+
require.NoError(t, err)
12324+
12325+
dt, err := os.ReadFile(filepath.Join(dest, "bar"))
12326+
require.NoError(t, err)
12327+
require.Equal(t, "content1", string(dt))
12328+
}
12329+
12330+
// testHTTPResolveMultiBuild is a negative test for testHTTPResolveMetaReuse to ensure that
12331+
// URLs are resolved in between separate builds
12332+
func testHTTPResolveMultiBuild(t *testing.T, sb integration.Sandbox) {
12333+
ctx := sb.Context()
12334+
c, err := New(ctx, sb.Address())
12335+
require.NoError(t, err)
12336+
defer c.Close()
12337+
12338+
resp := &httpserver.Response{
12339+
Etag: identity.NewID(),
12340+
Content: []byte("content1"),
12341+
}
12342+
server := httpserver.NewTestServer(map[string]*httpserver.Response{
12343+
"/foo": resp,
12344+
})
12345+
defer server.Close()
12346+
12347+
dest := t.TempDir()
12348+
_, err = c.Build(ctx, SolveOpt{
12349+
Exports: []ExportEntry{
12350+
{
12351+
Type: ExporterLocal,
12352+
OutputDir: dest,
12353+
},
12354+
},
12355+
}, "test", func(ctx context.Context, gc gateway.Client) (*gateway.Result, error) {
12356+
id := server.URL + "/foo"
12357+
md, err := gc.ResolveSourceMetadata(ctx, &pb.SourceOp{
12358+
Identifier: id,
12359+
}, sourceresolver.Opt{})
12360+
if err != nil {
12361+
return nil, err
12362+
}
12363+
require.NotNil(t, md.HTTP)
12364+
require.Equal(t, digest.FromBytes(resp.Content), md.HTTP.Digest)
12365+
12366+
st := llb.Scratch().File(llb.Copy(llb.HTTP(id), "foo", "bar"))
12367+
def, err := st.Marshal(sb.Context())
12368+
if err != nil {
12369+
return nil, err
12370+
}
12371+
return gc.Solve(ctx, gateway.SolveRequest{
12372+
Definition: def.ToPB(),
12373+
})
12374+
}, nil)
12375+
require.NoError(t, err)
12376+
12377+
dt, err := os.ReadFile(filepath.Join(dest, "bar"))
12378+
require.NoError(t, err)
12379+
require.Equal(t, "content1", string(dt))
12380+
12381+
resp.Etag = identity.NewID()
12382+
resp.Content = []byte("content2")
12383+
12384+
_, err = c.Build(ctx, SolveOpt{
12385+
Exports: []ExportEntry{
12386+
{
12387+
Type: ExporterLocal,
12388+
OutputDir: dest,
12389+
},
12390+
},
12391+
}, "test", func(ctx context.Context, gc gateway.Client) (*gateway.Result, error) {
12392+
id := server.URL + "/foo"
12393+
md, err := gc.ResolveSourceMetadata(ctx, &pb.SourceOp{
12394+
Identifier: id,
12395+
}, sourceresolver.Opt{})
12396+
if err != nil {
12397+
return nil, err
12398+
}
12399+
require.NotNil(t, md.HTTP)
12400+
require.Equal(t, digest.FromBytes(resp.Content), md.HTTP.Digest)
12401+
st := llb.Scratch().File(llb.Copy(llb.HTTP(id), "foo", "bar"))
12402+
def, err := st.Marshal(sb.Context())
12403+
if err != nil {
12404+
return nil, err
12405+
}
12406+
return gc.Solve(ctx, gateway.SolveRequest{
12407+
Definition: def.ToPB(),
12408+
})
12409+
}, nil)
12410+
require.NoError(t, err)
12411+
12412+
dt, err = os.ReadFile(filepath.Join(dest, "bar"))
12413+
require.NoError(t, err)
12414+
require.Equal(t, "content2", string(dt))
12415+
}
12416+
12417+
func testGitResolveMutatedSource(t *testing.T, sb integration.Sandbox) {
12418+
integration.SkipOnPlatform(t, "windows")
12419+
ctx := sb.Context()
12420+
c, err := New(ctx, sb.Address())
12421+
require.NoError(t, err)
12422+
defer c.Close()
12423+
12424+
gitDir := t.TempDir()
12425+
gitCommands := []string{
12426+
"git init",
12427+
"git config --local user.email test",
12428+
"git config --local user.name test",
12429+
"echo a > a",
12430+
"git add a",
12431+
"git commit -m a",
12432+
"git tag -a v0.1 -m v0.1",
12433+
"echo b > b",
12434+
"git add b",
12435+
"git commit -m b",
12436+
"git checkout -B v2",
12437+
"git update-server-info",
12438+
}
12439+
err = runInDir(gitDir, gitCommands...)
12440+
require.NoError(t, err)
12441+
12442+
// cmd := exec.Command("git", "rev-parse", "HEAD")
12443+
// cmd.Dir = gitDir
12444+
// out, err := cmd.Output()
12445+
// require.NoError(t, err)
12446+
// commitHEAD := strings.TrimSpace(string(out))
12447+
12448+
cmd := exec.Command("git", "rev-parse", "v0.1")
12449+
cmd.Dir = gitDir
12450+
out, err := cmd.Output()
12451+
require.NoError(t, err)
12452+
commitTag := strings.TrimSpace(string(out))
12453+
12454+
cmd = exec.Command("git", "rev-parse", "v0.1^{commit}")
12455+
cmd.Dir = gitDir
12456+
out, err = cmd.Output()
12457+
require.NoError(t, err)
12458+
commitTagCommit := strings.TrimSpace(string(out))
12459+
12460+
server := httptest.NewServer(http.FileServer(http.Dir(filepath.Clean(gitDir))))
12461+
defer server.Close()
12462+
12463+
dest := t.TempDir()
12464+
12465+
_, err = c.Build(ctx, SolveOpt{
12466+
Exports: []ExportEntry{
12467+
{
12468+
Type: ExporterLocal,
12469+
OutputDir: dest,
12470+
},
12471+
},
12472+
}, "test", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
12473+
id := "git://" + strings.TrimPrefix(server.URL, "http://") + "/.git#v0.1"
12474+
md, err := c.ResolveSourceMetadata(ctx, &pb.SourceOp{
12475+
Identifier: id,
12476+
Attrs: map[string]string{
12477+
"git.fullurl": server.URL + "/.git",
12478+
},
12479+
}, sourceresolver.Opt{})
12480+
if err != nil {
12481+
return nil, err
12482+
}
12483+
require.NotNil(t, md.Git)
12484+
require.Equal(t, "refs/tags/v0.1", md.Git.Ref)
12485+
require.Equal(t, commitTag, md.Git.Checksum)
12486+
require.Equal(t, commitTagCommit, md.Git.CommitChecksum)
12487+
require.Equal(t, id, md.Op.Identifier)
12488+
require.Equal(t, server.URL+"/.git", md.Op.Attrs["git.fullurl"])
12489+
12490+
// update the tag to point to a different commit
12491+
err = runInDir(gitDir, []string{
12492+
"git tag -f v0.1",
12493+
"git update-server-info",
12494+
}...)
12495+
require.NoError(t, err)
12496+
12497+
md, err = c.ResolveSourceMetadata(ctx, &pb.SourceOp{
12498+
Identifier: id,
12499+
Attrs: map[string]string{
12500+
"git.fullurl": server.URL + "/.git",
12501+
},
12502+
}, sourceresolver.Opt{})
12503+
if err != nil {
12504+
return nil, err
12505+
}
12506+
require.NotNil(t, md.Git)
12507+
require.Equal(t, "refs/tags/v0.1", md.Git.Ref)
12508+
require.Equal(t, commitTag, md.Git.Checksum)
12509+
require.Equal(t, commitTagCommit, md.Git.CommitChecksum)
12510+
require.Equal(t, id, md.Op.Identifier)
12511+
require.Equal(t, server.URL+"/.git", md.Op.Attrs["git.fullurl"])
12512+
12513+
st := llb.Git(server.URL+"/.git", "", llb.GitRef("v0.1"))
12514+
def, err := st.Marshal(sb.Context())
12515+
if err != nil {
12516+
return nil, err
12517+
}
12518+
return c.Solve(ctx, gateway.SolveRequest{
12519+
Definition: def.ToPB(),
12520+
})
12521+
}, nil)
12522+
require.NoError(t, err)
12523+
12524+
_, err = os.ReadFile(filepath.Join(dest, "b"))
12525+
require.Error(t, err)
12526+
require.True(t, os.IsNotExist(err), "expected file b to not exist")
12527+
12528+
dt, err := os.ReadFile(filepath.Join(dest, "a"))
12529+
require.NoError(t, err)
12530+
require.Equal(t, "a\n", string(dt))
12531+
12532+
checkAllReleasable(t, c, sb, false)
12533+
}
12534+
1227312535
func runInDir(dir string, cmds ...string) error {
1227412536
for _, args := range cmds {
1227512537
var cmd *exec.Cmd

solver/jobs.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,21 @@ func (s *state) Cleanup(fn func() error) error {
8585
return nil
8686
}
8787

88+
func (s *state) ResolverCache() ResolverCache {
89+
return s
90+
}
91+
92+
func (s *state) Lock(key any) (values []any, release func(any) error, err error) {
93+
var rcs []ResolverCache
94+
s.mu.Lock()
95+
for j := range s.jobs {
96+
rcs = append(rcs, j.resolverCache)
97+
}
98+
s.mu.Unlock()
99+
100+
return combinedResolverCache(rcs).Lock(key)
101+
}
102+
88103
func (s *state) SessionIterator() session.Iterator {
89104
return s.sessionIterator()
90105
}
@@ -329,6 +344,7 @@ type Job struct {
329344
startedTime time.Time
330345
completedTime time.Time
331346
releasers []func() error
347+
resolverCache *resolverCache
332348

333349
progressCloser func(error)
334350
SessionID string
@@ -645,6 +661,7 @@ func (jl *Solver) NewJob(id string) (*Job, error) {
645661
id: id,
646662
startedTime: time.Now(),
647663
uniqueID: identity.NewID(),
664+
resolverCache: newResolverCache(),
648665
}
649666
jl.jobs[id] = j
650667

@@ -862,6 +879,10 @@ func (j *Job) Cleanup(fn func() error) error {
862879
return nil
863880
}
864881

882+
func (j *Job) ResolverCache() ResolverCache {
883+
return j.resolverCache
884+
}
885+
865886
func (j *Job) SetValue(key string, v any) {
866887
j.values.Store(key, v)
867888
}

solver/llbsolver/ops/exec_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,3 +307,7 @@ func (j *jobCtx) Session() session.Group {
307307
func (j *jobCtx) Cleanup(f func() error) error {
308308
return errors.Errorf("cleanup not implemented for %T", j)
309309
}
310+
311+
func (j *jobCtx) ResolverCache() solver.ResolverCache {
312+
return nil
313+
}

0 commit comments

Comments
 (0)