package cache import ( "sort" "testing" "github.com/hanzoai/cd/gitops-engine/pkg/utils/kube" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/yaml" "github.com/hanzoai/cd/common" "github.com/hanzoai/cd/pkg/apis/application/v1alpha1" "github.com/hanzoai/cd/util/cd/normalizers" "github.com/hanzoai/cd/util/errors" ) func strToUnstructured(jsonStr string) *unstructured.Unstructured { obj := make(map[string]any) err := yaml.Unmarshal([]byte(jsonStr), &obj) errors.CheckError(err) return &unstructured.Unstructured{Object: obj} } var ( testService = strToUnstructured(` apiVersion: v1 kind: Service metadata: name: helm-guestbook namespace: default resourceVersion: "123" uid: "4" spec: selector: app: guestbook type: LoadBalancer status: loadBalancer: ingress: - hostname: localhost`) testLinkAnnotatedService = strToUnstructured(` apiVersion: v1 kind: Service metadata: name: helm-guestbook namespace: default resourceVersion: "123" uid: "4" annotations: link.cd.hanzo.ai/external-link: http://my-grafana.example.com/pre-generated-link spec: selector: app: guestbook type: LoadBalancer status: loadBalancer: ingress: - hostname: localhost`) testMaliciousLinkAnnotatedService = strToUnstructured(` apiVersion: v1 kind: Service metadata: name: helm-guestbook namespace: default resourceVersion: "123" uid: "4" annotations: link.cd.hanzo.ai/javascript: 'javascript:alert(1)' link.cd.hanzo.ai/data: 'data:text/html,' link.cd.hanzo.ai/vbscript: 'vbscript:msgbox(1)' link.cd.hanzo.ai/titled-javascript: 'click me|javascript:alert(1)' link.cd.hanzo.ai/no-scheme: 'example.com/foo' link.cd.hanzo.ai/safe: 'http://my-grafana.example.com/pre-generated-link' spec: selector: app: guestbook type: LoadBalancer status: loadBalancer: ingress: - hostname: localhost`) testIngress = strToUnstructured(` apiVersion: extensions/v1beta1 kind: Ingress metadata: name: helm-guestbook namespace: default uid: "4" spec: backend: serviceName: not-found-service servicePort: 443 rules: - host: helm-guestbook.example.com http: paths: - backend: serviceName: helm-guestbook servicePort: 443 path: / - backend: serviceName: helm-guestbook servicePort: https path: / tls: - host: helm-guestbook.example.com secretName: my-tls-secret status: loadBalancer: ingress: - ip: 107.178.210.11`) testLinkAnnotatedIngress = strToUnstructured(` apiVersion: extensions/v1beta1 kind: Ingress metadata: name: helm-guestbook namespace: default uid: "4" annotations: link.cd.hanzo.ai/external-link: http://my-grafana.example.com/ingress-link spec: backend: serviceName: not-found-service servicePort: 443 rules: - host: helm-guestbook.example.com http: paths: - backend: serviceName: helm-guestbook servicePort: 443 path: / - backend: serviceName: helm-guestbook servicePort: https path: / tls: - host: helm-guestbook.example.com secretName: my-tls-secret status: loadBalancer: ingress: - ip: 107.178.210.11`) testIgnoreDefaultLinksIngress = strToUnstructured(` apiVersion: extensions/v1beta1 kind: Ingress metadata: name: helm-guestbook namespace: default uid: "4" annotations: link.cd.hanzo.ai/external-link: http://my-grafana.example.com/ingress-link cd.hanzo.ai/ignore-default-links: "true" spec: backend: serviceName: not-found-service servicePort: 443 rules: - host: helm-guestbook.example.com http: paths: - backend: serviceName: helm-guestbook servicePort: 443 path: / - backend: serviceName: helm-guestbook servicePort: https path: / tls: - host: helm-guestbook.example.com secretName: my-tls-secret status: loadBalancer: ingress: - ip: 107.178.210.11`) testIngressWildCardPath = strToUnstructured(` apiVersion: extensions/v1beta1 kind: Ingress metadata: name: helm-guestbook namespace: default uid: "4" spec: backend: serviceName: not-found-service servicePort: 443 rules: - host: helm-guestbook.example.com http: paths: - backend: serviceName: helm-guestbook servicePort: 443 path: /* - backend: serviceName: helm-guestbook servicePort: https path: /* tls: - host: helm-guestbook.example.com secretName: my-tls-secret status: loadBalancer: ingress: - ip: 107.178.210.11`) testIngressWithoutTLS = strToUnstructured(` apiVersion: extensions/v1beta1 kind: Ingress metadata: name: helm-guestbook namespace: default uid: "4" spec: backend: serviceName: not-found-service servicePort: 443 rules: - host: helm-guestbook.example.com http: paths: - backend: serviceName: helm-guestbook servicePort: 443 path: / - backend: serviceName: helm-guestbook servicePort: https path: / status: loadBalancer: ingress: - ip: 107.178.210.11`) testIngressNetworkingV1 = strToUnstructured(` apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: helm-guestbook namespace: default uid: "4" spec: backend: service: name: not-found-service port: number: 443 rules: - host: helm-guestbook.example.com http: paths: - backend: service: name: helm-guestbook port: number: 443 path: / - backend: service: name: helm-guestbook port: name: https path: / tls: - host: helm-guestbook.example.com secretName: my-tls-secret status: loadBalancer: ingress: - ip: 107.178.210.11`) testIstioVirtualService = strToUnstructured(` apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: hello-world namespace: demo spec: http: - match: - uri: prefix: "/1" route: - destination: host: service_full.demo.svc.cluster.local - destination: host: service_namespace.namespace - match: - uri: prefix: "/2" route: - destination: host: service `) testIstioServiceEntry = strToUnstructured(` apiVersion: networking.istio.io/v1beta1 kind: ServiceEntry metadata: name: echo spec: exportTo: - '*' hosts: - echo.internal location: MESH_INTERNAL ports: - name: http number: 80 protocol: HTTP targetPort: 5678 resolution: DNS workloadSelector: labels: app.kubernetes.io/name: echo-2 `) testHTTPRoute = strToUnstructured(` apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: example-route namespace: default spec: parentRefs: - name: example-gateway namespace: default hostnames: - example.com rules: - backendRefs: - name: service-a port: 8080 - name: service-b port: 8080 `) testHTTPRouteCrossNamespace = strToUnstructured(` apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: cross-ns-route namespace: default spec: rules: - backendRefs: - name: service-other namespace: other-ns port: 8080 `) testHTTPRouteWithExternalLink = strToUnstructured(` apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: annotated-route namespace: default annotations: link.cd.hanzo.ai/external-link: https://example.com spec: rules: - backendRefs: - name: service-a port: 8080 `) testHTTPRouteMultipleRules = strToUnstructured(` apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: multi-rule-route namespace: default spec: rules: - matches: - path: value: /api backendRefs: - name: api-service port: 8080 - matches: - path: value: /web backendRefs: - name: web-service port: 80 `) testHTTPRouteCrossNamespaceParent = strToUnstructured(` apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: cross-ns-parent-route namespace: default spec: parentRefs: - name: shared-gateway namespace: gateway-ns rules: - backendRefs: - name: my-service port: 8080 `) testGRPCRoute = strToUnstructured(` apiVersion: gateway.networking.k8s.io/v1 kind: GRPCRoute metadata: name: grpc-route namespace: default spec: parentRefs: - name: grpc-gateway rules: - backendRefs: - name: grpc-service port: 50051 `) testGatewayWithIP = strToUnstructured(` apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: example-gateway namespace: default spec: gatewayClassName: example-class listeners: - name: http protocol: HTTP port: 80 status: addresses: - type: IPAddress value: 192.168.1.100 `) testGatewayWithHostname = strToUnstructured(` apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: hostname-gateway namespace: default spec: listeners: - name: https protocol: HTTPS port: 443 hostname: api.example.com status: addresses: - type: Hostname value: lb.example.com `) testGatewayMultipleListeners = strToUnstructured(` apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: multi-gateway namespace: default spec: listeners: - name: http protocol: HTTP port: 80 - name: https protocol: HTTPS port: 443 status: addresses: - type: IPAddress value: 10.0.0.1 `) testGatewayNonStandardPort = strToUnstructured(` apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: custom-port-gateway namespace: default spec: listeners: - name: http-alt protocol: HTTP port: 8080 status: addresses: - type: IPAddress value: 10.0.0.1 `) testGatewayNoAddresses = strToUnstructured(` apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: pending-gateway namespace: default spec: listeners: - name: http protocol: HTTP port: 80 `) testGatewayWithExternalLink = strToUnstructured(` apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: annotated-gateway namespace: default annotations: link.cd.hanzo.ai/external-link: https://custom-link.example.com spec: listeners: - name: http protocol: HTTP port: 80 status: addresses: - type: IPAddress value: 10.0.0.1 `) testGatewayIgnoreDefaultLinks = strToUnstructured(` apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: no-default-links-gateway namespace: default annotations: link.cd.hanzo.ai/external-link: https://custom-link.example.com cd.hanzo.ai/ignore-default-links: "true" spec: listeners: - name: http protocol: HTTP port: 80 status: addresses: - type: IPAddress value: 10.0.0.1 `) ) // These tests are equivalent to tests in ui/src/app/applications/components/utils.test.tsx. If you update tests here, // please make sure to update the equivalent tests in the UI. func TestGetPodInfo(t *testing.T) { t.Parallel() t.Run("TestGetPodInfo", func(t *testing.T) { t.Parallel() pod := strToUnstructured(` apiVersion: v1 kind: Pod metadata: name: helm-guestbook-pod namespace: default ownerReferences: - apiVersion: extensions/v1beta1 kind: ReplicaSet name: helm-guestbook-rs resourceVersion: "123" labels: app: guestbook spec: nodeName: minikube containers: - image: bar resources: requests: memory: 128Mi `) info := &ResourceInfo{} populateNodeInfo(pod, info, []string{}) assert.Equal(t, []v1alpha1.InfoItem{ {Name: "Node", Value: "minikube"}, {Name: "Containers", Value: "0/1"}, {Name: common.PodRequestsCPU, Value: "0"}, // strings imported from common {Name: common.PodRequestsMEM, Value: "134217728000"}, }, info.Info) assert.Equal(t, []string{"bar"}, info.Images) assert.Equal(t, &PodInfo{ NodeName: "minikube", ResourceRequests: corev1.ResourceList{corev1.ResourceMemory: resource.MustParse("128Mi")}, }, info.PodInfo) assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{Labels: map[string]string{"app": "guestbook"}}, info.NetworkingInfo) }) t.Run("TestGetPodWithInitialContainerInfo", func(t *testing.T) { t.Parallel() pod := strToUnstructured(` apiVersion: "v1" kind: "Pod" metadata: labels: app: "app-with-initial-container" name: "app-with-initial-container-5f46976fdb-vd6rv" namespace: "default" ownerReferences: - apiVersion: "apps/v1" kind: "ReplicaSet" name: "app-with-initial-container-5f46976fdb" spec: containers: - image: "alpine:latest" imagePullPolicy: "Always" name: "app-with-initial-container" initContainers: - image: "alpine:latest" imagePullPolicy: "Always" name: "app-with-initial-container-logshipper" nodeName: "minikube" status: containerStatuses: - image: "alpine:latest" name: "app-with-initial-container" ready: true restartCount: 0 started: true state: running: startedAt: "2024-10-08T08:44:25Z" initContainerStatuses: - image: "alpine:latest" name: "app-with-initial-container-logshipper" ready: true restartCount: 0 started: false state: terminated: exitCode: 0 reason: "Completed" phase: "Running" `) info := &ResourceInfo{} populateNodeInfo(pod, info, []string{}) assert.Equal(t, []v1alpha1.InfoItem{ {Name: "Status Reason", Value: "Running"}, {Name: "Node", Value: "minikube"}, {Name: "Containers", Value: "1/1"}, {Name: common.PodRequestsCPU, Value: "0"}, {Name: common.PodRequestsMEM, Value: "0"}, }, info.Info) }) t.Run("TestGetPodWithInitialContainerInfoWithResources", func(t *testing.T) { t.Parallel() pod := strToUnstructured(` apiVersion: "v1" kind: "Pod" metadata: labels: app: "app-with-initial-container" name: "app-with-initial-container-5f46976fdb-vd6rv" namespace: "default" ownerReferences: - apiVersion: "apps/v1" kind: "ReplicaSet" name: "app-with-initial-container-5f46976fdb" spec: containers: - image: "alpine:latest" imagePullPolicy: "Always" name: "app-with-initial-container" resources: requests: cpu: "100m" memory: "128Mi" limits: cpu: "500m" memory: "512Mi" initContainers: - image: "alpine:latest" imagePullPolicy: "Always" name: "app-with-initial-container-logshipper" resources: requests: cpu: "50m" memory: "64Mi" limits: cpu: "250m" memory: "256Mi" nodeName: "minikube" status: containerStatuses: - image: "alpine:latest" name: "app-with-initial-container" ready: true restartCount: 0 started: true state: running: startedAt: "2024-10-08T08:44:25Z" initContainerStatuses: - image: "alpine:latest" name: "app-with-initial-container-logshipper" ready: true restartCount: 0 started: false state: terminated: exitCode: 0 reason: "Completed" phase: "Running" `) info := &ResourceInfo{} populateNodeInfo(pod, info, []string{}) assert.Equal(t, []v1alpha1.InfoItem{ {Name: "Status Reason", Value: "Running"}, {Name: "Node", Value: "minikube"}, {Name: "Containers", Value: "1/1"}, {Name: common.PodRequestsCPU, Value: "100"}, {Name: common.PodRequestsMEM, Value: "134217728000"}, }, info.Info) }) t.Run("TestGetPodInfoWithSidecar", func(t *testing.T) { t.Parallel() pod := strToUnstructured(` apiVersion: v1 kind: Pod metadata: labels: app: app-with-sidecar name: app-with-sidecar-6664cc788c-lqlrp namespace: default ownerReferences: - apiVersion: apps/v1 kind: ReplicaSet name: app-with-sidecar-6664cc788c spec: containers: - image: 'docker.m.daocloud.io/library/alpine:latest' imagePullPolicy: Always name: app-with-sidecar initContainers: - image: 'docker.m.daocloud.io/library/alpine:latest' imagePullPolicy: Always name: logshipper restartPolicy: Always nodeName: minikube status: containerStatuses: - image: 'docker.m.daocloud.io/library/alpine:latest' name: app-with-sidecar ready: true restartCount: 0 started: true state: running: startedAt: '2024-10-08T08:39:43Z' initContainerStatuses: - image: 'docker.m.daocloud.io/library/alpine:latest' name: logshipper ready: true restartCount: 0 started: true state: running: startedAt: '2024-10-08T08:39:40Z' phase: Running `) info := &ResourceInfo{} populateNodeInfo(pod, info, []string{}) assert.Equal(t, []v1alpha1.InfoItem{ {Name: "Status Reason", Value: "Running"}, {Name: "Node", Value: "minikube"}, {Name: "Containers", Value: "2/2"}, {Name: common.PodRequestsCPU, Value: "0"}, {Name: common.PodRequestsMEM, Value: "0"}, }, info.Info) }) t.Run("TestGetPodInfoWithInitialContainer", func(t *testing.T) { t.Parallel() pod := strToUnstructured(` apiVersion: v1 kind: Pod metadata: generateName: myapp-long-exist-56b7d8794d- labels: app: myapp-long-exist name: myapp-long-exist-56b7d8794d-pbgrd namespace: linghao ownerReferences: - apiVersion: apps/v1 kind: ReplicaSet name: myapp-long-exist-56b7d8794d spec: containers: - image: alpine:latest imagePullPolicy: Always name: myapp-long-exist initContainers: - image: alpine:latest imagePullPolicy: Always name: myapp-long-exist-logshipper nodeName: minikube status: containerStatuses: - image: alpine:latest name: myapp-long-exist ready: false restartCount: 0 started: false state: waiting: reason: PodInitializing initContainerStatuses: - image: alpine:latest name: myapp-long-exist-logshipper ready: false restartCount: 0 started: true state: running: startedAt: '2024-10-09T08:03:45Z' phase: Pending startTime: '2024-10-09T08:02:39Z' `) info := &ResourceInfo{} populateNodeInfo(pod, info, []string{}) assert.Equal(t, []v1alpha1.InfoItem{ {Name: "Status Reason", Value: "Init:0/1"}, {Name: "Node", Value: "minikube"}, {Name: "Containers", Value: "0/1"}, {Name: common.PodRequestsCPU, Value: "0"}, {Name: common.PodRequestsMEM, Value: "0"}, }, info.Info) }) // Test pod has 2 restartable init containers, the first one running but not started. t.Run("TestGetPodInfoWithRestartableInitContainer", func(t *testing.T) { t.Parallel() pod := strToUnstructured(` apiVersion: v1 kind: Pod metadata: name: test1 spec: initContainers: - name: restartable-init-1 restartPolicy: Always - name: restartable-init-2 restartPolicy: Always containers: - name: container nodeName: minikube status: phase: Pending initContainerStatuses: - name: restartable-init-1 ready: false restartCount: 3 state: running: {} started: false lastTerminationState: terminated: finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time - name: restartable-init-2 ready: false state: waiting: {} started: false containerStatuses: - ready: false restartCount: 0 state: waiting: {} conditions: - type: ContainersReady status: "False" - type: Initialized status: "False" `) info := &ResourceInfo{} populateNodeInfo(pod, info, []string{}) assert.Equal(t, []v1alpha1.InfoItem{ {Name: "Status Reason", Value: "Init:0/2"}, {Name: "Node", Value: "minikube"}, {Name: "Containers", Value: "0/3"}, {Name: "Restart Count", Value: "3"}, {Name: common.PodRequestsCPU, Value: "0"}, {Name: common.PodRequestsMEM, Value: "0"}, }, info.Info) }) // Test pod has 2 restartable init containers, the first one started and the second one running but not started. t.Run("TestGetPodInfoWithPartiallyStartedInitContainers", func(t *testing.T) { t.Parallel() pod := strToUnstructured(` apiVersion: v1 kind: Pod metadata: name: test1 spec: initContainers: - name: restartable-init-1 restartPolicy: Always - name: restartable-init-2 restartPolicy: Always containers: - name: container nodeName: minikube status: phase: Pending initContainerStatuses: - name: restartable-init-1 ready: false restartCount: 3 state: running: {} started: true lastTerminationState: terminated: finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time - name: restartable-init-2 ready: false state: running: {} started: false containerStatuses: - ready: false restartCount: 0 state: waiting: {} conditions: - type: ContainersReady status: "False" - type: Initialized status: "False" `) info := &ResourceInfo{} populateNodeInfo(pod, info, []string{}) assert.Equal(t, []v1alpha1.InfoItem{ {Name: "Status Reason", Value: "Init:1/2"}, {Name: "Node", Value: "minikube"}, {Name: "Containers", Value: "0/3"}, {Name: "Restart Count", Value: "3"}, {Name: common.PodRequestsCPU, Value: "0"}, {Name: common.PodRequestsMEM, Value: "0"}, }, info.Info) }) // Test pod has 2 restartable init containers started and 1 container running t.Run("TestGetPodInfoWithStartedInitContainers", func(t *testing.T) { t.Parallel() pod := strToUnstructured(` apiVersion: v1 kind: Pod metadata: name: test2 spec: initContainers: - name: restartable-init-1 restartPolicy: Always - name: restartable-init-2 restartPolicy: Always containers: - name: container nodeName: minikube status: phase: Running initContainerStatuses: - name: restartable-init-1 ready: false restartCount: 3 state: running: {} started: true lastTerminationState: terminated: finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time - name: restartable-init-2 ready: false state: running: {} started: true containerStatuses: - ready: true restartCount: 4 state: running: {} lastTerminationState: terminated: finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time conditions: - type: ContainersReady status: "False" - type: Initialized status: "True" `) info := &ResourceInfo{} populateNodeInfo(pod, info, []string{}) assert.Equal(t, []v1alpha1.InfoItem{ {Name: "Status Reason", Value: "Running"}, {Name: "Node", Value: "minikube"}, {Name: "Containers", Value: "1/3"}, {Name: "Restart Count", Value: "7"}, {Name: common.PodRequestsCPU, Value: "0"}, {Name: common.PodRequestsMEM, Value: "0"}, }, info.Info) }) // Test pod has 1 init container restarting and 1 container not running t.Run("TestGetPodInfoWithNormalInitContainer", func(t *testing.T) { t.Parallel() pod := strToUnstructured(` apiVersion: v1 kind: Pod metadata: name: test7 spec: initContainers: - name: init-container containers: - name: main-container nodeName: minikube status: phase: podPhase initContainerStatuses: - ready: false restartCount: 3 state: running: {} lastTerminationState: terminated: finishedAt: "2023-10-01T00:00:00Z" # Replace with the actual time containerStatuses: - ready: false restartCount: 0 state: waiting: {} `) info := &ResourceInfo{} populateNodeInfo(pod, info, []string{}) assert.Equal(t, []v1alpha1.InfoItem{ {Name: "Status Reason", Value: "Init:0/1"}, {Name: "Node", Value: "minikube"}, {Name: "Containers", Value: "0/1"}, {Name: "Restart Count", Value: "3"}, {Name: common.PodRequestsCPU, Value: "0"}, {Name: common.PodRequestsMEM, Value: "0"}, }, info.Info) }) // Test pod condition succeed t.Run("TestPodConditionSucceeded", func(t *testing.T) { t.Parallel() pod := strToUnstructured(` apiVersion: v1 kind: Pod metadata: name: test8 spec: nodeName: minikube containers: - name: container status: phase: Succeeded containerStatuses: - ready: false restartCount: 0 state: terminated: reason: Completed exitCode: 0 `) info := &ResourceInfo{} populateNodeInfo(pod, info, []string{}) assert.Equal(t, []v1alpha1.InfoItem{ {Name: "Status Reason", Value: "Completed"}, {Name: "Node", Value: "minikube"}, {Name: "Containers", Value: "0/1"}, }, info.Info) }) // Test pod condition succeed which had some allocated resources t.Run("TestPodConditionSucceededWithResources", func(t *testing.T) { t.Parallel() pod := strToUnstructured(` apiVersion: v1 kind: Pod metadata: name: test8 spec: nodeName: minikube containers: - name: container resources: requests: cpu: "50m" memory: "64Mi" limits: cpu: "250m" memory: "256Mi" status: phase: Succeeded containerStatuses: - ready: false restartCount: 0 state: terminated: reason: Completed exitCode: 0 `) info := &ResourceInfo{} populateNodeInfo(pod, info, []string{}) assert.Equal(t, []v1alpha1.InfoItem{ {Name: "Status Reason", Value: "Completed"}, {Name: "Node", Value: "minikube"}, {Name: "Containers", Value: "0/1"}, }, info.Info) }) // Test pod condition failed t.Run("TestPodConditionFailed", func(t *testing.T) { t.Parallel() pod := strToUnstructured(` apiVersion: v1 kind: Pod metadata: name: test9 spec: nodeName: minikube containers: - name: container status: phase: Failed containerStatuses: - ready: false restartCount: 0 state: terminated: reason: Error exitCode: 1 `) info := &ResourceInfo{} populateNodeInfo(pod, info, []string{}) assert.Equal(t, []v1alpha1.InfoItem{ {Name: "Status Reason", Value: "Error"}, {Name: "Node", Value: "minikube"}, {Name: "Containers", Value: "0/1"}, }, info.Info) }) // Test pod condition failed with allocated resources t.Run("TestPodConditionFailedWithResources", func(t *testing.T) { t.Parallel() pod := strToUnstructured(` apiVersion: v1 kind: Pod metadata: name: test9 spec: nodeName: minikube containers: - name: container resources: requests: cpu: "50m" memory: "64Mi" limits: cpu: "250m" memory: "256Mi" status: phase: Failed containerStatuses: - ready: false restartCount: 0 state: terminated: reason: Error exitCode: 1 `) info := &ResourceInfo{} populateNodeInfo(pod, info, []string{}) assert.Equal(t, []v1alpha1.InfoItem{ {Name: "Status Reason", Value: "Error"}, {Name: "Node", Value: "minikube"}, {Name: "Containers", Value: "0/1"}, }, info.Info) }) // Test pod condition succeed with deletion t.Run("TestPodConditionSucceededWithDeletion", func(t *testing.T) { t.Parallel() pod := strToUnstructured(` apiVersion: v1 kind: Pod metadata: name: test10 deletionTimestamp: "2023-10-01T00:00:00Z" spec: nodeName: minikube containers: - name: container status: phase: Succeeded containerStatuses: - ready: false restartCount: 0 state: terminated: reason: Completed exitCode: 0 `) info := &ResourceInfo{} populateNodeInfo(pod, info, []string{}) assert.Equal(t, []v1alpha1.InfoItem{ {Name: "Status Reason", Value: "Completed"}, {Name: "Node", Value: "minikube"}, {Name: "Containers", Value: "0/1"}, }, info.Info) }) // Test pod condition running with deletion t.Run("TestPodConditionRunningWithDeletion", func(t *testing.T) { t.Parallel() pod := strToUnstructured(` apiVersion: v1 kind: Pod metadata: name: test11 deletionTimestamp: "2023-10-01T00:00:00Z" spec: nodeName: minikube containers: - name: container status: phase: Running containerStatuses: - ready: false restartCount: 0 state: running: {} `) info := &ResourceInfo{} populateNodeInfo(pod, info, []string{}) assert.Equal(t, []v1alpha1.InfoItem{ {Name: "Status Reason", Value: "Terminating"}, {Name: "Node", Value: "minikube"}, {Name: "Containers", Value: "0/1"}, {Name: common.PodRequestsCPU, Value: "0"}, {Name: common.PodRequestsMEM, Value: "0"}, }, info.Info) }) // Test pod condition pending with deletion t.Run("TestPodConditionPendingWithDeletion", func(t *testing.T) { t.Parallel() pod := strToUnstructured(` apiVersion: v1 kind: Pod metadata: name: test12 deletionTimestamp: "2023-10-01T00:00:00Z" spec: nodeName: minikube containers: - name: container status: phase: Pending `) info := &ResourceInfo{} populateNodeInfo(pod, info, []string{}) assert.Equal(t, []v1alpha1.InfoItem{ {Name: "Status Reason", Value: "Terminating"}, {Name: "Node", Value: "minikube"}, {Name: "Containers", Value: "0/1"}, {Name: common.PodRequestsCPU, Value: "0"}, {Name: common.PodRequestsMEM, Value: "0"}, }, info.Info) }) // Test PodScheduled condition with reason SchedulingGated t.Run("TestPodScheduledWithSchedulingGated", func(t *testing.T) { t.Parallel() pod := strToUnstructured(` apiVersion: v1 kind: Pod metadata: name: test13 spec: nodeName: minikube containers: - name: container1 - name: container2 status: phase: podPhase conditions: - type: PodScheduled status: "False" reason: SchedulingGated `) info := &ResourceInfo{} populateNodeInfo(pod, info, []string{}) assert.Equal(t, []v1alpha1.InfoItem{ {Name: "Status Reason", Value: "SchedulingGated"}, {Name: "Node", Value: "minikube"}, {Name: "Containers", Value: "0/2"}, {Name: common.PodRequestsCPU, Value: "0"}, {Name: common.PodRequestsMEM, Value: "0"}, }, info.Info) }) } func TestGetNodeInfo(t *testing.T) { node := strToUnstructured(` apiVersion: v1 kind: Node metadata: name: minikube labels: foo: bar spec: {} status: capacity: cpu: "6" memory: 6091320Ki nodeInfo: architecture: amd64 operatingSystem: linux osImage: Ubuntu 20.04 LTS `) info := &ResourceInfo{} populateNodeInfo(node, info, []string{}) assert.Equal(t, &NodeInfo{ Name: "minikube", Capacity: corev1.ResourceList{corev1.ResourceMemory: resource.MustParse("6091320Ki"), corev1.ResourceCPU: resource.MustParse("6")}, SystemInfo: corev1.NodeSystemInfo{Architecture: "amd64", OperatingSystem: "linux", OSImage: "Ubuntu 20.04 LTS"}, Labels: map[string]string{"foo": "bar"}, }, info.NodeInfo) } func TestGetServiceInfo(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testService, info, []string{}) assert.Empty(t, info.Info) assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{ TargetLabels: map[string]string{"app": "guestbook"}, Ingress: []corev1.LoadBalancerIngress{{Hostname: "localhost"}}, }, info.NetworkingInfo) } func TestGetLinkAnnotatedServiceInfo(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testLinkAnnotatedService, info, []string{}) assert.Empty(t, info.Info) assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{ TargetLabels: map[string]string{"app": "guestbook"}, Ingress: []corev1.LoadBalancerIngress{{Hostname: "localhost"}}, ExternalURLs: []string{"http://my-grafana.example.com/pre-generated-link"}, }, info.NetworkingInfo) } func TestMaliciousLinkAnnotatedServiceInfoFiltered(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testMaliciousLinkAnnotatedService, info, []string{}) require.NotNil(t, info.NetworkingInfo) // Only the http URL should make it through; javascript:, data:, vbscript:, // "title|javascript:..." and scheme-less values must be dropped. assert.Equal(t, []string{"http://my-grafana.example.com/pre-generated-link"}, info.NetworkingInfo.ExternalURLs) } func TestGetIstioVirtualServiceInfo(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testIstioVirtualService, info, []string{}) assert.Empty(t, info.Info) require.NotNil(t, info.NetworkingInfo) require.NotNil(t, info.NetworkingInfo.TargetRefs) assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{ Kind: kube.ServiceKind, Name: "service_full", Namespace: "demo", }) assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{ Kind: kube.ServiceKind, Name: "service_namespace", Namespace: "namespace", }) assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{ Kind: kube.ServiceKind, Name: "service", Namespace: "demo", }) } func TestGetIstioServiceEntryInfo(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testIstioServiceEntry, info, []string{}) assert.Empty(t, info.Info) require.NotNil(t, info.NetworkingInfo) require.NotNil(t, info.NetworkingInfo.TargetRefs) assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{ Kind: kube.PodKind, }) assert.Equal(t, map[string]string{ "app.kubernetes.io/name": "echo-2", }, info.NetworkingInfo.TargetLabels) } func TestGetHTTPRouteInfo(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testHTTPRoute, info, []string{}) assert.Empty(t, info.Info) require.NotNil(t, info.NetworkingInfo) // Should have Gateway (parentRef) + 2 Services (backendRefs) assert.Len(t, info.NetworkingInfo.TargetRefs, 3) // Check Gateway ref from parentRefs assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{ Group: "gateway.networking.k8s.io", Kind: "Gateway", Namespace: "default", Name: "example-gateway", }) // Check Service refs from backendRefs assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{ Group: "", Kind: kube.ServiceKind, Namespace: "default", Name: "service-a", }) assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{ Group: "", Kind: kube.ServiceKind, Namespace: "default", Name: "service-b", }) assert.Empty(t, info.NetworkingInfo.Ingress) } func TestGetHTTPRouteCrossNamespaceInfo(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testHTTPRouteCrossNamespace, info, []string{}) require.NotNil(t, info.NetworkingInfo) assert.Len(t, info.NetworkingInfo.TargetRefs, 1) assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{ Group: "", Kind: kube.ServiceKind, Namespace: "other-ns", Name: "service-other", }) } func TestGetHTTPRouteWithExternalLinkAnnotation(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testHTTPRouteWithExternalLink, info, []string{}) require.NotNil(t, info.NetworkingInfo) assert.Contains(t, info.NetworkingInfo.ExternalURLs, "https://example.com") } func TestGetHTTPRouteMultipleRules(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testHTTPRouteMultipleRules, info, []string{}) require.NotNil(t, info.NetworkingInfo) assert.Len(t, info.NetworkingInfo.TargetRefs, 2) serviceNames := make([]string, 0) for _, ref := range info.NetworkingInfo.TargetRefs { serviceNames = append(serviceNames, ref.Name) } assert.Contains(t, serviceNames, "api-service") assert.Contains(t, serviceNames, "web-service") } func TestGetHTTPRouteCrossNamespaceParent(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testHTTPRouteCrossNamespaceParent, info, []string{}) require.NotNil(t, info.NetworkingInfo) // Should have Gateway (cross-namespace parentRef) + Service (backendRef) assert.Len(t, info.NetworkingInfo.TargetRefs, 2) // Gateway should use explicit namespace from parentRef assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{ Group: "gateway.networking.k8s.io", Kind: "Gateway", Namespace: "gateway-ns", Name: "shared-gateway", }) // Service should use HTTPRoute's namespace (default) assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{ Group: "", Kind: kube.ServiceKind, Namespace: "default", Name: "my-service", }) } func TestGetGRPCRoute(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testGRPCRoute, info, []string{}) require.NotNil(t, info.NetworkingInfo) // Should have Gateway (parentRef) + Service (backendRef) assert.Len(t, info.NetworkingInfo.TargetRefs, 2) assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{ Group: "gateway.networking.k8s.io", Kind: "Gateway", Namespace: "default", Name: "grpc-gateway", }) assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{ Group: "", Kind: kube.ServiceKind, Namespace: "default", Name: "grpc-service", }) } func TestGetGatewayInfoWithIP(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testGatewayWithIP, info, []string{}) assert.Empty(t, info.Info) require.NotNil(t, info.NetworkingInfo) assert.Len(t, info.NetworkingInfo.Ingress, 1) assert.Equal(t, "192.168.1.100", info.NetworkingInfo.Ingress[0].IP) assert.Contains(t, info.NetworkingInfo.ExternalURLs, "http://192.168.1.100") } func TestGetGatewayInfoWithHostname(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testGatewayWithHostname, info, []string{}) require.NotNil(t, info.NetworkingInfo) assert.Len(t, info.NetworkingInfo.Ingress, 1) assert.Equal(t, "lb.example.com", info.NetworkingInfo.Ingress[0].Hostname) // Listener has explicit hostname, so URL should use that assert.Contains(t, info.NetworkingInfo.ExternalURLs, "https://api.example.com") } func TestGetGatewayInfoMultipleListeners(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testGatewayMultipleListeners, info, []string{}) require.NotNil(t, info.NetworkingInfo) assert.Len(t, info.NetworkingInfo.Ingress, 1) assert.Equal(t, "10.0.0.1", info.NetworkingInfo.Ingress[0].IP) // Should have both HTTP and HTTPS URLs assert.Len(t, info.NetworkingInfo.ExternalURLs, 2) assert.Contains(t, info.NetworkingInfo.ExternalURLs, "http://10.0.0.1") assert.Contains(t, info.NetworkingInfo.ExternalURLs, "https://10.0.0.1") } func TestGetGatewayInfoNonStandardPort(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testGatewayNonStandardPort, info, []string{}) require.NotNil(t, info.NetworkingInfo) assert.Len(t, info.NetworkingInfo.Ingress, 1) // Non-standard port should be included in URL assert.Contains(t, info.NetworkingInfo.ExternalURLs, "http://10.0.0.1:8080") } func TestGetGatewayInfoNoAddresses(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testGatewayNoAddresses, info, []string{}) require.NotNil(t, info.NetworkingInfo) // Gateway with no addresses (e.g., pending) should have empty ingress assert.Empty(t, info.NetworkingInfo.Ingress) // No URLs since no addresses to use assert.Empty(t, info.NetworkingInfo.ExternalURLs) } func TestGetGatewayInfoWithExternalLink(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testGatewayWithExternalLink, info, []string{}) require.NotNil(t, info.NetworkingInfo) // Should have both custom link and generated URL assert.Contains(t, info.NetworkingInfo.ExternalURLs, "https://custom-link.example.com") assert.Contains(t, info.NetworkingInfo.ExternalURLs, "http://10.0.0.1") } func TestGetGatewayInfoIgnoreDefaultLinks(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testGatewayIgnoreDefaultLinks, info, []string{}) require.NotNil(t, info.NetworkingInfo) // Should only have custom link, not generated URL assert.Len(t, info.NetworkingInfo.ExternalURLs, 1) assert.Contains(t, info.NetworkingInfo.ExternalURLs, "https://custom-link.example.com") } func TestGetIngressInfo(t *testing.T) { tests := []struct { Ingress *unstructured.Unstructured }{ {testIngress}, {testIngressNetworkingV1}, } for _, tc := range tests { info := &ResourceInfo{} populateNodeInfo(tc.Ingress, info, []string{}) assert.Empty(t, info.Info) sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool { return info.NetworkingInfo.TargetRefs[i].Name < info.NetworkingInfo.TargetRefs[j].Name }) assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{ Ingress: []corev1.LoadBalancerIngress{{IP: "107.178.210.11"}}, TargetRefs: []v1alpha1.ResourceRef{{ Namespace: "default", Group: "", Kind: kube.ServiceKind, Name: "helm-guestbook", }, { Namespace: "default", Group: "", Kind: kube.ServiceKind, Name: "not-found-service", }}, ExternalURLs: []string{"https://helm-guestbook.example.com/"}, }, info.NetworkingInfo) } } func TestGetLinkAnnotatedIngressInfo(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testLinkAnnotatedIngress, info, []string{}) assert.Empty(t, info.Info) sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool { return info.NetworkingInfo.TargetRefs[i].Name < info.NetworkingInfo.TargetRefs[j].Name }) assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{ Ingress: []corev1.LoadBalancerIngress{{IP: "107.178.210.11"}}, TargetRefs: []v1alpha1.ResourceRef{{ Namespace: "default", Group: "", Kind: kube.ServiceKind, Name: "helm-guestbook", }, { Namespace: "default", Group: "", Kind: kube.ServiceKind, Name: "not-found-service", }}, ExternalURLs: []string{"http://my-grafana.example.com/ingress-link", "https://helm-guestbook.example.com/"}, }, info.NetworkingInfo) } func TestGetIgnoreDefaultLinksIngressInfo(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testIgnoreDefaultLinksIngress, info, []string{}) assert.Empty(t, info.Info) sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool { return info.NetworkingInfo.TargetRefs[i].Name < info.NetworkingInfo.TargetRefs[j].Name }) assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{ Ingress: []corev1.LoadBalancerIngress{{IP: "107.178.210.11"}}, TargetRefs: []v1alpha1.ResourceRef{{ Namespace: "default", Group: "", Kind: kube.ServiceKind, Name: "helm-guestbook", }, { Namespace: "default", Group: "", Kind: kube.ServiceKind, Name: "not-found-service", }}, ExternalURLs: []string{"http://my-grafana.example.com/ingress-link"}, }, info.NetworkingInfo) } func TestGetIngressInfoWildCardPath(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testIngressWildCardPath, info, []string{}) assert.Empty(t, info.Info) sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool { return info.NetworkingInfo.TargetRefs[i].Name < info.NetworkingInfo.TargetRefs[j].Name }) assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{ Ingress: []corev1.LoadBalancerIngress{{IP: "107.178.210.11"}}, TargetRefs: []v1alpha1.ResourceRef{{ Namespace: "default", Group: "", Kind: kube.ServiceKind, Name: "helm-guestbook", }, { Namespace: "default", Group: "", Kind: kube.ServiceKind, Name: "not-found-service", }}, ExternalURLs: []string{"https://helm-guestbook.example.com/"}, }, info.NetworkingInfo) } func TestGetIngressInfoWithoutTls(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testIngressWithoutTLS, info, []string{}) assert.Empty(t, info.Info) sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool { return info.NetworkingInfo.TargetRefs[i].Name < info.NetworkingInfo.TargetRefs[j].Name }) assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{ Ingress: []corev1.LoadBalancerIngress{{IP: "107.178.210.11"}}, TargetRefs: []v1alpha1.ResourceRef{{ Namespace: "default", Group: "", Kind: kube.ServiceKind, Name: "helm-guestbook", }, { Namespace: "default", Group: "", Kind: kube.ServiceKind, Name: "not-found-service", }}, ExternalURLs: []string{"http://helm-guestbook.example.com/"}, }, info.NetworkingInfo) } func TestGetIngressInfoWithHost(t *testing.T) { ingress := strToUnstructured(` apiVersion: extensions/v1beta1 kind: Ingress metadata: name: helm-guestbook namespace: default spec: rules: - http: paths: - backend: serviceName: helm-guestbook servicePort: 443 path: / tls: - secretName: my-tls status: loadBalancer: ingress: - ip: 107.178.210.11`) info := &ResourceInfo{} populateNodeInfo(ingress, info, []string{}) assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{ Ingress: []corev1.LoadBalancerIngress{{IP: "107.178.210.11"}}, TargetRefs: []v1alpha1.ResourceRef{{ Namespace: "default", Group: "", Kind: kube.ServiceKind, Name: "helm-guestbook", }}, ExternalURLs: []string{"https://107.178.210.11/"}, }, info.NetworkingInfo) } func TestGetIngressInfoNoHost(t *testing.T) { ingress := strToUnstructured(` apiVersion: extensions/v1beta1 kind: Ingress metadata: name: helm-guestbook namespace: default spec: rules: - http: paths: - backend: serviceName: helm-guestbook servicePort: 443 path: / tls: - secretName: my-tls `) info := &ResourceInfo{} populateNodeInfo(ingress, info, []string{}) assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{ TargetRefs: []v1alpha1.ResourceRef{{ Namespace: "default", Group: "", Kind: kube.ServiceKind, Name: "helm-guestbook", }}, }, info.NetworkingInfo) assert.Empty(t, info.NetworkingInfo.ExternalURLs) } func TestExternalUrlWithSubPath(t *testing.T) { ingress := strToUnstructured(` apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: helm-guestbook namespace: default spec: rules: - http: paths: - backend: serviceName: helm-guestbook servicePort: 443 path: /my/sub/path/ tls: - secretName: my-tls status: loadBalancer: ingress: - ip: 107.178.210.11`) info := &ResourceInfo{} populateNodeInfo(ingress, info, []string{}) expectedExternalUrls := []string{"https://107.178.210.11/my/sub/path/"} assert.Equal(t, expectedExternalUrls, info.NetworkingInfo.ExternalURLs) } func TestExternalUrlWithMultipleSubPaths(t *testing.T) { ingress := strToUnstructured(` apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: helm-guestbook namespace: default spec: rules: - host: helm-guestbook.example.com http: paths: - backend: serviceName: helm-guestbook servicePort: 443 path: /my/sub/path/ - backend: serviceName: helm-guestbook-2 servicePort: 443 path: /my/sub/path/2 - backend: serviceName: helm-guestbook-3 servicePort: 443 tls: - secretName: my-tls status: loadBalancer: ingress: - ip: 107.178.210.11`) info := &ResourceInfo{} populateNodeInfo(ingress, info, []string{}) expectedExternalUrls := []string{"https://helm-guestbook.example.com/my/sub/path/", "https://helm-guestbook.example.com/my/sub/path/2", "https://helm-guestbook.example.com"} actualURLs := info.NetworkingInfo.ExternalURLs sort.Strings(expectedExternalUrls) sort.Strings(actualURLs) assert.Equal(t, expectedExternalUrls, actualURLs) } func TestExternalUrlWithNoSubPath(t *testing.T) { ingress := strToUnstructured(` apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: helm-guestbook namespace: default spec: rules: - http: paths: - backend: serviceName: helm-guestbook servicePort: 443 tls: - secretName: my-tls status: loadBalancer: ingress: - ip: 107.178.210.11`) info := &ResourceInfo{} populateNodeInfo(ingress, info, []string{}) expectedExternalUrls := []string{"https://107.178.210.11"} assert.Equal(t, expectedExternalUrls, info.NetworkingInfo.ExternalURLs) } func TestExternalUrlWithNetworkingApi(t *testing.T) { ingress := strToUnstructured(` apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: helm-guestbook namespace: default spec: rules: - http: paths: - backend: serviceName: helm-guestbook servicePort: 443 tls: - secretName: my-tls status: loadBalancer: ingress: - ip: 107.178.210.11`) info := &ResourceInfo{} populateNodeInfo(ingress, info, []string{}) expectedExternalUrls := []string{"https://107.178.210.11"} assert.Equal(t, expectedExternalUrls, info.NetworkingInfo.ExternalURLs) } func TestCustomLabel(t *testing.T) { configmap := strToUnstructured(` apiVersion: v1 kind: ConfigMap metadata: name: cm`) info := &ResourceInfo{} populateNodeInfo(configmap, info, []string{"my-label"}) assert.Empty(t, info.Info) configmap = strToUnstructured(` apiVersion: v1 kind: ConfigMap metadata: name: cm labels: my-label: value`) info = &ResourceInfo{} populateNodeInfo(configmap, info, []string{"my-label", "other-label"}) assert.Len(t, info.Info, 1) assert.Equal(t, "my-label", info.Info[0].Name) assert.Equal(t, "value", info.Info[0].Value) configmap = strToUnstructured(` apiVersion: v1 kind: ConfigMap metadata: name: cm labels: my-label: value other-label: value2`) info = &ResourceInfo{} populateNodeInfo(configmap, info, []string{"my-label", "other-label"}) assert.Len(t, info.Info, 2) assert.Equal(t, "my-label", info.Info[0].Name) assert.Equal(t, "value", info.Info[0].Value) assert.Equal(t, "other-label", info.Info[1].Name) assert.Equal(t, "value2", info.Info[1].Value) } func TestManifestHash(t *testing.T) { manifest := strToUnstructured(` apiVersion: v1 kind: Pod metadata: name: helm-guestbook-pod namespace: default ownerReferences: - apiVersion: extensions/v1beta1 kind: ReplicaSet name: helm-guestbook-rs resourceVersion: "123" labels: app: guestbook spec: nodeName: minikube containers: - image: bar resources: requests: memory: 128Mi `) ignores := []v1alpha1.ResourceIgnoreDifferences{ { Group: "*", Kind: "*", JSONPointers: []string{"/metadata/resourceVersion"}, }, } data, _ := strToUnstructured(` apiVersion: v1 kind: Pod metadata: name: helm-guestbook-pod namespace: default ownerReferences: - apiVersion: extensions/v1beta1 kind: ReplicaSet name: helm-guestbook-rs labels: app: guestbook spec: nodeName: minikube containers: - image: bar resources: requests: memory: 128Mi `).MarshalJSON() expected := hash(data) hash, err := generateManifestHash(manifest, ignores, nil, normalizers.IgnoreNormalizerOpts{}) assert.Equal(t, expected, hash) assert.NoError(t, err) }