Repository: plexsystems/konstraint (go)

Issue:
Request: Add type of rego to docs
It would be nice if the docs included the type of constraint being violented in the docs, i.e.: if it's a deny/warn/etc

i.e.:

|Type|API Groups|Kinds|Description|
|---|---|---|---|
|DENY|apps, core|DaemonSet, Deployment, StatefulSet, Pod|Container images can not use the latest tag.|
|WARN|apps, core|DaemonSet, Deployment, StatefulSet, Pod|Containers must have resource constraints specified.|

Ideally, this would be worked out from the rego and not from the comment.

Pull request:
Add policy name, link, and rule types to doc command
Resolves #8 

Patch:
diff --git a/examples/policies.md b/examples/policies.md
index d295397..c5d82ae 100755
--- a/examples/policies.md
+++ b/examples/policies.md
@@ -1,6 +1,6 @@
 # Policies
 
-|API Groups|Kinds|Description|
-|---|---|---|
-|apps, core|DaemonSet, Deployment, StatefulSet, Pod|Container images can not use the latest tag.|
-|apps, core|DaemonSet, Deployment, StatefulSet, Pod|Containers must have resource constraints specified.|
+|Name|Rule Types|API Groups|Kinds|Description|
+|---|---|---|---|---|
+|[Container Images](container-images)|violation|apps, core|DaemonSet, Deployment, StatefulSet, Pod|Container images can not use the latest tag.|
+|[Containers Resource Constraints Required](containers-resource-constraints-required)|violation|apps, core|DaemonSet, Deployment, StatefulSet, Pod|Containers must have resource constraints specified.|
diff --git a/internal/commands/create.go b/internal/commands/create.go
index d74b7f9..669146c 100644
--- a/internal/commands/create.go
+++ b/internal/commands/create.go
@@ -185,7 +185,7 @@ func getConstraint(policy rego.File) (unstructured.Unstructured, error) {
 		}
 	}
 
-	policyCommentBlocks, err := getPolicyCommentBlocks(policy.Contents)
+	policyCommentBlocks, err := getPolicyCommentBlocks(policy.Comments)
 	if err != nil {
 		return unstructured.Unstructured{}, fmt.Errorf("get policy comment blocks: %w", err)
 	}
@@ -262,10 +262,8 @@ func getRegoFilePaths(path string) ([]string, error) {
 }
 
 func getKindFromPath(path string) string {
-	kind := filepath.Base(filepath.Dir(path))
-	kind = strings.ReplaceAll(kind, "-", " ")
-	kind = strings.Title(kind)
-	kind = strings.ReplaceAll(kind, " ", "")
+	name := getNameFromPath(path)
+	kind := strings.ReplaceAll(name, " ", "")
 
 	return kind
 }
diff --git a/internal/commands/document.go b/internal/commands/document.go
index 3b77922..eb337cc 100644
--- a/internal/commands/document.go
+++ b/internal/commands/document.go
@@ -7,7 +7,7 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/open-policy-agent/opa/ast"
+	"github.com/plexsystems/konstraint/internal/rego"
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
 )
@@ -63,52 +63,57 @@ func getPolicyDocumentation(path string) (string, error) {
 		return "", fmt.Errorf("get rego files: %w", err)
 	}
 
-	var allPolicyCommentBlocks []PolicyCommentBlock
-	for _, regoFilePath := range regoFilePaths {
-		policyBytes, err := ioutil.ReadFile(regoFilePath)
-		if err != nil {
-			return "", fmt.Errorf("reading file: %w", err)
-		}
+	policies, err := rego.LoadPolicies(regoFilePaths)
+	if err != nil {
+		return "", fmt.Errorf("load policies: %w", err)
+	}
+
+	policyDocument := "# Policies\n\n"
+	policyDocument += "|Name|Rule Types|API Groups|Kinds|Description|\n"
+	policyDocument += "|---|---|---|---|---|\n"
 
-		policyCommentBlocks, err := getPolicyCommentBlocks(string(policyBytes))
+	for _, policy := range policies {
+		var policyCommentBlocks []PolicyCommentBlock
+		policyCommentBlocks, err := getPolicyCommentBlocks(policy.Comments)
 		if err != nil {
 			return "", fmt.Errorf("get policy comment blocks: %w", err)
 		}
 
-		allPolicyCommentBlocks = append(allPolicyCommentBlocks, policyCommentBlocks...)
-	}
+		relativePolicyPath, err := getRelativePolicyPath(path, policy.FilePath)
+		if err != nil {
+			return "", fmt.Errorf("get relative policy path: %w", err)
+		}
 
-	policyDocument := "# Policies\n\n"
-	policyDocument += "|API Groups|Kinds|Description|\n"
-	policyDocument += "|---|---|---|\n"
+		ruleTypes := strings.Join(policy.RulesActions, ", ")
 
-	for _, policyCommentBlock := range allPolicyCommentBlocks {
-		apiGroups := strings.Join(policyCommentBlock.APIGroups, ", ")
-		kinds := strings.Join(policyCommentBlock.Kinds, ", ")
+		for _, policyCommentBlock := range policyCommentBlocks {
+			apiGroups := strings.Join(policyCommentBlock.APIGroups, ", ")
+			kinds := strings.Join(policyCommentBlock.Kinds, ", ")
 
-		policyDocument += fmt.Sprintf("|%s|%s|%s|\n", apiGroups, kinds, policyCommentBlock.Description)
+			policyDocument += fmt.Sprintf("|[%s](%s)|%s|%s|%s|%s|\n",
+				getNameFromPath(policy.FilePath),
+				relativePolicyPath,
+				ruleTypes,
+				apiGroups,
+				kinds,
+				policyCommentBlock.Description,
+			)
+		}
 	}
 
 	return policyDocument, nil
 }
 
