Skip to content

Add BMCSettingsSet type and controller #471

Merged
Nuckal777 merged 18 commits intomainfrom
BMCSettingsSet
Jan 15, 2026
Merged

Add BMCSettingsSet type and controller #471
Nuckal777 merged 18 commits intomainfrom
BMCSettingsSet

Conversation

@davidgrun
Copy link
Copy Markdown
Contributor

@davidgrun davidgrun commented Sep 16, 2025

Added BMCSettingsSetReconciler to manage BMCSettingsSet resources.

fixes: #419

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced BMCSettingsSet resource to manage and apply BMC settings across multiple baseboard management controllers using label selectors.
    • Added template-based configuration pattern for streamlined settings management.
    • Implemented comprehensive status tracking for BMCSettingsSet operations including availability and completion metrics.
    • Added new RBAC roles for granular access control to BMCSettingsSet resources.
  • Changes

    • Restructured BMCSettings API to use template pattern; BMCRef now required.

✏️ Tip: You can customize this high-level summary in your review settings.

Comment thread PROJECT Outdated
Comment thread internal/controller/bmcsettingsset_controller.go Outdated
Comment thread internal/controller/bmcsettingsset_controller.go Outdated
Comment thread internal/controller/bmcsettingsset_controller.go Outdated
Comment thread internal/controller/bmcsettingsset_controller.go Outdated
Comment on lines +355 to +360
//avoid unnecessary updates
if r.refsAreEqual(bmcSettingsCopy.Spec.ServerMaintenanceRefs, mergedRefs) &&
r.templatesAreEqual(&bmcSettingsCopy.Spec.BMCSettingsTemplate, bmcSettingsTemplate) {
log.V(2).Info("No changes needed for BMCSettings", "BMCSettings", bmcSettingsCopy.Name)
return nil
}
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.

isnt this duplicate?
the method CreateOrPatch already checks before updating/patching the resources.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

correct removed the duplicate check and the unnecessary functions for check this in the new pr

Comment thread internal/controller/bmcsettingsset_controller.go Outdated
Comment thread internal/controller/bmcsettingsset_controller.go Outdated
Comment thread internal/controller/bmcsettingsset_controller.go Outdated
- Added BMCSettingsSetReconciler to manage BMCSettingsSet resources.
- Implemented reconciliation logic for creating, updating, and deleting BMCSettings based on BMC resources.
- Introduced finalizer handling for BMCSettingsSet to manage cleanup during deletion.
- Added methods to handle owned BMCSettings and update their status based on the reconciliation state.
- Implemented logic to create missing BMCSettings and delete orphaned ones.
- Added tests for BMCSettingsSet controller to ensure correct reconciliation behavior.

test: Add unit tests for BMCSettingsSet controller

- Created comprehensive tests for BMCSettingsSet controller to validate reconciliation logic.
- Ensured correct behavior when BMC resources are created, updated, or deleted.
- Verified that BMCSettings are generated correctly based on BMC labels.
- Added tests for updating BMCSettingsSet and ensuring status updates are reflected.

chore: Update test suite to include BMCSettingsSet reconciler

- Modified suite_test.go to register BMCSettingsSetReconciler with the test manager.
- Ensured all resources are cleaned up after tests, including BMCSettingsSet.

fix: Update BMCSettings webhook to include BMCSettingsTemplate

- Refactored BMCSettingsSpec to include BMCSettingsTemplate for better structure.
- Updated webhook tests to validate the new structure of BMCSettings resources.
…cumentation and tests. To ensure make and lint works
…enanceRefs filtering logic with clientutils.ListAndFilter
Comment thread api/v1alpha1/bmcsettings_types.go Outdated
Comment thread internal/controller/bmcsettingsset_controller.go Outdated
Comment thread internal/controller/bmcsettingsset_controller.go Outdated
Comment thread internal/controller/bmcsettingsset_controller.go
Comment thread internal/controller/bmcsettingsset_controller_test.go Outdated
@davidgrun davidgrun self-assigned this Nov 10, 2025
…prove server state handling. For this use the new server claim handling

Update biossettings_controller_test: add const for bar variable that go lint works correctly
Comment on lines +632 to +643
Eventually(Update(serverClaim02, func() {
metautils.SetAnnotation(serverClaim02, metalv1alpha1.ServerMaintenanceApprovalKey, "true")
})).Should(Succeed())

Eventually(Object(bmcSettings02)).Should(SatisfyAny(
HaveField("Status.State", metalv1alpha1.BMCSettingsStateInProgress),
HaveField("Status.State", metalv1alpha1.BMCSettingsStateApplied),
))

Eventually(Object(bmcSettings02)).Should(SatisfyAll(
HaveField("Status.State", metalv1alpha1.BMCSettingsStateApplied),
))
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.

duplicate code?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not in this case. During testing, there were sometimes problems when the transition from InProgress to StateApplied took too long. Therefore, the first step checks that the status is either InProgress or Applied, and then in the second step, we ensure that it actually lands on StateApplied. This makes the test process somewhat more stable.

Comment thread internal/controller/suite_test.go Outdated
Copy link
Copy Markdown
Contributor

@Nuckal777 Nuckal777 left a comment

Choose a reason for hiding this comment

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

Thanks. 👍 PR needs a rebase and the test need to be adjusted due to the removal of DeleteAllMetalResources(). Also, I would consider removing support for ServerMaintenanceRefs on the BMCSettingsSet. It complicates the code a lot and for practical uses I would imagine most people would just like to apply the set and have ServerMaintenances be automatically managed, instead of micro-managing them.

Comment thread config/samples/metal_v1alpha1_bmcsettingsset.yaml Outdated
Comment thread internal/controller/biossettings_controller_test.go Outdated
Comment thread internal/controller/bmcsettingsset_controller.go Outdated
Comment thread internal/controller/bmcsettingsset_controller.go Outdated
Comment thread internal/controller/bmcsettingsset_controller.go Outdated
@afritzler afritzler changed the title feat: Implement BMCSettingsSet controller with reconciliation logic Add BMCSettingsSet type and controller Dec 2, 2025
Copy link
Copy Markdown
Contributor

@Nuckal777 Nuckal777 left a comment

Choose a reason for hiding this comment

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

Thanks, just some comments regarding cosmetics from my side.

