ROX-35435: Support differing versions of Central and SecuredCluster - #246
ROX-35435: Support differing versions of Central and SecuredCluster#246vladbologa wants to merge 15 commits into
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughCentral and SecuredCluster operator versions now drive image generation, operator instance planning, validation, Konflux variables, deployment orchestration, and deployment summaries. ChangesSplit Operator Support
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant DeployCommand
participant Config
participant ImageBuilder
participant OperatorDeployment
participant Kubernetes
DeployCommand->>Config: apply component version flags
Config->>Config: build OperatorInstances()
Config-->>ImageBuilder: effective versions and instances
ImageBuilder-->>DeployCommand: versioned image list
DeployCommand->>OperatorDeployment: ensure operator instances
OperatorDeployment->>Kubernetes: inspect, deploy, or tear down resources
Kubernetes-->>OperatorDeployment: deployment state
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/deployer/acs_images.go`:
- Around line 38-40: Update the image collection loop in the deployer flow
around OperatorInstances so single-operator configurations consistently use the
explicit Operator.Version override instead of mixing it with the resolved
instance version; alternatively, unify OperatorBundleImageForVersion and the OLM
Konflux environment-variable path on the same resolved version. Add a regression
test covering both Roxie.Version and Operator.Version being set, ensuring all
generated image references use one consistent operator version.
In `@internal/deployer/deploy_via_operator.go`:
- Around line 138-141: Update the early-return logic in the deployment flow
around isOperatorVersionCorrect so it also verifies the manager environment
matches instance.EnvVars, including component-specific reconciler flags and
Konflux/custom variables. Only return when both the image version and
environment are correct; otherwise reapply the desired Deployment using the
existing operator deployment path, preserving the split-mode flags defined by
the operator instance plan.
- Around line 110-125: The cleanup loop in deploy_via_operator.go around
AllOperatorNamespaces must attempt teardown for every undesired known namespace
without gating on operatorDeploymentExists or namespaceExists, allowing
teardownOperatorNonOLMInNamespace to remove namespaced and cluster-scoped
resources idempotently. In operator.go around the non-OLM cleanup flow, invoke
cleanup even when no Deployment is detected so partial resources are removed;
apply the change at both affected sites.
In `@internal/deployer/operator_instance.go`:
- Around line 78-82: Update the single-operator path in NeedsSplitOperators to
derive the operator version from the effective component version, honoring equal
Roxie and Operator overrides instead of using Roxie.Version directly. Preserve
the fallback behavior when no converted version is available, and extend
TestEffectiveVersions_BothOverridesSame_NoSplit with an assertion that the
deployed operator uses the override version.
In `@internal/deployer/operator.go`:
- Around line 575-598: Update the teardown flow around the kubectl deletion
calls and waitForNamespaceDeletion to propagate genuine deletion or timeout
errors instead of ignoring them or returning nil. Keep --ignore-not-found=true
for idempotent cleanup, but capture each runKubectl error and return it
immediately; likewise return the waitForNamespaceDeletion error before logging
success. Only log the success message and return nil after all cleanup steps
complete successfully.
- Around line 217-222: Update the EnvVars logging block in the operator
deployment flow to stop emitting environment values, since entries may contain
credentials or tokens. Preserve the existing count log and, if individual
entries are still needed, log only each variable’s name from envVarsToSortedList
without accessing or formatting ev["value"].
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Enterprise
Run ID: b2693e61-392e-405e-a4bb-6dd14e9c6436
📒 Files selected for processing (10)
cmd/deploy.gointernal/deployer/acs_images.gointernal/deployer/config.gointernal/deployer/deploy_via_operator.gointernal/deployer/deployer.gointernal/deployer/konflux.gointernal/deployer/konflux_test.gointernal/deployer/operator.gointernal/deployer/operator_instance.gointernal/deployer/operator_instance_test.go
3c45e8d to
3776248
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
internal/deployer/operator_instance_test.go (1)
74-109: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMake override fixtures distinct from the global version.
These cases can pass if downstream planning accidentally uses
Roxie.Versionfor a component, because it equals one expected component version.
internal/deployer/operator_instance_test.go#L74-L109: use a third Roxie version and verify the sensor instance still usesSecuredCluster.Version.internal/deployer/operator_instance_test.go#L123-L139: setRoxie.Versionbelow the Central override so this case proves Central participates in newest-version selection.internal/deployer/operator_instance_test.go#L151-L165: use a third Roxie version and assert no global-version-only images are collected when both components override it.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/deployer/operator_instance_test.go` around lines 74 - 109, The operator instance tests use Roxie versions that can mask incorrect global-version handling. In internal/deployer/operator_instance_test.go:74-109, choose a distinct third Roxie version and keep asserting the sensor uses SecuredCluster.Version; at 123-139, set Roxie.Version below the Central override to verify Central drives newest-version selection; at 151-165, use a distinct Roxie version and assert no images for the global-only version are collected when both components override it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@internal/deployer/operator_instance_test.go`:
- Around line 74-109: The operator instance tests use Roxie versions that can
mask incorrect global-version handling. In
internal/deployer/operator_instance_test.go:74-109, choose a distinct third
Roxie version and keep asserting the sensor uses SecuredCluster.Version; at
123-139, set Roxie.Version below the Central override to verify Central drives
newest-version selection; at 151-165, use a distinct Roxie version and assert no
images for the global-only version are collected when both components override
it.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Enterprise
Run ID: 57f74974-e5ca-442d-b25f-d4c9e9bd1f49
📒 Files selected for processing (10)
cmd/deploy.gointernal/deployer/acs_images.gointernal/deployer/config.gointernal/deployer/deploy_via_operator.gointernal/deployer/deployer.gointernal/deployer/konflux.gointernal/deployer/konflux_test.gointernal/deployer/operator.gointernal/deployer/operator_instance.gointernal/deployer/operator_instance_test.go
🚧 Files skipped from review as they are similar to previous changes (9)
- internal/deployer/config.go
- internal/deployer/konflux.go
- internal/deployer/konflux_test.go
- internal/deployer/operator_instance.go
- internal/deployer/deployer.go
- cmd/deploy.go
- internal/deployer/acs_images.go
- internal/deployer/operator.go
- internal/deployer/deploy_via_operator.go
There was a problem hiding this comment.
Pull request overview
Adds mixed-version deployment support so Roxie can deploy Central and SecuredCluster at different versions on the same cluster, while preserving the existing single-version behavior.
Changes:
- Introduces per-component version overrides (
central.version/securedCluster.version, plus CLI flags) and resolves effective versions from config. - Enables dual non-OLM operator deployments (separate namespaces + per-instance reconciler toggles + distinct cluster-scoped RBAC names) and improves teardown/mode switching logic.
- Updates image planning (including operator/bundle images) and deployment summaries to reflect effective component versions.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/deployer/operator.go | Refactors non-OLM operator deployment/teardown to support multiple operator instances and per-instance RBAC names; gates CRD apply to the newest planned operator version. |
| internal/deployer/operator_instance.go | Adds effective version resolution, mixed-version detection, operator instance planning, and “newest operator version” selection. |
| internal/deployer/operator_instance_test.go | Adds unit tests for effective versions, operator instance planning, newest-version selection, and mixed-version image planning. |
| internal/deployer/konflux.go | Refactors Konflux env-var population to support per-instance operator versions (merge-style API). |
| internal/deployer/konflux_test.go | Updates tests for new Konflux APIs and adds coverage for per-instance version merging. |
| internal/deployer/deployer.go | Updates deployment summaries to display effective Central/SecuredCluster versions rather than the global Roxie version. |
| internal/deployer/deploy_via_operator.go | Extends operator deployment logic to deploy/validate multiple non-OLM operator instances and handle transitions between modes/namespaces. |
| internal/deployer/config.go | Adds version override fields to Central and SecuredCluster config structs. |
| internal/deployer/acs_images.go | Updates image selection to include unique effective main versions plus per-instance operator/bundle images (deduped). |
| cmd/deploy.go | Adds CLI flags for per-component tags, validates unsupported mode combinations for mixed versions, and adjusts version-gated checks/Konflux behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
3776248 to
d63b2af
Compare
| }), | ||
| ) | ||
|
|
||
| registerFlag(cmd, settings, "central-tag", "Image tag for Central (overrides --tag for Central)", |
There was a problem hiding this comment.
I need to think a bit about this PR from a conceptual viewpoint. But, looking at the concrete changes, I can already provide limited feedback.
For example, I don't think we need these flags. Direct CLI flags (IMHO) are remnants from the time before we had the unified roxie config. Everything can be set there and these settings specifically are not settings that we need every day (outside of automation), hence I think it's perfectly fine to drop these and let users configure those in the roxie config if they need to.
There was a problem hiding this comment.
As a roxie user I'd actually like to have them.
To give you an example, for cert refresh and CA rotation I had to do a lot of mixed version testing. It was quite tedious to do and because of that I didn't do as much compatibility testing as I should have. And these flags would make it quite easy to be able to switch around versions easily.
There was a problem hiding this comment.
If they are important for you, fine.
But you do know that you can do the same with --set, right?
There was a problem hiding this comment.
Oh, didn't think about that. Then I'm inclined to agree with you.
Having a flag is still somewhat useful because it makes the feature more easily discoverable, that's the only argument I have for it now.
…operator config blocks
|
@coderabbitai review |
✅ Action performedReview finished.
|
mclasmeier
left a comment
There was a problem hiding this comment.
I am not through yet.
| registerFlag(cmd, settings, "central-tag", "Image tag for Central (overrides --tag for Central)", | ||
| withApplyFn("version", func(config *deployer.Config, tag string) error { | ||
| if config.Central.Operator == nil { | ||
| config.Central.Operator = &deployer.OperatorConfig{} |
There was a problem hiding this comment.
Looking at internal/deployer/config.go I think it would be more consistent if we wouldn't make this a pointer type. I think this would be the only config struct used as pointer. Currently we use pointer where required by the merging behavior (pointer bools to differentiate between "unset" and "set to false").
We would still have the ability to check for the emptiness of these per-component blocks.
| hasSupport, err := stackroxversions.SupportsAdditionalPrinterColumns(deploySettings.Operator.Version) | ||
| if err != nil { | ||
| return fmt.Errorf("checking version constraint on main image tag %s: %w", deploySettings.Roxie.Version, err) | ||
| // Check every operator instance that will be deployed. |
There was a problem hiding this comment.
nit, we should try to not let runDeploy grow longer, actually I have been trying to reduce it's length recently. I know, what you are adding here isn't a whole lot, but maybe we could extract it into a helper validation function or something.
There was a problem hiding this comment.
These checks could fit in the existing deployValidate, I moved them there in a7a6b9e
|
|
||
| if deploySettings.HasMixedVersions() { | ||
| if components.IncludesOperatorExplicitly() { | ||
| return errors.New("mixed versions (--central-tag / --secured-cluster-tag / central.operator / securedCluster.operator) are not supported with operator-only deploy") |
There was a problem hiding this comment.
When providing CLI flags in addition to config fields is these error messages become quite complex...
nit:
What we are doing in the roxie code currently in a couple of places is:
Try to return a concise/short error but before doing that emit a more verbose error log.
| scVer := deploySettings.EffectiveSecuredClusterVersion() | ||
| if centralVer == scVer && centralVer != deploySettings.Roxie.Version && | ||
| deploySettings.Central.Operator != nil && deploySettings.SecuredCluster.Operator != nil { | ||
| return fmt.Errorf("both --central-tag and --secured-cluster-tag are set to %s which differs from --tag %s; use --tag %s instead", |
There was a problem hiding this comment.
Do we need this validation? I thought the intended logic would simply be that
component.operator, if present, takes precedence over top-level operator for that component?
| } | ||
|
|
||
| // EffectiveCentralVersion returns the main image tag used for Central. | ||
| // If central.operator.version is set, it is converted back to a main tag; |
There was a problem hiding this comment.
This back-conversion is claimed in the comment, but it is not in the implementation?
Same for SC below.
| newest := instances[0].Version | ||
| for _, inst := range instances[1:] { | ||
| if operatorVersionGreater(inst.Version, newest) { | ||
| newest = inst.Version | ||
| } | ||
| } | ||
| return newest | ||
| } | ||
|
|
||
| // operatorVersionGreater reports whether a is a newer operator tag than b. | ||
| func operatorVersionGreater(a, b string) bool { | ||
| av, aerr := parseOperatorSemver(a) | ||
| bv, berr := parseOperatorSemver(b) | ||
| if aerr == nil && berr == nil { | ||
| return av.GreaterThan(bv) | ||
| } | ||
| // Fall back to lexicographic compare when tags are not parseable as semver. | ||
| return a > b | ||
| } |
There was a problem hiding this comment.
I guess we could simplify this to something like
av, aerr := parseOperatorSemver(a.Version)
bv, berr := parseOperatorSemver(b.Version)
if aerr == nil && berr == nil {
return av.Compare(bv)
}
return cmp.Compare(a.Version, b.Version)
})| // EffectiveCentralVersion returns the main image tag used for Central. | ||
| // If central.operator.version is set, it is converted back to a main tag; | ||
| // otherwise falls back to roxie.version. | ||
| func (c *Config) EffectiveCentralVersion() string { |
There was a problem hiding this comment.
nit, why not simply call it CentralVersion() and SecuredClusterVersion()? IMHO "effective" in the name suggests that there already is some notion of "central/sc version", and we need "effective" as differentiator, because those are overwritable or something. But currently there is no notion "central/sc version".
| // entries for Konflux image rewriting. Used for the single-operator / OLM path | ||
| // where env vars live on the top-level OperatorConfig. | ||
| func PopulateKonfluxEnvVars(config *Config) { | ||
| config.Operator.EnvVars = MergeKonfluxEnvVars(config.Operator.EnvVars, config.Operator.Version) |
There was a problem hiding this comment.
Why do switch over this code to cloning the whole map before populating?
| func (d *Deployer) PrintSecuredClusterDeploymentSummary() { | ||
| component := "Secured Cluster" | ||
| mainImageTag := d.config.Roxie.Version | ||
| mainImageTag := d.config.EffectiveSecuredClusterVersion() |
There was a problem hiding this comment.
Need to be careful with distinguishing "main image tags" from "operator tags". Same above for central.
77806cb to
1e43049
Compare
Co-authored-by: Moritz Clasmeier <111092021+mclasmeier@users.noreply.github.com>
| newest := instances[0].Version | ||
| for _, inst := range instances[1:] { | ||
| if operatorVersionGreater(inst.Version, newest) { | ||
| newest = inst.Version | ||
| } | ||
| } | ||
| return newest | ||
| } | ||
|
|
||
| // operatorVersionGreater reports whether a is a newer operator tag than b. | ||
| func operatorVersionGreater(a, b string) bool { | ||
| av, aerr := parseOperatorSemver(a) | ||
| bv, berr := parseOperatorSemver(b) | ||
| if aerr == nil && berr == nil { | ||
| return av.GreaterThan(bv) | ||
| } | ||
| // Fall back to lexicographic compare when tags are not parseable as semver. | ||
| return a > b | ||
| } |
| // is sufficient for release-vs-release compat testing (e.g. 4.8.x vs 4.9.x). | ||
| func (c *Config) NewestOperatorVersion() string { | ||
| instances := c.OperatorInstances() | ||
| if len(instances) == 0 { |
| func (c *Config) OperatorInstances() []OperatorInstance { | ||
| if !c.HasMixedVersions() { | ||
| version := helpers.ConvertMainTagToOperatorTag(c.EffectiveCentralVersion()) | ||
| if version == "" { |
| operatorNamespaceSensor, | ||
| } | ||
|
|
||
| // OperatorInstance describes one operator deployment (single- or mixed-version). |
Description
Roxie can now deploy Central and SecuredCluster at different versions on the same cluster (for compatibility testing), while keeping the single-version path unchanged.
Config and CLI
roxie.version/--tagremains the global default.central.operator.version/--central-tagandsecuredCluster.operator.version/--secured-cluster-tag.CentralVersion()/SecuredClusterVersion(). When they differ,HasMixedVersions()is true.--tag/roxie.version.OperatorConfigblocks also allow setting custom env vars per operator instance.Operators
When versions match (common case):
rhacs-operator-system(unchanged).When versions differ:
rhacs-operator-centralwithSECURED_CLUSTER_RECONCILER_ENABLED=false.rhacs-operator-sensorwithCENTRAL_RECONCILER_ENABLED=false.…-central/…-sensor).This is derived automatically from version mismatch; users do not configure reconciler toggles or operator namespaces.
Limitations
--olm/ OLM deploy are not supported (hard error).roxie deploy operator(operator-only) are not supported (hard error).Approaches considered
roxie deployinvocationsCENTRAL_LABEL_SELECTOR/ …)Manual testing on an infra cluster
4.11.1, SecuredCluster4.10.0. Confirmed dual operators, correct reconciler toggles, matching product images;rhacs-operator-systemunused.roxie teardownremoved both dual-operator namespaces and component CRs/workloads.--tag 4.11.1only. Single operator inrhacs-operator-system, no reconciler toggles, no dual-operator namespaces.4.11.1/ SecuredCluster4.10.0.rhacs-operator-systemremoved; dual operators stood up correctly.--tag 4.11.1. Both dual-operator namespaces removed; single operator deployed torhacs-operator-system.--konflux --central-tag 4.11.1 --secured-cluster-tag 4.10.0. Each operator received correct per-versionRELATED_IMAGE_*env vars and rewritten operator image. CRD apply skipped for older operator.--olm --tag 4.11.1. Dual-operator namespaces removed; OLM deployed operator torhacs-operator-systemvia CatalogSource/Subscription.Summary by CodeRabbit
Summary by CodeRabbit
New Features
Bug Fixes
Tests