Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions build/invoke.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package build
import (
"context"
_ "crypto/sha256" // ensure digests can be computed
"fmt"
"io"
"io/fs"
"os"
"sync"
"sync/atomic"
"syscall"
Expand Down Expand Up @@ -146,6 +149,51 @@ func (c *Container) Exec(ctx context.Context, cfg *InvokeConfig, stdin io.ReadCl
return err
}

func (c *Container) CanInvoke(ctx context.Context, cfg *InvokeConfig) (string, bool) {
var cmd string
if len(cfg.Entrypoint) > 0 {
cmd = cfg.Entrypoint[0]
} else if len(cfg.Cmd) > 0 {
cmd = cfg.Cmd[0]
}

if cmd == "" {
return "no command specified", false
}

for {
path, index, err := c.resultCtx.inferMountIndex(cmd, cfg)
if err != nil {
return err.Error(), false
}

st, err := c.container.StatFile(ctx, gateway.StatContainerRequest{
StatRequest: gateway.StatRequest{
Path: path,
},
MountIndex: index,
})
if err != nil {
return fmt.Sprintf("stat error %s: %s", cmd, err), false
}

mode := fs.FileMode(st.Mode)
if mode&os.ModeSymlink != 0 {
// Follow the link.
cmd = st.Linkname
continue
}

if !mode.IsRegular() {
return fmt.Sprintf("%s: not a file", cmd), false
}
if mode&0o111 == 0 {
return fmt.Sprintf("%s: not an executable", cmd), false
}
return "", true
}
}

func (c *Container) ReadFile(ctx context.Context, req gateway.ReadContainerRequest) ([]byte, error) {
return c.container.ReadFile(ctx, req)
}
Expand Down
33 changes: 17 additions & 16 deletions build/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/tonistiigi/fsutil/types"
)

// NewResultHandle stores a gateway client, gateway reference, and the error from
Expand Down Expand Up @@ -81,38 +80,40 @@ func (r *ResultHandle) NewContainer(ctx context.Context, cfg *InvokeConfig) (gat
return r.gwClient.NewContainer(ctx, req)
}

func (r *ResultHandle) StatFile(ctx context.Context, fpath string, cfg *InvokeConfig) (*types.Stat, error) {
func (r *ResultHandle) inferMountIndex(fpath string, cfg *InvokeConfig) (string, int, error) {
containerCfg, err := r.getContainerConfig(cfg)
if err != nil {
return nil, err
return "", 0, err
}

type mountCandidate struct {
gateway.Mount
Index int
}

candidateMounts := make([]gateway.Mount, 0, len(containerCfg.Mounts))
for _, m := range containerCfg.Mounts {
candidateMounts := make([]mountCandidate, 0, len(containerCfg.Mounts))
for i, m := range containerCfg.Mounts {
if strings.HasPrefix(fpath, m.Dest) {
candidateMounts = append(candidateMounts, m)
candidateMounts = append(candidateMounts, mountCandidate{
Mount: m,
Index: i,
})
}
}
if len(candidateMounts) == 0 {
return nil, iofs.ErrNotExist
return "", 0, iofs.ErrNotExist
}

slices.SortFunc(candidateMounts, func(a, b gateway.Mount) int {
slices.SortFunc(candidateMounts, func(a, b mountCandidate) int {
return cmp.Compare(len(a.Dest), len(b.Dest))
})

m := candidateMounts[len(candidateMounts)-1]
relpath, err := filepath.Rel(m.Dest, fpath)
if err != nil {
return nil, err
return "", 0, err
}

if m.Ref == nil {
return nil, iofs.ErrNotExist
}

req := gateway.StatRequest{Path: filepath.ToSlash(relpath)}
return m.Ref.StatFile(ctx, req)
return filepath.Join("/", relpath), m.Index, nil
}

func (r *ResultHandle) getContainerConfig(cfg *InvokeConfig) (containerCfg gateway.NewContainerRequest, _ error) {
Expand Down
44 changes: 8 additions & 36 deletions dap/debug_shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"fmt"
"io"
"io/fs"
"net"
"os"
"path/filepath"
Expand Down Expand Up @@ -192,14 +191,6 @@ func (s *shell) attach(ctx context.Context, f dap.StackFrame, rCtx *build.Result
}
}()

// Check if the entrypoint is executable. If it isn't, don't bother
// trying to invoke.
if reason, ok := s.canInvoke(ctx, rCtx, cfg); !ok {
writeLineF(in.Stdout, "Build container is not executable. (reason: %s)", reason)
<-ctx.Done()
return context.Cause(ctx)
}

if err := s.sem.Acquire(ctx, 1); err != nil {
return err
}
Expand All @@ -211,6 +202,14 @@ func (s *shell) attach(ctx context.Context, f dap.StackFrame, rCtx *build.Result
}
defer ctr.Cancel()

// Check if the entrypoint is executable. If it isn't, don't bother
// trying to invoke.
if reason, ok := ctr.CanInvoke(ctx, cfg); !ok {
writeLineF(in.Stdout, "Build container is not executable. (reason: %s)", reason)
<-ctx.Done()
return context.Cause(ctx)
}

writeLineF(in.Stdout, "Running %s in build container from line %d.",
strings.Join(append(cfg.Entrypoint, cfg.Cmd...), " "),
f.Line,
Expand All @@ -231,33 +230,6 @@ func (s *shell) attach(ctx context.Context, f dap.StackFrame, rCtx *build.Result
return nil
}

func (s *shell) canInvoke(ctx context.Context, rCtx *build.ResultHandle, cfg *build.InvokeConfig) (reason string, ok bool) {
var cmd string
if len(cfg.Entrypoint) > 0 {
cmd = cfg.Entrypoint[0]
} else if len(cfg.Cmd) > 0 {
cmd = cfg.Cmd[0]
}

if cmd == "" {
return "no command specified", false
}

st, err := rCtx.StatFile(ctx, cmd, cfg)
if err != nil {
return fmt.Sprintf("stat error: %s", err), false
}

mode := fs.FileMode(st.Mode)
if !mode.IsRegular() {
return fmt.Sprintf("%s: not a file", cmd), false
}
if mode&0111 == 0 {
return fmt.Sprintf("%s: not an executable", cmd), false
}
return "", true
}

// SendRunInTerminalRequest will send the request to the client to attach to
// the socket path that was created by Init. This is intended to be run
// from the adapter and interact directly with the client.
Expand Down