Skip to content

OCPCLOUD-3359: Add component names, manifestSubstitutions, and observedGeneration to CAPI revisions#2786

Open
mdbooth wants to merge 2 commits intoopenshift:masterfrom
openshift-cloud-team:OCPCLOUD-3359
Open

OCPCLOUD-3359: Add component names, manifestSubstitutions, and observedGeneration to CAPI revisions#2786
mdbooth wants to merge 2 commits intoopenshift:masterfrom
openshift-cloud-team:OCPCLOUD-3359

Conversation

@mdbooth
Copy link
Copy Markdown
Contributor

@mdbooth mdbooth commented Mar 30, 2026

component.name: This is a cosmetic change, but makes it easier for a human (e.g. dev/support) to understand what each component in a revision refers to.

component.manifestSubstitutions: These are values passed to 'envsubst' when rendering manifests. The initial use is to pass through cluster-wide TLS configuration in a controlled manner.

status.observedRevisionGeneration: Required by an API consumer to know that the revision controller has observed spec changes. Specifically, if a user adds an entry to spec.unmanagedCustomResourceDefinitions they need to know:

  • that the revision controller has generated a new revision containing the change
  • that the installer revision has applied the new revision to the cluster

They are currently only able to observe the latter, making it impossible to use the API robustly.

@openshift-ci-robot
Copy link
Copy Markdown

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: LGTM mode

@openshift-ci openshift-ci bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Mar 30, 2026
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Mar 30, 2026
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci bot commented Mar 30, 2026

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci-robot
Copy link
Copy Markdown

openshift-ci-robot commented Mar 30, 2026

@mdbooth: This pull request references OCPCLOUD-3359 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci bot commented Mar 30, 2026

Hello @mdbooth! Some important instructions when contributing to openshift/api:
API design plays an important part in the user experience of OpenShift and as such API PRs are subject to a high level of scrutiny to ensure they follow our best practices. If you haven't already done so, please review the OpenShift API Conventions and ensure that your proposed changes are compliant. Following these conventions will help expedite the api review process for your PR.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds manifest substitution support, component naming, and observed revision generation tracking to ClusterAPI types and CRD schemas. Introduces ManifestSubstitutions (atomic envsubst-style key/value list) on installer revisions, Name on installer components, and observedRevisionGeneration on status with CEL/XValidation rules preventing decreases and preventing unsetting once previously set. Updates generated deepcopy and Swagger docs for the new types/fields. Expands tests to validate substitution key/value rules, component name formatting, union/discriminator behavior for Image components, and monotonic/immutable semantics for observedRevisionGeneration.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@openshift-ci openshift-ci bot added the size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. label Mar 30, 2026
@openshift-ci-robot
Copy link
Copy Markdown

openshift-ci-robot commented Mar 30, 2026

@mdbooth: This pull request references OCPCLOUD-3359 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Allows a revision to contain substitution variables which will be passed to envsubst when rendering manifests. The initial purpose of this is substitution of cluster-wide Post Quantum Cryptography settings.

Also adds a name field to components to make it easier to understand what the installer is rendering for support and debugging.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@mdbooth
Copy link
Copy Markdown
Contributor Author

mdbooth commented Mar 30, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@JoelSpeed
Copy link
Copy Markdown
Contributor

/test verify-hypershift-integration

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Apr 1, 2026

PR-Agent: could not fine a component named verify-hypershift-integration in a supported language in this PR.

@JoelSpeed
Copy link
Copy Markdown
Contributor

/test verify-hypershift-integration

1 similar comment
@JoelSpeed
Copy link
Copy Markdown
Contributor

/test verify-hypershift-integration

@qodo-code-review
Copy link
Copy Markdown

Generating tests for 'verify-hypershift-integration' ✏️️

Work in progress ...

@JoelSpeed
Copy link
Copy Markdown
Contributor

/test verify-hypershift-integration

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Apr 7, 2026

PR-Agent: could not fine a component named verify-hypershift-integration in a supported language in this PR.

@mdbooth mdbooth marked this pull request as ready for review April 10, 2026 15:42
@openshift-ci openshift-ci bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Apr 10, 2026
@qodo-code-review
Copy link
Copy Markdown

ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan

Review Summary by Qodo

Add manifest substitutions, component names, and revision generation tracking

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add manifestSubstitutions field to enable envsubst-style variable substitution in manifests
• Add name field to components for improved readability and debugging
• Add observedRevisionGeneration field to track ClusterAPI object generation observed by
  controller
