diff --git a/cmd/deploy.go b/cmd/deploy.go index f4e3286..42c6dd8 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -234,16 +234,21 @@ func runDeploy(cmd *cobra.Command, args []string) error { return err } - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute) - defer cancel() + // Cluster-config retrieval happens before the full config (and thus the configured deploy + // wait timeouts) is assembled, so give it its own short bounded context. + setupCtx, setupCancel := context.WithTimeout(context.Background(), 10*time.Minute) + defer setupCancel() - clusterConfig := retrieveClusterConfigForComponents(ctx, log, components) + clusterConfig := retrieveClusterConfigForComponents(setupCtx, log, components) deploySettings, err := assembleConfigForCommand(clusterConfig, deploySettingsFromArgs, skipUserConfig) if err != nil { return err } + ctx, cancel := context.WithTimeout(context.Background(), computeDeployContextTimeout(components, deploySettings)) + defer cancel() + if deploySettings.Roxie.Version != "" { log.Dimf("Using main image tag %s", deploySettings.Roxie.Version) } else { @@ -353,6 +358,34 @@ func runDeploy(cmd *cobra.Command, args []string) error { return nil } +// computeDeployContextTimeout returns how long the overall deploy context should live. It must +// outlive the per-component readiness waits, which run sequentially (Central, then SecuredCluster) +// and derive their own contexts from this one (see waitForComponentReady); since +// context.WithTimeout takes the earlier deadline, a fixed parent deadline would silently clamp the +// configured --central-wait / --secured-cluster-wait values. We therefore sum the DeployTimeouts of +// the components being deployed and add a margin for the surrounding setup/teardown steps that share +// the context. DeployTimeout is defaulted (20m per component) by assembleConfigForCommand for a +// fresh deploy; the fallback below keeps the context bounded if nothing contributes a positive value. +func computeDeployContextTimeout(components component.Component, cfg deployer.Config) time.Duration { + const margin = 10 * time.Minute + + var budget time.Duration + if components.IncludesCentral() { + budget += cfg.Central.DeployTimeout + } + if components.IncludesSensor() { + budget += cfg.SecuredCluster.DeployTimeout + } + if budget <= 0 { + // No Central/SecuredCluster readiness wait contributes here (e.g. an operator-only deploy, or + // a base config that left the timeouts unset); fall back to a sane default so the context is + // still bounded. + budget = deployer.DefaultCentralWaitTimeout + } + + return budget + margin +} + func retrieveClusterConfigForComponents( ctx context.Context, log *logger.Logger, diff --git a/cmd/deploy_test.go b/cmd/deploy_test.go index be7976b..f2f1604 100644 --- a/cmd/deploy_test.go +++ b/cmd/deploy_test.go @@ -8,6 +8,7 @@ import ( "time" "dario.cat/mergo" + "github.com/stackrox/roxie/internal/component" "github.com/stackrox/roxie/internal/deployer" "github.com/stackrox/roxie/internal/imagetag" "github.com/stackrox/roxie/internal/logger" @@ -427,3 +428,38 @@ func TestApplyUserDefaults(t *testing.T) { assert.Error(t, tryApplyUserDefaults(log, &cfg)) }) } + +func TestComputeDeployContextTimeout(t *testing.T) { + const margin = 10 * time.Minute + + cfg := deployer.Config{} + cfg.Central.DeployTimeout = 25 * time.Minute + cfg.SecuredCluster.DeployTimeout = 40 * time.Minute + + tests := map[string]struct { + components component.Component + want time.Duration + }{ + "central and secured cluster are summed": { + components: component.Both, + want: 25*time.Minute + 40*time.Minute + margin, + }, + "central only": { + components: component.Central, + want: 25*time.Minute + margin, + }, + "secured cluster only": { + components: component.SecuredCluster, + want: 40*time.Minute + margin, + }, + "neither central nor sensor falls back to default": { + components: component.Operator, + want: deployer.DefaultCentralWaitTimeout + margin, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tc.want, computeDeployContextTimeout(tc.components, cfg)) + }) + } +}