-
Notifications
You must be signed in to change notification settings - Fork 49
chore(notebook-controller): fix of some of the linter errors #780
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -251,9 +251,13 @@ func TestNotebookIsIdle(t *testing.T) { | |||||||||||||||
| for _, c := range testCases { | ||||||||||||||||
| t.Run(c.testName, func(t *testing.T) { | ||||||||||||||||
| for envVar, val := range c.env { | ||||||||||||||||
| os.Setenv(envVar, val) | ||||||||||||||||
| if err := os.Setenv(envVar, val); err != nil { | ||||||||||||||||
| t.Fatalf("Failed to set env var %s: %v", envVar, err) | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
Comment on lines
+254
to
+257
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Check the file and get context around lines 254-257
wc -l components/notebook-controller/controllers/culling_controller_test.goRepository: opendatahub-io/kubeflow Length of output: 140 🏁 Script executed: # Read the file around the specified lines with context
sed -n '240,270p' components/notebook-controller/controllers/culling_controller_test.goRepository: opendatahub-io/kubeflow Length of output: 792 🏁 Script executed: # Get a broader view of the test structure to understand if it's using subtests
sed -n '200,280p' components/notebook-controller/controllers/culling_controller_test.goRepository: opendatahub-io/kubeflow Length of output: 1819 🏁 Script executed: # Check Go version requirement
fd -e "go.mod" | head -1 | xargs cat | grep "^go "Repository: opendatahub-io/kubeflow Length of output: 49 🏁 Script executed: # Verify the actual current code is as expected
sed -n '254,257p' components/notebook-controller/controllers/culling_controller_test.goRepository: opendatahub-io/kubeflow Length of output: 187 🏁 Script executed: # Check the test function signature to understand the testing context better
sed -n '200,260p' components/notebook-controller/controllers/culling_controller_test.go | head -70Repository: opendatahub-io/kubeflow Length of output: 1651 🏁 Script executed: # Look for the test function name and start
rg "func Test.*notebookIsIdle" -A 5 components/notebook-controller/controllers/culling_controller_test.goRepository: opendatahub-io/kubeflow Length of output: 49 🏁 Script executed: # Search for the test function that contains the code around line 254
rg "func Test" components/notebook-controller/controllers/culling_controller_test.goRepository: opendatahub-io/kubeflow Length of output: 237 🏁 Script executed: # Get the full context of the issue - from the test function declaration onwards
sed -n '170,280p' components/notebook-controller/controllers/culling_controller_test.goRepository: opendatahub-io/kubeflow Length of output: 2498 Use The Proposed fix- for envVar, val := range c.env {
- if err := os.Setenv(envVar, val); err != nil {
- t.Fatalf("Failed to set env var %s: %v", envVar, err)
- }
- }
+ for envVar, val := range c.env {
+ t.Setenv(envVar, val)
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||
| if err := initGlobalVars(); err != nil { | ||||||||||||||||
| t.Fatalf("Failed to initialize global vars: %v", err) | ||||||||||||||||
| } | ||||||||||||||||
| initGlobalVars() | ||||||||||||||||
| if notebookIsIdle(c.meta, TestLogger) != c.result { | ||||||||||||||||
| t.Errorf("ENV VAR: %+v\n", c.env) | ||||||||||||||||
| t.Errorf("Wrong result for case object: %+v\n", c.meta) | ||||||||||||||||
|
|
||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -96,8 +96,7 @@ func (r *NotebookReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c | |
|
|
||
| // TODO(yanniszark): Can we avoid reconciling Events and Notebook in the same queue? | ||
| event := &corev1.Event{} | ||
| var getEventErr error | ||
| getEventErr = r.Get(ctx, req.NamespacedName, event) | ||
| getEventErr := r.Get(ctx, req.NamespacedName, event) | ||
| if getEventErr == nil { | ||
| log.Info("Found event for Notebook. Re-emitting...") | ||
|
|
||
|
|
@@ -185,11 +184,11 @@ func (r *NotebookReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c | |
| return ctrl.Result{}, err | ||
| } | ||
|
|
||
| // Copy the pod template labels, but reconcilation is not required | ||
| // exclusively based on ths pod template labels | ||
| // Copy the pod template labels, but reconciliation is not required | ||
| // exclusively based on the pod template labels | ||
| if ss.Spec.Replicas != nil && foundStateful.Spec.Replicas != nil && *ss.Spec.Replicas != *foundStateful.Spec.Replicas { | ||
| if !reflect.DeepEqual(foundStateful.Spec.Template.ObjectMeta.Labels, ss.Spec.Template.ObjectMeta.Labels) { | ||
| foundStateful.Spec.Template.ObjectMeta.Labels = ss.Spec.Template.ObjectMeta.Labels | ||
| if !reflect.DeepEqual(foundStateful.Spec.Template.Labels, ss.Spec.Template.Labels) { | ||
| foundStateful.Spec.Template.Labels = ss.Spec.Template.Labels | ||
| } | ||
|
Comment on lines
+190
to
192
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# 1) Inspect CopyStatefulSetFields behavior for ObjectMeta/Template label reconciliation
rg -nP --type=go 'func\s+CopyStatefulSetFields\s*\(' -C20
# 2) Show all StatefulSet label assignments in notebook controller
rg -nP --type=go 'foundStateful\.(Labels|Spec\.Template\.Labels)|ss\.(Labels|Spec\.Template\.Labels)' components/notebook-controller/controllers/notebook_controller.go -C3
# 3) Re-check test expectations around StatefulSet object labels
rg -nP 'Expect\(sts\.GetLabels\(\)\)\.To\(Equal\(notebook\.GetLabels\(\)\)\)' components/notebook-controller/controllers/notebook_controller_bdd_test.go -C3Repository: opendatahub-io/kubeflow Length of output: 3997 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Get the full reconciliation logic around lines 189-192
sed -n '170,210p' components/notebook-controller/controllers/notebook_controller.go
# Check if CopyStatefulSetFields is called before or after the replica check
rg -nP 'CopyStatefulSetFields' components/notebook-controller/controllers/notebook_controller.go -B5 -A5
# Verify what triggers the entire reconciliation block
sed -n '180,200p' components/notebook-controller/controllers/notebook_controller.goRepository: opendatahub-io/kubeflow Length of output: 2991 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Search for any other template label assignments/reconciliation in notebook controller
rg -nP 'Spec\.Template\.Labels\s*=' components/notebook-controller/controllers/notebook_controller.go
# Verify CopyStatefulSetFields doesn't handle template labels
sed -n '107,140p' components/common/reconcilehelper/util.goRepository: opendatahub-io/kubeflow Length of output: 933 Template labels lag behind ObjectMeta labels on label-only updates. Template label reconciliation is incorrectly gated by the replica check (lines 189–192). When a Notebook's labels change without replica scaling, Move the template label sync outside the replica condition: Fix- if ss.Spec.Replicas != nil && foundStateful.Spec.Replicas != nil && *ss.Spec.Replicas != *foundStateful.Spec.Replicas {
- if !reflect.DeepEqual(foundStateful.Spec.Template.Labels, ss.Spec.Template.Labels) {
- foundStateful.Spec.Template.Labels = ss.Spec.Template.Labels
- }
- }
+ if !reflect.DeepEqual(foundStateful.Spec.Template.Labels, ss.Spec.Template.Labels) {
+ foundStateful.Spec.Template.Labels = ss.Spec.Template.Labels
+ }🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
|
|
@@ -477,14 +476,14 @@ func generateStatefulSet(instance *v1beta1.Notebook, isGenerateName bool) *appsv | |
| } | ||
|
|
||
| // copy all of the Notebook labels to the pod including poddefault related labels | ||
| l := &ss.Spec.Template.ObjectMeta.Labels | ||
| for k, v := range instance.ObjectMeta.Labels { | ||
| l := &ss.Spec.Template.Labels | ||
| for k, v := range instance.Labels { | ||
| (*l)[k] = v | ||
| } | ||
|
|
||
| // copy all of the Notebook annotations to the pod. | ||
| a := &ss.Spec.Template.ObjectMeta.Annotations | ||
| for k, v := range instance.ObjectMeta.Annotations { | ||
| a := &ss.Spec.Template.Annotations | ||
| for k, v := range instance.Annotations { | ||
| if !strings.Contains(k, "kubectl") && !strings.Contains(k, "notebook") { | ||
| (*a)[k] = v | ||
| } | ||
|
|
@@ -563,7 +562,7 @@ func generateVirtualService(instance *v1beta1.Notebook) (*unstructured.Unstructu | |
|
|
||
| // unpack annotations from Notebook resource | ||
| annotations := make(map[string]string) | ||
| for k, v := range instance.ObjectMeta.Annotations { | ||
| for k, v := range instance.Annotations { | ||
| annotations[k] = v | ||
| } | ||
|
|
||
|
|
@@ -589,7 +588,7 @@ func generateVirtualService(instance *v1beta1.Notebook) (*unstructured.Unstructu | |
| istioHost = "*" | ||
| } | ||
| if err := unstructured.SetNestedStringSlice(vsvc.Object, []string{istioHost}, "spec", "hosts"); err != nil { | ||
| return nil, fmt.Errorf("Set .spec.hosts error: %v", err) | ||
| return nil, fmt.Errorf("set .spec.hosts error: %v", err) | ||
|
|
||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -133,11 +133,7 @@ func SetStopAnnotation(meta *metav1.ObjectMeta, m *metrics.Metrics) { | |||||||||||||||||||||||||||||
| m.NotebookCullingTimestamp.WithLabelValues(meta.Namespace, meta.Name).Set(float64(t.Unix())) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if meta.GetAnnotations() != nil { | ||||||||||||||||||||||||||||||
| if _, ok := meta.GetAnnotations()["notebooks.kubeflow.org/last_activity"]; ok { | ||||||||||||||||||||||||||||||
| delete(meta.GetAnnotations(), "notebooks.kubeflow.org/last_activity") | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| delete(meta.GetAnnotations(), "notebooks.kubeflow.org/last_activity") | ||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrong annotation key is deleted; stale The code deletes Proposed fix- delete(meta.GetAnnotations(), "notebooks.kubeflow.org/last_activity")
+ delete(meta.GetAnnotations(), LAST_ACTIVITY_ANNOTATION)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jstourac lets check this , please is the annotation really been wrong all this while ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it does seem like that - as I mentioned below, I didn't want to do any other changes here except the linter fixes. I have a followup branch (wip PR) with changes that should address all the comments from the bot here including this one https://github.com/opendatahub-io/kubeflow/pull/781/changes#diff-bfc792d035f63eea8527be116419aca884baf8d8bc2c3e758408e4e7e621af7eR137 . Will open a separate smaller PRs later on so that it's easier to review and faster to merge 🙂 |
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| func StopAnnotationIsSet(meta metav1.ObjectMeta) bool { | ||||||||||||||||||||||||||||||
|
|
@@ -190,7 +186,11 @@ func getNotebookApiKernels(nm, ns string) []KernelStatus { | |||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| var kernels []KernelStatus | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| defer resp.Body.Close() | ||||||||||||||||||||||||||||||
| defer func() { | ||||||||||||||||||||||||||||||
| if err := resp.Body.Close(); err != nil { | ||||||||||||||||||||||||||||||
| log.Error(err, "Error closing response body.") | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }() | ||||||||||||||||||||||||||||||
| err := json.NewDecoder(resp.Body).Decode(&kernels) | ||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||
|
Comment on lines
+189
to
195
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n components/notebook-controller/pkg/culler/culler.go | sed -n '180,230p'Repository: opendatahub-io/kubeflow Length of output: 1748 🏁 Script executed: cat -n components/notebook-controller/pkg/culler/culler.go | sed -n '1,180p' | tail -100Repository: opendatahub-io/kubeflow Length of output: 3611 🏁 Script executed: head -50 components/notebook-controller/pkg/culler/culler.goRepository: opendatahub-io/kubeflow Length of output: 1728 🏁 Script executed: rg -n "json\.(NewDecoder|Unmarshal)" components/notebook-controller/pkg/culler/culler.goRepository: opendatahub-io/kubeflow Length of output: 180 CWE-400: Unbounded JSON decode on HTTP response bodies allows memory exhaustion (lines 194, 218). Severity: Critical. Notebook API response body is decoded without size limits; malicious or oversized response exhausts controller memory and disrupts reconciliation. Proposed fix import (
"encoding/json"
"fmt"
+ "io"
"net/http"
"os"
"strconv"
"time"
@@
const DEFAULT_DEV = "false"
+const MAX_NOTEBOOK_API_BODY_BYTES int64 = 1 << 20 // 1 MiB
@@
- err := json.NewDecoder(resp.Body).Decode(&kernels)
+ err := json.NewDecoder(io.LimitReader(resp.Body, MAX_NOTEBOOK_API_BODY_BYTES)).Decode(&kernels)
@@
- err := json.NewDecoder(resp.Body).Decode(&terminals)
+ err := json.NewDecoder(io.LimitReader(resp.Body, MAX_NOTEBOOK_API_BODY_BYTES)).Decode(&terminals)Per Go security guidelines: Use io.LimitReader for HTTP response bodies to prevent memory exhaustion. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| log.Error(err, "Error parsing JSON response for Notebook API Kernels.") | ||||||||||||||||||||||||||||||
|
|
@@ -210,7 +210,11 @@ func getNotebookApiTerminals(nm, ns string) []TerminalStatus { | |||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| var terminals []TerminalStatus | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| defer resp.Body.Close() | ||||||||||||||||||||||||||||||
| defer func() { | ||||||||||||||||||||||||||||||
| if err := resp.Body.Close(); err != nil { | ||||||||||||||||||||||||||||||
| log.Error(err, "Error closing response body.") | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }() | ||||||||||||||||||||||||||||||
| err := json.NewDecoder(resp.Body).Decode(&terminals) | ||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||
| log.Error(err, "Error parsing JSON response for Notebook API terminals.") | ||||||||||||||||||||||||||||||
|
|
@@ -263,11 +267,11 @@ func getNotebookRecentTime(t []string, api string) string { | |||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Update LAST_ACTIVITY_ANNOTATION | ||||||||||||||||||||||||||||||
| func UpdateNotebookLastActivityAnnotation(meta *metav1.ObjectMeta) bool { | ||||||||||||||||||||||||||||||
| log := log.WithValues("notebook", getNamespacedNameFromMeta(*meta)) | ||||||||||||||||||||||||||||||
| if meta == nil { | ||||||||||||||||||||||||||||||
| log.Info("Metadata is Nil. Can't update Last Activity Annotation.") | ||||||||||||||||||||||||||||||
| return false | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| log := log.WithValues("notebook", getNamespacedNameFromMeta(*meta)) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| log.Info("Updating the last-activity annotation.") | ||||||||||||||||||||||||||||||
| nm, ns := meta.GetName(), meta.GetNamespace() | ||||||||||||||||||||||||||||||
|
|
@@ -322,7 +326,7 @@ func compareAnnotationTimeToResource(meta *metav1.ObjectMeta, resourceTime strin | |||||||||||||||||||||||||||||
| func updateTimestampFromKernelsActivity(meta *metav1.ObjectMeta, kernels []KernelStatus) bool { | ||||||||||||||||||||||||||||||
| log := log.WithValues("notebook", getNamespacedNameFromMeta(*meta)) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if kernels == nil || len(kernels) == 0 { | ||||||||||||||||||||||||||||||
| if len(kernels) == 0 { | ||||||||||||||||||||||||||||||
| log.Info("Notebook has no kernels. Will not update last-activity") | ||||||||||||||||||||||||||||||
| return false | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
@@ -343,7 +347,7 @@ func updateTimestampFromKernelsActivity(meta *metav1.ObjectMeta, kernels []Kerne | |||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| t := getNotebookRecentTime(arr, "api/kernels") | ||||||||||||||||||||||||||||||
| log.Info(fmt.Sprintf("Comparing api/kernels last_activity time to current notebook annotation time")) | ||||||||||||||||||||||||||||||
| log.Info("Comparing api/kernels last_activity time to current notebook annotation time") | ||||||||||||||||||||||||||||||
| if t == "" || !compareAnnotationTimeToResource(meta, t) { | ||||||||||||||||||||||||||||||
| return false | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
@@ -359,7 +363,7 @@ func updateTimestampFromTerminalsActivity(meta *metav1.ObjectMeta, terminals []T | |||||||||||||||||||||||||||||
| // going backwards in time. | ||||||||||||||||||||||||||||||
| log := log.WithValues("notebook", getNamespacedNameFromMeta(*meta)) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if terminals == nil || len(terminals) == 0 { | ||||||||||||||||||||||||||||||
| if len(terminals) == 0 { | ||||||||||||||||||||||||||||||
| log.Info("Notebook has no terminals. Will not update last-activity") | ||||||||||||||||||||||||||||||
| return false | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
@@ -370,7 +374,7 @@ func updateTimestampFromTerminalsActivity(meta *metav1.ObjectMeta, terminals []T | |||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| t := getNotebookRecentTime(arr, "api/terminals") | ||||||||||||||||||||||||||||||
| log.Info(fmt.Sprintf("Comparing api/terminals last_activity time to current notebook annotation time")) | ||||||||||||||||||||||||||||||
| log.Info("Comparing api/terminals last_activity time to current notebook annotation time") | ||||||||||||||||||||||||||||||
| if t == "" || !compareAnnotationTimeToResource(meta, t) { | ||||||||||||||||||||||||||||||
| return false | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: opendatahub-io/kubeflow
Length of output: 137
🏁 Script executed:
Repository: opendatahub-io/kubeflow
Length of output: 1310
🏁 Script executed:
Repository: opendatahub-io/kubeflow
Length of output: 180
🏁 Script executed:
Repository: opendatahub-io/kubeflow
Length of output: 135
🏁 Script executed:
Repository: opendatahub-io/kubeflow
Length of output: 2079
Critical: bound Notebook API body decoding to prevent memory-exhaustion DoS (CWE-400).
Lines 290 and 314 decode HTTP responses without size limits. A compromised or malicious notebook endpoint can exhaust controller memory by streaming oversized JSON, destabilizing reconciliation.
Fix
🤖 Prompt for AI Agents