Skip to content

Commit 34b1d1f

Browse files
authored
Connect KEDA scalers to observed authentication dependencies (#1987)
Connect KEDA ScaledObject and ScaledJob authentication references to observed TriggerAuthentication and ClusterTriggerAuthentication resources. Operators can navigate from a scaler to its authentication configuration and find the scalers that consume it without manually joining YAML names. Omitted authentication kind resolves to a same-namespace TriggerAuthentication. Explicit cluster authentication receives its exact cluster read tuple. Authentication configuration points into the scaler with EdgeConfigures; scaler-to-workload usage remains a separate EdgeUses relationship. Missing or invalid targets are not fabricated, and credential references are not traversed. Applications ScaledJob configuration references and counts exclude authentication objects the caller cannot read. The request projection uses exact KEDA read permissions and copies affected rows before deriving counts and the capped list, preserving the shared cache for other callers. Broader authorization of pre-existing Applications relationship categories remains a separate change. Validation: Go build, complete shared/internal Go suites, type-check, shared UI and web tests, and the embedded application build pass. Regression coverage includes mixed authentication permissions, counts above the 20-reference display cap, cache reuse and local mode. Real KEDA 2.21.0 runs cover 12 ScaledObject/ScaledJob × namespaced/cluster authentication × Secret/env/podIdentity combinations on REST, MCP and topology. All scalers reconcile Ready; the Secret/env fixture endpoint enforces its dummy bearer token. Restricted-user checks verify direct cluster authentication denial and omission from relationship projections and Applications. Test objects and the complete KEDA installation are removed and absence verified. ![Scaler authentication dependency](https://github.com/user-attachments/assets/b45de6eb-8df6-44b6-83c0-8714cc4eeb17) ![TriggerAuthentication: Required By the ScaledObject and ScaledJob](https://github.com/user-attachments/assets/c7b49964-76ad-420d-bc5b-1e14eafe76b5) Pod identity proof covers accepted AWS provider declarations and relationship navigation against a local fixture endpoint, not a cloud credential exchange. Declared authentication dependencies do not establish credential validity or trigger readiness.
1 parent 9c627cc commit 34b1d1f

9 files changed

Lines changed: 286 additions & 9 deletions

File tree

‎docs/integrations.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ Radar has first-class renderers for **AWS (CAPA)**, **GCP (CAPG)**, and **Azure
243243

244244
### What Radar Shows
245245

246-
**Topology:** ScaledObject → target workload (Deployment, StatefulSet, or Rollout). See which workloads are managed by KEDA and trace the scaling relationship.
246+
**Topology:** ScaledObject → target workload (Deployment, StatefulSet, or Rollout). ScaledObject and ScaledJob triggers also link to observed TriggerAuthentication or ClusterTriggerAuthentication resources. Authentication is a dependency, not an autoscaler target; omitted kind defaults to a same-namespace TriggerAuthentication. Cluster-scoped targets retain per-kind read gating. These declarations do not prove credential validity or trigger readiness. Applications configuration references and counts exclude authentication objects the caller cannot read.
247247

248248
<p align="center">
249249
<img src="screenshots/integrations/keda-topology.png" alt="KEDA Topology" width="800">
@@ -290,8 +290,8 @@ Radar has first-class renderers for **AWS (CAPA)**, **GCP (CAPG)**, and **Azure
290290
|-----|-------|----------|-------------|------------|
291291
| ScaledObject | `keda.sh/v1alpha1` | Yes | Yes | Yes |
292292
| ScaledJob | `keda.sh/v1alpha1` | Yes | Yes | Yes |
293-
| TriggerAuthentication | `keda.sh/v1alpha1` | — | Yes | Yes |
294-
| ClusterTriggerAuthentication | `keda.sh/v1alpha1` | — | Yes | Yes |
293+
| TriggerAuthentication | `keda.sh/v1alpha1` | When referenced | Yes | Yes |
294+
| ClusterTriggerAuthentication | `keda.sh/v1alpha1` | When referenced | Yes | Yes |
295295

296296
---
297297

‎internal/server/applications.go‎

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ func (s *Server) handleListApplications(w http.ResponseWriter, r *http.Request)
297297
s.writeError(w, http.StatusInternalServerError, err.Error())
298298
return
299299
}
300-
s.writeJSON(w, resp)
300+
s.writeJSON(w, s.filterApplicationAuthenticationRefs(r, resp))
301301
}
302302

