mirror of https://github.com/docker/buildx.git
181 lines
4.3 KiB
Go
181 lines
4.3 KiB
Go
package commands
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/docker/buildx/build"
|
|
"github.com/docker/buildx/monitor"
|
|
"github.com/docker/buildx/util/cobrautil"
|
|
"github.com/docker/buildx/util/ioset"
|
|
"github.com/docker/buildx/util/progress"
|
|
"github.com/docker/cli/cli/command"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
"github.com/tonistiigi/go-csvvalue"
|
|
)
|
|
|
|
type debugOptions struct {
|
|
// InvokeFlag is a flag to configure the launched debugger and the commaned executed on the debugger.
|
|
InvokeFlag string
|
|
|
|
// OnFlag is a flag to configure the timing of launching the debugger.
|
|
OnFlag string
|
|
}
|
|
|
|
// debuggerOptions will start a debuggerOptions instance.
|
|
type debuggerOptions interface {
|
|
New(in ioset.In) (debuggerInstance, error)
|
|
}
|
|
|
|
// debuggerInstance is an instance of a Debugger that has been started.
|
|
type debuggerInstance interface {
|
|
Start(printer *progress.Printer, opts *BuildOptions) error
|
|
Handler() build.Handler
|
|
Stop() error
|
|
Out() io.Writer
|
|
}
|
|
|
|
func debugCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|
var options debugOptions
|
|
cmd := &cobra.Command{
|
|
Use: "debug",
|
|
Short: "Start debugger",
|
|
|
|
DisableFlagsInUseLine: true,
|
|
}
|
|
cobrautil.MarkCommandExperimental(cmd)
|
|
|
|
flags := cmd.Flags()
|
|
flags.StringVar(&options.InvokeFlag, "invoke", "", "Launch a monitor with executing specified command")
|
|
flags.StringVar(&options.OnFlag, "on", "error", "When to launch the monitor ([always, error])")
|
|
|
|
cobrautil.MarkFlagsExperimental(flags, "invoke", "on")
|
|
|
|
cmd.AddCommand(buildCmd(dockerCli, rootOpts, &options))
|
|
return cmd
|
|
}
|
|
|
|
func (d *debugOptions) New(in ioset.In) (debuggerInstance, error) {
|
|
cfg, err := parseInvokeConfig(d.InvokeFlag, d.OnFlag)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &monitorDebuggerInstance{
|
|
cfg: cfg,
|
|
in: in.Stdin,
|
|
}, nil
|
|
}
|
|
|
|
type monitorDebuggerInstance struct {
|
|
cfg *build.InvokeConfig
|
|
in io.ReadCloser
|
|
m *monitor.Monitor
|
|
}
|
|
|
|
func (d *monitorDebuggerInstance) Start(printer *progress.Printer, opts *BuildOptions) error {
|
|
d.m = monitor.New(d.cfg, d.in, os.Stdout, os.Stderr, printer)
|
|
return nil
|
|
}
|
|
|
|
func (d *monitorDebuggerInstance) Handler() build.Handler {
|
|
return d.m.Handler()
|
|
}
|
|
|
|
func (d *monitorDebuggerInstance) Stop() error {
|
|
return d.m.Close()
|
|
}
|
|
|
|
func (d *monitorDebuggerInstance) Out() io.Writer {
|
|
return os.Stderr
|
|
}
|
|
|
|
func parseInvokeConfig(invoke, on string) (*build.InvokeConfig, error) {
|
|
cfg := &build.InvokeConfig{}
|
|
switch on {
|
|
case "always":
|
|
cfg.SuspendOn = build.SuspendAlways
|
|
case "error":
|
|
cfg.SuspendOn = build.SuspendError
|
|
default:
|
|
if invoke != "" {
|
|
cfg.SuspendOn = build.SuspendAlways
|
|
}
|
|
}
|
|
|
|
cfg.Tty = true
|
|
cfg.NoCmd = true
|
|
switch invoke {
|
|
case "default", "":
|
|
return cfg, nil
|
|
case "on-error":
|
|
// NOTE: we overwrite the command to run because the original one should fail on the failed step.
|
|
// TODO: make this configurable via flags or restorable from LLB.
|
|
// Discussion: https://github.com/docker/buildx/pull/1640#discussion_r1113295900
|
|
cfg.Cmd = []string{"/bin/sh"}
|
|
cfg.NoCmd = false
|
|
return cfg, nil
|
|
}
|
|
|
|
csvParser := csvvalue.NewParser()
|
|
csvParser.LazyQuotes = true
|
|
fields, err := csvParser.Fields(invoke, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(fields) == 1 && !strings.Contains(fields[0], "=") {
|
|
cfg.Cmd = []string{fields[0]}
|
|
cfg.NoCmd = false
|
|
return cfg, nil
|
|
}
|
|
cfg.NoUser = true
|
|
cfg.NoCwd = true
|
|
for _, field := range fields {
|
|
parts := strings.SplitN(field, "=", 2)
|
|
if len(parts) != 2 {
|
|
return nil, errors.Errorf("invalid value %s", field)
|
|
}
|
|
key := strings.ToLower(parts[0])
|
|
value := parts[1]
|
|
switch key {
|
|
case "args":
|
|
cfg.Cmd = append(cfg.Cmd, maybeJSONArray(value)...)
|
|
cfg.NoCmd = false
|
|
case "entrypoint":
|
|
cfg.Entrypoint = append(cfg.Entrypoint, maybeJSONArray(value)...)
|
|
if cfg.Cmd == nil {
|
|
cfg.Cmd = []string{}
|
|
cfg.NoCmd = false
|
|
}
|
|
case "env":
|
|
cfg.Env = append(cfg.Env, maybeJSONArray(value)...)
|
|
case "user":
|
|
cfg.User = value
|
|
cfg.NoUser = false
|
|
case "cwd":
|
|
cfg.Cwd = value
|
|
cfg.NoCwd = false
|
|
case "tty":
|
|
cfg.Tty, err = strconv.ParseBool(value)
|
|
if err != nil {
|
|
return nil, errors.Errorf("failed to parse tty: %v", err)
|
|
}
|
|
default:
|
|
return nil, errors.Errorf("unknown key %q", key)
|
|
}
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
func maybeJSONArray(v string) []string {
|
|
var list []string
|
|
if err := json.Unmarshal([]byte(v), &list); err == nil {
|
|
return list
|
|
}
|
|
return []string{v}
|
|
}
|