• Comprehensive validation rules for new fields with detailed error messages
Diagram
flowchart LR
  A["ClusterAPIInstallerRevision"] -->|adds| B["manifestSubstitutions"]
  C["ClusterAPIInstallerComponent"] -->|adds| D["name field"]
  E["ClusterAPIStatus"] -->|adds| F["observedRevisionGeneration"]
  B -->|contains| G["ClusterAPIInstallerRevisionManifestSubstitution"]
  G -->|has| H["key and value"]
Loading

Grey Divider

File Changes

1. operator/v1alpha1/types_clusterapi.go ✨ Enhancement +49/-0

Add new fields and types for manifest substitutions and component naming

• Add ObservedRevisionGeneration field to ClusterAPIStatus with validation rules preventing
 decrease or unsetting
• Add ManifestSubstitutions field to ClusterAPIInstallerRevision with atomic list type
• Add new ClusterAPIInstallerRevisionManifestSubstitution struct with Key and Value fields
• Add Name field to ClusterAPIInstallerComponent with alphanumeric and hyphen validation

operator/v1alpha1/types_clusterapi.go


2. openapi/generated_openapi/zz_generated.openapi.go ✨ Enhancement +64/-1

Update OpenAPI schema definitions for new fields

• Register new ClusterAPIInstallerRevisionManifestSubstitution schema in OpenAPI definitions
• Add name property to ClusterAPIInstallerComponent schema with validation
• Add manifestSubstitutions array property to ClusterAPIInstallerRevision schema
• Add observedRevisionGeneration integer property to ClusterAPIStatus schema

openapi/generated_openapi/zz_generated.openapi.go


3. operator/v1alpha1/zz_generated.deepcopy.go ✨ Enhancement +28/-0

Add deepcopy support for manifest substitution type

• Add DeepCopyInto and DeepCopy methods for ClusterAPIInstallerRevisionManifestSubstitution
• Update ClusterAPIInstallerRevision.DeepCopyInto to handle ManifestSubstitutions slice

operator/v1alpha1/zz_generated.deepcopy.go


View more (5)
4. operator/v1alpha1/zz_generated.swagger_doc_generated.go 📝 Documentation +17/-4

Add swagger documentation for new fields

• Add swagger documentation for ClusterAPIInstallerRevisionManifestSubstitution type
• Add documentation entries for name field in component and manifestSubstitutions in revision
• Add documentation for observedRevisionGeneration field in status

operator/v1alpha1/zz_generated.swagger_doc_generated.go


5. operator/v1alpha1/tests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml 🧪 Tests +639/-0

Add validation test cases for new fields

• Add comprehensive test cases for manifestSubstitutions key validation (valid patterns, invalid
 patterns)
• Add test cases for component name field validation (hyphens, uppercase, invalid characters)
• Add test cases for observedRevisionGeneration validation (minimum value, monotonic increase,
 unsetting prevention)
• Add test case for component union discriminator validation

operator/v1alpha1/tests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml


6. operator/v1alpha1/zz_generated.crd-manifests/0000_30_cluster-api_01_clusterapis.crd.yaml ✨ Enhancement +63/-0

Update CRD manifest with new field definitions

• Add observedRevisionGeneration field with int64 type, minimum value 1, and decrease prevention
 validation
• Add manifestSubstitutions array with ClusterAPIInstallerRevisionManifestSubstitution items and
 validation rules
• Add name field to component with alphanumeric and hyphen validation
• Add CEL validation rule preventing unsetting of observedRevisionGeneration once set

operator/v1alpha1/zz_generated.crd-manifests/0000_30_cluster-api_01_clusterapis.crd.yaml


7. operator/v1alpha1/zz_generated.featuregated-crd-manifests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml ✨ Enhancement +63/-0

Update featuregated CRD manifest with new fields

• Add observedRevisionGeneration field with int64 type, minimum value 1, and decrease prevention
 validation
• Add manifestSubstitutions array with ClusterAPIInstallerRevisionManifestSubstitution items and
 validation rules
• Add name field to component with alphanumeric and hyphen validation
• Add CEL validation rule preventing unsetting of observedRevisionGeneration once set

operator/v1alpha1/zz_generated.featuregated-crd-manifests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml


8. payload-manifests/crds/0000_30_cluster-api_01_clusterapis.crd.yaml ✨ Enhancement +63/-0

