diff --git a/pkg/libkubectl/apply_dynamic.go b/pkg/libkubectl/apply_dynamic.go index a9c311d17b..0d0cdd912f 100644 --- a/pkg/libkubectl/apply_dynamic.go +++ b/pkg/libkubectl/apply_dynamic.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "strings" + "time" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -28,6 +29,12 @@ func (c *Client) ApplyDynamic(ctx context.Context, manifests []string) (string, return "", fmt.Errorf("failed to create REST config: %w", err) } + // Propagate context deadline to the HTTP client so discovery calls (which + // don't accept a context) still honour the caller's timeout. + if deadline, ok := ctx.Deadline(); ok { + restConfig.Timeout = time.Until(deadline) + } + // Create dynamic client dynamicClient, err := dynamic.NewForConfig(restConfig) if err != nil { diff --git a/pkg/libstack/swarm/swarm.go b/pkg/libstack/swarm/swarm.go index 617c7e92f3..b49b59b666 100644 --- a/pkg/libstack/swarm/swarm.go +++ b/pkg/libstack/swarm/swarm.go @@ -75,11 +75,15 @@ func NewSwarmDeployer() *SwarmDeployer { return &SwarmDeployer{} } // Deploy creates or updates a Docker Swarm stack from the given compose files. func (d *SwarmDeployer) Deploy(ctx context.Context, filePaths []string, options DeployOptions) error { + // WithCli replaces the context with Background internally, so we capture the + // caller's context here to preserve cancellation. + callerCtx := ctx + return libstack.WithCli( ctx, libstack.DockerCliOptions{Host: options.Host, Registries: options.Registries}, - func(ctx context.Context, dockerCLI *command.DockerCli) error { - return deployStack(ctx, dockerCLI, filePaths, options) + func(_ context.Context, dockerCLI *command.DockerCli) error { + return deployStack(callerCtx, dockerCLI, filePaths, options) }) } @@ -91,28 +95,32 @@ func (d *SwarmDeployer) Validate(_ context.Context, filePaths []string, options // Remove deletes all resources belonging to a Swarm stack and waits for tasks to terminate. func (d *SwarmDeployer) Remove(ctx context.Context, projectName string, options RemoveOptions) error { + // WithCli replaces the context with Background internally, so we capture the + // caller's context here to preserve cancellation. + callerCtx := ctx + return libstack.WithCli( ctx, libstack.DockerCliOptions{Host: options.Host, Registries: options.Registries}, - func(ctx context.Context, dockerCLI *command.DockerCli) error { + func(_ context.Context, dockerCLI *command.DockerCli) error { apiClient := dockerCLI.Client() - services, err := getStackServices(ctx, apiClient, projectName) + services, err := getStackServices(callerCtx, apiClient, projectName) if err != nil { return err } - secrets, err := getStackSecrets(ctx, apiClient, projectName) + secrets, err := getStackSecrets(callerCtx, apiClient, projectName) if err != nil { return err } - configs, err := getStackConfigs(ctx, apiClient, projectName) + configs, err := getStackConfigs(callerCtx, apiClient, projectName) if err != nil { return err } - networks, err := getStackNetworks(ctx, apiClient, projectName) + networks, err := getStackNetworks(callerCtx, apiClient, projectName) if err != nil { return err } @@ -124,19 +132,19 @@ func (d *SwarmDeployer) Remove(ctx context.Context, projectName string, options var errs []error - if err := removeServices(ctx, apiClient, services); err != nil { + if err := removeServices(callerCtx, apiClient, services); err != nil { errs = append(errs, err) } - if err := removeSecrets(ctx, apiClient, secrets); err != nil { + if err := removeSecrets(callerCtx, apiClient, secrets); err != nil { errs = append(errs, err) } - if err := removeConfigs(ctx, apiClient, configs); err != nil { + if err := removeConfigs(callerCtx, apiClient, configs); err != nil { errs = append(errs, err) } - if err := removeNetworks(ctx, apiClient, networks); err != nil { + if err := removeNetworks(callerCtx, apiClient, networks); err != nil { errs = append(errs, err) } @@ -146,7 +154,7 @@ func (d *SwarmDeployer) Remove(ctx context.Context, projectName string, options // Wait for all tasks to reach a terminal state before returning, mirroring // the behaviour of `docker stack rm --detach=false`. - return waitOnTasks(ctx, apiClient, projectName) + return waitOnTasks(callerCtx, apiClient, projectName) }) }