Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

config: Search for .regal.yaml and use for roots #1357

Merged
merged 1 commit into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading