Skip to content

[RHOAIENG-48289] Don't reconcile labels we don't own#779

Open
jstourac wants to merge 1 commit intoopendatahub-io:mainfrom
jstourac:routeLabels
Open

[RHOAIENG-48289] Don't reconcile labels we don't own#779
jstourac wants to merge 1 commit intoopendatahub-io:mainfrom
jstourac:routeLabels

Conversation

@jstourac
Copy link
Member

@jstourac jstourac commented Mar 18, 2026

For the CRs we managed we should reconcile only those labels that we're managing. Other labels may be added/removed by other parties and we may thus break their functionality.

https://redhat.atlassian.net/browse/RHOAIENG-48289

Description

How Has This Been Tested?

Merge criteria:

  • The commits are squashed in a cohesive manner and have meaningful messages.
  • Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious).
  • The developer has manually tested the changes and verified that the changes work

Summary by CodeRabbit

  • Bug Fixes

    • Preservation of external labels and annotations during reconciliation: controller now only updates its own managed keys and merges controller-owned labels/annotations into existing resources (Deployments, StatefulSets, Services, HTTPRoutes, ReferenceGrants, NetworkPolicies, ConfigMaps, Secrets), avoiding overwriting non-managed metadata. Replica and pod-template spec differences correctly trigger necessary updates while keeping extra labels intact.
  • Tests

    • Added unit/integration tests validating label/annotation preservation and selective reconciliation behavior.

@jstourac jstourac self-assigned this Mar 18, 2026
@openshift-ci openshift-ci bot requested review from andyatmiami and caponetto March 18, 2026 18:07
@openshift-ci
Copy link

openshift-ci bot commented Mar 18, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please ask for approval from jstourac. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

Warning

Rate limit exceeded

@jstourac has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 9 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 3bebf31d-45a4-47db-9dc5-674d44331567

📥 Commits

Reviewing files that changed from the base of the PR and between 36c7720 and b53ddf6.

📒 Files selected for processing (10)
  • components/common/reconcilehelper/util.go
  • components/notebook-controller/controllers/notebook_controller.go
  • components/odh-notebook-controller/controllers/notebook_controller.go
  • components/odh-notebook-controller/controllers/notebook_controller_test.go
  • components/odh-notebook-controller/controllers/notebook_dspa_secret.go
  • components/odh-notebook-controller/controllers/notebook_kube_rbac_auth.go
  • components/odh-notebook-controller/controllers/notebook_network.go
  • components/odh-notebook-controller/controllers/notebook_referencegrant.go
  • components/odh-notebook-controller/controllers/notebook_route.go
  • components/odh-notebook-controller/controllers/notebook_route_compare_test.go
📝 Walkthrough

Walkthrough

This pull request changes reconciliation behavior across multiple controllers and helper utilities to merge controller-managed labels and annotations from desired resources into existing ones instead of replacing entire maps. Files changed include reconcilehelper/util.go (CopyStatefulSetFields, CopyDeploymentSetFields, CopyServiceFields) and several notebook-related controllers (notebook_controller, odh-notebook-controller: route, referencegrant, network, kube-rbac proxy, dspa secret, tests). Reconciliation now initializes nil maps and copies only managed keys from source into destination, preserving non-managed keys; specs and replica checks remain part of update detection. New unit tests verify preservation of extra (non-managed) labels.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Critical Issues

  • Controller-managed key set scattered and inconsistent — multiple files hard-code different managed keys (e.g., app.kubernetes.io/managed-by, opendatahub.io/component, notebook-name, notebook-namespace). Lack of a centralized authoritative list risks inconsistent behavior and missed reconciliation. Action: centralize managed-label/key definitions and reference them from a single package. (CWE-942: Permissive Cross-domain Whitelist — analogous risk of inconsistent policy)
  • Silent overwrite of controller-managed label values — merge logic assigns source values into destination without explicit conflict handling or audit when destination had a different value; external actors’ changes will be overwritten silently. Action: decide and implement conflict policy (reject, log/report, or annotate with last-applied metadata) and add tests for overwrite cases. (CWE-863: Incorrect Authorization)
  • Asymmetric drift detection vs. reconciliation — comparisons often ignore labels while reconcile merges/overwrites them (e.g., NetworkPolicy compares only Spec but reconciliation preserves labels; some controllers compare only annotations). This asymmetry can hide drift or cause persistent divergence. Action: make comparison and reconciliation symmetric for managed keys (compare same set you reconcile). (CWE-842: Unchecked Return Value — conceptual)
  • Map initialization and mutation order may mask deltas — merging modifies destination before some equality checks, potentially hiding removals or changes and causing incorrect no-op decisions. Action: compute diffs against originals (do not mutate in-place before deciding), or copy originals for comparison, then mutate only when update is confirmed. (CWE-683: Function Call With Incorrect Arguments)
  • Missing audit/logging for destructive updates — overwriting managed labels or specs lacks an audit/log entry; operators cannot trace controller-induced changes. Action: emit structured events or logs when controller overwrites or updates managed label values.

