render: Read FeatureGate CR from rendered manifests#1648
Conversation
WalkthroughThe render command accepts optional rendered-manifest paths and a payload version, resolves ChangesFeatureGate rendering
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
Suggested reviewers: 🚥 Pre-merge checks | ✅ 14 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (14 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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 Comment |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
pkg/cmd/render/render.gopkg/cmd/render/render_test.go
| 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 |
There was a problem hiding this comment.
🩺 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.
| 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
05a3ad1 to
74d2dc0
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
pkg/cmd/render/render_test.go (1)
650-658: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueKeep the feature-gate fixture consistent.
defaultFeatureGates()already putsConfigurablePKIindisabled, so appending it toenabledleaves the generated manifest listing the gate in both sections. Move it fromdisabledtoenabledfor 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
📒 Files selected for processing (2)
pkg/cmd/render/render.gopkg/cmd/render/render_test.go
| 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 |
There was a problem hiding this comment.
🎯 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.
| 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.
| 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) | ||
| } |
There was a problem hiding this comment.
🩺 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
|
@sanchezl: The following tests failed, say
Full PR test history. Your PR dashboard. DetailsInstructions 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
left a comment
There was a problem hiding this comment.
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.
| enabled := sets.New[configv1.FeatureGateName]() | ||
| disabled := sets.New[configv1.FeatureGateName]() | ||
|
|
||
| if featureSetRaw, found := installConfig["featureSet"]; found { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
🤖
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 createBootstrapCertSecrets → createCertSecrets (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.
There was a problem hiding this comment.
I know this goes away when openshift/installer#10679 lands but this has to come first I think.
d5bbe4f to
ad61905
Compare
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
pkg/cmd/render/render.gopkg/cmd/render/render_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
- pkg/cmd/render/render.go
| "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, | ||
| }, | ||
| } |
There was a problem hiding this comment.
🎯 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.
| "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
Summary
--rendered-manifest-filesand--payload-versionfeatureSetandfeatureGatesfrom the install-config when the new flags are not yet availablefeatureSetresolution gap that PR #1647 addressesBackground
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-resolvedFeatureGateCR from/assets/manifests/— theapi-renderstep inbootkube.shruns first and resolvesfeatureSet(e.g.TechPreviewNoUpgrade) into individual gates, writing the result into the CR status.Because the CEO was parsing the raw install-config, it missed
featureSetresolution entirely, causingConfigurablePKIto not be detected when usingfeatureSet: TechPreviewNoUpgradeinstead of explicitfeatureGatesentries.Approach
Primary path (when
--rendered-manifest-filesand--payload-versionare 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
featureSetinto individual gates viafeatures.FeatureSets()from openshift/api, then layer explicitfeatureGatesoverrides on top. This fallback usesstrings.Cutfor safe parsing and handles thefeatureSet→featureGatesoverride correctly. It should be removed once the installer carries the coordinated change.Changes
pkg/cmd/render/render.go:--rendered-manifest-filesand--payload-versionflagsgetFeatureGates()dispatcher that tries the CR path, falls back to install-configgetFeatureGatesFromManifests()— reads FeatureGate CR, returns enabled/disabled setsgetFeatureGatesFromInstallConfig()— resolvesfeatureSet+featureGatesfrom install-configgetFeatureGatesStatus()functionpkg/cmd/render/render_test.go:writeFeatureGateManifest()test helper for the CR pathTest_getFeatureGatesFromInstallConfigtesting featureSet resolution, overridesTestTemplateDataWithCustomPKIto provide ConfigurablePKI via FeatureGate CRCoordinated change
openshift/installer#10679 passes
--rendered-manifest-filesand--payload-versionto 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/— cleanSummary by CodeRabbit