Skip to content

Commit 213adf2

Browse files
committed
Add mindev ruletype init to kick off a rule type
This helps folks set up the basic skeleton for ruletype writing. Signed-off-by: Juan Antonio Osorio <[email protected]>
1 parent 0068f4d commit 213adf2

File tree

2 files changed

+188
-0
lines changed

2 files changed

+188
-0
lines changed

Diff for: cmd/dev/app/rule_type/init.go

+187
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// SPDX-FileCopyrightText: Copyright 2023 The Minder Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package rule_type
5+
6+
import (
7+
"errors"
8+
"fmt"
9+
"github.com/spf13/cobra"
10+
"os"
11+
"path/filepath"
12+
"regexp"
13+
)
14+
15+
// CmdInit is the command for initializing a rule type definition
16+
func CmdInit() *cobra.Command {
17+
initCmd := &cobra.Command{
18+
Use: "init",
19+
Short: "initialize a rule type definition",
20+
Long: `The 'ruletype init' subcommand allows you to initialize a rule type definition
21+
22+
The first positional argument is the directory to initialize the rule type in.
23+
The rule type will be initialized in the current directory if no directory is provided.
24+
`,
25+
RunE: initCmdRun,
26+
SilenceUsage: true,
27+
}
28+
29+
initCmd.Flags().StringP("name", "n", "", "name of the rule type")
30+
initCmd.Flags().BoolP("skip-tests", "s", false, "skip creating test files")
31+
32+
if err := initCmd.MarkFlagRequired("name"); err != nil {
33+
fmt.Fprintf(os.Stderr, "Error marking flag as required: %s\n", err)
34+
os.Exit(1)
35+
}
36+
37+
return initCmd
38+
}
39+
40+
func initCmdRun(cmd *cobra.Command, args []string) error {
41+
name := cmd.Flag("name").Value.String()
42+
skipTests := cmd.Flag("skip-tests").Value.String() == "true"
43+
dir := "."
44+
if len(args) > 0 {
45+
dir = args[0]
46+
}
47+
48+
if err := validateRuleTypeName(name); err != nil {
49+
return err
50+
}
51+
52+
ruleTypeFileName := filepath.Join(dir, name+".yaml")
53+
ruleTypeTestFileName := filepath.Join(dir, name+".test.yaml")
54+
ruleTypeTestDataDirName := filepath.Join(dir, name+".testdata")
55+
56+
if err := assertFilesDontExist(
57+
ruleTypeFileName, ruleTypeTestFileName, ruleTypeTestDataDirName); err != nil {
58+
return err
59+
}
60+
61+
// Create rule type file
62+
if err := createRuleTypeFile(ruleTypeFileName, name); err != nil {
63+
return err
64+
}
65+
cmd.Printf("Created rule type file: %s\n", ruleTypeFileName)
66+
67+
if !skipTests {
68+
// Create rule type test file
69+
if err := createRuleTypeTestFile(ruleTypeTestFileName, name); err != nil {
70+
return err
71+
}
72+
cmd.Printf("Created rule type test file: %s\n", ruleTypeTestFileName)
73+
74+
// Create rule type test data directory
75+
if err := createRuleTypeTestDataDir(ruleTypeTestDataDirName); err != nil {
76+
return err
77+
}
78+
cmd.Printf("Created rule type test data directory: %s\n", ruleTypeTestDataDirName)
79+
}
80+
81+
return nil
82+
}
83+
84+
func validateRuleTypeName(name string) error {
85+
if name == "" {
86+
return errors.New("name cannot be empty")
87+
}
88+
89+
validName := regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
90+
91+
// regexp to validate name
92+
if !validName.MatchString(name) {
93+
return errors.New("name must only contain alphanumeric characters and underscores")
94+
}
95+
96+
return nil
97+
}
98+
99+
func assertFilesDontExist(files ...string) error {
100+
for _, file := range files {
101+
if _, err := os.Stat(file); err == nil {
102+
103+
return fmt.Errorf("file %s already exists", file)
104+
}
105+
}
106+
107+
return nil
108+
}
109+
110+
func createRuleTypeFile(fileName, name string) error {
111+
return createFileWithContent(fileName, fmt.Sprintf(`---
112+
version: v1
113+
release_phase: alpha
114+
type: rule-type
115+
name: %s
116+
display_name: # Display name for the rule type
117+
short_failure_message: # Short message to display when the rule fails
118+
severity:
119+
value: medium
120+
context: {}
121+
description: | # Description of the rule type
122+
guidance: | # Guidance for the rule type. This helps users understand how to fix the issue.
123+
def:
124+
in_entity: repository # The entity type the rule applies to
125+
rule_schema: {}
126+
ingest:
127+
type: git
128+
git:
129+
eval:
130+
type: rego
131+
rego:
132+
type: deny-by-default
133+
def: |
134+
package minder
135+
136+
import rego.v1
137+
138+
default allow := false
139+
140+
allow if {
141+
true
142+
}
143+
144+
message := "This is a test message"
145+
`, name))
146+
}
147+
148+
func createRuleTypeTestFile(fileName, name string) error {
149+
return createFileWithContent(fileName, `---
150+
tests:
151+
- name: "TEST NAME GOES HERE""
152+
def: {}
153+
params: {}
154+
expect: "pass"
155+
entity: &test-repo
156+
type: repository
157+
entity:
158+
owner: "coolhead"
159+
name: "haze-wave"
160+
# http:
161+
# body_file: HTTP_BODY_FILE
162+
# git:
163+
# repo_base: REPO_BASE_PATH
164+
`)
165+
}
166+
167+
func createRuleTypeTestDataDir(dirName string) error {
168+
if err := os.Mkdir(dirName, 0755); err != nil {
169+
return fmt.Errorf("error creating directory %s: %w", dirName, err)
170+
}
171+
172+
return nil
173+
}
174+
175+
func createFileWithContent(fileName, content string) error {
176+
file, err := os.Create(fileName)
177+
if err != nil {
178+
return fmt.Errorf("error creating file %s: %w", fileName, err)
179+
}
180+
defer file.Close()
181+
182+
if _, err := file.WriteString(content); err != nil {
183+
return fmt.Errorf("error writing to file %s: %w", fileName, err)
184+
}
185+
186+
return nil
187+
}

Diff for: cmd/dev/app/rule_type/ruletype.go

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ func CmdRuleType() *cobra.Command {
1616
rtCmd.AddCommand(CmdTest())
1717
rtCmd.AddCommand(CmdLint())
1818
rtCmd.AddCommand(CmdValidateUpdate())
19+
rtCmd.AddCommand(CmdInit())
1920

2021
return rtCmd
2122
}

0 commit comments

Comments
 (0)