diff --git a/cmd/deploy.go b/cmd/deploy.go index 61a167e..6b41d00 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -47,6 +47,8 @@ Examples: cmd.Flags().BoolVar(&singleNamespace, "single-namespace", false, "Deploy all components in a single namespace ('stackrox' by default)") cmd.Flags().StringVarP(&tag, "tag", "t", "", "Main image tag to use for deployment (takes precedence over MAIN_IMAGE_TAG environment variable)") cmd.Flags().StringSliceVar(&featureFlags, "features", []string{}, "Feature flag settings (e.g., +ROX_FOO,-ROX_BAR,ROX_BAZ=true)") + cmd.Flags().StringVar(¢ralWait, "central-wait", deployer.DefaultCentralWaitTimeout.String(), "Maximum wait time for Central to become ready (e.g., 5m, 10m)") + cmd.Flags().StringVar(&securedClusterWait, "secured-cluster-wait", deployer.DefaultSecuredClusterWaitTimeout.String(), "Maximum wait time for SecuredCluster to become ready (e.g., 5m, 10m)") return cmd } @@ -207,6 +209,29 @@ func runDeploy(cmd *cobra.Command, args []string) error { d.SetPauseReconciliation(pauseReconciliation) d.SetSingleNamespace(singleNamespace) + // Parse and set wait timeouts only if flags were provided + if cmd.Flags().Changed("central-wait") { + centralWaitDuration, err := time.ParseDuration(centralWait) + if err == nil && centralWaitDuration <= 0 { + err = errors.New("--central-wait duration must be positive") + } + if err != nil { + return fmt.Errorf("invalid --central-wait duration: %w", err) + } + d.SetCentralWaitTimeout(centralWaitDuration) + } + + if cmd.Flags().Changed("secured-cluster-wait") { + securedClusterWaitDuration, err := time.ParseDuration(securedClusterWait) + if err == nil && securedClusterWaitDuration <= 0 { + err = errors.New("--secured-cluster-wait duration must be positive") + } + if err != nil { + return fmt.Errorf("invalid --secured-cluster-wait duration: %w", err) + } + d.SetSecuredClusterWaitTimeout(securedClusterWaitDuration) + } + var mainImageTag string if tag != "" { log.Dimf("Using main image tag from --tag flag: %s", tag) diff --git a/cmd/main.go b/cmd/main.go index b4a59ab..d64f90f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -26,6 +26,8 @@ var ( singleNamespace bool tag string featureFlags []string + centralWait string + securedClusterWait string ) func main() { diff --git a/internal/deployer/deploy_via_helm.go b/internal/deployer/deploy_via_helm.go index a410cdf..2319427 100644 --- a/internal/deployer/deploy_via_helm.go +++ b/internal/deployer/deploy_via_helm.go @@ -82,7 +82,7 @@ func (d *Deployer) deployCentralHelm(ctx context.Context, resources, exposure st return fmt.Errorf("failed to install helm chart: %w", err) } - if err := d.waitForCentralReady(ctx, 600); err != nil { + if err := d.waitForCentralReady(ctx, d.centralWaitTimeout); err != nil { return fmt.Errorf("failed waiting for Central: %w", err) } @@ -168,7 +168,7 @@ func (d *Deployer) deploySecuredClusterHelm(ctx context.Context, resources strin return fmt.Errorf("failed to install helm chart: %w", err) } - if err := d.waitForSecuredClusterReady(ctx, 600); err != nil { + if err := d.waitForSecuredClusterReady(ctx, d.securedClusterWaitTimeout); err != nil { return fmt.Errorf("failed waiting for SecuredCluster: %w", err) } diff --git a/internal/deployer/deploy_via_operator.go b/internal/deployer/deploy_via_operator.go index 4fe2134..e9b3daf 100644 --- a/internal/deployer/deploy_via_operator.go +++ b/internal/deployer/deploy_via_operator.go @@ -128,7 +128,7 @@ func (d *Deployer) deployCentralOperator(ctx context.Context, resources, exposur return fmt.Errorf("failed to apply Central CR: %w", err) } - if err := d.waitForCentralReady(ctx, 600); err != nil { + if err := d.waitForCentralReady(ctx, d.centralWaitTimeout); err != nil { return fmt.Errorf("failed waiting for Central: %w", err) } @@ -441,8 +441,8 @@ func (d *Deployer) applyCentralCR(ctx context.Context, cr map[string]interface{} } // waitForCentralReady waits for Central to be ready -func (d *Deployer) waitForCentralReady(ctx context.Context, timeout int) error { - d.logger.Info("⏳ Waiting for Central to become ready...") +func (d *Deployer) waitForCentralReady(ctx context.Context, timeout time.Duration) error { + d.logger.Infof("⏳ Waiting for Central to become ready (timeout: %s)...", timeout) // Track seen deployments and their states to avoid duplicate messages seenDeployments := make(map[string]string) @@ -451,7 +451,7 @@ func (d *Deployer) waitForCentralReady(ctx context.Context, timeout int) error { start := time.Now() checkInterval := 3 * time.Second - for time.Since(start) < time.Duration(timeout)*time.Second { + for time.Since(start) < timeout { // Check for new deployments d.checkDeploymentProgress(ctx, seenDeployments) @@ -643,7 +643,7 @@ func (d *Deployer) deploySecuredClusterOperator(ctx context.Context, resources s return fmt.Errorf("failed to apply SecuredCluster CR: %w", err) } - if err := d.waitForSecuredClusterReady(ctx, 600); err != nil { + if err := d.waitForSecuredClusterReady(ctx, d.securedClusterWaitTimeout); err != nil { return fmt.Errorf("failed waiting for SecuredCluster: %w", err) } @@ -770,8 +770,8 @@ func (d *Deployer) applySecuredClusterCR(ctx context.Context, cr map[string]inte } // waitForSecuredClusterReady waits for SecuredCluster to be ready -func (d *Deployer) waitForSecuredClusterReady(ctx context.Context, timeout int) error { - d.logger.Info("⏳ Waiting for SecuredCluster to become ready...") +func (d *Deployer) waitForSecuredClusterReady(ctx context.Context, timeout time.Duration) error { + d.logger.Infof("⏳ Waiting for SecuredCluster to become ready (timeout: %s)...", timeout) // Track seen deployments and their states to avoid duplicate messages seenDeployments := make(map[string]string) @@ -780,7 +780,7 @@ func (d *Deployer) waitForSecuredClusterReady(ctx context.Context, timeout int) start := time.Now() checkInterval := 3 * time.Second - for time.Since(start) < time.Duration(timeout)*time.Second { + for time.Since(start) < timeout { d.checkDeploymentProgressInNamespace(ctx, d.sensorNamespace, seenDeployments) if d.earlyReadiness || d.verbose { diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index 6597cd2..91b8d18 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -30,6 +30,9 @@ var ( sensorNamespace = "acs-sensor" defaultExposure = "loadbalancer" + DefaultCentralWaitTimeout = 10 * time.Minute + DefaultSecuredClusterWaitTimeout = 10 * time.Minute + pauseReconcileAnnotationKey = "stackrox.io/pause-reconcile" // AdminUsername is the default admin username for StackRox Central @@ -98,37 +101,39 @@ var ( // Deployer is the base deployer for ACS type Deployer struct { - logger *logger.Logger - startTime time.Time - dockerAuth *dockerauth.DockerAuth - imageCache *imagecache.ImageCache - portForward *portforward.Manager - clusterDefaults *clusterdefaults.Manager - kubectl string - roxctlVersion string - centralNamespace string - sensorNamespace string - mainImageTag string - operatorTag string - centralEndpoint string - centralPassword string - roxCACertFile string - kubeContext string - portForwardEnabled bool - pauseReconciliation bool - exposure string - centralOverrides map[string]interface{} - securedClusterOverrides map[string]interface{} - featureFlagOverrides map[string]interface{} - envrcFile string - useHelm bool - useOLM bool - useKonflux bool - shouldDeployOperator bool - verbose bool - earlyReadiness bool - dockerCreds *dockerauth.Credentials - clusterResourceKinds map[string]struct{} + logger *logger.Logger + startTime time.Time + dockerAuth *dockerauth.DockerAuth + imageCache *imagecache.ImageCache + portForward *portforward.Manager + clusterDefaults *clusterdefaults.Manager + kubectl string + roxctlVersion string + centralNamespace string + sensorNamespace string + mainImageTag string + operatorTag string + centralEndpoint string + centralPassword string + roxCACertFile string + kubeContext string + portForwardEnabled bool + pauseReconciliation bool + exposure string + centralOverrides map[string]interface{} + securedClusterOverrides map[string]interface{} + featureFlagOverrides map[string]interface{} + envrcFile string + useHelm bool + useOLM bool + useKonflux bool + shouldDeployOperator bool + verbose bool + earlyReadiness bool + centralWaitTimeout time.Duration + securedClusterWaitTimeout time.Duration + dockerCreds *dockerauth.Credentials + clusterResourceKinds map[string]struct{} } type ResourceKindWithName struct { @@ -476,14 +481,16 @@ func New(log *logger.Logger) (*Deployer, error) { kubectl := getKubectl() d := &Deployer{ - logger: log, - startTime: time.Now(), - kubectl: kubectl, - roxctlVersion: roxctlVersion, - centralNamespace: centralNamespace, - sensorNamespace: sensorNamespace, - exposure: defaultExposure, - shouldDeployOperator: true, + logger: log, + startTime: time.Now(), + kubectl: kubectl, + roxctlVersion: roxctlVersion, + centralNamespace: centralNamespace, + sensorNamespace: sensorNamespace, + exposure: defaultExposure, + shouldDeployOperator: true, + centralWaitTimeout: DefaultCentralWaitTimeout, + securedClusterWaitTimeout: DefaultSecuredClusterWaitTimeout, } d.dockerAuth = dockerauth.New(log) @@ -923,6 +930,14 @@ func (d *Deployer) SetEarlyReadiness(enabled bool) { d.earlyReadiness = enabled } +func (d *Deployer) SetCentralWaitTimeout(timeout time.Duration) { + d.centralWaitTimeout = timeout +} + +func (d *Deployer) SetSecuredClusterWaitTimeout(timeout time.Duration) { + d.securedClusterWaitTimeout = timeout +} + func (d *Deployer) SetPauseReconciliation(enabled bool) { d.pauseReconciliation = enabled }