Skip to content

render: Read FeatureGate CR from rendered manifests#1648

Open
sanchezl wants to merge 1 commit into
openshift:mainfrom
sanchezl:render-featuregate-cr
Open

render: Read FeatureGate CR from rendered manifests#1648
sanchezl wants to merge 1 commit into
openshift:mainfrom
sanchezl:render-featuregate-cr

Conversation

@sanchezl

@sanchezl sanchezl commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Replace manual install-config feature gate parsing with the standard operator render pattern: read the pre-resolved FeatureGate CR from rendered manifests via --rendered-manifest-files and --payload-version
  • Include a fallback that resolves featureSet and featureGates from the install-config when the new flags are not yet available
  • Fix the root cause of the featureSet resolution gap that PR #1647 addresses

Background

The CEO render was the only operator that parsed feature gates directly from the raw install-config (featureGates: [Key=Value] strings). Every other operator render (CKASO, CKCMO, MCO) reads the pre-resolved FeatureGate CR from /assets/manifests/ — the api-render step in bootkube.sh runs first and resolves featureSet (e.g. TechPreviewNoUpgrade) into individual gates, writing the result into the CR status.

Because the CEO was parsing the raw install-config, it missed featureSet resolution entirely, causing ConfigurablePKI to not be detected when using featureSet: TechPreviewNoUpgrade instead of explicit featureGates entries.

Approach

Primary path (when --rendered-manifest-files and --payload-version are provided): Read the pre-resolved FeatureGate CR from rendered manifests, matching the pattern used by CKASO and CKCMO. This eliminates the entire class of feature-gate-resolution bugs.

Fallback path (when the flags are not provided): Resolve featureSet into individual gates via features.FeatureSets() from openshift/api, then layer explicit featureGates overrides on top. This fallback uses strings.Cut for safe parsing and handles the featureSetfeatureGates override correctly. It should be removed once the installer carries the coordinated change.

Changes

pkg/cmd/render/render.go:

  • Add optional --rendered-manifest-files and --payload-version flags
  • Add getFeatureGates() dispatcher that tries the CR path, falls back to install-config
  • Add getFeatureGatesFromManifests() — reads FeatureGate CR, returns enabled/disabled sets
  • Add getFeatureGatesFromInstallConfig() — resolves featureSet + featureGates from install-config
  • Remove the old getFeatureGatesStatus() function

pkg/cmd/render/render_test.go:

  • Add writeFeatureGateManifest() test helper for the CR path
  • Add Test_getFeatureGatesFromInstallConfig testing featureSet resolution, overrides
  • Update test infrastructure to provide rendered manifest directories
  • Update TestTemplateDataWithCustomPKI to provide ConfigurablePKI via FeatureGate CR

Coordinated change

openshift/installer#10679 passes --rendered-manifest-files and --payload-version to the etcd-render invocation. This CEO PR can land independently — the fallback path handles the transition. Once the installer PR merges, a follow-up can remove the fallback.

Test plan

  • go test ./pkg/cmd/render/ — all tests pass (both primary and fallback paths)
  • go vet ./pkg/cmd/render/ — clean
  • CI

Summary by CodeRabbit

  • New Features
    • Rendering can now determine feature gate enablement from pre-rendered FeatureGate manifests supplied by the user.
    • Added CLI flags to specify rendered manifest locations and the payload version used during rendering.
  • Bug Fixes
    • Bootstrap certificate secrets now consistently reflect enabled/disabled feature gates derived from the rendered manifests or the install configuration (including correct handling of configurable PKI selection).
  • Tests
    • Updated the rendering/template test harness to generate a FeatureGate manifest at runtime and extended coverage for parsing feature gates from install configuration.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Walkthrough

The render command accepts optional rendered-manifest paths and a payload version, resolves FeatureGate configuration from manifests when both are available, and falls back to install-config parsing otherwise. Tests now generate FeatureGate manifests and pass these options.

Changes

FeatureGate rendering

Layer / File(s) Summary
Render inputs and option wiring
pkg/cmd/render/render.go
Render options and CLI flags carry rendered manifest paths and payload version into template data creation.
FeatureGate resolution and gate sets
pkg/cmd/render/render.go
FeatureGate manifests are parsed into enabled and disabled sets, with install-config featureSet and featureGates used as fallback inputs.
Manifest-backed render tests
pkg/cmd/render/render_test.go
Test helpers write FeatureGate manifests, pass the new options, configure custom PKI gates explicitly, and validate install-config fallback parsing.

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

