diff --git a/builder/builder.go b/builder/builder.go index d82af210..2bb83a44 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -344,6 +344,7 @@ type CreateOpts struct { Use bool Endpoint string Append bool + Timeout time.Duration } func Create(ctx context.Context, txn *store.Txn, dockerCli command.Cli, opts CreateOpts) (*Builder, error) { @@ -524,7 +525,7 @@ func Create(ctx context.Context, txn *store.Txn, dockerCli command.Cli, opts Cre } cancelCtx, cancel := context.WithCancelCause(ctx) - timeoutCtx, _ := context.WithTimeoutCause(cancelCtx, 20*time.Second, errors.WithStack(context.DeadlineExceeded)) //nolint:govet,lostcancel // no need to manually cancel this context as we already rely on parent + timeoutCtx, _ := context.WithTimeoutCause(cancelCtx, opts.Timeout, errors.WithStack(context.DeadlineExceeded)) //nolint:govet,lostcancel // no need to manually cancel this context as we already rely on parent defer func() { cancel(errors.WithStack(context.Canceled)) }() nodes, err := b.LoadNodes(timeoutCtx, WithData()) diff --git a/commands/create.go b/commands/create.go index 5cc90e72..164ee4a9 100644 --- a/commands/create.go +++ b/commands/create.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "time" "github.com/docker/buildx/builder" "github.com/docker/buildx/driver" @@ -27,6 +28,7 @@ type createOptions struct { buildkitdFlags string buildkitdConfigFile string bootstrap bool + timeout time.Duration // upgrade bool // perform upgrade of the driver } @@ -61,6 +63,7 @@ func runCreate(ctx context.Context, dockerCli command.Cli, in createOptions, arg Use: in.use, Endpoint: ep, Append: in.actionAppend, + Timeout: in.timeout, }) if err != nil { return err @@ -80,7 +83,7 @@ func runCreate(ctx context.Context, dockerCli command.Cli, in createOptions, arg return nil } -func createCmd(dockerCli command.Cli) *cobra.Command { +func createCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command { var options createOptions var drivers bytes.Buffer @@ -96,6 +99,7 @@ func createCmd(dockerCli command.Cli) *cobra.Command { Short: "Create a new builder instance", Args: cli.RequiresMaxArgs(1), RunE: func(cmd *cobra.Command, args []string) error { + options.timeout = rootOpts.timeout return runCreate(cmd.Context(), dockerCli, options, args) }, ValidArgsFunction: completion.Disable, diff --git a/commands/inspect.go b/commands/inspect.go index eeae99ea..e9cb5020 100644 --- a/commands/inspect.go +++ b/commands/inspect.go @@ -24,6 +24,7 @@ import ( type inspectOptions struct { bootstrap bool builder string + timeout time.Duration } func runInspect(ctx context.Context, dockerCli command.Cli, in inspectOptions) error { @@ -36,7 +37,7 @@ func runInspect(ctx context.Context, dockerCli command.Cli, in inspectOptions) e } timeoutCtx, cancel := context.WithCancelCause(ctx) - timeoutCtx, _ = context.WithTimeoutCause(timeoutCtx, 20*time.Second, errors.WithStack(context.DeadlineExceeded)) //nolint:govet,lostcancel // no need to manually cancel this context as we already rely on parent + timeoutCtx, _ = context.WithTimeoutCause(timeoutCtx, in.timeout, errors.WithStack(context.DeadlineExceeded)) //nolint:govet,lostcancel // no need to manually cancel this context as we already rely on parent defer func() { cancel(errors.WithStack(context.Canceled)) }() nodes, err := b.LoadNodes(timeoutCtx, builder.WithData()) @@ -180,6 +181,7 @@ func inspectCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command { if len(args) > 0 { options.builder = args[0] } + options.timeout = rootOpts.timeout return runInspect(cmd.Context(), dockerCli, options) }, ValidArgsFunction: completion.BuilderNames(dockerCli), diff --git a/commands/ls.go b/commands/ls.go index 8c247679..fe78c857 100644 --- a/commands/ls.go +++ b/commands/ls.go @@ -40,6 +40,7 @@ const ( type lsOptions struct { format string noTrunc bool + timeout time.Duration } func runLs(ctx context.Context, dockerCli command.Cli, in lsOptions) error { @@ -60,7 +61,7 @@ func runLs(ctx context.Context, dockerCli command.Cli, in lsOptions) error { } timeoutCtx, cancel := context.WithCancelCause(ctx) - timeoutCtx, _ = context.WithTimeoutCause(timeoutCtx, 20*time.Second, errors.WithStack(context.DeadlineExceeded)) //nolint:govet,lostcancel // no need to manually cancel this context as we already rely on parent + timeoutCtx, _ = context.WithTimeoutCause(timeoutCtx, in.timeout, errors.WithStack(context.DeadlineExceeded)) //nolint:govet,lostcancel // no need to manually cancel this context as we already rely on parent defer func() { cancel(errors.WithStack(context.Canceled)) }() eg, _ := errgroup.WithContext(timeoutCtx) @@ -97,7 +98,7 @@ func runLs(ctx context.Context, dockerCli command.Cli, in lsOptions) error { return nil } -func lsCmd(dockerCli command.Cli) *cobra.Command { +func lsCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command { var options lsOptions cmd := &cobra.Command{ @@ -105,6 +106,7 @@ func lsCmd(dockerCli command.Cli) *cobra.Command { Short: "List builder instances", Args: cli.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { + options.timeout = rootOpts.timeout return runLs(cmd.Context(), dockerCli, options) }, ValidArgsFunction: completion.Disable, diff --git a/commands/rm.go b/commands/rm.go index cabbe9aa..776efe26 100644 --- a/commands/rm.go +++ b/commands/rm.go @@ -21,6 +21,7 @@ type rmOptions struct { keepDaemon bool allInactive bool force bool + timeout time.Duration } const ( @@ -109,6 +110,7 @@ func rmCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command { } options.builders = args } + options.timeout = rootOpts.timeout return runRm(cmd.Context(), dockerCli, options) }, ValidArgsFunction: completion.BuilderNames(dockerCli), @@ -151,7 +153,7 @@ func rmAllInactive(ctx context.Context, txn *store.Txn, dockerCli command.Cli, i } timeoutCtx, cancel := context.WithCancelCause(ctx) - timeoutCtx, _ = context.WithTimeoutCause(timeoutCtx, 20*time.Second, errors.WithStack(context.DeadlineExceeded)) //nolint:govet,lostcancel // no need to manually cancel this context as we already rely on parent + timeoutCtx, _ = context.WithTimeoutCause(timeoutCtx, in.timeout, errors.WithStack(context.DeadlineExceeded)) //nolint:govet,lostcancel // no need to manually cancel this context as we already rely on parent defer func() { cancel(errors.WithStack(context.Canceled)) }() eg, _ := errgroup.WithContext(timeoutCtx) diff --git a/commands/root.go b/commands/root.go index d42eb44b..14edc490 100644 --- a/commands/root.go +++ b/commands/root.go @@ -3,6 +3,7 @@ package commands import ( "fmt" "os" + "time" debugcmd "github.com/docker/buildx/commands/debug" historycmd "github.com/docker/buildx/commands/history" @@ -22,6 +23,8 @@ import ( "github.com/spf13/pflag" ) +const defaultTimeoutCli = 20 * time.Second + func NewRootCmd(name string, isPlugin bool, dockerCli *command.DockerCli) *cobra.Command { var opt rootOptions cmd := &cobra.Command{ @@ -96,6 +99,7 @@ func NewRootCmd(name string, isPlugin bool, dockerCli *command.DockerCli) *cobra type rootOptions struct { builder string debug bool + timeout time.Duration } func addCommands(cmd *cobra.Command, opts *rootOptions, dockerCli command.Cli) { @@ -104,10 +108,10 @@ func addCommands(cmd *cobra.Command, opts *rootOptions, dockerCli command.Cli) { cmd.AddCommand( buildCmd(dockerCli, opts, nil), bakeCmd(dockerCli, opts), - createCmd(dockerCli), + createCmd(dockerCli, opts), dialStdioCmd(dockerCli, opts), rmCmd(dockerCli, opts), - lsCmd(dockerCli), + lsCmd(dockerCli, opts), useCmd(dockerCli, opts), inspectCmd(dockerCli, opts), stopCmd(dockerCli, opts), @@ -134,4 +138,14 @@ func addCommands(cmd *cobra.Command, opts *rootOptions, dockerCli command.Cli) { func rootFlags(options *rootOptions, flags *pflag.FlagSet) { flags.StringVar(&options.builder, "builder", os.Getenv("BUILDX_BUILDER"), "Override the configured builder instance") flags.BoolVarP(&options.debug, "debug", "D", debug.IsEnabled(), "Enable debug logging") + + var timeoutDuration = defaultTimeoutCli + if value, ok := os.LookupEnv("BUILDX_TIMEOUT"); ok { + var err error + timeoutDuration, err = time.ParseDuration(value) + if err != nil { + timeoutDuration = defaultTimeoutCli + } + } + flags.DurationVar(&options.timeout, "timeout", timeoutDuration, "Override the default global timeout (20 seconds)") } diff --git a/controller/control/controller.go b/controller/control/controller.go index cc501e9b..43d7bd77 100644 --- a/controller/control/controller.go +++ b/controller/control/controller.go @@ -3,6 +3,7 @@ package control import ( "context" "io" + "time" "github.com/docker/buildx/build" cbuild "github.com/docker/buildx/controller/build" @@ -24,3 +25,7 @@ type BuildxController interface { DisconnectProcess(ctx context.Context, pid string) error Inspect(ctx context.Context) *cbuild.Options } + +type ControlOptions struct { + Timeout time.Duration +} diff --git a/driver/docker-container/driver.go b/driver/docker-container/driver.go index cfcdf67c..1f272e78 100644 --- a/driver/docker-container/driver.go +++ b/driver/docker-container/driver.go @@ -217,7 +217,7 @@ func (d *Driver) wait(ctx context.Context, l progress.SubLogger) error { select { case <-ctx.Done(): return context.Cause(ctx) - case <-time.After(time.Duration(try*120) * time.Millisecond): + case <-time.After(d.Timeout / time.Microsecond): try++ continue } diff --git a/driver/kubernetes/driver.go b/driver/kubernetes/driver.go index 3bfe568a..ba48fe13 100644 --- a/driver/kubernetes/driver.go +++ b/driver/kubernetes/driver.go @@ -52,7 +52,6 @@ type Driver struct { configMapClient clientcorev1.ConfigMapInterface podChooser podchooser.PodChooser defaultLoad bool - timeout time.Duration } func (d *Driver) IsMobyDriver() bool { @@ -91,7 +90,7 @@ func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error { } } return sub.Wrap( - fmt.Sprintf("waiting for %d pods to be ready, timeout: %s", d.minReplicas, units.HumanDuration(d.timeout)), + fmt.Sprintf("waiting for %d pods to be ready, timeout: %s", d.minReplicas, units.HumanDuration(d.Timeout)), func() error { return d.wait(ctx) }) @@ -105,8 +104,8 @@ func (d *Driver) wait(ctx context.Context) error { depl *appsv1.Deployment ) - timeoutChan := time.After(d.timeout) - ticker := time.NewTicker(100 * time.Millisecond) + timeoutChan := time.After(d.Timeout) + ticker := time.NewTicker(d.Timeout / time.Microsecond) defer ticker.Stop() for { diff --git a/driver/kubernetes/factory.go b/driver/kubernetes/factory.go index b25abb89..8a0b9f01 100644 --- a/driver/kubernetes/factory.go +++ b/driver/kubernetes/factory.go @@ -23,7 +23,6 @@ import ( const ( prioritySupported = 40 priorityUnsupported = 80 - defaultTimeout = 120 * time.Second ) type ClientConfig interface { @@ -134,7 +133,7 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver } d.defaultLoad = defaultLoad - d.timeout = timeout + d.Timeout = timeout d.deployment, d.configMaps, err = manifest.NewDeployment(deploymentOpt) if err != nil { @@ -175,7 +174,7 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg } defaultLoad := false - timeout := defaultTimeout + timeout := cfg.Timeout deploymentOpt.Qemu.Image = bkimage.QemuImage diff --git a/driver/manager.go b/driver/manager.go index 400a0cdd..8449819a 100644 --- a/driver/manager.go +++ b/driver/manager.go @@ -4,6 +4,7 @@ import ( "context" "sort" "sync" + "time" "github.com/docker/cli/cli/context/store" dockerclient "github.com/docker/docker/client" @@ -38,6 +39,7 @@ type InitConfig struct { Platforms []specs.Platform ContextPathHash string DialMeta map[string][]string + Timeout time.Duration } var drivers map[string]Factory diff --git a/driver/remote/driver.go b/driver/remote/driver.go index 22145dd6..80aa34dc 100644 --- a/driver/remote/driver.go +++ b/driver/remote/driver.go @@ -8,7 +8,6 @@ import ( "os" "strings" "sync" - "time" "github.com/docker/buildx/driver" util "github.com/docker/buildx/driver/remote/util" @@ -48,7 +47,7 @@ func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error { } return progress.Wrap("[internal] waiting for connection", l, func(_ progress.SubLogger) error { cancelCtx, cancel := context.WithCancelCause(ctx) - ctx, _ := context.WithTimeoutCause(cancelCtx, 20*time.Second, errors.WithStack(context.DeadlineExceeded)) //nolint:govet,lostcancel // no need to manually cancel this context as we already rely on parent + ctx, _ := context.WithTimeoutCause(cancelCtx, d.Timeout, errors.WithStack(context.DeadlineExceeded)) //nolint:govet,lostcancel // no need to manually cancel this context as we already rely on parent defer func() { cancel(errors.WithStack(context.Canceled)) }() return c.Wait(ctx) }) diff --git a/driver/remote/factory.go b/driver/remote/factory.go index c07c08f2..be3c31c2 100644 --- a/driver/remote/factory.go +++ b/driver/remote/factory.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strconv" "strings" + "time" "github.com/docker/buildx/driver" util "github.com/docker/buildx/driver/remote/util" @@ -87,6 +88,12 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver return nil, err } d.defaultLoad = parsed + case "timeout": + parsed, err := time.ParseDuration(v) + if err != nil { + return nil, err + } + d.Timeout = parsed default: return nil, errors.Errorf("invalid driver option %s for remote driver", k) }