Skip to content

Commit 3cbf341

Browse files
authored
Merge pull request #9251 from kokes/sort_for_create
🌱 refactor SortForCreate to use sort.Slice
2 parents 8e3ef78 + 39d93e5 commit 3cbf341

File tree

2 files changed

+87
-41
lines changed

2 files changed

+87
-41
lines changed

util/resource/resource.go

Lines changed: 32 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,61 +17,54 @@ limitations under the License.
1717
// Package resource implements resource utilites.
1818
package resource
1919

20-
import "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
20+
import (
21+
"sort"
2122

22-
var defaultCreatePriorities = []string{
23+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
24+
)
25+
26+
var priorityMap = map[string]int{
2327
// Namespaces go first because all namespaced resources depend on them.
24-
"Namespace",
28+
"Namespace": -100,
2529
// Custom Resource Definitions come before Custom Resource so that they can be
2630
// restored with their corresponding CRD.
27-
"CustomResourceDefinition",
31+
"CustomResourceDefinition": -99,
2832
// Storage Classes are needed to create PVs and PVCs correctly.
29-
"StorageClass",
33+
"StorageClass": -98,
3034
// PVs go before PVCs because PVCs depend on them.
31-
"PersistentVolume",
35+
"PersistentVolume": -97,
3236
// PVCs go before pods or controllers so they can be mounted as volumes.
33-
"PersistentVolumeClaim",
37+
"PersistentVolumeClaim": -96,
3438
// Secrets and ConfigMaps go before pods or controllers so they can be mounted as volumes.
35-
"Secret",
36-
"ConfigMap",
39+
"Secret": -95,
40+
"ConfigMap": -94,
3741
// Service accounts go before pods or controllers so pods can use them.
38-
"ServiceAccount",
42+
"ServiceAccount": -93,
3943
// Limit ranges go before pods or controllers so pods can use them.
40-
"LimitRange",
44+
"LimitRange": -92,
4145
// Pods go before ReplicaSets
42-
"Pods",
46+
"Pod": -91,
4347
// ReplicaSets go before Deployments
44-
"ReplicaSet",
45-
// Endpoints go before Services
46-
"Endpoints",
48+
"ReplicaSet": -90,
49+
// Endpoints go before Services (and everything else)
50+
"Endpoint": -89,
51+
}
52+
53+
// priorityLess returns true if o1 should be created before o2.
54+
// To be used in sort.{Slice,SliceStable} to sort manifests in place.
55+
func priorityLess(o1, o2 unstructured.Unstructured) bool {
56+
p1 := priorityMap[o1.GetKind()]
57+
p2 := priorityMap[o2.GetKind()]
58+
return p1 < p2
4759
}
4860

4961
// SortForCreate sorts objects by creation priority.
5062
func SortForCreate(resources []unstructured.Unstructured) []unstructured.Unstructured {
51-
var ret []unstructured.Unstructured
52-
53-
// First get resources by priority
54-
for _, p := range defaultCreatePriorities {
55-
for _, o := range resources {
56-
if o.GetKind() == p {
57-
ret = append(ret, o)
58-
}
59-
}
60-
}
61-
62-
// Then get all the other resources
63-
for _, o := range resources {
64-
found := false
65-
for _, r := range ret {
66-
if o.GroupVersionKind() == r.GroupVersionKind() && o.GetNamespace() == r.GetNamespace() && o.GetName() == r.GetName() {
67-
found = true
68-
break
69-
}
70-
}
71-
if !found {
72-
ret = append(ret, o)
73-
}
74-
}
63+
ret := make([]unstructured.Unstructured, len(resources))
64+
copy(ret, resources)
65+
sort.SliceStable(ret, func(i, j int) bool {
66+
return priorityLess(ret[i], ret[j])
67+
})
7568

7669
return ret
7770
}

util/resource/resource_test.go

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,25 @@ limitations under the License.
1717
package resource
1818

1919
import (
20+
"math/rand"
2021
"testing"
2122

2223
. "github.com/onsi/gomega"
2324
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2425
)
2526

27+
var (
28+
kindsWithPriority = []string{
29+
"Namespace", "CustomResourceDefinition", "StorageClass", "PersistentVolume",
30+
"PersistentVolumeClaim", "Secret", "ConfigMap", "ServiceAccount", "LimitRange",
31+
"Pod", "ReplicaSet", "Endpoint",
32+
}
33+
kindsOther = []string{
34+
"Deployment", "Service", "DaemonSet", "StatefulSet", "Job", "CronJob", "Ingress",
35+
"NetworkPolicy", "Role", "RoleBinding",
36+
}
37+
)
38+
2639
func TestSortForCreate(t *testing.T) {
2740
g := NewWithT(t)
2841

@@ -35,9 +48,49 @@ func TestSortForCreate(t *testing.T) {
3548
ep := unstructured.Unstructured{}
3649
ep.SetKind("Endpoint")
3750

38-
resources := []unstructured.Unstructured{ep, cm, ns}
51+
dp := unstructured.Unstructured{}
52+
dp.SetKind("Deployment")
53+
54+
ds := unstructured.Unstructured{}
55+
ds.SetKind("DaemonSet")
56+
57+
resources := []unstructured.Unstructured{ds, dp, ep, cm, ns}
3958
sorted := SortForCreate(resources)
40-
g.Expect(sorted).To(HaveLen(3))
59+
g.Expect(sorted).To(HaveLen(5))
4160
g.Expect(sorted[0].GetKind()).To(BeIdenticalTo("Namespace"))
4261
g.Expect(sorted[1].GetKind()).To(BeIdenticalTo("ConfigMap"))
62+
g.Expect(sorted[2].GetKind()).To(BeIdenticalTo("Endpoint"))
63+
// we have no way to determine the order of the last two elements
64+
g.Expect(sorted[3].GetKind()).To(BeElementOf([]string{"DaemonSet", "Deployment"}))
65+
g.Expect(sorted[4].GetKind()).To(BeElementOf([]string{"DaemonSet", "Deployment"}))
66+
}
67+
68+
func TestSortForCreateAllShuffle(t *testing.T) {
69+
g := NewWithT(t)
70+
71+
resources := make([]unstructured.Unstructured, 0, len(kindsWithPriority)+len(kindsOther))
72+
for _, kind := range append(kindsWithPriority, kindsOther...) {
73+
resource := unstructured.Unstructured{}
74+
resource.SetKind(kind)
75+
resources = append(resources, resource)
76+
}
77+
for j := 0; j < 100; j++ {
78+
// determinically shuffle resources
79+
rnd := rand.New(rand.NewSource(int64(j))) //nolint:gosec
80+
rnd.Shuffle(len(resources), func(i, j int) {
81+
resources[i], resources[j] = resources[j], resources[i]
82+
})
83+
84+
sorted := SortForCreate(resources)
85+
g.Expect(sorted).To(HaveLen(len(kindsWithPriority) + len(kindsOther)))
86+
// first check that the first len(kindsWithPriority) elements are from
87+
// the list of kinds with priority (and in order)
88+
for i, res := range sorted[:len(kindsWithPriority)] {
89+
g.Expect(res.GetKind()).To(BeIdenticalTo(kindsWithPriority[i]))
90+
}
91+
// while the rest of resources can be any of the other kinds
92+
for _, res := range sorted[len(kindsWithPriority):] {
93+
g.Expect(kindsOther).To(ContainElement(res.GetKind()))
94+
}
95+
}
4396
}

0 commit comments

Comments
 (0)