Comment thread internal/controller/bmcsettingsset_controller.go Outdated
Comment thread internal/controller/bmcsettingsset_controller_test.go Outdated
@davidgrun davidgrun requested a review from a team as a code owner January 12, 2026 10:14
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 12, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • dist/chart/templates/crd/metal.ironcore.dev_bmcsettingssets.yaml is excluded by !**/dist/**

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Introduces a new BMCSettingsSet API resource with reconciliation controller that manages bulk BMC settings deployment. The controller selects BMCs by label, creates/updates corresponding BMCSettings resources, and aggregates operational status. Refactors BMCSettingsSpec to embed BMCSettingsTemplate, relocating version and settings fields into a reusable template component.

Changes

Cohort / File(s) Summary
Core API Types
api/v1alpha1/bmcsettings_types.go, api/v1alpha1/bmcsettingsset_types.go
Introduces BMCSettingsTemplate type containing version, settings, and maintenance policy. Restructures BMCSettingsSpec to embed the template and move BMCRef to top level as required field. Adds new BMCSettingsSet, BMCSettingsSetSpec, and BMCSettingsSetStatus types with status fields for tracking labeled BMCs and settings application progress.
Generated Code
api/v1alpha1/zz_generated.deepcopy.go
Extends DeepCopy methods for all new types (BMCSettingsSet, BMCSettingsSetList, BMCSettingsSetSpec, BMCSettingsSetStatus, BMCSettingsTemplate). Updates BMCSettingsSpec DeepCopy to handle embedded template and relocated BMCRef.
CRD Definitions
config/crd/bases/metal.ironcore.dev_bmcsettings.yaml, config/crd/bases/metal.ironcore.dev_bmcsettingssets.yaml
Updates BMCSettings CRD schema to reflect BMCRef as required field with updated validation messages. Introduces comprehensive CRD for BMCSettingsSet with cluster scope, label selector, status subresource, and printer columns for status tracking.
RBAC Configuration
config/rbac/bmcsettingsset_admin_role.yaml, config/rbac/bmcsettingsset_editor_role.yaml, config/rbac/bmcsettingsset_viewer_role.yaml
Adds three ClusterRole definitions (admin/editor/viewer) granting granular permissions for BMCSettingsSet resource management and status inspection.
Controller Implementation
internal/controller/bmcsettingsset_controller.go, cmd/manager/main.go
Implements BMCSettingsSetReconciler with reconciliation logic for fetching matching BMCs via selector, creating/updating/deleting BMCSettings, and aggregating status. Registers controller in manager initialization.
Test Suite
internal/controller/bmcsettingsset_controller_test.go, internal/controller/suite_test.go, internal/controller/bmcsettings_controller_test.go, internal/webhook/v1alpha1/bmcsettings_webhook_test.go
Adds comprehensive integration tests validating BMCSettingsSet reconciliation lifecycle, label-based BMC selection, BMCSettings creation/deletion, and status aggregation. Updates existing test code to reflect restructured BMCSettingsSpec with nested template.
Configuration & Samples
config/crd/kustomization.yaml, config/rbac/kustomization.yaml, config/samples/kustomization.yaml, config/samples/metal_v1alpha1_bmcsettingsset.yaml, PROJECT
Registers new CRD and RBAC resources in kustomization. Adds sample BMCSettingsSet manifest with label selector and settings template. Updates PROJECT file to declare new API resource.
Build & Documentation
.golangci.yml, docs/api-reference/api.md
Enables goconst linter on test files. Documents new BMCSettingsSet, BMCSettingsSetSpec, BMCSettingsSetStatus, and BMCSettingsTemplate types with schema and field descriptions.

Sequence Diagram

sequenceDiagram
    participant User as Cluster Administrator
    participant API as Kubernetes API Server
    participant Ctrl as BMCSettingsSetReconciler
    participant BMCList as BMC Resources
    participant SettingsList as BMCSettings Resources

    User->>API: Create BMCSettingsSet with selector & template
    API->>Ctrl: Trigger reconciliation
    Ctrl->>API: Fetch BMCSettingsSet
    Ctrl->>BMCList: List BMCs matching selector
    BMCList-->>Ctrl: Return matching BMCs
    Ctrl->>SettingsList: Get currently owned BMCSettings
    SettingsList-->>Ctrl: Return existing BMCSettings
    Ctrl->>SettingsList: Create BMCSettings for new BMCs
    SettingsList-->>Ctrl: Confirm creation
    Ctrl->>SettingsList: Delete orphaned BMCSettings
    SettingsList-->>Ctrl: Confirm deletion
    Ctrl->>SettingsList: List all owned BMCSettings
    SettingsList-->>Ctrl: Return all BMCSettings
    Ctrl->>Ctrl: Aggregate status (Pending, InProgress, Completed, Failed)
    Ctrl->>API: Patch BMCSettingsSet status
    API-->>User: Status updated
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Suggested labels

size/XL

🚥 Pre-merge checks | ✅ 2 | ❌ 3
❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR introduces out-of-scope changes beyond issue #419: refactoring of BMCSettingsSpec to embed BMCSettingsTemplate and making BMCRef required, which affects existing BMCSettings CRD and requires updates to tests and webhooks. Clarify whether the BMCSettingsSpec refactoring is intentional and required for BMCSettingsSet, or split it into a separate PR to keep the scope focused on adding BMCSettingsSet functionality.
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description is minimal and does not follow the provided template. While it mentions the core change (BMCSettingsSetReconciler) and references issue #419, it lacks the structured 'Proposed Changes' section from the template. Expand the description to include a 'Proposed Changes' section detailing the key changes: BMCSettingsSet type, controller logic, CRD, RBAC rules, and related refactoring to BMCSettingsSpec.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add BMCSettingsSet type and controller' directly aligns with the main change: introducing a new BMCSettingsSet resource type and its controller.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #419: BMCSettingsSet type with spec/status fields, controller with reconciliation logic for label-based BMC selection, creation/deletion of BMCSettings resources, and status aggregation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In @api/v1alpha1/bmcsettingsset_types.go:
- Around line 11-18: BMCSettingsTemplate on BMCSettingsSetSpec is currently
optional (json tag "omitempty") but the controller dereferences it (e.g.,
bmcSettingsSet.Spec.BMCSettingsTemplate.DeepCopy()), causing a panic; fix by
marking BMCSettingsTemplate as required (add the +required comment above the
field and remove "omitempty" from `json:"bmcSettingsTemplate,omitempty"` so it
becomes `json:"bmcSettingsTemplate"`), regenerate CRD/deepcopy if needed, and/or
add a validating webhook that rejects BMCSettingsSet objects missing
BMCSettingsTemplate to enforce the requirement at API validation time.

In @config/samples/metal_v1alpha1_bmcsettingsset.yaml:
- Around line 10-11: Fix the YAML formatting for the serverMaintenancePolicy key
by removing the extra space before the colon so it matches the file's key: value
style; update the line containing serverMaintenancePolicy to use
"serverMaintenancePolicy: OwnerApproval" (no space before the colon).

In @internal/controller/bmcsettingsset_controller.go:
- Around line 233-236: The loop building bmcWithSettings from bmcSettingsList
may panic when bmcSettings.Spec.BMCRef is nil; update the loop over
bmcSettingsList.Items to defensively check that bmcSettings.Spec and
bmcSettings.Spec.BMCRef are non-nil before reading BMCRef.Name (skip or continue
that item and optionally log a warning), ensuring you still populate
bmcWithSettings only with valid names to prevent nil-pointer dereference.
- Around line 197-221: The status is being computed from the initially fetched
ownedBMCSettings which may be stale after createMissingBMCSettings,
deleteOrphanedBMCSettings and patchBMCSettingsFromTemplate; after calling those
three mutating methods re-run getOwnedBMCSettings(ctx, bmcSettingsSet), handle
its error, and use the freshly returned ownedBMCSettings when calling
getOwnedBMCSettingsSetStatus and updateStatus so the FullyLabeledBMCs count and
status reflect current resources.
- Around line 322-325: Remove the unused dead variable bmcNameMap and its
population loop: delete the declaration "bmcNameMap :=
make(map[string]metalv1alpha1.BMC)" and the for-loop "for _, bmc := range
bmcList.Items { bmcNameMap[bmc.Name] = bmc }" (they are never referenced later);
if the map was intended for later lookups, instead implement the required lookup
logic where it's actually needed or use bmcList directly (referencing the
bmcNameMap symbol and the loop over bmcList.Items to locate the code to remove).
- Around line 293-302: The loop over bmcSettingsList.Items dereferences
bmcSettings.Spec.BMCRef.Name without checking that bmcSettings.Spec.BMCRef is
non-nil, risking a panic; update the loop to first guard on
bmcSettings.Spec.BMCRef != nil (or handle nil explicitly) before referencing
.Name when checking bmcMap, and preserve the existing logic that checks
bmcSettings.Status.State == metalv1alpha1.BMCSettingsStateInProgress and calls
r.Delete(ctx, &bmcSettings) for non-matching entries; ensure you use the same
identifiers (bmcSettings, Spec.BMCRef, Name, bmcMap, BMCSettingsStateInProgress,
r.Delete) so the nil-check is consistent with the codebase pattern.

In @internal/controller/test_helper.go:
- Around line 40-44: Remove the redundant cleanup assertion for
BIOSSettingsSetList: the block creating biosSettingsSetList and calling
g.Eventually(ObjectList(biosSettingsSetList)).Should(HaveField("Items",
HaveLen(0))) is duplicated; leave the intended check (the one using
biosSettingsSets) and delete the earlier duplicate that references
BIOSSettingsSetList/biosSettingsSetList so only one cleanup assertion for BIOS
settings remains.
🧹 Nitpick comments (8)
api/v1alpha1/bmcsettings_types.go (1)

38-41: Inconsistent +required marker with omitempty JSON tag and pointer type.

BMCRef is marked +required but declared as a pointer with omitempty. This creates ambiguity:

  • The CRD schema enforces the field as required
  • The Go type allows nil, and omitempty omits it from serialization when nil

For required fields, prefer a non-pointer type or remove omitempty to maintain consistency between the Go type and CRD validation.

Option 1: Use non-pointer type (preferred for required fields)
 	// BMCRef is a reference to a specific BMC to apply setting to.
 	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="BMCRef is immutable"
 	// +required
-	BMCRef *corev1.LocalObjectReference `json:"BMCRef,omitempty"`
+	BMCRef corev1.LocalObjectReference `json:"BMCRef"`
Option 2: Keep pointer but remove omitempty
 	// BMCRef is a reference to a specific BMC to apply setting to.
 	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="BMCRef is immutable"
 	// +required
-	BMCRef *corev1.LocalObjectReference `json:"BMCRef,omitempty"`
+	BMCRef *corev1.LocalObjectReference `json:"BMCRef"`
config/crd/bases/metal.ironcore.dev_bmcsettingssets.yaml (1)

134-136: Consider making bmcSettingsTemplate required in the spec.

The bmcSelector is marked as required, but bmcSettingsTemplate is optional. A BMCSettingsSet without a template would have no settings to apply, making it effectively a no-op. Consider adding bmcSettingsTemplate to the required list for better validation.

             required:
             - bmcSelector
+            - bmcSettingsTemplate
             type: object
config/rbac/bmcsettingsset_admin_role.yaml (1)

20-21: Consider replacing wildcard verb with explicit verbs for principle of least privilege.

The '*' wildcard grants all verbs including potentially unintended ones like deletecollection. While this is a helper role for cluster admins, explicit verbs provide clearer documentation of granted permissions and align with security best practices.

Suggested fix
   verbs:
-  - '*'
+  - create
+  - delete
+  - deletecollection
+  - get
+  - list
+  - patch
+  - update
+  - watch
internal/controller/bmcsettingsset_controller_test.go (1)

209-223: Minor: Redundant deletion check after BMCSettingsSet deletion.

Lines 212 and 214-215 both check if bmcSettingsSet was deleted. The second check is redundant. Additionally, manually deleting bmcSettings01 after bmcSettingsSet deletion may be unnecessary if owner references are configured correctly for cascading deletion.

Suggested simplification
 			By("Deleting the BMCSettingsSet")
 			Expect(k8sClient.Delete(ctx, bmcSettingsSet)).To(Succeed())
 			By("Waiting for BMCSettingsSet and dependent resources to be deleted")
 			Eventually(Get(bmcSettingsSet)).Should(MatchError(ContainSubstring("not found")))

-			By("Checking if the BMCSettingsSet was deleted")
-			Eventually(Get(bmcSettingsSet)).Should(MatchError(ContainSubstring("not found")))
-
-			By("Deleting the BMCSetting resource")
-			Expect(k8sClient.Delete(ctx, bmcSettings01)).To(Succeed())
-
-			By("Ensuring that the BMCSettings resource is removed")
-			Eventually(Get(bmcSettings01)).Should(Satisfy(apierrors.IsNotFound))
-			Consistently(Get(bmcSettings01)).Should(Satisfy(apierrors.IsNotFound))
+			By("Ensuring that the BMCSettings resource was cascaded deleted")
+			Eventually(Get(bmcSettings01)).Should(Satisfy(apierrors.IsNotFound))
internal/controller/bmcsettingsset_controller.go (4)

100-117: Consider simplifying the deletion readiness check.

The condition on line 108 is complex. The logic correctly waits for InProgress BMCSettings with active maintenance refs before removing the finalizer. However, the condition could be clearer.

♻️ Suggested simplification
-	if len(ownedBMCSettings.Items) != len(delTableBMCSettings) || int32(len(ownedBMCSettings.Items)) != bmcSettingsSet.Status.AvailableBMCSettings {
+	allDeletable := len(ownedBMCSettings.Items) == len(delTableBMCSettings)
+	statusInSync := int32(len(ownedBMCSettings.Items)) == bmcSettingsSet.Status.AvailableBMCSettings
+	if !allDeletable || !statusInSync {

144-155: Use idiomatic Go increment operator.

♻️ Use `++` instead of `+= 1`
 	for _, bmcSettings := range bmcSettingsList.Items {
 		switch bmcSettings.Status.State {
 		case metalv1alpha1.BMCSettingsStateApplied:
-			currentStatus.CompletedBMCSettings += 1
+			currentStatus.CompletedBMCSettings++
 		case metalv1alpha1.BMCSettingsStateFailed:
-			currentStatus.FailedBMCSettings += 1
+			currentStatus.FailedBMCSettings++
 		case metalv1alpha1.BMCSettingsStateInProgress:
-			currentStatus.InProgressBMCSettings += 1
+			currentStatus.InProgressBMCSettings++
 		case metalv1alpha1.BMCSettingsStatePending, "":
-			currentStatus.PendingBMCSettings += 1
+			currentStatus.PendingBMCSettings++
 		}
 	}

354-354: Add type assertion safety check.

While controller-runtime guarantees the type from the watch configuration, a defensive type assertion prevents potential panics during debugging or configuration errors.

🛡️ Safe type assertion
-	bmc := obj.(*metalv1alpha1.BMC)
+	bmc, ok := obj.(*metalv1alpha1.BMC)
+	if !ok {
+		log.Error(nil, "Unexpected object type", "type", fmt.Sprintf("%T", obj))
+		return nil
+	}

375-391: Loop variable capture issue with &bmcSettingsSet.

Taking the address of bmcSettingsSet inside the loop can cause issues in Go versions prior to 1.22.

🔧 Explicit copy for loop variable
 		} else {
-			ownedBMCSettings, err := r.getOwnedBMCSettings(ctx, &bmcSettingsSet)
+			settingsSet := bmcSettingsSet // explicit copy
+			ownedBMCSettings, err := r.getOwnedBMCSettings(ctx, &settingsSet)
 			if err != nil {
 				log.Error(err, "Failed to list owned BMCSettings")
 				return nil
 			}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2604637 and 176d9a8.

⛔ Files ignored due to path filters (5)
  • dist/chart/templates/crd/metal.ironcore.dev_bmcsettings.yaml is excluded by !**/dist/**
  • dist/chart/templates/crd/metal.ironcore.dev_bmcsettingssets.yaml is excluded by !**/dist/**
  • dist/chart/templates/rbac/bmcsettingsset_admin_role.yaml is excluded by !**/dist/**
  • dist/chart/templates/rbac/bmcsettingsset_editor_role.yaml is excluded by !**/dist/**
  • dist/chart/templates/rbac/bmcsettingsset_viewer_role.yaml is excluded by !**/dist/**
📒 Files selected for processing (21)
  • .golangci.yml
  • PROJECT
  • api/v1alpha1/bmcsettings_types.go
  • api/v1alpha1/bmcsettingsset_types.go
  • api/v1alpha1/zz_generated.deepcopy.go
  • cmd/manager/main.go
  • config/crd/bases/metal.ironcore.dev_bmcsettings.yaml
  • config/crd/bases/metal.ironcore.dev_bmcsettingssets.yaml
  • config/crd/kustomization.yaml
  • config/rbac/bmcsettingsset_admin_role.yaml
  • config/rbac/bmcsettingsset_editor_role.yaml
  • config/rbac/bmcsettingsset_viewer_role.yaml
  • config/rbac/kustomization.yaml
  • config/samples/kustomization.yaml
  • config/samples/metal_v1alpha1_bmcsettingsset.yaml
  • internal/controller/bmcsettings_controller_test.go
  • internal/controller/bmcsettingsset_controller.go
  • internal/controller/bmcsettingsset_controller_test.go
  • internal/controller/suite_test.go
  • internal/controller/test_helper.go
  • internal/webhook/v1alpha1/bmcsettings_webhook_test.go
🧰 Additional context used
📓 Path-based instructions (4)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Code must follow standard Go formatting and idioms; use clear, explicit error handling with minimal global state
Use small, focused functions; introduce interfaces only when justified

Files:

  • internal/controller/suite_test.go
  • internal/controller/test_helper.go
  • internal/controller/bmcsettings_controller_test.go
  • cmd/manager/main.go
  • api/v1alpha1/bmcsettings_types.go
  • internal/controller/bmcsettingsset_controller_test.go
  • api/v1alpha1/bmcsettingsset_types.go
  • api/v1alpha1/zz_generated.deepcopy.go
  • internal/controller/bmcsettingsset_controller.go
  • internal/webhook/v1alpha1/bmcsettings_webhook_test.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

Tests must be deterministic and not rely on timing assumptions

Files:

  • internal/controller/suite_test.go
  • internal/controller/bmcsettings_controller_test.go
  • internal/controller/bmcsettingsset_controller_test.go
  • internal/webhook/v1alpha1/bmcsettings_webhook_test.go
**/api/**/*_types.go

📄 CodeRabbit inference engine (AGENTS.md)

**/api/**/*_types.go: API type fields must follow Kubernetes naming conventions: camelCase in JSON, PascalCase in Go structs
Use metav1.Time, metav1.Condition, and resource.Quantity for standard Kubernetes types in API definitions
Spec fields must be declarative and user-facing; Status fields must be controller-owned
Conditions in Status must follow Kubernetes conventions with stable Type, correct Status, Reason, and Message fields
No mutable fields in Spec that belong in Status; maintain clear separation between desired state and observed state
Do not make breaking changes to existing API fields without versioning; ensure additive-only changes for existing API versions
Clearly mark and document deprecated fields in API types

Files:

  • api/v1alpha1/bmcsettings_types.go
  • api/v1alpha1/bmcsettingsset_types.go