Update payload manifest CRD with new fields

• Add observedRevisionGeneration field with int64 type, minimum value 1, and decrease prevention
 validation
• Add manifestSubstitutions array with ClusterAPIInstallerRevisionManifestSubstitution items and
 validation rules
• Add name field to component with alphanumeric and hyphen validation
• Add CEL validation rule preventing unsetting of observedRevisionGeneration once set

payload-manifests/crds/0000_30_cluster-api_01_clusterapis.crd.yaml


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Apr 10, 2026

Code Review by Qodo

🐞 Bugs (2)   📘 Rule violations (2)   📎 Requirement gaps (0)   🎨 UX Issues (0)
🐞\ ≡ Correctness (1) ☼ Reliability (1)
📘\ ⚙ Maintainability (2)

Grey Divider


Action required

1. ManifestSubstitutions missing constraint docs 📘
Description
ManifestSubstitutions adds MinItems/MaxItems validation but its comment does not document
these constraints or what happens when the optional field is omitted. This violates the requirements
to document validation markers and omitted behavior for optional fields.
Code

operator/v1alpha1/types_clusterapi.go[R156-162]

+	// manifestSubstitutions is a list of envsubst style substitutions which
+	// will be applied to manifests in the revision during rendering.
+	// +optional
+	// +listType=atomic
+	// +kubebuilder:validation:MinItems=1
+	// +kubebuilder:validation:MaxItems=32
+	ManifestSubstitutions []ClusterAPIInstallerRevisionManifestSubstitution `json:"manifestSubstitutions,omitempty"`
Evidence
The field ManifestSubstitutions is marked +optional and has kubebuilder validation markers
(MinItems=1, MaxItems=32), but the associated comment only describes purpose and does not
document the constraints or omitted behavior, which the checklist requires.