-func getPolicyCommentBlocks(policy string) ([]PolicyCommentBlock, error) {
-	stringReader := strings.NewReader(policy)
-	_, policyComments, errors := ast.NewParser().WithReader(stringReader).Parse()
-	if len(errors) > 0 {
-		return nil, fmt.Errorf("parsing rego: %w", errors)
-	}
-
+func getPolicyCommentBlocks(comments []string) ([]PolicyCommentBlock, error) {
 	var policyCommentBlocks []PolicyCommentBlock
 	var description string
-	for c := range policyComments {
-		commentText := string(policyComments[c].Text)
-		if !strings.Contains(commentText, "@Kinds") {
-			description = commentText
+	for _, c := range comments {
+		if !strings.Contains(c, "@Kinds") {
+			description = c
 			continue
 		}
 
-		kindGroups := strings.Split(commentText, " ")[2:]
+		kindGroups := strings.Split(c, " ")[2:]
 
 		var apiGroups []string
 		var kinds []string
@@ -143,3 +148,21 @@ func contains(collection []string, item string) bool {
 
 	return false
 }
+
+func getRelativePolicyPath(baseDir string, policyPath string) (string, error) {
+	fullBaseDir := filepath.Dir(filepath.Join(baseDir, viper.GetString("output")))
+	relPath, err := filepath.Rel(fullBaseDir, policyPath)
+	if err != nil {
+		return "", err
+	}
+
+	return filepath.Dir(relPath), nil
+}
+
+func getNameFromPath(path string) string {
+	name := filepath.Base(filepath.Dir(path))
+	name = strings.ReplaceAll(name, "-", " ")
+	name = strings.Title(name)
+
+	return name
+}


Test patch:
diff --git a/internal/commands/document_test.go b/internal/commands/document_test.go
index 877bbf2..502005b 100644
--- a/internal/commands/document_test.go
+++ b/internal/commands/document_test.go
@@ -1,15 +1,24 @@
 package commands
 
-import "testing"
+import (
+	"testing"
+
+	"github.com/plexsystems/konstraint/internal/rego"
+)
 
 func TestGetPolicyCommentBlocks_NoKinds(t *testing.T) {
-	policy := `
+	policy := `package test
 # Description
 violation[msg] {
 	false
 }`
 
-	actual, err := getPolicyCommentBlocks(policy)
+	regoFile, err := rego.NewRegoFile("test.rego", policy)
+	if err != nil {
+		t.Fatal("new rego file", err)
+	}
+
+	actual, err := getPolicyCommentBlocks(regoFile.Comments)
 	if err != nil {
 		t.Fatal("get policy comment blocks:", err)
 	}
@@ -20,14 +29,19 @@ violation[msg] {
 }
 
 func TestGetPolicyCommentBlocks(t *testing.T) {
-	policy := `
+	policy := `package test
 # First description
 # @Kinds core/Pod apps/Deployment apps/DaemonSet
 violation[msg] {
 	false
 }`
 
-	actual, err := getPolicyCommentBlocks(policy)
+	regoFile, err := rego.NewRegoFile("test.rego", policy)
+	if err != nil {
+		t.Fatal("new rego file", err)
+	}
+
+	actual, err := getPolicyCommentBlocks(regoFile.Comments)
 	if err != nil {
 		t.Fatal("get policy comment blocks:", err)
 	}