@@ -39,6 +39,47 @@ import (
3939 "github.com/kubefleet-dev/kubefleet/pkg/utils/controller"
4040)
4141
42+ var (
43+ // ApplyOrder is the order in which resources should be applied.
44+ // Those occurring earlier in the list get applied before those occurring later in the list.
45+ // Source: https://github.com/helm/helm/blob/31e22b9866af91e1a0ea2ad381798f6c5eec7f4f/pkg/release/util/kind_sorter.go#L31.
46+ applyOrder = []string {
47+ "PriorityClass" ,
48+ "Namespace" ,
49+ "NetworkPolicy" ,
50+ "ResourceQuota" ,
51+ "LimitRange" ,
52+ "PodDisruptionBudget" ,
53+ "ServiceAccount" ,
54+ "Secret" ,
55+ "ConfigMap" ,
56+ "StorageClass" ,
57+ "PersistentVolume" ,
58+ "PersistentVolumeClaim" ,
59+ "CustomResourceDefinition" ,
60+ "ClusterRole" ,
61+ "ClusterRoleBinding" ,
62+ "Role" ,
63+ "RoleBinding" ,
64+ "Service" ,
65+ "DaemonSet" ,
66+ "Pod" ,
67+ "ReplicationController" ,
68+ "ReplicaSet" ,
69+ "Deployment" ,
70+ "HorizontalPodAutoscaler" ,
71+ "StatefulSet" ,
72+ "Job" ,
73+ "CronJob" ,
74+ "IngressClass" ,
75+ "Ingress" ,
76+ "APIService" ,
77+ "MutatingWebhookConfiguration" ,
78+ "ValidatingWebhookConfiguration" ,
79+ }
80+ applyOrderMap = buildApplyOrderMap ()
81+ )
82+
4283// selectResources selects the resources according to the placement resourceSelectors.
4384// It also generates an array of manifests obj based on the selected resources.
4485func (r * Reconciler ) selectResources (placement * fleetv1alpha1.ClusterResourcePlacement ) ([]workv1alpha1.Manifest , error ) {
@@ -123,7 +164,7 @@ func (r *Reconciler) gatherSelectedResource(placement string, selectors []fleetv
123164 }
124165 }
125166 // sort the resources in strict order so that we will get the stable list of manifest so that
126- // the generated work object doesn't change between reconcile loops
167+ // the generated work object doesn't change between reconcile loops.
127168 sortResources (resources )
128169
129170 return resources , nil
@@ -133,36 +174,54 @@ func sortResources(resources []*unstructured.Unstructured) {
133174 sort .Slice (resources , func (i , j int ) bool {
134175 obj1 := resources [i ]
135176 obj2 := resources [j ]
136- gvk1 := obj1 .GetObjectKind ().GroupVersionKind ().String ()
137- gvk2 := obj2 .GetObjectKind ().GroupVersionKind ().String ()
138- // compare group/version;kind for the rest of type of resources
139- gvkComp := strings .Compare (gvk1 , gvk2 )
140- if gvkComp == 0 {
141- // same gvk, compare namespace/name, no duplication exists
142- return strings .Compare (fmt .Sprintf ("%s/%s" , obj1 .GetNamespace (), obj1 .GetName ()),
143- fmt .Sprintf ("%s/%s" , obj2 .GetNamespace (), obj2 .GetName ())) > 0
144- }
145- // sort by the cluster scoped priority resource types first
146- if gvk1 == utils .NamespaceMetaGVK .String () || gvk2 == utils .NamespaceMetaGVK .String () {
147- return gvk1 == utils .NamespaceMetaGVK .String ()
148- }
149- if gvk1 == utils .CRDMetaGVK .String () || gvk2 == utils .CRDMetaGVK .String () {
150- return gvk1 == utils .CRDMetaGVK .String ()
151- }
152- // followed by namespaced priority resource types
153- if gvk1 == utils .ConfigMapGVK .String () || gvk2 == utils .ConfigMapGVK .String () {
154- return gvk1 == utils .ConfigMapGVK .String ()
155- }
156- if gvk1 == utils .SecretGVK .String () || gvk2 == utils .SecretGVK .String () {
157- return gvk1 == utils .SecretGVK .String ()
158- }
159- if gvk1 == utils .PersistentVolumeClaimGVK .String () || gvk2 == utils .PersistentVolumeClaimGVK .String () {
160- return gvk1 == utils .PersistentVolumeClaimGVK .String ()
161- }
162- return gvkComp < 0
177+ k1 := obj1 .GetObjectKind ().GroupVersionKind ().Kind
178+ k2 := obj2 .GetObjectKind ().GroupVersionKind ().Kind
179+
180+ first , aok := applyOrderMap [k1 ]
181+ second , bok := applyOrderMap [k2 ]
182+ switch {
183+ // if both kinds are unknown.
184+ case ! aok && ! bok :
185+ return lessByGVK (obj1 , obj2 , false )
186+ // unknown kind should be last.
187+ case ! aok :
188+ return false
189+ case ! bok :
190+ return true
191+ // same kind.
192+ case first == second :
193+ return lessByGVK (obj1 , obj2 , true )
194+ }
195+ // different known kinds, sort based on order index.
196+ return first < second
163197 })
164198}
165199
200+ func lessByGVK (obj1 , obj2 * unstructured.Unstructured , ignoreKind bool ) bool {
201+ var gvk1 , gvk2 string
202+ if ignoreKind {
203+ gvk1 = obj1 .GetObjectKind ().GroupVersionKind ().GroupVersion ().String ()
204+ gvk2 = obj2 .GetObjectKind ().GroupVersionKind ().GroupVersion ().String ()
205+ } else {
206+ gvk1 = obj1 .GetObjectKind ().GroupVersionKind ().String ()
207+ gvk2 = obj2 .GetObjectKind ().GroupVersionKind ().String ()
208+ }
209+ comp := strings .Compare (gvk1 , gvk2 )
210+ if comp == 0 {
211+ return strings .Compare (fmt .Sprintf ("%s/%s" , obj1 .GetNamespace (), obj1 .GetName ()),
212+ fmt .Sprintf ("%s/%s" , obj2 .GetNamespace (), obj2 .GetName ())) < 0
213+ }
214+ return comp < 0
215+ }
216+
217+ func buildApplyOrderMap () map [string ]int {
218+ ordering := make (map [string ]int , len (applyOrder ))
219+ for v , k := range applyOrder {
220+ ordering [k ] = v
221+ }
222+ return ordering
223+ }
224+
166225// fetchClusterScopedResources retrieves the objects based on the selector.
167226func (r * Reconciler ) fetchClusterScopedResources (selector fleetv1beta1.ClusterResourceSelector , placeName string ) ([]runtime.Object , error ) {
168227 klog .V (2 ).InfoS ("start to fetch the cluster scoped resources by the selector" , "selector" , selector )
0 commit comments