Skip to content

OCPCLOUD-3647: Move envsubst transformations to use ManifestTransformer - #636

Open
mdbooth wants to merge 10 commits into
openshift:mainfrom
openshift-cloud-team:manifesttransformers/040-envsubst
Open

OCPCLOUD-3647: Move envsubst transformations to use ManifestTransformer#636
mdbooth wants to merge 10 commits into
openshift:mainfrom
openshift-cloud-team:manifesttransformers/040-envsubst

Conversation

@mdbooth

@mdbooth mdbooth commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

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.

Moves the hardcoded EXP_BOOTSTRAP_FORMAT_IGNITION substitution to manifest substitutions to ensure it upgrades cleanly when we remove it later.

Renames RenderedRevision to ParsedRevision, as it no longer does rendering.

TODO:

/hold

Summary by CodeRabbit

  • New Features
    • Added configurable manifest transformations, including environment-variable substitution, managed-by labeling, and opt-in adoption of pre-existing resources.
    • Adoption can omit collision reporting when explicitly enabled.
  • Bug Fixes
    • Invalid adoption settings are rejected early with clearer, non-retryable error details.
    • Transformer validation failures now properly surface in controller status.
  • Reliability
    • Manifest transformation and revision conversion now run in a consistent, on-demand manner.
    • Transformation errors are aggregated and reported with component/object context.

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:

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.

Moves the hardcoded EXP_BOOTSTRAP_FORMAT_IGNITION substitution to manifest substitutions to ensure it upgrades cleanly when we remove it later.

Renames RenderedRevision to ParsedRevision, as it no longer does rendering.

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.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Walkthrough

The revision model now stores parsed manifests and substitutions. A shared manifest transformer pipeline validates revisions, transforms install objects, applies reconcile options, supports adoption behavior, and is wired through revision and installer controllers.

Changes

Manifest transformation pipeline

Layer / File(s) Summary
Parsed revision model
pkg/revisiongenerator/revision.go, pkg/revisiongenerator/revision_test.go, pkg/revisiongenerator/helpers_test.go
Parsed revisions replace rendered revisions, retain substitutions, include unified object lists, and compute API-verifiable content IDs.
Transformer contracts and implementations
pkg/manifesttransformer/*
Adds the transformer interface plus envsubst, managed-by, adopt-existing, and aggregated validation implementations with unit coverage.
Installer phase transformation
pkg/controllers/installer/boxcutter.go, pkg/controllers/installer/revision_reconciler.go, pkg/controllers/installer/*_test.go
Transforms CRDs and regular objects while constructing phases, supports skipped objects and reconcile options, aggregates errors, and lazily converts API revisions during reconciliation.
Controller wiring and validation
cmd/capi-installer/main.go, pkg/controllers/installer/installer_controller.go, pkg/controllers/revision/revision_controller.go, pkg/controllers/*/*_test.go
Passes shared transformers through controller setup, validates desired revisions, updates managed-object tracking, and adds the ignition substitution.

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

Sequence Diagram(s)

sequenceDiagram
  participant RevisionController
  participant ValidateTransformers
  participant revisionReconciler
  participant toBoxcutterRevision
  participant Boxcutter
  RevisionController->>ValidateTransformers: validate parsed revision objects
  ValidateTransformers-->>RevisionController: validation result
  revisionReconciler->>toBoxcutterRevision: convert API revision with transformers
  toBoxcutterRevision->>Boxcutter: construct CRD and object phases
  Boxcutter-->>revisionReconciler: reconcile transformed phases
Loading

Possibly related PRs

Suggested reviewers: theobarberbany, racheljpg

🚥 Pre-merge checks | ✅ 14 | ❌ 1

