From 0cbad079745448f3e1b292d37779e7d56a1a1fe8 Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Sun, 15 May 2022 18:06:00 +0900 Subject: [PATCH] Initial support of DAP (Debug Adapter Protocol) Signed-off-by: Kohei Tokunaga --- README.md | 43 +- break.go | 74 +- commands.go | 224 +++++ docs/images/emacs-dap.png | Bin 0 -> 68960 bytes docs/images/nvim-dap.png | Bin 0 -> 92344 bytes docs/images/vscode-dap.png | Bin 0 -> 97418 bytes examples/dap/README.md | 26 + examples/dap/emacs/README.md | 19 + examples/dap/emacs/dap-dockerfile.el | 16 + examples/dap/nvim/README.md | 25 + examples/dap/nvim/plugins.lua | 21 + exec.go | 150 +-- exit.go | 21 + go.mod | 4 +- go.sum | 2 + handler.go | 395 -------- list.go | 18 +- main.go | 541 ++++------- main_test.go | 10 +- pkg/buildkit/breakpoint.go | 141 +++ pkg/buildkit/client.go | 429 +++++++++ debug.go => pkg/buildkit/controller.go | 72 +- pkg/buildkit/exec.go | 130 +++ pkg/buildkit/handler.go | 144 +++ pkg/buildkit/location.go | 16 + pkg/dap/dap.go | 904 ++++++++++++++++++ pkg/dap/dap_test.go | 1202 ++++++++++++++++++++++++ pkg/dap/debugger.go | 207 ++++ pkg/dap/io.go | 230 +++++ pkg/testutil/dap.go | 98 ++ pkg/testutil/debugshell.go | 1 + 31 files changed, 4191 insertions(+), 972 deletions(-) create mode 100644 commands.go create mode 100644 docs/images/emacs-dap.png create mode 100644 docs/images/nvim-dap.png create mode 100644 docs/images/vscode-dap.png create mode 100644 examples/dap/README.md create mode 100644 examples/dap/emacs/README.md create mode 100644 examples/dap/emacs/dap-dockerfile.el create mode 100644 examples/dap/nvim/README.md create mode 100644 examples/dap/nvim/plugins.lua create mode 100644 exit.go delete mode 100644 handler.go create mode 100644 pkg/buildkit/breakpoint.go create mode 100644 pkg/buildkit/client.go rename debug.go => pkg/buildkit/controller.go (86%) create mode 100644 pkg/buildkit/exec.go create mode 100644 pkg/buildkit/handler.go create mode 100644 pkg/buildkit/location.go create mode 100644 pkg/dap/dap.go create mode 100644 pkg/dap/dap_test.go create mode 100644 pkg/dap/debugger.go create mode 100644 pkg/dap/io.go create mode 100644 pkg/testutil/dap.go diff --git a/README.md b/README.md index 85a3ecc2..09d367ab 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ buildg debug --image=debugging-tools /path/to/build/context For the detailed command refenrece, refer to [Command reference](#command-reference) in the following -### Exmaple +### Exmaple with terminal Debug the following Dockerfile: @@ -109,6 +109,15 @@ hi (buildg) quit ``` +## Use on IDEs + +Buildg allows visual and interactive debugging of Dockerfile on editors like VS Code, emacs and Neovim. +This is provided throgh [DAP(Debug Adapter Protocol)](https://microsoft.github.io/debug-adapter-protocol/) supported by editors [(official list)](https://microsoft.github.io/debug-adapter-protocol/implementors/tools/). + +See [`./examples/dap/README.md`](./examples/dap/README.md) for usage of DAP. + +![Buildg on VS Code](./docs/images/vscode-dap.png) + ## Install - Requirements @@ -173,6 +182,9 @@ Leveraging the generic features added through the work, this project implements - [buildg debug](#buildg-debug) - [buildg prune](#buildg-prune) - [buildg du](#buildg-du) +- [buildg dap serve](#buildg-dap-serve) +- [buildg dap prune](#buildg-dap-prune) +- [buildg dap du](#buildg-dap-du) - [Debug shell commands](#debug-shell-commands) - [break](#break) - [breakpoints](#breakpoints) @@ -221,6 +233,35 @@ Show disk usage information Usage: `buildg du` +## buildg dap serve + +Serve Debug Adapter Protocol (DAP) via stdio. +Should be called from editors. +See [`./examples/dap/README.md`](./examples/dap/README.md) for usage of DAP. + +Usage: `buildg dap serve [OPTIONS] [ARGS...]` + +Flags: +- `--log-file value`: Path to the file to output logs + +## buildg dap prune + +Prune DAP cache. +See [`./examples/dap/README.md`](./examples/dap/README.md) for usage of DAP. + +Usage: `buildg dap prune [OPTIONS]` + +Flags: + +- `--all`: Prune including internal/frontend references + +## buildg dap du + +Show disk usage of DAP cache +See [`./examples/dap/README.md`](./examples/dap/README.md) for usage of DAP. + +Usage: `buildg dap du` + ## Debug shell commands ### break diff --git a/break.go b/break.go index 8e44135e..649b57c5 100644 --- a/break.go +++ b/break.go @@ -5,7 +5,7 @@ import ( "fmt" "strconv" - "github.com/moby/buildkit/solver/pb" + "github.com/ktock/buildg/pkg/buildkit" "github.com/urfave/cli" ) @@ -28,17 +28,18 @@ on-fail breaks on step that returns an error } h := hCtx.handler var key string - var b breakpoint + var b buildkit.Breakpoint if bp == "on-fail" { key = "on-fail" - b = newOnFailBreakpoint() + b = buildkit.NewOnFailBreakpoint() } else if l, err := strconv.ParseInt(bp, 10, 64); err == nil { - b = newLineBreakpoint(hCtx.locs[0].source.Filename, l) + b = buildkit.NewLineBreakpoint(hCtx.locs[0].Source.Filename, l) } if b == nil { return fmt.Errorf("cannot parse breakpoint %q", bp) } - return h.breakpoints.add(key, b) + _, err := h.Breakpoints().Add(key, b) + return err }, } } @@ -50,8 +51,8 @@ func breakpointsCommand(ctx context.Context, hCtx *handlerContext) cli.Command { Usage: "Show breakpoints key-value pairs", UsageText: "breakpoints", Action: func(clicontext *cli.Context) error { - hCtx.handler.breakpoints.forEach(func(key string, b breakpoint) bool { - fmt.Printf("[%s]: %v\n", key, b) + hCtx.handler.Breakpoints().ForEach(func(key string, b buildkit.Breakpoint) bool { + fmt.Fprintf(hCtx.stdout, "[%s]: %v\n", key, b) return true }) return nil @@ -72,10 +73,10 @@ BREAKPOINT_KEY is the key of a breakpoint which is printed when executing "break if bpKey == "" { return fmt.Errorf("breakpoint key must be set") } - if _, ok := hCtx.handler.breakpoints.get(bpKey); !ok { + if _, ok := hCtx.handler.Breakpoints().Get(bpKey); !ok { return fmt.Errorf("breakpoint %q not found", bpKey) } - hCtx.handler.breakpoints.clear(bpKey) + hCtx.handler.Breakpoints().Clear(bpKey) return nil }, } @@ -87,7 +88,7 @@ func clearAllCommand(ctx context.Context, hCtx *handlerContext) cli.Command { Usage: "Clear all breakpoints", UsageText: "clearall", Action: func(clicontext *cli.Context) error { - hCtx.handler.breakpoints.clearAll() + hCtx.handler.Breakpoints().ClearAll() return nil }, } @@ -100,7 +101,7 @@ func nextCommand(ctx context.Context, hCtx *handlerContext) cli.Command { Usage: "Proceed to the next line", UsageText: "next", Action: func(clicontext *cli.Context) error { - hCtx.handler.breakEachVertex = true + hCtx.handler.BreakEachVertex(true) hCtx.continueRead = false return nil }, @@ -114,58 +115,9 @@ func continueCommand(ctx context.Context, hCtx *handlerContext) cli.Command { Usage: "Proceed to the next breakpoint", UsageText: "continue", Action: func(clicontext *cli.Context) error { - hCtx.handler.breakEachVertex = false + hCtx.handler.BreakEachVertex(false) hCtx.continueRead = false return nil }, } } - -func newLineBreakpoint(filename string, line int64) breakpoint { - return &lineBreakpoint{filename, line} -} - -type lineBreakpoint struct { - filename string - line int64 -} - -func (b *lineBreakpoint) isTarget(ctx context.Context, info breakpointContext) (bool, string, error) { - for _, loc := range info.locs { - if loc.source.Filename != b.filename { - continue - } - for _, r := range loc.ranges { - if int64(r.Start.Line) <= b.line && b.line <= int64(r.End.Line) { - return true, "reached " + b.String(), nil - } - } - } - return false, "", nil -} - -func (b *lineBreakpoint) String() string { - return fmt.Sprintf("line: %s:%d", b.filename, b.line) -} - -func (b *lineBreakpoint) addMark(source *pb.SourceInfo, line int64) bool { - return source.Filename == b.filename && line == b.line -} - -func newOnFailBreakpoint() breakpoint { - return &onFailBreakpoint{} -} - -type onFailBreakpoint struct{} - -func (b *onFailBreakpoint) isTarget(ctx context.Context, info breakpointContext) (bool, string, error) { - return info.status.err != nil, fmt.Sprintf("caught error %v", info.status.err), nil -} - -func (b *onFailBreakpoint) String() string { - return "breaks on fail" -} - -func (b *onFailBreakpoint) addMark(source *pb.SourceInfo, line int64) bool { - return false -} diff --git a/commands.go b/commands.go new file mode 100644 index 00000000..f2a03303 --- /dev/null +++ b/commands.go @@ -0,0 +1,224 @@ +package main + +import ( + "bufio" + "context" + "fmt" + "io" + "os" + "sync" + + "github.com/google/shlex" + "github.com/ktock/buildg/pkg/buildkit" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +const ( + promptEnvKey = "BUILDG_PS1" + defaultPrompt = "(buildg) " +) + +type handlerContext struct { + handler *buildkit.Handler + stdin *sharedReader + stdout io.Writer // TODO: use cli.Context.App.Writer + info *buildkit.RegisteredStatus + locs []*buildkit.Location + continueRead bool + err error +} + +type handlerCommandFn func(ctx context.Context, hCtx *handlerContext) cli.Command + +var handlerCommands = []handlerCommandFn{ + breakCommand, + breakpointsCommand, + clearCommand, + clearAllCommand, + nextCommand, + continueCommand, + execCommand, + listCommand, + exitCommand, +} + +type commandHandler struct { + stdin *sharedReader + stdout io.Writer + prompt string +} + +func newCommandHandler(stdin *sharedReader, stdout io.Writer) *commandHandler { + prompt := defaultPrompt + if p := os.Getenv(promptEnvKey); p != "" { + prompt = p + } + return &commandHandler{stdin, stdout, prompt} +} + +func (h *commandHandler) breakHandler(ctx context.Context, bCtx buildkit.BreakContext) error { + globalProgressWriter.disable() + defer globalProgressWriter.enable() + + for key, bpInfo := range bCtx.Hits { + fmt.Fprintf(h.stdout, "Breakpoint[%s]: %s\n", key, bpInfo.Description) + } + printLines(bCtx.Handler, h.stdout, bCtx.Locs, defaultListRange, defaultListRange, false) + for { + ln, err := h.readLine(ctx) + if err != nil { + return err + } + if args, err := shlex.Split(ln); err != nil { + logrus.WithError(err).Warnf("failed to parse line") + } else if len(args) > 0 { + cont, err := h.dispatch(ctx, bCtx, args) + if err != nil { + return err + } + if !cont { + break + } + } + } + return nil +} + +func (h *commandHandler) readLine(ctx context.Context) (string, error) { + fmt.Fprintf(h.stdout, h.prompt) + r, done := h.stdin.use() + defer done() + lnCh := make(chan string) + errCh := make(chan error) + scanner := bufio.NewScanner(r) + go func() { + if scanner.Scan() { + lnCh <- scanner.Text() + } else if err := scanner.Err(); err != nil { + errCh <- err + } else { + // EOF thus exit + errCh <- buildkit.ErrExit + } + }() + var ln string + select { + case ln = <-lnCh: + case err := <-errCh: + return "", err + case <-ctx.Done(): + return "", fmt.Errorf("canceled reading line: %w", ctx.Err()) + } + return ln, scanner.Err() +} + +func (h *commandHandler) dispatch(ctx context.Context, bCtx buildkit.BreakContext, args []string) (continueRead bool, err error) { + if len(args) == 0 || args[0] == "" { + return true, nil // nop + } + continueRead = true + app := cli.NewApp() + rootCmd := "buildg" + app.Name = rootCmd + app.HelpName = rootCmd + app.Usage = "Interactive debugger for Dockerfile" + app.UsageText = "command [command options] [arguments...]" + app.ExitErrHandler = func(context *cli.Context, err error) {} + app.UseShortOptionHandling = true + hCtx := &handlerContext{ + handler: bCtx.Handler, + stdin: h.stdin, + stdout: h.stdout, + info: bCtx.Info, + locs: bCtx.Locs, + continueRead: true, + err: nil, + } + for _, fn := range handlerCommands { + app.Commands = append(app.Commands, fn(ctx, hCtx)) + } + if cliErr := app.Run(append([]string{rootCmd}, args...)); cliErr != nil { + fmt.Fprintf(h.stdout, "%v\n", cliErr) + } + return hCtx.continueRead, hCtx.err +} + +type sharedReader struct { + r io.Reader + currentW *io.PipeWriter + currentWMu sync.Mutex + useMu sync.Mutex + + closeOnce sync.Once + done chan struct{} +} + +func newSharedReader(r io.Reader) *sharedReader { + s := &sharedReader{ + r: r, + done: make(chan struct{}), + } + go func() { + buf := make([]byte, 4096) + for { + select { + case <-s.done: + return + default: + } + n, err := s.r.Read(buf) + if err != nil { + if err != io.EOF { + logrus.WithError(err).Warnf("sharedReader: failed to read stdin data") + return + } + s.currentWMu.Lock() + if w := s.currentW; w != nil { + w.Close() + s.currentW = nil + } + s.currentWMu.Unlock() + continue + } + dest := io.Discard + s.currentWMu.Lock() + w := s.currentW + s.currentWMu.Unlock() + if w != nil { + dest = w + } + if _, err := dest.Write(buf[:n]); err != nil { + logrus.WithError(err).Warnf("sharedReader: failed to write stdin data") + } + } + }() + return s +} + +func (s *sharedReader) use() (r io.Reader, done func()) { + s.useMu.Lock() + pr, pw := io.Pipe() + s.currentWMu.Lock() + if s.currentW != nil { + panic("non nil writer") + } + s.currentW = pw + s.currentWMu.Unlock() + return pr, func() { + s.currentWMu.Lock() + if s.currentW != nil { + s.currentW = nil + pw.Close() + pr.Close() + } + s.currentWMu.Unlock() + s.useMu.Unlock() + } +} + +func (s *sharedReader) close() { + s.closeOnce.Do(func() { + close(s.done) + }) +} diff --git a/docs/images/emacs-dap.png b/docs/images/emacs-dap.png new file mode 100644 index 0000000000000000000000000000000000000000..ed0f634811c54e1aac016855f537430f77a08fb5 GIT binary patch literal 68960 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 SAWSaj5e= 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 CB+O?3 literal 0 HcmV?d00001 diff --git a/docs/images/nvim-dap.png b/docs/images/nvim-dap.png new file mode 100644 index 0000000000000000000000000000000000000000..ed77f99443f552d44d65cde6f71ac6e316e1740e GIT binary patch literal 92344 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 RGFAn!SeU_nt2c3r`wt(?Fg^eP literal 0 HcmV?d00001 diff --git a/docs/images/vscode-dap.png b/docs/images/vscode-dap.png new file mode 100644 index 0000000000000000000000000000000000000000..8235d4bb0aafa23a8d08eaa93fc2e54b96ce0cce GIT binary patch literal 97418 zcmb@tb97(9`vw?WjqN6BY@@Ml+qUgSZP3__ZCj0P+jgV9?e~Y>e|Gnr-JX+v&b@c; z%scNq@AG2jMk>gOBf{arfq;M@N=k?*fq+23gMfggz(4_aFt8mBfFB5FAxRY&;GYkS zNd$0><07izqHJ&G;%?|<3Sw?&Z)-~DZ0uxeYUgZW?{W#=#Sb)Mu~gA;5q2^)bg{Iz zBUZ7rH3e>hfG{&MGw-+5GqN9KL@;w5Bo`1fF&?A_F|zMRgt0On7R7&iLOuZjAqJ5Y z5mfQWI9vDBP%(Y!zP=n|q^*q#psH*R&D+7GZ?U#2Nz$V4%3H%|il1RV_hn-l9l4`( zAI+ShO|Ey4l!bK05>*sb)LAk?Gc+?ec!Ua`mr_lIC)r@VvMkOU23Z&Vmd< z5rQH4|NP%bbY94iDwqzacQ=?qDlO^%^q43{kpmhZV6i3WD6g<)5(rjoYi+%{UZe8EA0VrF5!dnDp*#`8L!DNjsHeCu}YHaO$@HansYl9d&M zOILYtu<%(oU$AUGJ~cJ6$H2w&e`Y}?3>ONPBo*4y!u1mqbFJ0c{Bl4#xez25JC%`Y z;wq5)@B6NfZWr;18PRe@!c~jOR1W_uFF}W1oLmu^ZhmfUzOinZR1$9gz`z?UIj(d2 z%x@>^<&_mT_e!M#X(y>~cjW&gbru*@aNu4dx=u!B+bYn6!WyZU6o&^PSFQ|`4hb6} zEG?eue;d;S5{3$W^4ys#nJ^jGSD-TX##|~>DJ}g!H{yp122GOMhvLMp>#7+BgYVF* zqW9GJ-)YJtuij%0o>|+}el1u$HT4zw?>X#zxv$o>pA&*M8_ZG(BmSS&m5&8;P(F23 zF|82sU{D^`&75g&K)zMXaO;>J2K(=qg%bI&Sopy4*<7j@6qjYP4{M$fK8q{O+elUu zv-d6afDKUoZ{xfmk7dEWbh+acnwv|=EtA5c?2a%!H?7X7roCj5{6bsv^2waJwNpXA;d=UQ zY|mYwJTO+Tx&7^Nm?8uv@0H5>YUH&FXL$sY2mUiuN9p8Qza*{197eb+hPz+7zI>_v2z=Gb@+AvFOP zW9sb(k1PwqA450SdMUb_{l7j%aTPh{KDM*@TxGC*Ivr#&8}^)mr(q#_4E8^C5aQ5t zC>2evM0}>FO+)}uh%%Cz`c^VCtrReITX$RqHpVrQq%{x-=&e!Vv@aDv?4KRo$>KsX$uVvyEA${x8? zt#ME${ddgwHyfSy#7)wf=x>|lide043i-dIx59^apASq>msE;slF6F-h@RozYq;Nj zV5eh6N1&mzPhQRZl0-P(i;VJQR%DVI3&;={A0w*D^2t;A60 zJH7adjV|XS_F3nYtq7Dda>jVhGb$Qu1uE21n?7=ZAPec?hV%?oDcpk4)uvQa|K3y{8_rXp6fY-cRVA04GcNU0ri7H(>Ql2t= zaVL@T){;*d4Pk#U+4z&xLR7wOr1iO-(r-M7#Fc6GvlAr+!H||M_LU$XenaQj3R;|? zYShD9+m0<_D>N*5p_XBSXSVn9xoB|gUJ4Jn)mI|#IG21};iM}_FRFvCWuckw$HB*%xl5@qq%c`0?*Ox#s zmLBHE%A}WarWYlk+=7&iRL9S zpW#h{g#Qx66p9yjq6;_`O`Gz%Sl~v_i*qIDkTeY&MXFIzKWETv~AgYhtj`TJn;qb;42FDGhiNM7CPArOa9_@J)8izTTc zS%bqK5i3f-@J3im$PF4b6;`T={?5V*sn~L@j^=_Id22EnsIIE71!|ra9eYN; z#-~>s;iC(*o%2Xq?jTw7w>p%07p}-%Djaf3mFXuix0eXqt13< zFQNVBbNPoW9*qE@c=ljyG2tp#HaA<)Ob2vTeS>(aXF~R-Uu37R_w2_mnFT!0V8mj7 zzRHDbajx3e1J~H>=5%|~-r~iyPuD-^JLp1CcCsb;F@c~MTQpaNA79?K+WAWLJw?1u=ccvgB*FOG?FRm8oxC_H)48n*KFSrsZq{ z*8{n;!Ss+#Th{!E%DlZ089XH1Ek*RY!*?=4@h49&?Q#q_71grcv`fkq(9B8~7Dmi2 z9X0~~mC@}SV!&@Yk{?mE3E0H_nZCtU#)ACa{7!bOoVg`af=chm5?3lhBm@I~PjyY5X2UIn;>rl?$paJ- zt@h{|R$2O99G+^0*BrBQjyz{i&+IUE0D|r|&L?9%v}YX|xpfTIz97XaHnJVMWos$G zZE3b8YhK|wTyVj~C;JARr?hC?zjlE=#xd;a41DfG`K#+5D0?CXTJ)T_mY#7HaIprY zq>=KlbU}TGMO$BLrkYe0$A|*eC^IAtLX2!l(bq!p0LEQdS7pHc= zkgl4N;Z|47ixcRcgucLdy(`Q%9By{hA$s7h>Rdvq+Q`2;dBrb#6RZ1ENZ0|YP|CzH z*>!CFw&uZ)zks{?a=6^OM=3-&cew8!HZ|7XjFm%nux6*0y*lo$KVR@sUX{910(Y)= zDc-j(-K-*!iF+qHlsX&1p^W!2qy+ot>hw~?kz+O>U6W4Bqpg~qGZl#^8b>NmmK6e&T$30GBo*Ms2>7mW z7z0auGZb-Mbe?TFa&dCHyt1TM^y;0iwnHZ>Ggh3Dn-0Ba#$*CX?wPU3ScYXfM;Z{o zW${<0o4j2o&N@Re+1S~sm7H6rC~t;}vU3PtAU_tX@h*K`enbqXN3^?zMnoT8EAbc+ zb|fQqg(Q?hHw5D$t+e&0k!$8qvdn@kZSQEoib^o24ko6D$M$`k%Y}bn>M0%E_nz$V zKQ=&BmrAX3FHlt$zB-<3L1Rd#y4uycvM+)Oz6Jh$~y z_Sl?U3LSpJYNrFapY$Y-q^7jo!Mz~$8)0=IjjrpH{m-va*TK%8;R*fa2(4*o)-9GR zkiHyFpb)+V$mlZHz-rcUP?eXL{DKfRbS?ctE%!URSw=*RA#J50|KoO^tA!=|8HJGV zACcVE2;%cr<+e0^-Y`C7{_OiHJ-D}}h;1lWj4Vwp(rnX|OwT$}bV&QvTN{QXtNdFc z0#%eMGMo`e?61;e^K5IMT%kzQ7r(f1#qWIKfuiA^zs{Fa%tb@`9_J}Cy~W}ofu!#y zH*fl=;S8swSI0I?2^8Co-^CiK-X{Jh76KV4U>^iF3F9M7qX-|giPrJxvcJcGu8Y~|)y|5X1^`nrW{y%mE4 zBD!>^d&SVXf%@GH-p2q!TY{ta%?c0s1%u};jt$RU|Bw|oqu#wrl?CzI+8xbLt(P3Z=Vq)a+KgP@QKwxmGQrp}D1c|DoV)%IL0D)on>N zk|tQy*}OP>%)lRRlL{)`mTteD#+y^aVLW0P773Lq`D!?YM&J@sjqug6rklGXheux| zBd4n)y`c^2#Z6`^(-2ym9?V;P1qi?2 z9A4$(_1;#B7qt|xPwe>u7ZXv0UcnQ^XN;VKAUn?LQ)md;9}&G!1^jkv_CJBnJbZjz zAxgjHEo7ov%CIn;NilIr1L6Z6R`Rt-3MSdO-Z6Yh1tH#I+2u+7It2R5E# z+vdqgQ8Z8cWkUKjS#jHfot_Go*FX2@g_W%lP~8r3!MCqZ2KRasn{r`NNK4#(q5Sdb%ImO{~d~_RSFTIMdZ^Cr2!3qH}h(Y20x< zKi5H?@j6E(MyteMSoVC^VBg17!wS>zd$D213$FZ;Vi{>{3a_NZno>qRZPgFasvhY5 z^0VgM2lp6jp?2?410gBx<{(6`AS01J!Fm-8rnL(CuKygKxH_Wqh}x7{aWR3(y;Ati zs}jXIkZ+WKYqTR0TjI0Fv`gobUs|yVB)ckp&D1&>^%=T2Lw`K1g&e)8NNVa}2j1b*<6F4QJ9ESf)K`6l zOfa$jNRr4LNIx0vcb-GL0wc4_=nhS8M-F_R#?M;StP@L`LECewEa!pwqUV(Vyx+en zNd%mMWI2Yri`0gb)Q7i(5NJIVWZ4ewa+b-;HuUm8ivLe{Dkxp#T33g@Y{B@fF4fa~ zjZ#8BVetZ&Wy={BJ$z`$e7|M?vBtmQMIceLb4@HWFh+OE9aK*pl}tY)?6B1R6ldKp zg@*j`H)r@yjYbg^Cjf9W?ymBFwqTlwe~TvndE_baH}(Ag?IH!IZMP#Nq&8ake>%ko znH~;`N~TXT#s4p1fP#&&nwpx}|5-mUKf=l3|68U1uP?+~$l({tHTz--ct-M%z|NQJ z!Z-b1D%$&~ltjyUNJIv_Y}I~g{Oh)V_BfIp{`K`$quxSs;xZhWh=Q8>L=xS$Uf=&M ziN$8;bb8Bl_Ua``^Y=gCWp?tR892a5C^KP6#p?a0xT$y^6 zN{y(P*v-28<|kZSDJ?DBiHQjhPfsS#8vh2t5C14ty!!#(sg!rnyOta z>idsp@p(^KHKe4bDz-S8rg7L$Oyvrfs8wjCCTS;rQ7%Nm#nsx{)%Sb;EStvaX>_g9 z?`;MQ%ja5PZ~ob;S;m4BoOj>fnbnrtg+%E&KAn<&J;-B9nXfV>-%C9 zC=FL=H%UlIQP0-y?(8_YyPNOyN1B+KIo%$}Hrg!r_D7;=wz_-)G-|5lCkU*y$JG`n z2r!W8@dV8wi~=~C6QjG<(D-H{sOAz9iQ# z&NM(sNlCGJ+-S3XA8^LgIT+l|@YrpLSy|_b?SrC97vrlN8w(wG2SHeESI`+47{p@n zhjWF)u^A0Ogu{^v01UTZ58`nR-JQ%w!J<)I?L;wv75ej^E>>mVe%u~T=BrR!0v4I& z`>i#Z!)B?Q$p2MPM+ZMOH5CDmJsLppVLvV&UX)VFFTr4_znuV>aJt?RO_ch4zS{Ix zQ7P%^;BlKQgF2T0e4pN5E+r)-x>r`x09-KoJUNVw$vm)I)$er5)9QC-eqM}Y6 zyLfnbFdFqo3}u1;XI*Pk@2b|u#>RNN1Xg_8iPJgkDTs*!K9Pwj)S1b;oh|i5#=;ML3a29}ZcG?3Z5HdMs(4Uk`pW^sI@OS0vS(Onsc_`J{oV;zhmk~cFm zn>ZyQCl3Lz#%wx*uB5E2+2O_Z7a;(bwBL--thIY4B+;lVHQDLvdG05n>f3jC-T#%p zv9VF7#VOQu^rvEjmFl1SQ}bGGMr;I&h85mbW@hGZy6SP<-Vd~ZMLs=0|H#Uk0PxV$ zcG)@l`h0u+@W8J5`p+lqWcQg22LWpFdc4RqU%gmwi;}^%V8sP^Z7AXsna6f07Kp`M z@h5O7gsrRXzProKO_OVzm3mkn9v;8gqXVEmpyS56a}YpqmP9~&Lsb$09Ae;zL@%UVN~X*43?#r>%XQ|*)A`WmlUV}`3qMDb84`Z}3`Tflq|tKv zzx&8t#s($c@zr_VpKv{@h9eQAaJy2|s8{Z#+jlbkU)BUzQWndZU%ac)$C?higNOht zkN%`H2GGv6>dH+pJv~hkGX$_0pn}|K3yYO{%g9Mx+;bq60w@um$1S?M`|DsV!RRPM z*BIa_qja%E&xvqwB(1Ed>9iYZ9ov8W_yIVatr}-w(j^p{QOT1t8u8JV$Ej)8%JP{1eR_{P3gJ_5totTq4m{ab># z>`*CREan@Aw&KZr*-!{9+Ko+{_oH3bGl7h%`Xv7gEudPFqS;)vGHIgrf8oQEIFn{= z_smQ@087l5<};`t+1Z=Mh)-u1vxU*(`O_fJ?&rQrW8>qV7cGY#z#j|(f=N#hB&+p8 zG%yDE;G(FaYjQ>gLV!>%Rn#a|d}yd>O>5`jVL`H<(ZkuYPM2S1NC?EmMn^h}O}|YS!rA zwp2X=i!+wNl|ZlC%ILHwp+b!Tz=mewIoixXE7E(h~(-H8}ysMKLT*4Jv4N|Y?O*Jcxwozqe z))OXne|_K`o|ExOHDI*A3JY)gkOjU2=2=uk$=0aIP!RPyv`Fy{(KR$QU;=q3L{;}1?-d=wU9*30Ad+pV)P3Uhlq@-Y( za^Y1~41nv-1DbMmVBU`~zW(=FfZBaO+n)u1M?>FRYjK_{*PK0SG5t@_+BKSxJ(ylT zI08^ei{BeJKu=~@TRr94O-Kz|F-=VzI5;@KJK><

IdaKs+*$#rLxG07Mr%r>CPg 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

k?&=7 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

wT(k=Ong3 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 literal 0 HcmV?d00001 diff --git a/examples/dap/README.md b/examples/dap/README.md new file mode 100644 index 00000000..8b846742 --- /dev/null +++ b/examples/dap/README.md @@ -0,0 +1,26 @@ +# Visual and Interactive Debugging of Dockerfile on editors via DAP (Debug Adapter Protocol) + +Buildg allows visual and interactive debugging of Dockerfile on editors like VS Code, emacs and Neovim. + +This is provided throgh [DAP(Debug Adapter Protocol)](https://microsoft.github.io/debug-adapter-protocol/) supported by editors [(official list)](https://microsoft.github.io/debug-adapter-protocol/implementors/tools/). + +![Buildg on VS Code](../../docs/images/vscode-dap.png) + +## Getting Started + +- VS Code: https://github.com/ktock/vscode-buildg +- Emacs: [`./emacs`](./emacs) +- Neovim: [`./nvim`](./nvim) + +## Launch Configuration + +In the launch configuration (e.g. `launch.json` on VS Code), the following properties are provided. + +- `program` *string* **REQUIRED** : Absolute path to Dockerfile. +- `stopOnEntry` *boolean* : Automatically stop after launch. (default: `true`) +- `target` *string* : Target build stage to build. +- `image` *string* : Image to use for debugging stage. +- `build-args` *array* : Build-time variables. +- `ssh` *array* : Allow forwarding SSH agent to the build. Format: `default|[=|[,]]` +- `secrets` *array* : Expose secret value to the build. Format: `id=secretname,src=filepath` + diff --git a/examples/dap/emacs/README.md b/examples/dap/emacs/README.md new file mode 100644 index 00000000..f8b8999f --- /dev/null +++ b/examples/dap/emacs/README.md @@ -0,0 +1,19 @@ +# Buildg on emacs + +Buildg supports interactive debugging of Dockerfile on emacs. + +![Buildg on Emacs](../../../docs/images/emacs-dap.png) + +## Install + +- Requirements + - buildg + - [dap-mode](https://github.com/emacs-lsp/dap-mode) + - configuration guide: https://emacs-lsp.github.io/dap-mode/page/configuration/ + +Then add the example launch configuration shown in [`./dap-dockerfile.el`](./dap-dockerfile.el) to your `init.el`. +Also refer to [`../README.md`](../README.md) for available properties in the launch configuration. + +## Usage + +Run `M-x dap-debug` then select `Dockerfile Debug Configuration` template. diff --git a/examples/dap/emacs/dap-dockerfile.el b/examples/dap/emacs/dap-dockerfile.el new file mode 100644 index 00000000..82b38941 --- /dev/null +++ b/examples/dap/emacs/dap-dockerfile.el @@ -0,0 +1,16 @@ +(require 'dap-mode) +(require 'dap-utils) + +(dap-register-debug-provider "dockerfile" 'dap-dockerfile--populate-default-args) + +(defun dap-dockerfile--populate-default-args (conf) + "Populate CONF with the default arguments." + (-> conf + (dap--put-if-absent :program buffer-file-name) + (dap--put-if-absent :dap-server-path (list "buildg.sh" "dap" "serve")))) + +(dap-register-debug-template "Dockerfile Debug Configuration" + (list :type "dockerfile" + :request "launch" + :stopOnEntry t + :name "Debug Dockerfile")) diff --git a/examples/dap/nvim/README.md b/examples/dap/nvim/README.md new file mode 100644 index 00000000..d4b82a20 --- /dev/null +++ b/examples/dap/nvim/README.md @@ -0,0 +1,25 @@ +# Buildg on Neovim + +Buildg supports interactive debugging of Dockerfile on Neovim. + +This depends on [nvim-dap](https://github.com/mfussenegger/nvim-dap/blob/master/doc/dap.txt). + +![Buildg on Neovim](../../../docs/images/nvim-dap.png) + +## Install + +- Requirements + - Neovim (>= 0.6) + - buildg + +- Via [packer.nvim](https://github.com/wbthomason/packer.nvim) + - Add the example launch configuration shown in [`./plugins.lua`](./plugins.lua) to your packer configuration (e.g. `~/.config/nvim/init.lua`, `~/.config/nvim/lua/plugins.lua`, etc.). + - Also refer to [`../README.md`](../README.md) for available properties in the launch configuration. + +## Usage + +``` +nvim /path/to/Dockerfile +``` + +See also [`:help dap.txt`](https://github.com/mfussenegger/nvim-dap/blob/master/doc/dap.txt) of nvim-dap for available commands. diff --git a/examples/dap/nvim/plugins.lua b/examples/dap/nvim/plugins.lua new file mode 100644 index 00000000..4c7d8d31 --- /dev/null +++ b/examples/dap/nvim/plugins.lua @@ -0,0 +1,21 @@ +require'packer'.startup(function() + use "mfussenegger/nvim-dap" +end) + +local dap = require("dap") + +dap.adapters.dockerfile = { + type = 'executable'; + command = 'buildg.sh'; + args = { 'dap', "serve" }; +} + +dap.configurations.dockerfile = { + { + type = "dockerfile", + name = "Dockerfile Configuration", + request = "launch", + stopOnEntry = true, + program = "${file}", + }, +} diff --git a/exec.go b/exec.go index 78ea70b5..31a8d6b9 100644 --- a/exec.go +++ b/exec.go @@ -6,12 +6,11 @@ import ( "io" "os" "os/signal" - "path/filepath" "syscall" "github.com/containerd/console" + "github.com/ktock/buildg/pkg/buildkit" gwclient "github.com/moby/buildkit/frontend/gateway/client" - "github.com/moby/buildkit/solver/pb" "github.com/urfave/cli" ) @@ -67,28 +66,46 @@ Only supported on RUN instructions as of now. return fmt.Errorf("flag \"-i\" and \"-t\" must be set together") // FIXME } h := hCtx.handler - r, done := h.stdin.use() + r, done := hCtx.stdin.use() defer done() - cfg := containerConfig{ - info: hCtx.info, - args: args, - stdout: os.Stdout, - stderr: os.Stderr, - tty: flagT, - mountroot: clicontext.String("mountroot"), - inputMount: clicontext.Bool("init-state"), - env: clicontext.StringSlice("env"), - cwd: clicontext.String("workdir"), + cfg := buildkit.ContainerConfig{ + Info: hCtx.info, + Args: args, + Stdout: os.Stdout, + Stderr: os.Stderr, + Tty: flagT, + Mountroot: clicontext.String("mountroot"), + InputMount: clicontext.Bool("init-state"), + Env: clicontext.StringSlice("env"), + Cwd: clicontext.String("workdir"), + WatchSignal: watchSignal, + GatewayClient: h.GatewayClient(), } if clicontext.Bool("image") { - h.imageMu.Lock() - cfg.image = h.image - h.imageMu.Unlock() + cfg.Image = h.DebuggerImage() } if flagI { - cfg.stdin = io.NopCloser(r) + cfg.Stdin = io.NopCloser(r) } - if err := h.execContainer(ctx, cfg); err != nil { + proc, cleanup, err := buildkit.ExecContainer(ctx, cfg) + if err != nil { + return err + } + defer cleanup() + errCh := make(chan error) + doneCh := make(chan struct{}) + go func() { + if err := proc.Wait(); err != nil { + errCh <- err + return + } + close(doneCh) + }() + select { + case <-doneCh: + case <-ctx.Done(): + return ctx.Err() + case err := <-errCh: return fmt.Errorf("process execution failed: %w", err) } return nil @@ -96,103 +113,6 @@ Only supported on RUN instructions as of now. } } -type containerConfig struct { - info *registeredStatus - args []string - tty bool - stdin io.ReadCloser - stdout, stderr io.WriteCloser - image gwclient.Reference - mountroot string - inputMount bool - env []string - cwd string -} - -func (h *handler) execContainer(ctx context.Context, cfg containerConfig) error { - op := cfg.info.op - mountIDs := cfg.info.mountIDs - if cfg.inputMount { - mountIDs = cfg.info.inputIDs - } - var exec *pb.ExecOp - switch op := op.GetOp().(type) { - case *pb.Op_Exec: - exec = op.Exec - default: - return fmt.Errorf("this instruction doesn't support exec; try on RUN instructions") - } - var mounts []gwclient.Mount - for i, mnt := range exec.Mounts { - mounts = append(mounts, gwclient.Mount{ - Selector: mnt.Selector, - Dest: mnt.Dest, - ResultID: mountIDs[i], - Readonly: mnt.Readonly, - MountType: mnt.MountType, - CacheOpt: mnt.CacheOpt, - SecretOpt: mnt.SecretOpt, - SSHOpt: mnt.SSHOpt, - }) - } - if cfg.image != nil { - for i := range mounts { - mounts[i].Dest = filepath.Join(cfg.mountroot, mounts[i].Dest) - } - mounts = append([]gwclient.Mount{ - { - Dest: "/", - MountType: pb.MountType_BIND, - Ref: cfg.image, - }, - }, mounts...) - } - - ctr, err := h.gwclient.NewContainer(ctx, gwclient.NewContainerRequest{ - Mounts: mounts, - NetMode: exec.Network, - Platform: op.Platform, - Constraints: op.Constraints, - }) - if err != nil { - return fmt.Errorf("failed to create debug container: %v", err) - } - defer ctr.Release(ctx) - - meta := exec.Meta - cwd := meta.Cwd - if cfg.cwd != "" { - cwd = cfg.cwd - } - proc, err := ctr.Start(ctx, gwclient.StartRequest{ - Args: cfg.args, - Env: append(meta.Env, cfg.env...), - User: meta.User, - Cwd: cwd, - Tty: cfg.tty, - Stdin: cfg.stdin, - Stdout: cfg.stdout, - Stderr: cfg.stderr, - SecurityMode: exec.Security, - }) - if err != nil { - return fmt.Errorf("failed to start container: %w", err) - } - - var con console.Console - if cfg.tty { - con := console.Current() - if err := con.SetRaw(); err != nil { - return fmt.Errorf("failed to configure terminal: %v", err) - } - defer con.Reset() - } - ioCtx, ioCancel := context.WithCancel(ctx) - defer ioCancel() - watchSignal(ioCtx, proc, con) - return proc.Wait() -} - func watchSignal(ctx context.Context, proc gwclient.ContainerProcess, con console.Console) { globalSignalHandler.disable() ch := make(chan os.Signal, 1) diff --git a/exit.go b/exit.go new file mode 100644 index 00000000..29e997e5 --- /dev/null +++ b/exit.go @@ -0,0 +1,21 @@ +package main + +import ( + "context" + + "github.com/ktock/buildg/pkg/buildkit" + "github.com/urfave/cli" +) + +func exitCommand(ctx context.Context, hCtx *handlerContext) cli.Command { + return cli.Command{ + Name: "exit", + Aliases: []string{"quit", "q"}, + Usage: "exit command", + Action: func(clicontext *cli.Context) error { + hCtx.continueRead = false + hCtx.err = buildkit.ErrExit + return nil + }, + } +} diff --git a/go.mod b/go.mod index 0dd72830..e2d737c7 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,11 @@ go 1.18 require ( github.com/containerd/console v1.0.3 github.com/containerd/containerd v1.6.6 + github.com/containerd/fifo v1.0.0 + github.com/google/go-dap v0.6.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/moby/buildkit v0.0.0-00010101000000-000000000000 + github.com/moby/sys/signal v0.6.0 github.com/opencontainers/go-digest v1.0.0 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.1 @@ -56,7 +59,6 @@ require ( github.com/moby/locker v1.0.1 // indirect github.com/moby/sys/mount v0.3.0 // indirect github.com/moby/sys/mountinfo v0.6.0 // indirect - github.com/moby/sys/signal v0.6.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect github.com/opencontainers/runc v1.1.2 // indirect diff --git a/go.sum b/go.sum index 02f7e74c..d335eb83 100644 --- a/go.sum +++ b/go.sum @@ -482,6 +482,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= +github.com/google/go-dap v0.6.0 h1:Y1RHGUtv3R8y6sXq2dtGRMYrFB2hSqyFVws7jucrzX4= +github.com/google/go-dap v0.6.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= diff --git a/handler.go b/handler.go deleted file mode 100644 index e1b10088..00000000 --- a/handler.go +++ /dev/null @@ -1,395 +0,0 @@ -package main - -import ( - "bufio" - "context" - "fmt" - "io" - "os" - "sort" - "sync" - - "github.com/google/shlex" - "github.com/moby/buildkit/client/llb" - gwclient "github.com/moby/buildkit/frontend/gateway/client" - "github.com/moby/buildkit/solver/pb" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/urfave/cli" -) - -const ( - promptEnvKey = "BUILDG_PS1" - defaultPrompt = "(buildg) " -) - -var errExit = errors.New("exit") - -type handlerContext struct { - handler *handler - info *registeredStatus - locs []*location - continueRead bool - err error -} - -type handlerCommandFn func(ctx context.Context, hCtx *handlerContext) cli.Command - -var handlerCommands = []handlerCommandFn{ - breakCommand, - breakpointsCommand, - clearCommand, - clearAllCommand, - nextCommand, - continueCommand, - execCommand, - listCommand, - exitCommand, -} - -func exitCommand(ctx context.Context, hCtx *handlerContext) cli.Command { - return cli.Command{ - Name: "exit", - Aliases: []string{"quit", "q"}, - Usage: "exit command", - Action: func(clicontext *cli.Context) error { - hCtx.continueRead = false - hCtx.err = errExit - return nil - }, - } -} - -func withDebug(f gwclient.BuildFunc, debugController *debugController, debugImage string) (gwclient.BuildFunc, <-chan error) { - handleError := make(chan error) - return func(ctx context.Context, c gwclient.Client) (res *gwclient.Result, err error) { - stdinR := newSharedReader(os.Stdin) - defer stdinR.close() - handler := &handler{ - gwclient: c, - stdin: stdinR, - breakpoints: &breakpoints{ - breakpoints: map[string]breakpoint{ - "on-fail": newOnFailBreakpoint(), - }, - }, - prompt: defaultPrompt, - } - if p := os.Getenv(promptEnvKey); p != "" { - handler.prompt = p - } - - doneCh := make(chan struct{}) - defer close(doneCh) - go func() { - defer close(handleError) - select { - case handleError <- debugController.handle(context.Background(), handler): - case <-doneCh: - } - }() - - if debugImage != "" { - def, err := llb.Image(debugImage, withDescriptor(map[string]string{"debug": "no"})).Marshal(ctx) - if err != nil { - return nil, err - } - r, err := c.Solve(ctx, gwclient.SolveRequest{ - Definition: def.ToPB(), - }) - if err != nil { - return nil, err - } - handler.imageMu.Lock() - handler.image = r.Ref - handler.imageMu.Unlock() - } - - return f(ctx, debugController.gatewayClientWithDebug(c)) - }, handleError -} - -type withDescriptor map[string]string - -func (o withDescriptor) SetImageOption(ii *llb.ImageInfo) { - if ii.Metadata.Description == nil { - ii.Metadata.Description = make(map[string]string) - } - for k, v := range o { - ii.Metadata.Description[k] = v - } -} - -type handler struct { - gwclient gwclient.Client - stdin *sharedReader - - breakpoints *breakpoints - breakEachVertex bool - - initialized bool - - mu sync.Mutex - - image gwclient.Reference - imageMu sync.Mutex - - prompt string -} - -func (h *handler) handle(ctx context.Context, info *registeredStatus, locs []*location) error { - globalProgressWriter.disable() - defer globalProgressWriter.enable() - h.mu.Lock() - defer h.mu.Unlock() - - if len(locs) == 0 { - logrus.Warnf("no location info: %v", locs) - return nil - } - isBreakpoint := false - for key, bp := range h.breakpoints.check(ctx, breakpointContext{info, locs}) { - fmt.Printf("Breakpoint[%s]: %s\n", key, bp.description) - isBreakpoint = true - } - if h.initialized && !h.breakEachVertex && !isBreakpoint { - logrus.Debugf("skipping non-breakpoint: %v", locs) - return nil - } - if !h.initialized { - logrus.Infof("debug session started. type \"help\" for command reference.") - h.initialized = true - } - printLines(h, locs, defaultListRange, defaultListRange, false) - for { - ln, err := h.readLine(ctx) - if err != nil { - return err - } - if args, err := shlex.Split(ln); err != nil { - logrus.WithError(err).Warnf("failed to parse line") - } else if len(args) > 0 { - cont, err := h.dispatch(ctx, info, locs, args) - if err != nil { - return err - } - if !cont { - break - } - } - } - return nil -} - -func (h *handler) readLine(ctx context.Context) (string, error) { - fmt.Printf(h.prompt) - r, done := h.stdin.use() - defer done() - lnCh := make(chan string) - errCh := make(chan error) - scanner := bufio.NewScanner(r) - go func() { - if scanner.Scan() { - lnCh <- scanner.Text() - } else if err := scanner.Err(); err != nil { - errCh <- err - } else { - // EOF thus exit - errCh <- errExit - } - }() - var ln string - select { - case ln = <-lnCh: - case err := <-errCh: - return "", err - case <-ctx.Done(): - return "", fmt.Errorf("canceled reading line: %w", ctx.Err()) - } - return ln, scanner.Err() -} - -func (h *handler) dispatch(ctx context.Context, info *registeredStatus, locs []*location, args []string) (continueRead bool, err error) { - if len(args) == 0 || args[0] == "" { - return true, nil // nop - } - continueRead = true - app := cli.NewApp() - rootCmd := "buildg" - app.Name = rootCmd - app.HelpName = rootCmd - app.Usage = "Interactive debugger for Dockerfile" - app.UsageText = "command [command options] [arguments...]" - app.ExitErrHandler = func(context *cli.Context, err error) {} - app.UseShortOptionHandling = true - hCtx := &handlerContext{ - handler: h, - info: info, - locs: locs, - continueRead: true, - err: nil, - } - for _, fn := range handlerCommands { - app.Commands = append(app.Commands, fn(ctx, hCtx)) - } - if cliErr := app.Run(append([]string{rootCmd}, args...)); cliErr != nil { - fmt.Printf("%v\n", cliErr) - } - return hCtx.continueRead, hCtx.err -} - -type breakpoint interface { - isTarget(ctx context.Context, info breakpointContext) (yes bool, description string, err error) - addMark(source *pb.SourceInfo, line int64) bool - String() string -} - -type breakpointContext struct { - status *registeredStatus - locs []*location -} - -type breakpoints struct { - breakpoints map[string]breakpoint - breakpointIndex int -} - -func (b *breakpoints) add(key string, bp breakpoint) error { - if b.breakpoints == nil { - b.breakpoints = make(map[string]breakpoint) - } - if key == "" { - currentIdx := b.breakpointIndex - b.breakpointIndex++ - key = fmt.Sprintf("%d", currentIdx) - } - if _, ok := b.breakpoints[key]; ok { - return fmt.Errorf("breakpoint %q already exists: %v", key, b) - } - b.breakpoints[key] = bp - return nil -} - -func (b *breakpoints) get(key string) (breakpoint, bool) { - bp, ok := b.breakpoints[key] - return bp, ok -} - -func (b *breakpoints) clear(key string) { - delete(b.breakpoints, key) -} - -func (b *breakpoints) clearAll() { - b.breakpoints = nil - b.breakpointIndex = 0 -} - -func (b *breakpoints) forEach(f func(key string, bp breakpoint) bool) { - var keys []string - for k := range b.breakpoints { - keys = append(keys, k) - } - sort.Strings(keys) - for _, k := range keys { - if !f(k, b.breakpoints[k]) { - return - } - } -} - -type breakpointInfo struct { - breakpoint breakpoint - description string -} - -func (b *breakpoints) check(ctx context.Context, info breakpointContext) (hit map[string]breakpointInfo) { - hit = make(map[string]breakpointInfo) - b.forEach(func(key string, bp breakpoint) bool { - if yes, desc, err := bp.isTarget(ctx, info); err != nil { - logrus.WithError(err).Warnf("failed to check breakpoint") - } else if yes { - hit[key] = breakpointInfo{bp, desc} - } - return true - }) - return -} - -type sharedReader struct { - r io.Reader - currentW *io.PipeWriter - currentWMu sync.Mutex - useMu sync.Mutex - - closeOnce sync.Once - done chan struct{} -} - -func newSharedReader(r io.Reader) *sharedReader { - s := &sharedReader{ - r: r, - done: make(chan struct{}), - } - go func() { - buf := make([]byte, 4096) - for { - select { - case <-s.done: - return - default: - } - n, err := s.r.Read(buf) - if err != nil { - if err != io.EOF { - logrus.WithError(err).Warnf("sharedReader: failed to read stdin data") - return - } - s.currentWMu.Lock() - if w := s.currentW; w != nil { - w.Close() - s.currentW = nil - } - s.currentWMu.Unlock() - continue - } - dest := io.Discard - s.currentWMu.Lock() - w := s.currentW - s.currentWMu.Unlock() - if w != nil { - dest = w - } - if _, err := dest.Write(buf[:n]); err != nil { - logrus.WithError(err).Warnf("sharedReader: failed to write stdin data") - } - } - }() - return s -} - -func (s *sharedReader) use() (r io.Reader, done func()) { - s.useMu.Lock() - pr, pw := io.Pipe() - s.currentWMu.Lock() - if s.currentW != nil { - panic("non nil writer") - } - s.currentW = pw - s.currentWMu.Unlock() - return pr, func() { - s.currentWMu.Lock() - if s.currentW != nil { - s.currentW = nil - pw.Close() - pr.Close() - } - s.currentWMu.Unlock() - s.useMu.Unlock() - } -} - -func (s *sharedReader) close() { - s.closeOnce.Do(func() { - close(s.done) - }) -} diff --git a/list.go b/list.go index 73e420b5..aae69f2d 100644 --- a/list.go +++ b/list.go @@ -5,7 +5,9 @@ import ( "bytes" "context" "fmt" + "io" + "github.com/ktock/buildg/pkg/buildkit" "github.com/moby/buildkit/solver/pb" "github.com/urfave/cli" ) @@ -48,23 +50,23 @@ func listCommand(ctx context.Context, hCtx *handlerContext) cli.Command { if a := clicontext.Int("A"); a != defaultListRange { after = a } - printLines(hCtx.handler, hCtx.locs, before, after, clicontext.Bool("all")) + printLines(hCtx.handler, hCtx.stdout, hCtx.locs, before, after, clicontext.Bool("all")) return nil }, } } -func printLines(h *handler, locs []*location, before, after int, all bool) { +func printLines(h *buildkit.Handler, w io.Writer, locs []*buildkit.Location, before, after int, all bool) { sources := make(map[*pb.SourceInfo][]*pb.Range) for _, l := range locs { - sources[l.source] = append(sources[l.source], l.ranges...) + sources[l.Source] = append(sources[l.Source], l.Ranges...) } for source, ranges := range sources { if len(ranges) == 0 { continue } - fmt.Printf("Filename: %q\n", source.Filename) + fmt.Fprintf(w, "Filename: %q\n", source.Filename) scanner := bufio.NewScanner(bytes.NewReader(source.Data)) lastLinePrinted := false firstPrint := true @@ -86,12 +88,12 @@ func printLines(h *handler, locs []*location, before, after int, all bool) { continue } if !lastLinePrinted && !firstPrint { - fmt.Println("----------------") + fmt.Fprintln(w, "----------------") } prefix := " " - h.breakpoints.forEach(func(key string, b breakpoint) bool { - if b.addMark(source, int64(i)) { + h.Breakpoints().ForEach(func(key string, b buildkit.Breakpoint) bool { + if b.IsMarked(source, int64(i)) { prefix = "*" return false } @@ -101,7 +103,7 @@ func printLines(h *handler, locs []*location, before, after int, all bool) { if target { prefix2 = "=>" } - fmt.Println(prefix + prefix2 + fmt.Sprintf("%4d| ", i) + scanner.Text()) + fmt.Fprintln(w, prefix+prefix2+fmt.Sprintf("%4d| ", i)+scanner.Text()) lastLinePrinted = true firstPrint = false } diff --git a/main.go b/main.go index 6e0613ef..ba25f76b 100644 --- a/main.go +++ b/main.go @@ -3,51 +3,29 @@ package main import ( "context" "fmt" + "io" "net" "os" "os/signal" "path/filepath" "strings" "sync" - "text/tabwriter" + "syscall" "time" "github.com/containerd/console" "github.com/containerd/containerd/pkg/userns" - "github.com/containerd/containerd/remotes/docker" - ctdsnapshots "github.com/containerd/containerd/snapshots" - "github.com/containerd/containerd/snapshots/native" - "github.com/containerd/containerd/snapshots/overlay" - "github.com/containerd/containerd/snapshots/overlay/overlayutils" + "github.com/ktock/buildg/pkg/buildkit" + "github.com/ktock/buildg/pkg/dap" "github.com/ktock/buildg/pkg/version" - "github.com/moby/buildkit/cache/remotecache" - localremotecache "github.com/moby/buildkit/cache/remotecache/local" - registryremotecache "github.com/moby/buildkit/cache/remotecache/registry" "github.com/moby/buildkit/client" "github.com/moby/buildkit/cmd/buildctl/build" "github.com/moby/buildkit/cmd/buildkitd/config" - "github.com/moby/buildkit/control" - "github.com/moby/buildkit/executor/oci" - "github.com/moby/buildkit/frontend" - dockerfile "github.com/moby/buildkit/frontend/dockerfile/builder" - "github.com/moby/buildkit/frontend/gateway" "github.com/moby/buildkit/session" "github.com/moby/buildkit/session/auth/authprovider" "github.com/moby/buildkit/session/sshforward/sshprovider" - "github.com/moby/buildkit/solver/bboltcachestorage" - "github.com/moby/buildkit/util/grpcerrors" - "github.com/moby/buildkit/util/network/cniprovider" - "github.com/moby/buildkit/util/network/netproviders" - "github.com/moby/buildkit/util/progress/progresswriter" - "github.com/moby/buildkit/util/resolver" - "github.com/moby/buildkit/worker" - "github.com/moby/buildkit/worker/base" - "github.com/moby/buildkit/worker/runc" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/urfave/cli" - "golang.org/x/sync/errgroup" - "google.golang.org/grpc" ) func main() { @@ -99,6 +77,7 @@ func main() { newDebugCommand(), newDuCommand(), newPruneCommand(), + newDapCommand(), newVersionCommand(), } app.Before = func(context *cli.Context) error { @@ -189,6 +168,69 @@ func newDuCommand() cli.Command { } } +func newDapCommand() cli.Command { + return cli.Command{ + Name: "dap", + Usage: "DAP utilities", + Subcommands: []cli.Command{ + newDapServeCommand(), + newDapAttachContainerCommand(), + newDapPruneCommand(), + newDapDuCommand(), + }, + } +} + +func newDapPruneCommand() cli.Command { + return cli.Command{ + Name: "prune", + Usage: "prune DAP cache", + Action: dapPruneAction, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "all", + Usage: "Prune including internal/frontend references", + }, + }, + } +} + +func newDapAttachContainerCommand() cli.Command { + return cli.Command{ + Name: dap.AttachContainerCommand, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "set-tty-raw", + Usage: "Set tty raw", + }, + }, + Action: dapAttachContainerAction, + Hidden: true, + } +} + +func newDapServeCommand() cli.Command { + return cli.Command{ + Name: "serve", + Usage: "serve DAP via stdio", + Action: dapServeAction, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "log-file", + Usage: "Path to the file to output logs", + }, + }, + } +} + +func newDapDuCommand() cli.Command { + return cli.Command{ + Name: "du", + Usage: "show disk usage of DAP cache", + Action: dapDuAction, + } +} + // TODO: avoid global var var globalSignalHandler *signalHandler @@ -289,95 +331,35 @@ func debugAction(clicontext *cli.Context) error { defer doneCfg() logrus.Debugf("root dir: %q", cfg.Root) - // Prepare controller - debugController := newDebugController() - var c *client.Client - var doneController func() - createdCh := make(chan error) - errCh := make(chan error) - go func() { - var err error - c, doneController, err = newController(ctx, cfg, debugController) - if err != nil { - errCh <- err - return - } - close(createdCh) - }() - select { - case <-ctx.Done(): - err := ctx.Err() - return err - case <-time.After(3 * time.Second): - return fmt.Errorf("timed out to access cache storage. other debug session is running?") - case err := <-errCh: - return err - case <-createdCh: - } - defer doneController() - - // Prepare progress writer - progressCtx := context.TODO() - pw, err := progresswriter.NewPrinter(progressCtx, globalProgressWriter, "plain") - if err != nil { - return err - } - mw := progresswriter.NewMultiWriter(pw) - var writers []progresswriter.Writer - for _, at := range solveOpt.Session { - if s, ok := at.(interface { - SetLogger(progresswriter.Logger) - }); ok { - w := mw.WithPrefix("", false) - s.SetLogger(func(s *client.SolveStatus) { - w.Status() <- s - }) - writers = append(writers, w) - } - } - - // Start build with debugging - buildFunc, handleErrorCh := withDebug(dockerfile.Build, debugController, clicontext.String("image")) - eg, egCtx := errgroup.WithContext(ctx) - doneCh := make(chan struct{}) - eg.Go(func() (err error) { - select { - case err = <-handleErrorCh: - case <-doneCh: - } - if err != nil { - return fmt.Errorf("handler error: %w", err) - } - return nil - }) - eg.Go(func() error { - defer close(doneCh) - defer func() { - for _, w := range writers { - close(w.Status()) - } - }() - if _, err := c.Build(egCtx, *solveOpt, "", buildFunc, progresswriter.ResetTime(mw.WithPrefix("", false)).Status()); err != nil { - return fmt.Errorf("failed to build: %w", err) - } - return nil - }) - eg.Go(func() error { - <-pw.Done() - return pw.Err() + r := newSharedReader(os.Stdin) + defer r.close() + return buildkit.Debug(ctx, cfg, solveOpt, globalProgressWriter, buildkit.DebugConfig{ + BreakpointHandler: newCommandHandler(r, os.Stdout).breakHandler, + DebugImage: clicontext.String("image"), + StopOnEntry: true, }) +} - if err := eg.Wait(); err != nil { - if errors.Is(err, errExit) { +func pruneAction(clicontext *cli.Context) error { + ctx, ctxCancel := context.WithCancel(context.Background()) + globalSignalHandler = &signalHandler{ + handler: func(sig os.Signal) error { + ctxCancel() return nil - } - return err + }, } + globalSignalHandler.start() - return nil + // Parse config options + cfg, rootDir, err := parseGlobalWorkerConfig(clicontext) + if err != nil { + return err + } + cfg.Root = defaultServeRootDir(rootDir) + return buildkit.Prune(ctx, cfg, clicontext.Bool("all"), os.Stdout) } -func pruneAction(clicontext *cli.Context) error { +func duAction(clicontext *cli.Context) error { ctx, ctxCancel := context.WithCancel(context.Background()) globalSignalHandler = &signalHandler{ handler: func(sig os.Signal) error { @@ -393,84 +375,122 @@ func pruneAction(clicontext *cli.Context) error { return err } cfg.Root = defaultServeRootDir(rootDir) - c, done, err := newController(ctx, cfg, nil) + return buildkit.Du(ctx, cfg, os.Stdout) +} + +func dapServeAction(clicontext *cli.Context) error { + logrus.SetOutput(os.Stderr) + if logFile := clicontext.String("log-file"); logFile != "" { + f, _ := os.Create(logFile) + logrus.SetOutput(f) + } + cfg, rootDir, err := parseGlobalWorkerConfig(clicontext) if err != nil { return err } - defer done() - ch := make(chan client.UsageInfo) - donePrint := make(chan struct{}) - tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, '\t', 0) - first := true - total := int64(0) - go func() { - defer close(donePrint) - for du := range ch { - total += du.Size - if first { - fmt.Fprintln(tw, "ID\tRECLAIMABLE\tSIZE") - first = false - } - fmt.Fprintf(tw, "%-71s\t%-11v\t%s\n", du.ID, !du.InUse, fmt.Sprintf("%d B", du.Size)) - tw.Flush() + // Determine root dir to use + rootDir = filepath.Join(rootDir, "dap") // use a dedicated root dir for dap as of now; TODO: share cache globally + serveRoot := filepath.Join(rootDir, "buildg") + if err := os.MkdirAll(serveRoot, 0700); err != nil { + return err + } + f, err := os.Create(filepath.Join(serveRoot, "lock")) + if err != nil { + return err + } + defer f.Close() + var cleanupFunc func() error + var cleanupAll bool + if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err == nil { + logrus.Debugf("acquired lock on the root dir %q", serveRoot) + cleanupFunc = func() error { + return syscall.Flock(int(f.Fd()), syscall.LOCK_UN) + } + } else { + if err != syscall.EWOULDBLOCK { + return err + } + logrus.Warnf("failed to acquire lock on the root dir; other buildg dap session is running?; using a temporary dir") + // failed to get lock; use temporary root + // NOTE: all previous cache is ignored as of now. + // TODO: eliminate this limitation + serveRoot, err = os.MkdirTemp(rootDir, "buildg") + if err != nil { + return err + } + cleanupAll = true + cleanupFunc = func() error { + return os.RemoveAll(serveRoot) } - }() - var opts []client.PruneOption - if clicontext.Bool("all") { - opts = append(opts, client.PruneAll) } - - err = c.Prune(ctx, ch, opts...) - close(ch) - <-donePrint + cfg.Root = serveRoot + s, err := dap.NewServer(&stdioConn{os.Stdin, os.Stdout}, cfg, cleanupFunc, cleanupAll) if err != nil { return err } - fmt.Fprintf(tw, "Total:\t%d B\n", total) - tw.Flush() - + // NOTE: disconnect request will exit this process + // TODO: disconnect should return the control to this func rather than exit + if err := s.Serve(); err != nil { + logrus.WithError(err).Warnf("failed to serve") // TODO: should return error + } + logrus.Info("finishing server") return nil } -func duAction(clicontext *cli.Context) error { - ctx, ctxCancel := context.WithCancel(context.Background()) - globalSignalHandler = &signalHandler{ - handler: func(sig os.Signal) error { - ctxCancel() - return nil - }, +func dapAttachContainerAction(clicontext *cli.Context) error { + root := clicontext.Args().First() + if root == "" { + return fmt.Errorf("root needs to be specified") } - globalSignalHandler.start() - - // Parse config options + return dap.AttachContainerIO(root, clicontext.Bool("set-tty-raw")) +} +func dapPruneAction(clicontext *cli.Context) error { cfg, rootDir, err := parseGlobalWorkerConfig(clicontext) if err != nil { return err } - cfg.Root = defaultServeRootDir(rootDir) - c, done, err := newController(ctx, cfg, nil) + rootDir = filepath.Join(rootDir, "dap") // use a dedicated root dir for dap as of now; TODO: share cache globally + serveRoot := filepath.Join(rootDir, "buildg") + f, err := os.Create(filepath.Join(serveRoot, "lock")) if err != nil { return err } - defer done() + defer f.Close() + if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err == nil { + logrus.Debugf("acquired lock on the root dir %q", serveRoot) + defer syscall.Flock(int(f.Fd()), syscall.LOCK_UN) + } else if err == syscall.EWOULDBLOCK { + return fmt.Errorf("failed to acquire lock on the root dir; other buildg dap session is running?") + } else { + return err + } + cfg.Root = serveRoot + return buildkit.Prune(context.TODO(), cfg, clicontext.Bool("all"), os.Stdout) +} - tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, '\t', 0) - fmt.Fprintln(tw, "ID\tRECLAIMABLE\tSIZE") - dus, err := c.DiskUsage(ctx) +func dapDuAction(clicontext *cli.Context) error { + cfg, rootDir, err := parseGlobalWorkerConfig(clicontext) if err != nil { return err } - total := int64(0) - for _, du := range dus { - total += du.Size - fmt.Fprintf(tw, "%-71s\t%-11v\t%s\n", du.ID, !du.InUse, fmt.Sprintf("%d B", du.Size)) - tw.Flush() + rootDir = filepath.Join(rootDir, "dap") // use a dedicated root dir for dap as of now; TODO: share cache globally + serveRoot := filepath.Join(rootDir, "buildg") + f, err := os.Create(filepath.Join(serveRoot, "lock")) + if err != nil { + return err } - fmt.Fprintf(tw, "Total:\t%d B\n", total) - tw.Flush() - - return nil + defer f.Close() + if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err == nil { + logrus.Debugf("acquired lock on the root dir %q", serveRoot) + defer syscall.Flock(int(f.Fd()), syscall.LOCK_UN) + } else if err == syscall.EWOULDBLOCK { + return fmt.Errorf("failed to acquire lock on the root dir; other buildg dap session is running?") + } else { + return err + } + cfg.Root = serveRoot + return buildkit.Du(context.TODO(), cfg, os.Stdout) } func parseGlobalWorkerConfig(clicontext *cli.Context) (cfg *config.Config, rootDir string, err error) { @@ -606,137 +626,6 @@ func parseSolveOpt(clicontext *cli.Context) (*client.SolveOpt, error) { }, nil } -// newController creates controller client based on the passed config. -// optional debugController allows adding debugger support to the controller. -func newController(ctx context.Context, cfg *config.Config, debugController *debugController) (_ *client.Client, _ func(), retErr error) { - if cfg.Root == "" { - return nil, nil, fmt.Errorf("root directory must be specified") - } - var closeFuncs []func() - done := func() { - for i := len(closeFuncs) - 1; i >= 0; i-- { - closeFuncs[i]() - } - } - defer func() { - if retErr != nil { - done() - } - }() - - // Initialize OCI worker with debugging support - w, resolverFunc, err := newWorker(ctx, cfg) - if err != nil { - return nil, nil, err - } - if debugController != nil { - w = debugController.debugWorker(w) - } - wc := &worker.Controller{} - if err := wc.Add(w); err != nil { - return nil, nil, err - } - - // Create controller - sessionManager, err := session.NewManager() - if err != nil { - return nil, nil, err - } - cacheStorage, err := bboltcachestorage.NewStore(filepath.Join(cfg.Root, "cache.db")) - if err != nil { - return nil, nil, err - } - gwfrontend := gateway.NewGatewayFrontend(wc) - if debugController != nil { - gwfrontend = debugController.frontendWithDebug(gwfrontend) - } - frontends := map[string]frontend.Frontend{} - frontends["gateway.v0"] = gwfrontend - remoteCacheImporterFuncs := map[string]remotecache.ResolveCacheImporterFunc{ - "registry": registryremotecache.ResolveCacheImporterFunc(sessionManager, w.ContentStore(), resolverFunc), - "local": localremotecache.ResolveCacheImporterFunc(sessionManager), - } - controller, err := control.NewController(control.Opt{ - SessionManager: sessionManager, - WorkerController: wc, - CacheKeyStorage: cacheStorage, - Frontends: frontends, - ResolveCacheImporterFuncs: remoteCacheImporterFuncs, - ResolveCacheExporterFuncs: map[string]remotecache.ResolveCacheExporterFunc{}, // TODO: support remote cahce exporter - Entitlements: []string{}, // TODO - }) - if err != nil { - return nil, nil, err - } - - // Create client for the controller - controlServer := grpc.NewServer(grpc.UnaryInterceptor(grpcerrors.UnaryServerInterceptor), grpc.StreamInterceptor(grpcerrors.StreamServerInterceptor)) - controller.Register(controlServer) - lt := newPipeListener() - go controlServer.Serve(lt) - closeFuncs = append(closeFuncs, controlServer.GracefulStop) - c, err := client.New(ctx, "", client.WithContextDialer(lt.dial)) - if err != nil { - return nil, nil, err - } - - return c, done, nil -} - -func newWorker(ctx context.Context, cfg *config.Config) (worker.Worker, docker.RegistryHosts, error) { - root := cfg.Root - if root == "" { - return nil, nil, fmt.Errorf("failed to init worker: root directory must be set") - } - snName := cfg.Workers.OCI.Snapshotter - if snName == "auto" { - if err := overlayutils.Supported(root); err == nil { - snName = "overlayfs" - } else { - logrus.Debugf("overlayfs isn't supported. falling back to native snapshotter") - snName = "native" - } - logrus.Debugf("%q is used as the auto snapshotter", snName) - } - var snFactory runc.SnapshotterFactory - switch snName { - case "native": - snFactory = runc.SnapshotterFactory{ - Name: snName, - New: native.NewSnapshotter, - } - case "overlayfs": - snFactory = runc.SnapshotterFactory{ - Name: snName, - New: func(root string) (ctdsnapshots.Snapshotter, error) { - return overlay.NewSnapshotter(root, overlay.AsynchronousRemove) - }, - } - default: - return nil, nil, fmt.Errorf("unknown snapshotter %q", snName) - } - rootless := cfg.Workers.OCI.Rootless - nc := netproviders.Opt{ - Mode: cfg.Workers.OCI.Mode, - CNI: cniprovider.Opt{ - Root: root, - ConfigPath: cfg.Workers.OCI.CNIConfigPath, - BinaryDir: cfg.Workers.OCI.CNIBinaryPath, - }, - } - opt, err := runc.NewWorkerOpt(root, snFactory, rootless, oci.ProcessSandbox, nil, nil, nc, nil, "", "", nil, "", "") - if err != nil { - return nil, nil, err - } - resolverFunc := resolver.NewRegistryConfig(cfg.Registries) - opt.RegistryHosts = resolverFunc - w, err := base.NewWorker(ctx, opt) - if err != nil { - return nil, nil, err - } - return w, resolverFunc, nil -} - func defaultServeRootDir(rootDir string) string { return filepath.Join(rootDir, "data") } @@ -755,65 +644,23 @@ func rootDataDir(rootless bool) (string, error) { return filepath.Join(home, ".local/share/buildg"), nil } -func newPipeListener() *pipeListener { - return &pipeListener{ - ch: make(chan net.Conn), - done: make(chan struct{}), - } -} - -type pipeListener struct { - ch chan net.Conn - done chan struct{} - closed bool - closeOnce sync.Once - closedMu sync.Mutex +type stdioConn struct { + io.Reader + io.Writer } -func (l *pipeListener) dial(ctx context.Context, _ string) (net.Conn, error) { - if l.isClosed() { - return nil, fmt.Errorf("closed") - } - c1, c2 := net.Pipe() - select { - case <-l.done: - return nil, fmt.Errorf("closed") - case l.ch <- c1: - } - return c2, nil +func (c *stdioConn) Read(b []byte) (n int, err error) { + return c.Reader.Read(b) } - -func (l *pipeListener) Accept() (net.Conn, error) { - if l.isClosed() { - return nil, fmt.Errorf("closed") - } - select { - case <-l.done: - return nil, fmt.Errorf("closed") - case conn := <-l.ch: - return conn, nil - } -} - -func (l *pipeListener) Close() error { - l.closeOnce.Do(func() { - l.closedMu.Lock() - l.closed = true - close(l.done) - l.closedMu.Unlock() - }) - return nil -} - -func (l *pipeListener) isClosed() bool { - l.closedMu.Lock() - defer l.closedMu.Unlock() - return l.closed -} - -func (l *pipeListener) Addr() net.Addr { - return dummyAddr{} +func (c *stdioConn) Write(b []byte) (n int, err error) { + return c.Writer.Write(b) } +func (c *stdioConn) Close() error { return nil } +func (c *stdioConn) LocalAddr() net.Addr { return dummyAddr{} } +func (c *stdioConn) RemoteAddr() net.Addr { return dummyAddr{} } +func (c *stdioConn) SetDeadline(t time.Time) error { return nil } +func (c *stdioConn) SetReadDeadline(t time.Time) error { return nil } +func (c *stdioConn) SetWriteDeadline(t time.Time) error { return nil } type dummyAddr struct{} diff --git a/main_test.go b/main_test.go index ac8c098c..2011bd14 100644 --- a/main_test.go +++ b/main_test.go @@ -11,8 +11,6 @@ import ( "github.com/ktock/buildg/pkg/testutil" ) -const buildgTestTmpDirEnv = "TEST_BUILDG_TMP_DIR" - func TestCacheReuse(t *testing.T) { t.Parallel() tmpCtx, doneTmpCtx := testutil.NewTempContext(t, fmt.Sprintf(`FROM %s @@ -22,7 +20,7 @@ RUN date > / `, testutil.Mirror("busybox:1.32.0"))) defer doneTmpCtx() - tmpRoot, err := os.MkdirTemp(os.Getenv(buildgTestTmpDirEnv), "buildg-test-tmproot") + tmpRoot, err := os.MkdirTemp(os.Getenv(testutil.BuildgTestTmpDirEnv), "buildg-test-tmproot") if err != nil { t.Fatal(err) return @@ -120,7 +118,7 @@ RUN date > /a `, testutil.Mirror("busybox:1.32.0"))) defer doneTmpCtx() - tmpRoot, err := os.MkdirTemp(os.Getenv(buildgTestTmpDirEnv), "buildg-test-tmproot") + tmpRoot, err := os.MkdirTemp(os.Getenv(testutil.BuildgTestTmpDirEnv), "buildg-test-tmproot") if err != nil { t.Fatal(err) return @@ -181,7 +179,7 @@ RUN --mount=type=bind,target=/root/mnt cat /root/mnt/data/hi > /a && date > /b t.Fatal(err) } - tmpRoot, err := os.MkdirTemp(os.Getenv(buildgTestTmpDirEnv), "buildg-test-tmproot") + tmpRoot, err := os.MkdirTemp(os.Getenv(testutil.BuildgTestTmpDirEnv), "buildg-test-tmproot") if err != nil { t.Fatal(err) return @@ -226,7 +224,7 @@ RUN date > /ok `, testutil.Mirror("busybox:1.32.0"), testutil.Mirror("busybox:1.32.0"))) defer doneTmpCtx() - tmpRoot, err := os.MkdirTemp(os.Getenv(buildgTestTmpDirEnv), "buildg-test-tmproot") + tmpRoot, err := os.MkdirTemp(os.Getenv(testutil.BuildgTestTmpDirEnv), "buildg-test-tmproot") if err != nil { t.Fatal(err) return diff --git a/pkg/buildkit/breakpoint.go b/pkg/buildkit/breakpoint.go new file mode 100644 index 00000000..28aabfc2 --- /dev/null +++ b/pkg/buildkit/breakpoint.go @@ -0,0 +1,141 @@ +package buildkit + +import ( + "context" + "fmt" + "sort" + + "github.com/moby/buildkit/solver/pb" +) + +type BreakpointHandler func(ctx context.Context, bCtx BreakContext) error + +type BreakContext struct { + Handler *Handler + Info *RegisteredStatus + Locs []*Location + Hits map[string]BreakpointInfo +} + +type BreakpointInfo struct { + Description string + Hits []*Location +} + +type Breakpoint interface { + isTarget(ctx context.Context, status *RegisteredStatus, locs []*Location) (yes bool, description string, hitLocations []*Location, err error) + IsMarked(source *pb.SourceInfo, line int64) bool + String() string +} + +func NewBreakpoints() *Breakpoints { + return &Breakpoints{ + breakpoints: make(map[string]Breakpoint), + } +} + +type Breakpoints struct { + breakpoints map[string]Breakpoint + breakpointIndex int +} + +func (b *Breakpoints) Add(key string, bp Breakpoint) (string, error) { + if b.breakpoints == nil { + b.breakpoints = make(map[string]Breakpoint) + } + if key == "" { + currentIdx := b.breakpointIndex + b.breakpointIndex++ + key = fmt.Sprintf("%d", currentIdx) + } + if _, ok := b.breakpoints[key]; ok { + return "", fmt.Errorf("breakpoint %q already exists: %v", key, b) + } + b.breakpoints[key] = bp + return key, nil +} + +func (b *Breakpoints) Get(key string) (Breakpoint, bool) { + bp, ok := b.breakpoints[key] + return bp, ok +} + +func (b *Breakpoints) Clear(key string) { + delete(b.breakpoints, key) +} + +func (b *Breakpoints) ClearAll() { + b.breakpoints = nil + b.breakpointIndex = 0 +} + +func (b *Breakpoints) ForEach(f func(key string, bp Breakpoint) bool) { + var keys []string + for k := range b.breakpoints { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + if !f(k, b.breakpoints[k]) { + return + } + } +} + +func NewOnFailBreakpoint() Breakpoint { + return &onFailBreakpoint{} +} + +type onFailBreakpoint struct{} + +func (b *onFailBreakpoint) isTarget(ctx context.Context, status *RegisteredStatus, locs []*Location) (bool, string, []*Location, error) { + return status.Err != nil, fmt.Sprintf("caught error %v", status.Err), locs, nil +} + +func (b *onFailBreakpoint) String() string { + return "breaks on fail" +} + +func (b *onFailBreakpoint) IsMarked(source *pb.SourceInfo, line int64) bool { + return false +} + +func NewLineBreakpoint(filename string, line int64) Breakpoint { + return &lineBreakpoint{filename, line} +} + +type lineBreakpoint struct { + filename string + line int64 +} + +func (b *lineBreakpoint) isTarget(ctx context.Context, status *RegisteredStatus, locs []*Location) (bool, string, []*Location, error) { + var hits []*Location + var found bool + for _, loc := range locs { + if loc.Source.Filename != b.filename { + continue + } + for _, r := range loc.Ranges { + if int64(r.Start.Line) <= b.line && b.line <= int64(r.End.Line) { + hits = append(hits, &Location{ + Source: loc.Source, + Ranges: []*pb.Range{r}, + }) + found = true + } + } + } + if found { + return true, "reached " + b.String(), hits, nil + } + return false, "", nil, nil +} + +func (b *lineBreakpoint) String() string { + return fmt.Sprintf("line: %s:%d", b.filename, b.line) +} + +func (b *lineBreakpoint) IsMarked(source *pb.SourceInfo, line int64) bool { + return source.Filename == b.filename && line == b.line +} diff --git a/pkg/buildkit/client.go b/pkg/buildkit/client.go new file mode 100644 index 00000000..005644fc --- /dev/null +++ b/pkg/buildkit/client.go @@ -0,0 +1,429 @@ +package buildkit + +import ( + "context" + "fmt" + "io" + "net" + "path/filepath" + "sync" + "text/tabwriter" + "time" + + "github.com/containerd/containerd/remotes/docker" + ctdsnapshots "github.com/containerd/containerd/snapshots" + "github.com/containerd/containerd/snapshots/native" + "github.com/containerd/containerd/snapshots/overlay" + "github.com/containerd/containerd/snapshots/overlay/overlayutils" + "github.com/moby/buildkit/cache/remotecache" + localremotecache "github.com/moby/buildkit/cache/remotecache/local" + registryremotecache "github.com/moby/buildkit/cache/remotecache/registry" + "github.com/moby/buildkit/client" + "github.com/moby/buildkit/cmd/buildkitd/config" + "github.com/moby/buildkit/control" + "github.com/moby/buildkit/executor/oci" + "github.com/moby/buildkit/frontend" + dockerfile "github.com/moby/buildkit/frontend/dockerfile/builder" + "github.com/moby/buildkit/frontend/gateway" + "github.com/moby/buildkit/session" + "github.com/moby/buildkit/solver/bboltcachestorage" + "github.com/moby/buildkit/util/grpcerrors" + "github.com/moby/buildkit/util/network/cniprovider" + "github.com/moby/buildkit/util/network/netproviders" + "github.com/moby/buildkit/util/progress/progresswriter" + "github.com/moby/buildkit/util/resolver" + "github.com/moby/buildkit/worker" + "github.com/moby/buildkit/worker/base" + "github.com/moby/buildkit/worker/runc" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "golang.org/x/sync/errgroup" + "google.golang.org/grpc" +) + +var ErrExit = errors.New("exit") + +type DebugConfig struct { + BreakpointHandler BreakpointHandler + DebugImage string + Breakpoints *Breakpoints + StopOnEntry bool + DisableBreakpoints bool + CleanupAll bool +} + +func Debug(ctx context.Context, cfg *config.Config, solveOpt *client.SolveOpt, progressWriter io.Writer, debugConfig DebugConfig) error { + if debugConfig.BreakpointHandler == nil { + return fmt.Errorf("BreakpointHandler must not be nil") + } + + // Prepare controller + debugController := newDebugController() + var c *client.Client + var doneController func() + createdCh := make(chan error) + errCh := make(chan error) + go func() { + var err error + c, doneController, err = newClient(ctx, cfg, debugController) + if err != nil { + errCh <- err + return + } + close(createdCh) + }() + select { + case <-ctx.Done(): + err := ctx.Err() + return err + case <-time.After(3 * time.Second): + return fmt.Errorf("timed out to access cache storage. other debug session is running?") + case err := <-errCh: + return err + case <-createdCh: + } + defer doneController() + + // Prepare progress writer + progressCtx := context.TODO() + pw, err := progresswriter.NewPrinter(progressCtx, &nopConsoleFile{progressWriter}, "plain") + if err != nil { + return err + } + mw := progresswriter.NewMultiWriter(pw) + var writers []progresswriter.Writer + for _, at := range solveOpt.Session { + if s, ok := at.(interface { + SetLogger(progresswriter.Logger) + }); ok { + w := mw.WithPrefix("", false) + s.SetLogger(func(s *client.SolveStatus) { + w.Status() <- s + }) + writers = append(writers, w) + } + } + + // Start build with debugging + buildFunc, handleErrorCh := withDebug(dockerfile.Build, debugController, debugConfig) + eg, egCtx := errgroup.WithContext(ctx) + doneCh := make(chan struct{}) + eg.Go(func() (err error) { + select { + case err = <-handleErrorCh: + case <-doneCh: + } + if err != nil { + return fmt.Errorf("handler error: %w", err) + } + return nil + }) + eg.Go(func() error { + defer close(doneCh) + defer func() { + for _, w := range writers { + close(w.Status()) + } + }() + if _, err := c.Build(egCtx, *solveOpt, "", buildFunc, progresswriter.ResetTime(mw.WithPrefix("", false)).Status()); err != nil { + return fmt.Errorf("failed to build: %w", err) + } + return nil + }) + eg.Go(func() error { + <-pw.Done() + return pw.Err() + }) + + waitErr := eg.Wait() + + if debugConfig.CleanupAll { + logrus.Infof("cleaning up cache") + if err := c.Prune(context.TODO(), nil, client.PruneAll); err != nil { + logrus.WithError(err).Warnf("failed to cleaning up cache") + } + } + + if waitErr != nil { + if errors.Is(waitErr, ErrExit) { + return nil + } + return waitErr + } + + return nil +} + +func Prune(ctx context.Context, cfg *config.Config, all bool, w io.Writer) error { + c, done, err := newClient(ctx, cfg, nil) + if err != nil { + return err + } + defer done() + + ch := make(chan client.UsageInfo) + donePrint := make(chan struct{}) + tw := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0) + first := true + total := int64(0) + go func() { + defer close(donePrint) + for du := range ch { + total += du.Size + if first { + fmt.Fprintln(tw, "ID\tRECLAIMABLE\tSIZE") + first = false + } + fmt.Fprintf(tw, "%-71s\t%-11v\t%s\n", du.ID, !du.InUse, fmt.Sprintf("%d B", du.Size)) + tw.Flush() + } + }() + var opts []client.PruneOption + if all { + opts = append(opts, client.PruneAll) + } + + err = c.Prune(ctx, ch, opts...) + close(ch) + <-donePrint + if err != nil { + return err + } + fmt.Fprintf(tw, "Total:\t%d B\n", total) + tw.Flush() + + return nil +} + +func Du(ctx context.Context, cfg *config.Config, w io.Writer) error { + c, done, err := newClient(ctx, cfg, nil) + if err != nil { + return err + } + defer done() + + tw := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0) + fmt.Fprintln(tw, "ID\tRECLAIMABLE\tSIZE") + dus, err := c.DiskUsage(ctx) + if err != nil { + return err + } + total := int64(0) + for _, du := range dus { + total += du.Size + fmt.Fprintf(tw, "%-71s\t%-11v\t%s\n", du.ID, !du.InUse, fmt.Sprintf("%d B", du.Size)) + tw.Flush() + } + fmt.Fprintf(tw, "Total:\t%d B\n", total) + tw.Flush() + + return nil +} + +// newClient creates controller client based on the passed config. +// optional debugController allows adding debugger support to the controller. +func newClient(ctx context.Context, cfg *config.Config, debugController *debugController) (_ *client.Client, _ func(), retErr error) { + if cfg.Root == "" { + return nil, nil, fmt.Errorf("root directory must be specified") + } + var closeFuncs []func() + done := func() { + for i := len(closeFuncs) - 1; i >= 0; i-- { + closeFuncs[i]() + } + } + defer func() { + if retErr != nil { + done() + } + }() + + // Initialize OCI worker with debugging support + w, resolverFunc, err := newWorker(ctx, cfg) + if err != nil { + return nil, nil, err + } + if debugController != nil { + w = debugController.debugWorker(w) + } + wc := &worker.Controller{} + if err := wc.Add(w); err != nil { + return nil, nil, err + } + + // Create controller + sessionManager, err := session.NewManager() + if err != nil { + return nil, nil, err + } + cacheStorage, err := bboltcachestorage.NewStore(filepath.Join(cfg.Root, "cache.db")) + if err != nil { + return nil, nil, err + } + gwfrontend := gateway.NewGatewayFrontend(wc) + if debugController != nil { + gwfrontend = debugController.frontendWithDebug(gwfrontend) + } + frontends := map[string]frontend.Frontend{} + frontends["gateway.v0"] = gwfrontend + remoteCacheImporterFuncs := map[string]remotecache.ResolveCacheImporterFunc{ + "registry": registryremotecache.ResolveCacheImporterFunc(sessionManager, w.ContentStore(), resolverFunc), + "local": localremotecache.ResolveCacheImporterFunc(sessionManager), + } + controller, err := control.NewController(control.Opt{ + SessionManager: sessionManager, + WorkerController: wc, + CacheKeyStorage: cacheStorage, + Frontends: frontends, + ResolveCacheImporterFuncs: remoteCacheImporterFuncs, + ResolveCacheExporterFuncs: map[string]remotecache.ResolveCacheExporterFunc{}, // TODO: support remote cahce exporter + Entitlements: []string{}, // TODO + }) + if err != nil { + return nil, nil, err + } + + // Create client for the controller + controlServer := grpc.NewServer(grpc.UnaryInterceptor(grpcerrors.UnaryServerInterceptor), grpc.StreamInterceptor(grpcerrors.StreamServerInterceptor)) + controller.Register(controlServer) + lt := newPipeListener() + go controlServer.Serve(lt) + closeFuncs = append(closeFuncs, controlServer.GracefulStop) + c, err := client.New(ctx, "", client.WithContextDialer(lt.dial)) + if err != nil { + return nil, nil, err + } + + return c, done, nil +} + +func newWorker(ctx context.Context, cfg *config.Config) (worker.Worker, docker.RegistryHosts, error) { + root := cfg.Root + if root == "" { + return nil, nil, fmt.Errorf("failed to init worker: root directory must be set") + } + snName := cfg.Workers.OCI.Snapshotter + if snName == "auto" { + if err := overlayutils.Supported(root); err == nil { + snName = "overlayfs" + } else { + logrus.Debugf("overlayfs isn't supported. falling back to native snapshotter") + snName = "native" + } + logrus.Debugf("%q is used as the auto snapshotter", snName) + } + var snFactory runc.SnapshotterFactory + switch snName { + case "native": + snFactory = runc.SnapshotterFactory{ + Name: snName, + New: native.NewSnapshotter, + } + case "overlayfs": + snFactory = runc.SnapshotterFactory{ + Name: snName, + New: func(root string) (ctdsnapshots.Snapshotter, error) { + return overlay.NewSnapshotter(root, overlay.AsynchronousRemove) + }, + } + default: + return nil, nil, fmt.Errorf("unknown snapshotter %q", snName) + } + rootless := cfg.Workers.OCI.Rootless + nc := netproviders.Opt{ + Mode: cfg.Workers.OCI.Mode, + CNI: cniprovider.Opt{ + Root: root, + ConfigPath: cfg.Workers.OCI.CNIConfigPath, + BinaryDir: cfg.Workers.OCI.CNIBinaryPath, + }, + } + opt, err := runc.NewWorkerOpt(root, snFactory, rootless, oci.ProcessSandbox, nil, nil, nc, nil, "", "", nil, "", "") + if err != nil { + return nil, nil, err + } + resolverFunc := resolver.NewRegistryConfig(cfg.Registries) + opt.RegistryHosts = resolverFunc + w, err := base.NewWorker(ctx, opt) + if err != nil { + return nil, nil, err + } + return w, resolverFunc, nil +} + +func newPipeListener() *pipeListener { + return &pipeListener{ + ch: make(chan net.Conn), + done: make(chan struct{}), + } +} + +type pipeListener struct { + ch chan net.Conn + done chan struct{} + closed bool + closeOnce sync.Once + closedMu sync.Mutex +} + +func (l *pipeListener) dial(ctx context.Context, _ string) (net.Conn, error) { + if l.isClosed() { + return nil, fmt.Errorf("closed") + } + c1, c2 := net.Pipe() + select { + case <-l.done: + return nil, fmt.Errorf("closed") + case l.ch <- c1: + } + return c2, nil +} + +func (l *pipeListener) Accept() (net.Conn, error) { + if l.isClosed() { + return nil, fmt.Errorf("closed") + } + select { + case <-l.done: + return nil, fmt.Errorf("closed") + case conn := <-l.ch: + return conn, nil + } +} + +func (l *pipeListener) Close() error { + l.closeOnce.Do(func() { + l.closedMu.Lock() + l.closed = true + close(l.done) + l.closedMu.Unlock() + }) + return nil +} + +func (l *pipeListener) isClosed() bool { + l.closedMu.Lock() + defer l.closedMu.Unlock() + return l.closed +} + +func (l *pipeListener) Addr() net.Addr { + return dummyAddr{} +} + +type dummyAddr struct{} + +func (a dummyAddr) Network() string { return "dummy" } +func (a dummyAddr) String() string { return "dummy" } + +type nopConsoleFile struct { + io.Writer +} + +func (f *nopConsoleFile) Close() error { return nil } + +func (f *nopConsoleFile) Read(p []byte) (int, error) { return 0, io.EOF } + +func (f *nopConsoleFile) Fd() uintptr { return 0 } + +func (f *nopConsoleFile) Name() string { return "dummy" } diff --git a/debug.go b/pkg/buildkit/controller.go similarity index 86% rename from debug.go rename to pkg/buildkit/controller.go index aa9d2e7d..352d0b6f 100644 --- a/debug.go +++ b/pkg/buildkit/controller.go @@ -1,4 +1,4 @@ -package main +package buildkit import ( "context" @@ -20,13 +20,13 @@ import ( func newDebugController() *debugController { return &debugController{ - eventCh: make(chan *registeredStatus), + eventCh: make(chan *RegisteredStatus), pause: make(map[string]*chan struct{}), } } type debugController struct { - eventCh chan *registeredStatus + eventCh chan *RegisteredStatus pause map[string]*chan struct{} mu sync.Mutex @@ -36,16 +36,7 @@ type debugController struct { handleStarted bool } -type location struct { - source *pb.SourceInfo - ranges []*pb.Range -} - -func (l *location) String() string { - return fmt.Sprintf("%q %+v", l.source.Filename, l.ranges) -} - -func (d *debugController) handle(ctx context.Context, handler *handler) error { +func (d *debugController) handle(ctx context.Context, handler *Handler) error { if d.handleStarted { return fmt.Errorf("on going handler exists") } @@ -58,8 +49,8 @@ func (d *debugController) handle(ctx context.Context, handler *handler) error { case <-ctx.Done(): return ctx.Err() case msg := <-d.eventCh: - logrus.Debugf("got debug event %q", msg.debugID) - if locs, err := d.getLocation(msg.vertex.String()); err != nil { + logrus.Debugf("got debug event %q", msg.DebugID) + if locs, err := d.getLocation(msg.Vertex.String()); err != nil { logrus.WithError(err).Debug("failed to get location info") } else { if err := handler.handle(ctx, msg, locs); err != nil { @@ -69,12 +60,12 @@ func (d *debugController) handle(ctx context.Context, handler *handler) error { return err } } - d.continueID(msg.debugID) + d.continueID(msg.DebugID) } } } -func (d *debugController) addLocation(source *pb.Source) { +func (d *debugController) addLocationSource(source *pb.Source) { d.sourcesMu.Lock() if d.sources == nil { d.sources = make(map[*pb.Source]int) @@ -88,7 +79,7 @@ func (d *debugController) addLocation(source *pb.Source) { d.sourcesMu.Unlock() } -func (d *debugController) deleteLocation(source *pb.Source) { +func (d *debugController) deleteLocationSource(source *pb.Source) { d.sourcesMu.Lock() if _, ok := d.sources[source]; ok { d.sources[source]-- @@ -99,13 +90,13 @@ func (d *debugController) deleteLocation(source *pb.Source) { d.sourcesMu.Unlock() } -func (d *debugController) getLocation(v string) (locs []*location, err error) { +func (d *debugController) getLocation(v string) (locs []*Location, err error) { d.sourcesMu.Lock() defer d.sourcesMu.Unlock() for s := range d.sources { if locsInfo, ok := s.Locations[v]; ok { for _, loc := range locsInfo.Locations { - locs = append(locs, &location{s.Infos[loc.SourceIndex], loc.Ranges}) + locs = append(locs, &Location{s.Infos[loc.SourceIndex], loc.Ranges}) } } } @@ -153,8 +144,8 @@ type debugFrontendBridge struct { func (f *debugFrontendBridge) Solve(ctx context.Context, req frontend.SolveRequest, sid string) (*frontend.Result, error) { req.Evaluate = true if req.Definition != nil && req.Definition.Source != nil { - f.debugController.addLocation(req.Definition.Source) - defer f.debugController.deleteLocation(req.Definition.Source) + f.debugController.addLocationSource(req.Definition.Source) + defer f.debugController.deleteLocationSource(req.Definition.Source) } return f.FrontendLLBBridge.Solve(ctx, req, sid) } @@ -171,8 +162,8 @@ type debugGatewayClient struct { func (c *debugGatewayClient) Solve(ctx context.Context, req gwclient.SolveRequest) (*gwclient.Result, error) { req.Evaluate = true if req.Definition != nil && req.Definition.Source != nil { - c.debugController.addLocation(req.Definition.Source) - defer c.debugController.deleteLocation(req.Definition.Source) + c.debugController.addLocationSource(req.Definition.Source) + defer c.debugController.deleteLocationSource(req.Definition.Source) } return c.Client.Solve(ctx, req) } @@ -214,6 +205,7 @@ func (d *debugWorkerWrapper) WorkerRefByID(id string) (*worker.WorkerRef, bool) } type status struct { + name string inputs []solver.Result mounts []solver.Result vertex digest.Digest @@ -221,13 +213,14 @@ type status struct { err error } -type registeredStatus struct { - debugID string - inputIDs []string - mountIDs []string - vertex digest.Digest - op *pb.Op - err error +type RegisteredStatus struct { + Name string + DebugID string + InputIDs []string + MountIDs []string + Vertex digest.Digest + Op *pb.Op + Err error } func (d *debugWorkerWrapper) notifyAndWait(ctx context.Context, s status) error { @@ -245,13 +238,14 @@ func (d *debugWorkerWrapper) notifyAndWait(ctx context.Context, s status) error select { case <-ctx.Done(): return ctx.Err() - case d.controller.eventCh <- ®isteredStatus{ - debugID: id, - vertex: s.vertex, - op: s.op, - inputIDs: inputIDs, - mountIDs: mountIDs, - err: s.err, + case d.controller.eventCh <- &RegisteredStatus{ + Name: s.name, + DebugID: id, + Vertex: s.vertex, + Op: s.op, + InputIDs: inputIDs, + MountIDs: mountIDs, + Err: s.err, }: } select { @@ -290,6 +284,7 @@ func (o *debugOpWrapper) LoadCacheHook(ctx context.Context, inputs []solver.Resu logrus.Infof("CACHED %v", o.vertex.Name()) execInputs, execMounts := o.getResultMounts(inputs, outputs) if err := o.worker.notifyAndWait(ctx, status{ + name: o.vertex.Name(), inputs: execInputs, mounts: execMounts, vertex: o.vertex.Digest(), @@ -312,6 +307,7 @@ func (o *debugOpWrapper) Exec(ctx context.Context, g session.Group, inputs []sol execInputs, execMounts = o.getResultMounts(inputs, outputs) } if nErr := o.worker.notifyAndWait(ctx, status{ + name: o.vertex.Name(), inputs: execInputs, mounts: execMounts, vertex: o.vertex.Digest(), diff --git a/pkg/buildkit/exec.go b/pkg/buildkit/exec.go new file mode 100644 index 00000000..9f1ce1a9 --- /dev/null +++ b/pkg/buildkit/exec.go @@ -0,0 +1,130 @@ +package buildkit + +import ( + "context" + "fmt" + "io" + "path/filepath" + + "github.com/containerd/console" + gwclient "github.com/moby/buildkit/frontend/gateway/client" + "github.com/moby/buildkit/solver/pb" + "github.com/sirupsen/logrus" +) + +type ContainerConfig struct { + GatewayClient gwclient.Client + Info *RegisteredStatus + Args []string + Tty bool + Stdin io.ReadCloser + Stdout, Stderr io.WriteCloser + Image gwclient.Reference + Mountroot string + InputMount bool + Env []string + Cwd string + WatchSignal func(ctx context.Context, proc gwclient.ContainerProcess, con console.Console) + NoSetRaw bool // TODO: FIXME: execContainer should be agnostic about console config +} + +func ExecContainer(ctx context.Context, cfg ContainerConfig) (_ gwclient.ContainerProcess, _ func(), retErr error) { + op := cfg.Info.Op + mountIDs := cfg.Info.MountIDs + if cfg.InputMount { + mountIDs = cfg.Info.InputIDs + } + var exec *pb.ExecOp + switch op := op.GetOp().(type) { + case *pb.Op_Exec: + exec = op.Exec + default: + return nil, nil, fmt.Errorf("this instruction doesn't support exec; try on RUN instructions") + } + + var cleanups []func() + defer func() { + if retErr != nil { + for i := len(cleanups) - 1; i >= 0; i-- { + cleanups[i]() + } + } + }() + + var mounts []gwclient.Mount + for i, mnt := range exec.Mounts { + mounts = append(mounts, gwclient.Mount{ + Selector: mnt.Selector, + Dest: mnt.Dest, + ResultID: mountIDs[i], + Readonly: mnt.Readonly, + MountType: mnt.MountType, + CacheOpt: mnt.CacheOpt, + SecretOpt: mnt.SecretOpt, + SSHOpt: mnt.SSHOpt, + }) + } + if cfg.Image != nil { + for i := range mounts { + mounts[i].Dest = filepath.Join(cfg.Mountroot, mounts[i].Dest) + } + mounts = append([]gwclient.Mount{ + { + Dest: "/", + MountType: pb.MountType_BIND, + Ref: cfg.Image, + }, + }, mounts...) + } + + ctr, err := cfg.GatewayClient.NewContainer(ctx, gwclient.NewContainerRequest{ + Mounts: mounts, + NetMode: exec.Network, + Platform: op.Platform, + Constraints: op.Constraints, + }) + if err != nil { + return nil, nil, fmt.Errorf("failed to create debug container: %v", err) + } + cleanups = append(cleanups, func() { ctr.Release(ctx) }) + meta := exec.Meta + cwd := meta.Cwd + if cfg.Cwd != "" { + cwd = cfg.Cwd + } + proc, err := ctr.Start(ctx, gwclient.StartRequest{ + Args: cfg.Args, + Env: append(meta.Env, cfg.Env...), + User: meta.User, + Cwd: cwd, + Tty: cfg.Tty, + Stdin: cfg.Stdin, + Stdout: cfg.Stdout, + Stderr: cfg.Stderr, + SecurityMode: exec.Security, + }) + if err != nil { + return nil, nil, fmt.Errorf("failed to start container: %w", err) + } + + var con console.Console + if cfg.Tty && !cfg.NoSetRaw { + con = console.Current() + if err := con.SetRaw(); err != nil { + return nil, nil, fmt.Errorf("failed to configure terminal: %v", err) + } + cleanups = append(cleanups, func() { con.Reset() }) + } + if cfg.WatchSignal != nil { + ioCtx, ioCancel := context.WithCancel(ctx) + cleanups = append(cleanups, func() { ioCancel() }) + cfg.WatchSignal(ioCtx, proc, con) + } + return proc, func() { + logrus.Warnf("cleaning up container exec") + for i := len(cleanups) - 1; i >= 0; i-- { + cleanups[i]() + } + logrus.Warnf("finished container exec") + }, nil +} diff --git a/pkg/buildkit/handler.go b/pkg/buildkit/handler.go new file mode 100644 index 00000000..3f912a01 --- /dev/null +++ b/pkg/buildkit/handler.go @@ -0,0 +1,144 @@ +package buildkit + +import ( + "context" + "sync" + + "github.com/moby/buildkit/client/llb" + gwclient "github.com/moby/buildkit/frontend/gateway/client" + "github.com/sirupsen/logrus" +) + +func withDebug(f gwclient.BuildFunc, debugController *debugController, cfg DebugConfig) (gwclient.BuildFunc, <-chan error) { + handleError := make(chan error) + return func(ctx context.Context, c gwclient.Client) (res *gwclient.Result, err error) { + b := cfg.Breakpoints + if b == nil { + b = &Breakpoints{ + breakpoints: map[string]Breakpoint{ + "on-fail": NewOnFailBreakpoint(), + }, + } + } + handler := &Handler{ + gwclient: c, + breakpoints: b, + breakpointHandler: cfg.BreakpointHandler, + stopOnEntry: cfg.StopOnEntry, + disableBreakpoints: cfg.DisableBreakpoints, + } + + doneCh := make(chan struct{}) + defer close(doneCh) + go func() { + defer close(handleError) + select { + case handleError <- debugController.handle(context.Background(), handler): + case <-doneCh: + } + }() + + if cfg.DebugImage != "" { + def, err := llb.Image(cfg.DebugImage, withDescriptor(map[string]string{"debug": "no"})).Marshal(ctx) + if err != nil { + return nil, err + } + r, err := c.Solve(ctx, gwclient.SolveRequest{ + Definition: def.ToPB(), + }) + if err != nil { + return nil, err + } + handler.imageMu.Lock() + handler.image = r.Ref + handler.imageMu.Unlock() + } + + return f(ctx, debugController.gatewayClientWithDebug(c)) + }, handleError +} + +type withDescriptor map[string]string + +func (o withDescriptor) SetImageOption(ii *llb.ImageInfo) { + if ii.Metadata.Description == nil { + ii.Metadata.Description = make(map[string]string) + } + for k, v := range o { + ii.Metadata.Description[k] = v + } +} + +type Handler struct { + gwclient gwclient.Client + + breakpoints *Breakpoints + breakEachVertex bool + + entried bool + stopOnEntry bool + disableBreakpoints bool + + mu sync.Mutex + + image gwclient.Reference + imageMu sync.Mutex + + breakpointHandler BreakpointHandler +} + +func (h *Handler) Breakpoints() *Breakpoints { + return h.breakpoints +} + +func (h *Handler) GatewayClient() gwclient.Client { + return h.gwclient +} + +func (h *Handler) DebuggerImage() gwclient.Reference { + h.imageMu.Lock() + defer h.imageMu.Unlock() + return h.image +} + +func (h *Handler) BreakEachVertex(b bool) { + h.breakEachVertex = b +} + +func (h *Handler) handle(ctx context.Context, info *RegisteredStatus, locs []*Location) error { + h.mu.Lock() + defer h.mu.Unlock() + + if len(locs) == 0 { + logrus.Warnf("no location info: %v", locs) + return nil + } + isBreakpoint := false + hits := make(map[string]BreakpointInfo) + for key, bp := range getHitBreakpoints(ctx, h.breakpoints, info, locs) { + hits[key] = bp + isBreakpoint = true + } + if h.disableBreakpoints || !(h.stopOnEntry && !h.entried) && !h.breakEachVertex && !isBreakpoint { + logrus.Debugf("skipping non-breakpoint: %v", locs) + return nil + } + if !h.entried { + logrus.Infof("debug session started. type \"help\" for command reference.") + h.entried = true + } + return h.breakpointHandler(ctx, BreakContext{h, info, locs, hits}) +} + +func getHitBreakpoints(ctx context.Context, b *Breakpoints, info *RegisteredStatus, locs []*Location) (hit map[string]BreakpointInfo) { + hit = make(map[string]BreakpointInfo) + b.ForEach(func(key string, bp Breakpoint) bool { + if yes, desc, hits, err := bp.isTarget(ctx, info, locs); err != nil { + logrus.WithError(err).Warnf("failed to check breakpoint") + } else if yes { + hit[key] = BreakpointInfo{desc, hits} + } + return true + }) + return +} diff --git a/pkg/buildkit/location.go b/pkg/buildkit/location.go new file mode 100644 index 00000000..12f09fdd --- /dev/null +++ b/pkg/buildkit/location.go @@ -0,0 +1,16 @@ +package buildkit + +import ( + "fmt" + + "github.com/moby/buildkit/solver/pb" +) + +type Location struct { + Source *pb.SourceInfo + Ranges []*pb.Range +} + +func (l *Location) String() string { + return fmt.Sprintf("%q %+v", l.Source.Filename, l.Ranges) +} diff --git a/pkg/dap/dap.go b/pkg/dap/dap.go new file mode 100644 index 00000000..15b67fdd --- /dev/null +++ b/pkg/dap/dap.go @@ -0,0 +1,904 @@ +package dap + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "time" + + "github.com/google/go-dap" + "github.com/google/shlex" + "github.com/ktock/buildg/pkg/buildkit" + "github.com/moby/buildkit/cmd/buildkitd/config" + "github.com/moby/buildkit/solver/pb" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" + "golang.org/x/sync/errgroup" +) + +const AttachContainerCommand = "_dap_attach_container" + +func NewServer(conn net.Conn, cfg *config.Config, cleanupFunc func() error, cleanupAll bool) (*Server, error) { + ctx, cancel := context.WithCancel(context.TODO()) + eg := new(errgroup.Group) + s := &Server{ + conn: conn, + cleanupFunc: cleanupFunc, + ctx: ctx, + cancel: cancel, + eg: eg, + } + var err error + s.debugger, err = newDebugger(cfg, cleanupAll) + if err != nil { + return nil, err + } + return s, nil +} + +type Server struct { + conn net.Conn + debugger *debugger + debuggerMu sync.Mutex + sendMu sync.Mutex + cleanupFunc func() error + + ctx context.Context + cancel func() + eg *errgroup.Group +} + +func (s *Server) Serve() error { + var eg errgroup.Group + r := bufio.NewReader(s.conn) + for { + req, err := dap.ReadProtocolMessage(r) + if err != nil { + if err == io.EOF { + break + } + return err + } + eg.Go(func() error { return s.handle(req) }) + } + return eg.Wait() +} + +func (s *Server) send(message dap.Message) { + s.sendMu.Lock() + dap.WriteProtocolMessage(s.conn, message) + logrus.WithField("dst", s.conn.RemoteAddr()).Debugf("message sent %+v", message) + s.sendMu.Unlock() +} + +const ( + unsupportedError = 1000 + failedError = 1001 + unknownError = 9999 +) + +var errorMessage = map[int]string{ + unsupportedError: "unsupported", + failedError: "failed", + unknownError: "unknown", +} + +func (s *Server) sendErrorResponse(requestSeq int, command string, errID int, message string, showUser bool) { + id, summary := unknownError, errorMessage[unknownError] + if m, ok := errorMessage[errID]; ok { + id, summary = errID, m + } + r := &dap.ErrorResponse{} + r.Response = *newResponse(requestSeq, command) + r.Success = false + r.Message = summary + r.Body.Error.Format = message + r.Body.Error.Id = id + r.Body.Error.ShowUser = showUser + s.send(r) +} + +func (s *Server) sendUnsupportedResponse(requestSeq int, command string, message string) { + s.sendErrorResponse(requestSeq, command, unsupportedError, message, false) +} + +func (s *Server) outputStdoutWriter() io.Writer { + return &outputWriter{s, "stdout"} +} + +func (s *Server) handle(request dap.Message) error { + logrus.Debugf("got request: %+v", request) + switch request := request.(type) { + case *dap.InitializeRequest: + s.onInitializeRequest(request) + case *dap.LaunchRequest: + s.onLaunchRequest(request) + case *dap.AttachRequest: + s.onAttachRequest(request) + case *dap.DisconnectRequest: + s.onDisconnectRequest(request) + case *dap.TerminateRequest: + s.onTerminateRequest(request) + case *dap.RestartRequest: + s.onRestartRequest(request) + case *dap.SetBreakpointsRequest: + s.onSetBreakpointsRequest(request) + case *dap.SetFunctionBreakpointsRequest: + s.onSetFunctionBreakpointsRequest(request) + case *dap.SetExceptionBreakpointsRequest: + s.onSetExceptionBreakpointsRequest(request) + case *dap.ConfigurationDoneRequest: + s.onConfigurationDoneRequest(request) + case *dap.ContinueRequest: + s.onContinueRequest(request) + case *dap.NextRequest: + s.onNextRequest(request) + case *dap.StepInRequest: + s.onStepInRequest(request) + case *dap.StepOutRequest: + s.onStepOutRequest(request) + case *dap.StepBackRequest: + s.onStepBackRequest(request) + case *dap.ReverseContinueRequest: + s.onReverseContinueRequest(request) + case *dap.RestartFrameRequest: + s.onRestartFrameRequest(request) + case *dap.GotoRequest: + s.onGotoRequest(request) + case *dap.PauseRequest: + s.onPauseRequest(request) + case *dap.StackTraceRequest: + s.onStackTraceRequest(request) + case *dap.ScopesRequest: + s.onScopesRequest(request) + case *dap.VariablesRequest: + s.onVariablesRequest(request) + case *dap.SetVariableRequest: + s.onSetVariableRequest(request) + case *dap.SetExpressionRequest: + s.onSetExpressionRequest(request) + case *dap.SourceRequest: + s.onSourceRequest(request) + case *dap.ThreadsRequest: + s.onThreadsRequest(request) + case *dap.TerminateThreadsRequest: + s.onTerminateThreadsRequest(request) + case *dap.EvaluateRequest: + s.onEvaluateRequest(request) + case *dap.StepInTargetsRequest: + s.onStepInTargetsRequest(request) + case *dap.GotoTargetsRequest: + s.onGotoTargetsRequest(request) + case *dap.CompletionsRequest: + s.onCompletionsRequest(request) + case *dap.ExceptionInfoRequest: + s.onExceptionInfoRequest(request) + case *dap.LoadedSourcesRequest: + s.onLoadedSourcesRequest(request) + case *dap.DataBreakpointInfoRequest: + s.onDataBreakpointInfoRequest(request) + case *dap.SetDataBreakpointsRequest: + s.onSetDataBreakpointsRequest(request) + case *dap.ReadMemoryRequest: + s.onReadMemoryRequest(request) + case *dap.DisassembleRequest: + s.onDisassembleRequest(request) + case *dap.CancelRequest: + s.onCancelRequest(request) + case *dap.BreakpointLocationsRequest: + s.onBreakpointLocationsRequest(request) + default: + logrus.Warnf("Unable to process %#v\n", request) + } + return nil +} + +func (s *Server) onInitializeRequest(request *dap.InitializeRequest) { + response := &dap.InitializeResponse{} + response.Response = *newResponse(request.Seq, request.Command) + response.Body.SupportsConfigurationDoneRequest = true + response.Body.SupportsFunctionBreakpoints = false + response.Body.SupportsConditionalBreakpoints = false + response.Body.SupportsHitConditionalBreakpoints = false + response.Body.SupportsEvaluateForHovers = false + response.Body.ExceptionBreakpointFilters = make([]dap.ExceptionBreakpointsFilter, 0) + response.Body.SupportsStepBack = false + response.Body.SupportsSetVariable = false + response.Body.SupportsRestartFrame = false + response.Body.SupportsGotoTargetsRequest = false + response.Body.SupportsStepInTargetsRequest = false + response.Body.SupportsCompletionsRequest = false + response.Body.CompletionTriggerCharacters = make([]string, 0) + response.Body.SupportsModulesRequest = false + response.Body.AdditionalModuleColumns = make([]dap.ColumnDescriptor, 0) + response.Body.SupportedChecksumAlgorithms = make([]dap.ChecksumAlgorithm, 0) + response.Body.SupportsRestartRequest = false + response.Body.SupportsExceptionOptions = false + response.Body.SupportsValueFormattingOptions = false + response.Body.SupportsExceptionInfoRequest = false + response.Body.SupportTerminateDebuggee = false + response.Body.SupportSuspendDebuggee = false + response.Body.SupportsDelayedStackTraceLoading = false + response.Body.SupportsLoadedSourcesRequest = false + response.Body.SupportsLogPoints = false + response.Body.SupportsTerminateThreadsRequest = false + response.Body.SupportsSetExpression = false + response.Body.SupportsTerminateRequest = false + response.Body.SupportsDataBreakpoints = false + response.Body.SupportsReadMemoryRequest = false + response.Body.SupportsWriteMemoryRequest = false + response.Body.SupportsDisassembleRequest = false + response.Body.SupportsCancelRequest = false + response.Body.SupportsBreakpointLocationsRequest = false + response.Body.SupportsClipboardContext = false + response.Body.SupportsSteppingGranularity = false + response.Body.SupportsInstructionBreakpoints = false + response.Body.SupportsExceptionFilterOptions = false + + s.send(response) + s.send(&dap.InitializedEvent{Event: *newEvent("initialized")}) +} + +func (s *Server) onLaunchRequest(request *dap.LaunchRequest) { + cfg := new(LaunchConfig) + if err := json.Unmarshal(request.Arguments, cfg); err != nil { + s.sendErrorResponse(request.Seq, request.Command, failedError, fmt.Sprintf("failed to launch: %v", err), true) + return + } + if err := s.launchDebugger(*cfg); err != nil { + s.sendErrorResponse(request.Seq, request.Command, failedError, fmt.Sprintf("failed to launch: %v", err), true) + return + } + response := &dap.LaunchResponse{} + response.Response = *newResponse(request.Seq, request.Command) + s.send(response) + go func() { + for breakpoints := range s.debugger.stopped() { + reason := "breakpoint" + if len(breakpoints) == 0 { + reason = "step" + } + s.send(&dap.StoppedEvent{ + Event: *newEvent("stopped"), + Body: dap.StoppedEventBody{Reason: reason, ThreadId: 1, AllThreadsStopped: true, HitBreakpointIds: breakpoints}, + }) + } + }() +} + +type LaunchConfig struct { + Program string `json:"program"` + NoDebug bool `json:"noDebug"` + StopOnEntry bool `json:"stopOnEntry"` + + Target string `json:"target"` + BuildArgs []string `json:"build-args"` + SSH []string `json:"ssh"` + Secrets []string `json:"secrets"` + + Image string `json:"image"` +} + +func (s *Server) launchDebugger(cfg LaunchConfig) error { + if cfg.Program == "" { + return fmt.Errorf("launch error: program must be specified") + } + if s.debugger == nil { + return fmt.Errorf("launch error: debugger is not available") + } + disableBreakpoints := false + if cfg.NoDebug { + disableBreakpoints = true + } + startedCh := make(chan struct{}) + errCh := make(chan error) + go func() { + err := s.debugger.launch(cfg, + func() { + s.send(&dap.ThreadEvent{Event: *newEvent("thread"), Body: dap.ThreadEventBody{Reason: "started", ThreadId: 1}}) + close(startedCh) + }, + func() { + s.send(&dap.ThreadEvent{Event: *newEvent("thread"), Body: dap.ThreadEventBody{Reason: "exited", ThreadId: 1}}) + if s.cleanupFunc != nil { + // Cleanup before exit event to make sure cleanup happens before this server killed by emacs + if err := s.cleanupFunc(); err != nil { + logrus.WithError(err).Warnf("failed to cleanup") + } + } + s.send(&dap.TerminatedEvent{Event: *newEvent("terminated")}) + s.send(&dap.ExitedEvent{Event: *newEvent("exited")}) + }, cfg.StopOnEntry, disableBreakpoints, s.outputStdoutWriter(), + ) + if err != nil { + errCh <- err + return + } + }() + select { + case <-startedCh: + case err := <-errCh: + return err + } + return nil +} + +func (s *Server) onDisconnectRequest(request *dap.DisconnectRequest) { + s.debuggerMu.Lock() + defer s.debuggerMu.Unlock() + if s.cancel != nil { + s.cancel() + } + if err := s.eg.Wait(); err != nil { // wait for container cleanup + logrus.WithError(err).Warnf("failed to close tasks") + } + if s.debugger != nil { + if err := s.debugger.cancel(); err != nil { + logrus.WithError(err).Warnf("failed to cancel debugger") + } + s.debugger = nil + } + response := &dap.DisconnectResponse{} + response.Response = *newResponse(request.Seq, request.Command) + s.send(response) + os.Exit(0) // TODO: should return the control to the main func +} + +func (s *Server) onSetBreakpointsRequest(request *dap.SetBreakpointsRequest) { + args := request.Arguments + breakpoints := make([]dap.Breakpoint, 0) + + s.debugger.breakpoints().ClearAll() + if _, err := s.debugger.breakpoints().Add("on-fail", buildkit.NewOnFailBreakpoint()); err != nil { + logrus.WithError(err).Warnf("failed to add on-fail breakpoints") + } + for i := 0; i < len(args.Breakpoints); i++ { + bp := buildkit.NewLineBreakpoint(args.Source.Name, int64(args.Breakpoints[i].Line)) + key, err := s.debugger.breakpoints().Add("", bp) + if err != nil { + logrus.WithError(err).Warnf("failed to add breakpoints") + continue + } + keyI, err := strconv.ParseInt(key, 10, 64) + if err != nil { + logrus.WithError(err).Warnf("failed to parse breakpoint key") + continue + } + breakpoints = append(breakpoints, dap.Breakpoint{ + Id: int(keyI), + Source: args.Source, + Line: args.Breakpoints[i].Line, + Verified: true, + }) + } + + response := &dap.SetBreakpointsResponse{} + response.Response = *newResponse(request.Seq, request.Command) + response.Body.Breakpoints = breakpoints + s.send(response) +} + +func (s *Server) onConfigurationDoneRequest(request *dap.ConfigurationDoneRequest) { + response := &dap.ConfigurationDoneResponse{} + response.Response = *newResponse(request.Seq, request.Command) + s.send(response) +} + +func (s *Server) onContinueRequest(request *dap.ContinueRequest) { + response := &dap.ContinueResponse{} + response.Response = *newResponse(request.Seq, request.Command) + s.send(response) + s.debugger.doContinue() +} + +func (s *Server) onNextRequest(request *dap.NextRequest) { + response := &dap.NextResponse{} + response.Response = *newResponse(request.Seq, request.Command) + s.send(response) + s.debugger.doNext() +} + +func (s *Server) onStackTraceRequest(request *dap.StackTraceRequest) { + response := &dap.StackTraceResponse{} + response.Response = *newResponse(request.Seq, request.Command) + response.Body = dap.StackTraceResponseBody{ + StackFrames: make([]dap.StackFrame, 0), + } + + bCtx := s.debugger.breakContext() + launchConfig := s.debugger.launchConfig() + if bCtx == nil || launchConfig == nil { + // no stack trace is available now + s.send(response) + return + } + + var lines []*pb.Range + + // If there are hit breakpoints on the current Op, return them. + // FIXME: This is a workaround against stackFrame doesn't support + // multiple sources per frame. Once dap support it, we can + // return all current locations. + // TODO: show non-breakpoint locations to output as well + for _, bpInfo := range bCtx.Hits { + for _, loc := range bpInfo.Hits { + lines = append(lines, loc.Ranges...) + } + } + if len(lines) == 0 { + // no breakpoints on the current Op. This can happen on + // step execution. + for _, loc := range bCtx.Locs { + lines = append(lines, loc.Ranges...) + } + } + if len(lines) > 0 { + name := "instruction" + if bCtx.Info.Name != "" { + name = bCtx.Info.Name + } + f := launchConfig.Program + response.Body.StackFrames = []dap.StackFrame{ + { + Id: 0, + Source: dap.Source{Name: filepath.Base(f), Path: f}, + // FIXME: We only return lines[0] because stackFrame doesn't support + // multiple sources per frame. Once dap support it, we can + // return all current locations. + Line: int(lines[0].Start.Line), + EndLine: int(lines[0].End.Line), + Name: name, + }, + } + response.Body.TotalFrames = 1 + } + s.send(response) +} + +func (s *Server) onScopesRequest(request *dap.ScopesRequest) { + response := &dap.ScopesResponse{} + response.Response = *newResponse(request.Seq, request.Command) + response.Body = dap.ScopesResponseBody{ + Scopes: []dap.Scope{ + { + Name: "Environment Variables", + VariablesReference: 1, + }, + }, + } + s.send(response) +} + +func (s *Server) onVariablesRequest(request *dap.VariablesRequest) { + response := &dap.VariablesResponse{} + response.Response = *newResponse(request.Seq, request.Command) + response.Body = dap.VariablesResponseBody{ + Variables: make([]dap.Variable, 0), // neovim doesn't allow nil + } + + bCtx := s.debugger.breakContext() + if bCtx == nil { + s.send(response) + return + } + + var variables []dap.Variable + if info := bCtx.Info; info != nil { + switch op := info.Op.GetOp().(type) { + case *pb.Op_Exec: + for _, e := range op.Exec.Meta.Env { + var k, v string + if kv := strings.SplitN(e, "=", 2); len(kv) >= 2 { + k, v = kv[0], kv[1] + } else if len(kv) == 1 { + k = kv[0] + } else { + continue + } + variables = append(variables, dap.Variable{ + Name: k, + Value: v, + }) + } + default: + // TODO: support other Ops + } + } + + if s := request.Arguments.Start; s > 0 { + if s < len(variables) { + variables = variables[s:] + } else { + variables = nil + } + } + if c := request.Arguments.Count; c > 0 { + if c < len(variables) { + variables = variables[:c] + } + } + response.Body.Variables = append(response.Body.Variables, variables...) + s.send(response) +} + +func (s *Server) onThreadsRequest(request *dap.ThreadsRequest) { + response := &dap.ThreadsResponse{} + response.Response = *newResponse(request.Seq, request.Command) + response.Body = dap.ThreadsResponseBody{Threads: []dap.Thread{{Id: 1, Name: "build"}}} + s.send(response) +} + +type handlerContext struct { + breakContext buildkit.BreakContext + stdout io.Writer + evaluateDoneCallback func() +} + +type replCommand func(ctx context.Context, hCtx *handlerContext) cli.Command + +func (s *Server) onEvaluateRequest(request *dap.EvaluateRequest) { + if request.Arguments.Context != "repl" { // TODO: support other contexts + s.sendUnsupportedResponse(request.Seq, request.Command, "evaluating non-repl input is unsupported as of now") + return + } + + bCtx := s.debugger.breakContext() + if bCtx == nil { + s.sendErrorResponse(request.Seq, request.Command, failedError, "no breakpoint available", true) + return + } + + replCommands := []replCommand{s.execCommand} // TODO: breakpointsCommand, listCommand, ... + + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + hCtx := new(handlerContext) + out := new(bytes.Buffer) + if args, err := shlex.Split(request.Arguments.Expression); err != nil { + logrus.WithError(err).Warnf("failed to parse line") + } else if len(args) > 0 && args[0] != "" { + app := cli.NewApp() + rootCmd := "buildg" + app.Name = rootCmd + app.HelpName = rootCmd + app.Usage = "Interactive debugger for Dockerfile" + app.UsageText = "command [command options] [arguments...]" + app.ExitErrHandler = func(context *cli.Context, err error) {} + app.UseShortOptionHandling = true + app.Writer = out + hCtx = &handlerContext{ + breakContext: *bCtx, + stdout: out, + } + for _, fn := range replCommands { + app.Commands = append(app.Commands, fn(ctx, hCtx)) + } + if err := app.Run(append([]string{rootCmd}, args...)); err != nil { + out.WriteString(err.Error() + "\n") + } + } + response := &dap.EvaluateResponse{} + response.Response = *newResponse(request.Seq, request.Command) + response.Body = dap.EvaluateResponseBody{ + Result: out.String(), + } + s.send(response) + if hCtx.evaluateDoneCallback != nil { + hCtx.evaluateDoneCallback() + } +} + +func (s *Server) execCommand(_ context.Context, hCtx *handlerContext) cli.Command { + return cli.Command{ + Name: "exec", + Aliases: []string{"e"}, + Usage: "Execute command in the step", + UsageText: `exec [OPTIONS] [ARGS...] + +If ARGS isn't provided, "/bin/sh" is used by default. +Only supported on RUN instructions as of now. +`, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "image", + Usage: "Execute command in the debuger image. If not specified, the command is executed on the rootfs of the current step.", + }, + cli.StringFlag{ + Name: "mountroot", + Usage: "Mountpoint to mount the rootfs of the current step. Ignored if --image isn't specified.", + Value: "/debugroot", + }, + cli.BoolFlag{ + Name: "init-state", + Usage: "Execute commands in an initial state of that step (experimental)", + }, + cli.BoolTFlag{ + Name: "tty,t", + Usage: "Allocate tty (enabled by default)", + }, + cli.BoolTFlag{ + Name: "i", + Usage: "Enable stdin (FIXME: must be set with tty) (enabled by default)", + }, + cli.StringSliceFlag{ + Name: "env,e", + Usage: "Set environment variables", + }, + cli.StringFlag{ + Name: "workdir,w", + Usage: "Working directory inside the container", + }, + }, + Action: func(clicontext *cli.Context) (retErr error) { + args := clicontext.Args() + if len(args) == 0 || args[0] == "" { + args = []string{"/bin/sh"} + } + flagI := clicontext.Bool("i") + flagT := clicontext.Bool("tty") + if flagI && !flagT || !flagI && flagT { + return fmt.Errorf("flag \"-i\" and \"-t\" must be set together") // FIXME + } + + gCtx := s.ctx // cancelled on disconnect + var cleanups []func() + defer func() { + if retErr != nil { + for i := len(cleanups) - 1; i >= 0; i-- { + cleanups[i]() + } + } + }() + + // Prepare state dir + tmpRoot, err := os.MkdirTemp("", "buildg-serve-state") + if err != nil { + return err + } + cleanups = append(cleanups, func() { os.RemoveAll(tmpRoot) }) + + // Server IO + logrus.Debugf("container root %+v", tmpRoot) + stdin, stdout, stderr, sigForwarder, done, err := serveContainerIO(gCtx, tmpRoot) + if err != nil { + return err + } + cleanups = append(cleanups, func() { done() }) + + // Search container client + self, err := os.Executable() // TODO: make it configurable + if err != nil { + return err + } + + // Launch container + execCfg := buildkit.ContainerConfig{ + Info: hCtx.breakContext.Info, + Args: args, + Stdout: stdout, + Stderr: stderr, + Tty: clicontext.Bool("tty"), + Mountroot: clicontext.String("mountroot"), + InputMount: clicontext.Bool("init-state"), + Env: clicontext.StringSlice("env"), + Cwd: clicontext.String("workdir"), + WatchSignal: sigForwarder.watchSignal, + GatewayClient: hCtx.breakContext.Handler.GatewayClient(), + NoSetRaw: true, + } + if clicontext.Bool("image") { + execCfg.Image = hCtx.breakContext.Handler.DebuggerImage() + } + if flagI { + execCfg.Stdin = stdin + } + proc, containerCleanups, err := buildkit.ExecContainer(context.TODO(), execCfg) // do not pass gCtx here to allow buildkit graceful cleanup + if err != nil { + return err + } + cleanups = append(cleanups, func() { containerCleanups() }) + + // Let the caller to attach to the container after evaluation response received. + hCtx.evaluateDoneCallback = func() { + s.send(&dap.RunInTerminalRequest{ + Request: dap.Request{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: 0, + Type: "request", + }, + Command: "runInTerminal", + }, + Arguments: dap.RunInTerminalRequestArguments{ + Kind: "integrated", + Title: "containerclient", + Args: []string{self, "dap", AttachContainerCommand, "--set-tty-raw=" + strconv.FormatBool(clicontext.Bool("tty")), tmpRoot}, + Env: make(map[string]interface{}), + // emacs requires this nonempty otherwise error (Wrong type argument: stringp, nil) will occur on dap-ui-breakpoints() + Cwd: filepath.Dir(s.debugger.launchConfig().Program), + }, + }) + } + s.eg.Go(func() error { + // let disconnect API to wait for the cleanup of container processes + doneCh := make(chan struct{}) + errCh := make(chan error) + go func() { + defer close(doneCh) + if err := proc.Wait(); err != nil { + errCh <- err + return + } + }() + select { + case <-doneCh: + s.outputStdoutWriter().Write([]byte("container finished")) + case err := <-errCh: + s.outputStdoutWriter().Write([]byte(fmt.Sprintf("container finished with error: %v", err))) + case err := <-gCtx.Done(): + s.outputStdoutWriter().Write([]byte(fmt.Sprintf("finishing container due to server shutdown: %v", err))) + } + for i := len(cleanups) - 1; i >= 0; i-- { + cleanups[i]() + } + select { + case <-doneCh: + case err := <-errCh: + s.outputStdoutWriter().Write([]byte(fmt.Sprintf("container exit with error: %v", err))) + case <-time.After(3 * time.Second): + s.outputStdoutWriter().Write([]byte("container exit timeout")) + } + return nil + }) + return nil + }, + } +} + +func (s *Server) onSetExceptionBreakpointsRequest(request *dap.SetExceptionBreakpointsRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "Request unsupported") +} + +func (s *Server) onRestartRequest(request *dap.RestartRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "RestartRequest unsupported") +} + +func (s *Server) onAttachRequest(request *dap.AttachRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "AttachRequest unsupported") +} + +func (s *Server) onTerminateRequest(request *dap.TerminateRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "TerminateRequest unsupported") +} + +func (s *Server) onSetFunctionBreakpointsRequest(request *dap.SetFunctionBreakpointsRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "FunctionBreakpointsRequest unsupported") +} + +func (s *Server) onStepInRequest(request *dap.StepInRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "StepInRequest unsupported") +} + +func (s *Server) onStepOutRequest(request *dap.StepOutRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "StepOutRequest unsupported") +} + +func (s *Server) onStepBackRequest(request *dap.StepBackRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "StepBackRequest unsupported") +} + +func (s *Server) onReverseContinueRequest(request *dap.ReverseContinueRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "ReverseContinueRequest unsupported") +} + +func (s *Server) onRestartFrameRequest(request *dap.RestartFrameRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "RestartFrameRequest unsupported") +} + +func (s *Server) onGotoRequest(request *dap.GotoRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "GotoRequest unsupported") +} + +func (s *Server) onPauseRequest(request *dap.PauseRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "PauseRequest unsupported") +} + +func (s *Server) onSetVariableRequest(request *dap.SetVariableRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "SetVariablesRequest unsupported") +} + +func (s *Server) onSetExpressionRequest(request *dap.SetExpressionRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "SetExpressionRequest unsupported") +} + +func (s *Server) onSourceRequest(request *dap.SourceRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "SourceRequest unsupported") +} + +func (s *Server) onTerminateThreadsRequest(request *dap.TerminateThreadsRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "TerminateRequest unsupported") +} + +func (s *Server) onStepInTargetsRequest(request *dap.StepInTargetsRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "StepInTargetsRequest unsupported") +} + +func (s *Server) onGotoTargetsRequest(request *dap.GotoTargetsRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "GotoTargetsRequest unsupported") +} + +func (s *Server) onCompletionsRequest(request *dap.CompletionsRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "CompletionsRequest unsupported") +} + +func (s *Server) onExceptionInfoRequest(request *dap.ExceptionInfoRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "ExceptionInfoRequest unsupported") +} + +func (s *Server) onLoadedSourcesRequest(request *dap.LoadedSourcesRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "LoadedSourcesRequest unsupported") +} + +func (s *Server) onDataBreakpointInfoRequest(request *dap.DataBreakpointInfoRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "DataBreakpointInfoRequest unsupported") +} + +func (s *Server) onSetDataBreakpointsRequest(request *dap.SetDataBreakpointsRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "SetDataBreakpointsRequest unsupported") +} + +func (s *Server) onReadMemoryRequest(request *dap.ReadMemoryRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "ReadMemoryRequest unsupported") +} + +func (s *Server) onDisassembleRequest(request *dap.DisassembleRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "DisassembleRequest unsupported") +} + +func (s *Server) onCancelRequest(request *dap.CancelRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "CancelRequest unsupported") +} + +func (s *Server) onBreakpointLocationsRequest(request *dap.BreakpointLocationsRequest) { + s.sendUnsupportedResponse(request.Seq, request.Command, "BreakpointLocationsRequest unsupported") +} + +type outputWriter struct { + s *Server + category string +} + +func (w *outputWriter) Write(p []byte) (int, error) { + w.s.send(&dap.OutputEvent{Event: *newEvent("output"), Body: dap.OutputEventBody{Category: w.category, Output: string(p)}}) + return len(p), nil +} + +func newEvent(event string) *dap.Event { + return &dap.Event{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: 0, + Type: "event", + }, + Event: event, + } +} + +func newResponse(requestSeq int, command string) *dap.Response { + return &dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: 0, + Type: "response", + }, + Command: command, + RequestSeq: requestSeq, + Success: true, + } +} diff --git a/pkg/dap/dap_test.go b/pkg/dap/dap_test.go new file mode 100644 index 00000000..b61694b6 --- /dev/null +++ b/pkg/dap/dap_test.go @@ -0,0 +1,1202 @@ +package dap + +import ( + "bufio" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + "io" + "net" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + "testing" + "time" + + "github.com/google/go-dap" + "github.com/ktock/buildg/pkg/testutil" + "golang.org/x/crypto/ssh/agent" +) + +func TestContinueExit(t *testing.T) { + t.Parallel() + dt := fmt.Sprintf(`FROM %s +RUN echo a > /a +RUN echo b > /b +RUN echo c > /c`, testutil.Mirror("busybox:1.32.0")) + tmpCtx, doneTmpCtx := testutil.NewTempContext(t, dt) + defer doneTmpCtx() + + s := testutil.NewDAPServer(t) + defer s.Close() + c := newDAPClient(t, s.Conn()) + c.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: true, + }, []dap.SourceBreakpoint{}, true) + c.continueAndExit(t) +} + +func TestInitialBreakpoints(t *testing.T) { + t.Parallel() + dt := fmt.Sprintf(`FROM %s +RUN echo a > /a +RUN echo b > /b +RUN echo c > /c +RUN echo d > /d +RUN echo e > /e`, testutil.Mirror("busybox:1.32.0")) + tmpCtx, doneTmpCtx := testutil.NewTempContext(t, dt) + defer doneTmpCtx() + + s := testutil.NewDAPServer(t) + defer s.Close() + c := newDAPClient(t, s.Conn()) + c.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: true, + }, []dap.SourceBreakpoint{ + { + Line: 2, + }, + { + Line: 4, + }, + }, true) + c.continueAndStop(t, 2) + c.continueAndStop(t, 4) + c.continueAndExit(t) +} + +func TestInitialBreakpointsStopOnEntryFalse(t *testing.T) { + t.Parallel() + dt := fmt.Sprintf(`FROM %s +RUN echo a > /a +RUN echo b > /b +RUN echo c > /c +RUN echo d > /d +RUN echo e > /e`, testutil.Mirror("busybox:1.32.0")) + tmpCtx, doneTmpCtx := testutil.NewTempContext(t, dt) + defer doneTmpCtx() + + s := testutil.NewDAPServer(t) + defer s.Close() + c := newDAPClient(t, s.Conn()) + c.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: false, + }, []dap.SourceBreakpoint{ + { + Line: 2, + }, + { + Line: 4, + }, + }, true) + c.testCurrentLine(t, 2) + c.continueAndStop(t, 4) + c.continueAndExit(t) +} + +func TestSetBreakpoints(t *testing.T) { + t.Parallel() + dt := fmt.Sprintf(`FROM %s +RUN echo a > /a +RUN echo b > /b +RUN echo c > /c +RUN echo d > /d +RUN echo e > /e`, testutil.Mirror("busybox:1.32.0")) + tmpCtx, doneTmpCtx := testutil.NewTempContext(t, dt) + defer doneTmpCtx() + + s := testutil.NewDAPServer(t) + defer s.Close() + c := newDAPClient(t, s.Conn()) + c.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: true, + }, []dap.SourceBreakpoint{}, true) + c.setBreakpoints(t, []dap.SourceBreakpoint{ + { + Line: 2, + }, + { + Line: 4, + }, + }) + c.continueAndStop(t, 2) + c.continueAndStop(t, 4) + c.continueAndExit(t) +} + +func TestSetBreakpointsChange(t *testing.T) { + t.Parallel() + dt := fmt.Sprintf(`FROM %s +RUN echo a > /a +RUN echo b > /b +RUN echo c > /c +RUN echo d > /d +RUN echo e > /e`, testutil.Mirror("busybox:1.32.0")) + tmpCtx, doneTmpCtx := testutil.NewTempContext(t, dt) + defer doneTmpCtx() + + s := testutil.NewDAPServer(t) + defer s.Close() + c := newDAPClient(t, s.Conn()) + c.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: true, + }, []dap.SourceBreakpoint{}, true) + c.setBreakpoints(t, []dap.SourceBreakpoint{ + { + Line: 2, + }, + { + Line: 4, + }, + }) + c.continueAndStop(t, 2) + c.setBreakpoints(t, []dap.SourceBreakpoint{ + { + Line: 3, + }, + { + Line: 5, + }, + }) + c.continueAndStop(t, 3) + c.continueAndStop(t, 5) + c.continueAndExit(t) +} + +func TestNext(t *testing.T) { + t.Parallel() + dt := fmt.Sprintf(`FROM %s +RUN echo a > /a +RUN echo b > /b +RUN echo c > /c +RUN echo d > /d +RUN echo e > /e`, testutil.Mirror("busybox:1.32.0")) + tmpCtx, doneTmpCtx := testutil.NewTempContext(t, dt) + defer doneTmpCtx() + + s := testutil.NewDAPServer(t) + defer s.Close() + c := newDAPClient(t, s.Conn()) + c.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: true, + }, []dap.SourceBreakpoint{ + { + Line: 3, + }, + }, true) + c.continueAndStop(t, 3) + c.nextAndStop(t, 4) + c.nextAndStop(t, 5) + c.nextAndStop(t, 6) + c.nextAndExit(t) +} + +func TestNoDebug(t *testing.T) { + t.Parallel() + dt := fmt.Sprintf(`FROM %s +RUN echo a > /a +RUN echo b > /b +RUN echo c > /c +RUN echo d > /d +RUN echo e > /e`, testutil.Mirror("busybox:1.32.0")) + tmpCtx, doneTmpCtx := testutil.NewTempContext(t, dt) + defer doneTmpCtx() + + s := testutil.NewDAPServer(t) + defer s.Close() + c := newDAPClient(t, s.Conn()) + c.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: false, + NoDebug: true, + }, []dap.SourceBreakpoint{}, false) + if e, ok := c.receive(t).(*dap.ThreadEvent); !ok || e.Event.Event != "thread" || e.Body.Reason != "exited" { + t.Fatalf("thread event (exited) must be returned") + } + if _, ok := c.receive(t).(*dap.TerminatedEvent); !ok { + t.Fatalf("terminated event must be returned") + } + if _, ok := c.receive(t).(*dap.ExitedEvent); !ok { + t.Fatalf("exited event must be returned") + } +} + +func TestMultipleLineHits(t *testing.T) { + t.Parallel() + dt := fmt.Sprintf(`FROM %s AS dev-a +RUN echo a > /a + +FROM %s AS dev-b +RUN echo b > /b + +FROM scratch +COPY --from=dev-a /a /a +COPY --from=dev-b /b /b`, testutil.Mirror("busybox:1.32.0"), testutil.Mirror("busybox:1.32.0")) + tmpCtx, doneTmpCtx := testutil.NewTempContext(t, dt) + defer doneTmpCtx() + + s := testutil.NewDAPServer(t) + defer s.Close() + c := newDAPClient(t, s.Conn()) + c.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: true, + }, []dap.SourceBreakpoint{ + { + Line: 4, + }, + }, true) + c.testCurrentLine(t, 4) + c.continueAndExit(t) +} + +func TestBreakpointVariables(t *testing.T) { + t.Parallel() + dt := fmt.Sprintf(`FROM %s +ARG FOO=bar +RUN echo a > /a +RUN echo b > /b`, testutil.Mirror("busybox:1.32.0")) + tmpCtx, doneTmpCtx := testutil.NewTempContext(t, dt) + defer doneTmpCtx() + + s := testutil.NewDAPServer(t) + defer s.Close() + c := newDAPClient(t, s.Conn()) + c.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: true, + }, []dap.SourceBreakpoint{ + { + Line: 3, + }, + }, true) + c.continueAndStop(t, 3) + c.send(&dap.ThreadsRequest{ + Request: c.newRequest("threads"), + }) + if r, ok := c.receive(t).(*dap.ThreadsResponse); !ok || len(r.Body.Threads) != 1 || r.Body.Threads[0].Id != 1 { + t.Fatalf("threads response must be returned") + } + c.send(&dap.StackTraceRequest{ + Request: c.newRequest("stackTrace"), + Arguments: dap.StackTraceArguments{ + ThreadId: 1, + }, + }) + if r, ok := c.receive(t).(*dap.StackTraceResponse); !ok || len(r.Body.StackFrames) != 1 || r.Body.StackFrames[0].Line != 3 { + t.Fatalf("stackTrace response must be returned") + } + c.send(&dap.ScopesRequest{ + Request: c.newRequest("scopes"), + Arguments: dap.ScopesArguments{ + FrameId: 0, + }, + }) + if r, ok := c.receive(t).(*dap.ScopesResponse); !ok || len(r.Body.Scopes) != 1 || r.Body.Scopes[0].Name != "Environment Variables" || r.Body.Scopes[0].VariablesReference != 1 { + t.Fatalf("scopes response must be returned") + } + c.send(&dap.VariablesRequest{ + Request: c.newRequest("variables"), + Arguments: dap.VariablesArguments{ + VariablesReference: 1, + }, + }) + r, ok := c.receive(t).(*dap.VariablesResponse) + if !ok || len(r.Body.Variables) == 0 { + t.Fatalf("variables response must be returned") + } + var found bool + for _, kv := range r.Body.Variables { + if kv.Name == "FOO" && kv.Value == "bar" { + found = true + } + } + if !found { + t.Fatalf("failed to get variable") + } + c.continueAndExit(t) +} + +func TestTarget(t *testing.T) { + t.Parallel() + dt := fmt.Sprintf(`FROM %s AS dev-1 +RUN echo a > /a +RUN echo b > /b + +FROM %s AS dev-2 +RUN echo c > /c +COPY --from=dev-1 /a /a +COPY --from=dev-1 /b /b +RUN echo d > /d + +FROM %s AS dev-3 +RUN echo e > /e +RUN echo f > /f`, testutil.Mirror("busybox:1.32.0"), testutil.Mirror("busybox:1.32.0"), testutil.Mirror("busybox:1.32.0")) + tmpCtx, doneTmpCtx := testutil.NewTempContext(t, dt) + defer doneTmpCtx() + + s := testutil.NewDAPServer(t) + defer s.Close() + c := newDAPClient(t, s.Conn()) + c.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: true, + Target: "dev-2", + }, []dap.SourceBreakpoint{ + { + Line: 3, + }, + { + Line: 9, + }, + { + Line: 12, // will be skipped + }, + }, true) + c.continueAndStop(t, 3) + c.continueAndStop(t, 9) + c.continueAndExit(t) +} + +func TestExecSimple(t *testing.T) { + t.Parallel() + dt := fmt.Sprintf(`FROM %s +RUN echo -n a > /a +RUN echo -n b > /b +RUN echo -n c > /c`, testutil.Mirror("busybox:1.32.0")) + tmpCtx, doneTmpCtx := testutil.NewTempContext(t, dt) + defer doneTmpCtx() + + s := testutil.NewDAPServer(t) + defer s.Close() + c := newDAPClient(t, s.Conn()) + c.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: true, + Image: testutil.Mirror("ubuntu:20.04"), + }, []dap.SourceBreakpoint{ + { + Line: 2, + }, + { + Line: 3, + }, + }, true) + c.continueAndStop(t, 2) + if out := c.execContainer(t, "cat /a"); out != "a" { + t.Fatalf("wanted: \"a\"; got: %q", out) + } + if out := c.execContainer(t, "cat /b"); out != "" { + t.Fatalf("wanted: \"\"; got: %q", out) + } + if out := c.execContainer(t, "--image cat /debugroot/a"); out != "a" { + t.Fatalf("wanted: \"a\"; got: %q", out) + } + if out := c.execContainer(t, "--image cat /etc/os-release"); !strings.Contains(out, `NAME="Ubuntu"`) { + t.Fatalf("wanted: \"NAME=\"Ubuntu\"\"; got: %q", out) + } + if out := c.execContainer(t, "--image --mountroot=/testdebugroot/rootdir/ cat /testdebugroot/rootdir/a"); out != "a" { + t.Fatalf("wanted: \"a\"; got: %q", out) + } + if out := c.execContainer(t, "--init-state cat /a"); out != "" { + t.Fatalf("wanted: \"\"; got: %q", out) + } + if out := c.execContainer(t, `-e MSG=hello -e MSG2=world /bin/sh -c "echo -n $MSG $MSG2"`); out != "hello world" { + t.Fatalf("wanted: \"hello world\"; got: %q", out) + } + if out := c.execContainer(t, `--workdir /tmp /bin/sh -c "echo -n $(pwd)"`); out != "/tmp" { + t.Fatalf("wanted: \"/tmp\"; got: %q", out) + } + c.continueAndStop(t, 3) + if out := c.execContainer(t, "cat /a"); out != "a" { + t.Fatalf("wanted: \"a\"; got: %q", out) + } + if out := c.execContainer(t, "cat /b"); out != "b" { + t.Fatalf("wanted: \"b\"; got: %q", out) + } + c.continueAndExit(t) +} + +func TestExecSecrets(t *testing.T) { + t.Parallel() + dt := fmt.Sprintf(`FROM %s +RUN --mount=type=secret,id=testsecret,target=/root/secret [ "$(cat /root/secret)" = 'test-secret' ]`, + testutil.Mirror("busybox:1.32.0")) + tmpCtx, doneTmpCtx := testutil.NewTempContext(t, dt) + defer doneTmpCtx() + + tmpSec, err := os.CreateTemp("", "testexecsecret") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpSec.Name()) + if _, err := tmpSec.Write([]byte("test-secret")); err != nil { + t.Fatal(err) + } + if err := tmpSec.Close(); err != nil { + t.Fatal(err) + } + + // secrets from path + s := testutil.NewDAPServer(t) + defer s.Close() + c := newDAPClient(t, s.Conn()) + c.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: true, + Secrets: []string{"id=testsecret,src=" + tmpSec.Name()}, + }, []dap.SourceBreakpoint{}, true) + c.nextAndStop(t, 2) + if out := c.execContainer(t, "cat /root/secret"); out != "test-secret" { + t.Fatalf("wanted: \"test-secret\"; got: %q", out) + } + c.continueAndExit(t) + s.Close() + + // secrets from env + s2 := testutil.NewDAPServer(t, testutil.WithDAPServerEnv("TEST_SECRET=test-secret")) + defer s2.Close() + c2 := newDAPClient(t, s2.Conn()) + c2.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: true, + Secrets: []string{"id=testsecret,env=TEST_SECRET"}, + }, []dap.SourceBreakpoint{}, true) + c2.nextAndStop(t, 2) + if out := c2.execContainer(t, "cat /root/secret"); out != "test-secret" { + t.Fatalf("wanted: \"test-secret\"; got: %q", out) + } + c2.continueAndExit(t) +} + +func TestExecSSH(t *testing.T) { + t.Parallel() + // test ssh from socket + tests := []struct { + name string + launchConfig func(sockPath string) LaunchConfig + buildgOptions func(sockPath string) []testutil.DAPServerOption + mountOption string + }{ + { + name: "default", + launchConfig: func(sockPath string) LaunchConfig { + return LaunchConfig{ + SSH: []string{"default=" + sockPath}, + } + }, + mountOption: "type=ssh", + }, + { + name: "default-env", + launchConfig: func(sockPath string) LaunchConfig { + return LaunchConfig{ + SSH: []string{"default"}, + } + }, + buildgOptions: func(sockPath string) []testutil.DAPServerOption { + return []testutil.DAPServerOption{ + testutil.WithDAPServerEnv("SSH_AUTH_SOCK=" + sockPath), + } + }, + mountOption: "type=ssh", + }, + { + name: "id", + launchConfig: func(sockPath string) LaunchConfig { + return LaunchConfig{ + SSH: []string{"mysecret=" + sockPath}, + } + }, + mountOption: "type=ssh,id=mysecret", + }, + { + name: "id-env", + launchConfig: func(sockPath string) LaunchConfig { + return LaunchConfig{ + SSH: []string{"mysecret"}, + } + }, + buildgOptions: func(sockPath string) []testutil.DAPServerOption { + return []testutil.DAPServerOption{ + testutil.WithDAPServerEnv("SSH_AUTH_SOCK=" + sockPath), + } + }, + mountOption: "type=ssh,id=mysecret", + }, + { + name: "id-env2", + launchConfig: func(sockPath string) LaunchConfig { + return LaunchConfig{ + SSH: []string{"mysecret", "mysecret2"}, + } + }, + buildgOptions: func(sockPath string) []testutil.DAPServerOption { + return []testutil.DAPServerOption{ + testutil.WithDAPServerEnv("SSH_AUTH_SOCK=" + sockPath), + } + }, + mountOption: "type=ssh,id=mysecret2", + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + a := agent.NewKeyring() + k, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatal(err) + } + if err := a.Add(agent.AddedKey{PrivateKey: k}); err != nil { + t.Fatal(err) + } + tmpSock, err := os.MkdirTemp("", "sshsockroot") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpSock) + sockPath := filepath.Join(tmpSock, "ssh_auth_sock") + l, err := net.Listen("unix", sockPath) + if err != nil { + t.Fatal(err) + } + defer l.Close() + go func() { + for { + c, err := l.Accept() + if err != nil { + return + } + go agent.ServeAgent(a, c) + } + }() + tmpCtx, doneTmpCtx := testutil.NewTempContext(t, fmt.Sprintf(`FROM %s +RUN apk add openssh +RUN --mount=%s ssh-add -l | grep 2048 | grep RSA`, + testutil.Mirror("alpine:3.15.3"), tt.mountOption)) + defer doneTmpCtx() + + var bOpts []testutil.DAPServerOption + if tt.buildgOptions != nil { + bOpts = tt.buildgOptions(sockPath) + } + s := testutil.NewDAPServer(t, bOpts...) + defer s.Close() + c := newDAPClient(t, s.Conn()) + lCfg := tt.launchConfig(sockPath) + lCfg.Program = filepath.Join(tmpCtx, "Dockerfile") + lCfg.StopOnEntry = true + c.start(t, lCfg, []dap.SourceBreakpoint{ + { + Line: 3, + }, + }, true) + c.continueAndStop(t, 3) + if out := c.execContainer(t, `ssh-add -l | grep 2048 | grep RSA`); !strings.Contains(out, "2048") || !strings.Contains(out, "(RSA)") { + t.Fatalf("wanted: \"2048\" and \"(RSA)\"; got: %q", out) + } + if out := c.execContainer(t, `/bin/sh -c "ssh-keygen -f /tmp/key -N '' && ssh-add -k /tmp/key 2>&1"`); !strings.Contains(out, "agent refused operation") { + t.Fatalf("wanted: \"agent refused operation\"; got: %q", out) + } + c.continueAndExit(t) + }) + } + + // test ssh from file + tmpCtx, doneTmpCtx := testutil.NewTempContext(t, fmt.Sprintf(`FROM %s +RUN apk add openssh +RUN --mount=type=ssh,id=testsecret ssh-add -l | grep 2048 | grep RSA`, + testutil.Mirror("alpine:3.15.3"))) + defer doneTmpCtx() + tmpSec, err := os.CreateTemp("", "testexecsecret") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpSec.Name()) + k, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatal(err) + } + if _, err := tmpSec.Write(pem.EncodeToMemory( + &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(k), + }, + )); err != nil { + t.Fatal(err) + } + if err := tmpSec.Close(); err != nil { + t.Fatal(err) + } + + s := testutil.NewDAPServer(t) + defer s.Close() + c := newDAPClient(t, s.Conn()) + c.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: true, + SSH: []string{"testsecret=" + tmpSec.Name()}, + }, []dap.SourceBreakpoint{ + { + Line: 3, + }, + }, true) + c.continueAndStop(t, 3) + if out := c.execContainer(t, `ssh-add -l | grep 2048 | grep RSA`); !strings.Contains(out, "2048") || !strings.Contains(out, "(RSA)") { + t.Fatalf("wanted: \"2048\" and \"(RSA)\"; got: %q", out) + } + c.continueAndExit(t) +} + +func TestCacheReuse(t *testing.T) { + t.Parallel() + tmpCtx, doneTmpCtx := testutil.NewTempContext(t, fmt.Sprintf(`FROM %s +RUN date > /a +RUN date > /b +RUN date > / +`, testutil.Mirror("busybox:1.32.0"))) + defer doneTmpCtx() + + tmpRoot, err := os.MkdirTemp(os.Getenv(testutil.BuildgTestTmpDirEnv), "buildg-test-tmproot") + if err != nil { + t.Fatal(err) + return + } + defer os.RemoveAll(tmpRoot) + + s := testutil.NewDAPServer(t, testutil.WithDAPServerGlobalOptions("--root="+tmpRoot)) + defer s.Close() + c := newDAPClient(t, s.Conn()) + c.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: true, + }, []dap.SourceBreakpoint{}, true) + c.nextAndStop(t, 2) + a := nonEmpty(t, c.execContainer(t, "cat /a")) + c.nextAndStop(t, 3) + b := nonEmpty(t, c.execContainer(t, "cat /b")) + c.nextAndStop(t, 4) + a2 := nonEmpty(t, c.execContainer(t, "cat /a")) + b2 := nonEmpty(t, c.execContainer(t, "cat /b")) + c.nextAndExit(t) + s.Close() + + s2 := testutil.NewDAPServer(t, testutil.WithDAPServerGlobalOptions("--root="+tmpRoot)) + defer s2.Close() + c2 := newDAPClient(t, s2.Conn()) + c2.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: true, + }, []dap.SourceBreakpoint{}, true) + c2.nextAndStop(t, 2) + if out := c2.execContainer(t, "cat /a"); out != a { + t.Fatalf("want %q; got %q", a, out) + } + c2.nextAndStop(t, 3) + if out := c2.execContainer(t, "cat /b"); out != b { + t.Fatalf("want %q; got %q", b, out) + } + c2.nextAndStop(t, 4) + if out := c2.execContainer(t, "cat /a"); out != a2 { + t.Fatalf("want %q; got %q", a2, out) + } + if out := c2.execContainer(t, "cat /b"); out != b2 { + t.Fatalf("want %q; got %q", b2, out) + } + c2.nextAndExit(t) + s2.Close() + + if _, err := testutil.BuildgCmd(t, []string{"dap", "prune"}, testutil.WithGlobalOptions("--root="+tmpRoot)).Output(); err != nil { + t.Fatal(err) + } + duOut, err := testutil.BuildgCmd(t, []string{"dap", "du"}, testutil.WithGlobalOptions("--root="+tmpRoot)).Output() + if err != nil { + t.Fatal(err) + } + zeroOut := "Total:\t0 B" + if !strings.Contains(string(duOut), zeroOut) { + t.Fatalf("du must contain %q; got %q", zeroOut, string(duOut)) + } + + sp := testutil.NewDAPServer(t, testutil.WithDAPServerGlobalOptions("--root="+tmpRoot)) + defer sp.Close() + cp := newDAPClient(t, sp.Conn()) + cp.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: true, + }, []dap.SourceBreakpoint{}, true) + cp.nextAndStop(t, 2) + if out := cp.execContainer(t, "cat /a"); out == a { + t.Fatalf("shouldn't be %q; got %q", a, out) + } + cp.nextAndStop(t, 3) + if out := cp.execContainer(t, "cat /b"); out == b { + t.Fatalf("shouldn't be %q; got %q", b, out) + } + cp.nextAndStop(t, 4) + if out := cp.execContainer(t, "cat /a"); out == a2 { + t.Fatalf("shouldn't be %q; got %q", a2, out) + } + if out := cp.execContainer(t, "cat /b"); out == b2 { + t.Fatalf("shouldn't be %q; got %q", b2, out) + } + cp.nextAndExit(t) +} + +func TestDisconnect(t *testing.T) { + t.Parallel() + tmpCtx, doneTmpCtx := testutil.NewTempContext(t, fmt.Sprintf(`FROM %s +RUN date > /a +RUN date > /b +RUN date > / +`, testutil.Mirror("alpine:3.15.3"))) + defer doneTmpCtx() + + tmpRoot, err := os.MkdirTemp(os.Getenv(testutil.BuildgTestTmpDirEnv), "buildg-test-tmproot") + if err != nil { + t.Fatal(err) + return + } + defer os.RemoveAll(tmpRoot) + + // Test normal disconnection + s := testutil.NewDAPServer(t, testutil.WithDAPServerGlobalOptions("--root="+tmpRoot)) + defer s.Close() + c := newDAPClient(t, s.Conn()) + c.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: true, + }, []dap.SourceBreakpoint{}, true) + c.nextAndStop(t, 2) + nonEmpty(t, c.execContainer(t, "cat /a")) + c.nextAndStop(t, 3) + nonEmpty(t, c.execContainer(t, "cat /b")) + c.nextAndStop(t, 4) + nonEmpty(t, c.execContainer(t, "cat /a")) + nonEmpty(t, c.execContainer(t, "cat /b")) + c.disconnect(t) + doneCh := make(chan struct{}) + go func() { + s.Wait() + close(doneCh) + }() + select { + case <-doneCh: + case <-time.After(5 * time.Second): + t.Fatalf("disconnect takes too long") + } + + // Test disconnection under the case where running container and active I/O exist + s2 := testutil.NewDAPServer(t, testutil.WithDAPServerGlobalOptions("--root="+tmpRoot)) + defer s2.Close() + c2 := newDAPClient(t, s2.Conn()) + c2.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: true, + }, []dap.SourceBreakpoint{}, true) + c2.nextAndStop(t, 2) + cmd := c2.execContainerCmd(t, `/bin/sh -c "echo started; sleep 1000000"`) + pr, pw := io.Pipe() + cmd.Stdout = pw + cmd.Stderr = os.Stderr + if err := cmd.Start(); err != nil { + t.Fatalf("failed to start io client: %v", err) + } + doneCh = make(chan struct{}) + errCh := make(chan error) + go func() { + scanner := bufio.NewScanner(io.TeeReader(pr, os.Stdout)) + for scanner.Scan() { + txt := scanner.Text() + if txt == "started" { + close(doneCh) + return + } + } + if err := scanner.Err(); err != nil { + errCh <- err + } + }() + select { + case <-doneCh: + case err := <-errCh: + t.Fatalf("failed to wait for container output: %v", err) + case <-time.After(3 * time.Second): + t.Fatalf("io timeout during waiting for container startup string") + } + c2.disconnect(t) + doneCh = make(chan struct{}) + go func() { + s2.Wait() + cmd.Process.Wait() + close(doneCh) + }() + select { + case <-doneCh: + case <-time.After(5 * time.Second): + t.Fatalf("disconnect takes too long") + } + + // Test disconnection under the corner case where I/O attacher starts too late after disconnection + s3 := testutil.NewDAPServer(t, testutil.WithDAPServerGlobalOptions("--root="+tmpRoot)) + defer s3.Close() + c3 := newDAPClient(t, s3.Conn()) + c3.start(t, LaunchConfig{ + Program: filepath.Join(tmpCtx, "Dockerfile"), + StopOnEntry: true, + }, []dap.SourceBreakpoint{}, true) + c3.nextAndStop(t, 2) + cmd = c3.execContainerCmd(t, `/bin/sh -c "echo started; sleep 1000000"`) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + c3.disconnect(t) // disconnect before attaching + s3.Wait() // wait for server ending + if err := cmd.Start(); err != nil { + t.Fatalf("failed to start io client: %v", err) + } + doneCh = make(chan struct{}) + go func() { + cmd.Process.Wait() // this should not hang + close(doneCh) + }() + select { + case <-doneCh: + case <-time.After(5 * time.Second): + t.Fatalf("disconnect takes too long") + } +} + +func newDAPClient(t *testing.T, conn net.Conn) *dapClient { + c := &dapClient{ + conn: conn, + mesCh: make(chan dap.Message), + errCh: make(chan error), + } + go c.listen() + return c +} + +type dapClient struct { + conn net.Conn + sendMu sync.Mutex + seq int + mesCh chan dap.Message + errCh chan error +} + +func (c *dapClient) setBreakpoints(t *testing.T, breakpoints []dap.SourceBreakpoint) { + c.send(&dap.SetBreakpointsRequest{ + Request: c.newRequest("setBreakpoints"), + Arguments: dap.SetBreakpointsArguments{ + Source: dap.Source{ + Name: "Dockerfile", + }, + Breakpoints: breakpoints, + }, + }) + if _, ok := c.receive(t).(*dap.SetBreakpointsResponse); !ok { + t.Fatalf("setBreakpoints response must be returned") + } +} + +func (c *dapClient) disconnect(t *testing.T) { + c.send(&dap.DisconnectRequest{ + Request: c.newRequest("disconnect"), + }) + if e, ok := c.receive(t).(*dap.ThreadEvent); !ok || e.Event.Event != "thread" || e.Body.Reason != "exited" { + t.Fatalf("thread event (exited) must be returned") + } + if _, ok := c.receive(t).(*dap.TerminatedEvent); !ok { + t.Fatalf("terminated event must be returned") + } + if _, ok := c.receive(t).(*dap.ExitedEvent); !ok { + t.Fatalf("exited event must be returned") + } + if _, ok := c.receive(t).(*dap.DisconnectResponse); !ok { + t.Fatalf("disconnect response must be returned") + } +} + +// https://microsoft.github.io/debug-adapter-protocol/overview#debug-session-end +func (c *dapClient) continueAndExit(t *testing.T) { + c.send(&dap.ContinueRequest{ + Request: c.newRequest("continue"), + Arguments: dap.ContinueArguments{ + ThreadId: 1, + }, + }) + if _, ok := c.receive(t).(*dap.ContinueResponse); !ok { + t.Fatalf("continue response must be returned") + } + if e, ok := c.receive(t).(*dap.ThreadEvent); !ok || e.Event.Event != "thread" || e.Body.Reason != "exited" { + t.Fatalf("thread event (exited) must be returned") + } + if _, ok := c.receive(t).(*dap.TerminatedEvent); !ok { + t.Fatalf("terminated event must be returned") + } + if _, ok := c.receive(t).(*dap.ExitedEvent); !ok { + t.Fatalf("exited event must be returned") + } +} + +func (c *dapClient) continueAndStop(t *testing.T, line int) { + c.send(&dap.ContinueRequest{ + Request: c.newRequest("continue"), + Arguments: dap.ContinueArguments{ + ThreadId: 1, + }, + }) + if _, ok := c.receive(t).(*dap.ContinueResponse); !ok { + t.Fatalf("continue response must be returned") + } + if _, ok := c.receive(t).(*dap.StoppedEvent); !ok { + t.Fatalf("stopped event must be returned") + } + c.testCurrentLine(t, line) +} + +func (c *dapClient) nextAndExit(t *testing.T) { + c.send(&dap.NextRequest{ + Request: c.newRequest("next"), + Arguments: dap.NextArguments{ + ThreadId: 1, + }, + }) + if _, ok := c.receive(t).(*dap.NextResponse); !ok { + t.Fatalf("next response must be returned") + } + if e, ok := c.receive(t).(*dap.ThreadEvent); !ok || e.Event.Event != "thread" || e.Body.Reason != "exited" { + t.Fatalf("thread event (exited) must be returned") + } + if _, ok := c.receive(t).(*dap.TerminatedEvent); !ok { + t.Fatalf("terminated event must be returned") + } + if _, ok := c.receive(t).(*dap.ExitedEvent); !ok { + t.Fatalf("exited event must be returned") + } +} + +func (c *dapClient) nextAndStop(t *testing.T, line int) { + c.send(&dap.NextRequest{ + Request: c.newRequest("next"), + Arguments: dap.NextArguments{ + ThreadId: 1, + }, + }) + if _, ok := c.receive(t).(*dap.NextResponse); !ok { + t.Fatalf("next response must be returned") + } + if _, ok := c.receive(t).(*dap.StoppedEvent); !ok { + t.Fatalf("stopped event must be returned") + } + c.testCurrentLine(t, line) +} + +func (c *dapClient) testCurrentLine(t *testing.T, line int) { + c.send(&dap.ThreadsRequest{ + Request: c.newRequest("threads"), + }) + if r, ok := c.receive(t).(*dap.ThreadsResponse); !ok || len(r.Body.Threads) != 1 || r.Body.Threads[0].Id != 1 { + t.Fatalf("threads response must be returned") + } + c.send(&dap.StackTraceRequest{ + Request: c.newRequest("stackTrace"), + Arguments: dap.StackTraceArguments{ + ThreadId: 1, + }, + }) + if r, ok := c.receive(t).(*dap.StackTraceResponse); !ok || len(r.Body.StackFrames) != 1 || r.Body.StackFrames[0].Line != line { + t.Fatalf("stackTrace response (want line: %d) must be returned", line) + } +} + +// https://microsoft.github.io/debug-adapter-protocol/overview#configuring-breakpoint-and-exception-behavior +func (c *dapClient) start(t *testing.T, launchCfg LaunchConfig, initialBreakpoints []dap.SourceBreakpoint, wait bool) { + c.send(&dap.InitializeRequest{ + Request: c.newRequest("initialize"), + Arguments: dap.InitializeRequestArguments{ + AdapterID: "dockerfile", + LinesStartAt1: true, + ColumnsStartAt1: true, + PathFormat: "path", + SupportsRunInTerminalRequest: true, + }, + }) + if _, ok := c.receive(t).(*dap.InitializeResponse); !ok { + t.Fatalf("initialize response must be returned") + } + if _, ok := c.receive(t).(*dap.InitializedEvent); !ok { + t.Fatalf("initialized event must be returned") + } // TODO: check capabilities + + c.send(&dap.SetBreakpointsRequest{ + Request: c.newRequest("setBreakpoints"), + Arguments: dap.SetBreakpointsArguments{ + Source: dap.Source{ + Name: "Dockerfile", + }, + Breakpoints: initialBreakpoints, + }, + }) + if _, ok := c.receive(t).(*dap.SetBreakpointsResponse); !ok { + t.Fatalf("setBreakpoints response must be returned") + } + + // TODO: check breakpoints + + // setExceptionBreakpoints request is optional + + c.send(&dap.ConfigurationDoneRequest{ + Request: c.newRequest("configurationDone"), + }) + if _, ok := c.receive(t).(*dap.ConfigurationDoneResponse); !ok { + t.Fatalf("configurationDone response must be returned") + } + + launchArgument, err := json.Marshal(launchCfg) + if err != nil { + t.Fatalf("failed to marshal launch config: %v", err) + } + c.send(&dap.LaunchRequest{ + Request: c.newRequest("launch"), + Arguments: json.RawMessage(launchArgument), + }) + if e, ok := c.receive(t).(*dap.ThreadEvent); !ok || e.Event.Event != "thread" || e.Body.Reason != "started" { + t.Fatalf("thread event (stardted) must be returned") // TODO: do we need this (undocumented in DAP) + } + if _, ok := c.receive(t).(*dap.LaunchResponse); !ok { + t.Fatalf("launch response must be returned") + } + if wait { + if _, ok := c.receive(t).(*dap.StoppedEvent); !ok { + t.Fatalf("stopped event must be returned") + } + } +} + +func (c *dapClient) execContainer(t *testing.T, args string) string { + c.send(&dap.EvaluateRequest{ + Request: c.newRequest("evaluate"), + Arguments: dap.EvaluateArguments{ + Expression: "exec -t=false -i=false " + args, + Context: "repl", + }, + }) + if _, ok := c.receive(t).(*dap.EvaluateResponse); !ok { + t.Fatalf("evaluate response must be returned") + } + runInTerminalReq, ok := c.receive(t).(*dap.RunInTerminalRequest) + if !ok { + t.Fatalf("runInTerminal request must be returned") + } + if k := runInTerminalReq.Arguments.Kind; k != "integrated" { + t.Fatalf("wants: \"integrated\", got: %q", k) + } + if args := runInTerminalReq.Arguments.Args; len(args) < 2 { + t.Fatalf("not enough arguments: %q", args) + } + if cwd := runInTerminalReq.Arguments.Cwd; cwd == "" { + t.Fatalf("cwd must be provided") + } + execCmd := exec.Command(runInTerminalReq.Arguments.Args[0], runInTerminalReq.Arguments.Args[1:]...) + execCmd.Stderr = os.Stderr + out, err := execCmd.Output() + if err != nil { + t.Fatalf("failed to run command: %v", err) + } + return string(out) +} + +func (c *dapClient) execContainerCmd(t *testing.T, args string) *exec.Cmd { + c.send(&dap.EvaluateRequest{ + Request: c.newRequest("evaluate"), + Arguments: dap.EvaluateArguments{ + Expression: "exec -t=false -i=false " + args, + Context: "repl", + }, + }) + if _, ok := c.receive(t).(*dap.EvaluateResponse); !ok { + t.Fatalf("evaluate response must be returned") + } + runInTerminalReq, ok := c.receive(t).(*dap.RunInTerminalRequest) + if !ok { + t.Fatalf("runInTerminal request must be returned") + } + if k := runInTerminalReq.Arguments.Kind; k != "integrated" { + t.Fatalf("wants: \"integrated\", got: %q", k) + } + if args := runInTerminalReq.Arguments.Args; len(args) < 2 { + t.Fatalf("not enough arguments: %q", args) + } + return exec.Command(runInTerminalReq.Arguments.Args[0], runInTerminalReq.Arguments.Args[1:]...) +} + +func (c *dapClient) send(message dap.Message) { + c.sendMu.Lock() + dap.WriteProtocolMessage(c.conn, message) + fmt.Printf("message sent %+v\n", message) + c.sendMu.Unlock() +} + +func (c *dapClient) receive(t *testing.T) dap.Message { + select { + case mes := <-c.mesCh: + return mes + case err := <-c.errCh: + t.Fatalf("failed to receive message: %v", err) + } + return nil +} + +func (c *dapClient) listen() { + defer close(c.mesCh) + r := bufio.NewReader(c.conn) + for { + req, err := dap.ReadProtocolMessage(r) + if err != nil { + if err == io.EOF { + return + } + c.errCh <- err + } + if _, ok := req.(*dap.OutputEvent); ok { + fmt.Printf("output message received %+v\n", req) + continue + } + fmt.Printf("message received %+v\n", req) + c.mesCh <- req + } +} + +func (c *dapClient) newRequest(command string) dap.Request { + c.sendMu.Lock() + defer c.sendMu.Unlock() + c.seq++ + return dap.Request{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: c.seq, + Type: "request", + }, + Command: command, + } +} + +func nonEmpty(t *testing.T, s string) string { + if s == "" { + t.Fatal("must not empty") + } + return s +} diff --git a/pkg/dap/debugger.go b/pkg/dap/debugger.go new file mode 100644 index 00000000..08b9fc5d --- /dev/null +++ b/pkg/dap/debugger.go @@ -0,0 +1,207 @@ +package dap + +import ( + "context" + "fmt" + "io" + "os" + "path/filepath" + "strconv" + + "github.com/ktock/buildg/pkg/buildkit" + "github.com/moby/buildkit/client" + "github.com/moby/buildkit/cmd/buildctl/build" + "github.com/moby/buildkit/cmd/buildkitd/config" + "github.com/moby/buildkit/session" + "github.com/moby/buildkit/session/auth/authprovider" + "github.com/moby/buildkit/session/sshforward/sshprovider" + "github.com/sirupsen/logrus" +) + +type debugger struct { + mesCh chan string + bps *buildkit.Breakpoints + config *config.Config + stoppedCh chan []int + + lConfig *LaunchConfig + debugCancel func() error + + bCtx *buildkit.BreakContext + cleanupAll bool +} + +func newDebugger(cfg *config.Config, cleanupAll bool) (*debugger, error) { + bp := buildkit.NewBreakpoints() + if _, err := bp.Add("on-fail", buildkit.NewOnFailBreakpoint()); err != nil { + return nil, err + } + return &debugger{ + mesCh: make(chan string), + bps: bp, + config: cfg, + stoppedCh: make(chan []int), + cleanupAll: cleanupAll, + }, nil +} + +func (d *debugger) launchConfig() *LaunchConfig { + return d.lConfig +} + +func (d *debugger) launch(cfg LaunchConfig, onStartHook, onFinishHook func(), stopOnEntry, disableBreakpoints bool, pw io.Writer) error { + if d.lConfig != nil { + // debugger has already been lauched + return fmt.Errorf("multi session unsupported") + } + + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + doneCh := make(chan struct{}) + defer close(doneCh) + d.debugCancel = func() error { + cancel() + <-doneCh + logrus.Debugf("debug finished") + return nil + } + + solveOpt, err := parseDAPSolveOpt(cfg) + if err != nil { + return err + } + + logrus.Debugf("root dir: %v", d.config.Root) + defer logrus.Debugf("done debug on %v", d.config.Root) + + d.lConfig = &cfg + onStartHook() + err = buildkit.Debug(ctx, d.config, solveOpt, pw, buildkit.DebugConfig{ + BreakpointHandler: d.breakHandler, + DebugImage: cfg.Image, + Breakpoints: d.bps, + StopOnEntry: stopOnEntry, + DisableBreakpoints: disableBreakpoints, + CleanupAll: d.cleanupAll, + }) + onFinishHook() + return err +} + +func parseDAPSolveOpt(cfg LaunchConfig) (*client.SolveOpt, error) { + if cfg.Program == "" { + return nil, fmt.Errorf("program must be specified") + } + + var optStr []string + dir, file := filepath.Split(cfg.Program) + localDirs, err := build.ParseLocal([]string{ + "context=" + dir, + "dockerfile=" + dir, + }) + if err != nil { + return nil, err + } + optStr = append(optStr, "filename="+file) + if target := cfg.Target; target != "" { + optStr = append(optStr, "target="+target) + } + for _, ba := range cfg.BuildArgs { + optStr = append(optStr, "build-arg:"+ba) + } + frontendAttrs, err := build.ParseOpt(optStr) + if err != nil { + return nil, err + } + exports, err := build.ParseOutput([]string{"type=image"}) + if err != nil { + return nil, err + } + attachable := []session.Attachable{authprovider.NewDockerAuthProvider(os.Stderr)} + if ssh := cfg.SSH; len(ssh) > 0 { + configs, err := build.ParseSSH(ssh) + if err != nil { + return nil, err + } + sp, err := sshprovider.NewSSHAgentProvider(configs) + if err != nil { + return nil, err + } + attachable = append(attachable, sp) + } + if secrets := cfg.Secrets; len(secrets) > 0 { + secretProvider, err := build.ParseSecret(secrets) + if err != nil { + return nil, err + } + attachable = append(attachable, secretProvider) + } + return &client.SolveOpt{ + Exports: exports, + LocalDirs: localDirs, + FrontendAttrs: frontendAttrs, + Session: attachable, + // CacheImports: + // CacheExports: + }, nil +} + +func (d *debugger) cancel() error { + close(d.stoppedCh) + if d.debugCancel == nil { + return nil + } + if err := d.debugCancel(); err != nil { + logrus.WithError(err).Warnf("failed to close") + } + return nil +} + +func (d *debugger) breakpoints() *buildkit.Breakpoints { + return d.bps +} + +func (d *debugger) doContinue() { + if d.bCtx != nil && d.bCtx.Handler != nil { + d.bCtx.Handler.BreakEachVertex(false) + d.mesCh <- "continue" + } else { + logrus.Warnf("continue is reqested but no break happens") + } +} + +func (d *debugger) doNext() { + if d.bCtx != nil && d.bCtx.Handler != nil { + d.bCtx.Handler.BreakEachVertex(true) + d.mesCh <- "next" + } else { + logrus.Warnf("next is reqested but no break happens") + } +} + +func (d *debugger) breakContext() *buildkit.BreakContext { + return d.bCtx +} + +func (d *debugger) stopped() <-chan []int { + return d.stoppedCh +} + +func (d *debugger) breakHandler(ctx context.Context, bCtx buildkit.BreakContext) error { + for key, desc := range bCtx.Hits { + logrus.Debugf("Breakpoint[%s]: %s", key, desc) + } + bpIDs := make([]int, 0) + for si := range bCtx.Hits { + keyI, err := strconv.ParseInt(si, 10, 64) + if err != nil { + logrus.WithError(err).Warnf("failed to parse breakpoint key") + continue + } + bpIDs = append(bpIDs, int(keyI)) + } + d.stoppedCh <- bpIDs + d.bCtx = &bCtx + <-d.mesCh + return nil +} diff --git a/pkg/dap/io.go b/pkg/dap/io.go new file mode 100644 index 00000000..74ec7454 --- /dev/null +++ b/pkg/dap/io.go @@ -0,0 +1,230 @@ +package dap + +import ( + "context" + "fmt" + "io" + "net" + "net/http" + "net/url" + "os" + "os/signal" + "path/filepath" + "sync" + "syscall" + "time" + + "github.com/containerd/console" + "github.com/containerd/fifo" + gwclient "github.com/moby/buildkit/frontend/gateway/client" + mobysignal "github.com/moby/sys/signal" + "github.com/sirupsen/logrus" + "golang.org/x/sync/errgroup" +) + +func AttachContainerIO(root string, setTtyRaw bool) error { + if root == "" { + return fmt.Errorf("root needs to be specified") + } + + type ioSet struct { + stdin io.WriteCloser + stdout io.ReadCloser + stderr io.ReadCloser + } + ioSetCh := make(chan ioSet) + errCh := make(chan error) + go func() { + stdin, stdout, stderr, err := openFifosClient(context.TODO(), root) + if err != nil { + errCh <- err + return + } + ioSetCh <- ioSet{stdin, stdout, stderr} + }() + var ( + stdin io.WriteCloser + stdout io.ReadCloser + stderr io.ReadCloser + ) + select { + case ioSet := <-ioSetCh: + stdin, stdout, stderr = ioSet.stdin, ioSet.stdout, ioSet.stderr + case err := <-errCh: + return err + case <-time.After(500 * time.Millisecond): + return fmt.Errorf("i/o timeout; check server is up and running") + } + defer func() { stdin.Close(); stdout.Close(); stderr.Close() }() + + sigToName := map[syscall.Signal]string{} + for name, value := range mobysignal.SignalMap { + sigToName[value] = name + } + ch := make(chan os.Signal, 1) + signals := []os.Signal{syscall.SIGWINCH, syscall.SIGINT, syscall.SIGTERM} + signal.Notify(ch, signals...) + go func() { + sockPath := filepath.Join(root, "signal.sock") + c := http.Client{ + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial("unix", sockPath) + }, + }, + } + defer signal.Stop(ch) + for ss := range ch { + sigName, ok := sigToName[ss.(syscall.Signal)] + if !ok { + continue + } + v := url.Values{} + v.Set("signal", sigName) + if _, err := c.PostForm("http://localhost", v); err != nil { + fmt.Fprintf(os.Stderr, "failed to send signal: %v\n", err) + } + } + }() + + if setTtyRaw { + con := console.Current() + if err := con.SetRaw(); err != nil { + return fmt.Errorf("failed to configure terminal: %v", err) + } + defer con.Reset() + } + + go io.Copy(stdin, os.Stdin) + eg, _ := errgroup.WithContext(context.TODO()) + eg.Go(func() error { _, err := io.Copy(os.Stdout, stdout); return err }) + eg.Go(func() error { _, err := io.Copy(os.Stderr, stderr); return err }) + if err := eg.Wait(); err != nil { + return err + } + fmt.Fprintf(os.Stderr, "exec finished\n") + return nil +} + +func serveContainerIO(ctx context.Context, root string) (io.ReadCloser, io.WriteCloser, io.WriteCloser, *signalForwarder, func(), error) { + stdin, stdout, stderr, err := openFifosServer(ctx, root) + if err != nil { + return nil, nil, nil, nil, nil, err + } + var srv http.Server + sf := new(signalForwarder) + srv.Handler = sf + go func() { + ln, err := net.Listen("unix", filepath.Join(root, "signal.sock")) + if err != nil { + logrus.WithError(err).Warnf("failed to listen contaienr IO") + return + } + if err := srv.Serve(ln); err != nil { + logrus.WithError(err).Warnf("failed to serve contaienr IO") + } + }() + return stdin, stdout, stderr, sf, func() { + ctx, cancel := context.WithTimeout(ctx, 3*time.Second) + defer cancel() + if err := srv.Shutdown(ctx); err != nil { + logrus.WithError(err).Warnf("failed to gracefully shutdown container IO") + } + srv.Close() + stdin.Close() + stdout.Close() + stderr.Close() + }, nil +} + +func openFifosClient(ctx context.Context, fifosDir string) (stdin io.WriteCloser, stdout, stderr io.ReadCloser, retErr error) { + if stdin, retErr = fifo.OpenFifo(ctx, filepath.Join(fifosDir, "stdin"), syscall.O_WRONLY, 0700); retErr != nil { + return nil, nil, nil, fmt.Errorf("failed to open stdin fifo: %w", retErr) + } + defer func() { + if retErr != nil && stdin != nil { + stdin.Close() + } + }() + if stdout, retErr = fifo.OpenFifo(ctx, filepath.Join(fifosDir, "stdout"), syscall.O_RDONLY, 0700); retErr != nil { + return nil, nil, nil, fmt.Errorf("failed to open stdout fifo: %w", retErr) + } + defer func() { + if retErr != nil && stdout != nil { + stdout.Close() + } + }() + if stderr, retErr = fifo.OpenFifo(ctx, filepath.Join(fifosDir, "stderr"), syscall.O_RDONLY, 0700); retErr != nil { + return nil, nil, nil, fmt.Errorf("failed to open stderr fifo: %w", retErr) + } + defer func() { + if retErr != nil && stderr != nil { + stderr.Close() + } + }() + return stdin, stdout, stderr, nil +} + +func openFifosServer(ctx context.Context, fifosDir string) (stdin io.ReadCloser, stdout, stderr io.WriteCloser, retErr error) { + if stdin, retErr = fifo.OpenFifo(ctx, filepath.Join(fifosDir, "stdin"), syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); retErr != nil { + return nil, nil, nil, fmt.Errorf("failed to open stdin fifo: %w", retErr) + } + defer func() { + if retErr != nil && stdin != nil { + stdin.Close() + } + }() + if stdout, retErr = fifo.OpenFifo(ctx, filepath.Join(fifosDir, "stdout"), syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); retErr != nil { + return nil, nil, nil, fmt.Errorf("failed to open stdout fifo: %w", retErr) + } + defer func() { + if retErr != nil && stdout != nil { + stdout.Close() + } + }() + if stderr, retErr = fifo.OpenFifo(ctx, filepath.Join(fifosDir, "stderr"), syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); retErr != nil { + return nil, nil, nil, fmt.Errorf("failed to open stderr fifo: %w", retErr) + } + defer func() { + if retErr != nil && stderr != nil { + stderr.Close() + } + }() + return stdin, stdout, stderr, nil +} + +type signalForwarder struct { + proc gwclient.ContainerProcess + procMu sync.Mutex +} + +func (s *signalForwarder) ServeHTTP(w http.ResponseWriter, r *http.Request) { + sig := r.PostFormValue("signal") + syscallSignal, ok := mobysignal.SignalMap[sig] + if !ok { + logrus.Warnf("unknown signal: %q", sig) + return + } + s.procMu.Lock() + proc := s.proc + s.procMu.Unlock() + if proc != nil { + proc.Signal(context.TODO(), syscallSignal) + } else { + logrus.Debugf("got signal %q but no proc is available", sig) + } +} + +func (s *signalForwarder) watchSignal(ctx context.Context, proc gwclient.ContainerProcess, con console.Console) { + go func() { + s.procMu.Lock() + s.proc = proc + s.procMu.Unlock() + + <-ctx.Done() + + s.procMu.Lock() + s.proc = nil + s.procMu.Unlock() + }() +} diff --git a/pkg/testutil/dap.go b/pkg/testutil/dap.go new file mode 100644 index 00000000..5046bc30 --- /dev/null +++ b/pkg/testutil/dap.go @@ -0,0 +1,98 @@ +package testutil + +import ( + "net" + "os" + "os/exec" + "sync" + "testing" + "time" + + "github.com/google/go-dap" +) + +type dapOptions struct { + env []string + globalOpts []string +} + +func WithDAPServerEnv(env ...string) DAPServerOption { + return func(o *dapOptions) { + o.env = env + } +} + +func WithDAPServerGlobalOptions(globalOpts ...string) DAPServerOption { + return func(o *dapOptions) { + o.globalOpts = append(o.globalOpts, globalOpts...) + } +} + +type DAPServerOption func(*dapOptions) + +func NewDAPServer(t *testing.T, opts ...DAPServerOption) *DAPServer { + gotOpts := dapOptions{} + for _, o := range opts { + o(&gotOpts) + } + + buildgCmd := getBuildgBinary(t) + args := []string{"--debug", "dap", "serve"} + cmd := exec.Command(buildgCmd, append(gotOpts.globalOpts, args...)...) + cmd.Env = append(os.Environ(), gotOpts.env...) + c1, c2 := net.Pipe() + cmd.Stdin = c1 + cmd.Stdout = c1 + cmd.Stderr = os.Stdout + if err := cmd.Start(); err != nil { + t.Fatal(err) + } + return &DAPServer{cmd: cmd, conn: c2} +} + +type DAPServer struct { + cmd *exec.Cmd + conn net.Conn + closeOnce sync.Once +} + +func (s *DAPServer) Conn() net.Conn { + return s.conn +} + +func (s *DAPServer) Wait() (retErr error) { + _, err := s.cmd.Process.Wait() + s.conn.Close() + return err +} + +func (s *DAPServer) Close() (retErr error) { + s.closeOnce.Do(func() { + if s.cmd.ProcessState != nil && s.cmd.ProcessState.Exited() { + return + } + dap.WriteProtocolMessage(s.conn, &dap.DisconnectRequest{ + Request: dap.Request{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: 99999, + Type: "request", + }, + Command: "disconnect", + }, + }) + s.conn.Close() + doneCh := make(chan struct{}) + go func() { + s.cmd.Wait() + doneCh <- struct{}{} + }() + select { + case <-doneCh: + return + case <-time.After(3 * time.Second): + s.cmd.Process.Kill() + } + retErr = s.cmd.Wait() + }) + return retErr +} diff --git a/pkg/testutil/debugshell.go b/pkg/testutil/debugshell.go index 17ee3b05..074c1591 100644 --- a/pkg/testutil/debugshell.go +++ b/pkg/testutil/debugshell.go @@ -16,6 +16,7 @@ import ( ) const buildgPathEnv = "TEST_BUILDG_PATH" +const BuildgTestTmpDirEnv = "TEST_BUILDG_TMP_DIR" func getBuildgBinary(t *testing.T) string { buildgCmd := "buildg"