Skip to content

Commit 048cfc6

Browse files
authored
feat: use helm apply order to sort selected resources (#50)
1 parent 03e2c94 commit 048cfc6

File tree

6 files changed

+473
-84
lines changed

6 files changed

+473
-84
lines changed

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ require (
4242
)
4343

4444
require (
45-
dario.cat/mergo v1.0.0 // indirect
45+
dario.cat/mergo v1.0.1 // indirect
4646
github.com/Azure/azure-kusto-go v0.16.1 // indirect
4747
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
4848
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
@@ -104,6 +104,7 @@ require (
104104
github.com/prometheus/common v0.55.0 // indirect
105105
github.com/prometheus/procfs v0.15.1 // indirect
106106
github.com/samber/lo v1.38.1 // indirect
107+
github.com/shopspring/decimal v1.4.0 // indirect
107108
github.com/tidwall/gjson v1.18.0 // indirect
108109
github.com/tidwall/match v1.1.1 // indirect
109110
github.com/tidwall/pretty v1.2.1 // indirect

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
2-
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
1+
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
2+
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
33
github.com/Azure/azure-kusto-go v0.16.1 h1:vCBWcQghmC1qIErUUgVNWHxGhZVStu1U/hki6iBA14k=
44
github.com/Azure/azure-kusto-go v0.16.1/go.mod h1:9F2zvXH8B6eWzgI1S4k1ZXAIufnBZ1bv1cW1kB1n3D0=
55
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
@@ -219,8 +219,8 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN
219219
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
220220
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
221221
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
222-
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
223-
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
222+
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
223+
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
224224
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
225225
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
226226
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=

pkg/controllers/clusterresourceplacement/resource_selector.go

Lines changed: 87 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
4485
func (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.
167226
func (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

Comments
 (0)