Sequence Diagram(s)

sequenceDiagram
  participant RenderCommand
  participant renderOpts
  participant FeatureGateManifest
  participant InstallConfig
  participant TemplateData
  RenderCommand->>renderOpts: pass manifest paths and payload version
  renderOpts->>FeatureGateManifest: scan and parse FeatureGate resource
  FeatureGateManifest-->>renderOpts: return enabled and disabled feature sets
  renderOpts->>InstallConfig: parse fallback featureSet and featureGates
  renderOpts->>TemplateData: provide resolved feature gate state
  TemplateData-->>RenderCommand: create template and PKI data
Loading

Suggested reviewers: ingvagabund, everettraven

🚥 Pre-merge checks | ✅ 14 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 clearly matches the main change: reading the FeatureGate CR from rendered manifests.
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 test titles in the changed file are fixed strings; no dynamic values, timestamps, UUIDs, or generated identifiers appear in titles.
Test Structure And Quality ✅ Passed PASS: pkg/cmd/render has no Ginkgo tests; the touched tests are standard table-driven testing cases with t.TempDir and no Eventually/cluster cleanup concerns.
Microshift Test Compatibility ✅ Passed No new Ginkgo e2e tests were added; the touched tests are standard Go unit tests using testing.T, so MicroShift API restrictions don’t apply.
Single Node Openshift (Sno) Test Compatibility ✅ Passed No new Ginkgo e2e tests were added; the PR only changes pkg/cmd/render unit tests and contains no multi-node or SNO-sensitive assumptions.
Topology-Aware Scheduling Compatibility ✅ Passed The PR only changes feature-gate parsing/test fixtures; no new affinity, topology spread, nodeSelector, PDB, or replica-scheduling logic was added.
Ote Binary Stdout Contract ✅ Passed No process-level stdout writes were added; render.go only logs via klog/errOut, render_test.go has no TestMain/init, and vendored klog defaults to stderr.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed Touched tests are unit tests only; no It/Describe/Context/When constructs or external-network calls were added, so the IPv6/disconnected e2e check is not applicable.
No-Weak-Crypto ✅ Passed Changed code only adds feature-gate parsing/tests; no MD5/SHA1/DES/RC4/3DES/Blowfish/ECB, custom crypto, or secret/token comparisons found.
Container-Privileges ✅ Passed PR only changes feature-gate parsing/test helpers; no modified manifests or securityContext fields add privileged/root/host* settings.
No-Sensitive-Data-In-Logs ✅ Passed No new log statements expose secrets or PII; the added code only logs bootstrap IP/strategy and a missing-flags warning.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

Error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions
The command is terminated due to an error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions


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

@openshift-ci

openshift-ci Bot commented Jul 10, 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 everettraven 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

