Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Examples:
cmd.Flags().BoolVar(&helm, "helm", false, "Deploy using Helm charts instead of operator")
cmd.Flags().BoolVar(&olm, "olm", false, "Deploy operator via OLM (requires OLM installed)")
cmd.Flags().BoolVar(&portForwarding, "port-forwarding", false, "Enable localhost port-forward for Central")
cmd.Flags().BoolVar(&pauseReconciliation, "pause-reconciliation", false, "Pause reconciliation after deployment")
cmd.Flags().StringVar(&overrideFile, "override", "", "Path to YAML file with overrides")
cmd.Flags().StringArrayVar(&overrideSetExpressions, "set", []string{}, "Set override values (can specify multiple times, e.g., --set foo.bar=val)")
cmd.Flags().StringVar(&exposure, "exposure", "loadbalancer", "Central exposure backend (loadbalancer, none)")
Expand Down Expand Up @@ -125,6 +126,7 @@ func runDeploy(cmd *cobra.Command, args []string) error {
d.SetVerbose(verbose)
d.SetEarlyReadiness(earlyReadiness)
d.SetPortForwardingEnabled(portForwardEnabledFinal)
d.SetPauseReconciliation(pauseReconciliation)

// Resolve "auto" resources based on cluster type
resolvedResources := resources
Expand Down
1 change: 1 addition & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var (
helm bool
olm bool
portForwarding bool
pauseReconciliation bool
overrideFile string
overrideSetExpressions []string
exposure string
Expand Down
8 changes: 8 additions & 0 deletions internal/deployer/deploy_via_operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ func (d *Deployer) deployCentralOperator(ctx context.Context, resources, exposur
return fmt.Errorf("failed waiting for Central: %w", err)
}

if err := d.maybeAddPauseReconcileAnnotation(ctx, "central", "stackrox-central-services", d.centralNamespace); err != nil {
d.logger.Warningf("failed to add pause-reconcile annotation: %v", err)
Comment thread
mclasmeier marked this conversation as resolved.
}

return d.configureCentralEndpoint(ctx, exposure)
}

Expand Down Expand Up @@ -589,6 +593,10 @@ func (d *Deployer) deploySecuredClusterOperator(ctx context.Context, resources s
return fmt.Errorf("failed waiting for SecuredCluster: %w", err)
}

if err := d.maybeAddPauseReconcileAnnotation(ctx, "securedcluster", "stackrox-secured-cluster-services", d.sensorNamespace); err != nil {
d.logger.Warningf("failed to add pause-reconcile annotation: %v", err)
}

d.logger.Successf("✓ SecuredCluster '%s' is ready", clusterName)
return nil
}
Expand Down
53 changes: 53 additions & 0 deletions internal/deployer/deployer.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ var (
centralNamespace = "acs-central"
sensorNamespace = "acs-sensor"
defaultExposure = "loadbalancer"

pauseReconcileAnnotationKey = "stackrox.io/pause-reconcile"
)

// Deployer is the base deployer for ACS
Expand All @@ -46,6 +48,7 @@ type Deployer struct {
roxCACertFile string
kubeContext string
portForwardEnabled bool
pauseReconciliation bool
exposure string
overrideFile string
overrideSetExpressions []string
Expand Down Expand Up @@ -260,6 +263,9 @@ func (d *Deployer) teardownCentral(ctx context.Context) error {

d.portForward.Stop()

// Remove pause-reconcile annotation before deleting to allow operator to clean up properly
d.removePauseReconcileAnnotation(ctx, "central", "stackrox-central-services", d.centralNamespace)

d.logger.Info("Deleting Central custom resource")
d.runKubectl(ctx, KubectlOptions{
Args: []string{"delete", "central", "stackrox-central-services", "-n", d.centralNamespace, "--wait=false"},
Expand Down Expand Up @@ -292,6 +298,9 @@ func (d *Deployer) teardownSecuredCluster(ctx context.Context) error {
return nil
}

// Remove pause-reconcile annotation before deleting to allow operator to clean up properly
d.removePauseReconcileAnnotation(ctx, "securedcluster", "stackrox-secured-cluster-services", d.sensorNamespace)

d.logger.Info("Deleting SecuredCluster custom resource")
d.runKubectl(ctx, KubectlOptions{
Args: []string{"delete", "securedcluster", "stackrox-secured-cluster-services", "-n", d.sensorNamespace, "--wait=false"},
Expand Down Expand Up @@ -500,6 +509,50 @@ func (d *Deployer) SetEarlyReadiness(enabled bool) {
d.earlyReadiness = enabled
}

func (d *Deployer) SetPauseReconciliation(enabled bool) {
d.pauseReconciliation = enabled
}

// maybeAddPauseReconcileAnnotation adds the stackrox.io/pause-reconcile annotation to a custom resource
func (d *Deployer) maybeAddPauseReconcileAnnotation(ctx context.Context, resourceType, resourceName, namespace string) error {
if !d.pauseReconciliation {
return nil
}

d.logger.Infof("Adding pause-reconcile annotation to %s/%s", resourceType, resourceName)

_, err := d.runKubectl(ctx, KubectlOptions{
Args: []string{
"annotate", resourceType, resourceName,
"-n", namespace,
pauseReconcileAnnotationKey,
"--overwrite",
},
})
if err != nil {
return fmt.Errorf("failed to add pause-reconcile annotation: %w", err)
}

d.logger.Successf("✓ Added pause-reconcile annotation to %s/%s", resourceType, resourceName)
return nil
}

// removePauseReconcileAnnotation removes the stackrox.io/pause-reconcile annotation from a custom resource
func (d *Deployer) removePauseReconcileAnnotation(ctx context.Context, resourceType, resourceName, namespace string) {
Comment thread
mclasmeier marked this conversation as resolved.
d.logger.Dimf("Removing pause-reconcile annotation from %s/%s", resourceType, resourceName)

_, err := d.runKubectl(ctx, KubectlOptions{
Args: []string{
"annotate", resourceType, resourceName,
"-n", namespace,
fmt.Sprintf("%s-", pauseReconcileAnnotationKey),
},
})
if err != nil {
d.logger.Dimf("Could not remove pause-reconcile annotation (expected if CR does not exist): %v", err)
}
}

func (d *Deployer) GetDeploymentInfo() (endpoint, password, caCertFile, kubeContext, exposure string) {
return d.centralEndpoint, d.centralPassword, d.roxCACertFile, d.kubeContext, d.exposure
}
Expand Down
36 changes: 35 additions & 1 deletion tests/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package e2e
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
Expand Down Expand Up @@ -296,14 +297,24 @@ func TestDeployBothComponentsTogether(t *testing.T) {
defer os.Remove(envrcPath)

t.Log("=== Deploying both components ===")
args := append([]string{roxieBinary, "deploy", "both", "--envrc", envrcPath}, commonDeployArgsNoPortForward...)
// We also test --pause-reconciliation flag here.
args := append([]string{roxieBinary, "deploy", "both", "--early-readiness", "--pause-reconciliation", "--envrc", envrcPath}, commonDeployArgsNoPortForward...)
runCommand(t, deployTimeout*2, nil, args...)

t.Log("Verifying namespace: acs-central")
verifyNamespaceExists(t, "acs-central")

t.Log("Verifying namespace: acs-sensor")
verifyNamespaceExists(t, "acs-sensor")

// Verify Central has the pause-reconcile annotation.
t.Log("Verifying pause-reconcile annotation on Central CR")
verifyAnnotation(t, "central", "stackrox-central-services", "acs-central", "stackrox.io/pause-reconcile", "true")

// Verify SecuredCluster has the pause-reconcile annotation.
t.Log("Verifying pause-reconcile annotation on SecuredCluster CR")
verifyAnnotation(t, "securedcluster", "stackrox-secured-cluster-services", "acs-sensor", "stackrox.io/pause-reconcile", "true")

}

func TestDeployCentralAndSecuredClusterViaHelm(t *testing.T) {
Expand Down Expand Up @@ -337,3 +348,26 @@ func TestDeployCentralAndSecuredClusterViaHelm(t *testing.T) {
t.Log("Verifying namespace: acs-sensor")
verifyNamespaceExists(t, "acs-sensor")
}

func verifyAnnotation(t *testing.T, resourceType, resourceName, namespace, annotationKey, expectedValue string) {
t.Helper()

cmd := exec.Command("kubectl", "get", resourceType, resourceName, "-n", namespace, "-o", "jsonpath={.metadata.annotations}")
output, err := cmd.Output()
if err != nil {
t.Fatalf("Failed to get annotation %s on %s/%s in namespace %s: %v", annotationKey, resourceType, resourceName, namespace, err)
}

annotations := make(map[string]string)
err = json.Unmarshal(output, &annotations)
if err != nil {
t.Fatalf("Failed to unmarshal JSON: %v", err)
}

currentValue := annotations[annotationKey]
if currentValue != expectedValue {
t.Fatalf("Annotation %s on %s/%s has incorrect value. Expected: %s, Got: %s", annotationKey, resourceType, resourceName, expectedValue, currentValue)
}

t.Logf("✓ Annotation %s=%s verified on %s/%s", annotationKey, expectedValue, resourceType, resourceName)
}