Moderate Issues

  • Tests do not cover label-value conflicts — new tests confirm preserved extra labels and managed-label drift detection but do not assert behavior when managed labels differ in value in the destination (overwrite vs. reject). Action: add tests covering overwrite semantics and expected logs/events.
  • Inconsistent merge semantics for metadata vs data — some resources (e.g., dspa secret) merge labels but fully replace Data; this asymmetry may confuse expectations. Action: document per-resource merge semantics and ensure consistency or explicit exceptions.
  • Duplication of nil-map initialization across files — repeated if X.Labels == nil { X.Labels = map[...] } appears in many places; small but repetitive; consider helper to ensure map existence to reduce copy-paste errors.
  • Lack of explicit unit tests for replica/template spec deep-equality and update triggers — changes moved to unconditional assignment in some places after difference checks; add tests ensuring replicas and template spec updates behave as intended under various diff scenarios.
  • No explicit policy for which non-managed labels should be preserved — tests imply preservation (e.g., route-shard, wrong-label) but policy is implicit. Action: document preservation policy and add comment/central list to avoid regressions.
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and specifically describes the main change: the operator will now reconcile only labels it owns, not all labels on resources.

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

📝 Coding Plan
  • Generate coding plan for human review comments

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.

@jstourac
Copy link
Member Author

@coderabbitai full review

@openshift-ci openshift-ci bot added the size/l label Mar 18, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

✅ Actions performed

Full review triggered.

@openshift-ci openshift-ci bot added size/l and removed size/l labels Mar 18, 2026
@rhods-ci-bot
Copy link

/group-test

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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/odh-notebook-controller/controllers/notebook_dspa_secret.go (1)

395-407: ⚠️ Potential issue | 🟠 Major

Add conflict retry loop to Secret update for concurrent external mutations.

Line 405: Single cli.Update() on a shared resource that external tooling may also mutate will fail transiently on conflict errors. In webhook-triggered reconciliation paths, this blocks notebook admission. Wrap the update in retry.RetryOnConflict(retry.DefaultRetry, func() error {...}) with a fresh Get per attempt to re-apply mutations on the latest object state.

