diff --git a/CHANGELOG.md b/CHANGELOG.md index b8b6100..63821bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Unreleased +### Changed + +- Respond to cancel for exec builds. () + ## 0.0.11 (2025-04-11) ### Changed diff --git a/provider/internal/cli.go b/provider/internal/cli.go index bb5a280..ab33922 100644 --- a/provider/internal/cli.go +++ b/provider/internal/cli.go @@ -25,6 +25,7 @@ import ( "fmt" "io" "os" + "os/exec" "path/filepath" "strings" @@ -203,7 +204,7 @@ func (c *cli) Close() error { // execBuild performs a build by os.Exec'ing the docker-buildx binary. // Credentials are communicated to docker-buildx via a temporary directory. // Secrets are communicated via dynamic environment variables. -func (c *cli) execBuild(b Build) (*client.SolveResponse, error) { +func (c *cli) execBuild(ctx context.Context, b Build) (*client.SolveResponse, error) { // Setup a temporary directory for auth, and clean it up when we're done. tmp, err := os.MkdirTemp("", "pulumi-docker-") if err != nil { @@ -213,7 +214,7 @@ func (c *cli) execBuild(b Build) (*client.SolveResponse, error) { opts := b.BuildOptions() - builder, err := c.host.builderFor(b) + builder, err := c.host.builderFor(ctx, b) if err != nil { return nil, err } @@ -339,7 +340,7 @@ func (c *cli) execBuild(b Build) (*client.SolveResponse, error) { } // Invoke docker-buildx. - err = c.exec(args, env) + err = c.exec(ctx, args, env) if err != nil { return nil, err } @@ -378,7 +379,7 @@ func (c *cli) execBuild(b Build) (*client.SolveResponse, error) { // exec invokes a Docker plugin binary. The first argument should be the name // of the plugin's subcommand, e.g. "buildx". -func (c *cli) exec(args, extraEnv []string) error { +func (c *cli) exec(ctx context.Context, args, extraEnv []string) error { if len(args) == 0 { return errors.New("args must be non-empty") } @@ -395,16 +396,18 @@ func (c *cli) exec(args, extraEnv []string) error { defer contract.IgnoreClose(c.w) - cmd, err := manager.PluginRunCommand(c, name, root) + runCmd, err := manager.PluginRunCommand(c, name, root) if err != nil { return err } - cmd.Args = append([]string{cmd.Args[0]}, args...) + // Create a new command that inherits from ctx. + cmd := exec.CommandContext(ctx, //nolint:gosec // We take the first argument and binary from runCmd. + runCmd.Path, append([]string{runCmd.Args[1]}, args...)..., + ) cmd.Stderr = c.Err() cmd.Stdout = c.Out() cmd.Stdin = c.In() - - cmd.Env = append(cmd.Env, extraEnv...) + cmd.Env = append(runCmd.Env, extraEnv...) //nolint:gocritic // We are intentionally assigning from runCmd to cmd return cmd.Run() } diff --git a/provider/internal/cli_test.go b/provider/internal/cli_test.go index 8169450..48f59fb 100644 --- a/provider/internal/cli_test.go +++ b/provider/internal/cli_test.go @@ -28,12 +28,12 @@ import ( func TestExec(t *testing.T) { t.Parallel() - h, err := newHost(context.Background(), nil) + h, err := newHost(t.Context(), nil) require.NoError(t, err) cli, err := wrap(h) require.NoError(t, err) - err = cli.exec([]string{"buildx", "version"}, nil) + err = cli.exec(t.Context(), []string{"buildx", "version"}, nil) assert.NoError(t, err) out, err := io.ReadAll(cli.r) diff --git a/provider/internal/client.go b/provider/internal/client.go index d09021c..6381704 100644 --- a/provider/internal/client.go +++ b/provider/internal/client.go @@ -106,10 +106,10 @@ func (c *cli) Build( defer contract.IgnoreClose(c) if build.ShouldExec() { - return c.execBuild(build) + return c.execBuild(ctx, build) } - b, err := c.host.builderFor(build) + b, err := c.host.builderFor(ctx, build) if err != nil { return nil, err } diff --git a/provider/internal/host.go b/provider/internal/host.go index 7b34f1c..3f852b0 100644 --- a/provider/internal/host.go +++ b/provider/internal/host.go @@ -68,7 +68,7 @@ func newHost(ctx context.Context, config *Config) (*host, error) { // // If the build doesn't specify a builder by name, we will iterate through all // available builders until we find one that we can connect to. -func (h *host) builderFor(build Build) (*cachedBuilder, error) { +func (h *host) builderFor(ctx context.Context, build Build) (*cachedBuilder, error) { h.mu.Lock() defer h.mu.Unlock() @@ -116,7 +116,7 @@ func (h *host) builderFor(build Build) (*cachedBuilder, error) { if bb.Err() != nil { continue } - nodes, err := bb.LoadNodes(context.Background()) + nodes, err := bb.LoadNodes(ctx) if err != nil { continue } @@ -125,7 +125,7 @@ func (h *host) builderFor(build Build) (*cachedBuilder, error) { if n.Driver == nil { continue nextbuilder } - if _, err := n.Driver.Dial(context.Background()); err != nil { + if _, err := n.Driver.Dial(ctx); err != nil { continue nextbuilder } // TODO: Confirm the builder supports the requested platforms. @@ -139,7 +139,7 @@ func (h *host) builderFor(build Build) (*cachedBuilder, error) { // If we STILL don't have a builder, create a docker-container instance. b, err = builder.Create( - context.Background(), + ctx, txn, h.cli, builder.CreateOpts{Driver: "docker-container"}, @@ -147,7 +147,7 @@ func (h *host) builderFor(build Build) (*cachedBuilder, error) { if err != nil { return nil, fmt.Errorf("creating builder: %w", err) } - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() if _, err := b.Boot(ctx); err != nil { return nil, fmt.Errorf("booting builder: %w", err) @@ -157,7 +157,7 @@ func (h *host) builderFor(build Build) (*cachedBuilder, error) { // Attempt to load nodes in order to determine the builder's driver. Ignore // errors for "exec" builds because it's possible to request builders with // drivers that are unknown to us. - nodes, err := b.LoadNodes(context.Background(), builder.WithData()) + nodes, err := b.LoadNodes(ctx, builder.WithData()) if err != nil && !build.ShouldExec() { return nil, fmt.Errorf("loading nodes: %w", err) }