🤖 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/cmd/render/render.go`:
- Around line 863-874: In the FeatureGate manifest loop, replace each silent
continue for os.ReadFile, yaml.YAMLToJSON, and json.Unmarshal failures with
contextual error returns that identify the affected file and wrap the original
error; update the enclosing function as needed to propagate these errors instead
of reporting that no FeatureGate exists. Use the loop and FeatureGate decoding
logic around os.ReadFile, YAMLToJSON, and json.Unmarshal as the implementation
reference.
🪄 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: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 7e583b38-9d3d-4186-bf0c-2dc2791d0d0e

📥 Commits

Reviewing files that changed from the base of the PR and between 831d5e6 and 05a3ad1.

📒 Files selected for processing (2)
  • pkg/cmd/render/render.go
  • pkg/cmd/render/render_test.go

Comment thread pkg/cmd/render/render.go
Comment on lines +863 to +874
for _, file := range files {
data, err := os.ReadFile(file)
if err != nil {
continue
}
jsonBytes, err := yaml.YAMLToJSON(data)
if err != nil {
continue
}
fg := &configv1.FeatureGate{}
if err := json.Unmarshal(jsonBytes, fg); err != nil {
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Preserve manifest read and decoding errors.

These continue branches hide the actual failing file and can misleadingly report that no FeatureGate exists. Return contextual errors instead. As per path instructions, Go code must never ignore error returns.

Proposed fix
 data, err := os.ReadFile(file)
 if err != nil {
-	continue
+	return nil, fmt.Errorf("failed to read manifest %s: %w", file, err)
 }
 jsonBytes, err := yaml.YAMLToJSON(data)
 if err != nil {
-	continue
+	return nil, fmt.Errorf("failed to convert manifest %s to JSON: %w", file, err)
 }
 fg := &configv1.FeatureGate{}
 if err := json.Unmarshal(jsonBytes, fg); err != nil {
-	continue
+	return nil, fmt.Errorf("failed to decode manifest %s: %w", file, 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
for _, file := range files {
data, err := os.ReadFile(file)
if err != nil {
continue
}
jsonBytes, err := yaml.YAMLToJSON(data)
if err != nil {
continue
}
fg := &configv1.FeatureGate{}
if err := json.Unmarshal(jsonBytes, fg); err != nil {
continue
for _, file := range files {
data, err := os.ReadFile(file)
if err != nil {
return nil, fmt.Errorf("failed to read manifest %s: %w", file, err)
}
jsonBytes, err := yaml.YAMLToJSON(data)
if err != nil {
return nil, fmt.Errorf("failed to convert manifest %s to JSON: %w", file, err)
}
fg := &configv1.FeatureGate{}
if err := json.Unmarshal(jsonBytes, fg); err != nil {
return nil, fmt.Errorf("failed to decode manifest %s: %w", file, 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/cmd/render/render.go` around lines 863 - 874, In the FeatureGate manifest
loop, replace each silent continue for os.ReadFile, yaml.YAMLToJSON, and
json.Unmarshal failures with contextual error returns that identify the affected
file and wrap the original error; update the enclosing function as needed to
propagate these errors instead of reporting that no FeatureGate exists. Use the
loop and FeatureGate decoding logic around os.ReadFile, YAMLToJSON, and
json.Unmarshal as the implementation reference.

Source: Path instructions

@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

🧹 Nitpick comments (1)
pkg/cmd/render/render_test.go (1)

650-658: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Keep the feature-gate fixture consistent. defaultFeatureGates() already puts ConfigurablePKI in disabled, so appending it to enabled leaves the generated manifest listing the gate in both sections. Move it from disabled to enabled for this test fixture.

🤖 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/cmd/render/render_test.go` around lines 650 - 658, Update the test
fixture setup around defaultFeatureGates() so FeatureGateConfigurablePKI is
moved from the disabled slice to the enabled slice rather than appended to both;
remove it from disabled before assigning enabledFeatureGates and
disabledFeatureGates in testConfig.
🤖 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/cmd/render/render.go`:
- Around line 922-935: The fallback feature-gate parser uses unchecked
assertions that can panic on malformed install-config data. In the parser
containing featureGatesRaw and enabled/disabled updates, validate that
featureGatesRaw is a []any and each entry is a string, returning contextual
errors for invalid types; propagate these errors through newTemplateData so
parsing fails cleanly.
- Around line 817-823: Update renderOpts.getFeatureGates to distinguish both
flags being absent from only one being provided: fall back to install-config
parsing only when renderedManifestFiles and payloadVersion are both empty; if
exactly one is set, return a configuration error instead of silently falling
back, and continue using getFeatureGatesFromManifests when both are present.

---

Nitpick comments:
In `@pkg/cmd/render/render_test.go`:
- Around line 650-658: Update the test fixture setup around
defaultFeatureGates() so FeatureGateConfigurablePKI is moved from the disabled
slice to the enabled slice rather than appended to both; remove it from disabled
before assigning enabledFeatureGates and disabledFeatureGates in testConfig.
🪄 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: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: b49c7794-19d5-4e54-bb09-cfc5fe96ab1d

📥 Commits

Reviewing files that changed from the base of the PR and between 05a3ad1 and 74d2dc0.

📒 Files selected for processing (2)
  • pkg/cmd/render/render.go
  • pkg/cmd/render/render_test.go

Comment thread pkg/cmd/render/render.go
Comment on lines +817 to +823
func (r *renderOpts) getFeatureGates(installConfig map[string]any) (sets.Set[configv1.FeatureGateName], sets.Set[configv1.FeatureGateName], error) {
if len(r.renderedManifestFiles) > 0 && len(r.payloadVersion) > 0 {
return r.getFeatureGatesFromManifests()
}
klog.Warningf("--rendered-manifest-files or --payload-version not provided, falling back to install-config feature gate parsing")
enabled, disabled := getFeatureGatesFromInstallConfig(installConfig)
return enabled, disabled, nil

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 | 🟠 Major | ⚡ Quick win

Reject partially configured manifest resolution.

If only one new flag is supplied, rendering silently falls back and can use feature gates different from the rendered FeatureGate. Fall back only when both flags are absent; otherwise return a configuration error.

Proposed fix
+	hasManifests := len(r.renderedManifestFiles) > 0
+	hasPayloadVersion := len(r.payloadVersion) > 0
-	if len(r.renderedManifestFiles) > 0 && len(r.payloadVersion) > 0 {
+	if hasManifests && hasPayloadVersion {
 		return r.getFeatureGatesFromManifests()
 	}
+	if hasManifests != hasPayloadVersion {
+		return nil, nil, fmt.Errorf("--rendered-manifest-files and --payload-version must be provided together")
+	}
📝 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 *renderOpts) getFeatureGates(installConfig map[string]any) (sets.Set[configv1.FeatureGateName], sets.Set[configv1.FeatureGateName], error) {
if len(r.renderedManifestFiles) > 0 && len(r.payloadVersion) > 0 {
return r.getFeatureGatesFromManifests()
}
klog.Warningf("--rendered-manifest-files or --payload-version not provided, falling back to install-config feature gate parsing")
enabled, disabled := getFeatureGatesFromInstallConfig(installConfig)
return enabled, disabled, nil
func (r *renderOpts) getFeatureGates(installConfig map[string]any) (sets.Set[configv1.FeatureGateName], sets.Set[configv1.FeatureGateName], error) {
hasManifests := len(r.renderedManifestFiles) > 0
hasPayloadVersion := len(r.payloadVersion) > 0
if hasManifests && hasPayloadVersion {
return r.getFeatureGatesFromManifests()
}
if hasManifests != hasPayloadVersion {
return nil, nil, fmt.Errorf("--rendered-manifest-files and --payload-version must be provided together")
}
klog.Warningf("--rendered-manifest-files or --payload-version not provided, falling back to install-config feature gate parsing")
enabled, disabled := getFeatureGatesFromInstallConfig(installConfig)
return enabled, disabled, nil
🤖 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/cmd/render/render.go` around lines 817 - 823, Update
renderOpts.getFeatureGates to distinguish both flags being absent from only one
being provided: fall back to install-config parsing only when
renderedManifestFiles and payloadVersion are both empty; if exactly one is set,
return a configuration error instead of silently falling back, and continue
using getFeatureGatesFromManifests when both are present.

Comment thread pkg/cmd/render/render.go
Comment on lines +922 to +935
if featureGatesRaw, found := installConfig["featureGates"]; found {
for _, entry := range featureGatesRaw.([]any) {
key, value, found := strings.Cut(entry.(string), "=")
if !found {
continue
}
name := configv1.FeatureGateName(key)
if value == "true" {
enabled.Insert(name)
disabled.Delete(name)
} else if value == "false" {
enabled.Delete(name)
disabled.Insert(name)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Replace unchecked install-config assertions with validation errors.

featureGatesRaw.([]any) and entry.(string) panic on malformed input. Return contextual errors from the fallback parser so newTemplateData can fail cleanly.

As per path instructions, validate input at trust boundaries rather than relying on unchecked types.

🤖 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/cmd/render/render.go` around lines 922 - 935, The fallback feature-gate
parser uses unchecked assertions that can panic on malformed install-config
data. In the parser containing featureGatesRaw and enabled/disabled updates,
validate that featureGatesRaw is a []any and each entry is a string, returning
contextual errors for invalid types; propagate these errors through
newTemplateData so parsing fails cleanly.

Source: Path instructions

@openshift-ci

openshift-ci Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

@sanchezl: 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-ovn-serial-1of2 74d2dc0 link true /test e2e-aws-ovn-serial-1of2
ci/prow/e2e-aws-ovn-serial-2of2 74d2dc0 link true /test e2e-aws-ovn-serial-2of2
ci/prow/e2e-metal-ipi-ovn-ipv6 74d2dc0 link true /test e2e-metal-ipi-ovn-ipv6
ci/prow/e2e-agnostic-ovn 74d2dc0 link true /test e2e-agnostic-ovn
ci/prow/e2e-gcp-operator 74d2dc0 link true /test e2e-gcp-operator
ci/prow/e2e-aws-ovn-single-node 74d2dc0 link true /test e2e-aws-ovn-single-node

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.

@hasbro17 hasbro17 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

See: https://github.com/openshift/cluster-etcd-operator/pull/1648/changes#r3561973119
TLDR: The fallback getFeatureGatesFromInstallConfig must resolve configv1.Default when featureSet is absent from install-config, otherwise the empty gate sets cause a panic at
featureGate.Enabled(ShortCertRotation) during bootstrap. See this job's
bootkube.log
.

Comment thread pkg/cmd/render/render.go
enabled := sets.New[configv1.FeatureGateName]()
disabled := sets.New[configv1.FeatureGateName]()

if featureSetRaw, found := installConfig["featureSet"]; found {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this fallback path is still causing issues on non-techpreview installs. I'm seeing repeated panics in the bootkube logs for etcd-render.

Jul 10 03:42:15 ip-10-0-97-204 bootkube.sh[5696]: W0710 03:42:15.634081       1 render.go:821] --rendered-manifest-files or --payload-version not provided, falling back to install-config feature gate parsing
Jul 10 03:42:15 ip-10-0-97-204 bootkube.sh[5696]: panic: feature "ShortCertRotation" is not registered in FeatureGates []
...
Jul 10 03:42:15 ip-10-0-97-204 systemd[1]: bootkube.service: Main process exited, code=exited, status=2/INVALIDARGUMENT
Jul 10 03:42:15 ip-10-0-97-204 systemd[1]: bootkube.service: Failed with result 'exit-code'.
Jul 10 03:42:15 ip-10-0-97-204 systemd[1]: bootkube.service: Consumed 20.897s CPU time, 1.3G memory peak.
Jul 10 03:42:20 ip-10-0-97-204 systemd[1]: bootkube.service: Scheduled restart job, restart counter is at 1.

Will share the review bot analysis below for more details.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖
The fallback path in getFeatureGatesFromInstallConfig panics on Default installs where featureSet is absent from the install-config (the standard case — configv1.Default is "" and omitempty causes the
installer to omit it entirely).

When neither featureSet nor featureGates is present, the function returns empty enabled/disabled sets. These flow through createBootstrapCertSecretscreateCertSecrets (certs.go:58) →
NewEtcdCertSignerController (etcdcertsignercontroller.go:159) → NewHardcodedFeatureGateAccess([], []). The controller then calls featureGates.Enabled(features.FeatureShortCertRotation), and since
ShortCertRotation is in neither set, featuregate.go:39 panics:

panic(fmt.Errorf("feature %q is not registered in FeatureGates %v", key, f.KnownFeatures()))

The old getFeatureGatesStatus prevented this with disabled.Insert(necessaryFeatureGates...) where necessaryFeatureGates = []configv1.FeatureGateName{"ShortCertRotation"}. The new code drops that safety
net without a complete replacement for the absent-featureSet case.

CI evidence: The bootkube.log from this job's artifacts
shows the etcd-render container crashing twice with the panic stack trace:

Jul 10 03:42:15 bootkube.sh[5696]: etcdcertsignercontroller.go:159 +0x528
Jul 10 03:42:15 bootkube.sh[5696]: certs.go:58 +0xb49
Jul 10 03:42:15 bootkube.sh[5696]: render.go:301 +0xbdb
...
Jul 10 03:42:15 systemd[1]: bootkube.service: Main process exited, code=exited, status=2/INVALIDARGUMENT
Jul 10 03:42:15 systemd[1]: bootkube.service: Failed with result 'exit-code'.
Jul 10 03:42:20 systemd[1]: bootkube.service: Scheduled restart job, restart counter is at 1.

Then the same panic repeats at 03:42:26. The job eventually passes because bootkube.service keeps restarting and on a later attempt the api-render step has already produced the FeatureGate CR manifest, so
the primary getFeatureGatesFromManifests path succeeds. But the fallback panics every time it's hit, adding fragility and latency to bootstrap.

The install-config for this job (install-config.yaml) confirms no featureSet field is present.

Suggested fix: Always resolve the featureSet, defaulting to configv1.Default when absent:

featureSet := configv1.Default
if featureSetRaw, found := installConfig["featureSet"]; found {
    if featureSetStr, ok := featureSetRaw.(string); ok {
        featureSet = configv1.FeatureSet(featureSetStr)
    }
}
if resolved := features.FeatureSets(0, features.SelfManaged, featureSet); resolved != nil {
    // populate enabled/disabled from resolved
}

This populates all known gates (including ShortCertRotation in disabled) for every featureSet, making the old hardcode genuinely unnecessary.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I know this goes away when openshift/installer#10679 lands but this has to come first I think.

@sanchezl sanchezl force-pushed the render-featuregate-cr branch from d5bbe4f to ad61905 Compare July 13, 2026 14:24

@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

🤖 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/cmd/render/render_test.go`:
- Around line 1057-1067: Extend the test cases around the existing
ConfigurablePKI feature-gate scenarios in the render test table with a Default
featureSet configuration that explicitly sets featureGates to
ConfigurablePKI=true and expects ConfigurablePKI enabled. This should exercise
the fallback parser’s explicit-enable branch without changing the existing
TechPreview or false-override cases.
🪄 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: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 473854b1-b33d-4caa-9078-8020dc0593db

📥 Commits

Reviewing files that changed from the base of the PR and between d5bbe4f and ad61905.

📒 Files selected for processing (2)
  • pkg/cmd/render/render.go
  • pkg/cmd/render/render_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/cmd/render/render.go

Comment on lines +1057 to 1067
"featureGates override disables ConfigurablePKI even with TechPreview": {
installConfig: `
apiVersion: v1
metadata:
name: my-cluster
featureGates: [ShortCertRotation=true, UpgradeStatus=false]
featureSet: TechPreviewNoUpgrade
featureGates: [ConfigurablePKI=false]
`,
expectedEnabled: sets.New(features.FeatureShortCertRotation),
expectedDisabled: sets.New(features.FeatureGateUpgradeStatus),
},
"unexpected data": {
installConfig: `
apiVersion: v1
metadata:
name: my-cluster
featureGates: [ShortCertRotation=true, UpgradeStatus=foobar]
`,
expectedEnabled: sets.New(features.FeatureShortCertRotation),
expectedDisabled: sets.New[configv1.FeatureGateName](),
expectConfigurablePKI: false,
},
}

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

Cover explicit fallback enablement.

Add a Default-featureSet case with featureGates: [ConfigurablePKI=true]. The suite covers FeatureSet enablement and the false override, but not the fallback parser’s explicit-enable branch; the Custom PKI integration test uses manifest-backed resolution instead. As per coding guidelines, “All changes must include unit test additions/changes.”

Suggested test case
+       "featureGates override enables ConfigurablePKI with default featureSet": {
+           installConfig: `
+apiVersion: v1
+metadata:
+  name: my-cluster
+featureGates: [ConfigurablePKI=true]
+`,
+           expectConfigurablePKI: true,
+       },
📝 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
"featureGates override disables ConfigurablePKI even with TechPreview": {
installConfig: `
apiVersion: v1
metadata:
name: my-cluster
featureGates: [ShortCertRotation=true, UpgradeStatus=false]
featureSet: TechPreviewNoUpgrade
featureGates: [ConfigurablePKI=false]
`,
expectedEnabled: sets.New(features.FeatureShortCertRotation),
expectedDisabled: sets.New(features.FeatureGateUpgradeStatus),
},
"unexpected data": {
installConfig: `
apiVersion: v1
metadata:
name: my-cluster
featureGates: [ShortCertRotation=true, UpgradeStatus=foobar]
`,
expectedEnabled: sets.New(features.FeatureShortCertRotation),
expectedDisabled: sets.New[configv1.FeatureGateName](),
expectConfigurablePKI: false,
},
}
"featureGates override disables ConfigurablePKI even with TechPreview": {
installConfig: `
apiVersion: v1
metadata:
name: my-cluster
featureSet: TechPreviewNoUpgrade
featureGates: [ConfigurablePKI=false]
`,
expectConfigurablePKI: false,
},
"featureGates override enables ConfigurablePKI with default featureSet": {
installConfig: `
apiVersion: v1
metadata:
name: my-cluster
featureGates: [ConfigurablePKI=true]
`,
expectConfigurablePKI: true,
},
}
🤖 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/cmd/render/render_test.go` around lines 1057 - 1067, Extend the test
cases around the existing ConfigurablePKI feature-gate scenarios in the render
test table with a Default featureSet configuration that explicitly sets
featureGates to ConfigurablePKI=true and expects ConfigurablePKI enabled. This
should exercise the fallback parser’s explicit-enable branch without changing
the existing TechPreview or false-override cases.

Source: Coding guidelines

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants