From 2b6964dcd54ae4200240803189c3ee9ce63ba439 Mon Sep 17 00:00:00 2001 From: Andrei Pohilko Date: Tue, 17 Mar 2026 18:19:39 +0000 Subject: [PATCH] refactor: reduce cyclomatic complexity in relations extraction Break up ExtractRelations, extractVolumes, extractEnvRefs, and extractIngressBackends into smaller focused functions to pass CI complexity checks. Co-Authored-By: Claude Opus 4.6 (1M context) --- pkg/dashboard/objects/relations.go | 398 +++++++++++++++-------------- 1 file changed, 213 insertions(+), 185 deletions(-) diff --git a/pkg/dashboard/objects/relations.go b/pkg/dashboard/objects/relations.go index aa0ad21..a371b43 100644 --- a/pkg/dashboard/objects/relations.go +++ b/pkg/dashboard/objects/relations.go @@ -28,38 +28,28 @@ type RelationEdge struct { Type string `json:"type"` } +type edgeAdder func(sourceID, targetKind, targetName, edgeType string) + +type workloadLabels struct { + id string + labels map[string]interface{} +} + func nodeID(kind, name string) string { return kind + "/" + name } +func docKindAndName(doc map[string]interface{}) (string, string) { + kind, _ := doc["kind"].(string) + metadata, _ := doc["metadata"].(map[string]interface{}) + name, _ := metadata["name"].(string) + return kind, name +} + // ExtractRelations parses a manifest and returns the resource relation graph. func ExtractRelations(manifest string) RelationGraph { - dec := yaml.NewYAMLOrJSONDecoder(strings.NewReader(manifest), 4096) - - var docs []map[string]interface{} - for { - var tmp map[string]interface{} - if err := dec.Decode(&tmp); err != nil { - break - } - if tmp == nil { - continue - } - kind, _ := tmp["kind"].(string) - if kind == "" { - continue - } - docs = append(docs, tmp) - } - - nodes := map[string]RelationNode{} - for _, doc := range docs { - kind, _ := doc["kind"].(string) - metadata, _ := doc["metadata"].(map[string]interface{}) - name, _ := metadata["name"].(string) - id := nodeID(kind, name) - nodes[id] = RelationNode{ID: id, Kind: kind, Name: name, InRelease: true} - } + docs := parseManifestDocs(manifest) + nodes := buildNodeSet(docs) var edges []RelationEdge addEdge := func(sourceID, targetKind, targetName, edgeType string) { @@ -73,83 +63,11 @@ func ExtractRelations(manifest string) RelationGraph { edges = append(edges, RelationEdge{Source: sourceID, Target: tid, Type: edgeType}) } - // Build label index for selector matching: workload ID -> template labels - type workloadLabels struct { - id string - labels map[string]interface{} - } - var workloads []workloadLabels + workloads := buildWorkloadIndex(docs) for _, doc := range docs { - kind, _ := doc["kind"].(string) - metadata, _ := doc["metadata"].(map[string]interface{}) - name, _ := metadata["name"].(string) - spec, _ := doc["spec"].(map[string]interface{}) - if spec == nil { - continue - } - tpl, _ := spec["template"].(map[string]interface{}) - if tpl == nil { - continue - } - tplMeta, _ := tpl["metadata"].(map[string]interface{}) - if tplMeta == nil { - continue - } - lbls, _ := tplMeta["labels"].(map[string]interface{}) - if lbls != nil { - workloads = append(workloads, workloadLabels{id: nodeID(kind, name), labels: lbls}) - } + extractDocEdges(doc, workloads, addEdge) } - for _, doc := range docs { - kind, _ := doc["kind"].(string) - metadata, _ := doc["metadata"].(map[string]interface{}) - name, _ := metadata["name"].(string) - srcID := nodeID(kind, name) - - // ownerReferences - extractOwnerRefs(doc, srcID, addEdge) - - // recursive *Ref fields (skip metadata to avoid self-refs from ownerReferences) - collectRefFields(doc, srcID, addEdge) - - // volumes, envFrom, serviceAccount from pod specs - for _, podSpec := range findPodSpecs(kind, doc) { - extractVolumes(podSpec, srcID, addEdge) - extractEnvRefs(podSpec, srcID, addEdge) - extractServiceAccount(podSpec, srcID, addEdge) - } - - // Service selector -> workloads - if kind == "Service" { - spec, _ := doc["spec"].(map[string]interface{}) - if spec != nil { - selector, _ := spec["selector"].(map[string]interface{}) - if len(selector) > 0 { - for _, wl := range workloads { - if labelsMatch(selector, wl.labels) { - parts := strings.SplitN(wl.id, "/", 2) - if len(parts) == 2 { - addEdge(srcID, parts[0], parts[1], "selector") - } - } - } - } - } - } - - // Ingress -> Service - if kind == "Ingress" { - extractIngressBackends(doc, srcID, addEdge) - } - - // RoleBinding / ClusterRoleBinding - if kind == "RoleBinding" || kind == "ClusterRoleBinding" { - extractRoleBindingRefs(doc, srcID, addEdge) - } - } - - // Deduplicate edges edges = deduplicateEdges(edges) nodeSlice := make([]RelationNode, 0, len(nodes)) @@ -160,7 +78,86 @@ func ExtractRelations(manifest string) RelationGraph { return RelationGraph{Nodes: nodeSlice, Edges: edges} } -func extractOwnerRefs(doc map[string]interface{}, srcID string, addEdge func(string, string, string, string)) { +func parseManifestDocs(manifest string) []map[string]interface{} { + dec := yaml.NewYAMLOrJSONDecoder(strings.NewReader(manifest), 4096) + var docs []map[string]interface{} + for { + var tmp map[string]interface{} + if err := dec.Decode(&tmp); err != nil { + break + } + kind, _ := tmp["kind"].(string) + if kind != "" { + docs = append(docs, tmp) + } + } + return docs +} + +func buildNodeSet(docs []map[string]interface{}) map[string]RelationNode { + nodes := map[string]RelationNode{} + for _, doc := range docs { + kind, name := docKindAndName(doc) + id := nodeID(kind, name) + nodes[id] = RelationNode{ID: id, Kind: kind, Name: name, InRelease: true} + } + return nodes +} + +func buildWorkloadIndex(docs []map[string]interface{}) []workloadLabels { + var workloads []workloadLabels + for _, doc := range docs { + kind, name := docKindAndName(doc) + lbls := getTemplateLabels(doc) + if lbls != nil { + workloads = append(workloads, workloadLabels{id: nodeID(kind, name), labels: lbls}) + } + } + return workloads +} + +func getTemplateLabels(doc map[string]interface{}) map[string]interface{} { + spec, _ := doc["spec"].(map[string]interface{}) + if spec == nil { + return nil + } + tpl, _ := spec["template"].(map[string]interface{}) + if tpl == nil { + return nil + } + tplMeta, _ := tpl["metadata"].(map[string]interface{}) + if tplMeta == nil { + return nil + } + lbls, _ := tplMeta["labels"].(map[string]interface{}) + return lbls +} + +func extractDocEdges(doc map[string]interface{}, workloads []workloadLabels, addEdge edgeAdder) { + kind, name := docKindAndName(doc) + srcID := nodeID(kind, name) + + extractOwnerRefs(doc, srcID, addEdge) + collectRefFields(doc, srcID, addEdge) + + for _, podSpec := range findPodSpecs(kind, doc) { + extractVolumes(podSpec, srcID, addEdge) + extractEnvRefs(podSpec, srcID, addEdge) + extractServiceAccount(podSpec, srcID, addEdge) + } + + if kind == "Service" { + extractServiceSelector(doc, srcID, workloads, addEdge) + } + if kind == "Ingress" { + extractIngressBackends(doc, srcID, addEdge) + } + if kind == "RoleBinding" || kind == "ClusterRoleBinding" { + extractRoleBindingRefs(doc, srcID, addEdge) + } +} + +func extractOwnerRefs(doc map[string]interface{}, srcID string, addEdge edgeAdder) { metadata, _ := doc["metadata"].(map[string]interface{}) if metadata == nil { return @@ -179,8 +176,7 @@ func extractOwnerRefs(doc map[string]interface{}, srcID string, addEdge func(str } } -func collectRefFields(doc map[string]interface{}, srcID string, addEdge func(string, string, string, string)) { - // Walk the doc but skip metadata (ownerReferences handled separately) +func collectRefFields(doc map[string]interface{}, srcID string, addEdge edgeAdder) { for key, value := range doc { if key == "metadata" { continue @@ -189,7 +185,7 @@ func collectRefFields(doc map[string]interface{}, srcID string, addEdge func(str } } -func collectRefFieldsRecursive(obj interface{}, srcID string, addEdge func(string, string, string, string)) { +func collectRefFieldsRecursive(obj interface{}, srcID string, addEdge edgeAdder) { switch v := obj.(type) { case map[string]interface{}: for key, value := range v { @@ -206,7 +202,7 @@ func collectRefFieldsRecursive(obj interface{}, srcID string, addEdge func(strin } } -func tryAddRef(value interface{}, srcID string, addEdge func(string, string, string, string)) { +func tryAddRef(value interface{}, srcID string, addEdge edgeAdder) { switch v := value.(type) { case map[string]interface{}: kind, _ := v["kind"].(string) @@ -227,51 +223,59 @@ func tryAddRef(value interface{}, srcID string, addEdge func(string, string, str } } -func extractVolumes(podSpec map[string]interface{}, srcID string, addEdge func(string, string, string, string)) { +func extractVolumes(podSpec map[string]interface{}, srcID string, addEdge edgeAdder) { volumes, _ := podSpec["volumes"].([]interface{}) for _, vol := range volumes { v, ok := vol.(map[string]interface{}) if !ok { continue } - if cm, ok := v["configMap"].(map[string]interface{}); ok { + extractVolumeSource(v, srcID, addEdge) + } +} + +func extractVolumeSource(v map[string]interface{}, srcID string, addEdge edgeAdder) { + if cm, ok := v["configMap"].(map[string]interface{}); ok { + if name, _ := cm["name"].(string); name != "" { + addEdge(srcID, "ConfigMap", name, "volume") + } + } + if sec, ok := v["secret"].(map[string]interface{}); ok { + if name, _ := sec["secretName"].(string); name != "" { + addEdge(srcID, "Secret", name, "volume") + } + } + if pvc, ok := v["persistentVolumeClaim"].(map[string]interface{}); ok { + if name, _ := pvc["claimName"].(string); name != "" { + addEdge(srcID, "PersistentVolumeClaim", name, "volume") + } + } + if proj, ok := v["projected"].(map[string]interface{}); ok { + extractProjectedSources(proj, srcID, addEdge) + } +} + +func extractProjectedSources(proj map[string]interface{}, srcID string, addEdge edgeAdder) { + sources, _ := proj["sources"].([]interface{}) + for _, s := range sources { + src, ok := s.(map[string]interface{}) + if !ok { + continue + } + if cm, ok := src["configMap"].(map[string]interface{}); ok { if name, _ := cm["name"].(string); name != "" { addEdge(srcID, "ConfigMap", name, "volume") } } - if sec, ok := v["secret"].(map[string]interface{}); ok { - if name, _ := sec["secretName"].(string); name != "" { + if sec, ok := src["secret"].(map[string]interface{}); ok { + if name, _ := sec["name"].(string); name != "" { addEdge(srcID, "Secret", name, "volume") } } - if pvc, ok := v["persistentVolumeClaim"].(map[string]interface{}); ok { - if name, _ := pvc["claimName"].(string); name != "" { - addEdge(srcID, "PersistentVolumeClaim", name, "volume") - } - } - if proj, ok := v["projected"].(map[string]interface{}); ok { - sources, _ := proj["sources"].([]interface{}) - for _, s := range sources { - src, ok := s.(map[string]interface{}) - if !ok { - continue - } - if cm, ok := src["configMap"].(map[string]interface{}); ok { - if name, _ := cm["name"].(string); name != "" { - addEdge(srcID, "ConfigMap", name, "volume") - } - } - if sec, ok := src["secret"].(map[string]interface{}); ok { - if name, _ := sec["name"].(string); name != "" { - addEdge(srcID, "Secret", name, "volume") - } - } - } - } } } -func extractEnvRefs(podSpec map[string]interface{}, srcID string, addEdge func(string, string, string, string)) { +func extractEnvRefs(podSpec map[string]interface{}, srcID string, addEdge edgeAdder) { containers, _ := podSpec["containers"].([]interface{}) initContainers, _ := podSpec["initContainers"].([]interface{}) allContainers := append(containers, initContainers...) @@ -281,91 +285,119 @@ func extractEnvRefs(podSpec map[string]interface{}, srcID string, addEdge func(s if !ok { continue } - // envFrom - envFrom, _ := cMap["envFrom"].([]interface{}) - for _, ef := range envFrom { - e, ok := ef.(map[string]interface{}) - if !ok { - continue - } - if cmRef, ok := e["configMapRef"].(map[string]interface{}); ok { - if name, _ := cmRef["name"].(string); name != "" { - addEdge(srcID, "ConfigMap", name, "envRef") - } - } - if secRef, ok := e["secretRef"].(map[string]interface{}); ok { - if name, _ := secRef["name"].(string); name != "" { - addEdge(srcID, "Secret", name, "envRef") - } + extractContainerEnvRefs(cMap, srcID, addEdge) + } +} + +func extractContainerEnvRefs(cMap map[string]interface{}, srcID string, addEdge edgeAdder) { + envFrom, _ := cMap["envFrom"].([]interface{}) + for _, ef := range envFrom { + e, ok := ef.(map[string]interface{}) + if !ok { + continue + } + if cmRef, ok := e["configMapRef"].(map[string]interface{}); ok { + if name, _ := cmRef["name"].(string); name != "" { + addEdge(srcID, "ConfigMap", name, "envRef") } } - // env[].valueFrom - envVars, _ := cMap["env"].([]interface{}) - for _, ev := range envVars { - envVar, ok := ev.(map[string]interface{}) - if !ok { - continue + if secRef, ok := e["secretRef"].(map[string]interface{}); ok { + if name, _ := secRef["name"].(string); name != "" { + addEdge(srcID, "Secret", name, "envRef") } - valueFrom, _ := envVar["valueFrom"].(map[string]interface{}) - if valueFrom == nil { - continue + } + } + extractEnvValueFromRefs(cMap, srcID, addEdge) +} + +func extractEnvValueFromRefs(cMap map[string]interface{}, srcID string, addEdge edgeAdder) { + envVars, _ := cMap["env"].([]interface{}) + for _, ev := range envVars { + envVar, ok := ev.(map[string]interface{}) + if !ok { + continue + } + valueFrom, _ := envVar["valueFrom"].(map[string]interface{}) + if valueFrom == nil { + continue + } + if cmKeyRef, ok := valueFrom["configMapKeyRef"].(map[string]interface{}); ok { + if name, _ := cmKeyRef["name"].(string); name != "" { + addEdge(srcID, "ConfigMap", name, "envRef") } - if cmKeyRef, ok := valueFrom["configMapKeyRef"].(map[string]interface{}); ok { - if name, _ := cmKeyRef["name"].(string); name != "" { - addEdge(srcID, "ConfigMap", name, "envRef") - } - } - if secKeyRef, ok := valueFrom["secretKeyRef"].(map[string]interface{}); ok { - if name, _ := secKeyRef["name"].(string); name != "" { - addEdge(srcID, "Secret", name, "envRef") - } + } + if secKeyRef, ok := valueFrom["secretKeyRef"].(map[string]interface{}); ok { + if name, _ := secKeyRef["name"].(string); name != "" { + addEdge(srcID, "Secret", name, "envRef") } } } } -func extractServiceAccount(podSpec map[string]interface{}, srcID string, addEdge func(string, string, string, string)) { +func extractServiceAccount(podSpec map[string]interface{}, srcID string, addEdge edgeAdder) { if sa, _ := podSpec["serviceAccountName"].(string); sa != "" && sa != "default" { addEdge(srcID, "ServiceAccount", sa, "serviceAccount") } } -func extractIngressBackends(doc map[string]interface{}, srcID string, addEdge func(string, string, string, string)) { +func extractServiceSelector(doc map[string]interface{}, srcID string, workloads []workloadLabels, addEdge edgeAdder) { + spec, _ := doc["spec"].(map[string]interface{}) + if spec == nil { + return + } + selector, _ := spec["selector"].(map[string]interface{}) + if len(selector) == 0 { + return + } + for _, wl := range workloads { + if labelsMatch(selector, wl.labels) { + parts := strings.SplitN(wl.id, "/", 2) + if len(parts) == 2 { + addEdge(srcID, parts[0], parts[1], "selector") + } + } + } +} + +func extractIngressBackends(doc map[string]interface{}, srcID string, addEdge edgeAdder) { spec, _ := doc["spec"].(map[string]interface{}) if spec == nil { return } - // default backend if backend, ok := spec["defaultBackend"].(map[string]interface{}); ok { addIngressServiceRef(backend, srcID, addEdge) } + extractIngressRules(spec, srcID, addEdge) + extractIngressTLS(spec, srcID, addEdge) +} + +func extractIngressRules(spec map[string]interface{}, srcID string, addEdge edgeAdder) { rules, _ := spec["rules"].([]interface{}) for _, r := range rules { rule, ok := r.(map[string]interface{}) if !ok { continue } - http, _ := rule["http"].(map[string]interface{}) - if http == nil { + httpSection, _ := rule["http"].(map[string]interface{}) + if httpSection == nil { continue } - paths, _ := http["paths"].([]interface{}) + paths, _ := httpSection["paths"].([]interface{}) for _, p := range paths { path, ok := p.(map[string]interface{}) if !ok { continue } - backend, _ := path["backend"].(map[string]interface{}) - if backend == nil { - continue + if backend, ok := path["backend"].(map[string]interface{}); ok { + addIngressServiceRef(backend, srcID, addEdge) } - addIngressServiceRef(backend, srcID, addEdge) } } +} - // TLS secrets +func extractIngressTLS(spec map[string]interface{}, srcID string, addEdge edgeAdder) { tls, _ := spec["tls"].([]interface{}) for _, t := range tls { tlsEntry, ok := t.(map[string]interface{}) @@ -378,21 +410,18 @@ func extractIngressBackends(doc map[string]interface{}, srcID string, addEdge fu } } -func addIngressServiceRef(backend map[string]interface{}, srcID string, addEdge func(string, string, string, string)) { - // v1 Ingress: backend.service.name +func addIngressServiceRef(backend map[string]interface{}, srcID string, addEdge edgeAdder) { if svc, ok := backend["service"].(map[string]interface{}); ok { if name, _ := svc["name"].(string); name != "" { addEdge(srcID, "Service", name, "ingressBackend") } } - // v1beta1 Ingress: backend.serviceName if name, _ := backend["serviceName"].(string); name != "" { addEdge(srcID, "Service", name, "ingressBackend") } } -func extractRoleBindingRefs(doc map[string]interface{}, srcID string, addEdge func(string, string, string, string)) { - // roleRef +func extractRoleBindingRefs(doc map[string]interface{}, srcID string, addEdge edgeAdder) { if roleRef, ok := doc["roleRef"].(map[string]interface{}); ok { kind, _ := roleRef["kind"].(string) name, _ := roleRef["name"].(string) @@ -400,7 +429,6 @@ func extractRoleBindingRefs(doc map[string]interface{}, srcID string, addEdge fu addEdge(srcID, kind, name, "roleBinding") } } - // subjects subjects, _ := doc["subjects"].([]interface{}) for _, s := range subjects { subj, ok := s.(map[string]interface{})