From 9d29f5778927db4655f5eaa31eb6c736ea81dee3 Mon Sep 17 00:00:00 2001 From: Sam Dowell Date: Wed, 1 Dec 2021 20:48:15 +0000 Subject: [PATCH] feat: Add Sort method to kyaml fn Results Issues: GoogleContainerTools/kpt#2195 --- kyaml/fn/framework/result.go | 59 +++++++ kyaml/fn/framework/result_test.go | 274 ++++++++++++++++++++++++++++++ 2 files changed, 333 insertions(+) create mode 100644 kyaml/fn/framework/result_test.go diff --git a/kyaml/fn/framework/result.go b/kyaml/fn/framework/result.go index 9d9065ea60..e75eb380ae 100644 --- a/kyaml/fn/framework/result.go +++ b/kyaml/fn/framework/result.go @@ -5,6 +5,7 @@ package framework import ( "fmt" + "sort" "strings" "sigs.k8s.io/kustomize/kyaml/yaml" @@ -126,3 +127,61 @@ func (e Results) ExitCode() int { } return 0 } + +// Sort performs an in place stable sort of Results +func (e Results) Sort() { + sort.SliceStable(e, func(i, j int) bool { + if fileLess(e, i, j) != 0 { + return fileLess(e, i, j) < 0 + } + if severityLess(e, i, j) != 0 { + return severityLess(e, i, j) < 0 + } + return resultToString(*e[i]) < resultToString(*e[j]) + }) +} + +func severityLess(items Results, i, j int) int { + severityToNumber := map[Severity]int{ + Error: 0, + Warning: 1, + Info: 2, + } + + severityLevelI, found := severityToNumber[items[i].Severity] + if !found { + severityLevelI = 3 + } + severityLevelJ, found := severityToNumber[items[j].Severity] + if !found { + severityLevelJ = 3 + } + return severityLevelI - severityLevelJ +} + +func fileLess(items Results, i, j int) int { + var fileI, fileJ File + if items[i].File == nil { + fileI = File{} + } else { + fileI = *items[i].File + } + if items[j].File == nil { + fileJ = File{} + } else { + fileJ = *items[j].File + } + if fileI.Path != fileJ.Path { + if fileI.Path < fileJ.Path { + return -1 + } else { + return 1 + } + } + return fileI.Index - fileJ.Index +} + +func resultToString(item Result) string { + return fmt.Sprintf("resource-ref:%s,field:%s,message:%s", + item.ResourceRef, item.Field, item.Message) +} diff --git a/kyaml/fn/framework/result_test.go b/kyaml/fn/framework/result_test.go new file mode 100644 index 0000000000..461b38180b --- /dev/null +++ b/kyaml/fn/framework/result_test.go @@ -0,0 +1,274 @@ +// Copyright 2021 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package framework_test + +import ( + "reflect" + "testing" + + "sigs.k8s.io/kustomize/kyaml/fn/framework" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +func TestResults_Sort(t *testing.T) { + testcases := []struct { + name string + input framework.Results + output framework.Results + }{ + { + name: "sort based on severity", + input: framework.Results{ + { + Message: "Error message 1", + Severity: framework.Info, + }, + { + Message: "Error message 2", + Severity: framework.Error, + }, + }, + output: framework.Results{ + { + Message: "Error message 2", + Severity: framework.Error, + }, + { + Message: "Error message 1", + Severity: framework.Info, + }, + }, + }, + { + name: "sort based on file", + input: framework.Results{ + { + Message: "Error message", + Severity: framework.Error, + File: &framework.File{ + Path: "resource.yaml", + Index: 1, + }, + }, + { + Message: "Error message", + Severity: framework.Info, + File: &framework.File{ + Path: "resource.yaml", + Index: 0, + }, + }, + { + Message: "Error message", + Severity: framework.Info, + File: &framework.File{ + Path: "other-resource.yaml", + Index: 0, + }, + }, + { + Message: "Error message", + Severity: framework.Warning, + File: &framework.File{ + Path: "resource.yaml", + Index: 2, + }, + }, + { + Message: "Error message", + Severity: framework.Warning, + }, + }, + output: framework.Results{ + { + Message: "Error message", + Severity: framework.Warning, + }, + { + Message: "Error message", + Severity: framework.Info, + File: &framework.File{ + Path: "other-resource.yaml", + Index: 0, + }, + }, + { + Message: "Error message", + Severity: framework.Info, + File: &framework.File{ + Path: "resource.yaml", + Index: 0, + }, + }, + { + Message: "Error message", + Severity: framework.Error, + File: &framework.File{ + Path: "resource.yaml", + Index: 1, + }, + }, + { + Message: "Error message", + Severity: framework.Warning, + File: &framework.File{ + Path: "resource.yaml", + Index: 2, + }, + }, + }, + }, + + { + name: "sort based on other fields", + input: framework.Results{ + { + Message: "Error message", + Severity: framework.Error, + ResourceRef: &yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + NameMeta: yaml.NameMeta{ + Namespace: "foo-ns", + Name: "bar", + }, + }, + Field: &framework.Field{ + Path: "spec", + }, + }, + { + Message: "Error message", + Severity: framework.Error, + ResourceRef: &yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + NameMeta: yaml.NameMeta{ + Namespace: "foo-ns", + Name: "bar", + }, + }, + Field: &framework.Field{ + Path: "metadata.name", + }, + }, + { + Message: "Another error message", + Severity: framework.Error, + ResourceRef: &yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + NameMeta: yaml.NameMeta{ + Namespace: "foo-ns", + Name: "bar", + }, + }, + Field: &framework.Field{ + Path: "metadata.name", + }, + }, + { + Message: "Another error message", + Severity: framework.Error, + ResourceRef: &yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + NameMeta: yaml.NameMeta{ + Namespace: "foo-ns", + Name: "bar", + }, + }, + Field: &framework.Field{ + Path: "metadata.name", + }, + }, + }, + output: framework.Results{ + { + Message: "Another error message", + Severity: framework.Error, + ResourceRef: &yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + NameMeta: yaml.NameMeta{ + Namespace: "foo-ns", + Name: "bar", + }, + }, + Field: &framework.Field{ + Path: "metadata.name", + }, + }, + { + Message: "Another error message", + Severity: framework.Error, + ResourceRef: &yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + NameMeta: yaml.NameMeta{ + Namespace: "foo-ns", + Name: "bar", + }, + }, + Field: &framework.Field{ + Path: "metadata.name", + }, + }, + { + Message: "Error message", + Severity: framework.Error, + ResourceRef: &yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + NameMeta: yaml.NameMeta{ + Namespace: "foo-ns", + Name: "bar", + }, + }, + Field: &framework.Field{ + Path: "metadata.name", + }, + }, + { + Message: "Error message", + Severity: framework.Error, + ResourceRef: &yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + NameMeta: yaml.NameMeta{ + Namespace: "foo-ns", + Name: "bar", + }, + }, + Field: &framework.Field{ + Path: "spec", + }, + }, + }, + }, + } + + for _, tc := range testcases { + tc.input.Sort() + if !reflect.DeepEqual(tc.input, tc.output) { + t.Errorf("in testcase %q, expect: %#v, but got: %#v", tc.name, tc.output, tc.input) + } + } +}