Skip to content

OCPCLOUD-3647: Move TLS substitution variables out of the Revision - #637

Open
mdbooth wants to merge 11 commits into
openshift:mainfrom
openshift-cloud-team:manifesttransformers/050-tls
Open

OCPCLOUD-3647: Move TLS substitution variables out of the Revision#637
mdbooth wants to merge 11 commits into
openshift:mainfrom
openshift-cloud-team:manifesttransformers/050-tls

Conversation

@mdbooth

@mdbooth mdbooth commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

TLS substitutions are moved out of the revision. They are now a pure runtime concern, meaning they rollout without creating a new revision. If the latest revision cannot be installed, they will be still applied to a prior revision.

TODO:

/hold

Summary by CodeRabbit

  • New Features

    • Added install-time manifest transformations (environment substitution, managed-by labeling, and adopting existing resources).
    • Introduced TLS-aware manifest transformations during installation.
    • Validates configured transformers up front and blocks installation on invalid settings.
  • Bug Fixes

    • Improved transformation error reporting (including object-level context) and aggregation.
    • Ensures skipped/failed transformations don’t produce or chain into installation phases.
  • Tests

    • Added end-to-end and unit test coverage for transformer behavior, phase construction (including CRD vs non-CRD splitting), substitution precedence, validation, and transformer drift handling.

mdbooth added 5 commits July 28, 2026 18:09
We were previously pre-converting all API revisions into installer
revisions. The purpose of this was to assemble all related objects
before reconciliation. However, we weren't actually writing them so this
served no purpose. We take the opportunity to remove the unnecessary
complexity.
This allows us to add code to component generation which can fail.
This is now handled in toBoxcutterRevision
ManifestTransformer is a common interface for transformations on
resource manifests. It is intended to cover all existing ad-hoc
transformations, although they are not implemented in this change.
@openshift-merge-bot

Copy link
Copy Markdown
Contributor

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-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jul 29, 2026
@openshift-ci-robot

openshift-ci-robot commented Jul 29, 2026

Copy link
Copy Markdown

@mdbooth: This pull request references OCPCLOUD-3647 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 "5.0.0" version, but no target version was set.

Details

In response to this:

TLS substitutions are moved out of the revision. They are now a pure runtime concern, meaning they rollout without creating a new revision. If the latest revision cannot be installed, they will be still applied to a prior revision.

TODO:

/hold

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 openshift-ci Bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Walkthrough

This PR replaces rendered revisions with parsed revisions and introduces install-time manifest transformers for envsubst expansion, managed-by labels, and adopt-existing handling. Controller reconciliation, Boxcutter phase construction, validation, TLS-aware wiring, and related tests are updated.

Changes

Manifest processing pipeline

