@@ -10,39 +10,53 @@ import (
1010
1111type resource v1.Resource
1212
13- // DefaultOrderedKinds provides the default order of Kubernetes resource kinds.
14- var DefaultOrderedKinds = []string {
15- "Namespace" ,
16- "ResourceQuota" ,
17- "StorageClass" ,
18- "CustomResourceDefinition" ,
19- "ServiceAccount" ,
20- "PodSecurityPolicy" ,
21- "Role" ,
22- "ClusterRole" ,
23- "RoleBinding" ,
24- "ClusterRoleBinding" ,
25- "ConfigMap" ,
26- "Secret" ,
27- "Endpoints" ,
28- "Service" ,
29- "LimitRange" ,
30- "PriorityClass" ,
31- "PersistentVolume" ,
32- "PersistentVolumeClaim" ,
33- "Deployment" ,
34- "StatefulSet" ,
35- "CronJob" ,
36- "PodDisruptionBudget" ,
37- "MutatingWebhookConfiguration" ,
38- "ValidatingWebhookConfiguration" ,
13+ // DefaultDependsKindsGraph defines the default dependency relationships between
14+ // Kubernetes resource kinds. This graph maps each resource kind to the list of
15+ // resource kinds it potentially depends on (not strictly required, but commonly
16+ // associated in practice).
17+ //
18+ // Structure:
19+ // - Key: The resource kind (e.g., "Deployment")
20+ // - Value: Slice of resource kinds this resource may depend on
21+ //
22+ // Example:
23+ //
24+ // "Deployment": {"Namespace", "ServiceAccount", ...}
25+ var DefaultDependsKindsGraph = map [string ][]string {
26+ "Namespace" : {},
27+ "ResourceQuota" : {"Namespace" },
28+ "StorageClass" : {},
29+ "CustomResourceDefinition" : {},
30+ "ServiceAccount" : {"Namespace" },
31+ "PodSecurityPolicy" : {},
32+ "Role" : {"Namespace" },
33+ "ClusterRole" : {},
34+ "RoleBinding" : {"Namespace" , "ServiceAccount" , "Role" },
35+ "ClusterRoleBinding" : {"ServiceAccount" , "ClusterRole" },
36+ "ConfigMap" : {"Namespace" },
37+ "Secret" : {"Namespace" },
38+ "Endpoints" : {"Namespace" },
39+ "Service" : {"Namespace" , "Endpoints" },
40+ "LimitRange" : {"Namespace" , "StorageClass" },
41+ "PriorityClass" : {},
42+ "PersistentVolume" : {"StorageClass" },
43+ "PersistentVolumeClaim" : {"Namespace" , "ResourceQuota" , "StorageClass" , "PersistentVolume" },
44+ "Deployment" : {"Namespace" , "ResourceQuota" , "PersistentVolumeClaim" , "ServiceAccount" , "PodSecurityPolicy" , "ConfigMap" , "Secret" , "Service" , "LimitRange" },
45+ "StatefulSet" : {"Namespace" , "ResourceQuota" , "PersistentVolumeClaim" , "ServiceAccount" , "PodSecurityPolicy" , "ConfigMap" , "Secret" , "Service" , "LimitRange" },
46+ "CronJob" : {"Namespace" , "ResourceQuota" , "PersistentVolumeClaim" , "ServiceAccount" , "PodSecurityPolicy" , "ConfigMap" , "Secret" , "Service" , "LimitRange" },
47+ "PodDisruptionBudget" : {"Namespace" , "Deployment" , "StatefulSet" , "CronJob" },
48+ "MutatingWebhookConfiguration" : {"Namespace" , "ServiceAccount" , "RoleBinding" , "ClusterRoleBinding" , "ConfigMap" , "Secret" , "Service" },
49+ "ValidatingWebhookConfiguration" : {"Namespace" , "ServiceAccount" , "RoleBinding" , "ClusterRoleBinding" , "ConfigMap" , "Secret" , "Service" },
3950}
4051
4152// OrderedResources returns a list of Kusion Resources with the injected `dependsOn`
4253// in a specified order.
43- func OrderedResources (ctx context.Context , resources v1.Resources , orderedKinds []string ) (v1.Resources , error ) {
44- if len (orderedKinds ) == 0 {
45- orderedKinds = DefaultOrderedKinds
54+ func OrderedResources (ctx context.Context , resources v1.Resources , dependsKindsGraph map [string ][]string ) (v1.Resources , error ) {
55+ if dependsKindsGraph == nil {
56+ dependsKindsGraph = DefaultDependsKindsGraph
57+ }
58+ if HasCycleInGraph (dependsKindsGraph ) {
59+ return nil , errors .New ("find cycles in giving depends kinds grach" )
4660 }
4761
4862 if len (resources ) == 0 {
@@ -57,7 +71,7 @@ func OrderedResources(ctx context.Context, resources v1.Resources, orderedKinds
5771
5872 // Inject dependsOn of the resource.
5973 r := (* resource )(& resources [i ])
60- r .injectDependsOn (orderedKinds , resources )
74+ r .injectDependsOn (dependsKindsGraph , resources )
6175 resources [i ] = v1 .Resource (* r )
6276 }
6377
@@ -72,8 +86,8 @@ func (r resource) kubernetesKind() string {
7286}
7387
7488// injectDependsOn injects all dependsOn relationships for the given resource and dependent kinds.
75- func (r * resource ) injectDependsOn (orderedKinds []string , rs []v1.Resource ) {
76- kinds := r .findDependKinds (orderedKinds )
89+ func (r * resource ) injectDependsOn (dependsKindsGraph map [ string ] []string , rs []v1.Resource ) {
90+ kinds := r .findDependKinds (dependsKindsGraph )
7791 for _ , kind := range kinds {
7892 drs := findDependResources (kind , rs )
7993 r .appendDependsOn (drs )
@@ -88,16 +102,16 @@ func (r *resource) appendDependsOn(dependResources []*v1.Resource) {
88102}
89103
90104// findDependKinds returns the dependent resource kinds for the specified kind.
91- func (r * resource ) findDependKinds (orderedKinds []string ) []string {
105+ func (r * resource ) findDependKinds (dependsKindsGraph map [ string ] []string ) []string {
92106 curKind := r .kubernetesKind ()
93- dependKinds := make ([] string , 0 )
94- for _ , previousKind := range orderedKinds {
95- if curKind == previousKind {
96- break
107+ if _ , exists := dependsKindsGraph [ curKind ]; ! exists {
108+ depends := [] string {}
109+ for resourceKinds := range dependsKindsGraph {
110+ depends = append ( depends , resourceKinds )
97111 }
98- dependKinds = append ( dependKinds , previousKind )
112+ return depends
99113 }
100- return dependKinds
114+ return dependsKindsGraph [ curKind ]
101115}
102116
103117// findDependResources returns the dependent resources of the specified kind.
@@ -110,3 +124,48 @@ func findDependResources(dependKind string, rs []v1.Resource) []*v1.Resource {
110124 }
111125 return dependResources
112126}
127+
128+ // HasCycleInGraph checks if there's a cycle in the dependency graph.
129+ // Returns true if a cycle is detected, false otherwise.
130+ func HasCycleInGraph (graph map [string ][]string ) bool {
131+ // Track visited nodes and recursion stack for cycle detection
132+ visited := make (map [string ]bool )
133+ recursionStack := make (map [string ]bool )
134+
135+ // Check each node in the graph
136+ for node := range graph {
137+ if ! visited [node ] {
138+ if hasCycle (node , visited , recursionStack , graph ) {
139+ return true // Cycle detected
140+ }
141+ }
142+ }
143+
144+ return false // No cycle found
145+ }
146+
147+ // hasCycle performs DFS to detect cycles recursively
148+ func hasCycle (node string , visited , recursionStack map [string ]bool , graph map [string ][]string ) bool {
149+ if recursionStack [node ] {
150+ return true // Cycle detected
151+ }
152+
153+ if visited [node ] {
154+ return false // Already visited and no cycle found
155+ }
156+
157+ // Mark as visited and add to recursion stack
158+ visited [node ] = true
159+ recursionStack [node ] = true
160+
161+ // Recursively check dependencies
162+ for _ , dep := range graph [node ] {
163+ if hasCycle (dep , visited , recursionStack , graph ) {
164+ return true
165+ }
166+ }
167+
168+ // Remove from recursion stack after processing
169+ recursionStack [node ] = false
170+ return false
171+ }
0 commit comments