sg/internal/source/fs.go (129 lines of code) (raw):

package source import ( "fmt" "io/fs" "maps" "path/filepath" "sort" "strings" "github.com/open-policy-agent/conftest/parser" "github.com/open-policy-agent/opa/ast" "github.com/open-policy-agent/opa/util" ) type fsSource struct { // filePath is the full path of the read file. filePath string // configurations is the loaded configurations. configurations []ast.Value } var _ Source = (*fsSource)(nil) func (s *fsSource) Name() string { return s.filePath } func (s *fsSource) ParsedConfigurations() ([]ast.Value, error) { return s.configurations, nil } func relativeToContextRootFn(contextRoot string) func(string) string { if contextRoot == "" { return func(path string) string { return path } } return func(s string) string { rel, err := filepath.Rel(contextRoot, s) if err != nil { // NOTE: we don't expect this as we guard this in loadSourceFromPaths. panic(fmt.Sprintf("path %q is not relative to context root %q", s, contextRoot)) } return rel } } // ref: https://github.com/open-policy-agent/opa/blob/af8f915846fa325fe009dd6c226122bb205cff0a/rego/rego.go#L1937-L1956 func parseRawConfiguration(s any) (ast.Value, error) { rawPtr := util.Reference(s) if err := util.RoundTrip(rawPtr); err != nil { return nil, fmt.Errorf("convert raw configuration to JSON: %w", err) } rv, err := ast.InterfaceToValue(rawPtr) if err != nil { return nil, fmt.Errorf("convert raw configuration to OPA value: %w", err) } return rv, nil } // ref: https://github.com/open-policy-agent/conftest/blob/f18b7bbde2fdbd766c8348dff3a0a24792eb98c7/runner/test.go#L99 func loadSourceFromPaths(contextRoot string, paths []string) ([]Source, error) { // when contextRoot specified, all paths must be relative to contextRoot. // FIXME(hbc): this implementation may not be correct in Windows (see context in `filepath.HasPrefix`) // We should revisit this in later changes. if contextRoot != "" { contextRootAbs, err := filepath.Abs(contextRoot) if err != nil { return nil, fmt.Errorf("failed to get absolute path of context root %q: %w", contextRoot, err) } for _, p := range paths { pAbs, err := filepath.Abs(p) if err != nil { return nil, fmt.Errorf("failed to get absolute path of %q: %w", p, err) } if !strings.HasPrefix(pAbs, contextRootAbs) { return nil, fmt.Errorf("path %q is not relative to context root %q", p, contextRoot) } } } relativeToContextRoot := relativeToContextRootFn(contextRoot) var files []string var jsonFiles []string walk := func(path string, info fs.DirEntry, err error) error { if err != nil { return err } if info.IsDir() { return nil } if parser.FileSupported(path) { if strings.EqualFold(filepath.Ext(path)[1:], parser.JSON) { jsonFiles = append(jsonFiles, path) } else { files = append(files, path) } } return nil } for _, path := range paths { if err := filepath.WalkDir(path, walk); err != nil { return nil, fmt.Errorf("walk path %q: %w", path, err) } } if len(files)+len(jsonFiles) < 1 { return nil, fmt.Errorf("no files found from given paths: %v", paths) } // parse json files with jsonc to allow comments jsonConfigurations, err := parser.ParseConfigurationsAs(jsonFiles, parser.JSONC) if err != nil { return nil, fmt.Errorf("parse configurations: %w", err) } configurations, err := parser.ParseConfigurations(files) if err != nil { return nil, fmt.Errorf("parse configurations: %w", err) } maps.Copy(configurations, jsonConfigurations) filePathsSorted := make([]string, 0, len(configurations)) for filePath := range configurations { filePathsSorted = append(filePathsSorted, filePath) } sort.Strings(filePathsSorted) var rv []Source for _, filePath := range filePathsSorted { c := configurations[filePath] var subConfigurations []any if cc, ok := c.([]any); ok { subConfigurations = cc } else { subConfigurations = []any{c} } var parsedConfigurations []ast.Value for _, rawConfiguration := range subConfigurations { parsedConfiguration, err := parseRawConfiguration(rawConfiguration) if err != nil { return nil, fmt.Errorf("parse raw configuration: %w", err) } parsedConfigurations = append(parsedConfigurations, parsedConfiguration) } rv = append(rv, &fsSource{ filePath: relativeToContextRoot(filePath), configurations: parsedConfigurations, }) } return rv, nil }