Repository: plexsystems/konstraint (go)

Issue:
Merge duplicate matcher annotations
I want duplicate instances of `@kinds` and `@matchLabels` annotations to merge into resulting matchers instead of overriding last one – so they match behaviour of `@matchExpression`, `@namespaces` and `@excludedNamespaces`

For example, I want these annotations:
```rego
# @kinds apps/Deployment apps/ReplicaSet apps/StatefulSet apps/DaemonSet
# @kinds core/Pod
# @matchlabels foo=bar
# @matchlabels env=prod
```

to produce following spec.match:
```yaml
kinds:
- apiGroups:
  - ""
  kinds:
  - Pod
- apiGroups:
  - apps
  kinds:
  - Deployment
  - ReplicaSet
  - StatefulSet
  - DaemonSet
labelSelector:
  matchLabels:
    env: prod
    foo: bar
```

instead of this:
```yaml
kinds:
- apiGroups:
  - ""
  kinds:
  - Pod
labelSelector:
  matchLabels:
    env: prod
```

Pull request description:
Merge @matchlabels and @kinds annotations
Fixes #268 

Patch:
diff --git a/docs/constraint_creation.md b/docs/constraint_creation.md
index 02aced1..d8388ae 100644
--- a/docs/constraint_creation.md
+++ b/docs/constraint_creation.md
@@ -103,6 +103,14 @@ The comment block is also what is used when generating documentation via the `do
     # @excludedNamespaces kube-system gatekeeper-system
     ```
 
+Multiple instances of annotations for matching of the same type are merged:
+```
+# @kinds apps/Deployment
+# @kinds core/Pod
+# is equivalent to
+# @kinds apps/Deployment core/Pod
+```
+
 ### Skipping generation of the Constraint resource
 
 In some scenarios, you may wish for Konstraint to skip the generation of the `Constraint` resource for a policy and manage that externally. To do so, add the `@skip-constraint` tag in the header comment block.
diff --git a/internal/rego/matchers.go b/internal/rego/matchers.go
index e35eb2b..7fbe0b1 100644
--- a/internal/rego/matchers.go
+++ b/internal/rego/matchers.go
@@ -61,7 +61,7 @@ func (r Rego) Matchers() (Matchers, error) {
 			if err != nil {
 				return Matchers{}, fmt.Errorf("get kind matchers: %w", err)
 			}
-			matchers.KindMatchers = m
+			matchers.KindMatchers = append(matchers.KindMatchers, m...)
 		}
 
 		if commentStartsWith(comment, "@matchlabels") {
@@ -69,7 +69,13 @@ func (r Rego) Matchers() (Matchers, error) {
 			if err != nil {
 				return Matchers{}, fmt.Errorf("get match labels matcher: %w", err)
 			}
-			matchers.MatchLabelsMatcher = m
+			if matchers.MatchLabelsMatcher == nil {
+				matchers.MatchLabelsMatcher = m
+				m = nil
+			}
+			for k, v := range m {
+				matchers.MatchLabelsMatcher[k] = v
+			}
 		}
 
 		if commentStartsWith(comment, "@matchExpression") {


Test patch:
diff --git a/internal/rego/matchers_test.go b/internal/rego/matchers_test.go
index 432bdfc..d4dea2d 100644
--- a/internal/rego/matchers_test.go
+++ b/internal/rego/matchers_test.go
@@ -8,6 +8,7 @@ import (
 func TestGetKindMatchers(t *testing.T) {
 	comments := []string{
 		"@kinds core/Pod apps/Deployment",
+		"@kinds apps/StatefulSet",
 	}
 	rego := Rego{
 		headerComments: comments,
@@ -16,6 +17,7 @@ func TestGetKindMatchers(t *testing.T) {
 	expected := KindMatchers{
 		{APIGroup: "core", Kind: "Pod"},
 		{APIGroup: "apps", Kind: "Deployment"},
+		{APIGroup: "apps", Kind: "StatefulSet"},
 	}
 
 	matchers, err := rego.Matchers()
@@ -32,6 +34,7 @@ func TestGetKindMatchers(t *testing.T) {
 func TestGetMatchLabelsMatcher(t *testing.T) {
 	comments := []string{
 		"@matchlabels team=a app.kubernetes.io/name=test",
+		"@matchlabels example.com/env=production",
 	}
 	rego := Rego{
 		headerComments: comments,
@@ -40,6 +43,7 @@ func TestGetMatchLabelsMatcher(t *testing.T) {
 	expected := MatchLabelsMatcher{
 		"team":                   "a",
 		"app.kubernetes.io/name": "test",
+		"example.com/env":        "production",
 	}
 
 	matchers, err := rego.Matchers()