**/api/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Implement defaulting and validation via webhooks (preferred) or OpenAPI schema annotations

Files:

  • api/v1alpha1/bmcsettings_types.go
  • api/v1alpha1/bmcsettingsset_types.go
  • api/v1alpha1/zz_generated.deepcopy.go
🧠 Learnings (10)
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/controllers/**/*_controller_test.go : Write unit tests for reconcile logic and pure functions

Applied to files:

  • internal/controller/suite_test.go
  • internal/controller/bmcsettings_controller_test.go
  • internal/controller/bmcsettingsset_controller_test.go
  • internal/controller/bmcsettingsset_controller.go
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/controllers/**/*_controller.go : Flag non-idempotent reconcile logic, Spec mutations during reconciliation, and custom patterns duplicating standard Kubernetes behavior

Applied to files:

  • internal/controller/suite_test.go
  • internal/controller/bmcsettings_controller_test.go
  • cmd/manager/main.go
  • internal/controller/bmcsettingsset_controller_test.go
  • internal/controller/bmcsettingsset_controller.go
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/controllers/**/*_controller.go : Prevent hot loops caused by status-only changes triggering reconciliation

Applied to files:

  • internal/controller/suite_test.go
  • internal/controller/bmcsettingsset_controller_test.go
  • internal/controller/bmcsettingsset_controller.go
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/controllers/**/*_controller_test.go : Use Envtest-based tests for controller behavior and API interactions

Applied to files:

  • internal/controller/bmcsettings_controller_test.go
  • internal/controller/bmcsettingsset_controller_test.go
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/api/**/*_types.go : No mutable fields in Spec that belong in Status; maintain clear separation between desired state and observed state

Applied to files:

  • internal/controller/bmcsettings_controller_test.go
  • api/v1alpha1/bmcsettings_types.go
  • api/v1alpha1/bmcsettingsset_types.go
  • internal/webhook/v1alpha1/bmcsettings_webhook_test.go
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/controllers/**/*_controller.go : Status updates must be performed via `Status().Update()` or `Status().Patch()` and separated from spec mutations

Applied to files:

  • internal/controller/bmcsettings_controller_test.go
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/controllers/**/*_controller.go : Reconciliation must be based on current cluster state, not assumptions; handle NotFound errors correctly and gracefully manage partial failures

Applied to files:

  • internal/controller/bmcsettingsset_controller_test.go
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/controllers/**/*_controller.go : Reconcile logic must be idempotent and safe to re-run at any time without side effects

Applied to files:

  • internal/controller/bmcsettingsset_controller_test.go
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/api/**/*_types.go : Spec fields must be declarative and user-facing; Status fields must be controller-owned

Applied to files:

  • api/v1alpha1/bmcsettingsset_types.go
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/controllers/**/*_controller.go : Use `client.Client`, `controllerutil.CreateOrUpdate`, and OwnerReferences from controller-runtime rather than direct API calls

Applied to files:

  • internal/controller/bmcsettingsset_controller.go
🧬 Code graph analysis (7)
internal/controller/suite_test.go (1)
internal/controller/bmcsettingsset_controller.go (1)
  • BMCSettingsSetReconciler (27-30)
internal/controller/test_helper.go (2)
api/v1alpha1/bmcsettingsset_types.go (1)
  • BMCSettingsSetList (60-64)
api/v1alpha1/biossettingsset_types.go (1)
  • BIOSSettingsSetList (60-64)
internal/controller/bmcsettings_controller_test.go (2)
api/v1alpha1/bmcsettings_types.go (1)
  • BMCSettingsTemplate (13-26)
api/v1alpha1/servermaintenance_types.go (3)
  • ServerMaintenancePolicy (51-51)
  • ServerMaintenancePolicyEnforced (57-57)
  • ServerMaintenancePolicyOwnerApproval (55-55)
cmd/manager/main.go (1)
internal/controller/bmcsettingsset_controller.go (1)
  • BMCSettingsSetReconciler (27-30)
internal/controller/bmcsettingsset_controller_test.go (6)
api/v1alpha1/bmcsecret_types.go (3)
  • BMCSecret (24-57)
  • BMCSecretUsernameKeyName (13-13)
  • BMCSecretPasswordKeyName (15-15)
internal/bmcutils/bmcutils.go (1)
  • GetServerNameFromBMCandIndex (219-221)
api/v1alpha1/server_types.go (1)
  • ServerStateAvailable (153-153)
internal/controller/test_helper.go (1)
  • EnsureCleanState (15-62)
api/v1alpha1/bmcsettingsset_types.go (2)
  • BMCSettingsSet (49-55)
  • BMCSettingsSetSpec (11-18)
api/v1alpha1/bmcsettings_types.go (2)
  • BMCSettingsTemplate (13-26)
  • BMCSettings (86-92)
internal/controller/bmcsettingsset_controller.go (3)
api/v1alpha1/bmcsettingsset_types.go (3)
  • BMCSettingsSet (49-55)
  • BMCSettingsSetStatus (21-34)
  • BMCSettingsSetList (60-64)
api/v1alpha1/bmcsettings_types.go (5)
  • BMCSettingsStateApplied (60-60)
  • BMCSettingsStateFailed (62-62)
  • BMCSettingsStatePending (56-56)
  • BMCSettings (86-92)
  • BMCSettingsTemplate (13-26)
api/v1alpha1/bmc_types.go (1)
  • BMCList (259-263)
internal/webhook/v1alpha1/bmcsettings_webhook_test.go (2)
api/v1alpha1/bmcsettings_types.go (1)
  • BMCSettingsTemplate (13-26)
api/v1alpha1/servermaintenance_types.go (2)
  • ServerMaintenancePolicy (51-51)
  • ServerMaintenancePolicyEnforced (57-57)
🪛 Checkov (3.2.334)
config/rbac/bmcsettingsset_admin_role.yaml

[medium] 8-27: Minimize wildcard use in Roles and ClusterRoles

(CKV_K8S_49)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run linter
  • GitHub Check: Run tests
  • GitHub Check: Run test e2e
  • GitHub Check: Run test chart
🔇 Additional comments (26)
.golangci.yml (1)

37-39: LGTM!

Excluding goconst for test files is a sensible choice. Test files often contain repeated string literals for fixtures and assertions where extracting constants would reduce readability without meaningful benefit.

PROJECT (1)

145-152: LGTM!

The new BMCSettingsSet resource entry follows the established pattern of similar set-type resources (e.g., BIOSSettingsSet, BMCVersionSet) and is correctly configured as cluster-scoped with a controller.

config/crd/bases/metal.ironcore.dev_bmcsettings.yaml (2)

67-68: LGTM!

The validation message now correctly references BMCRef instead of the outdated serverRef.


121-123: LGTM!

Making BMCRef a required field aligns with the API type definition and ensures every BMCSettings resource explicitly references its target BMC.

api/v1alpha1/bmcsettings_types.go (2)

13-26: LGTM!

The BMCSettingsTemplate struct provides a clean abstraction for reuse in both BMCSettingsSpec (via inline embedding) and BMCSettingsSetSpec, supporting the template-based approach for bulk BMC settings management.


33-36: Verify if ServerMaintenanceRefs belongs in Status.

The description states this field contains "references to ServerMaintenance objects which are created by the controller." Controller-owned/managed data typically belongs in Status, not Spec. Based on learnings, mutable controller-managed fields should not be in Spec to maintain clear separation between desired state (user-facing) and observed state (controller-owned).

If users don't set this field and only the controller populates it, consider moving it to BMCSettingsStatus.

config/samples/kustomization.yaml (1)

17-17: LGTM!

The new sample resource is correctly added to the kustomization, following the existing naming conventions.

config/crd/kustomization.yaml (1)

19-19: LGTM!

The new CRD base is correctly registered in the kustomization resources.

cmd/manager/main.go (1)

526-532: LGTM!

The BMCSettingsSetReconciler registration follows the established pattern for "Set" reconcilers (BIOSSettingsSetReconciler, BMCVersionSetReconciler) with minimal configuration. The setup is consistent with the reconciler struct definition in internal/controller/bmcsettingsset_controller.go.

config/rbac/kustomization.yaml (1)

55-61: LGTM!

The RBAC roles for BMCSettingsSet (admin, editor, viewer) are correctly added following the established pattern for other CRDs.

internal/controller/suite_test.go (1)

277-280: LGTM!

The BMCSettingsSetReconciler is correctly registered in the test suite, following the established pattern for other reconcilers. This enables the reconciliation path to be exercised during integration tests.

config/rbac/bmcsettingsset_editor_role.yaml (1)

1-33: LGTM!

The editor role is well-defined with explicit verbs for the main resource and appropriately restricts status access to read-only, following the pattern that status updates should be controller-owned.

config/rbac/bmcsettingsset_viewer_role.yaml (1)

1-29: LGTM!

The viewer role correctly restricts access to read-only operations, making it suitable for monitoring purposes as stated in the comments.

internal/controller/bmcsettings_controller_test.go (2)

108-114: LGTM!

Test data correctly reflects the new BMCSettingsSpec structure with BMCRef at the top level and Version, SettingsMap, and ServerMaintenancePolicy nested inside BMCSettingsTemplate. This aligns with the API type changes in api/v1alpha1/bmcsettings_types.go.


87-97: LGTM!

The AfterEach cleanup properly resets the mock, deletes resources, and calls EnsureCleanState() to ensure test isolation. This follows best practices for Envtest-based controller tests.

internal/webhook/v1alpha1/bmcsettings_webhook_test.go (2)

31-38: LGTM!

Webhook test correctly initializes BMCSettingsSpec with the new structure: BMCRef at the top level and BMCSettingsTemplate containing Version, SettingsMap, and ServerMaintenancePolicy. This aligns with the API type definition.


138-160: LGTM!

Good coverage of the in-progress state protection mechanism. The test verifies that updates are blocked during in-progress state unless the ForceUpdateInProgress annotation is set, ensuring safe handling of ongoing operations.

internal/controller/bmcsettingsset_controller_test.go (2)

121-142: LGTM!

The AfterEach cleanup is well-structured with proper checks for server existence before status updates, and uses client.IgnoreNotFound to handle cases where resources may have been deleted during tests. The call to EnsureCleanState() provides a safety net for complete cleanup.


177-190: The field paths in the assertions are correct and require no changes.

The BMCSettingsTemplate struct is embedded in BMCSettingsSpec with the json:",inline" tag (line 31 of bmcsettings_types.go), which promotes its fields to the top level. Therefore, Spec.Version and Spec.SettingsMap are the correct paths, not Spec.BMCSettingsTemplate.Version and Spec.BMCSettingsTemplate.SettingsMap. This is confirmed by the kubebuilder printcolumn at line 81 which uses .spec.version.

api/v1alpha1/bmcsettingsset_types.go (3)

20-34: LGTM!

The status fields follow Kubernetes conventions correctly:

  • All fields are controller-owned counters representing observed state
  • Using int32 with omitempty is appropriate for optional numeric fields
  • Clear documentation explains each field's purpose
  • No mutable fields that should belong in Spec

36-55: LGTM!

The kubebuilder markers are well-configured:

  • Cluster scope is appropriate for cross-namespace BMC management
  • Status subresource enables proper status updates via Status().Update()
  • PrintColumns provide useful kubectl output including version, counts, and age

66-68: LGTM!

The init() function correctly registers both BMCSettingsSet and BMCSettingsSetList with the scheme builder, enabling proper serialization and API discovery.

api/v1alpha1/zz_generated.deepcopy.go (1)

718-878: LGTM - Auto-generated deep copy functions for BMCSettingsSet types.

The generated code correctly handles all new types. The BMCSettingsSetStatus simple assignment is appropriate since it contains only value types, and BMCSettingsTemplate.SettingsMap is properly deep copied.

internal/controller/bmcsettingsset_controller.go (3)

27-32: LGTM - Standard reconciler structure with proper finalizer naming.


397-409: LGTM - Proper controller setup with ownership and label-based watches.

The setup correctly uses:

  • Owns() for automatic reconciliation when owned BMCSettings change
  • Watches() with LabelChangedPredicate to react to BMC label changes affecting selector matches

Based on learnings, this follows recommended controller-runtime patterns.


70-73: The shouldIgnoreReconciliation function exists in internal/controller/helper.go (line 81) and is properly defined to accept any client.Object type. BMCSettingsSet implements this interface, so the usage at line 70 is correct. No issues found.

Comment on lines +11 to +18
type BMCSettingsSetSpec struct {
// BMCSettingsTemplate defines the template for the BMCSettings Resource to be applied to the servers.
BMCSettingsTemplate BMCSettingsTemplate `json:"bmcSettingsTemplate,omitempty"`

// BMCSelector specifies a label selector to identify the servers that are to be selected.
// +required
BMCSelector metav1.LabelSelector `json:"bmcSelector"`
}
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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cd . && find . -name "bmcsettingsset_types.go" -type f