303303
// handleApplicationHistory serves a focused app-level change narrative.
@@ -2709,3 +2709,42 @@ func namespaceFromKey(key string) string {
27092709

27102710
// (worstAppHealth / appHealthRank removed — the app rollup now uses
27112711
// packages.WorseHealth, the single rollup ordering.)
2712+
2713+
// Applications rows are shared across callers, so authentication references are authorized on a copy after the cache.
2714+
func (s *Server) filterApplicationAuthenticationRefs(r *http.Request, shared *applicationsResponse) *applicationsResponse {
2715+
if auth.UserFromContext(r.Context()) == nil {
2716+
return shared
2717+
}
2718+
out := *shared
2719+
out.Applications = append([]appRow(nil), shared.Applications...)
2720+
for i := range out.Applications {
2721+
row := &out.Applications[i]
2722+
if row.Relationships == nil {
2723+
continue
2724+
}
2725+
rel := *row.Relationships
2726+
refs := make(map[string]topology.ResourceRef, len(rel.configRefs))
2727+
withheld := false
2728+
for key, ref := range rel.configRefs {
2729+
if ref.Group == "keda.sh" && (ref.Kind == "TriggerAuthentication" || ref.Kind == "ClusterTriggerAuthentication") {
2730+
resource := "triggerauthentications"
2731+
if ref.Kind == "ClusterTriggerAuthentication" {
2732+
resource = "clustertriggerauthentications"
2733+
}
2734+
if !s.canRead(r, ref.Group, resource, ref.Namespace, "get") {
2735+
withheld = true
2736+
continue
2737+
}
2738+
}
2739+
refs[key] = ref
2740+
}
2741+
if !withheld {
2742+
continue
2743+
}
2744+
rel.configRefs = refs
2745+
rel.ConfigRefs = sortedRefs(refs, 20)
2746+
rel.Configs = len(refs)
2747+
row.Relationships = &rel
2748+
}
2749+
return &out
2750+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package server
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
9+
"github.com/skyhook-io/radar/internal/auth"
10+
"github.com/skyhook-io/radar/pkg/topology"
11+
)
12+
13+
func TestApplicationAuthenticationReferencesAreFilteredWithoutChangingSharedRows(t *testing.T) {
14+
s := &Server{permCache: auth.NewPermissionCache()}
15+
references := []topology.ResourceRef{
16+
{Kind: "TriggerAuthentication", Group: "keda.sh", Namespace: "team", Name: "private-local"},
17+
{Kind: "ClusterTriggerAuthentication", Group: "keda.sh", Name: "private-cluster"},
18+
}
19+
for i := 0; i < 21; i++ {
20+
references = append(references, topology.ResourceRef{Kind: "ConfigMap", Namespace: "team", Name: fmt.Sprintf("config-%02d", i)})
21+
}
22+
shared := &applicationsResponse{Applications: []appRow{{Relationships: &appRelationships{Configs: len(references), ConfigRefs: sortedRefs(refsByKey(references), 20), configRefs: refsByKey(references)}}}}
23+
for _, allowed := range [][2]bool{{false, false}, {true, false}, {false, true}, {true, true}, {false, false}} {
24+
permissions := &auth.UserPermissions{AllowedNamespaces: []string{"team"}}
25+
permissions.SetCanI("get", "keda.sh", "triggerauthentications", "team", allowed[0])
26+
permissions.SetCanI("get", "keda.sh", "clustertriggerauthentications", "", allowed[1])
27+
s.permCache.Set("viewer", nil, permissions)
28+
r := httptest.NewRequest(http.MethodGet, "/api/applications", nil)
29+
r = r.WithContext(auth.ContextWithUser(r.Context(), &auth.User{Username: "viewer"}))
30+
response := s.filterApplicationAuthenticationRefs(r, shared)
31+
expected := 21
32+
for _, yes := range allowed {
33+
if yes {
34+
expected++
35+
}
36+
}
37+
rel := response.Applications[0].Relationships
38+
if rel.Configs != expected || len(rel.ConfigRefs) != 20 || len(rel.configRefs) != expected {
39+
t.Fatalf("allowed=%v count/ref leak: %+v", allowed, rel)
40+
}
41+
for _, ref := range rel.configRefs {
42+
if ref.Kind == "TriggerAuthentication" && !allowed[0] || ref.Kind == "ClusterTriggerAuthentication" && !allowed[1] {
43+
t.Fatalf("denied ref retained: %+v", ref)
44+
}
45+
}
46+
if shared.Applications[0].Relationships.Configs != 23 || len(shared.Applications[0].Relationships.configRefs) != 23 {
47+
t.Fatal("per-user projection mutated shared cache")
48+
}
49+
}
50+
anonymous := httptest.NewRequest(http.MethodGet, "/api/applications", nil)
51+
if s.filterApplicationAuthenticationRefs(anonymous, shared) != shared {
52+
t.Fatal("local no-auth response was copied or changed")
53+
}
54+
}

‎pkg/configrefs/keda.go‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package configrefs
2+
3+
import (
4+
"github.com/skyhook-io/radar/pkg/resourceid"
5+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
6+
)
7+
8+
// KEDAAuthenticationReferences extracts trigger authentication declarations.
9+
// KEDA defines omitted kind as TriggerAuthentication in the scaler namespace;
10+
// ClusterTriggerAuthentication is cluster-scoped. Credential scope is separate.
11+
func KEDAAuthenticationReferences(scaler *unstructured.Unstructured) []resourceid.Ref {
12+
if scaler == nil || resourceid.GroupFromAPIVersion(scaler.GetAPIVersion()) != "keda.sh" || scaler.GetKind() != "ScaledObject" && scaler.GetKind() != "ScaledJob" {
13+
return nil
14+
}
15+
triggers, _, _ := unstructured.NestedSlice(scaler.Object, "spec", "triggers")
16+
seen := map[resourceid.Ref]bool{}
17+
var refs []resourceid.Ref
18+
for _, value := range triggers {
19+
trigger, ok := value.(map[string]any)
20+
if !ok {
21+
continue
22+
}
23+
name, _, _ := unstructured.NestedString(trigger, "authenticationRef", "name")
24+
kind, _, _ := unstructured.NestedString(trigger, "authenticationRef", "kind")
25+
if kind == "" {
26+
kind = "TriggerAuthentication"
27+
}
28+
if name == "" || kind != "TriggerAuthentication" && kind != "ClusterTriggerAuthentication" {
29+
continue
30+
}
31+
namespace := scaler.GetNamespace()
32+
if kind == "ClusterTriggerAuthentication" {
33+
namespace = ""
34+
}
35+
ref := resourceid.NewRef("keda.sh", kind, namespace, name)
36+
if !seen[ref] {
37+
refs = append(refs, ref)
38+
seen[ref] = true
39+
}
40+
}
41+
return refs
42+
}

‎pkg/configrefs/keda_test.go‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package configrefs
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
7+
"github.com/skyhook-io/radar/pkg/resourceid"
8+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
9+
)
10+
11+
func TestKEDAAuthenticationReferences(t *testing.T) {
12+
for _, kind := range []string{"ScaledObject", "ScaledJob"} {
13+
t.Run(kind, func(t *testing.T) {
14+
scaler := &unstructured.Unstructured{Object: map[string]any{
15+
"apiVersion": "keda.sh/v1alpha1", "kind": kind,
16+
"metadata": map[string]any{"name": "worker", "namespace": "team"},
17+
"spec": map[string]any{"triggers": []any{
18+
map[string]any{"authenticationRef": map[string]any{"name": "credentials"}},
19+
map[string]any{"authenticationRef": map[string]any{"name": "credentials", "kind": "TriggerAuthentication"}},
20+
map[string]any{"authenticationRef": map[string]any{"name": "credentials", "kind": "ClusterTriggerAuthentication"}},
21+
map[string]any{"authenticationRef": map[string]any{"name": "bad", "kind": "Secret"}},
22+
map[string]any{"authenticationRef": map[string]any{"kind": "TriggerAuthentication"}},
23+
map[string]any{},
24+
}},
25+
}}
26+
before := scaler.DeepCopy()
27+
want := []resourceid.Ref{resourceid.NewRef("keda.sh", "TriggerAuthentication", "team", "credentials"), resourceid.NewRef("keda.sh", "ClusterTriggerAuthentication", "", "credentials")}
28+
if got := KEDAAuthenticationReferences(scaler); !reflect.DeepEqual(got, want) {
29+
t.Fatalf("references = %+v, want %+v", got, want)
30+
}
31+
if !reflect.DeepEqual(scaler, before) {
32+
t.Fatal("mutated scaler")
33+
}
34+
scaler.SetAPIVersion("other.example/v1")
35+
if got := KEDAAuthenticationReferences(scaler); len(got) != 0 {
36+
t.Fatalf("non-KEDA source: %+v", got)
37+
}
38+
})
39+
}
40+
if got := KEDAAuthenticationReferences(nil); len(got) != 0 {
41+
t.Fatal(got)
42+
}
43+
}

