Skip to content

Conversation

@defo89
Copy link
Contributor

@defo89 defo89 commented Jan 27, 2026

Proposed Changes

  • To align with CCM flow, we will support ServerMaintenanceApproval label on ServerClaims
  • Later, the annotation will be removed

Summary by CodeRabbit

  • Chores

    • Maintenance approval handling now recognizes both annotation and label metadata for approval, improving flexibility in state tracking.
  • Tests

    • Updated test suites to reflect the dual metadata approach for maintenance approvals.
  • Documentation

    • Clarified the approval metadata comment to reference a label key (in addition to annotations).

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

@coderabbitai
Copy link

coderabbitai bot commented Jan 27, 2026

Walkthrough

Updates ServerMaintenance approval handling to accept either an annotation or a label on ServerClaim; controller checks both, clears the key during cleanup, and tests updated to set both metadata channels (annotation + label) using the trueValue constant.

Changes

Cohort / File(s) Summary
API constant doc
api/v1alpha1/servermaintenance_types.go
Documentation comment for ServerMaintenanceApprovalKey changed from "annotation key" to "label key"
Controller logic
internal/controller/servermaintenance_controller.go
Pending-state approval check now treats ServerClaim as approved if either the ServerMaintenanceApprovalKey annotation OR label is present; on approval updates Server ref and patches state to InMaintenance; cleanup removes the approval key from claim metadata
Controller tests
internal/controller/biossettings_controller_test.go, internal/controller/biosversion_controller_test.go, internal/controller/bmcsettings_controller_test.go, internal/controller/bmcversion_controller_test.go, internal/controller/servermaintenance_controller_test.go
Tests updated to set approval via trueValue constant (replacing literal "true") and to add a corresponding label alongside the annotation; expected label maps updated accordingly

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Controller as ServerMaintenanceController
  participant K8sAPI as Kubernetes API (ServerClaim)
  participant Server as Server resource

  Controller->>K8sAPI: Fetch ServerClaim
  alt approval via annotation or label present
    note right of Controller: checks annotation OR label for ServerMaintenanceApprovalKey
    Controller->>K8sAPI: Update Server reference on ServerMaintenance (set Server)
    Controller->>K8sAPI: Patch ServerMaintenance state -> InMaintenance
    K8sAPI-->>Controller: Persisted updates (200)
    Controller->>Server: (optional) ensure server reference consistency
  else no approval found
    Controller->>Controller: Log "waiting for approval"
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

Suggested labels

area/metal-automation

Suggested reviewers

  • afritzler
  • Nuckal777
  • nagadeesh-nagaraja
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Support ServerMaintenanceApproval label' directly and clearly describes the main change: adding label support for server maintenance approval alongside the existing annotation.
Description check ✅ Passed The description covers the main objective (supporting the ServerMaintenanceApproval label) and references alignment with CCM flow, but only provides 2 of 3 bullet points specified in the template.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

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

🤖 Fix all issues with AI agents
In `@api/v1alpha1/servermaintenance_types.go`:
- Around line 16-17: Update the comment for ServerMaintenanceApprovalKey to
clarify semantics: state that the string
"metal.ironcore.dev/maintenance-approval" is the preferred label key for storing
server maintenance approval, but note that it is still read/written as a
deprecated annotation in controllers and tests (include "deprecated" and
migration/removal intent), and document which metadata form should be used going
forward so consumers know label is preferred and annotation support is legacy.

In `@internal/controller/servermaintenance_controller.go`:
- Around line 156-171: When cleaning up a ServerMaintenance object, ensure you
remove the approval key from both annotations and labels to avoid stale
auto-approvals: in the cleanup path that currently calls
updateServerRef/patchMaintenanceState (use the serverClaim and server variables
and the existing updateServerRef method), check for
metalv1alpha1.ServerMaintenanceApprovalKey in serverClaim.GetAnnotations() and
serverClaim.GetLabels() and remove it from both maps, persist the change via the
same update/patch flow (e.g., call updateServerRef or client.Update on the
ServerClaim) so the approval label/annotation is cleared during maintenance
teardown.

