Repository: plexsystems/konstraint
Pull request #116 (2021-01-18 09:40:41)

bugfix: duplication of kinds
Fix https://github.com/plexsystems/konstraint/issues/114

jpreese: Thanks for the PR @dulltz! Left a comment

Patch:
diff --git a/docs/cli/konstraint.md b/docs/cli/konstraint.md
index b431244..9d57ab1 100644
--- a/docs/cli/konstraint.md
+++ b/docs/cli/konstraint.md
@@ -17,4 +17,4 @@ A tool to create and manage Gatekeeper CRDs from Rego
 * [konstraint create](konstraint_create.md)	 - Create Gatekeeper constraints from Rego policies
 * [konstraint doc](konstraint_doc.md)	 - Generate documentation from Rego policies
 
-###### Auto generated by spf13/cobra on 14-Dec-2020
+###### Auto generated by spf13/cobra on 19-Jan-2021
diff --git a/docs/cli/konstraint_create.md b/docs/cli/konstraint_create.md
index ff6d788..61aa7e7 100644
--- a/docs/cli/konstraint_create.md
+++ b/docs/cli/konstraint_create.md
@@ -32,4 +32,4 @@ Create constraints with the Gatekeeper enforcement action set to dryrun
 
 * [konstraint](konstraint.md)	 - Konstraint
 
-###### Auto generated by spf13/cobra on 14-Dec-2020
+###### Auto generated by spf13/cobra on 19-Jan-2021
diff --git a/docs/cli/konstraint_doc.md b/docs/cli/konstraint_doc.md
index 525b702..ff01d3d 100644
--- a/docs/cli/konstraint_doc.md
+++ b/docs/cli/konstraint_doc.md
@@ -31,4 +31,4 @@ Set the URL where the policies are hosted at
 
 * [konstraint](konstraint.md)	 - Konstraint
 
-###### Auto generated by spf13/cobra on 14-Dec-2020
+###### Auto generated by spf13/cobra on 19-Jan-2021
diff --git a/internal/commands/create.go b/internal/commands/create.go
index d8cc06f..3a9a1af 100644
--- a/internal/commands/create.go
+++ b/internal/commands/create.go
@@ -5,6 +5,7 @@ import (
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"strings"
 
 	"github.com/plexsystems/konstraint/internal/rego"
 
@@ -218,10 +219,10 @@ func getConstraint(violation rego.Rego) (unstructured.Unstructured, error) {
 }
 
 func setKindMatcher(constraint *unstructured.Unstructured, kindMatchers []rego.KindMatcher) error {
-	var kinds []interface{}
-	var apiGroups []interface{}
+	var kinds []string
+	var apiGroups []string
 	for _, kindMatcher := range kindMatchers {
-		kinds = append(kinds, kindMatcher.Kind)
+		kinds = appendIfNotExists(kinds, kindMatcher.Kind)
 	}
 
 	for _, kindMatcher := range kindMatchers {
@@ -229,21 +230,12 @@ func setKindMatcher(constraint *unstructured.Unstructured, kindMatchers []rego.K
 		if kindMatcher.APIGroup == "core" {
 			apiGroup = ""
 		}
-
-		var exists bool
-		for _, addedGroup := range apiGroups {
-			if apiGroup == addedGroup {
-				exists = true
-			}
-		}
-		if !exists {
-			apiGroups = append(apiGroups, apiGroup)
-		}
+		apiGroups = appendIfNotExists(apiGroups, apiGroup)
 	}
 
 	constraintMatcher := map[string]interface{}{
-		"apiGroups": apiGroups,
-		"kinds":     kinds,
+		"apiGroups": toInterfaceSlice(apiGroups),
+		"kinds":     toInterfaceSlice(kinds),
 	}
 
 	if err := unstructured.SetNestedSlice(constraint.Object, []interface{}{constraintMatcher}, "spec", "match", "kinds"); err != nil {
@@ -252,6 +244,23 @@ func setKindMatcher(constraint *unstructured.Unstructured, kindMatchers []rego.K
 	return nil
 }
 
+func appendIfNotExists(currentItems []string, newItem string) []string {
+	for _, item := range currentItems {
+		if strings.EqualFold(newItem, item) {
+			return currentItems
+		}
+	}
+	return append(currentItems, newItem)
+}
+
+func toInterfaceSlice(input []string) []interface{} {
+	res := make([]interface{}, len(input))
+	for i, v := range input {
+		res[i] = v
+	}
+	return res
+}
+
 func setMatchLabelsMatcher(constraint *unstructured.Unstructured, matcher rego.MatchLabelsMatcher) error {
 	if err := unstructured.SetNestedStringMap(constraint.Object, matcher, "spec", "match", "labelSelector", "matchLabels"); err != nil {
 		return fmt.Errorf("set constraint labelSelector.matchLabels matchers: %w", err)


Test patch:
diff --git a/internal/commands/create_test.go b/internal/commands/create_test.go
new file mode 100644
index 0000000..a41ddc7
--- /dev/null
+++ b/internal/commands/create_test.go
@@ -0,0 +1,65 @@
+package commands
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestAppendIfNotExists(t *testing.T) {
+	type args struct {
+		currentItems []string
+		newItem      string
+	}
+	tests := []struct {
+		name string
+		args args
+		want []string
+	}{
+		{
+			"append",
+			args{currentItems: []string{"a"}, newItem: "b"},
+			[]string{"a", "b"},
+		},
+		{
+			"skip",
+			args{currentItems: []string{"a"}, newItem: "a"},
+			[]string{"a"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := appendIfNotExists(tt.args.currentItems, tt.args.newItem); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("appendIfNotExists() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestToInterfaceSlice(t *testing.T) {
+	type args struct {
+		input []string
+	}
+	tests := []struct {
+		name string
+		args args
+		want []interface{}
+	}{
+		{
+			"empty",
+			args{[]string{}},
+			[]interface{}{},
+		},
+		{
+			"valid",
+			args{[]string{"a", "b"}},
+			[]interface{}{"a", "b"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := toInterfaceSlice(tt.args.input); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("toInterfaceSlice() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}