Layer / File(s) Summary
Parsed revision model and content handling
pkg/revisiongenerator/revision.go, pkg/revisiongenerator/*_test.go, pkg/controllers/installer/helpers_test.go
Parsed revisions retain unexpanded unstructured objects and substitutions, with updated content-ID, API conversion, and install-revision behavior.
Manifest transformer contracts and implementations
pkg/manifesttransformer/*
Adds transformer validation, recursive envsubst expansion, managed-by labeling, and adopt-existing annotation processing.
Transformed Boxcutter phase construction
pkg/controllers/installer/boxcutter.go, pkg/controllers/installer/boxcutter_test.go
Applies ordered transformers to CRD and non-CRD objects, supports skipping and reconcile options, and aggregates transformation errors.
Controller reconciliation and wiring
cmd/capi-installer/main.go, pkg/controllers/installer/*, pkg/controllers/revision/*
Passes transformer chains through controllers, validates revisions, reconciles API revisions directly, and updates drift and validation tests.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Installer
  participant RevisionController
  participant RevisionReconciler
  participant ManifestTransformers
  participant Boxcutter
  Installer->>RevisionController: configure transformer chain
  RevisionController->>RevisionReconciler: reconcile API revision
  RevisionReconciler->>ManifestTransformers: validate and transform objects
  ManifestTransformers-->>RevisionReconciler: transformed objects and reconcile options
  RevisionReconciler->>Boxcutter: construct CRD and component phases
Loading

Possibly related PRs

Suggested reviewers: damdo, racheljpg

🚥 Pre-merge checks | ✅ 12 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Structure And Quality ⚠️ Warning Revision Ginkgo helpers/tests still use Eventually without explicit timeouts (e.g. waitForConditions/waitForProgressingFalse and manager stop), so waits can hang. Add explicit WithTimeout/defaultEventuallyTimeout (or suite defaults) to these Eventually calls and cleanup waits, matching the bounded pattern used in installer tests.
Microshift Test Compatibility ⚠️ Warning New Ginkgo test RevisionController error handling reads configv1.ClusterOperator with no MicroShift skip/tag; config.openshift.io APIs aren’t served on MicroShift. Add [apigroup:config.openshift.io] or [Skipped:MicroShift], or guard with exutil.IsMicroShiftCluster(); alternatively avoid OpenShift API types in the test.
✅ Passed checks (12 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: TLS substitution variables were moved out of revision generation into runtime manifest handling.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed All changed Ginkgo titles are static string literals; no dynamic values or generated identifiers appear in test names.
Single Node Openshift (Sno) Test Compatibility ✅ Passed New Ginkgo tests are envtest/controller tests; they only manage ConfigMaps/ClusterAPI state and show no multi-node or topology assumptions.
Topology-Aware Scheduling Compatibility ✅ Passed No new scheduling constraints in touched code; changes are transformer/controller wiring only, and searches found no node selectors, affinities, topology spreads, replicas, or PDB logic.
Ote Binary Stdout Contract ✅ Passed Scanned cmd/capi-installer/main.go and touched suite setup files; found no fmt.Print*, os.Stdout, or stdout-bound klog/log calls in process-level code.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed PASS: The new Ginkgo tests are envtest/controller tests, and I found no hardcoded IPv4, localhost, ParseIP/CIDR, JoinHostPort, or external internet dependencies.
No-Weak-Crypto ✅ Passed No MD5/SHA1/DES/RC4/3DES/Blowfish/ECB or secret-compare usage appears in touched files; TLS code only derives names from existing tls.Config.
Container-Privileges ✅ Passed No changed manifest or code paths set privileged/hostPID/hostNetwork/hostIPC/SYS_ADMIN/allowPrivilegeEscalation, and deployment YAMLs show no securityContext escalation settings.
No-Sensitive-Data-In-Logs ✅ Passed No new log statements expose secrets/PII; changed code only adds error text, and the existing provider-profile imageRef log predates this PR.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@openshift-ci
openshift-ci Bot requested review from damdo and racheljpg July 29, 2026 14:56
@openshift-ci

openshift-ci Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign mdbooth for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found 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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/revisiongenerator/revision.go (1)

396-412: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Skip empty manifests before appending. Manifests() only skips separator-only empties; comment-only or whitespace-only documents still unmarshal to a nil Object, which then flows through validation/boxcutter as a GVK-less object (/).

🤖 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 `@pkg/revisiongenerator/revision.go` around lines 396 - 412, Update the
manifest loop around providerProfile.Manifests() to detect and skip documents
that are empty after replacing SelfImageRef, including whitespace-only and
comment-only YAML, before unmarshalling or appending. Preserve processing for
manifests containing actual resources, and ensure nil or empty unstructured
objects never reach component.objects.
🧹 Nitpick comments (7)
pkg/controllers/installer/installer_controller.go (1)

142-142: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stale comment above this line.

The preceding comment still says the label is "applied by the revision generator"; it is now applied by ManagedByTransformer. Label key value is unchanged, so cache behaviour is preserved.

🤖 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 `@pkg/controllers/installer/installer_controller.go` at line 142, Update the
preceding comment near the managedByReq construction to state that the label is
applied by ManagedByTransformer instead of the revision generator. Leave the
label key and requirement logic unchanged.
pkg/controllers/installer/revision_reconciler.go (1)

146-152: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Per-revision parsing now happens on every reconcile pass.

Each revision in the recursion re-reads and re-parses provider manifests and recomputes the content ID, for reconcile and teardown alike. With several retained revisions and frequent watch-driven reconciles this is repeated I/O plus SHA-256 work on the reconcile thread. Consider memoizing installer revisions by (name, contentID) on the controller.

Also applies to: 172-172

🤖 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 `@pkg/controllers/installer/revision_reconciler.go` around lines 146 - 152,
Update revisionReconciler.reconcileRevisions and the related per-revision
processing to memoize parsed provider manifests and computed content IDs on the
controller, keyed by revision name and contentID. Reuse cached results across
reconcile and teardown passes, while invalidating or replacing entries when
either key changes.
pkg/controllers/revision/revision_controller_test.go (1)

573-595: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert on the Reconcile return instead of discarding it.

Silently dropping both return values means the test still passes if Reconcile starts returning an unexpected (retryable) error while conditions happen to match.

💚 Suggested change
-		_, _ = r.Reconcile(ctx, reconcile.Request{NamespacedName: client.ObjectKey{Name: "cluster"}})
+		_, err := r.Reconcile(ctx, reconcile.Request{NamespacedName: client.ObjectKey{Name: "cluster"}})
+		Expect(err).NotTo(HaveOccurred(), "non-retryable errors are reported via conditions, not returned")
🤖 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 `@pkg/controllers/revision/revision_controller_test.go` around lines 573 - 595,
The test case around the `Reconcile` call should assert its returned result
instead of discarding both return values. Capture the `Reconcile` response and
error, then verify the expected non-retryable outcome while retaining the
existing ClusterOperator condition assertions.
pkg/controllers/installer/boxcutter.go (1)

109-133: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Phase is appended even when every object was skipped.

The len(objects) == 0 early return signals empty phases are undesirable, but if all objects are skipped by a transformer the function still appends a zero-object phase. Skip the append when transformedObjects is empty.

♻️ Suggested change
+	if len(transformedObjects) == 0 {
+		return phases, errors.Join(allErrs...)
+	}
+
 	allOpts := slices.Concat(probeOpts, xfmrOpts)
 	bcPhase := boxcutter.NewPhase(name, util.SliceMap(transformedObjects, toClientObject)).WithReconcileOptions(allOpts...)
🤖 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 `@pkg/controllers/installer/boxcutter.go` around lines 109 - 133, Avoid
appending an empty phase when transformers skip every object: after the
transformation loop in the phase-building flow, check whether transformedObjects
is empty and return the existing phases with joined errors before constructing
or appending bcPhase. Preserve accumulated transformer errors and the current
behavior when at least one transformed object remains.
pkg/revisiongenerator/revision.go (1)

354-364: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

opts = append(opts, ...) mutates the caller's variadic slice.

If a caller ever passes a named slice with spare capacity, this write is visible to them. Prefer building a new slice.

♻️ Suggested change
-	opts = append(opts, WithManifestSubstitutions(apiSubs))
-
-	parsed, err := newParsedRevision(matched, opts...)
+	allOpts := append(slices.Clone(opts), WithManifestSubstitutions(apiSubs))
+
+	parsed, err := newParsedRevision(matched, allOpts...)
🤖 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 `@pkg/revisiongenerator/revision.go` around lines 354 - 364, Update the options
assembly before calling newParsedRevision so adding WithManifestSubstitutions
does not append into the caller-owned variadic slice when it has spare capacity.
Create an independent options slice, preserving the existing options and
appending the manifest substitutions to that copy before passing it to
newParsedRevision.
pkg/manifesttransformer/envsubst_transformer.go (1)

44-48: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Static substitutions are silently ignored until WithRevision is called.

mergedSubs is only populated in WithRevision, so a transformer used directly from NewEnvsubstTransformer expands everything to empty strings — including the TLS substitutions this PR moves to runtime. Seeding mergedSubs with staticSubs makes the base transformer behave correctly regardless of call order.

♻️ Proposed change
 func NewEnvsubstTransformer(staticSubs map[string]string) *EnvsubstTransformer {
 	return &EnvsubstTransformer{
 		staticSubs: maps.Clone(staticSubs),
+		mergedSubs: maps.Clone(staticSubs),
 	}
 }
🤖 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 `@pkg/manifesttransformer/envsubst_transformer.go` around lines 44 - 48,
Initialize mergedSubs from staticSubs in NewEnvsubstTransformer, while retaining
the cloned staticSubs map for later revision merging. Ensure direct use of the
newly constructed EnvsubstTransformer applies static substitutions before
WithRevision is called.
pkg/manifesttransformer/validate_test.go (1)

134-181: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider a shared test fake for the new transformer/revision contracts. Each package re-implements near-identical ManifestTransformer and ParsedRevision doubles, so every future interface change requires three parallel edits.

  • pkg/manifesttransformer/validate_test.go#L134-L181: extract stubTransformer/fakeComponent/fakeRevision into an exported test helper package (e.g. under testutils) and consume it here.
  • pkg/controllers/revision/helpers_test.go#L192-L229: replace the local stubTransformer/fakeRevision copies with the shared helpers.
  • pkg/controllers/installer/boxcutter_test.go#L68-L115: replace the local stubTransformer with the shared one, keeping only the installer-specific fnTransformer.
🤖 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 `@pkg/manifesttransformer/validate_test.go` around lines 134 - 181, Extract the
shared stubTransformer, fakeComponent, and fakeRevision implementations into an
exported test helper package, preserving their current contract behavior. Update
pkg/manifesttransformer/validate_test.go lines 134-181 and
pkg/controllers/revision/helpers_test.go lines 192-229 to use the shared
helpers, and update pkg/controllers/installer/boxcutter_test.go lines 68-115 to
use the shared stubTransformer while retaining only its installer-specific
fnTransformer.
🤖 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 `@pkg/controllers/installer/revision_reconciler.go`:
- Around line 171-188: Update the revisiongenerator.NewInstallerRevisionFromAPI
error path in reconcileRevision to return the formatted error message instead of
an empty string, matching the other failure paths. Apply the same change to the
corresponding construction failure paths in teardownRevision so ClusterOperator
status preserves the error reason, including content-ID mismatches.

---

Outside diff comments:
In `@pkg/revisiongenerator/revision.go`:
- Around line 396-412: Update the manifest loop around
providerProfile.Manifests() to detect and skip documents that are empty after
replacing SelfImageRef, including whitespace-only and comment-only YAML, before
unmarshalling or appending. Preserve processing for manifests containing actual
resources, and ensure nil or empty unstructured objects never reach
component.objects.

---

Nitpick comments:
In `@pkg/controllers/installer/boxcutter.go`:
- Around line 109-133: Avoid appending an empty phase when transformers skip
every object: after the transformation loop in the phase-building flow, check
whether transformedObjects is empty and return the existing phases with joined
errors before constructing or appending bcPhase. Preserve accumulated
transformer errors and the current behavior when at least one transformed object
remains.

In `@pkg/controllers/installer/installer_controller.go`:
- Line 142: Update the preceding comment near the managedByReq construction to
state that the label is applied by ManagedByTransformer instead of the revision
generator. Leave the label key and requirement logic unchanged.

In `@pkg/controllers/installer/revision_reconciler.go`:
- Around line 146-152: Update revisionReconciler.reconcileRevisions and the
related per-revision processing to memoize parsed provider manifests and
computed content IDs on the controller, keyed by revision name and contentID.
Reuse cached results across reconcile and teardown passes, while invalidating or
replacing entries when either key changes.

In `@pkg/controllers/revision/revision_controller_test.go`:
- Around line 573-595: The test case around the `Reconcile` call should assert
its returned result instead of discarding both return values. Capture the
`Reconcile` response and error, then verify the expected non-retryable outcome
while retaining the existing ClusterOperator condition assertions.

In `@pkg/manifesttransformer/envsubst_transformer.go`:
- Around line 44-48: Initialize mergedSubs from staticSubs in
NewEnvsubstTransformer, while retaining the cloned staticSubs map for later
revision merging. Ensure direct use of the newly constructed EnvsubstTransformer
applies static substitutions before WithRevision is called.

In `@pkg/manifesttransformer/validate_test.go`:
- Around line 134-181: Extract the shared stubTransformer, fakeComponent, and
fakeRevision implementations into an exported test helper package, preserving
their current contract behavior. Update pkg/manifesttransformer/validate_test.go
lines 134-181 and pkg/controllers/revision/helpers_test.go lines 192-229 to use
the shared helpers, and update pkg/controllers/installer/boxcutter_test.go lines
68-115 to use the shared stubTransformer while retaining only its
installer-specific fnTransformer.

In `@pkg/revisiongenerator/revision.go`:
- Around line 354-364: Update the options assembly before calling
newParsedRevision so adding WithManifestSubstitutions does not append into the
caller-owned variadic slice when it has spare capacity. Create an independent
options slice, preserving the existing options and appending the manifest
substitutions to that copy before passing it to newParsedRevision.
🪄 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), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: a975c5b8-a8e4-445b-93bf-07db5482e776

📥 Commits

Reviewing files that changed from the base of the PR and between d6c24ca and 722d7c2.

📒 Files selected for processing (27)
  • cmd/capi-installer/main.go
  • pkg/controllers/installer/boxcutter.go
  • pkg/controllers/installer/boxcutter_test.go
  • pkg/controllers/installer/helpers_test.go
  • pkg/controllers/installer/installer_controller.go
  • pkg/controllers/installer/revision_reconciler.go
  • pkg/controllers/installer/revision_reconciler_test.go
  • pkg/controllers/installer/suite_test.go
  • pkg/controllers/revision/helpers_test.go
  • pkg/controllers/revision/revision_controller.go
  • pkg/controllers/revision/revision_controller_test.go
  • pkg/manifesttransformer/adopt_existing_transformer.go
  • pkg/manifesttransformer/adopt_existing_transformer_test.go
  • pkg/manifesttransformer/envsubst_transformer.go
  • pkg/manifesttransformer/envsubst_transformer_test.go
  • pkg/manifesttransformer/managed_by_transformer.go
  • pkg/manifesttransformer/managed_by_transformer_test.go
  • pkg/manifesttransformer/manifest_transformer.go
  • pkg/manifesttransformer/validate.go
  • pkg/manifesttransformer/validate_test.go
  • pkg/revisiongenerator/helpers_test.go
  • pkg/revisiongenerator/revision.go
  • pkg/revisiongenerator/revision_test.go
  • pkg/revisiongenerator/transform.go
  • pkg/revisiongenerator/transform_test.go
  • pkg/revisiongenerator/validate.go
  • pkg/revisiongenerator/validate_test.go
💤 Files with no reviewable changes (3)
  • pkg/revisiongenerator/transform_test.go
  • pkg/revisiongenerator/validate_test.go
  • pkg/revisiongenerator/transform.go

Comment on lines +171 to +188
func (r *revisionReconciler) reconcileRevision(ctx context.Context, apiRevision operatorv1alpha1.ClusterAPIInstallerRevision) (bool, string, error) {
revision, err := revisiongenerator.NewInstallerRevisionFromAPI(apiRevision, r.providerProfiles)
if err != nil {
return false, "", fmt.Errorf("error creating installer revision from API revision %s: %w", apiRevision.Name, reconcile.TerminalError(err))
}

// Defence in depth: the revision controller validates transformers before
// writing a new revision, but a revision read back from the API could in
// principle be malformed (e.g. hand-edited), so validate again here.
if err := manifesttransformer.ValidateTransformers(r.transformers, revision); err != nil {
err = fmt.Errorf("validating revision %s: %w", revision.RevisionName(), reconcile.TerminalError(err))
return false, err.Error(), err
}

revision := conv.revision
bcRevision, err := toBoxcutterRevision(ctx, revision, r.transformers, r.collectObjects)
if err != nil {
return false, err.Error(), reconcile.TerminalError(fmt.Errorf("building boxcutter revision %s: %w", revision.RevisionName(), err))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Construction failure returns an empty status message.

Line 174 returns "" as the message while every other failure path returns err.Error(), so the ClusterOperator status loses the reason when a revision can't be constructed (e.g. content-ID mismatch). Same pattern at Line 349/357 in teardownRevision.

🐛 Proposed fix
 	revision, err := revisiongenerator.NewInstallerRevisionFromAPI(apiRevision, r.providerProfiles)
 	if err != nil {
-		return false, "", fmt.Errorf("error creating installer revision from API revision %s: %w", apiRevision.Name, reconcile.TerminalError(err))
+		err = fmt.Errorf("error creating installer revision from API revision %s: %w", apiRevision.Name, reconcile.TerminalError(err))
+		return false, err.Error(), err
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (r *revisionReconciler) reconcileRevision(ctx context.Context, apiRevision operatorv1alpha1.ClusterAPIInstallerRevision) (bool, string, error) {
revision, err := revisiongenerator.NewInstallerRevisionFromAPI(apiRevision, r.providerProfiles)
if err != nil {
return false, "", fmt.Errorf("error creating installer revision from API revision %s: %w", apiRevision.Name, reconcile.TerminalError(err))
}
// Defence in depth: the revision controller validates transformers before
// writing a new revision, but a revision read back from the API could in
// principle be malformed (e.g. hand-edited), so validate again here.
if err := manifesttransformer.ValidateTransformers(r.transformers, revision); err != nil {
err = fmt.Errorf("validating revision %s: %w", revision.RevisionName(), reconcile.TerminalError(err))
return false, err.Error(), err
}
revision := conv.revision
bcRevision, err := toBoxcutterRevision(ctx, revision, r.transformers, r.collectObjects)
if err != nil {
return false, err.Error(), reconcile.TerminalError(fmt.Errorf("building boxcutter revision %s: %w", revision.RevisionName(), err))
}
func (r *revisionReconciler) reconcileRevision(ctx context.Context, apiRevision operatorv1alpha1.ClusterAPIInstallerRevision) (bool, string, error) {
revision, err := revisiongenerator.NewInstallerRevisionFromAPI(apiRevision, r.providerProfiles)
if err != nil {
err = fmt.Errorf("error creating installer revision from API revision %s: %w", apiRevision.Name, reconcile.TerminalError(err))
return false, err.Error(), err
}
// Defence in depth: the revision controller validates transformers before
// writing a new revision, but a revision read back from the API could in
// principle be malformed (e.g. hand-edited), so validate again here.
if err := manifesttransformer.ValidateTransformers(r.transformers, revision); err != nil {
err = fmt.Errorf("validating revision %s: %w", revision.RevisionName(), reconcile.TerminalError(err))
return false, err.Error(), err
}
bcRevision, err := toBoxcutterRevision(ctx, revision, r.transformers, r.collectObjects)
if err != nil {
return false, err.Error(), reconcile.TerminalError(fmt.Errorf("building boxcutter revision %s: %w", revision.RevisionName(), err))
}
🤖 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 `@pkg/controllers/installer/revision_reconciler.go` around lines 171 - 188,
Update the revisiongenerator.NewInstallerRevisionFromAPI error path in
reconcileRevision to return the formatted error message instead of an empty
string, matching the other failure paths. Apply the same change to the
corresponding construction failure paths in teardownRevision so ClusterOperator
status preserves the error reason, including content-ID mismatches.

mdbooth added 6 commits July 29, 2026 16:14
Adds EnvsubstTransformer and ManagedByTransformer, moving envsubst
expansion and managed-by labelling out of the revision generator and
into install-time ManifestTransformers. The revision generator now
stores manifests with envsubst variables unexpanded, and records
substitutions on the revision for use by EnvsubstTransformer.
We moved manifest rendering (envsubst expansion, managed-by labelling)
out to install-time ManifestTransformers, so the old "Rendered" name
is now confusing. Renamed RenderedRevision/RenderedComponent (and
their constructors and unexported implementations) to
ParsedRevision/ParsedComponent throughout, adapted to the
manifesttransformer package.
This makes them a pure runtime substitution which doesn't require a
rollout.
@mdbooth
mdbooth force-pushed the manifesttransformers/050-tls branch from 722d7c2 to a44f32d Compare July 29, 2026 15:23

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/revisiongenerator/revision.go (1)

51-93: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Update the test transformer to the renamed revision interfaces. pkg/controllers/installer/testvalue_transformer_test.go still refers to revisiongenerator.RenderedRevision and revisiongenerator.RenderedComponent; switch these to revisiongenerator.ParsedRevision and revisiongenerator.ParsedComponent or the package won’t compile.

🤖 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 `@pkg/revisiongenerator/revision.go` around lines 51 - 93, Update the test
transformer in installer test values to use revisiongenerator.ParsedRevision and
revisiongenerator.ParsedComponent instead of the removed RenderedRevision and
RenderedComponent interfaces, preserving the existing transformer behavior.

Source: Linters/SAST tools

🧹 Nitpick comments (2)
pkg/controllers/installer/installer_controller_test.go (2)

198-241: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the repository’s required Ginkgo test style.

The new It titles should use the should... form, and each scenario should expose setup, mutation, trigger, and verification steps through By().

As per coding guidelines, Ginkgo tests should use descriptive should... names and By() for test steps.

🤖 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 `@pkg/controllers/installer/installer_controller_test.go` around lines 198 -
241, Update the two `It` blocks in the `ManifestTransformer Drift` context to
use descriptive titles beginning with “should”. Add `By()` steps that clearly
identify setup, mutation, reconciliation trigger, and verification in each
scenario, while preserving the existing test behavior and assertions.

Source: Coding guidelines


203-218: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Assert that drift correction does not create a new revision.

This test verifies the updated annotation but not the “without a new revision” contract stated in its title. Capture the revision state before changing testAnnotationValue and assert that the revision name/status remains unchanged after triggerReconcile().

The PR objective specifically requires transformer changes to roll out without creating a new Revision.

🤖 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 `@pkg/controllers/installer/installer_controller_test.go` around lines 203 -
218, Update the test identified by “re-applies an updated transformer output
without a new revision” to capture the current revision name and status after
the initial successful reconciliation, before changing testAnnotationValue.
After triggerReconcile() and verifying the updated annotation, assert that the
revision name and status are unchanged, preserving the no-new-Revision contract.
🤖 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 `@pkg/controllers/installer/testvalue_transformer_test.go`:
- Around line 72-79: Update testValueTransformer.WithRevision and WithComponent
to accept revisiongenerator.ParsedRevision and revisiongenerator.ParsedComponent
respectively, replacing the nonexistent RenderedRevision and RenderedComponent
types while preserving the existing no-op behavior and ManifestTransformer
return values.

---

Outside diff comments:
In `@pkg/revisiongenerator/revision.go`:
- Around line 51-93: Update the test transformer in installer test values to use
revisiongenerator.ParsedRevision and revisiongenerator.ParsedComponent instead
of the removed RenderedRevision and RenderedComponent interfaces, preserving the
existing transformer behavior.

---

Nitpick comments:
In `@pkg/controllers/installer/installer_controller_test.go`:
- Around line 198-241: Update the two `It` blocks in the `ManifestTransformer
Drift` context to use descriptive titles beginning with “should”. Add `By()`
steps that clearly identify setup, mutation, reconciliation trigger, and
verification in each scenario, while preserving the existing test behavior and
assertions.
- Around line 203-218: Update the test identified by “re-applies an updated
transformer output without a new revision” to capture the current revision name
and status after the initial successful reconciliation, before changing
testAnnotationValue. After triggerReconcile() and verifying the updated
annotation, assert that the revision name and status are unchanged, preserving
the no-new-Revision contract.
🪄 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), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: d4d9425d-988e-4207-bc26-0a73b2fd5a8b

📥 Commits

Reviewing files that changed from the base of the PR and between 722d7c2 and a44f32d.

📒 Files selected for processing (28)
  • cmd/capi-installer/main.go
  • pkg/controllers/installer/boxcutter.go
  • pkg/controllers/installer/boxcutter_test.go
  • pkg/controllers/installer/helpers_test.go
  • pkg/controllers/installer/installer_controller.go
  • pkg/controllers/installer/installer_controller_test.go
  • pkg/controllers/installer/revision_reconciler_test.go
  • pkg/controllers/installer/suite_test.go
  • pkg/controllers/installer/testvalue_transformer_test.go
  • pkg/controllers/revision/helpers_test.go
  • pkg/controllers/revision/revision_controller.go
  • pkg/controllers/revision/revision_controller_test.go
  • pkg/manifesttransformer/adopt_existing_transformer.go
  • pkg/manifesttransformer/adopt_existing_transformer_test.go
  • pkg/manifesttransformer/envsubst_transformer.go
  • pkg/manifesttransformer/envsubst_transformer_test.go
  • pkg/manifesttransformer/managed_by_transformer.go
  • pkg/manifesttransformer/managed_by_transformer_test.go
  • pkg/manifesttransformer/manifest_transformer.go
  • pkg/manifesttransformer/validate.go
  • pkg/manifesttransformer/validate_test.go
  • pkg/revisiongenerator/helpers_test.go
  • pkg/revisiongenerator/revision.go
  • pkg/revisiongenerator/revision_test.go
  • pkg/revisiongenerator/transform.go
  • pkg/revisiongenerator/transform_test.go
  • pkg/revisiongenerator/validate.go
  • pkg/revisiongenerator/validate_test.go
💤 Files with no reviewable changes (3)
  • pkg/revisiongenerator/transform.go
  • pkg/revisiongenerator/transform_test.go
  • pkg/revisiongenerator/validate_test.go
🚧 Files skipped from review as they are similar to previous changes (15)
  • pkg/manifesttransformer/manifest_transformer.go
  • pkg/controllers/installer/suite_test.go
  • pkg/manifesttransformer/validate.go
  • pkg/revisiongenerator/helpers_test.go
  • pkg/controllers/installer/helpers_test.go
  • cmd/capi-installer/main.go
  • pkg/manifesttransformer/validate_test.go
  • pkg/controllers/installer/boxcutter.go
  • pkg/controllers/installer/installer_controller.go
  • pkg/controllers/installer/revision_reconciler_test.go
  • pkg/revisiongenerator/validate.go
  • pkg/controllers/installer/boxcutter_test.go
  • pkg/controllers/revision/revision_controller.go
  • pkg/revisiongenerator/revision_test.go
  • pkg/controllers/revision/revision_controller_test.go

Comment on lines +72 to +79
func (t testValueTransformer) WithRevision(_ context.Context, _ revisiongenerator.RenderedRevision) manifesttransformer.ManifestTransformer {
return t
}

// WithComponent implements manifesttransformer.ManifestTransformer. It is a
// no-op; testValueTransformer does not need component context.
func (t testValueTransformer) WithComponent(_ context.Context, _ revisiongenerator.RenderedComponent) manifesttransformer.ManifestTransformer {
return t

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n 'WithRevision|WithComponent|ParsedRevision|ParsedComponent|RenderedRevision|RenderedComponent' \
  pkg/manifesttransformer/manifest_transformer.go \
  pkg/controllers/installer/testvalue_transformer_test.go \
  pkg/revisiongenerator

Repository: openshift/cluster-capi-operator

Length of output: 7405


Use ParsedRevision and ParsedComponent in the transformer methods. revisiongenerator.RenderedRevision and revisiongenerator.RenderedComponent don’t exist, so this test file won’t compile.

🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 72-72: : # github.com/openshift/cluster-capi-operator/pkg/controllers/installer [github.com/openshift/cluster-capi-operator/pkg/controllers/installer.test]
pkg/controllers/installer/testvalue_transformer_test.go:72:83: undefined: revisiongenerator.RenderedRevision
pkg/controllers/installer/testvalue_transformer_test.go:78:84: undefined: revisiongenerator.RenderedComponent

(typecheck)

🤖 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 `@pkg/controllers/installer/testvalue_transformer_test.go` around lines 72 -
79, Update testValueTransformer.WithRevision and WithComponent to accept
revisiongenerator.ParsedRevision and revisiongenerator.ParsedComponent
respectively, replacing the nonexistent RenderedRevision and RenderedComponent
types while preserving the existing no-op behavior and ManifestTransformer
return values.

Source: Linters/SAST tools

@openshift-ci

openshift-ci Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

@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/lint a44f32d link true /test lint
ci/prow/unit a44f32d link true /test unit

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.

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

Labels

do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants