Skip to content

Commit 1da36d1

Browse files
committed
dap: use container fs requests to get a more accurate state for the file system
The container filesystem request API that has been added to buildkit allows a container created through the `NewContainer` API to also access the filesystems. This is useful when an error occurs because it allows us to grab the mutable state of the mounts used during the actual build rather than the input version copies which don't contain any files that were added as part of the failed command. This gives us a more accurate view of the filesystem that was previously only accessible through using `exec` and `ls`/`cat` commands that may not always exist. Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
1 parent 44945d7 commit 1da36d1

File tree

2 files changed

+100
-7
lines changed

2 files changed

+100
-7
lines changed

build/invoke.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
gateway "github.com/moby/buildkit/frontend/gateway/client"
1212
"github.com/pkg/errors"
1313
"github.com/sirupsen/logrus"
14+
"github.com/tonistiigi/fsutil/types"
1415
)
1516

1617
type InvokeConfig struct {
@@ -145,6 +146,18 @@ func (c *Container) Exec(ctx context.Context, cfg *InvokeConfig, stdin io.ReadCl
145146
return err
146147
}
147148

149+
func (c *Container) ReadFile(ctx context.Context, req gateway.ReadContainerRequest) ([]byte, error) {
150+
return c.container.ReadFile(ctx, req)
151+
}
152+
153+
func (c *Container) ReadDir(ctx context.Context, req gateway.ReadDirContainerRequest) ([]*types.Stat, error) {
154+
return c.container.ReadDir(ctx, req)
155+
}
156+
157+
func (c *Container) StatFile(ctx context.Context, req gateway.StatContainerRequest) (*types.Stat, error) {
158+
return c.container.StatFile(ctx, req)
159+
}
160+
148161
func exec(ctx context.Context, resultCtx *ResultHandle, cfg *InvokeConfig, ctr gateway.Container, stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) error {
149162
processCfg, err := resultCtx.getProcessConfig(cfg, stdin, stdout, stderr)
150163
if err != nil {

dap/thread.go

Lines changed: 87 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ import (
1212
"github.com/google/go-dap"
1313
"github.com/moby/buildkit/client/llb"
1414
gateway "github.com/moby/buildkit/frontend/gateway/client"
15+
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
1516
"github.com/moby/buildkit/solver/errdefs"
1617
"github.com/moby/buildkit/solver/pb"
1718
"github.com/opencontainers/go-digest"
1819
"github.com/pkg/errors"
20+
"github.com/tonistiigi/fsutil/types"
1921
"golang.org/x/sync/errgroup"
2022
)
2123

@@ -323,16 +325,12 @@ func (t *thread) pause(c Context, k string, refs map[string]gateway.Reference, e
323325
}
324326
t.paused = make(chan stepType, 1)
325327

328+
t.prepareResultHandle(c, k, refs, err)
329+
326330
ctx, cancel := context.WithCancelCause(c)
327331
t.collectStackTrace(ctx, pos, refs)
328332
t.cancel = cancel
329333

330-
// Used for exec. Only works if there was an error or if the step returns
331-
// a root mount.
332-
if ref, ok := refs[k]; ok || err != nil {
333-
t.prepareResultHandle(c, ref, err)
334-
}
335-
336334
event.ThreadId = t.id
337335
c.C() <- &dap.StoppedEvent{
338336
Event: dap.Event{Event: "stopped"},
@@ -341,7 +339,15 @@ func (t *thread) pause(c Context, k string, refs map[string]gateway.Reference, e
341339
return t.paused
342340
}
343341

344-
func (t *thread) prepareResultHandle(c Context, ref gateway.Reference, err error) {
342+
func (t *thread) prepareResultHandle(c Context, k string, refs map[string]gateway.Reference, err error) {
343+
var ref gateway.Reference
344+
if err == nil {
345+
var ok bool
346+
if ref, ok = refs[k]; !ok {
347+
return
348+
}
349+
}
350+
345351
// Create a context for cancellations and make the cancel function
346352
// block on the wait group.
347353
var wg sync.WaitGroup
@@ -353,6 +359,31 @@ func (t *thread) prepareResultHandle(c Context, ref gateway.Reference, err error
353359

354360
t.rCtx = build.NewResultHandle(ctx, t.c, ref, t.meta, err)
355361

362+
if err != nil {
363+
gwcaps := t.c.BuildOpts().Caps
364+
365+
var solveErr *errdefs.SolveError
366+
// If we had a solve error and the exec filesystem capability, we can
367+
// get the filesystem mounts used in the actual build rather than only the input
368+
// mounts.
369+
if gwcaps.Supports(gwpb.CapGatewayExecFilesystem) == nil && errors.As(err, &solveErr) {
370+
if exec, ok := solveErr.Op.Op.(*pb.Op_Exec); ok {
371+
rCtx := t.rCtx
372+
373+
getContainer := sync.OnceValues(func() (*build.Container, error) {
374+
return build.NewContainer(c, rCtx, &build.InvokeConfig{})
375+
})
376+
377+
for i, m := range exec.Exec.Mounts {
378+
refs[m.Dest] = &mountReference{
379+
getContainer: getContainer,
380+
index: i,
381+
}
382+
}
383+
}
384+
}
385+
}
386+
356387
// Start the attach. Use the context we created and perform it in
357388
// a goroutine. We aren't necessarily assuming this will actually work.
358389
wg.Go(func() {
@@ -678,3 +709,52 @@ func (t *thread) rewind(ctx Context, inErr error) (k string, result *step, mount
678709
}
679710
return k, result, mounts, inErr
680711
}
712+
713+
type mountReference struct {
714+
getContainer func() (*build.Container, error)
715+
index int
716+
}
717+
718+
func (r *mountReference) ToState() (llb.State, error) {
719+
return llb.State{}, errors.New("unimplemented, cannot use ToState with mount reference")
720+
}
721+
722+
func (r *mountReference) Evaluate(ctx context.Context) error {
723+
return nil
724+
}
725+
726+
func (r *mountReference) ReadFile(ctx context.Context, req gateway.ReadRequest) ([]byte, error) {
727+
ctr, err := r.getContainer()
728+
if err != nil {
729+
return nil, err
730+
}
731+
732+
return ctr.ReadFile(ctx, gateway.ReadContainerRequest{
733+
ReadRequest: req,
734+
MountIndex: r.index,
735+
})
736+
}
737+
738+
func (r *mountReference) StatFile(ctx context.Context, req gateway.StatRequest) (*types.Stat, error) {
739+
ctr, err := r.getContainer()
740+
if err != nil {
741+
return nil, err
742+
}
743+
744+
return ctr.StatFile(ctx, gateway.StatContainerRequest{
745+
StatRequest: req,
746+
MountIndex: r.index,
747+
})
748+
}
749+
750+
func (r *mountReference) ReadDir(ctx context.Context, req gateway.ReadDirRequest) ([]*types.Stat, error) {
751+
ctr, err := r.getContainer()
752+
if err != nil {
753+
return nil, err
754+
}
755+
756+
return ctr.ReadDir(ctx, gateway.ReadDirContainerRequest{
757+
ReadDirRequest: req,
758+
MountIndex: r.index,
759+
})
760+
}

0 commit comments

Comments
 (0)