Skip to content

Commit

Permalink
config: Search for .regal.yaml and use for roots
Browse files Browse the repository at this point in the history
This updates the search for config to correctly look for .regal.yaml
too. It also correctly uses .regal.yaml files for root determination.

```
$ cat foo.rego
───────┬────────────────────────────────────────────────────────────────────
       │ File: foo.rego
───────┼────────────────────────────────────────────────────────────────────
   1   │ package foo
   2   │
   3   │ allow if      true
───────┴────────────────────────────────────────────────────────────────────
$ cat .regal.yaml
───────┬────────────────────────────────────────────────────────────────────
       │ File: .regal.yaml
───────┼────────────────────────────────────────────────────────────────────
   1   │ rules:
   2   │   style:
   3   │     opa-fmt:
   4   │       level: ignore
───────┴────────────────────────────────────────────────────────────────────
$ ../regal fix --force foo.rego
1 fix applied:
In project root: /Users/charlieegan3/Code/regal/temp
foo.rego -> foo/foo.rego:
- directory-package-mismatch
```

Signed-off-by: Charlie Egan <[email protected]>
  • Loading branch information
charlieegan3 committed Jan 22, 2025
1 parent 760b484 commit b9e3791
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 27 deletions.
2 changes: 1 addition & 1 deletion cmd/fix.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ func fix(args []string, params *fixCommandParams) error {

var userConfig config.Config

userConfigFile, err := readUserConfig(params, regalDir)
userConfigFile, err := readUserConfig(params, configSearchPath)

switch {
case err == nil:
Expand Down
2 changes: 1 addition & 1 deletion cmd/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ func lint(args []string, params *lintCommandParams) (report.Report, error) {

var userConfig config.Config

userConfigFile, err := readUserConfig(params, regalDir)
userConfigFile, err := readUserConfig(params, configSearchPath)

switch {
case err == nil:
Expand Down
11 changes: 4 additions & 7 deletions cmd/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,18 @@ type configFileParams interface {
getConfigFile() string
}

func readUserConfig(params configFileParams, regalDir *os.File) (userConfig *os.File, err error) {
func readUserConfig(params configFileParams, searchPath string) (userConfig *os.File, err error) {
if cfgFile := params.getConfigFile(); cfgFile != "" {
userConfig, err = os.Open(cfgFile)
if err != nil {
return nil, fmt.Errorf("failed to open config file %w", err)
}
} else {
searchPath, _ := os.Getwd()
if regalDir != nil {
searchPath = regalDir.Name()
if searchPath == "" {
searchPath, _ = os.Getwd()
}

if searchPath != "" {
userConfig, err = config.FindConfig(searchPath)
}
userConfig, err = config.FindConfig(searchPath)
}

return userConfig, err //nolint:wrapcheck
Expand Down
75 changes: 75 additions & 0 deletions e2e/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1201,6 +1201,81 @@ import rego.v1
}
}

func TestFixSingleFileNested(t *testing.T) {
t.Parallel()

stdout, stderr := bytes.Buffer{}, bytes.Buffer{}
td := t.TempDir()

initialState := map[string]string{
".regal/config.yaml": `
project:
rego-version: 1
`,
"foo/.regal.yaml": `
project:
rego-version: 1
rules:
style:
opa-fmt:
level: ignore
`,
"foo/foo.rego": `package wow`,
}

for file, content := range initialState {
mustWriteToFile(t, filepath.Join(td, file), content)
}

// --force is required to make the changes when there is no git repo
err := regal(&stdout, &stderr)(
"fix",
"--force",
filepath.Join(td, "foo/foo.rego"),
)

// 0 exit status is expected as all violations should have been fixed
expectExitCode(t, err, 0, &stdout, &stderr)

exp := fmt.Sprintf(`1 fix applied:
In project root: %[1]s
foo.rego -> wow/foo.rego:
- directory-package-mismatch
`, filepath.Join(td, "foo"))

if act := stdout.String(); exp != act {
t.Fatalf("expected stdout:\n%s\ngot:\n%s", exp, act)
}

if exp, act := "", stderr.String(); exp != act {
t.Fatalf("expected stderr %q, got %q", exp, act)
}

expectedState := map[string]string{
".regal/config.yaml": `
project:
rego-version: 1
`,
"foo/.regal.yaml": `
project:
rego-version: 1
rules:
style:
opa-fmt:
level: ignore
`,
"foo/wow/foo.rego": `package wow`,
}

for file, expectedContent := range expectedState {
bs := testutil.Must(os.ReadFile(filepath.Join(td, file)))(t)

if act := string(bs); expectedContent != act {
t.Errorf("expected %s contents:\n%s\ngot\n%s", file, expectedContent, act)
}
}
}

// verify fix for https://github.com/StyraInc/regal/issues/1082
func TestLintAnnotationCustomAttributeMultipleItems(t *testing.T) {
t.Parallel()
Expand Down
64 changes: 47 additions & 17 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,14 +246,25 @@ func FindBundleRootDirectories(path string) ([]string, error) {
// This will traverse the tree **upwards** searching for a .regal directory
regalDir, err := FindRegalDirectory(path)
if err == nil {
roots, err := rootsFromRegalDirectory(regalDir)
roots, err := rootsFromRegalConfigDirOrFile(regalDir)
if err != nil {
return nil, fmt.Errorf("failed to get roots from .regal directory: %w", err)
}

foundBundleRoots = append(foundBundleRoots, roots...)
}

// This will traverse the tree **upwards** searching for a .regal.yaml file
regalConfigFile, err := FindRegalConfigFile(path)
if err == nil {
roots, err := rootsFromRegalConfigDirOrFile(regalConfigFile)
if err != nil {
return nil, fmt.Errorf("failed to get roots from .regal.yaml: %w", err)
}

foundBundleRoots = append(foundBundleRoots, roots...)
}

// This will traverse the tree **downwards** searching for .regal directories
// Not using rio.WalkFiles here as we're specifically looking for directories
if err := filepath.WalkDir(path, func(path string, info os.DirEntry, err error) error {
Expand All @@ -272,7 +283,7 @@ func FindBundleRootDirectories(path string) ([]string, error) {

defer rd.Close()

roots, err := rootsFromRegalDirectory(rd)
roots, err := rootsFromRegalConfigDirOrFile(rd)
if err != nil {
return fmt.Errorf("failed to get roots from .regal directory: %w", err)
}
Expand All @@ -295,23 +306,38 @@ func FindBundleRootDirectories(path string) ([]string, error) {
return slices.Compact(foundBundleRoots), nil
}

func rootsFromRegalDirectory(regalDir *os.File) ([]string, error) {
foundBundleRoots := make([]string, 0)
func rootsFromRegalConfigDirOrFile(file *os.File) ([]string, error) {
defer file.Close()

defer regalDir.Close()
fileInfo, err := file.Stat()
if err != nil {
return nil, fmt.Errorf("failed to stat file: %w", err)
}

if (fileInfo.IsDir() && filepath.Base(file.Name()) != ".regal") ||
(!fileInfo.IsDir() && filepath.Base(file.Name()) != ".regal.yaml") {
return nil, fmt.Errorf(
"expected a directory named '.regal' or a file named '.regal.yaml', got '%s'",
filepath.Base(file.Name()),
)
}

parent, _ := filepath.Split(regalDir.Name())
parent := filepath.Dir(file.Name())

parent = filepath.Clean(parent)
foundBundleRoots := []string{parent}

// add the parent directory of .regal
foundBundleRoots = append(foundBundleRoots, parent)
var configFilePath string

file, err := os.ReadFile(filepath.Join(regalDir.Name(), "config.yaml"))
if fileInfo.IsDir() {
configFilePath = filepath.Join(file.Name(), "config.yaml")
} else {
configFilePath = file.Name()
}

fileContent, err := os.ReadFile(configFilePath)
if err == nil {
var conf Config

if err = yaml.Unmarshal(file, &conf); err != nil {
if err = yaml.Unmarshal(fileContent, &conf); err != nil {
return nil, fmt.Errorf("failed to unmarshal config file: %w", err)
}

Expand All @@ -322,14 +348,18 @@ func rootsFromRegalDirectory(regalDir *os.File) ([]string, error) {
}
}

customRulesDir := filepath.Join(regalDir.Name(), "rules")
// Include the "rules" directory when loading from a .regal dir
if fileInfo.IsDir() {
customDir := filepath.Join(file.Name(), "rules")

info, err := os.Stat(customRulesDir)
if err == nil && info.IsDir() {
foundBundleRoots = append(foundBundleRoots, customRulesDir)
info, err := os.Stat(customDir)
if err == nil && info.IsDir() {
foundBundleRoots = append(foundBundleRoots, customDir)
}
}

manifestRoots, err := rio.FindManifestLocations(filepath.Dir(regalDir.Name()))
// Include a search for manifest files
manifestRoots, err := rio.FindManifestLocations(parent)
if err != nil {
return nil, fmt.Errorf("failed while looking for manifest locations: %w", err)
}
Expand Down
37 changes: 36 additions & 1 deletion pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,42 @@ project:
expected := util.Map(util.FilepathJoiner(root), []string{"", ".regal/rules", "baz", "bundle", "foo/bar"})

if !slices.Equal(expected, locations) {
t.Errorf("expected %v, got %v", expected, locations)
t.Errorf("expected\n%s\ngot\n%s", strings.Join(expected, "\n"), strings.Join(locations, "\n"))
}
})
}

func TestFindBundleRootDirectoriesWithStandaloneConfig(t *testing.T) {
t.Parallel()

cfg := `
project:
roots:
- foo/bar
- baz
`

fs := map[string]string{
"/.regal.yaml": cfg, // root from config
"/bundle/.manifest": "", // bundle from .manifest
"/foo/bar/baz/policy.rego": "", // foo/bar from config
"/baz": "", // baz from config
}

test.WithTempFS(fs, func(root string) {
locations, err := FindBundleRootDirectories(root)
if err != nil {
t.Error(err)
}

if len(locations) != 4 {
t.Errorf("expected 5 locations, got %d", len(locations))
}

expected := util.Map(util.FilepathJoiner(root), []string{"", "baz", "bundle", "foo/bar"})

if !slices.Equal(expected, locations) {
t.Errorf("expected\n%s\ngot\n%s", strings.Join(expected, "\n"), strings.Join(locations, "\n"))
}
})
}
Expand Down

0 comments on commit b9e3791

Please sign in to comment.