Skip to content

Commit

Permalink
Merge pull request #13 from cinaq/extension
Browse files Browse the repository at this point in the history
Extension
  • Loading branch information
xiwenc authored Aug 20, 2024
2 parents 7e8cc13 + 22fd46d commit 5a89705
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 86 deletions.
4 changes: 3 additions & 1 deletion cmd/mendix-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func main() {
policiesDirectory, _ := cmd.Flags().GetString("policies")
modelDirectory, _ := cmd.Flags().GetString("modelsource")
xunitReport, _ := cmd.Flags().GetString("xunit-report")
JsonFile, _ := cmd.Flags().GetString("json-file")
verbose, _ := cmd.Flags().GetBool("verbose")

log := logrus.New()
Expand All @@ -62,7 +63,7 @@ func main() {
}

lint.SetLogger(log)
err := lint.EvalAll(policiesDirectory, modelDirectory, xunitReport)
err := lint.EvalAll(policiesDirectory, modelDirectory, xunitReport, JsonFile)
if err != nil {
log.Errorf("lint failed: %s", err)
os.Exit(1)
Expand All @@ -73,6 +74,7 @@ func main() {
cmdLint.Flags().StringP("policies", "p", "policies", "Path to directory with policies")
cmdLint.Flags().StringP("modelsource", "m", "modelsource", "Path to directory with exported model")
cmdLint.Flags().StringP("xunit-report", "x", "", "Path to output file for xunit report. If not provided, no xunit report will be generated")
cmdLint.Flags().StringP("json-file", "j", "", "Path to output file for JSON report. If not provided, no JSON file will be generated")
cmdLint.Flags().Bool("verbose", false, "Turn on for debug logs")
rootCmd.AddCommand(cmdLint)

Expand Down
100 changes: 35 additions & 65 deletions lint/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package lint
import (
"context"
"encoding/xml"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"time"

Expand All @@ -28,24 +28,22 @@ func printTestsuite(ts Testsuite) {
fmt.Println("")
}

func EvalAll(policiesPath string, modelSourcePath string, xunitReport string) error {
func EvalAll(policiesPath string, modelSourcePath string, xunitReport string, jsonFile string) error {
testsuites := make([]Testsuite, 0)
policies, err := readPoliciesMetadata(policiesPath)
if err != nil {
return err
}
failuresCount := 0
filepath.Walk(policiesPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && !strings.HasSuffix(info.Name(), "_test.rego") && strings.HasSuffix(info.Name(), ".rego") {
testsuite, err := evalTestsuite(path, modelSourcePath)
for _, policy := range policies {
testsuite, err := evalTestsuite(policy, modelSourcePath)
if err != nil {
return err
}
printTestsuite(*testsuite)
failuresCount += testsuite.Failures
testsuites = append(testsuites, *testsuite)
}
return nil
})
}

if xunitReport != "" {
file, err := os.Create(xunitReport)
Expand All @@ -62,75 +60,47 @@ func EvalAll(policiesPath string, modelSourcePath string, xunitReport string) er
}
}

if jsonFile != "" {
file, err := os.Create(jsonFile)
if err != nil {
panic(err)
}
defer file.Close()

encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
testsuitesContainer := TestSuites{Testsuites: testsuites, Policies: policies}
if err := encoder.Encode(testsuitesContainer); err != nil {
panic(err)
}
}

if failuresCount > 0 {
return fmt.Errorf("%d failures", failuresCount)
}
return nil
}

func evalTestsuite(policyPath string, modelSourcePath string) (*Testsuite, error) {
func evalTestsuite(policy Policy, modelSourcePath string) (*Testsuite, error) {

log.Debugf("evaluating policy %s", policyPath)

// read the policy file
policyFile, err := os.Open(policyPath)
if err != nil {
return nil, err
}
defer policyFile.Close()

policyContent, err := os.ReadFile(policyPath)
if err != nil {
return nil, err
}
var inputFiles []string = nil
var packageName string = ""
var pattern string = ""
var policy_canonical_name string = ""
var skipReason string = ""

lines := strings.Split(string(policyContent), "\n")

for _, line := range lines {
tokens := strings.Split(line, "# input: ")
if len(tokens) > 1 && inputFiles == nil {
pattern = strings.ReplaceAll(tokens[1], "\"", "")
inputFiles, err = expandPaths(pattern, modelSourcePath)
if err != nil {
return nil, err
}
}
tokens = strings.Split(line, "# skip: ")
if len(tokens) > 1 && skipReason == "" {
skipReason = tokens[1]
}
tokens = strings.Split(line, "package ")
if len(tokens) > 1 && packageName == "" {
packageName = tokens[1]
}
tokens = strings.Split(line, "default ")
if len(tokens) > 1 && policy_canonical_name == "" {
policy_canonical_name = strings.Split(tokens[1], " := ")[0]
}
}

log.Debugf("package name: %s", packageName)
log.Debugf("policy name: %s", policy_canonical_name)
log.Debugf("input pattern: %s", pattern)
log.Debugf("expanded input files %v", inputFiles)
log.Debugf("evaluating policy %s", policy.Path)

var skipped *Skipped = nil
if skipReason != "" {
if policy.SkipReason != "" {
skipped = &Skipped{
Message: skipReason,
Message: policy.SkipReason,
}
}

queryString := "data." + packageName
queryString := "data." + policy.PackageName
testcases := make([]Testcase, 0)
failuresCount := 0
skippedCount := 0
totalTime := 0.0
inputFiles, err := expandPaths(policy.Pattern, modelSourcePath)
if err != nil {
return nil, err
}
testcase := &Testcase{}

for _, inputFile := range inputFiles {
Expand All @@ -142,7 +112,7 @@ func evalTestsuite(policyPath string, modelSourcePath string) (*Testsuite, error
}
skippedCount++
} else {
testcase, err = evalTestcase(policyPath, queryString, inputFile)
testcase, err = evalTestcase(policy.Path, queryString, inputFile)
if err != nil {
return nil, err
}
Expand All @@ -156,7 +126,7 @@ func evalTestsuite(policyPath string, modelSourcePath string) (*Testsuite, error
}

testsuite := &Testsuite{
Name: policyPath,
Name: policy.Path,
Tests: len(testcases),
Failures: failuresCount,
Skipped: skippedCount,
Expand Down
5 changes: 3 additions & 2 deletions lint/lint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ func TestLintSingle(t *testing.T) {
// }
// })
t.Run("single policy passes", func(t *testing.T) {
result, err := evalTestsuite("./../policies/001_project_settings/001_0003_security_checks.rego", "./../modelsource")
policy, _ := parsePolicyMetadata("./../policies/001_project_settings/001_0003_security_checks.rego")
result, err := evalTestsuite(*policy, "./../modelsource")

if err != nil {
t.Errorf("Failed to evaluate")
Expand All @@ -32,7 +33,7 @@ func TestLintSingle(t *testing.T) {

func TestLintBundle(t *testing.T) {
t.Run("all-policy", func(t *testing.T) {
err := EvalAll("./../policies", "./../modelsource", "")
err := EvalAll("./../policies", "./../modelsource", "", "")

if err != nil {
t.Errorf("No failures expected: %v", err)
Expand Down
110 changes: 110 additions & 0 deletions lint/policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package lint

import (
"os"
"path/filepath"
"strings"
)

func readPoliciesMetadata(policiesPath string) ([]Policy, error) {
policies := make([]Policy, 0)
filepath.Walk(policiesPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && !strings.HasSuffix(info.Name(), "_test.rego") && strings.HasSuffix(info.Name(), ".rego") {
policy, err := parsePolicyMetadata(path)
if err != nil {
return err
}
policies = append(policies, *policy)
}
return nil
})
return policies, nil
}

func parsePolicyMetadata(policyPath string) (*Policy, error) {

log.Debugf("reading policy %s", policyPath)

// read the policy file
policyFile, err := os.Open(policyPath)
if err != nil {
return nil, err
}
defer policyFile.Close()

policyContent, err := os.ReadFile(policyPath)
if err != nil {
return nil, err
}

var packageName string = ""
var pattern string = ""
var skipReason string = ""
var title string = ""
var description string = ""
var category string = ""
var severity string = ""
var ruleNumber string = ""
var remediation string = ""
var ruleName string = ""
var key string = ""
var value string = ""

lines := strings.Split(string(policyContent), "\n")

for _, line := range lines {
tokens := strings.Split(line, "package ")
if len(tokens) > 1 && packageName == "" {
packageName = tokens[1]
}
// only read the comments as that is where the metadata is stored
if !strings.HasPrefix(line, "# ") {
continue
}
// strip the comment prefix
line = strings.TrimPrefix(line, "# ")
tokens = strings.SplitN(line, ":", 2)
if len(tokens) == 2 {
key = strings.Trim(strings.TrimSpace(tokens[0]), "\"")
value = strings.Trim(strings.TrimSpace(tokens[1]), "\"")
}
switch key {
case "input":
pattern = value
case "skip":
skipReason = value
case "title":
title = value
case "description":
description = value
case "category":
category = value
case "rulename":
ruleName = value
case "severity":
severity = value
case "rulenumber":
ruleNumber = value
case "remediation":
remediation = value
}
}

policy := &Policy{
Title: title,
Description: description,
Category: category,
Severity: severity,
RuleNumber: ruleNumber,
Remediation: remediation,
RuleName: ruleName,
Path: policyPath,
SkipReason: skipReason,
Pattern: pattern,
PackageName: packageName,
}
return policy, nil
}
52 changes: 34 additions & 18 deletions lint/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,49 @@ package lint
import "encoding/xml"

type TestSuites struct {
XMLName xml.Name `xml:"testsuites"`
Testsuites []Testsuite
XMLName xml.Name `xml:"testsuites" json:"-"`
Testsuites []Testsuite `xml:"testsuite" json:"testsuites"`
Policies []Policy `xml:"-" json:"policies"`
}

type Testsuite struct {
XMLName xml.Name `xml:"testsuite"`
Name string `xml:"name,attr"`
Tests int `xml:"tests,attr"`
Failures int `xml:"failures,attr"`
Skipped int `xml:"skipped,attr"`
Time float64 `xml:"time,attr"`
Testcases []Testcase
XMLName xml.Name `xml:"testsuite" json:"-"`
Name string `xml:"name,attr" json:"name"`
Tests int `xml:"tests,attr" json:"tests"`
Failures int `xml:"failures,attr" json:"failures"`
Skipped int `xml:"skipped,attr" json:"skipped"`
Time float64 `xml:"time,attr" json:"time"`
Testcases []Testcase `xml:"testcase" json:"testcases"`
}

type Testcase struct {
XMLName xml.Name `xml:"testcase"`
Name string `xml:"name,attr"`
Time float64 `xml:"time,attr"`
Failure *Failure `xml:"failure,omitempty"`
Skipped *Skipped `xml:"skipped,omitempty"`
XMLName xml.Name `xml:"testcase" json:"-"`
Name string `xml:"name,attr" json:"name"`
Time float64 `xml:"time,attr" json:"time"`
Failure *Failure `xml:"failure,omitempty" json:"failure,omitempty"`
Skipped *Skipped `xml:"skipped,omitempty" json:"skipped,omitempty"`
}

type Failure struct {
Message string `xml:"message,attr"`
Type string `xml:"type,attr"`
Data string `xml:",chardata"`
Message string `xml:"message,attr" json:"message"`
Type string `xml:"type,attr" json:"type"`
Data string `xml:",chardata" json:"-"`
}

type Skipped struct {
Message string `xml:"message,attr"`
Message string `xml:"message,attr" json:"message"`
}

type Policy struct {
Title string `json:"title"`
Description string `json:"description"`
Category string `json:"category"`
Severity string `json:"severity"`
RuleNumber string `json:"ruleNumber"`
Remediation string `json:"remediation"`
RuleName string `json:"ruleName"`
Path string `json:"path"`
SkipReason string `json:"skipReason"`
Pattern string `json:"pattern"`
PackageName string `json:"packageName"`
}
4 changes: 4 additions & 0 deletions mpr/mpr.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ func ExportModel(inputDirectory string, outputDirectory string, raw bool, mode s
if err != nil {
return err
}
if strings.Contains(path, ".mendix-cache") {
log.Debugf("Skipping system managed file %s", path)
return nil
}
if !info.IsDir() && strings.HasSuffix(info.Name(), ".mpr") {
exportMPR(path, outputDirectory, raw, mode)
}
Expand Down

0 comments on commit 5a89705

Please sign in to comment.