AGENTS.md
AGENTS.md
AGENTS.md
operator/v1alpha1/types_clusterapi.go[156-162]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ManifestSubstitutions` has validation constraints (`MinItems=1`, `MaxItems=32`) and is optional, but the field comment does not document these constraints nor describe behavior when the field is omitted.

## Issue Context
Compliance requires that all kubebuilder validation markers be reflected in the field’s Go doc comment, and that optional fields explicitly describe omitted/default behavior.

## Fix Focus Areas
- operator/v1alpha1/types_clusterapi.go[156-162]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Component Name length undocumented 📘
Description
ClusterAPIInstallerComponent.Name adds MinLength/MaxLength validation but its comment does not
document the length constraints or what happens when the optional field is omitted. This violates
the requirements for documenting validation markers and omitted behavior.
Code

operator/v1alpha1/types_clusterapi.go[R214-221]

+	// name is the human-readable name of the component. It is only used to make
+	// api revisions easier to understand. It must consist of alphanumeric
+	// characters, or '-'.
+	// +optional
+	// +kubebuilder:validation:MinLength=1
+	// +kubebuilder:validation:MaxLength=255
+	// +kubebuilder:validation:XValidation:rule="self.matches('^[A-Za-z0-9-]+$')",message="name must consist of alphanumeric characters or '-'"
+	Name string `json:"name,omitempty"`
Evidence
The new optional field Name includes kubebuilder validations (MinLength=1, MaxLength=255, and
an XValidation pattern), but the comment only documents the character set requirement and does not
mention the length limits or omitted behavior, contrary to the checklist.

AGENTS.md
AGENTS.md
AGENTS.md
operator/v1alpha1/types_clusterapi.go[214-221]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `ClusterAPIInstallerComponent.Name` field comment does not document all validation constraints (min/max length) and does not explicitly state behavior when the optional field is omitted.

## Issue Context
Compliance requires field comments to include all constraints expressed via kubebuilder markers and to describe omitted/default behavior for optional fields.

## Fix Focus Areas
- operator/v1alpha1/types_clusterapi.go[214-221]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Unset-forbidden status field 🐞
Description
The new status-level XValidation forbidding unsetting observedRevisionGeneration can make status
updates fail during controller version skew, because multiple controllers write ClusterAPIStatus and
an older writer will drop the new field on marshal. This can block unrelated status updates (e.g.,
installer controller updating currentRevision) once the revision controller has set
observedRevisionGeneration.
Code

operator/v1alpha1/types_clusterapi.go[R78-84]

// ClusterAPIStatus describes the current state of the capi-operator.
// +kubebuilder:validation:XValidation:rule="self.revisions.exists(r, r.name == self.desiredRevision && self.revisions.all(s, s.revision <= r.revision))",message="desiredRevision must be the name of the revision with the highest revision number"
// +kubebuilder:validation:XValidation:rule="!has(self.currentRevision) || self.revisions.exists(r, r.name == self.currentRevision)",message="currentRevision must correspond to an entry in the revisions list"
+// +kubebuilder:validation:XValidation:rule="!has(oldSelf.observedRevisionGeneration) || has(self.observedRevisionGeneration)",message="observedRevisionGeneration may not be unset once set"
type ClusterAPIStatus struct {
	// currentRevision is the name of the most recently fully applied revision.
	// It is written by the installer controller. If it is absent, it indicates
Evidence
ClusterAPIStatus is written by more than one controller (comments: currentRevision by installer
controller; desiredRevision/revisions by revision controller). Once observedRevisionGeneration is
set, the new validation explicitly rejects updates that omit it, so any status writer that doesn’t
preserve the field (e.g., an older controller version that doesn’t have the field in its Go type)
will have its status updates rejected by the API server.

operator/v1alpha1/types_clusterapi.go[83-104]
operator/v1alpha1/types_clusterapi.go[78-123]
operator/v1alpha1/zz_generated.crd-manifests/0000_30_cluster-api_01_clusterapis.crd.yaml[314-325]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
A new XValidation rule forbids unsetting `status.observedRevisionGeneration` once set. Because `ClusterAPIStatus` is written by multiple controllers, and because older controller versions (or any writer using stale types) will drop unknown fields when marshalling status, this rule can cause status update failures and block progress.

### Issue Context
- `currentRevision` is written by the installer controller.
- `desiredRevision`/`revisions` are written by the revision controller.
Once the revision controller starts setting `observedRevisionGeneration`, any older installer controller still running (or any status writer not preserving the field) will have its `status` updates rejected.

### Fix Focus Areas
- operator/v1alpha1/types_clusterapi.go[78-123]
- operator/v1alpha1/zz_generated.crd-manifests/0000_30_cluster-api_01_clusterapis.crd.yaml[314-325]
- operator/v1alpha1/tests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml[1074-1290]

### Suggested fix
Prefer backward-compatible validation:
- Drop the `may not be unset once set` XValidation, keeping only:
 - `Minimum=1`
 - `self >= oldSelf` (monotonic, when present)
This avoids hard failures during version skew; the revision controller can re-populate the field if an older writer clears it during rollout.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Union metadata includes name 🐞
Description
The generated OpenAPI marks ClusterAPIInstallerComponent.name as a union member with discriminator
value "Name", but the discriminator field (type) only allows the enum value "Image". This makes the
published OpenAPI internally inconsistent and can confuse or break tooling that consumes
x-kubernetes-unions (e.g., schema conversion / clients).
Code

openapi/generated_openapi/zz_generated.openapi.go[R62702-62706]

							"discriminator": "type",
							"fields-to-discriminateBy": map[string]interface{}{
								"image": "Image",
+								"name":  "Name",
							},
Evidence
The OpenAPI union extension now includes a mapping for the new "name" field ("name": "Name") even
though the discriminator enum only includes "Image". In this repo, correct union usage relies on
explicitly marking union members with +unionMember (see APIServerEncryption), which is missing on
ClusterAPIInstallerComponent’s union member fields—leading to name being mistakenly treated as a
union member during generation.

openapi/generated_openapi/zz_generated.openapi.go[62666-62710]
operator/v1alpha1/types_clusterapi.go[200-234]
config/v1/types_apiserver.go[211-245]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`ClusterAPIInstallerComponent` is annotated as a `+union`, but the new `name` field is being treated as a union member during OpenAPI generation, producing `x-kubernetes-unions.fields-to-discriminateBy.name = "Name"` even though the discriminator (`type`) enum only includes `Image`.

### Issue Context
This is an OpenAPI generation correctness issue: the CRD schema doesn’t enforce unions here, but the published OpenAPI becomes inconsistent and can confuse tooling.

### Fix Focus Areas
- operator/v1alpha1/types_clusterapi.go[210-234]
- openapi/generated_openapi/zz_generated.openapi.go[62698-62710]

### Suggested fix
Add `// +unionMember` to the actual union member field(s) (e.g., `Image`) so only those fields appear under `fields-to-discriminateBy`, and regenerate OpenAPI. Alternatively, if the union marker isn’t providing value here, remove `// +union` from the struct and regenerate.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@mdbooth mdbooth changed the title OCPCLOUD-3359: Add component names and manifestSubstitutions to CAPI revisions OCPCLOUD-3359: Add component names, manifestSubstitutions, and observedGeneration to CAPI revisions Apr 10, 2026
Comment on lines +214 to +221
// name is the human-readable name of the component. It is only used to make
// api revisions easier to understand. It must consist of alphanumeric
// characters, or '-'.
// +optional
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=255
// +kubebuilder:validation:XValidation:rule="self.matches('^[A-Za-z0-9-]+$')",message="name must consist of alphanumeric characters or '-'"
Name string `json:"name,omitempty"`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Component name length undocumented 📘 Rule violation ⚙ Maintainability