Repository: ironcore-dev/metal-operator

Length of output: 109


🏁 Script executed:

cat -n ./api/v1alpha1/bmcsettingsset_types.go

Repository: ironcore-dev/metal-operator

Length of output: 3674


🏁 Script executed:

# Search for validation webhooks related to BMCSettingsSet
rg -i "bmcsettingsset.*webhook|validate.*bmcsettingsset" --type go

Repository: ironcore-dev/metal-operator

Length of output: 53


🏁 Script executed:

# Search for any validation functions in the file
rg "func.*BMCSettingsSet.*Validate|ValidatingWebhook" --type go -A 5

Repository: ironcore-dev/metal-operator

Length of output: 53


🏁 Script executed:

# Search for webhook files related to BMCSettingsSet
find . -name "*webhook*.go" -type f | grep -i bmcsettingsset

Repository: ironcore-dev/metal-operator

Length of output: 53


🏁 Script executed:

# Search for OpenAPI schema validation annotations on BMCSettingsSet
rg "BMCSettingsSet|bmcSettingsTemplate" --type go -A 3 | grep -i "validation\|openapi\|rule"

Repository: ironcore-dev/metal-operator

Length of output: 53


🏁 Script executed:

# Find BMCSettingsTemplate type definition
rg "type BMCSettingsTemplate" --type go -A 20

Repository: ironcore-dev/metal-operator

Length of output: 1501


🏁 Script executed:

# Search for any existing validation logic in the repository
find . -name "*webhook*.go" -type f -exec head -50 {} + 2>/dev/null | head -100

Repository: ironcore-dev/metal-operator

Length of output: 3957


🏁 Script executed:

# Search specifically for BMCSettingsSet webhook
find . -name "*bmcsettingsset*webhook*" -type f

Repository: ironcore-dev/metal-operator

Length of output: 53


🏁 Script executed:

# Search for any validation logic or webhook setup for BMCSettingsSet
rg "BMCSettingsSet" --type go -B 2 -A 10 | grep -i "webhook\|validate" -A 5 -B 5

Repository: ironcore-dev/metal-operator

Length of output: 53


🏁 Script executed:

# Check if there's a main webhook registration file
find . -path "*/webhook/*" -name "*.go" -type f | head -20

Repository: ironcore-dev/metal-operator

Length of output: 784


🏁 Script executed:

# Search for BMCSettingsSet controller to understand how it uses the template
find . -path "*/controller/*" -name "*bmcsettingsset*" -type f

Repository: ironcore-dev/metal-operator

Length of output: 177


🏁 Script executed:

# Check the BMCSettingsSet controller to understand template usage
cat ./internal/controller/bmcsettingsset_controller.go

Repository: ironcore-dev/metal-operator

Length of output: 14472


🏁 Script executed:

# Check the BMCSettings webhook to see how template validation is done there
cat ./internal/webhook/v1alpha1/bmcsettings_webhook.go

Repository: ironcore-dev/metal-operator

Length of output: 5785


🏁 Script executed:

# Search for any mentions of BMCSettingsSet in webhook setup or validation
rg "BMCSettingsSet" --type go

Repository: ironcore-dev/metal-operator

Length of output: 11051


Mark BMCSettingsTemplate as required and implement validation.

BMCSettingsTemplate must be required in BMCSettingsSetSpec. The controller code directly dereferences the template without nil checks (bmcSettingsSet.Spec.BMCSettingsTemplate.DeepCopy()), which will cause a panic if the field is omitted. Currently, only BMCSelector is marked +required while BMCSettingsTemplate has omitempty, creating a runtime crash scenario. Either mark it as +required in the struct or add a validating webhook to enforce the requirement per Kubernetes API conventions.

🤖 Prompt for AI Agents
In @api/v1alpha1/bmcsettingsset_types.go around lines 11 - 18,
BMCSettingsTemplate on BMCSettingsSetSpec is currently optional (json tag
"omitempty") but the controller dereferences it (e.g.,
bmcSettingsSet.Spec.BMCSettingsTemplate.DeepCopy()), causing a panic; fix by
marking BMCSettingsTemplate as required (add the +required comment above the
field and remove "omitempty" from `json:"bmcSettingsTemplate,omitempty"` so it
becomes `json:"bmcSettingsTemplate"`), regenerate CRD/deepcopy if needed, and/or
add a validating webhook that rejects BMCSettingsSet objects missing
BMCSettingsTemplate to enforce the requirement at API validation time.

Comment thread config/samples/metal_v1alpha1_bmcsettingsset.yaml Outdated
Comment on lines +197 to +221
ownedBMCSettings, err := r.getOwnedBMCSettings(ctx, bmcSettingsSet)
if err != nil {
return ctrl.Result{}, err
}

if err := r.createMissingBMCSettings(ctx, log, bmcList, ownedBMCSettings, bmcSettingsSet); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to create missing BMCSettings resources %w", err)
}
log.V(1).Info("Summary of BMCs and BMCSettings", "BMC count", len(bmcList.Items),
"BMCSettings count", len(ownedBMCSettings.Items))

if err := r.deleteOrphanedBMCSettings(ctx, log, bmcList, ownedBMCSettings); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to delete orphaned BMCSettings resource %w", err)
}

if err := r.patchBMCSettingsFromTemplate(ctx, log, bmcList, bmcSettingsSet, ownedBMCSettings); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to patch BMCSettings from template %w", err)
}

log.V(1).Info("Updating BMCSettingsSet status")
currentStatus := r.getOwnedBMCSettingsSetStatus(ownedBMCSettings)
currentStatus.FullyLabeledBMCs = int32(len(bmcList.Items))
if err := r.updateStatus(ctx, log, currentStatus, bmcSettingsSet); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to update current BMCSettingsSet Status %w", err)
}
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.

⚠️ Potential issue | 🟠 Major

Status is calculated from potentially stale data.

The ownedBMCSettings list is fetched at line 197, but createMissingBMCSettings (line 202), deleteOrphanedBMCSettings (line 208), and patchBMCSettingsFromTemplate (line 212) may modify BMCSettings resources. The status calculation at line 217 uses this stale list, which may report incorrect counts until the next reconciliation.

🔧 Suggested fix: Re-fetch owned BMCSettings before status calculation
 	if err := r.patchBMCSettingsFromTemplate(ctx, log, bmcList, bmcSettingsSet, ownedBMCSettings); err != nil {
 		return ctrl.Result{}, fmt.Errorf("failed to patch BMCSettings from template %w", err)
 	}

 	log.V(1).Info("Updating BMCSettingsSet status")
+	// Re-fetch owned BMCSettings to get accurate counts after create/delete/patch operations
+	ownedBMCSettings, err = r.getOwnedBMCSettings(ctx, bmcSettingsSet)
+	if err != nil {
+		return ctrl.Result{}, fmt.Errorf("failed to re-fetch owned BMCSettings for status update %w", err)
+	}
 	currentStatus := r.getOwnedBMCSettingsSetStatus(ownedBMCSettings)
📝 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
ownedBMCSettings, err := r.getOwnedBMCSettings(ctx, bmcSettingsSet)
if err != nil {
return ctrl.Result{}, err
}
if err := r.createMissingBMCSettings(ctx, log, bmcList, ownedBMCSettings, bmcSettingsSet); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to create missing BMCSettings resources %w", err)
}
log.V(1).Info("Summary of BMCs and BMCSettings", "BMC count", len(bmcList.Items),
"BMCSettings count", len(ownedBMCSettings.Items))
if err := r.deleteOrphanedBMCSettings(ctx, log, bmcList, ownedBMCSettings); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to delete orphaned BMCSettings resource %w", err)
}
if err := r.patchBMCSettingsFromTemplate(ctx, log, bmcList, bmcSettingsSet, ownedBMCSettings); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to patch BMCSettings from template %w", err)
}
log.V(1).Info("Updating BMCSettingsSet status")
currentStatus := r.getOwnedBMCSettingsSetStatus(ownedBMCSettings)
currentStatus.FullyLabeledBMCs = int32(len(bmcList.Items))
if err := r.updateStatus(ctx, log, currentStatus, bmcSettingsSet); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to update current BMCSettingsSet Status %w", err)
}
ownedBMCSettings, err := r.getOwnedBMCSettings(ctx, bmcSettingsSet)
if err != nil {
return ctrl.Result{}, err
}
if err := r.createMissingBMCSettings(ctx, log, bmcList, ownedBMCSettings, bmcSettingsSet); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to create missing BMCSettings resources %w", err)
}
log.V(1).Info("Summary of BMCs and BMCSettings", "BMC count", len(bmcList.Items),
"BMCSettings count", len(ownedBMCSettings.Items))
if err := r.deleteOrphanedBMCSettings(ctx, log, bmcList, ownedBMCSettings); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to delete orphaned BMCSettings resource %w", err)
}
if err := r.patchBMCSettingsFromTemplate(ctx, log, bmcList, bmcSettingsSet, ownedBMCSettings); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to patch BMCSettings from template %w", err)
}
log.V(1).Info("Updating BMCSettingsSet status")
// Re-fetch owned BMCSettings to get accurate counts after create/delete/patch operations
ownedBMCSettings, err = r.getOwnedBMCSettings(ctx, bmcSettingsSet)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to re-fetch owned BMCSettings for status update %w", err)
}
currentStatus := r.getOwnedBMCSettingsSetStatus(ownedBMCSettings)
currentStatus.FullyLabeledBMCs = int32(len(bmcList.Items))
if err := r.updateStatus(ctx, log, currentStatus, bmcSettingsSet); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to update current BMCSettingsSet Status %w", err)
}
🤖 Prompt for AI Agents
In @internal/controller/bmcsettingsset_controller.go around lines 197 - 221, The
status is being computed from the initially fetched ownedBMCSettings which may
be stale after createMissingBMCSettings, deleteOrphanedBMCSettings and
patchBMCSettingsFromTemplate; after calling those three mutating methods re-run
getOwnedBMCSettings(ctx, bmcSettingsSet), handle its error, and use the freshly
returned ownedBMCSettings when calling getOwnedBMCSettingsSetStatus and
updateStatus so the FullyLabeledBMCs count and status reflect current resources.