‎pkg/topology/builder.go‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,8 @@ func (b *Builder) buildResourcesTopology(opts BuildOptions) (*Topology, error) {
11761176
}
11771177
}
11781178

1179+
var kedaAuthenticationDependencies []declaredDependency
1180+
11791181
// 1i. Add KEDA ScaledObject and ScaledJob nodes (CRD - fetched via dynamic cache)
11801182
var scaledObjectGVR schema.GroupVersionResource
11811183
hasScaledObjects := false
@@ -1208,6 +1210,10 @@ func (b *Builder) buildResourcesTopology(opts BuildOptions) (*Topology, error) {
12081210
},
12091211
})
12101212

1213+
for _, ref := range configrefs.KEDAAuthenticationReferences(so) {
1214+
kedaAuthenticationDependencies = append(kedaAuthenticationDependencies, declaredDependency{Source: resourceid.NewRef("keda.sh", "ScaledObject", ns, name), Target: ref, Label: "authentication", Configuration: true})
1215+
}
1216+
12111217
// ScaledObject → target workload edge (via spec.scaleTargetRef)
12121218
targetAPIVersion, _, _ := unstructured.NestedString(so.Object, "spec", "scaleTargetRef", "apiVersion")
12131219
targetKind, _, _ := unstructured.NestedString(so.Object, "spec", "scaleTargetRef", "kind")
@@ -1269,6 +1275,10 @@ func (b *Builder) buildResourcesTopology(opts BuildOptions) (*Topology, error) {
12691275
"apiVersion": sj.GetAPIVersion(),
12701276
},
12711277
})
1278+
for _, ref := range configrefs.KEDAAuthenticationReferences(sj) {
1279+
kedaAuthenticationDependencies = append(kedaAuthenticationDependencies, declaredDependency{Source: resourceid.NewRef("keda.sh", "ScaledJob", ns, name), Target: ref, Label: "authentication", Configuration: true})
1280+
}
1281+
12721282
}
12731283
}
12741284

@@ -3774,6 +3784,9 @@ func (b *Builder) buildResourcesTopology(opts BuildOptions) (*Topology, error) {
37743784
}
37753785
}
37763786

3787+
nodes, edges, kedaAuthenticationWarnings := addObservedDependencyEdges(nodes, edges, kedaAuthenticationDependencies, dynamicCache, opts)
3788+
warnings = append(warnings, kedaAuthenticationWarnings...)
3789+
37773790
// 8. Add ConfigMap nodes (if enabled)
37783791
if opts.IncludeConfigMaps {
37793792
configmaps, cmErr := b.provider.ConfigMaps()
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package topology
2+
3+
import (
4+
"testing"
5+
6+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
7+
"k8s.io/apimachinery/pkg/runtime/schema"
8+
)
9+
10+
func TestKEDAAuthenticationDependenciesObservedAndScoped(t *testing.T) {
11+
gvrs := map[string]schema.GroupVersionResource{}
12+
for kind, resource := range map[string]string{"ScaledObject": "scaledobjects", "ScaledJob": "scaledjobs", "TriggerAuthentication": "triggerauthentications", "ClusterTriggerAuthentication": "clustertriggerauthentications"} {
13+
gvrs[kind] = schema.GroupVersionResource{Group: "keda.sh", Version: "v1alpha1", Resource: resource}
14+
}
15+
makeObject := func(kind, namespace, name string) *unstructured.Unstructured {
16+
return &unstructured.Unstructured{Object: map[string]any{"apiVersion": "keda.sh/v1alpha1", "kind": kind, "metadata": map[string]any{"namespace": namespace, "name": name}}}
17+
}
18+
so, sj := makeObject("ScaledObject", "team", "worker"), makeObject("ScaledJob", "team", "jobs")
19+
so.Object["spec"] = map[string]any{"triggers": []any{
20+
map[string]any{"authenticationRef": map[string]any{"name": "local"}},
21+
map[string]any{"authenticationRef": map[string]any{"name": "local"}},
22+
map[string]any{"authenticationRef": map[string]any{"name": "absent"}},
23+
}}
24+
sj.Object["spec"] = map[string]any{"triggers": []any{map[string]any{"authenticationRef": map[string]any{"name": "global", "kind": "ClusterTriggerAuthentication"}}}}
25+
p := &issuerTestProvider{monitorDynamicProvider: monitorDynamicProvider{gvrs: gvrs, resources: map[schema.GroupVersionResource][]*unstructured.Unstructured{
26+
gvrs["ScaledObject"]: {so}, gvrs["ScaledJob"]: {sj},
27+
gvrs["TriggerAuthentication"]: {makeObject("TriggerAuthentication", "team", "local"), makeObject("TriggerAuthentication", "other", "local"), makeObject("TriggerAuthentication", "team", "unused")},
28+
gvrs["ClusterTriggerAuthentication"]: {makeObject("ClusterTriggerAuthentication", "", "global")},
29+
}}, listCalls: map[string]int{}}
30+
opts := DefaultBuildOptions()
31+
opts.Namespaces = []string{"team"}
32+
topo, err := NewBuilder(&mockProvider{}).WithDynamic(p).Build(opts)
33+
if err != nil {
34+
t.Fatal(err)
35+
}
36+
if len(topo.Nodes) != 4 || len(topo.Edges) != 2 {
37+
t.Fatalf("observed authentication graph: nodes=%+v edges=%+v", topo.Nodes, topo.Edges)
38+
}
39+
for _, edge := range topo.Edges {
40+
if edge.Type != EdgeConfigures || edge.Label != "authentication" {
41+
t.Fatalf("authentication presented as scaler usage: %+v", edge)
42+
}
43+
}
44+
for _, tc := range []struct{ kind, name, auth, namespace string }{{"ScaledObject", "worker", "TriggerAuthentication", "team"}, {"ScaledJob", "jobs", "ClusterTriggerAuthentication", ""}} {
45+
rel := GetRelationships(tc.kind, "team", tc.name, topo, nil, p)
46+
if rel == nil || len(rel.Dependencies) != 1 || rel.Dependencies[0].Kind != tc.auth || rel.Dependencies[0].Namespace != tc.namespace || rel.ScaleTarget != nil {
47+
t.Fatalf("authentication misclassified for %s: %+v", tc.kind, rel)
48+
}
49+
name := "local"
50+
if tc.namespace == "" {
51+
name = "global"
52+
}
53+
reverse := GetRelationships(tc.auth, tc.namespace, name, topo, nil, p)
54+
if reverse == nil || len(reverse.Dependents) != 1 || reverse.Dependents[0].Kind != tc.kind {
55+
t.Fatalf("reverse authentication: %+v", reverse)
56+
}
57+
}
58+
if p.getCalls != 0 || p.listCalls["triggerauthentications"] != 1 || p.listCalls["clustertriggerauthentications"] != 1 {
59+
t.Fatalf("per-object lookup or repeated kind list: gets=%d lists=%v", p.getCalls, p.listCalls)
60+
}
61+
if tuples := topo.ClusterScopedDynamicRBACTuples(); len(tuples) != 1 || tuples[0].Group != "keda.sh" || tuples[0].Resource != "clustertriggerauthentications" {
62+
t.Fatalf("cluster authorization tuples = %+v", tuples)
63+
}
64+
topo.StripClusterScopedDynamicExcept(nil)
65+
if len(topo.Nodes) != 3 || len(topo.Edges) != 1 {
66+
t.Fatalf("denied cluster authentication retained: %+v", topo)
67+
}
68+
}

‎pkg/topology/observed_dependencies.go‎

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import (
1212
type declaredDependency struct {
1313
Source, Target resourceid.Ref
1414
Label string
15+
// Configuration emits target → source, matching EdgeConfigures semantics.
16+
// Other declarations emit source → target as EdgeUses dependencies.
17+
Configuration bool
1518
}
1619

1720
func addObservedDependencyEdges(nodes []Node, edges []Edge, refs []declaredDependency, provider DynamicProvider, opts BuildOptions) ([]Node, []Edge, []string) {
@@ -45,10 +48,10 @@ func addObservedDependencyEdges(nodes []Node, edges []Edge, refs []declaredDepen
4548
}
4649
return kinds[i].Kind < kinds[j].Kind
4750
})
48-
seenEdges := map[[2]string]bool{}
51+
seenEdges := map[[3]string]bool{}
4952
for _, edge := range edges {
50-
if edge.Type == EdgeUses {
51-
seenEdges[[2]string{edge.Source, edge.Target}] = true
53+
if edge.Type == EdgeUses || edge.Type == EdgeConfigures {
54+
seenEdges[[3]string{edge.Source, edge.Target, string(edge.Type)}] = true
5255
}
5356
}
5457
var warnings []string
@@ -95,11 +98,20 @@ func addObservedDependencyEdges(nodes []Node, edges []Edge, refs []declaredDepen
9598
nodes = append(nodes, Node{ID: target, Kind: NodeKind(kind.Kind), Name: obj.GetName(), Status: extractGenericStatus(obj), Data: data})
9699
byResource[ref.Target.Key()] = target
97100
}
98-
pair := [2]string{source, target}
101+
edgeType := EdgeUses
102+
if ref.Configuration {
103+
source, target = target, source
104+
edgeType = EdgeConfigures
105+
}
106+
pair := [3]string{source, target, string(edgeType)}
99107
if seenEdges[pair] {
100108
continue
101109
}
102-
edges = append(edges, Edge{ID: source + "-to-" + target, Source: source, Target: target, Type: EdgeUses, Label: ref.Label})
110+
id := source + "-to-" + target
111+
if edgeType == EdgeConfigures {
112+
id += "-configures"
113+
}
114+
edges = append(edges, Edge{ID: id, Source: source, Target: target, Type: edgeType, Label: ref.Label})
103115
seenEdges[pair] = true
104116
}
105117
}

‎pkg/topology/relationships.go‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,9 @@ func GetRelationshipsWithObject(kind, namespace, name string, obj any, topo *Top
394394
continue
395395
}
396396
rel.Consumers = append(rel.Consumers, *ref)
397+
if isConfigurationDependency(refForNodeID(edge.Source)) {
398+
rel.Dependents = appendResourceRef(rel.Dependents, *ref)
399+
}
397400
if reflectionEdge(edge, nodeByID) {
398401
if rel.Reflection == nil {
399402
rel.Reflection = &ReflectionRelationships{}
@@ -479,6 +482,9 @@ func GetRelationshipsWithObject(kind, namespace, name string, obj any, topo *Top
479482
}
480483
}
481484
case EdgeConfigures:
485+
if isConfigurationDependency(ref) {
486+
rel.Dependencies = appendResourceRef(rel.Dependencies, *ref)
487+
}
482488
if reflectionEdge(edge, nodeByID) {
483489
if rel.Reflection == nil {
484490
rel.Reflection = &ReflectionRelationships{}

0 commit comments

Comments
 (0)