ClusterAPIInstallerComponent.Name adds MinLength/MaxLength validation but its comment does not
document the length constraints or what happens when the optional field is omitted. This violates
the requirements for documenting validation markers and omitted behavior.
Agent Prompt
## Issue description
The `ClusterAPIInstallerComponent.Name` field comment does not document all validation constraints (min/max length) and does not explicitly state behavior when the optional field is omitted.

## Issue Context
Compliance requires field comments to include all constraints expressed via kubebuilder markers and to describe omitted/default behavior for optional fields.

## Fix Focus Areas
- operator/v1alpha1/types_clusterapi.go[214-221]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 78 to 84
// ClusterAPIStatus describes the current state of the capi-operator.
// +kubebuilder:validation:XValidation:rule="self.revisions.exists(r, r.name == self.desiredRevision && self.revisions.all(s, s.revision <= r.revision))",message="desiredRevision must be the name of the revision with the highest revision number"
// +kubebuilder:validation:XValidation:rule="!has(self.currentRevision) || self.revisions.exists(r, r.name == self.currentRevision)",message="currentRevision must correspond to an entry in the revisions list"
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.observedRevisionGeneration) || has(self.observedRevisionGeneration)",message="observedRevisionGeneration may not be unset once set"
type ClusterAPIStatus struct {
// currentRevision is the name of the most recently fully applied revision.
// It is written by the installer controller. If it is absent, it indicates
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Unset-forbidden status field 🐞 Bug ☼ Reliability

The new status-level XValidation forbidding unsetting observedRevisionGeneration can make status
updates fail during controller version skew, because multiple controllers write ClusterAPIStatus and
an older writer will drop the new field on marshal. This can block unrelated status updates (e.g.,
installer controller updating currentRevision) once the revision controller has set
observedRevisionGeneration.
Agent Prompt
### Issue description
A new XValidation rule forbids unsetting `status.observedRevisionGeneration` once set. Because `ClusterAPIStatus` is written by multiple controllers, and because older controller versions (or any writer using stale types) will drop unknown fields when marshalling status, this rule can cause status update failures and block progress.

### Issue Context
- `currentRevision` is written by the installer controller.
- `desiredRevision`/`revisions` are written by the revision controller.
Once the revision controller starts setting `observedRevisionGeneration`, any older installer controller still running (or any status writer not preserving the field) will have its `status` updates rejected.

### Fix Focus Areas
- operator/v1alpha1/types_clusterapi.go[78-123]
- operator/v1alpha1/zz_generated.crd-manifests/0000_30_cluster-api_01_clusterapis.crd.yaml[314-325]
- operator/v1alpha1/tests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml[1074-1290]

### Suggested fix
Prefer backward-compatible validation:
- Drop the `may not be unset once set` XValidation, keeping only:
  - `Minimum=1`
  - `self >= oldSelf` (monotonic, when present)
This avoids hard failures during version skew; the revision controller can re-populate the field if an older writer clears it during rollout.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API is v1alpha1 and the controller is in TPNU. I don't think we need to worry about this. I prefer the strictness.

@openshift-ci-robot
Copy link
Copy Markdown

openshift-ci-robot commented Apr 10, 2026

@mdbooth: This pull request references OCPCLOUD-3359 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

component.name: This is a cosmetic change, but makes it easier for a human (e.g. dev/support) to understand what each component in a revision refers to.

component.manifestSubstitutions: These are values passed to 'envsubst' when rendering manifests. The initial use is to pass through cluster-wide TLS configuration in a controlled manner.

status.observedRevisionGeneration: Required by an API consumer to know that the revision controller has observed spec changes. Specifically, if a user adds an entry to spec.unmanagedCustomResourceDefinitions they need to know:

  • that the revision controller has generated a new revision containing the change
  • that the installer revision has applied the new revision to the cluster

They are currently only able to observe the latter, making it impossible to use the API robustly.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@operator/v1alpha1/types_clusterapi.go`:
- Around line 191-197: The manifestSubstitutions entry declares Value as a
pointer which prevents kubebuilder from marking it required; change the Value
field from a pointer (*string) to a non-pointer string type (string), keep the
existing kubebuilder comment markers (e.g., // +required and
MinLength/MaxLength), and remove the `omitempty` from the
`json:"value,omitempty"` tag (use `json:"value"`) so the generated CRD requires
the field while still allowing an empty string as a valid value; update the
struct field named `Value` in the same type that contains
`manifestSubstitutions` accordingly.
🪄 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: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 1ca73141-9640-4909-bd58-075370a70025

📥 Commits

Reviewing files that changed from the base of the PR and between 55afea3 and bf499d1.

⛔ Files ignored due to path filters (3)
  • openapi/generated_openapi/zz_generated.openapi.go is excluded by !openapi/**
  • operator/v1alpha1/zz_generated.crd-manifests/0000_30_cluster-api_01_clusterapis.crd.yaml is excluded by !**/zz_generated.crd-manifests/*
  • operator/v1alpha1/zz_generated.featuregated-crd-manifests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
📒 Files selected for processing (5)
  • operator/v1alpha1/tests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml
  • operator/v1alpha1/types_clusterapi.go
  • operator/v1alpha1/zz_generated.deepcopy.go
  • operator/v1alpha1/zz_generated.swagger_doc_generated.go
  • payload-manifests/crds/0000_30_cluster-api_01_clusterapis.crd.yaml
🚧 Files skipped from review as they are similar to previous changes (2)
  • payload-manifests/crds/0000_30_cluster-api_01_clusterapis.crd.yaml
  • operator/v1alpha1/zz_generated.swagger_doc_generated.go

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
operator/v1alpha1/tests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml (1)

857-904: Add one negative test for omitted manifestSubstitutions[].value.

You already validate empty-string values; adding a case where value is entirely missing would lock in required-field behavior and prevent schema regressions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@operator/v1alpha1/tests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml`
around lines 857 - 904, Add a new negative test case alongside "Should accept
empty value in manifestSubstitutions" that attempts to apply a status revision
whose revisions[].manifestSubstitutions contains an item with key: MY_VAR but no
value field; name it something like "Should reject missing manifestSubstitutions
value" and assert the update is rejected (i.e., the updated status should not be
accepted / validation fails) to ensure manifestSubstitutions[].value is
required. Locate the existing test block for manifestSubstitutions to copy the
initial/updated/expected structure, change the updated to omit the value field
under manifestSubstitutions and set the expected outcome to a
rejection/validation failure for the status update.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@operator/v1alpha1/tests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml`:
- Around line 857-904: Add a new negative test case alongside "Should accept
empty value in manifestSubstitutions" that attempts to apply a status revision
whose revisions[].manifestSubstitutions contains an item with key: MY_VAR but no
value field; name it something like "Should reject missing manifestSubstitutions
value" and assert the update is rejected (i.e., the updated status should not be
accepted / validation fails) to ensure manifestSubstitutions[].value is
required. Locate the existing test block for manifestSubstitutions to copy the
initial/updated/expected structure, change the updated to omit the value field
under manifestSubstitutions and set the expected outcome to a
rejection/validation failure for the status update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: a067e912-1eea-4a62-bed6-3e5f6a86a92b

📥 Commits

Reviewing files that changed from the base of the PR and between bf499d1 and 558db4f.

⛔ Files ignored due to path filters (3)
  • openapi/generated_openapi/zz_generated.openapi.go is excluded by !openapi/**
  • operator/v1alpha1/zz_generated.crd-manifests/0000_30_cluster-api_01_clusterapis.crd.yaml is excluded by !**/zz_generated.crd-manifests/*
  • operator/v1alpha1/zz_generated.featuregated-crd-manifests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
📒 Files selected for processing (5)
  • operator/v1alpha1/tests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml
  • operator/v1alpha1/types_clusterapi.go
  • operator/v1alpha1/zz_generated.deepcopy.go
  • operator/v1alpha1/zz_generated.swagger_doc_generated.go
  • payload-manifests/crds/0000_30_cluster-api_01_clusterapis.crd.yaml
🚧 Files skipped from review as they are similar to previous changes (3)
  • payload-manifests/crds/0000_30_cluster-api_01_clusterapis.crd.yaml
  • operator/v1alpha1/zz_generated.swagger_doc_generated.go
  • operator/v1alpha1/zz_generated.deepcopy.go

@openshift-ci openshift-ci bot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Apr 10, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
operator/v1alpha1/types_clusterapi.go (1)

156-199: Enforce unique substitution keys.

The schema currently permits the same envsubst variable name more than once, which makes the effective rendered value depend on downstream precedence. Adding a list-level uniqueness rule on key would keep revision content unambiguous.

Suggested schema tweak
  // +optional
  // +listType=atomic
  // +kubebuilder:validation:MinItems=1
  // +kubebuilder:validation:MaxItems=32
+ // +kubebuilder:validation:XValidation:rule="self.all(x, self.exists_one(y, x.key == y.key))",message="each manifest substitution must have a unique key"
  ManifestSubstitutions []ClusterAPIInstallerRevisionManifestSubstitution `json:"manifestSubstitutions,omitempty"`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@operator/v1alpha1/types_clusterapi.go` around lines 156 - 199, The
ManifestSubstitutions list allows duplicate keys; change its list metadata to
enforce uniqueness by replacing the current +listType=atomic with +listType=map
and add a list map key annotation (+listMapKey=key or the equivalent
x-kubernetes list-map-keys) on the ManifestSubstitutions field so the CRD treats
items as a map keyed by ClusterAPIInstallerRevisionManifestSubstitution.Key (the
Key field already has the necessary validation), ensuring no two items can share
the same key.
operator/v1alpha1/tests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml (1)

655-904: Good coverage here—please add one negative case for omitted manifestSubstitutions[].value.

These cases prove empty string is allowed, but they do not lock in the equally important invariant that value must still be present. Given the *string + +required combination, a regression test here would be valuable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@operator/v1alpha1/tests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml`
around lines 655 - 904, Add a negative test case under the existing
manifestSubstitutions tests that submits a revision with a manifestSubstitutions
entry that includes a key but omits the value field (e.g.,
manifestSubstitutions: - key: MISSING) and assert it fails by setting
expectedStatusError to the required-field error for the missing value (use the
same pattern as other negative tests and reference manifestSubstitutions, value,
and expectedStatusError so the test ensures the value field is required).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@operator/v1alpha1/tests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml`:
- Around line 655-904: Add a negative test case under the existing
manifestSubstitutions tests that submits a revision with a manifestSubstitutions
entry that includes a key but omits the value field (e.g.,
manifestSubstitutions: - key: MISSING) and assert it fails by setting
expectedStatusError to the required-field error for the missing value (use the
same pattern as other negative tests and reference manifestSubstitutions, value,
and expectedStatusError so the test ensures the value field is required).

