diff --git a/internal/deployer/crds.go b/internal/deployer/crds.go new file mode 100644 index 0000000..2238d25 --- /dev/null +++ b/internal/deployer/crds.go @@ -0,0 +1,120 @@ +package deployer + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" +) + +// requiredCRDs lists the CRDs required for ACS operator. +var requiredCRDs = []string{ + "centrals.platform.stackrox.io", + "securedclusters.platform.stackrox.io", + "securitypolicies.config.stackrox.io", +} + +// ensureCRDsInstalled ensures required CRDs exist, installing them from the provided bundle directory. +func (d *Deployer) ensureCRDsInstalled(ctx context.Context, bundleDir string) error { + var missing []string + for _, crd := range requiredCRDs { + _, err := d.runKubectl(ctx, KubectlOptions{ + Args: []string{"get", "crd", crd}, + }) + if err != nil { + missing = append(missing, crd) + } + } + + if len(missing) > 0 { + d.logger.Warningf("Missing CRDs detected (%s)", strings.Join(missing, ", ")) + + crdFiles, err := d.identifyCRDFileNames(bundleDir) + if err != nil { + return err + } + + return d.applyCRDsToCluster(ctx, crdFiles) + } + + return nil +} + +// identifyCRDFileNames identifies CRD files in the bundle directory. +func (d *Deployer) identifyCRDFileNames(bundleDir string) ([]string, error) { + var crdFiles []string + + err := filepath.Walk(bundleDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + return nil + } + + if !strings.HasSuffix(info.Name(), ".yaml") && !strings.HasSuffix(info.Name(), ".yml") { + return nil + } + + // TODO(#91): The following detection logic does not seem particularly robust. We should + // probably parse the YAML and check api group and kind fields. + name := strings.ToLower(info.Name()) + if strings.Contains(name, "customresourcedefinition") || + strings.Contains(name, "platform.stackrox.io") || + strings.Contains(name, "config.stackrox.io") { + if strings.Contains(name, "clusterserviceversion") { + return nil + } + + content, err := os.ReadFile(path) + if err != nil { + return nil + } + + if strings.Contains(string(content), "kind: CustomResourceDefinition") { + crdFiles = append(crdFiles, path) + } + } + + return nil + }) + + if err != nil { + return nil, fmt.Errorf("failed to walk bundle directory: %w", err) + } + + return crdFiles, nil +} + +// applyCRDsToCluster applies CRD files to the cluster +func (d *Deployer) applyCRDsToCluster(ctx context.Context, crdFiles []string) error { + d.logger.Infof("Applying %d CRD(s) to cluster", len(crdFiles)) + + for _, crdFile := range crdFiles { + result, err := d.runKubectl(ctx, KubectlOptions{ + Args: []string{"apply", "-f", crdFile}, + }) + if err != nil { + d.logger.Errorf("kubectl stderr: %s", result.Stderr) + return fmt.Errorf("failed to apply CRD %s: %w\nStderr: %s", crdFile, err, result.Stderr) + } + + basename := filepath.Base(crdFile) + d.logger.Successf("โœ“ Successfully applied CRD %s", basename) + } + + return nil +} + +// deleteCRDs deletes RHACS CRDs from the cluster +func (d *Deployer) deleteCRDs(ctx context.Context) { + d.logger.Info("Deleting CRDs...") + + args := append([]string{"delete", "crd", "--ignore-not-found=true"}, requiredCRDs...) + d.runKubectl(ctx, KubectlOptions{ + Args: args, + IgnoreErrors: true, + }) +} diff --git a/internal/deployer/deploy_via_helm.go b/internal/deployer/deploy_via_helm.go index a410cdf..27c7510 100644 --- a/internal/deployer/deploy_via_helm.go +++ b/internal/deployer/deploy_via_helm.go @@ -518,20 +518,3 @@ func (d *Deployer) installSecuredClusterHelmChart(ctx context.Context, chartDir, d.logger.Success("โœ“ Helm chart installed") return nil } - -// deleteCRDs deletes ACS CRDs (used before Helm deployment) -func (d *Deployer) deleteCRDs(ctx context.Context) { - crds := []string{ - "centrals.platform.stackrox.io", - "securedclusters.platform.stackrox.io", - "securitypolicies.config.stackrox.io", - } - - d.logger.Info("Deleting CRDs...") - - args := append([]string{"delete", "crd", "--ignore-not-found=true"}, crds...) - d.runKubectl(ctx, KubectlOptions{ - Args: args, - IgnoreErrors: true, - }) -} diff --git a/internal/deployer/deploy_via_operator.go b/internal/deployer/deploy_via_operator.go index 4fe2134..2951077 100644 --- a/internal/deployer/deploy_via_operator.go +++ b/internal/deployer/deploy_via_operator.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/stackrox/roxie/internal/component" "github.com/stackrox/roxie/internal/env" "github.com/stackrox/roxie/internal/helpers" "gopkg.in/yaml.v3" @@ -47,10 +48,6 @@ func (d *Deployer) ensureOperatorDeployed(ctx context.Context) error { return nil } - if err := d.ensureCRDsInstalled(ctx); err != nil { - return fmt.Errorf("failed to ensure CRDs installed: %w", err) - } - // Detect current operator deployment mode operatorExists, currentMode := d.detectOperatorDeploymentMode(ctx) needsDeployment := false @@ -80,26 +77,45 @@ func (d *Deployer) ensureOperatorDeployed(ctx context.Context) error { } if needsTeardown { - // Perform teardown for the current mode - if currentMode == OperatorModeOLM { - if err := d.teardownOperatorOLM(ctx); err != nil { - return fmt.Errorf("failed to teardown OLM operator: %w", err) - } - } else { - if err := d.teardownOperatorNonOLM(ctx); err != nil { - return fmt.Errorf("failed to teardown non-OLM operator: %w", err) - } - } + // It is a prerequisite that all CRs are removed before the operator is removed to avoid blocking during teardown. + d.Teardown(ctx, component.All) } if needsDeployment { + d.logger.Info("๐Ÿš€ Deploying operator...") + d.logger.Infof("Operator tag: %s", d.operatorTag) + if d.useOLM { + // OLM takes care of CRD installation. if err := d.deployOperatorViaOLM(ctx); err != nil { return fmt.Errorf("failed to deploy operator via OLM: %w", err) } } else { - if err := d.deployOperatorNonOLM(ctx); err != nil { - return fmt.Errorf("failed to deploy operator: %w", err) + if d.useKonflux { + if err := d.ensureKonfluxImageRewriting(ctx); err != nil { + return fmt.Errorf("failed to configure Konflux image rewriting: %w", err) + } + } else { + if err := d.removeKonfluxImageRewriting(ctx); err != nil { + return fmt.Errorf("failed to remove Konflux ImageContentSourcePolicy: %v", err) + } + } + + bundleImage := d.getOperatorBundleImage() + d.logger.Infof("Bundle image: %s", bundleImage) + + bundleDir, err := d.downloadAndExtractOperatorBundle(ctx, bundleImage) + if err != nil { + return fmt.Errorf("failed to download operator bundle: %w", err) + } + defer d.cleanupTempDir(bundleDir, "operator bundle directory") + + if err := d.ensureCRDsInstalled(ctx, bundleDir); err != nil { + return fmt.Errorf("failed to ensure CRDs installed: %w", err) + } + + if err := d.deployOperatorFromCSV(ctx, bundleDir); err != nil { + return fmt.Errorf("failed to deploy operator from CSV: %w", err) } } } diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index 6597cd2..c831ae0 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -612,11 +612,8 @@ func (d *Deployer) prepareCredentials() error { func (d *Deployer) deployCentral(ctx context.Context, resources, exposure string) error { d.logger.Infof("Deploying Central to namespace %s", d.centralNamespace) - if d.namespaceExists(d.centralNamespace) { - d.logger.Info("Existing Central deployment found, tearing down...") - if err := d.teardownCentral(ctx); err != nil { - d.logger.Warningf("Error during teardown: %v", err) - } + if err := d.Teardown(ctx, component.Both); err != nil { + d.logger.Warningf("Error during teardown: %v", err) } portForwardWanted := d.portForwardEnabled @@ -645,11 +642,8 @@ func (d *Deployer) deployCentral(ctx context.Context, resources, exposure string func (d *Deployer) deploySecuredCluster(ctx context.Context, resources string) error { d.logger.Infof("Deploying SecuredCluster to namespace %s", d.sensorNamespace) - if d.namespaceExists(d.sensorNamespace) { - d.logger.Info("Existing SecuredCluster deployment found, tearing down...") - if err := d.teardownSecuredCluster(ctx); err != nil { - d.logger.Warningf("Error during teardown: %v", err) - } + if err := d.Teardown(ctx, component.SecuredCluster); err != nil { + d.logger.Warningf("Error during teardown: %v", err) } if d.useHelm { diff --git a/internal/deployer/operator.go b/internal/deployer/operator.go index d76d224..9d5b663 100644 --- a/internal/deployer/operator.go +++ b/internal/deployer/operator.go @@ -26,50 +26,6 @@ const ( operatorBundleImageReleaseRepo = "quay.io/rhacs-eng/release-operator-bundle" ) -var requiredCRDs = []string{ - "centrals.platform.stackrox.io", - "securedclusters.platform.stackrox.io", - "securitypolicies.config.stackrox.io", -} - -// deployOperatorNonOLM deploys the RHACS operator without OLM -func (d *Deployer) deployOperatorNonOLM(ctx context.Context) error { - d.logger.Infof("Operator tag: %s", d.operatorTag) - if d.useKonflux { - if err := d.ensureKonfluxImageRewriting(ctx); err != nil { - return fmt.Errorf("failed to configure Konflux image rewriting: %w", err) - } - } else { - if err := d.removeKonfluxImageRewriting(ctx); err != nil { - return fmt.Errorf("failed to remove Konflux ImageContentSourcePolicy: %v", err) - } - } - bundleImage := d.getOperatorBundleImage() - - bundleDir, err := d.downloadAndExtractOperatorBundle(ctx, bundleImage) - if err != nil { - return fmt.Errorf("failed to download operator bundle: %w", err) - } - defer d.cleanupTempDir(bundleDir, "operator bundle directory") - - d.logger.Infof("Bundle image: %s", bundleImage) - - crdFiles, err := d.identifyCRDFileNames(bundleDir) - if err != nil { - return err - } - - if err := d.applyCRDsToCluster(ctx, crdFiles); err != nil { - return err - } - - if err := d.deployOperatorFromCSV(ctx, bundleDir); err != nil { - return err - } - - return nil -} - // downloadAndExtractOperatorBundle downloads and extracts the operator bundle func (d *Deployer) downloadAndExtractOperatorBundle(ctx context.Context, bundleImage string) (string, error) { bundleDir, err := os.MkdirTemp("", "stackrox-operator-bundle-") @@ -90,107 +46,6 @@ func (d *Deployer) downloadAndExtractOperatorBundle(ctx context.Context, bundleI return bundleDir, nil } -// identifyCRDFileNames identifies CRD files in the bundle directory -func (d *Deployer) identifyCRDFileNames(bundleDir string) ([]string, error) { - var crdFiles []string - - err := filepath.Walk(bundleDir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - if info.IsDir() { - return nil - } - - if !strings.HasSuffix(info.Name(), ".yaml") && !strings.HasSuffix(info.Name(), ".yml") { - return nil - } - - // TODO(#91): The following detection logic does not seem particularly robust. We should - // probably parse the YAML and check api group and kind fields. - name := strings.ToLower(info.Name()) - if strings.Contains(name, "customresourcedefinition") || - strings.Contains(name, "platform.stackrox.io") || - strings.Contains(name, "config.stackrox.io") { - if strings.Contains(name, "clusterserviceversion") { - return nil - } - - content, err := os.ReadFile(path) - if err != nil { - return nil - } - - if strings.Contains(string(content), "kind: CustomResourceDefinition") { - crdFiles = append(crdFiles, path) - } - } - - return nil - }) - - if err != nil { - return nil, fmt.Errorf("failed to walk bundle directory: %w", err) - } - - return crdFiles, nil -} - -// applyCRDsToCluster applies CRD files to the cluster -func (d *Deployer) applyCRDsToCluster(ctx context.Context, crdFiles []string) error { - d.logger.Infof("Applying %d CRD(s) to cluster", len(crdFiles)) - - for _, crdFile := range crdFiles { - result, err := d.runKubectl(ctx, KubectlOptions{ - Args: []string{"apply", "-f", crdFile}, - }) - if err != nil { - d.logger.Errorf("kubectl stderr: %s", result.Stderr) - return fmt.Errorf("failed to apply CRD %s: %w\nStderr: %s", crdFile, err, result.Stderr) - } - - basename := filepath.Base(crdFile) - d.logger.Successf("โœ“ Successfully applied CRD %s", basename) - } - - return nil -} - -// ensureCRDsInstalled ensures required CRDs exist -func (d *Deployer) ensureCRDsInstalled(ctx context.Context) error { - var missing []string - for _, crd := range requiredCRDs { - _, err := d.runKubectl(ctx, KubectlOptions{ - Args: []string{"get", "crd", crd}, - }) - if err != nil { - missing = append(missing, crd) - } - } - - if len(missing) > 0 { - bundleImage := d.getOperatorBundleImage() - d.logger.Warningf("Missing CRDs detected (%s)", strings.Join(missing, ", ")) - d.logger.Warningf("Fetching bundle %s", bundleImage) - - bundleDir, err := d.downloadAndExtractOperatorBundle(ctx, bundleImage) - if err != nil { - return err - } - defer d.cleanupTempDir(bundleDir, "CRD bundle directory") - - crdFiles, err := d.identifyCRDFileNames(bundleDir) - if err != nil { - return err - } - - return d.applyCRDsToCluster(ctx, crdFiles) - } - - return nil -} - func (d *Deployer) getOperatorBundleImage() string { if d.useKonflux { d.logger.Infof("Using Konflux-built operator bundle image") @@ -608,6 +463,7 @@ func generateClusterName() string { } // teardownOperatorNonOLM removes the operator when installed without OLM. +// Requires that all CRs are removed beforehand. func (d *Deployer) teardownOperatorNonOLM(ctx context.Context) error { d.logger.Info("๐Ÿงน Tearing down operator deployed without OLM...") @@ -632,6 +488,9 @@ func (d *Deployer) teardownOperatorNonOLM(ctx context.Context) error { }) } + // Delete CRDs to ensure clean redeployment with updated CRD versions. + d.deleteCRDs(ctx) + if err := d.waitForNamespaceDeletion(operatorNamespace); err != nil { d.logger.Warningf("Namespace %s deletion incomplete: %v", operatorNamespace, err) } @@ -641,6 +500,7 @@ func (d *Deployer) teardownOperatorNonOLM(ctx context.Context) error { } // teardownOperator removes the operator if it exists, detecting the deployment mode automatically. +// Requires that all CRs are removed beforehand. func (d *Deployer) teardownOperator(ctx context.Context) error { operatorExists, operatorMode := d.detectOperatorDeploymentMode(ctx) if !operatorExists { @@ -649,7 +509,14 @@ func (d *Deployer) teardownOperator(ctx context.Context) error { } if operatorMode == OperatorModeOLM { - return d.teardownOperatorOLM(ctx) + if err := d.teardownOperatorOLM(ctx); err != nil { + return fmt.Errorf("failed to teardown OLM operator: %w", err) + } + return nil + } + + if err := d.teardownOperatorNonOLM(ctx); err != nil { + return fmt.Errorf("failed to teardown non-OLM operator: %w", err) } - return d.teardownOperatorNonOLM(ctx) + return nil } diff --git a/internal/deployer/operator_olm.go b/internal/deployer/operator_olm.go index 94b33f0..86ace96 100644 --- a/internal/deployer/operator_olm.go +++ b/internal/deployer/operator_olm.go @@ -29,9 +29,6 @@ const ( // deployOperatorViaOLM deploys the RHACS operator using OLM. func (d *Deployer) deployOperatorViaOLM(ctx context.Context) error { - d.logger.Info("๐Ÿš€ Deploying operator via OLM...") - d.logger.Infof("Operator tag: %s", d.operatorTag) - if err := d.checkOLMInstalled(ctx); err != nil { return err } @@ -366,6 +363,7 @@ func (d *Deployer) detectOperatorDeploymentMode(ctx context.Context) (bool, Oper } // teardownOperatorOLM removes the operator when installed via OLM. +// Requires that all CRs are removed beforehand. func (d *Deployer) teardownOperatorOLM(ctx context.Context) error { d.logger.Info("๐Ÿงน Tearing down operator deployed via OLM...")