diff --git a/cmd/controller/main.go b/cmd/controller/main.go index f9a612bc..a4f5ad2c 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -1,19 +1,3 @@ -/* -Copyright 2024. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - package main import ( @@ -216,59 +200,7 @@ func main() { os.Exit(1) } - cacheByObject := map[client.Object]cache.ByObject{ - &metav1.PartialObjectMetadata{ - TypeMeta: metav1.TypeMeta{ - APIVersion: storagev1alpha1.SchemeGroupVersion.String(), - Kind: "VulnerabilityReport", - }, - }: { - // Read-only - UnsafeDisableDeepCopy: new(true), - }, - &metav1.PartialObjectMetadata{ - TypeMeta: metav1.TypeMeta{ - APIVersion: corev1.SchemeGroupVersion.String(), - Kind: "Namespace", - }, - }: { - // Read-only - UnsafeDisableDeepCopy: new(true), - }, - } - - if cfg.WorkloadScan { - cacheByObject[&storagev1alpha1.Image{}] = cache.ByObject{ - Label: labels.SelectorFromSet(labels.Set{api.LabelWorkloadScanKey: api.LabelWorkloadScanValue}), - Transform: storage.TransformStripImage, - } - cacheByObject[&storagev1alpha1.WorkloadScanReport{}] = cache.ByObject{ - Transform: storage.TransformStripWorkloadScanReport, - } - cacheByObject[&corev1.Pod{}] = cache.ByObject{ - Transform: controller.TransformStripPod, - // Read-only - UnsafeDisableDeepCopy: new(true), - } - cacheByObject[&metav1.PartialObjectMetadata{ - TypeMeta: metav1.TypeMeta{ - APIVersion: appsv1.SchemeGroupVersion.String(), - Kind: "ReplicaSet", - }, - }] = cache.ByObject{ - // Read-only - UnsafeDisableDeepCopy: new(true), - } - cacheByObject[&metav1.PartialObjectMetadata{ - TypeMeta: metav1.TypeMeta{ - APIVersion: batchv1.SchemeGroupVersion.String(), - Kind: "Job", - }, - }] = cache.ByObject{ - // Read-only - UnsafeDisableDeepCopy: new(true), - } - } + cacheByObject := buildCacheByObject(cfg) mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, @@ -417,3 +349,74 @@ func main() { os.Exit(1) } } + +func buildCacheByObject(cfg Config) map[client.Object]cache.ByObject { + cacheByObject := map[client.Object]cache.ByObject{ + &metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + APIVersion: storagev1alpha1.SchemeGroupVersion.String(), + Kind: "VulnerabilityReport", + }, + }: { + // Read-only + UnsafeDisableDeepCopy: new(true), + }, + &metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Namespace", + }, + }: { + // Read-only + UnsafeDisableDeepCopy: new(true), + }, + } + + if cfg.WorkloadScan { + cacheByObject[&storagev1alpha1.Image{}] = cache.ByObject{ + Label: labels.SelectorFromSet(labels.Set{api.LabelWorkloadScanKey: api.LabelWorkloadScanValue}), + Transform: storage.TransformStripImage, + } + cacheByObject[&storagev1alpha1.WorkloadScanReport{}] = cache.ByObject{ + Transform: storage.TransformStripWorkloadScanReport, + } + cacheByObject[&corev1.Pod{}] = cache.ByObject{ + Transform: controller.TransformStripPod, + // Read-only + UnsafeDisableDeepCopy: new(true), + } + cacheByObject[&metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + APIVersion: appsv1.SchemeGroupVersion.String(), + Kind: "ReplicaSet", + }, + }] = cache.ByObject{ + // Read-only + UnsafeDisableDeepCopy: new(true), + } + cacheByObject[&metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + APIVersion: batchv1.SchemeGroupVersion.String(), + Kind: "Job", + }, + }] = cache.ByObject{ + // Read-only + UnsafeDisableDeepCopy: new(true), + } + } + + if cfg.NodeScan { + cacheByObject[&corev1.Node{}] = cache.ByObject{ + Transform: controller.TransformStripNode, + // Read-only + UnsafeDisableDeepCopy: new(true), + } + cacheByObject[&storagev1alpha1.NodeSBOM{}] = cache.ByObject{ + Transform: storage.TransformStripNodeSBOM, + // Read-only + UnsafeDisableDeepCopy: new(true), + } + } + + return cacheByObject +} diff --git a/internal/controller/transform.go b/internal/controller/transform.go index 84e5cc9d..5a5716c9 100644 --- a/internal/controller/transform.go +++ b/internal/controller/transform.go @@ -45,3 +45,23 @@ func TransformStripPod(object any) (any, error) { return cache.TransformStripManagedFields()(pod) } + +// TransformStripNode strips a Node object to reduce cache memory usage. +// It clears Spec entirely, clears Status except for NodeInfo.OperatingSystem/Architecture, and strips Annotations and managed fields. +func TransformStripNode(object any) (any, error) { + node, ok := object.(*corev1.Node) + if !ok { + return object, fmt.Errorf("expected Node object, got %T", object) + } + + node.Annotations = nil + node.Spec = corev1.NodeSpec{} + node.Status = corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + OperatingSystem: node.Status.NodeInfo.OperatingSystem, + Architecture: node.Status.NodeInfo.Architecture, + }, + } + + return cache.TransformStripManagedFields()(node) +} diff --git a/internal/controller/transform_test.go b/internal/controller/transform_test.go index a9e262c3..cfe901f7 100644 --- a/internal/controller/transform_test.go +++ b/internal/controller/transform_test.go @@ -110,3 +110,92 @@ func TestTransformStripPod(t *testing.T) { assert.Empty(t, resultPod.Status.PodIP) assert.Nil(t, resultPod.Status.ContainerStatuses) } + +func TestTransformStripNode(t *testing.T) { + node := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + Labels: map[string]string{ + "kubernetes.io/os": "linux", + "kubernetes.io/arch": "amd64", + }, + Annotations: map[string]string{ + "node.alpha.kubernetes.io/ttl": "0", + }, + }, + Spec: corev1.NodeSpec{ + PodCIDR: "10.244.0.0/24", + ProviderID: "aws:///us-east-1a/i-0abcdef", + Unschedulable: true, + Taints: []corev1.Taint{ + {Key: "dedicated", Value: "gpu", Effect: corev1.TaintEffectNoSchedule}, + }, + }, + Status: corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + OperatingSystem: "linux", + Architecture: "amd64", + KernelVersion: "5.15.0", + OSImage: "Ubuntu 22.04", + KubeletVersion: "v1.30.0", + }, + Capacity: corev1.ResourceList{ //nolint:exhaustive // this is just a test + corev1.ResourceCPU: resource.MustParse("4"), + corev1.ResourceMemory: resource.MustParse("8Gi"), + }, + Allocatable: corev1.ResourceList{ //nolint:exhaustive // this is just a test + corev1.ResourceCPU: resource.MustParse("3500m"), + corev1.ResourceMemory: resource.MustParse("7Gi"), + }, + Conditions: []corev1.NodeCondition{ + {Type: corev1.NodeReady, Status: corev1.ConditionTrue}, + }, + Addresses: []corev1.NodeAddress{ + {Type: corev1.NodeInternalIP, Address: "10.0.0.1"}, + }, + Images: []corev1.ContainerImage{ + {Names: []string{"nginx:1.25"}, SizeBytes: 12345}, + {Names: []string{"busybox:latest"}, SizeBytes: 6789}, + }, + VolumesInUse: []corev1.UniqueVolumeName{"v1", "v2"}, + VolumesAttached: []corev1.AttachedVolume{ + {Name: "v1", DevicePath: "/dev/sda"}, + }, + }, + } + + result, err := TransformStripNode(node) + require.NoError(t, err) + + resultNode := result.(*corev1.Node) + + // Name and Labels should be preserved + assert.Equal(t, "test-node", resultNode.Name) + assert.Equal(t, "linux", resultNode.Labels["kubernetes.io/os"]) + assert.Equal(t, "amd64", resultNode.Labels["kubernetes.io/arch"]) + + // Only OperatingSystem and Architecture from NodeInfo should be preserved + assert.Equal(t, "linux", resultNode.Status.NodeInfo.OperatingSystem) + assert.Equal(t, "amd64", resultNode.Status.NodeInfo.Architecture) + assert.Empty(t, resultNode.Status.NodeInfo.KernelVersion) + assert.Empty(t, resultNode.Status.NodeInfo.OSImage) + assert.Empty(t, resultNode.Status.NodeInfo.KubeletVersion) + + // Annotations should be stripped + assert.Nil(t, resultNode.Annotations) + + // Spec should be stripped entirely + assert.Empty(t, resultNode.Spec.PodCIDR) + assert.Empty(t, resultNode.Spec.ProviderID) + assert.False(t, resultNode.Spec.Unschedulable) + assert.Nil(t, resultNode.Spec.Taints) + + // All other Status fields should be stripped + assert.Empty(t, resultNode.Status.Capacity) + assert.Empty(t, resultNode.Status.Allocatable) + assert.Nil(t, resultNode.Status.Conditions) + assert.Nil(t, resultNode.Status.Addresses) + assert.Nil(t, resultNode.Status.Images) + assert.Nil(t, resultNode.Status.VolumesInUse) + assert.Nil(t, resultNode.Status.VolumesAttached) +}