In `@operator/v1alpha1/types_clusterapi.go`:
- Around line 156-199: The ManifestSubstitutions list allows duplicate keys;
change its list metadata to enforce uniqueness by replacing the current
+listType=atomic with +listType=map and add a list map key annotation
(+listMapKey=key or the equivalent x-kubernetes list-map-keys) on the
ManifestSubstitutions field so the CRD treats items as a map keyed by
ClusterAPIInstallerRevisionManifestSubstitution.Key (the Key field already has
the necessary validation), ensuring no two items can share the same key.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 04e9d3fc-fa9a-4746-8729-0ba98e60a4e3

📥 Commits

Reviewing files that changed from the base of the PR and between 558db4f and b38ffe8.

⛔ Files ignored due to path filters (4)
  • openapi/generated_openapi/zz_generated.openapi.go is excluded by !openapi/**
  • openapi/openapi.json is excluded by !openapi/**
  • operator/v1alpha1/zz_generated.crd-manifests/0000_30_cluster-api_01_clusterapis.crd.yaml is excluded by !**/zz_generated.crd-manifests/*
  • operator/v1alpha1/zz_generated.featuregated-crd-manifests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
📒 Files selected for processing (5)
  • operator/v1alpha1/tests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml
  • operator/v1alpha1/types_clusterapi.go
  • operator/v1alpha1/zz_generated.deepcopy.go
  • operator/v1alpha1/zz_generated.swagger_doc_generated.go
  • payload-manifests/crds/0000_30_cluster-api_01_clusterapis.crd.yaml
🚧 Files skipped from review as they are similar to previous changes (3)
  • operator/v1alpha1/zz_generated.swagger_doc_generated.go
  • payload-manifests/crds/0000_30_cluster-api_01_clusterapis.crd.yaml
  • operator/v1alpha1/zz_generated.deepcopy.go

Copy link
Copy Markdown
Contributor

@everettraven everettraven left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few comments. Aside from these, this looks pretty good to me.

@damdo
Copy link
Copy Markdown
Member

damdo commented Apr 13, 2026

/pipeline auto

@openshift-merge-bot
Copy link
Copy Markdown
Contributor

Pipeline controller notification

The pipeline-auto label has been added to this PR. Second-stage tests will be triggered automatically when all first-stage tests pass.

@openshift-merge-bot
Copy link
Copy Markdown
Contributor

Scheduling tests matching the pipeline_run_if_changed or not excluded by pipeline_skip_if_only_changed parameters:
/test e2e-aws-ovn
/test e2e-aws-ovn-hypershift
/test e2e-aws-ovn-hypershift-conformance
/test e2e-aws-ovn-techpreview
/test e2e-aws-serial-1of2
/test e2e-aws-serial-2of2
/test e2e-aws-serial-techpreview-1of2
/test e2e-aws-serial-techpreview-2of2
/test e2e-azure
/test e2e-gcp
/test e2e-upgrade
/test e2e-upgrade-out-of-change
/test minor-e2e-upgrade-minor

@openshift-merge-bot
Copy link
Copy Markdown
Contributor

Tests from second stage were triggered manually. Pipeline can be controlled only manually, until HEAD changes. Use command to trigger second stage.

@damdo
Copy link
Copy Markdown
Member

damdo commented Apr 13, 2026

/retest

@openshift-merge-bot
Copy link
Copy Markdown
Contributor

Tests from second stage were triggered manually. Pipeline can be controlled only manually, until HEAD changes. Use command to trigger second stage.

@damdo
Copy link
Copy Markdown
Member

damdo commented Apr 14, 2026

/pipeline required

@openshift-merge-bot
Copy link
Copy Markdown
Contributor

Scheduling tests matching the pipeline_run_if_changed or not excluded by pipeline_skip_if_only_changed parameters:
/test e2e-aws-ovn
/test e2e-aws-ovn-hypershift
/test e2e-aws-ovn-hypershift-conformance
/test e2e-aws-ovn-techpreview
/test e2e-aws-serial-1of2
/test e2e-aws-serial-2of2
/test e2e-aws-serial-techpreview-1of2
/test e2e-aws-serial-techpreview-2of2
/test e2e-azure
/test e2e-gcp
/test e2e-upgrade
/test e2e-upgrade-out-of-change
/test minor-e2e-upgrade-minor

@openshift-merge-bot
Copy link
Copy Markdown
Contributor

Tests from second stage were triggered manually. Pipeline can be controlled only manually, until HEAD changes. Use command to trigger second stage.

Copy link
Copy Markdown
Contributor

@everettraven everettraven left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm

@openshift-ci openshift-ci bot added the lgtm Indicates that a PR is ready to be merged. label Apr 14, 2026
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci bot commented Apr 14, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: everettraven

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Apr 14, 2026
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci bot commented Apr 14, 2026

@mdbooth: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/e2e-aws-serial-techpreview-2of2 59fa653 link true /test e2e-aws-serial-techpreview-2of2
ci/prow/e2e-azure 59fa653 link true /test e2e-azure
ci/prow/e2e-aws-ovn 59fa653 link true /test e2e-aws-ovn
ci/prow/e2e-gcp 59fa653 link true /test e2e-gcp
ci/prow/e2e-aws-ovn-hypershift-conformance 59fa653 link true /test e2e-aws-ovn-hypershift-conformance
ci/prow/e2e-aws-ovn-techpreview 59fa653 link true /test e2e-aws-ovn-techpreview
ci/prow/e2e-aws-serial-techpreview-1of2 59fa653 link true /test e2e-aws-serial-techpreview-1of2

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@openshift-merge-bot
Copy link
Copy Markdown
Contributor

Tests from second stage were triggered manually. Pipeline can be controlled only manually, until HEAD changes. Use command to trigger second stage.

@damdo
Copy link
Copy Markdown
Member

damdo commented Apr 15, 2026

/retest

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. lgtm Indicates that a PR is ready to be merged. pipeline-auto size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants