@@ -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,25 @@ 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 , resourceKindsDepends := range dependsKindsGraph {
110+ depends = append (depends , resourceKinds )
111+ for _ , resourceKindDepend := range resourceKindsDepends {
112+ // if this curKind is depends by other kinds, and not in this graph,
113+ // return empty depends.
114+ if curKind == resourceKindDepend {
115+ return []string {}
116+ }
117+ }
97118 }
98- dependKinds = append (dependKinds , previousKind )
119+ // if this curKind is not depends by any other kinds, and not in this graph,
120+ // curkind will depends on all kinds in this graph.
121+ return depends
99122 }
100- return dependKinds
123+ return dependsKindsGraph [ curKind ]
101124}
102125
103126// findDependResources returns the dependent resources of the specified kind.
@@ -110,3 +133,48 @@ func findDependResources(dependKind string, rs []v1.Resource) []*v1.Resource {
110133 }
111134 return dependResources
112135}
136+
137+ // HasCycleInGraph checks if there's a cycle in the dependency graph.
138+ // Returns true if a cycle is detected, false otherwise.
139+ func HasCycleInGraph (graph map [string ][]string ) bool {
140+ // Track visited nodes and recursion stack for cycle detection
141+ visited := make (map [string ]bool )
142+ recursionStack := make (map [string ]bool )
143+
144+ // Check each node in the graph
145+ for node := range graph {
146+ if ! visited [node ] {
147+ if hasCycle (node , visited , recursionStack , graph ) {
148+ return true // Cycle detected
149+ }
150+ }
151+ }
152+
153+ return false // No cycle found
154+ }
155+
156+ // hasCycle performs DFS to detect cycles recursively
157+ func hasCycle (node string , visited , recursionStack map [string ]bool , graph map [string ][]string ) bool {
158+ if recursionStack [node ] {
159+ return true // Cycle detected
160+ }
161+
162+ if visited [node ] {
163+ return false // Already visited and no cycle found
164+ }
165+
166+ // Mark as visited and add to recursion stack
167+ visited [node ] = true
168+ recursionStack [node ] = true
169+
170+ // Recursively check dependencies
171+ for _ , dep := range graph [node ] {
172+ if hasCycle (dep , visited , recursionStack , graph ) {
173+ return true
174+ }
175+ }
176+
177+ // Remove from recursion stack after processing
178+ recursionStack [node ] = false
179+ return false
180+ }
0 commit comments