❌ Failed checks (1 warning)

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.
✅ Passed checks (14 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main envsubst-to-ManifestTransformer change and is specific enough for a history scan.
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 fmt.Sprintf, concatenation, dates, UUIDs, or runtime-generated names found.
Test Structure And Quality ✅ Passed The changed Ginkgo tests are well-scoped, clean up shared resources, and use explicit timeouts for cluster waits; no clear quality regressions found.
Microshift Test Compatibility ✅ Passed The added Ginkgo tests are envtest/unit controller tests; the new cases use only ConfigMaps and fakes, with no new MicroShift-unsupported APIs or features.
Single Node Openshift (Sno) Test Compatibility ✅ Passed The new Ginkgo tests are controller/envtest coverage and I found no multi-node, HA, scheduling, or topology assumptions.
Topology-Aware Scheduling Compatibility ✅ Passed Changed files only add transformer/revision plumbing and tests; no affinity, topology spread, replica, or node-selector scheduling constraints were introduced.
Ote Binary Stdout Contract ✅ Passed No process-level stdout writes were added; main.go only uses log.Errorfmt, installer BeforeSuite logs to GinkgoWriter, and no init/TestMain stdout calls exist.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed Added Ginkgo tests stay cluster/envtest-only; no hardcoded IPv4, JoinHostPort/ParseIP use, or public-internet connectivity was found.
No-Weak-Crypto ✅ Passed No weak crypto or secret/token comparisons were added; changed files only use SHA-256 content hashing and standard TLS config.
Container-Privileges ✅ Passed No privileged settings were found in non-vendor manifests; the main deployments use restricted SCC annotations and lack explicit host* / allowPrivilegeEscalation / root fields.
No-Sensitive-Data-In-Logs ✅ Passed No new sensitive logging found; changed controller logs are generic and only include revision names/content IDs, not secrets, tokens, PII, or hostnames.
✨ 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 added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jul 29, 2026
@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 damdo 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: 5

🧹 Nitpick comments (4)
pkg/controllers/installer/boxcutter.go (1)

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

Transformer errors leave subsequent transformers operating on the pre-transform object. On error the loop continues without advancing obj, so later transformers in the chain see stale input. Harmless today (any error discards the object), but worth a short comment noting the aggregation intent so a future refactor doesn't turn this into silent divergence.

🤖 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 144 - 159, Add a concise
comment in the transformer loop around the TransformObject error branch
explaining that errors are aggregated while subsequent transformers
intentionally continue with the current object, preserving the existing behavior
and documenting the intent.
cmd/capi-installer/main.go (1)

134-138: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No canonical constructor for the default transformer pipeline. The pipeline is re-declared at each wiring site, and the revision-controller envtest already diverges from production (adopt-existing only), so validation coverage in tests can drift from what runs in the operator. Export something like manifesttransformer.DefaultTransformers() and use it everywhere.

  • cmd/capi-installer/main.go#L134-L138: replace the inline slice with the shared constructor.
  • pkg/controllers/installer/suite_test.go#L110-L114: use the same constructor so the suite exercises the production pipeline.
  • pkg/controllers/revision/helpers_test.go#L77-L82: use the shared constructor instead of adopt-existing only.
🤖 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 `@cmd/capi-installer/main.go` around lines 134 - 138, Introduce and export a
canonical manifesttransformer.DefaultTransformers() constructor containing the
production default pipeline, then replace each inline transformer slice with it:
cmd/capi-installer/main.go lines 134-138,
pkg/controllers/installer/suite_test.go lines 110-114, and
pkg/controllers/revision/helpers_test.go lines 77-82. Ensure all three sites use
the shared constructor, including the full envsubst, managed-by, and
adopt-existing sequence.
pkg/controllers/revision/revision_controller_test.go (1)

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

Index-based substitution assertions are order-coupled. Consider ConsistOf/HaveField so a future ordering change in substitutionsFromMap doesn't break these specs.

As per coding guidelines, prefer Gomega built-ins such as HaveField/ConsistOf for unordered slice matching.

🤖 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 524 - 530,
Update the ManifestSubstitutions assertions in this test to validate the
expected key/value entries without relying on slice indexes or ordering. Use
Gomega’s unordered matching built-ins, such as ConsistOf with HaveField, while
preserving the existing three expected substitutions and values.

Source: Coding guidelines

pkg/manifesttransformer/managed_by_transformer_test.go (1)

27-101: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Align these manifesttransformer tests with the Ginkgo style

  • pkg/manifesttransformer/managed_by_transformer_test.go
  • pkg/manifesttransformer/adopt_existing_transformer_test.go
  • pkg/manifesttransformer/validate_test.go

They still use testing.T/t.Run; convert them to Describe/Context/It with By() steps, and switch validate_test.go to matcher-based error assertions instead of checking err.Error() directly.

🤖 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/managed_by_transformer_test.go` around lines 27 -
101, Convert the tests in pkg/manifesttransformer/managed_by_transformer_test.go
(lines 27-101), pkg/manifesttransformer/adopt_existing_transformer_test.go
(lines 41-120), and pkg/manifesttransformer/validate_test.go (lines 31-132) from
testing.T and t.Run to Ginkgo Describe/Context/It structure, adding By() steps
for the test actions. In validate_test.go, replace direct err.Error()
comparisons with the appropriate matcher-based error assertions while preserving
the existing validation behavior.

Source: Coding guidelines

🤖 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/boxcutter_test.go`:
- Around line 146-170: Update the DescribeTable entry named “adopt-existing
annotation is stable across calls” so it exercises adopt-existing transformation
by passing an AdoptExistingTransformer instead of nil for that case, or rename
the entry to describe only phase stability. Ensure the test title matches the
behavior actually asserted while preserving the existing phase-count checks.

In `@pkg/controllers/installer/installer_controller.go`:
- Around line 140-142: Update the comment immediately above the managedByReq
initialization to state that the managed-by label is applied at install time by
ManagedByTransformer, replacing the outdated reference to the revision
generator; leave the labels.NewRequirement logic unchanged.

In `@pkg/controllers/installer/revision_reconciler.go`:
- Around line 171-183: Update reconcileRevision’s
revisiongenerator.NewInstallerRevisionFromAPI error path to return the formatted
error’s message in the second result, matching the
manifesttransformer.ValidateTransformers failure path. Preserve the existing
terminal error wrapping and ensure malformed API revisions no longer return an
empty status message.

In `@pkg/controllers/revision/revision_controller_test.go`:
- Around line 581-591: Update the “sets NonRetryableError when a transformer
Validate fails” test to reuse the outer RevisionController `r` configured by the
error-handling Describe/BeforeEach instead of declaring a shadowing local
controller. Capture the Reconcile result and error, then assert the expected
non-retryable error outcome with a meaningful failure message rather than
discarding both return values.

In `@pkg/manifesttransformer/envsubst_transformer_test.go`:
- Around line 42-47: Update fakeRevisionWithSubs.ManifestSubstitutions to
preserve nil substitutions: return nil when f.subs is nil, while continuing to
return a copied non-nil map for initialized substitutions. This will allow the
no-substitutions test to exercise EnvsubstTransformer.WithRevision’s merged ==
nil branch without changing behavior for empty-but-initialized maps.

---

Nitpick comments:
In `@cmd/capi-installer/main.go`:
- Around line 134-138: Introduce and export a canonical
manifesttransformer.DefaultTransformers() constructor containing the production
default pipeline, then replace each inline transformer slice with it:
cmd/capi-installer/main.go lines 134-138,
pkg/controllers/installer/suite_test.go lines 110-114, and
pkg/controllers/revision/helpers_test.go lines 77-82. Ensure all three sites use
the shared constructor, including the full envsubst, managed-by, and
adopt-existing sequence.

In `@pkg/controllers/installer/boxcutter.go`:
- Around line 144-159: Add a concise comment in the transformer loop around the
TransformObject error branch explaining that errors are aggregated while
subsequent transformers intentionally continue with the current object,
preserving the existing behavior and documenting the intent.

In `@pkg/controllers/revision/revision_controller_test.go`:
- Around line 524-530: Update the ManifestSubstitutions assertions in this test
to validate the expected key/value entries without relying on slice indexes or
ordering. Use Gomega’s unordered matching built-ins, such as ConsistOf with
HaveField, while preserving the existing three expected substitutions and
values.

In `@pkg/manifesttransformer/managed_by_transformer_test.go`:
- Around line 27-101: Convert the tests in
pkg/manifesttransformer/managed_by_transformer_test.go (lines 27-101),
pkg/manifesttransformer/adopt_existing_transformer_test.go (lines 41-120), and
pkg/manifesttransformer/validate_test.go (lines 31-132) from testing.T and t.Run
to Ginkgo Describe/Context/It structure, adding By() steps for the test actions.
In validate_test.go, replace direct err.Error() comparisons with the appropriate
matcher-based error assertions while preserving the existing validation
behavior.
🪄 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: 49dd196c-281f-4dd4-966f-e7d14e4db2c0

📥 Commits

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

📒 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.go
  • pkg/revisiongenerator/validate_test.go
  • pkg/revisiongenerator/transform_test.go

Comment on lines +146 to +170
DescribeTable("should return stable phases on every call",
func(providerName string, wantPhaseCount int) {
rev := installerRevisionFromProfiles(providerName)

bcRev, err := toBoxcutterRevision(context.Background(), rev, nil, noopCollector)
Expect(err).NotTo(HaveOccurred())

first := bcRev.GetPhases()
second := bcRev.GetPhases()

Expect(second).To(HaveLen(wantPhaseCount),
"expected %d phase(s) for provider %q", wantPhaseCount, providerName)

for i := range first {
Expect(second[i].GetName()).To(Equal(first[i].GetName()),
"phase[%d] name must be stable across GetPhases calls", i)
Expect(second[i].GetObjects()).To(HaveLen(len(first[i].GetObjects())),
"phase[%d] object count must be stable across GetPhases calls", i)
}
},
Entry("objects only — one objects phase", providerCore, 1),
Entry("CRDs only — one CRD phase", providerCRD, 1),
Entry("CRDs and objects — two phases", providerMixed, 2),
Entry("adopt-existing annotation is stable across calls", providerAdoptExisting, 1),
)

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

Entry title promises adopt-existing behaviour that isn't exercised. The table passes nil transformers, so the providerAdoptExisting entry only checks phase stability — the annotation is never stripped and no collision-protection option is produced. Either rename the entry to reflect what it asserts or pass &manifesttransformer.AdoptExistingTransformer{} for that case.

As per coding guidelines, flag "overly-specific test titles likely to change" and ensure each test asserts the behaviour it names.

🤖 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_test.go` around lines 146 - 170, Update
the DescribeTable entry named “adopt-existing annotation is stable across calls”
so it exercises adopt-existing transformation by passing an
AdoptExistingTransformer instead of nil for that case, or rename the entry to
describe only phase stability. Ensure the test title matches the behavior
actually asserted while preserving the existing phase-count checks.

Source: Coding guidelines

Comment thread pkg/controllers/installer/installer_controller.go
Comment on lines +171 to 183
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
}

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

Conversion failure returns an empty status message. Every other failure path returns err.Error() as the message, so a malformed API revision surfaces as an error with a blank user-visible message (and an empty entry in the merged message list). Suggest mirroring the validation path.

🩹 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, 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
}
🤖 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 - 183,
Update reconcileRevision’s revisiongenerator.NewInstallerRevisionFromAPI error
path to return the formatted error’s message in the second result, matching the
manifesttransformer.ValidateTransformers failure path. Preserve the existing
terminal error wrapping and ensure malformed API revisions no longer return an
empty status message.

Comment on lines +581 to +591
It("sets NonRetryableError when a transformer Validate fails", func(ctx context.Context) {
stub := &stubTransformer{validateErr: errors.New("invalid manifest")}
r := &RevisionController{
Client: cl,
ProviderProfiles: defaultProviderImgs,
ReleaseVersion: "4.18.0",
Transformers: []manifesttransformer.ManifestTransformer{stub},
}

_, _ = r.Reconcile(ctx, reconcile.Request{NamespacedName: client.ObjectKey{Name: "cluster"}})

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

Shadowed r and discarded reconcile result weaken this test. The spec lives in the "error handling" Describe, whose BeforeEach builds a failing interceptor client and assigns the outer r; here a second r shadows it, so the surrounding fixture is misleading. Also assert the reconcile outcome instead of discarding both return values.

💚 Suggested changes
-		stub := &stubTransformer{validateErr: errors.New("invalid manifest")}
-		r := &RevisionController{
+		stub := &stubTransformer{validateErr: errors.New("invalid manifest")}
+		nonRetryableR := &RevisionController{
 			Client:           cl,
 			ProviderProfiles: defaultProviderImgs,
 			ReleaseVersion:   "4.18.0",
 			Transformers:     []manifesttransformer.ManifestTransformer{stub},
 		}
 
-		_, _ = r.Reconcile(ctx, reconcile.Request{NamespacedName: client.ObjectKey{Name: "cluster"}})
+		_, err := nonRetryableR.Reconcile(ctx, reconcile.Request{NamespacedName: client.ObjectKey{Name: "cluster"}})
+		Expect(err).NotTo(HaveOccurred(), "a non-retryable error should not be returned for requeue")

As per coding guidelines, tests should be "Consistent with codebase" and assertions "should include meaningful failure messages".

📝 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
It("sets NonRetryableError when a transformer Validate fails", func(ctx context.Context) {
stub := &stubTransformer{validateErr: errors.New("invalid manifest")}
r := &RevisionController{
Client: cl,
ProviderProfiles: defaultProviderImgs,
ReleaseVersion: "4.18.0",
Transformers: []manifesttransformer.ManifestTransformer{stub},
}
_, _ = r.Reconcile(ctx, reconcile.Request{NamespacedName: client.ObjectKey{Name: "cluster"}})
stub := &stubTransformer{validateErr: errors.New("invalid manifest")}
nonRetryableR := &RevisionController{
Client: cl,
ProviderProfiles: defaultProviderImgs,
ReleaseVersion: "4.18.0",
Transformers: []manifesttransformer.ManifestTransformer{stub},
}
_, err := nonRetryableR.Reconcile(ctx, reconcile.Request{NamespacedName: client.ObjectKey{Name: "cluster"}})
Expect(err).NotTo(HaveOccurred(), "a non-retryable error should not be returned for requeue")
🤖 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 581 - 591,
Update the “sets NonRetryableError when a transformer Validate fails” test to
reuse the outer RevisionController `r` configured by the error-handling
Describe/BeforeEach instead of declaring a shadowing local controller. Capture
the Reconcile result and error, then assert the expected non-retryable error
outcome with a meaningful failure message rather than discarding both return
values.

Source: Coding guidelines

Comment on lines +42 to +47
func (f *fakeRevisionWithSubs) ManifestSubstitutions() map[string]string {
out := make(map[string]string, len(f.subs))
maps.Copy(out, f.subs)

return out
}

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 | 🟡 Minor | ⚡ Quick win

Fake doesn't mirror the real nil-substitutions contract, leaving WithRevision's nil-guard untested.

parsedRevision.ManifestSubstitutions() returns nil when there are no substitutions (via maps.Clone(nil)), and EnvsubstTransformer.WithRevision has an explicit if merged == nil branch to handle that. This fake always returns a non-nil map (even for f.subs == nil), so the "does not panic when revision has no substitutions" test (lines 196-207) never actually exercises that nil branch.

🧪 Proposed fix to mirror the real nil-vs-empty contract
 func (f *fakeRevisionWithSubs) ManifestSubstitutions() map[string]string {
+	if f.subs == nil {
+		return nil
+	}
+
 	out := make(map[string]string, len(f.subs))
 	maps.Copy(out, f.subs)
 
 	return out
 }
📝 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 (f *fakeRevisionWithSubs) ManifestSubstitutions() map[string]string {
out := make(map[string]string, len(f.subs))
maps.Copy(out, f.subs)
return out
}
func (f *fakeRevisionWithSubs) ManifestSubstitutions() map[string]string {
if f.subs == nil {
return nil
}
out := make(map[string]string, len(f.subs))
maps.Copy(out, f.subs)
return out
}
🤖 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_test.go` around lines 42 - 47,
Update fakeRevisionWithSubs.ManifestSubstitutions to preserve nil substitutions:
return nil when f.subs is nil, while continuing to return a copied non-nil map
for initialized substitutions. This will allow the no-substitutions test to
exercise EnvsubstTransformer.WithRevision’s merged == nil branch without
changing behavior for empty-but-initialized maps.

mdbooth added 2 commits July 29, 2026 16:21
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.

@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: 2

🤖 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 70-80: Update the WithRevision and WithComponent method signatures
on testValueTransformer to use revisiongenerator.ParsedRevision and
revisiongenerator.ParsedComponent respectively, matching the renamed types while
preserving their existing no-op behavior and return value.

In `@pkg/manifesttransformer/adopt_existing_transformer.go`:
- Around line 99-109: Update the installer test package in
testvalue_transformer_test.go to use revisiongenerator.ParsedRevision and
revisiongenerator.ParsedComponent wherever the obsolete RenderedRevision and
RenderedComponent types are referenced, preserving the existing test behavior.
🪄 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: d4fdd8c9-29e7-4ba2-8e38-fb654453e569

📥 Commits

Reviewing files that changed from the base of the PR and between fda9884 and 220bb05.

📒 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/validate_test.go
  • pkg/revisiongenerator/transform_test.go
  • pkg/revisiongenerator/transform.go
🚧 Files skipped from review as they are similar to previous changes (16)
  • pkg/controllers/installer/suite_test.go
  • pkg/manifesttransformer/manifest_transformer.go
  • cmd/capi-installer/main.go
  • pkg/controllers/installer/helpers_test.go
  • pkg/revisiongenerator/helpers_test.go
  • pkg/manifesttransformer/validate.go
  • pkg/controllers/revision/helpers_test.go
  • pkg/controllers/revision/revision_controller_test.go
  • pkg/controllers/installer/revision_reconciler_test.go
  • pkg/revisiongenerator/validate.go
  • pkg/controllers/revision/revision_controller.go
  • pkg/controllers/installer/installer_controller.go
  • pkg/manifesttransformer/validate_test.go
  • pkg/controllers/installer/boxcutter.go
  • pkg/controllers/installer/boxcutter_test.go
  • pkg/revisiongenerator/revision_test.go

Comment on lines +70 to +80
// WithRevision implements manifesttransformer.ManifestTransformer. It is a
// no-op; testValueTransformer does not need revision context.
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

Build break: stale Rendered* types no longer exist.

revisiongenerator.RenderedRevision/RenderedComponent were renamed to ParsedRevision/ParsedComponent in pkg/revisiongenerator/revision.go as part of this same PR. This file still references the old names, breaking compilation of the pkg/controllers/installer test package (confirmed by the golangci-lint typecheck error).

🐛 Proposed fix
-func (t testValueTransformer) WithRevision(_ context.Context, _ revisiongenerator.RenderedRevision) manifesttransformer.ManifestTransformer {
+func (t testValueTransformer) WithRevision(_ context.Context, _ revisiongenerator.ParsedRevision) 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 {
+func (t testValueTransformer) WithComponent(_ context.Context, _ revisiongenerator.ParsedComponent) manifesttransformer.ManifestTransformer {
 	return t
 }
📝 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
// WithRevision implements manifesttransformer.ManifestTransformer. It is a
// no-op; testValueTransformer does not need revision context.
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
}
// WithRevision implements manifesttransformer.ManifestTransformer. It is a
// no-op; testValueTransformer does not need revision context.
func (t testValueTransformer) WithRevision(_ context.Context, _ revisiongenerator.ParsedRevision) 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.ParsedComponent) manifesttransformer.ManifestTransformer {
return t
}
🧰 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 70 -
80, Update the WithRevision and WithComponent method signatures on
testValueTransformer to use revisiongenerator.ParsedRevision and
revisiongenerator.ParsedComponent respectively, matching the renamed types while
preserving their existing no-op behavior and return value.

Source: Linters/SAST tools

Comment on lines +99 to +109
// WithRevision implements ManifestTransformer. AdoptExistingTransformer does
// not need revision context.
func (a *AdoptExistingTransformer) WithRevision(_ context.Context, _ revisiongenerator.ParsedRevision) ManifestTransformer {
return a
}

// WithComponent implements ManifestTransformer. AdoptExistingTransformer does
// not need component context.
func (a *AdoptExistingTransformer) WithComponent(_ context.Context, _ revisiongenerator.ParsedComponent) ManifestTransformer {
return a
}

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
# Confirm remaining references to the old type names across the repo.
rg -n 'RenderedRevision|RenderedComponent' --type=go

Repository: openshift/cluster-capi-operator

Length of output: 169


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant files and remaining revisiongenerator type usage.
git ls-files 'pkg/manifesttransformer/adopt_existing_transformer.go' 'pkg/controllers/installer/testvalue_transformer_test.go' || true
printf '\n-- matches for ParsedRevision/ParsedComponent --\n'
rg -n 'ParsedRevision|ParsedComponent' --type=go pkg
printf '\n-- matches for RenderedRevision/RenderedComponent --\n'
rg -n 'RenderedRevision|RenderedComponent' --type=go pkg || true

Repository: openshift/cluster-capi-operator

Length of output: 12868


Update pkg/controllers/installer/testvalue_transformer_test.go to ParsedRevision / ParsedComponent. The old revisiongenerator.RenderedRevision and revisiongenerator.RenderedComponent names still break the installer test package build.

🤖 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/adopt_existing_transformer.go` around lines 99 - 109,
Update the installer test package in testvalue_transformer_test.go to use
revisiongenerator.ParsedRevision and revisiongenerator.ParsedComponent wherever
the obsolete RenderedRevision and RenderedComponent types are referenced,
preserving the existing test behavior.

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

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