Comment on lines +293 to +302
for _, bmcSettings := range bmcSettingsList.Items {
if _, ok := bmcMap[bmcSettings.Spec.BMCRef.Name]; !ok {
if bmcSettings.Status.State == metalv1alpha1.BMCSettingsStateInProgress {
log.V(1).Info("Waiting for BMCSettings to move out of InProgress state",
"BMCSettings", bmcSettings.Name, "status", bmcSettings.Status)
continue
}
if err := r.Delete(ctx, &bmcSettings); err != nil {
errs = append(errs, err)
}
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check Go version requirement in go.mod
cat go.mod | head -30 | grep -E "^go [0-9.]+"

Repository: ironcore-dev/metal-operator

Length of output: 80


🏁 Script executed:

#!/bin/bash
# Find BMCSettings type definition to check BMCRef field nullability
find . -name "*.go" -type f | xargs grep -l "type BMCSettings struct" | head -5

Repository: ironcore-dev/metal-operator

Length of output: 106


🏁 Script executed:

#!/bin/bash
# Search for BMCRef definition and its type to understand nullability
find . -name "*.go" -type f | xargs grep -B2 -A2 "BMCRef" | grep -E "(BMCRef|type.*Ref)" | head -20

Repository: ironcore-dev/metal-operator

Length of output: 1998


🏁 Script executed:

#!/bin/bash
# Check BMCSettings type definition and validation constraints for BMCRef
cat api/v1alpha1/bmcsettings_types.go | head -100

Repository: ironcore-dev/metal-operator

Length of output: 3913


🏁 Script executed:

#!/bin/bash
# Check for kubebuilder validation rules that might enforce non-nil BMCRef
grep -B5 -A5 "BMCRef" api/v1alpha1/bmcsettings_types.go

Repository: ironcore-dev/metal-operator

Length of output: 1349


🏁 Script executed:

#!/bin/bash
# Examine the full deleteOrphanedBMCSettings function for context
sed -n '282,308p' internal/controller/bmcsettingsset_controller.go

Repository: ironcore-dev/metal-operator

Length of output: 871


Add nil check for BMCRef before dereferencing.

Line 294 accesses bmcSettings.Spec.BMCRef.Name without validating that BMCRef is not nil. Although marked +required, the field is a pointer and could be nil. Add a defensive check to prevent a potential nil pointer panic, consistent with the pattern used elsewhere in the codebase.

Suggested fix
 	for _, bmcSettings := range bmcSettingsList.Items {
+		if bmcSettings.Spec.BMCRef == nil {
+			continue
+		}
 		if _, ok := bmcMap[bmcSettings.Spec.BMCRef.Name]; !ok {
 			if bmcSettings.Status.State == metalv1alpha1.BMCSettingsStateInProgress {
 				log.V(1).Info("Waiting for BMCSettings to move out of InProgress state",
 					"BMCSettings", bmcSettings.Name, "status", bmcSettings.Status)
 				continue
 			}
 			if err := r.Delete(ctx, &bmcSettings); err != nil {
 				errs = append(errs, err)
 			}
 		}
 	}
🤖 Prompt for AI Agents
In @internal/controller/bmcsettingsset_controller.go around lines 293 - 302, The
loop over bmcSettingsList.Items dereferences bmcSettings.Spec.BMCRef.Name
without checking that bmcSettings.Spec.BMCRef is non-nil, risking a panic;
update the loop to first guard on bmcSettings.Spec.BMCRef != nil (or handle nil
explicitly) before referencing .Name when checking bmcMap, and preserve the
existing logic that checks bmcSettings.Status.State ==
metalv1alpha1.BMCSettingsStateInProgress and calls r.Delete(ctx, &bmcSettings)
for non-matching entries; ensure you use the same identifiers (bmcSettings,
Spec.BMCRef, Name, bmcMap, BMCSettingsStateInProgress, r.Delete) so the
nil-check is consistent with the codebase pattern.

Comment thread internal/controller/bmcsettingsset_controller.go Outdated
Comment thread internal/controller/test_helper.go Outdated
Comment on lines +40 to +44
bmcSettingsSetList := &metalv1alpha1.BMCSettingsSetList{}
g.Eventually(ObjectList(bmcSettingsSetList)).Should(HaveField("Items", HaveLen(0)))

biosSettingsSetList := &metalv1alpha1.BIOSSettingsSetList{}
g.Eventually(ObjectList(biosSettingsSetList)).Should(HaveField("Items", HaveLen(0)))
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.

⚠️ Potential issue | 🟡 Minor

Duplicate cleanup check for BIOSSettingsSetList.

The check for BIOSSettingsSetList at lines 43-44 duplicates the existing check at lines 48-49 (biosSettingsSets). Remove the redundant check.

Proposed fix
 		bmcSettingsSetList := &metalv1alpha1.BMCSettingsSetList{}
 		g.Eventually(ObjectList(bmcSettingsSetList)).Should(HaveField("Items", HaveLen(0)))

-		biosSettingsSetList := &metalv1alpha1.BIOSSettingsSetList{}
-		g.Eventually(ObjectList(biosSettingsSetList)).Should(HaveField("Items", HaveLen(0)))
 		biosVersions := &metalv1alpha1.BIOSVersionList{}
📝 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
bmcSettingsSetList := &metalv1alpha1.BMCSettingsSetList{}
g.Eventually(ObjectList(bmcSettingsSetList)).Should(HaveField("Items", HaveLen(0)))
biosSettingsSetList := &metalv1alpha1.BIOSSettingsSetList{}
g.Eventually(ObjectList(biosSettingsSetList)).Should(HaveField("Items", HaveLen(0)))
bmcSettingsSetList := &metalv1alpha1.BMCSettingsSetList{}
g.Eventually(ObjectList(bmcSettingsSetList)).Should(HaveField("Items", HaveLen(0)))
🤖 Prompt for AI Agents
In @internal/controller/test_helper.go around lines 40 - 44, Remove the
redundant cleanup assertion for BIOSSettingsSetList: the block creating
biosSettingsSetList and calling
g.Eventually(ObjectList(biosSettingsSetList)).Should(HaveField("Items",
HaveLen(0))) is duplicated; leave the intended check (the one using
biosSettingsSets) and delete the earlier duplicate that references
BIOSSettingsSetList/biosSettingsSetList so only one cleanup assertion for BIOS
settings remains.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In @docs/api-reference/api.md:
- Line 508: The description for the `bmcSelector` field incorrectly says
"servers that are to be selected"; update the table cell for `bmcSelector` (type
LabelSelector) to state that it selects BMCs (e.g., "BMCs that are to be
selected" or "BMC objects to be selected") so the wording matches the selector's
purpose; ensure the phrasing is consistent with surrounding docs for other
selectors and retains the reference to LabelSelector.

In @internal/controller/bmcsettingsset_controller_test.go:
- Around line 478-482: The test's "Updating the BMCSettingsSet template" block
only changes SettingsMap but leaves
bmcSettingsSet.Spec.BMCSettingsTemplate.Version unchanged; modify the Update
call in the test (the closure passed to Update for bmcSettingsSet) to set
bmcSettingsSet.Spec.BMCSettingsTemplate.Version to a different value (e.g.,
increment the revision) so the template version actually changes and the test
exercises template propagation.

In @internal/controller/bmcsettingsset_controller.go:
- Around line 233-236: The map population for bmcWithSettings iterates
bmcSettingsList.Items and assumes Spec.BMCRef is non-nil, which can cause a
nil-pointer panic; update the loop that builds bmcWithSettings to check that
bmcSettings.Spec.BMCRef != nil before accessing Spec.BMCRef.Name (skip or log
entries where BMCRef is nil) so the map insertion uses only valid names and
avoids panics.
- Around line 376-385: The loop over ownedBMCSettings accesses
bmcSettings.Spec.BMCRef.Name without checking for nil, which can panic; update
the loop in the reconcile/enqueue logic that iterates ownedBMCSettings.Items to
first verify bmcSettings.Spec.BMCRef != nil before comparing its Name to
bmc.Name (same defensive nil-check pattern used in deleteOrphanedBMCSettings),
and only append the ctrl.Request for bmcSettingsSet when the BMCRef is non-nil
and the names match.
- Around line 310-341: The parameter bmcList is unused in
patchBMCSettingsFromTemplate; remove it from the function signature (change func
(r *BMCSettingsSetReconciler) patchBMCSettingsFromTemplate(ctx context.Context,
log logr.Logger, bmcSettingsSet *metalv1alpha1.BMCSettingsSet, bmcSettingsList
*metalv1alpha1.BMCSettingsList) error) and update all call sites that pass a
bmcList argument to instead call the revised signature (remove the corresponding
argument where patchBMCSettingsFromTemplate is invoked); ensure any references
to the removed bmcList variable inside the function are deleted and run go
vet/go build to confirm the linter error is resolved.
- Around line 293-304: The loop over bmcSettingsList may dereference a nil
pointer when accessing bmcSettings.Spec.BMCRef.Name; add a nil check for
bmcSettings.Spec.BMCRef before using .Name (e.g., if bmcSettings.Spec.BMCRef ==
nil { log.V(1).Info("Skipping BMCSettings with nil BMCRef", "BMCSettings",
bmcSettings.Name); continue }) so you avoid a panic; keep existing logic for
checking BMCSettingsStateInProgress and calling r.Delete(ctx, &bmcSettings) when
appropriate.
🧹 Nitpick comments (1)
internal/controller/bmcsettingsset_controller.go (1)

357-362: Continue loop instead of returning on selector parse error.

Returning nil on a single selector parse error prevents checking other BMCSettingsSets, potentially missing valid reconciliation requests. Consider logging and continuing.

♻️ Suggested improvement
 	selector, err := metav1.LabelSelectorAsSelector(&bmcSettingsSet.Spec.BMCSelector)
 	if err != nil {
 		log.Error(err, "Failed to parse BMCSelector", "BMCSettingsSet", bmcSettingsSet.Name)
-		return nil
+		continue
 	}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 176d9a8 and 33271d0.

📒 Files selected for processing (3)
  • docs/api-reference/api.md
  • internal/controller/bmcsettingsset_controller.go
  • internal/controller/bmcsettingsset_controller_test.go
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Code must follow standard Go formatting and idioms; use clear, explicit error handling with minimal global state
Use small, focused functions; introduce interfaces only when justified

Files:

  • internal/controller/bmcsettingsset_controller.go
  • internal/controller/bmcsettingsset_controller_test.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

Tests must be deterministic and not rely on timing assumptions

Files:

  • internal/controller/bmcsettingsset_controller_test.go
🧠 Learnings (10)
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/controllers/**/*_controller.go : Flag non-idempotent reconcile logic, Spec mutations during reconciliation, and custom patterns duplicating standard Kubernetes behavior

Applied to files:

  • internal/controller/bmcsettingsset_controller.go
  • internal/controller/bmcsettingsset_controller_test.go
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/controllers/**/*_controller.go : Prevent hot loops caused by status-only changes triggering reconciliation

Applied to files:

  • internal/controller/bmcsettingsset_controller.go
  • internal/controller/bmcsettingsset_controller_test.go
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/controllers/**/*_controller.go : Use `client.Client`, `controllerutil.CreateOrUpdate`, and OwnerReferences from controller-runtime rather than direct API calls

Applied to files:

  • internal/controller/bmcsettingsset_controller.go
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/controllers/**/*_controller.go : Status must reflect observed state, not desired state; conditions must be updated consistently and deterministically

Applied to files:

  • internal/controller/bmcsettingsset_controller.go
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/controllers/**/*_controller.go : Status updates must be performed via `Status().Update()` or `Status().Patch()` and separated from spec mutations

Applied to files:

  • internal/controller/bmcsettingsset_controller.go
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/controllers/**/*_controller.go : Reconciliation must be based on current cluster state, not assumptions; handle NotFound errors correctly and gracefully manage partial failures

Applied to files:

  • internal/controller/bmcsettingsset_controller.go
  • internal/controller/bmcsettingsset_controller_test.go
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/controllers/**/*_controller_test.go : Use Envtest-based tests for controller behavior and API interactions

Applied to files:

  • internal/controller/bmcsettingsset_controller_test.go
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/controllers/**/*_controller_test.go : Write unit tests for reconcile logic and pure functions

Applied to files:

  • internal/controller/bmcsettingsset_controller_test.go
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/controllers/**/*_controller.go : Reconcile logic must be idempotent and safe to re-run at any time without side effects

Applied to files:

  • internal/controller/bmcsettingsset_controller_test.go
📚 Learning: 2026-01-09T15:31:43.828Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.828Z
Learning: Applies to **/api/**/*_types.go : No mutable fields in Spec that belong in Status; maintain clear separation between desired state and observed state

Applied to files:

  • docs/api-reference/api.md
🧬 Code graph analysis (1)
internal/controller/bmcsettingsset_controller.go (4)
api/v1alpha1/bmcsettingsset_types.go (3)
  • BMCSettingsSet (49-55)
  • BMCSettingsSetStatus (21-34)
  • BMCSettingsSetList (60-64)
api/v1alpha1/bmcsettings_types.go (6)
  • BMCSettingsStateInProgress (58-58)
  • BMCSettingsStateApplied (60-60)
  • BMCSettingsStateFailed (62-62)
  • BMCSettingsStatePending (56-56)
  • BMCSettings (86-92)
  • BMCSettingsTemplate (13-26)
api/v1alpha1/bmc_types.go (1)
  • BMCList (259-263)
bmc/bmc.go (1)
  • Manager (289-300)
🪛 GitHub Actions: Lint
internal/controller/bmcsettingsset_controller.go

[error] 313-313: golangci-lint failed: bmcList is unused (unparam) in patchBMCSettingsFromTemplate at line 313.

🪛 GitHub Check: Run linter
internal/controller/bmcsettingsset_controller.go

[failure] 313-313:
(*BMCSettingsSetReconciler).patchBMCSettingsFromTemplate - bmcList is unused (unparam)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run test e2e
  • GitHub Check: Run test chart
  • GitHub Check: Run tests
🔇 Additional comments (10)
docs/api-reference/api.md (1)

475-530: BMCSettingsSet API documentation looks complete.

The new BMCSettingsSet, BMCSettingsSetSpec, and BMCSettingsSetStatus types are properly documented with all fields matching the CRD definitions. Status fields correctly track FullyLabeledBMCs and various BMCSettings states (Pending, InProgress, Completed, Failed).

internal/controller/bmcsettingsset_controller.go (4)

159-172: Status update pattern follows best practices.

Using Status().Patch() with MergeFrom for status updates is the correct approach. This separates status mutations from spec changes as recommended by controller-runtime patterns. Based on learnings, status updates should be performed via Status().Patch() and separated from spec mutations.


267-280: Good use of controllerutil.CreateOrPatch with owner reference.

The creation logic properly uses CreateOrPatch for idempotency, sets the owner reference correctly, and handles errors appropriately by collecting them. This follows the controller-runtime best practices mentioned in the learnings.


392-404: Controller wiring looks correct.

The SetupWithManager properly:

  • Watches the primary resource (BMCSettingsSet)
  • Owns secondary resources (BMCSettings) for automatic reconciliation on changes
  • Watches BMC resources with label-change predicates for selector-based triggers

70-73: No action needed — shouldIgnoreReconciliation function exists and correctly handles the type.

The function is defined in internal/controller/helper.go:81 with signature func shouldIgnoreReconciliation(obj client.Object) bool. It accepts the client.Object interface, which *metalv1alpha1.BMCSettingsSet implements as a standard Kubernetes resource. The call at line 70 is valid.

internal/controller/bmcsettingsset_controller_test.go (5)

121-142: AfterEach cleanup is thorough and defensive.

The cleanup logic properly:

  • Sets servers to Available state before deletion (handling potential finalizer requirements)
  • Uses client.IgnoreNotFound for idempotent cleanup
  • Calls EnsureCleanState() for full cleanup

This follows best practices for test cleanup.


144-224: Comprehensive test for basic BMCSettingsSet reconciliation.

This test covers:

  • BMCSettings creation for matching BMCs
  • Owner reference propagation
  • Template field propagation (Version, SettingsMap)
  • No BMCSettings created for non-matching BMCs
  • Status field verification
  • Deletion cascade

Good use of Eventually and Consistently matchers.


315-411: Good test for label-change reconciliation.

This test validates that when BMC labels change to match the selector, the controller creates BMCSettings for the newly matching BMC. This is an important edge case for the watch predicate.


35-120: Test setup is well-structured.

The BeforeEach creates all necessary resources (BMCSecret, two BMCs with different labels, corresponding Servers) and waits for proper state before tests run. Using Eventually to wait for BMC state ensures tests don't rely on timing assumptions.


66-67: ProtocolRedfishLocal constant is properly defined.

The constant exists in api/v1alpha1/bmc_types.go and is correctly used in the test.

Comment thread docs/api-reference/api.md Outdated
Comment on lines +478 to +482
By("Updating the BMCSettingsSet template")
Eventually(Update(bmcSettingsSet, func() {
bmcSettingsSet.Spec.BMCSettingsTemplate.Version = "1.45.455b66-rev4"
bmcSettingsSet.Spec.BMCSettingsTemplate.SettingsMap = bmcSettingNew
})).Should(Succeed())
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.

⚠️ Potential issue | 🟡 Minor

Template update test doesn't actually change the version.

The test claims to update the template, but the version is set to the same value ("1.45.455b66-rev4"). Only SettingsMap is actually changed. Consider using a different version to fully test template propagation.

📝 Suggested fix
 By("Updating the BMCSettingsSet template")
 Eventually(Update(bmcSettingsSet, func() {
-	bmcSettingsSet.Spec.BMCSettingsTemplate.Version = "1.45.455b66-rev4"
+	bmcSettingsSet.Spec.BMCSettingsTemplate.Version = "1.45.455b66-rev5"
 	bmcSettingsSet.Spec.BMCSettingsTemplate.SettingsMap = bmcSettingNew
 })).Should(Succeed())

 By("Checking if the bmcSettings was updated")
-Eventually(Object(bmcSettings01)).Should(HaveField("Spec.Version", Equal("1.45.455b66-rev4")))
+Eventually(Object(bmcSettings01)).Should(HaveField("Spec.Version", Equal("1.45.455b66-rev5")))
🤖 Prompt for AI Agents
In @internal/controller/bmcsettingsset_controller_test.go around lines 478 -
482, The test's "Updating the BMCSettingsSet template" block only changes
SettingsMap but leaves bmcSettingsSet.Spec.BMCSettingsTemplate.Version
unchanged; modify the Update call in the test (the closure passed to Update for
bmcSettingsSet) to set bmcSettingsSet.Spec.BMCSettingsTemplate.Version to a
different value (e.g., increment the revision) so the template version actually
changes and the test exercises template propagation.

Comment on lines +233 to +236
bmcWithSettings := make(map[string]struct{})
for _, bmcSettings := range bmcSettingsList.Items {
bmcWithSettings[bmcSettings.Spec.BMCRef.Name] = struct{}{}
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Jan 12, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add nil check for BMCRef in the lookup map.

Same nil pointer risk as flagged elsewhere. If a BMCSettings exists with a nil BMCRef, this will panic.

🛡️ Defensive fix
 bmcWithSettings := make(map[string]struct{})
 for _, bmcSettings := range bmcSettingsList.Items {
+	if bmcSettings.Spec.BMCRef == nil {
+		continue
+	}
 	bmcWithSettings[bmcSettings.Spec.BMCRef.Name] = struct{}{}
 }
📝 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
bmcWithSettings := make(map[string]struct{})
for _, bmcSettings := range bmcSettingsList.Items {
bmcWithSettings[bmcSettings.Spec.BMCRef.Name] = struct{}{}
}
bmcWithSettings := make(map[string]struct{})
for _, bmcSettings := range bmcSettingsList.Items {
if bmcSettings.Spec.BMCRef == nil {
continue
}
bmcWithSettings[bmcSettings.Spec.BMCRef.Name] = struct{}{}
}
🤖 Prompt for AI Agents
In @internal/controller/bmcsettingsset_controller.go around lines 233 - 236, The
map population for bmcWithSettings iterates bmcSettingsList.Items and assumes
Spec.BMCRef is non-nil, which can cause a nil-pointer panic; update the loop
that builds bmcWithSettings to check that bmcSettings.Spec.BMCRef != nil before
accessing Spec.BMCRef.Name (skip or log entries where BMCRef is nil) so the map
insertion uses only valid names and avoids panics.

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.

@coderabbitai BMCRef is tagged as required and immutable in bmcsettings_types.go

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.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the BMCRef field definition and validation markers in bmcsettings_types.go
cat api/v1alpha1/bmcsettings_types.go | grep -A 5 -B 5 "BMCRef"

Length of output: 1278


@Nuckal777, you're absolutely right! I confirmed that BMCRef has the +required kubebuilder marker in the type definition:

// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="BMCRef is immutable"
// +required
BMCRef *corev1.LocalObjectReference `json:"BMCRef,omitempty"`

With the +required marker, the API server will reject any BMCSettings resource that doesn't have BMCRef set through CRD validation. This means any BMCSettings object returned from a List operation is guaranteed to have BMCRef populated, so the nil check I suggested is unnecessary defensive programming in this context.

Thank you for the clarification!

🐰


✏️ Learnings added
Learnt from: Nuckal777
Repo: ironcore-dev/metal-operator PR: 471
File: internal/controller/bmcsettingsset_controller.go:233-236
Timestamp: 2026-01-14T19:27:09.972Z
Learning: In the ironcore-dev/metal-operator repository, BMCSettings.Spec.BMCRef is marked with +required kubebuilder marker, ensuring API-level validation that prevents nil values; defensive nil checks are not needed when iterating BMCSettings from List operations.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.862Z
Learning: Applies to **/controllers/**/*_controller.go : Reconciliation must be based on current cluster state, not assumptions; handle NotFound errors correctly and gracefully manage partial failures

Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.862Z
Learning: Applies to **/controllers/**/*_controller.go : Flag non-idempotent reconcile logic, Spec mutations during reconciliation, and custom patterns duplicating standard Kubernetes behavior

Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.862Z
Learning: Applies to **/controllers/**/*_controller.go : Prevent hot loops caused by status-only changes triggering reconciliation

Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.862Z
Learning: Applies to **/controllers/**/*_controller_test.go : Write unit tests for reconcile logic and pure functions

Comment on lines +293 to +304
for _, bmcSettings := range bmcSettingsList.Items {
if _, ok := bmcMap[bmcSettings.Spec.BMCRef.Name]; !ok {
if bmcSettings.Status.State == metalv1alpha1.BMCSettingsStateInProgress {
log.V(1).Info("Waiting for BMCSettings to move out of InProgress state",
"BMCSettings", bmcSettings.Name, "status", bmcSettings.Status)
continue
}
if err := r.Delete(ctx, &bmcSettings); err != nil {
errs = append(errs, err)
}
}
}
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.

⚠️ Potential issue | 🟠 Major

Potential nil pointer dereference on BMCRef.

bmcSettings.Spec.BMCRef is a pointer field. If it's nil (e.g., due to a partially created resource or data corruption), accessing .Name will cause a panic.

🛡️ Defensive fix
 for _, bmcSettings := range bmcSettingsList.Items {
+	if bmcSettings.Spec.BMCRef == nil {
+		log.V(1).Info("BMCSettings has nil BMCRef, skipping", "BMCSettings", bmcSettings.Name)
+		continue
+	}
 	if _, ok := bmcMap[bmcSettings.Spec.BMCRef.Name]; !ok {
📝 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 _, bmcSettings := range bmcSettingsList.Items {
if _, ok := bmcMap[bmcSettings.Spec.BMCRef.Name]; !ok {
if bmcSettings.Status.State == metalv1alpha1.BMCSettingsStateInProgress {
log.V(1).Info("Waiting for BMCSettings to move out of InProgress state",
"BMCSettings", bmcSettings.Name, "status", bmcSettings.Status)
continue
}
if err := r.Delete(ctx, &bmcSettings); err != nil {
errs = append(errs, err)
}
}
}
for _, bmcSettings := range bmcSettingsList.Items {
if bmcSettings.Spec.BMCRef == nil {
log.V(1).Info("BMCSettings has nil BMCRef, skipping", "BMCSettings", bmcSettings.Name)
continue
}
if _, ok := bmcMap[bmcSettings.Spec.BMCRef.Name]; !ok {
if bmcSettings.Status.State == metalv1alpha1.BMCSettingsStateInProgress {
log.V(1).Info("Waiting for BMCSettings to move out of InProgress state",
"BMCSettings", bmcSettings.Name, "status", bmcSettings.Status)
continue
}
if err := r.Delete(ctx, &bmcSettings); err != nil {
errs = append(errs, err)
}
}
}
🤖 Prompt for AI Agents
In @internal/controller/bmcsettingsset_controller.go around lines 293 - 304, The
loop over bmcSettingsList may dereference a nil pointer when accessing
bmcSettings.Spec.BMCRef.Name; add a nil check for bmcSettings.Spec.BMCRef before
using .Name (e.g., if bmcSettings.Spec.BMCRef == nil { log.V(1).Info("Skipping
BMCSettings with nil BMCRef", "BMCSettings", bmcSettings.Name); continue }) so
you avoid a panic; keep existing logic for checking BMCSettingsStateInProgress
and calling r.Delete(ctx, &bmcSettings) when appropriate.

Comment on lines +376 to +385
for _, bmcSettings := range ownedBMCSettings.Items {
if bmcSettings.Spec.BMCRef.Name == bmc.Name {
reqs = append(reqs, ctrl.Request{
NamespacedName: client.ObjectKey{
Name: bmcSettingsSet.Name,
Namespace: bmcSettingsSet.Namespace,
},
})
}
}
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.

⚠️ Potential issue | 🟠 Major

Add nil check for BMCRef before accessing .Name.

Same potential nil pointer issue as in deleteOrphanedBMCSettings. If BMCRef is nil, this will panic.

🛡️ Defensive fix
 for _, bmcSettings := range ownedBMCSettings.Items {
-	if bmcSettings.Spec.BMCRef.Name == bmc.Name {
+	if bmcSettings.Spec.BMCRef != nil && bmcSettings.Spec.BMCRef.Name == bmc.Name {
 		reqs = append(reqs, ctrl.Request{
📝 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 _, bmcSettings := range ownedBMCSettings.Items {
if bmcSettings.Spec.BMCRef.Name == bmc.Name {
reqs = append(reqs, ctrl.Request{
NamespacedName: client.ObjectKey{
Name: bmcSettingsSet.Name,
Namespace: bmcSettingsSet.Namespace,
},
})
}
}
for _, bmcSettings := range ownedBMCSettings.Items {
if bmcSettings.Spec.BMCRef != nil && bmcSettings.Spec.BMCRef.Name == bmc.Name {
reqs = append(reqs, ctrl.Request{
NamespacedName: client.ObjectKey{
Name: bmcSettingsSet.Name,
Namespace: bmcSettingsSet.Namespace,
},
})
}
}
🤖 Prompt for AI Agents
In @internal/controller/bmcsettingsset_controller.go around lines 376 - 385, The
loop over ownedBMCSettings accesses bmcSettings.Spec.BMCRef.Name without
checking for nil, which can panic; update the loop in the reconcile/enqueue
logic that iterates ownedBMCSettings.Items to first verify
bmcSettings.Spec.BMCRef != nil before comparing its Name to bmc.Name (same
defensive nil-check pattern used in deleteOrphanedBMCSettings), and only append
the ctrl.Request for bmcSettingsSet when the BMCRef is non-nil and the names
match.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@internal/controller/bmcsettingsset_controller.go`:
- Around line 310-340: The loop in patchBMCSettingsFromTemplate is patching
BMCSettings even when they're in terminal states, which triggers the BMCSettings
controller (and handleSettingAppliedState) to reset state; update
patchBMCSettingsFromTemplate to skip items whose Status.State is a terminal
state (e.g., metalv1alpha1.BMCSettingsStateApplied and
metalv1alpha1.BMCSettingsStateFailed) in addition to the existing InProgress
check, or alternatively add a spec-change predicate to the BMCSettings
controller watch so spec-only updates don't reconcile; locate the logic inside
patchBMCSettingsFromTemplate and implement the guard against terminal states (or
add the predicate on the BMCSettings watch) to prevent re-patching terminal
resources.
♻️ Duplicate comments (2)
internal/controller/bmcsettingsset_controller_test.go (1)

475-478: Template update test only changes SettingsMap, not Version.

The version remains "1.45.455b66-rev4" before and after the update. Consider changing the version to a different value (e.g., "1.45.455b66-rev5") to fully exercise template version propagation to child BMCSettings resources.

📝 Suggested improvement
 By("Updating the BMCSettingsSet template")
 Eventually(Update(bmcSettingsSet, func() {
+	bmcSettingsSet.Spec.BMCSettingsTemplate.Version = "1.45.455b66-rev5"
 	bmcSettingsSet.Spec.BMCSettingsTemplate.SettingsMap = bmcSettingNew
 })).Should(Succeed())

 By("Checking if the bmcSettings was updated")
-Eventually(Object(bmcSettings01)).Should(HaveField("Spec.Version", Equal("1.45.455b66-rev4")))
+Eventually(Object(bmcSettings01)).Should(HaveField("Spec.Version", Equal("1.45.455b66-rev5")))
internal/controller/bmcsettingsset_controller.go (1)

282-308: LGTM!

Correctly handles orphaned BMCSettings deletion while protecting in-progress operations. Based on learnings, BMCRef is +required with API validation, so nil checks are not needed.

🧹 Nitpick comments (3)
internal/controller/bmcsettingsset_controller.go (3)

100-117: Potential issue with status-based deletion guard.

The condition at line 108 includes int32(len(ownedBMCSettings.Items)) != bmcSettingsSet.Status.AvailableBMCSettings which could be problematic:

  1. If a BMCSettingsSet is deleted immediately after creation (before first status update), AvailableBMCSettings would be 0, potentially causing the condition to pass incorrectly when there are owned BMCSettings.

  2. The logic conflates "waiting for terminal state" with "status staleness check" in a single condition.

Consider simplifying to rely solely on the delTableBMCSettings check for deletion readiness:

-	if len(ownedBMCSettings.Items) != len(delTableBMCSettings) || int32(len(ownedBMCSettings.Items)) != bmcSettingsSet.Status.AvailableBMCSettings {
+	if len(ownedBMCSettings.Items) != len(delTableBMCSettings) {

The status update inside the block will keep the status accurate regardless.


197-221: Status calculated from potentially stale data.

The ownedBMCSettings list is fetched at line 197, but createMissingBMCSettings, deleteOrphanedBMCSettings, and patchBMCSettingsFromTemplate may create/delete/update BMCSettings resources. The status calculation at line 217 uses this stale list.

While eventual consistency is acceptable for Kubernetes controllers (the next reconciliation will correct), consider re-fetching for more accurate immediate status reporting, especially for operators monitoring the set during initial rollout.

Optional: Re-fetch for accurate status
 	if err := r.patchBMCSettingsFromTemplate(ctx, log, bmcSettingsSet, ownedBMCSettings); err != nil {
 		return ctrl.Result{}, fmt.Errorf("failed to patch BMCSettings from template %w", err)
 	}

 	log.V(1).Info("Updating BMCSettingsSet status")
+	// Re-fetch to get accurate counts after mutations
+	ownedBMCSettings, err = r.getOwnedBMCSettings(ctx, bmcSettingsSet)
+	if err != nil {
+		return ctrl.Result{}, fmt.Errorf("failed to re-fetch owned BMCSettings %w", err)
+	}
 	currentStatus := r.getOwnedBMCSettingsSetStatus(ownedBMCSettings)

356-385: Early return on error loses other potential matches.

At lines 359-360 and 372-373, returning nil on error aborts processing for all remaining BMCSettingsSet items. This could cause missed reconciliations if one set has an invalid selector.

Consider using continue to process remaining sets:

Proposed fix
 	for _, bmcSettingsSet := range bmcSettingsSetList.Items {
 		selector, err := metav1.LabelSelectorAsSelector(&bmcSettingsSet.Spec.BMCSelector)
 		if err != nil {
 			log.Error(err, "Failed to parse BMCSelector", "BMCSettingsSet", bmcSettingsSet.Name)
-			return nil
+			continue
 		}
 		if selector.Matches(labels.Set(bmc.GetLabels())) {
 			reqs = append(reqs, ctrl.Request{
 				NamespacedName: client.ObjectKey{
 					Name:      bmcSettingsSet.Name,
 					Namespace: bmcSettingsSet.Namespace,
 				},
 			})
 		} else {
 			ownedBMCSettings, err := r.getOwnedBMCSettings(ctx, &bmcSettingsSet)
 			if err != nil {
 				log.Error(err, "Failed to list owned BMCSettings")
-				return nil
+				continue
 			}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 33271d0 and c80d100.

📒 Files selected for processing (9)
  • api/v1alpha1/bmcsettingsset_types.go
  • config/crd/bases/metal.ironcore.dev_bmcsettingssets.yaml
  • config/samples/metal_v1alpha1_bmcsettingsset.yaml
  • docs/api-reference/api.md
  • internal/controller/bmcsettings_controller_test.go
  • internal/controller/bmcsettingsset_controller.go
  • internal/controller/bmcsettingsset_controller_test.go
  • internal/controller/suite_test.go
  • internal/webhook/v1alpha1/bmcsettings_webhook_test.go
✅ Files skipped from review due to trivial changes (1)
  • config/samples/metal_v1alpha1_bmcsettingsset.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • api/v1alpha1/bmcsettingsset_types.go
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Code must follow standard Go formatting and idioms; use clear, explicit error handling with minimal global state
Use small, focused functions; introduce interfaces only when justified

Files:

  • internal/controller/suite_test.go
  • internal/controller/bmcsettings_controller_test.go
  • internal/webhook/v1alpha1/bmcsettings_webhook_test.go
  • internal/controller/bmcsettingsset_controller_test.go
  • internal/controller/bmcsettingsset_controller.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

Tests must be deterministic and not rely on timing assumptions

Files:

  • internal/controller/suite_test.go
  • internal/controller/bmcsettings_controller_test.go
  • internal/webhook/v1alpha1/bmcsettings_webhook_test.go
  • internal/controller/bmcsettingsset_controller_test.go
🧠 Learnings (12)
📓 Common learnings
Learnt from: Nuckal777
Repo: ironcore-dev/metal-operator PR: 471
File: internal/controller/bmcsettingsset_controller.go:233-236
Timestamp: 2026-01-14T19:27:09.972Z
Learning: In the ironcore-dev/metal-operator repository, BMCSettings.Spec.BMCRef is marked with +required kubebuilder marker, ensuring API-level validation that prevents nil values; defensive nil checks are not needed when iterating BMCSettings from List operations.
📚 Learning: 2026-01-09T15:31:43.862Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.862Z
Learning: Applies to **/controllers/**/*_controller_test.go : Write unit tests for reconcile logic and pure functions

Applied to files:

  • internal/controller/suite_test.go
  • internal/controller/bmcsettingsset_controller_test.go
  • internal/controller/bmcsettingsset_controller.go
📚 Learning: 2026-01-09T15:31:43.862Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.862Z
Learning: Applies to **/controllers/**/*_controller.go : Flag non-idempotent reconcile logic, Spec mutations during reconciliation, and custom patterns duplicating standard Kubernetes behavior

Applied to files:

  • internal/controller/suite_test.go
  • internal/controller/bmcsettingsset_controller_test.go
  • internal/controller/bmcsettingsset_controller.go
📚 Learning: 2026-01-09T15:31:43.862Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.862Z
Learning: Applies to **/controllers/**/*_controller.go : Reconciliation must be based on current cluster state, not assumptions; handle NotFound errors correctly and gracefully manage partial failures

Applied to files:

  • internal/controller/suite_test.go
  • internal/controller/bmcsettingsset_controller_test.go
  • internal/controller/bmcsettingsset_controller.go
📚 Learning: 2026-01-09T15:31:43.862Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.862Z
Learning: Applies to **/controllers/**/*_controller_test.go : Use Envtest-based tests for controller behavior and API interactions

Applied to files:

  • internal/controller/suite_test.go
  • internal/controller/bmcsettings_controller_test.go
  • internal/controller/bmcsettingsset_controller_test.go
📚 Learning: 2026-01-14T19:27:09.972Z
Learnt from: Nuckal777
Repo: ironcore-dev/metal-operator PR: 471
File: internal/controller/bmcsettingsset_controller.go:233-236
Timestamp: 2026-01-14T19:27:09.972Z
Learning: In the ironcore-dev/metal-operator repository, BMCSettings.Spec.BMCRef is marked with +required kubebuilder marker, ensuring API-level validation that prevents nil values; defensive nil checks are not needed when iterating BMCSettings from List operations.

Applied to files:

  • internal/controller/suite_test.go
  • internal/controller/bmcsettings_controller_test.go
  • internal/webhook/v1alpha1/bmcsettings_webhook_test.go
  • internal/controller/bmcsettingsset_controller_test.go
  • config/crd/bases/metal.ironcore.dev_bmcsettingssets.yaml
  • docs/api-reference/api.md
  • internal/controller/bmcsettingsset_controller.go
📚 Learning: 2026-01-09T15:31:43.862Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.862Z
Learning: Applies to **/api/**/*_types.go : No mutable fields in Spec that belong in Status; maintain clear separation between desired state and observed state

Applied to files:

  • internal/controller/bmcsettings_controller_test.go
  • internal/webhook/v1alpha1/bmcsettings_webhook_test.go
  • docs/api-reference/api.md
📚 Learning: 2026-01-09T15:31:43.862Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.862Z
Learning: Applies to **/controllers/**/*_controller.go : Status updates must be performed via `Status().Update()` or `Status().Patch()` and separated from spec mutations

Applied to files:

  • internal/controller/bmcsettings_controller_test.go
  • internal/controller/bmcsettingsset_controller.go
📚 Learning: 2026-01-09T15:31:43.862Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.862Z
Learning: Applies to **/controllers/**/*_controller.go : Prevent hot loops caused by status-only changes triggering reconciliation

Applied to files:

  • internal/controller/bmcsettingsset_controller_test.go
  • internal/controller/bmcsettingsset_controller.go
📚 Learning: 2026-01-09T15:31:43.862Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.862Z
Learning: Applies to **/controllers/**/*_controller.go : Reconcile logic must be idempotent and safe to re-run at any time without side effects

Applied to files:

  • internal/controller/bmcsettingsset_controller_test.go
  • internal/controller/bmcsettingsset_controller.go
📚 Learning: 2026-01-09T15:31:43.862Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.862Z
Learning: Applies to **/controllers/**/*_controller.go : Use `client.Client`, `controllerutil.CreateOrUpdate`, and OwnerReferences from controller-runtime rather than direct API calls

Applied to files:

  • internal/controller/bmcsettingsset_controller.go
📚 Learning: 2026-01-09T15:31:43.862Z
Learnt from: CR
Repo: ironcore-dev/metal-operator PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T15:31:43.862Z
Learning: Applies to **/controllers/**/*_controller.go : Status must reflect observed state, not desired state; conditions must be updated consistently and deterministically

Applied to files:

  • internal/controller/bmcsettingsset_controller.go
🧬 Code graph analysis (5)
internal/controller/suite_test.go (2)
internal/controller/bmcsettingsset_controller.go (1)
  • BMCSettingsSetReconciler (27-30)
api/v1alpha1/bmcsettingsset_types.go (1)
  • BMCSettingsSetList (61-65)
internal/controller/bmcsettings_controller_test.go (2)
api/v1alpha1/bmcsettings_types.go (1)
  • BMCSettingsTemplate (13-26)
api/v1alpha1/servermaintenance_types.go (2)
  • ServerMaintenancePolicy (51-51)
  • ServerMaintenancePolicyEnforced (57-57)
internal/webhook/v1alpha1/bmcsettings_webhook_test.go (2)
api/v1alpha1/bmcsettings_types.go (1)
  • BMCSettingsTemplate (13-26)
api/v1alpha1/servermaintenance_types.go (2)
  • ServerMaintenancePolicy (51-51)
  • ServerMaintenancePolicyEnforced (57-57)
internal/controller/bmcsettingsset_controller_test.go (4)
api/v1alpha1/bmcsecret_types.go (3)
  • BMCSecret (24-57)
  • BMCSecretUsernameKeyName (13-13)
  • BMCSecretPasswordKeyName (15-15)
api/v1alpha1/endpoint_types.go (1)
  • Endpoint (35-41)
api/v1alpha1/common_types.go (1)
  • IP (32-34)
internal/bmcutils/bmcutils.go (1)
  • GetServerNameFromBMCandIndex (219-221)
internal/controller/bmcsettingsset_controller.go (4)
api/v1alpha1/bmcsettingsset_types.go (3)
  • BMCSettingsSet (50-56)
  • BMCSettingsSetStatus (22-35)
  • BMCSettingsSetList (61-65)
api/v1alpha1/bmcsettings_types.go (6)
  • BMCSettingsStateInProgress (58-58)
  • BMCSettingsList (97-101)
  • BMCSettingsStateApplied (60-60)
  • BMCSettingsStateFailed (62-62)
  • BMCSettingsStatePending (56-56)
  • BMCSettings (86-92)
api/v1alpha1/bmc_types.go (1)
  • BMCList (259-263)
bmc/bmc.go (1)
  • Manager (289-300)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run linter
  • GitHub Check: Run test e2e
  • GitHub Check: Run test chart
  • GitHub Check: Run tests
🔇 Additional comments (33)
internal/controller/suite_test.go (2)

279-283: LGTM!

The BMCSettingsSetReconciler registration follows the established pattern used by other reconcilers in the test suite, correctly passing Client and Scheme from the manager.


354-356: LGTM!

The BMCSettingsSetList cleanup is correctly placed before BMCSettingsList, ensuring parent resources are verified clean before child resources. This follows the existing cleanup pattern.

docs/api-reference/api.md (5)

21-21: LGTM!

The BMCSettingsSet resource is correctly added to the resource types list, maintaining alphabetical ordering consistent with other resources.


475-530: LGTM!

The BMCSettingsSet, BMCSettingsSetSpec, and BMCSettingsSetStatus documentation follows the established patterns used by similar resources (e.g., BIOSSettingsSet, BMCVersionSet). The status fields correctly document the tracking counts as specified in the PR objectives.


536-549: LGTM!

The BMCSettingsSpec documentation correctly reflects the restructured API with BMCRef at the top level and the embedded BMCSettingsTemplate.


588-605: LGTM!

The BMCSettingsTemplate documentation correctly describes the reusable template structure with Version, SettingsMap, and ServerMaintenancePolicy fields, following the pattern established by BIOSSettingsTemplate.


1383-1383: LGTM!

The ServerMaintenancePolicy appears-in list correctly includes BMCSettingsTemplate, reflecting its use in the new template structure.

internal/webhook/v1alpha1/bmcsettings_webhook_test.go (5)

31-36: LGTM!

The test correctly initializes BMCSettingsSpec with BMCRef at the top level and BMCSettingsTemplate containing the version, settings, and maintenance policy, aligning with the restructured API.


60-65: LGTM!

Consistent use of the nested BMCSettingsTemplate structure for the duplicate BMC reference validation test.


78-83: LGTM!

The non-duplicate BMC reference test correctly uses the new template structure.


96-101: LGTM!

The update validation test correctly initializes the spec with the nested template structure.


119-124: LGTM!

Consistent template structure usage for the non-duplicate update test case.

internal/controller/bmcsettings_controller_test.go (6)

109-114: LGTM!

The test correctly uses the nested BMCSettingsTemplate structure with BMCRef at the top level, consistent with the API restructuring.


141-146: LGTM!

Consistent template structure for the "no changes" completion test case.


185-190: LGTM!

The settings change test correctly uses the nested template structure with ServerMaintenancePolicyEnforced.


273-278: LGTM!

The maintenance approval test correctly uses ServerMaintenancePolicyOwnerApproval within the template structure.


374-379: LGTM!

The version upgrade wait test correctly initializes the spec with a different version to trigger the upgrade flow.


481-486: LGTM!

The retry annotation test correctly uses the nested template structure.

internal/controller/bmcsettingsset_controller_test.go (7)

1-21: LGTM!

The test file follows proper structure with appropriate imports and uses Envtest-based testing as per coding guidelines.


35-120: LGTM!

The BeforeEach setup correctly creates the test fixtures: a BMCSecret, two BMCs with different labels (to test selector matching), and waits for corresponding Server resources and BMC state to be enabled. This provides a solid foundation for testing the BMCSettingsSet controller behavior.


121-142: LGTM!

The AfterEach cleanup correctly handles potential not-found errors when deleting resources and ensures servers are in available state before deletion. The use of client.IgnoreNotFound is appropriate for cleanup code.


144-221: LGTM!

This test comprehensively validates:

  • BMCSettings creation for matching BMCs based on label selector
  • Correct field propagation from BMCSettingsTemplate to BMCSettings.Spec
  • Owner reference setup with controller and blockOwnerDeletion flags
  • Non-creation of BMCSettings for non-matching BMCs
  • Status count accuracy (FullyLabeledBMCs, AvailableBMCSettings, CompletedBMCSettings)

223-310: LGTM!

This test correctly verifies that deleting a BMC triggers deletion of its associated BMCSettings resource and updates the BMCSettingsSet status counts to reflect the removal.


312-408: LGTM!

This test effectively validates that changing BMC labels to match the selector triggers creation of a new BMCSettings resource with correct field values and owner references. The status count update to reflect 2 matching BMCs is also verified.


498-508: LGTM!

Good practice to clean up ServerMaintenance objects that may have been created during the test to prevent interference with other tests.

config/crd/bases/metal.ironcore.dev_bmcsettingssets.yaml (1)

1-175: LGTM! Well-structured CRD definition.

The CRD correctly defines the BMCSettingsSet resource with:

  • Appropriate cluster scope for cross-namespace BMC management
  • Comprehensive printer columns for operational visibility
  • Proper OpenAPI v3 validation schema with required field constraints
  • Status subresource enabled for proper status updates

The schema aligns with the PR objectives for label-based BMC selection and status tracking.

internal/controller/bmcsettingsset_controller.go (7)

27-33: LGTM!

The reconciler structure and finalizer constant follow the established patterns in this repository.


41-63: LGTM!

Standard reconciliation entrypoint with proper NotFound handling and deletion timestamp check.


65-84: LGTM!

Clean reconciliation flow with proper finalizer management and delegation to handleBMCSettings.


128-157: LGTM!

Clean helper functions with proper use of clientutils.ListAndFilterControlledBy and comprehensive state counting including empty state fallback.


159-172: LGTM!

Correct status update pattern using DeepCopy and Status().Patch() with MergeFrom.


226-280: LGTM!

Well-implemented with:

  • Proper handling of BMCs that already have a different BMCSettingRef
  • DNS1123 name length compliance with GenerateName fallback
  • Idempotent CreateOrPatch with controller reference

Based on learnings, BMCRef is marked +required in the API, so the access at line 235 is safe.


390-402: LGTM!

Well-configured controller setup with:

  • Primary watch on BMCSettingsSet
  • Ownership tracking for BMCSettings
  • BMC label change watch with appropriate predicate filter

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +310 to +340
func (r *BMCSettingsSetReconciler) patchBMCSettingsFromTemplate(
ctx context.Context,
log logr.Logger,
bmcSettingsSet *metalv1alpha1.BMCSettingsSet,
bmcSettingsList *metalv1alpha1.BMCSettingsList,
) error {
if len(bmcSettingsList.Items) == 0 {
log.V(1).Info("No BMCSettings found, skipping spec template update")
return nil
}

var errs []error
for _, bmcSettings := range bmcSettingsList.Items {
if bmcSettings.Status.State == metalv1alpha1.BMCSettingsStateInProgress {
log.V(1).Info("Skipping BMCSettings spec patching as it is in InProgress state")
continue
}

opResult, err := controllerutil.CreateOrPatch(ctx, r.Client, &bmcSettings, func() error {
bmcSettings.Spec.BMCSettingsTemplate = *bmcSettingsSet.Spec.BMCSettingsTemplate.DeepCopy()
return nil
})
if err != nil {
errs = append(errs, err)
}
if opResult != controllerutil.OperationResultNone {
log.V(1).Info("Patched BMCSettings with updated spec", "BMCSettings", bmcSettings.Name, "Operation", opResult)
}
}
return errors.Join(errs...)
}
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, find the BMCSettings controller file
find . -name "*bmcsettings_controller.go" -type f | head -5

Repository: ironcore-dev/metal-operator

Length of output: 118


🏁 Script executed:

# Check the BMCSettings controller's SetupWithManager to see how it watches for changes
rg -n -A 30 "BMCSettingsReconciler.*SetupWithManager" --type go

Repository: ironcore-dev/metal-operator

Length of output: 1059


🏁 Script executed:

# Look at the entire watch setup for BMCSettings
rg -n -B 5 -A 20 "For\(&metalv1alpha1\.BMCSettings" internal/controller/ --type go

Repository: ironcore-dev/metal-operator

Length of output: 2603


🏁 Script executed:

# Check the BMCSettingsReconciler Reconcile method to see how it handles spec changes
rg -n -A 50 "func \(r \*BMCSettingsReconciler\) Reconcile" internal/controller/bmcsettings_controller.go | head -80

Repository: ironcore-dev/metal-operator

Length of output: 1768


🏁 Script executed:

# Check where patchBMCSettingsFromTemplate is called in BMCSettingsSetReconciler
rg -n "patchBMCSettingsFromTemplate" internal/controller/bmcsettingsset_controller.go

Repository: ironcore-dev/metal-operator

Length of output: 243


🏁 Script executed:

# Look at the Reconcile logic in BMCSettingsSetReconciler to see when patching is triggered
rg -n -A 100 "func \(r \*BMCSettingsSetReconciler\) Reconcile" internal/controller/bmcsettingsset_controller.go | head -120

Repository: ironcore-dev/metal-operator

Length of output: 4155


🏁 Script executed:

# Check the handleBMCSettings method to see the full context of when patching happens
rg -n -A 50 "func \(r \*BMCSettingsSetReconciler\) handleBMCSettings" internal/controller/bmcsettingsset_controller.go

Repository: ironcore-dev/metal-operator

Length of output: 2235


🏁 Script executed:

# Check if there's any guard or condition that prevents re-patching on spec changes
# Look for generation tracking or update guards in BMCSettings
rg -n "ObjectMeta" internal/controller/bmcsettings_controller.go -A 5 -B 5 | head -40

Repository: ironcore-dev/metal-operator

Length of output: 478


🏁 Script executed:

# Check the reconcile method in BMCSettings to see how it handles spec changes
rg -n -A 200 "func \(r \*BMCSettingsReconciler\) reconcile\(" internal/controller/bmcsettings_controller.go | head -250

Repository: ironcore-dev/metal-operator

Length of output: 9716


🏁 Script executed:

# Check if BMCSettingsTemplate changes compared to what's already in spec
# Look for any idempotency checks or generation tracking
rg -n "Generation\|ObservedGeneration" internal/controller/bmcsettings_controller.go

Repository: ironcore-dev/metal-operator

Length of output: 53


🏁 Script executed:

# Check what handleSettingAppliedState does to understand terminal state behavior
rg -n -A 30 "func \(r \*BMCSettingsReconciler\) handleSettingAppliedState" internal/controller/bmcsettings_controller.go

Repository: ironcore-dev/metal-operator

Length of output: 1070


🏁 Script executed:

# Check if there's any predicate on the watch to filter spec changes
rg -n "predicate\." internal/controller/bmcsettings_controller.go

Repository: ironcore-dev/metal-operator

Length of output: 53


Re-patching BMCSettings in terminal states will trigger unnecessary state resets.

The function patches Applied and Failed BMCSettings unconditionally. Since the BMCSettings controller watches all spec changes (no predicates), each spec patch triggers reconciliation. The handleSettingAppliedState logic (line 457-482) detects the spec update as a diff and resets the state back to empty/Pending, causing unnecessary reprocessing cycles.

If template updates are frequent, this creates churn. Consider skipping terminal states like Applied or Failed, or add a predicate to the BMCSettings watch to ignore spec-only changes.

🤖 Prompt for AI Agents
In `@internal/controller/bmcsettingsset_controller.go` around lines 310 - 340, The
loop in patchBMCSettingsFromTemplate is patching BMCSettings even when they're
in terminal states, which triggers the BMCSettings controller (and
handleSettingAppliedState) to reset state; update patchBMCSettingsFromTemplate
to skip items whose Status.State is a terminal state (e.g.,
metalv1alpha1.BMCSettingsStateApplied and metalv1alpha1.BMCSettingsStateFailed)
in addition to the existing InProgress check, or alternatively add a spec-change
predicate to the BMCSettings controller watch so spec-only updates don't
reconcile; locate the logic inside patchBMCSettingsFromTemplate and implement
the guard against terminal states (or add the predicate on the BMCSettings
watch) to prevent re-patching terminal resources.

@Nuckal777 Nuckal777 merged commit 8d58f19 into main Jan 15, 2026
15 checks passed
@Nuckal777 Nuckal777 deleted the BMCSettingsSet branch January 15, 2026 09:58
@github-project-automation github-project-automation Bot moved this to Done in Roadmap Jan 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api-change area/metal-automation documentation Improvements or additions to documentation enhancement New feature or request size/XXL

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Add BMCSettingSet type and controller

4 participants