Comment on lines 156 to +171
annotations := serverClaim.GetAnnotations()
if _, ok := annotations[metalv1alpha1.ServerMaintenanceApprovalKey]; !ok {
log.V(1).Info("Server not approved for maintenance, waiting for approval", "Server", server.Name)
return ctrl.Result{}, nil
}
log.V(1).Info("Server approved for maintenance", "Server", server.Name)
if err = r.updateServerRef(ctx, log, maintenance, server); err != nil {
return ctrl.Result{}, err
}
if modified, err := r.patchMaintenanceState(ctx, maintenance, metalv1alpha1.ServerMaintenanceStateInMaintenance); err != nil || modified {
return ctrl.Result{}, err
labels := serverClaim.GetLabels()
_, hasAnnotation := annotations[metalv1alpha1.ServerMaintenanceApprovalKey]
_, hasLabel := labels[metalv1alpha1.ServerMaintenanceApprovalKey]

if hasAnnotation || hasLabel {
log.V(1).Info("Server approved for maintenance", "Server", server.Name)
if err = r.updateServerRef(ctx, log, maintenance, server); err != nil {
return ctrl.Result{}, err
}
if modified, err := r.patchMaintenanceState(ctx, maintenance, metalv1alpha1.ServerMaintenanceStateInMaintenance); err != nil || modified {
return ctrl.Result{}, err
}
}
log.V(1).Info("Server not approved for maintenance, waiting for approval", "Server", server.Name)
return ctrl.Result{}, nil
Copy link

@coderabbitai coderabbitai bot Jan 27, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Clear approval label on cleanup to avoid stale auto-approvals.

With label-based approval now accepted, cleanup must also remove the approval label; otherwise a previously approved ServerClaim could silently auto-approve future maintenances.

🔧 Suggested fix (cleanup)
-	metautils.DeleteLabels(serverClaim, []string{
-		metalv1alpha1.ServerMaintenanceNeededLabelKey,
-	})
+	metautils.DeleteLabels(serverClaim, []string{
+		metalv1alpha1.ServerMaintenanceNeededLabelKey,
+		metalv1alpha1.ServerMaintenanceApprovalKey,
+	})
🤖 Prompt for AI Agents
In `@internal/controller/servermaintenance_controller.go` around lines 156 - 171,
When cleaning up a ServerMaintenance object, ensure you remove the approval key
from both annotations and labels to avoid stale auto-approvals: in the cleanup
path that currently calls updateServerRef/patchMaintenanceState (use the
serverClaim and server variables and the existing updateServerRef method), check
for metalv1alpha1.ServerMaintenanceApprovalKey in serverClaim.GetAnnotations()
and serverClaim.GetLabels() and remove it from both maps, persist the change via
the same update/patch flow (e.g., call updateServerRef or client.Update on the
ServerClaim) so the approval label/annotation is cleared during maintenance
teardown.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good bot

Copy link

Choose a reason for hiding this comment

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

@defo89, thank you! Happy to help ensure the cleanup flow is complete. 🚀


🧠 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 : 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.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 : Status must reflect observed state, not desired state; conditions must be updated consistently and deterministically

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

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

Copy link

@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/servermaintenance_controller.go`:
- Around line 156-171: The approval check currently treats any presence of
metalv1alpha1.ServerMaintenanceApprovalKey on serverClaim as approval; change it
to verify the value equals the trueValue constant instead. Replace the
hasAnnotation/hasLabel boolean presence tests with value lookups from
annotations and labels (e.g., val, ok :=
annotations[metalv1alpha1.ServerMaintenanceApprovalKey]) and only consider
approved when val == trueValue (same for labels). Keep the existing behavior
that on approval you call r.updateServerRef(ctx, log, maintenance, server) and
r.patchMaintenanceState(..., metalv1alpha1.ServerMaintenanceStateInMaintenance);
otherwise log the waiting-for-approval message and return. Ensure you reference
serverClaim, annotations, labels, metalv1alpha1.ServerMaintenanceApprovalKey,
trueValue, updateServerRef, and patchMaintenanceState when making the change.

Comment on lines 156 to +171
annotations := serverClaim.GetAnnotations()
if _, ok := annotations[metalv1alpha1.ServerMaintenanceApprovalKey]; !ok {
log.V(1).Info("Server not approved for maintenance, waiting for approval", "Server", server.Name)
return ctrl.Result{}, nil
}
log.V(1).Info("Server approved for maintenance", "Server", server.Name)
if err = r.updateServerRef(ctx, log, maintenance, server); err != nil {
return ctrl.Result{}, err
}
if modified, err := r.patchMaintenanceState(ctx, maintenance, metalv1alpha1.ServerMaintenanceStateInMaintenance); err != nil || modified {
return ctrl.Result{}, err
labels := serverClaim.GetLabels()
_, hasAnnotation := annotations[metalv1alpha1.ServerMaintenanceApprovalKey]
_, hasLabel := labels[metalv1alpha1.ServerMaintenanceApprovalKey]

if hasAnnotation || hasLabel {
log.V(1).Info("Server approved for maintenance", "Server", server.Name)
if err = r.updateServerRef(ctx, log, maintenance, server); err != nil {
return ctrl.Result{}, err
}
if modified, err := r.patchMaintenanceState(ctx, maintenance, metalv1alpha1.ServerMaintenanceStateInMaintenance); err != nil || modified {
return ctrl.Result{}, err
}
}
log.V(1).Info("Server not approved for maintenance, waiting for approval", "Server", server.Name)
return ctrl.Result{}, nil
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -n --type=go -C3 'ServerMaintenanceApprovalKey'

Repository: ironcore-dev/metal-operator

Length of output: 9599


🏁 Script executed:

rg -n --type=go -C3 'trueValue'

Repository: ironcore-dev/metal-operator

Length of output: 8859


🏁 Script executed:

rg -n --type=go -C3 '"true".*metalv1alpha1\|metalv1alpha1.*"true"'

Repository: ironcore-dev/metal-operator

Length of output: 53


Check approval value explicitly—presence alone is insufficient.

The current code approves maintenance if the key exists, regardless of its value. This means setting ServerMaintenanceApprovalKey to "false" or any other value would still trigger approval. However, tests throughout the codebase (servermaintenance_controller_test.go, biossettings_controller_test.go, etc.) explicitly set this key to the trueValue constant ("true"), indicating the intent is value-based approval.

Since trueValue is already defined in this file (line 30), update the approval check to explicitly verify the value:

🔧 Proposed fix
-		_, hasAnnotation := annotations[metalv1alpha1.ServerMaintenanceApprovalKey]
-		_, hasLabel := labels[metalv1alpha1.ServerMaintenanceApprovalKey]
-
-		if hasAnnotation || hasLabel {
+		approved := annotations[metalv1alpha1.ServerMaintenanceApprovalKey] == trueValue ||
+			labels[metalv1alpha1.ServerMaintenanceApprovalKey] == trueValue
+		if approved {
 			log.V(1).Info("Server approved for maintenance", "Server", server.Name)
 			if err = r.updateServerRef(ctx, log, maintenance, server); err != nil {
 				return ctrl.Result{}, err
 			}
 			if modified, err := r.patchMaintenanceState(ctx, maintenance, metalv1alpha1.ServerMaintenanceStateInMaintenance); err != nil || modified {
 				return ctrl.Result{}, err
 			}
 		}
📝 Committable suggestion

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

Suggested change
annotations := serverClaim.GetAnnotations()
if _, ok := annotations[metalv1alpha1.ServerMaintenanceApprovalKey]; !ok {
log.V(1).Info("Server not approved for maintenance, waiting for approval", "Server", server.Name)
return ctrl.Result{}, nil
}
log.V(1).Info("Server approved for maintenance", "Server", server.Name)
if err = r.updateServerRef(ctx, log, maintenance, server); err != nil {
return ctrl.Result{}, err
}
if modified, err := r.patchMaintenanceState(ctx, maintenance, metalv1alpha1.ServerMaintenanceStateInMaintenance); err != nil || modified {
return ctrl.Result{}, err
labels := serverClaim.GetLabels()
_, hasAnnotation := annotations[metalv1alpha1.ServerMaintenanceApprovalKey]
_, hasLabel := labels[metalv1alpha1.ServerMaintenanceApprovalKey]
if hasAnnotation || hasLabel {
log.V(1).Info("Server approved for maintenance", "Server", server.Name)
if err = r.updateServerRef(ctx, log, maintenance, server); err != nil {
return ctrl.Result{}, err
}
if modified, err := r.patchMaintenanceState(ctx, maintenance, metalv1alpha1.ServerMaintenanceStateInMaintenance); err != nil || modified {
return ctrl.Result{}, err
}
}
log.V(1).Info("Server not approved for maintenance, waiting for approval", "Server", server.Name)
return ctrl.Result{}, nil
annotations := serverClaim.GetAnnotations()
labels := serverClaim.GetLabels()
approved := annotations[metalv1alpha1.ServerMaintenanceApprovalKey] == trueValue ||
labels[metalv1alpha1.ServerMaintenanceApprovalKey] == trueValue
if approved {
log.V(1).Info("Server approved for maintenance", "Server", server.Name)
if err = r.updateServerRef(ctx, log, maintenance, server); err != nil {
return ctrl.Result{}, err
}
if modified, err := r.patchMaintenanceState(ctx, maintenance, metalv1alpha1.ServerMaintenanceStateInMaintenance); err != nil || modified {
return ctrl.Result{}, err
}
}
log.V(1).Info("Server not approved for maintenance, waiting for approval", "Server", server.Name)
return ctrl.Result{}, nil
🤖 Prompt for AI Agents
In `@internal/controller/servermaintenance_controller.go` around lines 156 - 171,
The approval check currently treats any presence of
metalv1alpha1.ServerMaintenanceApprovalKey on serverClaim as approval; change it
to verify the value equals the trueValue constant instead. Replace the
hasAnnotation/hasLabel boolean presence tests with value lookups from
annotations and labels (e.g., val, ok :=
annotations[metalv1alpha1.ServerMaintenanceApprovalKey]) and only consider
approved when val == trueValue (same for labels). Keep the existing behavior
that on approval you call r.updateServerRef(ctx, log, maintenance, server) and
r.patchMaintenanceState(..., metalv1alpha1.ServerMaintenanceStateInMaintenance);
otherwise log the waiting-for-approval message and return. Ensure you reference
serverClaim, annotations, labels, metalv1alpha1.ServerMaintenanceApprovalKey,
trueValue, updateServerRef, and patchMaintenanceState when making the change.

@Nuckal777 Nuckal777 merged commit f345f9c into main Jan 27, 2026
16 checks passed
@Nuckal777 Nuckal777 deleted the maintenance-approval-label branch January 27, 2026 14:23
@github-project-automation github-project-automation bot moved this from Backlog to Done in Metal Automation Jan 27, 2026
@github-project-automation github-project-automation bot moved this to Done in Roadmap Jan 27, 2026
defo89 added a commit to sapcc/helm-charts that referenced this pull request Jan 27, 2026
defo89 added a commit to sapcc/helm-charts that referenced this pull request Jan 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done
Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants