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
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
@@ -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:
2 changes: 1 addition & 1 deletion cmd/lint.go
Original file line number Diff line number Diff line change
@@ -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:
11 changes: 4 additions & 7 deletions cmd/utils.go
Original file line number Diff line number Diff line change
@@ -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
75 changes: 75 additions & 0 deletions e2e/cli_test.go
Original file line number Diff line number Diff line change
@@ -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()
64 changes: 47 additions & 17 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -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 {
@@ -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)
}
@@ -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)
}

@@ -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)
}
37 changes: 36 additions & 1 deletion pkg/config/config_test.go
Original file line number Diff line number Diff line change
@@ -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"))
}
})
}