Proposed fix
 import (
 	"context"
 	"encoding/json"
 	"fmt"
 	"reflect"
 
 	"github.com/go-logr/logr"
 	nbv1 "github.com/kubeflow/kubeflow/components/notebook-controller/api/v1"
 	dspav1 "github.com/opendatahub-io/data-science-pipelines-operator/api/v1"
 	routev1 "github.com/openshift/api/route/v1"
 	corev1 "k8s.io/api/core/v1"
 	apierrs "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/api/meta"
+	"k8s.io/client-go/util/retry"
 
 	"k8s.io/client-go/dynamic"
 	"k8s.io/client-go/rest"
 	"k8s.io/utils/ptr"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
 	if requiresUpdate {
 		log.Info("Updating existing Elyra runtime config secret", "name", elyraRuntimeSecretName)
-		// Set controller-managed label and data. Preserve any other labels that
-		// may have been added by external tooling.
-		if existingSecret.Labels == nil {
-			existingSecret.Labels = map[string]string{}
-		}
-		existingSecret.Labels[managedByKey] = managedByValue
-		existingSecret.Data = desiredSecret.Data
-
-		if err := cli.Update(ctx, existingSecret); err != nil {
+		if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
+			latest := &corev1.Secret{}
+			if err := cli.Get(ctx, client.ObjectKey{
+				Name: elyraRuntimeSecretName, Namespace: notebook.Namespace,
+			}, latest); err != nil {
+				return err
+			}
+			// Set controller-managed label and data. Preserve any other labels that
+			// may have been added by external tooling.
+			if latest.Labels == nil {
+				latest.Labels = map[string]string{}
+			}
+			latest.Labels[managedByKey] = managedByValue
+			latest.Data = desiredSecret.Data
+			return cli.Update(ctx, latest)
+		}); err != nil {
 			log.Error(err, "Failed to update existing Elyra runtime config secret")
 			return err
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/odh-notebook-controller/controllers/notebook_dspa_secret.go`
around lines 395 - 407, The update of the shared Secret (existingSecret) should
be retried on conflicts: wrap the cli.Update call inside
retry.RetryOnConflict(retry.DefaultRetry, func() error { ... }), and in each
attempt re-fetch the latest Secret via cli.Get, re-apply controller-managed
label (managedByKey = managedByValue) and set Data from desiredSecret.Data
before calling cli.Update; on persistent error log via log.Error and return the
error from the retry wrapper.
🧹 Nitpick comments (2)
components/common/reconcilehelper/util.go (1)

109-134: Extract shared map-merge reconciliation helper

The same managed-key merge logic for labels/annotations is duplicated in three functions. Centralizing it reduces divergence risk.

Proposed refactor sketch
+func mergeManagedStringMap(dst map[string]string, src map[string]string) (map[string]string, bool) {
+	updated := false
+	if dst == nil {
+		dst = map[string]string{}
+	}
+	for k, v := range src {
+		if dst[k] != v {
+			updated = true
+		}
+		dst[k] = v
+	}
+	return dst, updated
+}

Then call this helper for Labels and Annotations in:

  • CopyStatefulSetFields
  • CopyDeploymentSetFields
  • CopyServiceFields

As per coding guidelines, **: REVIEW PRIORITIES: 2. Architectural issues and anti-patterns.

Also applies to: 151-173, 191-213

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/common/reconcilehelper/util.go` around lines 109 - 134, The
label/annotation merge logic is duplicated; extract it into a single helper
(e.g., reconcileManagedMap or mergeManagedMap) that takes (from
map[string]string, to *map[string]string) and returns whether an update is
required, then replace the duplicated blocks in CopyStatefulSetFields,
CopyDeploymentSetFields, and CopyServiceFields to call that helper for both
Labels and Annotations (preserve existing extra keys on `to`, create `to` if
nil, copy controller-owned keys from `from`, and accumulate the requireUpdate
result).
components/odh-notebook-controller/controllers/notebook_referencegrant.go (1)

75-78: Centralize managed ReferenceGrant label keys

Managed label keys are duplicated between compare and reconcile paths. A future one-sided edit will desynchronize detection vs remediation.

Proposed refactor
+var notebookReferenceGrantManagedLabelKeys = []string{
+	"app.kubernetes.io/managed-by",
+	"opendatahub.io/component",
+}

 func CompareNotebookReferenceGrants(rg1 gatewayv1beta1.ReferenceGrant, rg2 gatewayv1beta1.ReferenceGrant) bool {
-	managedLabelKeys := []string{
-		"app.kubernetes.io/managed-by",
-		"opendatahub.io/component",
-	}
-	for _, k := range managedLabelKeys {
+	for _, k := range notebookReferenceGrantManagedLabelKeys {
 		v1, ok1 := rg1.Labels[k]
 		v2, ok2 := rg2.Labels[k]
 		if ok1 != ok2 || v1 != v2 {
 			return false
 		}
 	}
 	return reflect.DeepEqual(rg1.Spec, rg2.Spec)
 }

-			for _, k := range []string{"app.kubernetes.io/managed-by", "opendatahub.io/component"} {
+			for _, k := range notebookReferenceGrantManagedLabelKeys {
 				if desiredRefGrant.Labels != nil {
 					if v, ok := desiredRefGrant.Labels[k]; ok {
 						foundRefGrant.Labels[k] = v
 					}
 				}
 			}

As per coding guidelines, **: REVIEW PRIORITIES: 2. Architectural issues and anti-patterns.

Also applies to: 132-133

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/odh-notebook-controller/controllers/notebook_referencegrant.go`
around lines 75 - 78, Extract the duplicated slice literal managedLabelKeys into
a single shared package-level variable or constant (e.g., managedLabelKeys or
ManagedLabelKeys) and replace both inline occurrences (the current
managedLabelKeys declarations used in compare and reconcile paths) to reference
that shared identifier; ensure the shared identifier is declared once at the top
of the file (or in a common file in the same package) so both the compare and
reconcile code paths use the same source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/odh-notebook-controller/controllers/notebook_controller_test.go`:
- Around line 156-160: The test creates shardedNotebook (shardedNotebookName,
created via createNotebook and cli.Create) but only deletes it much later, so
failures leave resources behind; immediately after successfully creating
shardedNotebook call a cleanup to always remove it (use t.Cleanup or defer a
deletion that calls cli.Delete with the same ctx and shardedNotebook) so the
resource is deleted on test exit/failure; update the block around
createNotebook/cli.Create to register that cleanup right after the
Expect(cli.Create(...)).Should(Succeed()) to guarantee removal even if later
assertions fail.

In `@components/odh-notebook-controller/controllers/notebook_controller.go`:
- Around line 115-119: The comparison uses the embedded ObjectMeta field
directly which triggers staticcheck QF1008; update the equality check in the
notebook reconciliation (the code comparing nb1 and nb2) to use the promoted
field names—replace nb1.ObjectMeta.Annotations with nb1.Annotations and
nb2.ObjectMeta.Annotations with nb2.Annotations in the reflect.DeepEqual
expression so the annotations comparison reads
reflect.DeepEqual(nb1.Annotations, nb2.Annotations) &&
reflect.DeepEqual(nb1.Spec, nb2.Spec).

In `@components/odh-notebook-controller/controllers/notebook_route.go`:
- Around line 144-145: The lint error is caused by accessing the embedded
ObjectMeta field explicitly; replace uses of r1.ObjectMeta.Labels and
r2.ObjectMeta.Labels with the promoted Labels field (r1.Labels and r2.Labels) in
the comparison logic where v1, ok1 := r1.ObjectMeta.Labels[k] and v2, ok2 :=
r2.ObjectMeta.Labels[k] are set; update those expressions to v1, ok1 :=
r1.Labels[k] and v2, ok2 := r2.Labels[k] so the code uses the promoted field and
resolves the QF1008 staticcheck warning.

---

Outside diff comments:
In `@components/odh-notebook-controller/controllers/notebook_dspa_secret.go`:
- Around line 395-407: The update of the shared Secret (existingSecret) should
be retried on conflicts: wrap the cli.Update call inside
retry.RetryOnConflict(retry.DefaultRetry, func() error { ... }), and in each
attempt re-fetch the latest Secret via cli.Get, re-apply controller-managed
label (managedByKey = managedByValue) and set Data from desiredSecret.Data
before calling cli.Update; on persistent error log via log.Error and return the
error from the retry wrapper.

---

Nitpick comments:
In `@components/common/reconcilehelper/util.go`:
- Around line 109-134: The label/annotation merge logic is duplicated; extract
it into a single helper (e.g., reconcileManagedMap or mergeManagedMap) that
takes (from map[string]string, to *map[string]string) and returns whether an
update is required, then replace the duplicated blocks in CopyStatefulSetFields,
CopyDeploymentSetFields, and CopyServiceFields to call that helper for both
Labels and Annotations (preserve existing extra keys on `to`, create `to` if
nil, copy controller-owned keys from `from`, and accumulate the requireUpdate
result).

In `@components/odh-notebook-controller/controllers/notebook_referencegrant.go`:
- Around line 75-78: Extract the duplicated slice literal managedLabelKeys into
a single shared package-level variable or constant (e.g., managedLabelKeys or
ManagedLabelKeys) and replace both inline occurrences (the current
managedLabelKeys declarations used in compare and reconcile paths) to reference
that shared identifier; ensure the shared identifier is declared once at the top
of the file (or in a common file in the same package) so both the compare and
reconcile code paths use the same source of truth.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 2b6676fb-bbb7-456b-b960-a2d0c07c7d59

📥 Commits

Reviewing files that changed from the base of the PR and between cbb479d and 36c7720.

📒 Files selected for processing (10)
  • components/common/reconcilehelper/util.go
  • components/notebook-controller/controllers/notebook_controller.go
  • components/odh-notebook-controller/controllers/notebook_controller.go
  • components/odh-notebook-controller/controllers/notebook_controller_test.go
  • components/odh-notebook-controller/controllers/notebook_dspa_secret.go
  • components/odh-notebook-controller/controllers/notebook_kube_rbac_auth.go
  • components/odh-notebook-controller/controllers/notebook_network.go
  • components/odh-notebook-controller/controllers/notebook_referencegrant.go
  • components/odh-notebook-controller/controllers/notebook_route.go
  • components/odh-notebook-controller/controllers/notebook_route_compare_test.go

Comment on lines +156 to +160
By("By creating a new Notebook dedicated to this test")
shardedNotebookName := "test-notebook-shard-label"
shardedNotebook := createNotebook(shardedNotebookName, Namespace)
Expect(cli.Create(ctx, shardedNotebook)).Should(Succeed())

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Make test cleanup failure-safe

Line 159 creates shardedNotebook, but cleanup happens only at Line 247. Any earlier failed assertion leaves resources behind and can cascade failures.

Proposed patch
 			shardedNotebook := createNotebook(shardedNotebookName, Namespace)
 			Expect(cli.Create(ctx, shardedNotebook)).Should(Succeed())
+			defer func() {
+				_ = cli.Delete(ctx, shardedNotebook)
+			}()
@@
-			By("By deleting the Notebook created for this test")
-			Expect(cli.Delete(ctx, shardedNotebook)).Should(Succeed())

As per coding guidelines, **: REVIEW PRIORITIES: 3. Bug-prone patterns and error handling gaps.

Also applies to: 246-248

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/odh-notebook-controller/controllers/notebook_controller_test.go`
around lines 156 - 160, The test creates shardedNotebook (shardedNotebookName,
created via createNotebook and cli.Create) but only deletes it much later, so
failures leave resources behind; immediately after successfully creating
shardedNotebook call a cleanup to always remove it (use t.Cleanup or defer a
deletion that calls cli.Delete with the same ctx and shardedNotebook) so the
resource is deleted on test exit/failure; update the block around
createNotebook/cli.Create to register that cleanup right after the
Expect(cli.Create(...)).Should(Succeed()) to guarantee removal even if later
assertions fail.

@openshift-ci openshift-ci bot added size/l and removed size/l labels Mar 18, 2026
For the CRs we managed we should reconcile only those labels that we're
managing. Other labels may be added/removed by other parties and we may
thus break their functionality.
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/notebook-controller/controllers/notebook_controller.go (1)

190-211: ⚠️ Potential issue | 🟠 Major

Pod-template label drift only reconciled on replica change; label-only drift skipped entirely.

The label merge at lines 194–207 runs only when replicas differ. CopyStatefulSetFields does not reconcile Spec.Template.Labels (only Spec.Template.Spec). If a desired template label is removed while replicas remain stable, the drift is never detected and persisted; new pods inherit stale labels. Move the merge unconditionally and include its result in the update predicate.

Patch sketch
-	if ss.Spec.Replicas != nil && foundStateful.Spec.Replicas != nil && *ss.Spec.Replicas != *foundStateful.Spec.Replicas {
-		// Reconcile only controller-owned labels (present on `ss`), preserve any
-		// extra labels on the existing StatefulSet pod template.
-		needsLabelUpdate := false
-		for k, v := range ss.Spec.Template.Labels {
-			if foundStateful.Spec.Template.Labels == nil || foundStateful.Spec.Template.Labels[k] != v {
-				needsLabelUpdate = true
-				break
-			}
-		}
-		if needsLabelUpdate {
-			if foundStateful.Spec.Template.Labels == nil {
-				foundStateful.Spec.Template.Labels = map[string]string{}
-			}
-			for k, v := range ss.Spec.Template.Labels {
-				foundStateful.Spec.Template.Labels[k] = v
-			}
-		}
+	// Reconcile only controller-owned labels (present on `ss`), preserve any
+	// extra labels on the existing StatefulSet pod template.
+	needsTemplateLabelUpdate := false
+	for k, v := range ss.Spec.Template.Labels {
+		if foundStateful.Spec.Template.Labels == nil || foundStateful.Spec.Template.Labels[k] != v {
+			needsTemplateLabelUpdate = true
+			break
+		}
+	}
+	if needsTemplateLabelUpdate {
+		if foundStateful.Spec.Template.Labels == nil {
+			foundStateful.Spec.Template.Labels = map[string]string{}
+		}
+		for k, v := range ss.Spec.Template.Labels {
+			foundStateful.Spec.Template.Labels[k] = v
+		}
 	}
 
 	// Update the foundStateful object and write the result back if there are any changes
-	if !justCreated && reconcilehelper.CopyStatefulSetFields(ss, foundStateful) {
+	if !justCreated && (needsTemplateLabelUpdate || reconcilehelper.CopyStatefulSetFields(ss, foundStateful)) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/notebook-controller/controllers/notebook_controller.go` around
lines 190 - 211, The label-merge logic currently guarded by the replica-change
check should be performed unconditionally so Pod-template label drift is always
reconciled: move the block that compares and merges
ss.Spec.Template.ObjectMeta.Labels into
foundStateful.Spec.Template.ObjectMeta.Labels out of the if that checks replicas
(so it runs regardless of replicas), and ensure its result (e.g., a boolean
needsLabelUpdate or merged flag) is factored into the same update predicate used
with reconcilehelper.CopyStatefulSetFields(foundStateful, ss) and the
justCreated guard so the controller will update the StatefulSet whenever labels
were merged even if replicas didn’t change; reference ss, foundStateful,
Spec.Template.ObjectMeta.Labels, justCreated and
reconcilehelper.CopyStatefulSetFields when applying this change.
♻️ Duplicate comments (3)
components/odh-notebook-controller/controllers/notebook_route.go (1)

143-145: ⚠️ Potential issue | 🟠 Major

Use promoted Labels here to unblock staticcheck.

Lines 144-145 still use ObjectMeta.Labels, which is what QF1008 is complaining about. HTTPRoute already promotes ObjectMeta, so r1.Labels / r2.Labels are equivalent.

Patch
-		v1, ok1 := r1.ObjectMeta.Labels[k]
-		v2, ok2 := r2.ObjectMeta.Labels[k]
+		v1, ok1 := r1.Labels[k]
+		v2, ok2 := r2.Labels[k]
#!/bin/bash
rg -nP --type=go '\bObjectMeta\.Labels\b' components/odh-notebook-controller/controllers/notebook_route.go
# Expected: no matches after the fix
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/odh-notebook-controller/controllers/notebook_route.go` around
lines 143 - 145, The loop over managedLabelKeys uses the promoted ObjectMeta
incorrectly; replace r1.ObjectMeta.Labels and r2.ObjectMeta.Labels with the
promoted fields r1.Labels and r2.Labels in the for loop that references
managedLabelKeys (and any other occurrences in the same function), so the code
reads v1, ok1 := r1.Labels[k] and v2, ok2 := r2.Labels[k] to satisfy staticcheck
QF1008.
components/odh-notebook-controller/controllers/notebook_controller.go (1)

115-119: ⚠️ Potential issue | 🟠 Major

Use the promoted Annotations field here to clear the lint blocker.

nb1.ObjectMeta.Annotations and nb2.ObjectMeta.Annotations on Line 118 still trigger QF1008. Notebook promotes ObjectMeta, so nb1.Annotations / nb2.Annotations are equivalent and keep CI green.

Patch
-	return reflect.DeepEqual(nb1.ObjectMeta.Annotations, nb2.ObjectMeta.Annotations) &&
+	return reflect.DeepEqual(nb1.Annotations, nb2.Annotations) &&
 		reflect.DeepEqual(nb1.Spec, nb2.Spec)
#!/bin/bash
rg -nP --type=go '\bObjectMeta\.Annotations\b' components/odh-notebook-controller/controllers/notebook_controller.go
# Expected: no matches after the fix
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/odh-notebook-controller/controllers/notebook_controller.go` around
lines 115 - 119, The equality check uses nb1.ObjectMeta.Annotations and
nb2.ObjectMeta.Annotations which triggers QF1008; update the reflect.DeepEqual
call in notebook_controller.go (the comparison that returns
reflect.DeepEqual(... ) && reflect.DeepEqual(nb1.Spec, nb2.Spec)) to use the
promoted fields nb1.Annotations and nb2.Annotations instead of
nb1.ObjectMeta.Annotations/nb2.ObjectMeta.Annotations so the linter warning is
cleared.
components/odh-notebook-controller/controllers/notebook_controller_test.go (1)

156-160: ⚠️ Potential issue | 🟡 Minor

Register cleanup immediately after creating shardedNotebook.

If any assertion between Line 159 and Line 247 fails, this notebook stays behind in default and can skew later namespace-scoped assertions. Defer the delete right after cli.Create(...).

Patch
 			shardedNotebook := createNotebook(shardedNotebookName, Namespace)
 			Expect(cli.Create(ctx, shardedNotebook)).Should(Succeed())
+			defer func() {
+				_ = cli.Delete(ctx, shardedNotebook)
+			}()
@@
-			By("By deleting the Notebook created for this test")
-			Expect(cli.Delete(ctx, shardedNotebook)).Should(Succeed())

Also applies to: 246-247

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/odh-notebook-controller/controllers/notebook_controller_test.go`
around lines 156 - 160, After creating the test resource shardedNotebook via
createNotebook and cli.Create (shardedNotebookName, shardedNotebook, Namespace),
register a deferred cleanup immediately—i.e., call cli.Delete within a defer
using the same ctx and shardedNotebook so the notebook is removed even if
subsequent assertions fail; reference the createNotebook, shardedNotebook,
shardedNotebookName and cli.Create/cli.Delete symbols to locate where to add the
defer.
🧹 Nitpick comments (2)
components/odh-notebook-controller/controllers/notebook_dspa_secret.go (1)

48-49: Centralize managedByKey and managedByValue constants to prevent drift.

Constants defined at notebook_dspa_secret.go:48-49 are not reused; the label "opendatahub.io/managed-by": "workbenches" is hardcoded in notebook_controller.go:573, notebook_webhook.go:643, and notebook_runtime.go:150. Extract both constants to a shared package to ensure consistency across all notebook controllers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/odh-notebook-controller/controllers/notebook_dspa_secret.go`
around lines 48 - 49, Extract the string literals into shared exported constants
(e.g., ManagedByKey and ManagedByValue) instead of the local
managedByKey/managedByValue in notebook_dspa_secret.go, place them in a small
shared package (e.g., labels or common) and update all call sites—replace
hardcoded `"opendatahub.io/managed-by"`/`"workbenches"` usages in
notebook_controller.go (where used), notebook_webhook.go, and
notebook_runtime.go to import and reference the new exported constants
(ManagedByKey, ManagedByValue); ensure the original managedByKey/managedByValue
are removed or redirected to the shared constants to prevent duplication.
components/common/reconcilehelper/util.go (1)

109-134: Extract the managed-metadata merge into one helper.

The same three-phase pattern now exists here and in multiple controllers: diff desired keys, initialize a nil map, then copy desired keys. The pod-template drift bug in this PR is already a sign these paths are diverging. Centralizing the merge logic will keep nil handling, change detection, and any future key-removal policy consistent across StatefulSets, Deployments, Services, Routes, ReferenceGrants, and ConfigMaps.

Also applies to: 151-172, 191-212

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/common/reconcilehelper/util.go` around lines 109 - 134, Extract
the repeated three-step pattern (diff desired keys against target, initialize a
nil map on target, then copy desired keys) into a single helper (e.g.,
MergeManagedMap or MergeManagedMetadata) that accepts the source map
(from.Labels / from.Annotations), a pointer/reference to the target map
(to.Labels / to.Annotations) and the current requireUpdate flag and returns the
updated target map and a bool indicating whether an update is required; replace
the duplicated blocks in this file (the labels block and the annotations block)
and the other occurrences you noted (the blocks at the equivalents of lines
151-172 and 191-212) to call this helper, ensuring nil handling, change
detection (compare values), and assignment of keys are centralized and that
requireUpdate is updated based on the helper's returned bool.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@components/notebook-controller/controllers/notebook_controller.go`:
- Around line 190-211: The label-merge logic currently guarded by the
replica-change check should be performed unconditionally so Pod-template label
drift is always reconciled: move the block that compares and merges
ss.Spec.Template.ObjectMeta.Labels into
foundStateful.Spec.Template.ObjectMeta.Labels out of the if that checks replicas
(so it runs regardless of replicas), and ensure its result (e.g., a boolean
needsLabelUpdate or merged flag) is factored into the same update predicate used
with reconcilehelper.CopyStatefulSetFields(foundStateful, ss) and the
justCreated guard so the controller will update the StatefulSet whenever labels
were merged even if replicas didn’t change; reference ss, foundStateful,
Spec.Template.ObjectMeta.Labels, justCreated and
reconcilehelper.CopyStatefulSetFields when applying this change.

---

Duplicate comments:
In `@components/odh-notebook-controller/controllers/notebook_controller_test.go`:
- Around line 156-160: After creating the test resource shardedNotebook via
createNotebook and cli.Create (shardedNotebookName, shardedNotebook, Namespace),
register a deferred cleanup immediately—i.e., call cli.Delete within a defer
using the same ctx and shardedNotebook so the notebook is removed even if
subsequent assertions fail; reference the createNotebook, shardedNotebook,
shardedNotebookName and cli.Create/cli.Delete symbols to locate where to add the
defer.

In `@components/odh-notebook-controller/controllers/notebook_controller.go`:
- Around line 115-119: The equality check uses nb1.ObjectMeta.Annotations and
nb2.ObjectMeta.Annotations which triggers QF1008; update the reflect.DeepEqual
call in notebook_controller.go (the comparison that returns
reflect.DeepEqual(... ) && reflect.DeepEqual(nb1.Spec, nb2.Spec)) to use the
promoted fields nb1.Annotations and nb2.Annotations instead of
nb1.ObjectMeta.Annotations/nb2.ObjectMeta.Annotations so the linter warning is
cleared.

In `@components/odh-notebook-controller/controllers/notebook_route.go`:
- Around line 143-145: The loop over managedLabelKeys uses the promoted
ObjectMeta incorrectly; replace r1.ObjectMeta.Labels and r2.ObjectMeta.Labels
with the promoted fields r1.Labels and r2.Labels in the for loop that references
managedLabelKeys (and any other occurrences in the same function), so the code
reads v1, ok1 := r1.Labels[k] and v2, ok2 := r2.Labels[k] to satisfy staticcheck
QF1008.

---

Nitpick comments:
In `@components/common/reconcilehelper/util.go`:
- Around line 109-134: Extract the repeated three-step pattern (diff desired
keys against target, initialize a nil map on target, then copy desired keys)
into a single helper (e.g., MergeManagedMap or MergeManagedMetadata) that
accepts the source map (from.Labels / from.Annotations), a pointer/reference to
the target map (to.Labels / to.Annotations) and the current requireUpdate flag
and returns the updated target map and a bool indicating whether an update is
required; replace the duplicated blocks in this file (the labels block and the
annotations block) and the other occurrences you noted (the blocks at the
equivalents of lines 151-172 and 191-212) to call this helper, ensuring nil
handling, change detection (compare values), and assignment of keys are
centralized and that requireUpdate is updated based on the helper's returned
bool.

In `@components/odh-notebook-controller/controllers/notebook_dspa_secret.go`:
- Around line 48-49: Extract the string literals into shared exported constants
(e.g., ManagedByKey and ManagedByValue) instead of the local
managedByKey/managedByValue in notebook_dspa_secret.go, place them in a small
shared package (e.g., labels or common) and update all call sites—replace
hardcoded `"opendatahub.io/managed-by"`/`"workbenches"` usages in
notebook_controller.go (where used), notebook_webhook.go, and
notebook_runtime.go to import and reference the new exported constants
(ManagedByKey, ManagedByValue); ensure the original managedByKey/managedByValue
are removed or redirected to the shared constants to prevent duplication.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 9ea1028e-ed39-413f-8a5a-1a37a1b70f71

📥 Commits

Reviewing files that changed from the base of the PR and between cbb479d and 36c7720.

📒 Files selected for processing (10)
  • components/common/reconcilehelper/util.go
  • components/notebook-controller/controllers/notebook_controller.go
  • components/odh-notebook-controller/controllers/notebook_controller.go
  • components/odh-notebook-controller/controllers/notebook_controller_test.go
  • components/odh-notebook-controller/controllers/notebook_dspa_secret.go
  • components/odh-notebook-controller/controllers/notebook_kube_rbac_auth.go
  • components/odh-notebook-controller/controllers/notebook_network.go
  • components/odh-notebook-controller/controllers/notebook_referencegrant.go
  • components/odh-notebook-controller/controllers/notebook_route.go
  • components/odh-notebook-controller/controllers/notebook_route_compare_test.go

@openshift-ci
Copy link

openshift-ci bot commented Mar 18, 2026

@jstourac: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/odh-notebook-controller-e2e b53ddf6 link true /test odh-notebook-controller-e2e

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants