From c8d3349c7b23761e4aea94f25a45ce75e09f7d8e Mon Sep 17 00:00:00 2001 From: Chris Randles Date: Fri, 3 Nov 2023 14:00:00 -0700 Subject: [PATCH] add support for weighted manifests Signed-off-by: Chris Randles --- pkg/releaseutil/kind_sorter.go | 30 +++++++++ pkg/releaseutil/kind_sorter_test.go | 98 +++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) diff --git a/pkg/releaseutil/kind_sorter.go b/pkg/releaseutil/kind_sorter.go index b5d75b88b..5371341dc 100644 --- a/pkg/releaseutil/kind_sorter.go +++ b/pkg/releaseutil/kind_sorter.go @@ -18,6 +18,7 @@ package releaseutil import ( "sort" + "strconv" "helm.sh/helm/v3/pkg/release" ) @@ -109,11 +110,40 @@ var UninstallOrder KindSortOrder = []string{ "PriorityClass", } +// OrderWeightAnnotation is the label name for configuring the sorted weight of a manifest +const OrderWeightAnnotation = "helm.sh/order-weight" + // sort manifests by kind. // // Results are sorted by 'ordering', keeping order of items with equal kind/priority +// +// If a manifest defines a `helm.sh/order-weight` annotation, its value is used in addition to the kind's priority value. func sortManifestsByKind(manifests []Manifest, ordering KindSortOrder) []Manifest { sort.SliceStable(manifests, func(i, j int) bool { + var err error + + iPriority := 0 + if metadata := manifests[i].Head.Metadata; metadata != nil { + iPriority, err = strconv.Atoi(metadata.Annotations[OrderWeightAnnotation]) + if err != nil { + iPriority = 0 + } + } + + jPriority := 0 + if metadata := manifests[j].Head.Metadata; metadata != nil { + jPriority, err = strconv.Atoi(metadata.Annotations[OrderWeightAnnotation]) + if err != nil { + jPriority = 0 + } + } + + if iPriority < jPriority { + return true + } else if iPriority > jPriority { + return false + } + // if the resolved priorities match, default to the legacy lessByKind logic for final sorting return lessByKind(manifests[i], manifests[j], manifests[i].Head.Kind, manifests[j].Head.Kind, ordering) }) diff --git a/pkg/releaseutil/kind_sorter_test.go b/pkg/releaseutil/kind_sorter_test.go index 9e24c4399..6598d7b78 100644 --- a/pkg/releaseutil/kind_sorter_test.go +++ b/pkg/releaseutil/kind_sorter_test.go @@ -18,9 +18,11 @@ package releaseutil import ( "bytes" + "fmt" "testing" "helm.sh/helm/v3/pkg/release" + "sigs.k8s.io/yaml" ) func TestKindSorter(t *testing.T) { @@ -287,6 +289,102 @@ func TestKindSorterNamespaceAgainstUnknown(t *testing.T) { } } +func TestKindSorterOrderWeight(t *testing.T) { + + for _, test := range []struct { + description string + input string + expected []string + }{ + { + description: "no weights", + input: ` +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: a +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: b +`, + expected: []string{"rbac.authorization.k8s.io/v1:ClusterRole:a", "rbac.authorization.k8s.io/v1:ClusterRole:b"}, + }, + { + description: "one weighted object", + input: ` +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: a +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: b + annotations: + "helm.sh/order-weight": 1 +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: c +`, + expected: []string{"rbac.authorization.k8s.io/v1:ClusterRole:a", "rbac.authorization.k8s.io/v1:ClusterRole:c", "rbac.authorization.k8s.io/v1:ClusterRole:b"}, + }, + { + description: "negative weight", + input: ` +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: a +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: b + annotations: + "helm.sh/order-weight": -1 +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: c +`, + expected: []string{"rbac.authorization.k8s.io/v1:ClusterRole:b", "rbac.authorization.k8s.io/v1:ClusterRole:a", "rbac.authorization.k8s.io/v1:ClusterRole:c"}, + }, + } { + t.Run(test.description, func(t *testing.T) { + objects := SplitManifests(test.input) + manifests := []Manifest{} + for _, m := range objects { + var sh SimpleHead + if err := yaml.Unmarshal([]byte(m), &sh); err != nil { + t.Log(err) + continue + } + name := sh.Metadata.Name + content := m + manifests = append(manifests, Manifest{Name: name, Content: content, Head: &sh}) + } + manifests = sortManifestsByKind(manifests, InstallOrder) + if got := len(manifests); got != len(test.expected) { + t.Errorf("Expected %d, got %d", len(test.expected), got) + } else { + for i, manifest := range manifests { + got := fmt.Sprintf("%s:%s:%s", manifest.Head.Version, manifest.Head.Kind, manifest.Name) + if test.expected[i] != got { + t.Errorf("Expected %s, got %s", test.expected[i], got) + } + } + } + + }) + } +} + // test hook sorting with a small subset of kinds, since it uses the same algorithm as sortManifestsByKind func TestKindSorterForHooks(t *testing.T) { hooks := []*release.Hook{