mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-14 12:22:51 -06:00
55b053b79a
This is just a skeleton but the basic functionality is there: run OPA in a "one shot" mode against a set of input files and print the results for each. Fixes #3525 Signed-off-by: Torin Sandall <torinsandall@gmail.com>
185 lines
4.1 KiB
Go
185 lines
4.1 KiB
Go
package exec
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/open-policy-agent/opa/sdk"
|
|
"github.com/open-policy-agent/opa/util"
|
|
)
|
|
|
|
type Params struct {
|
|
Paths []string // file paths to execute against
|
|
Output io.Writer // output stream to write normal output to
|
|
ConfigFile string // OPA configuration file path
|
|
ConfigOverrides []string // OPA configuration overrides (--set arguments)
|
|
ConfigOverrideFiles []string // OPA configuration overrides (--set-file arguments)
|
|
OutputFormat *util.EnumFlag // output format (default: pretty)
|
|
LogLevel *util.EnumFlag // log level for plugins
|
|
LogFormat *util.EnumFlag // log format for plugins
|
|
BundlePaths []string // explicit paths of bundles to inject into the configuration
|
|
Decision string // decision to evaluate (overrides default decision set by configuration)
|
|
}
|
|
|
|
func NewParams(w io.Writer) *Params {
|
|
return &Params{
|
|
Output: w,
|
|
OutputFormat: util.NewEnumFlag("pretty", []string{"pretty", "json"}),
|
|
LogLevel: util.NewEnumFlag("error", []string{"debug", "info", "error"}),
|
|
LogFormat: util.NewEnumFlag("json", []string{"text", "json", "json-pretty"}),
|
|
}
|
|
}
|
|
|
|
// Exec executes OPA against the supplied files and outputs each result.
|
|
//
|
|
// NOTE(tsandall): consider expanding functionality:
|
|
//
|
|
// * specialized output formats (e.g., pretty/non-JSON outputs)
|
|
// * exit codes set by convention or policy (e.g,. non-empty set => error)
|
|
// * support for new input file formats beyond JSON and YAML
|
|
func Exec(ctx context.Context, opa *sdk.OPA, params *Params) error {
|
|
|
|
now := time.Now()
|
|
r := &jsonReporter{w: params.Output, buf: make([]result, 0)}
|
|
|
|
for item := range listAllPaths(params.Paths) {
|
|
|
|
if item.Error != nil {
|
|
return item.Error
|
|
}
|
|
|
|
input, err := parse(item.Path)
|
|
|
|
if err != nil {
|
|
if err2 := r.Report(result{Path: item.Path, Error: err}); err2 != nil {
|
|
return err2
|
|
}
|
|
continue
|
|
} else if input == nil {
|
|
continue
|
|
}
|
|
|
|
rs, err := opa.Decision(ctx, sdk.DecisionOptions{
|
|
Path: params.Decision,
|
|
Now: now,
|
|
Input: input,
|
|
})
|
|
if err != nil {
|
|
if err2 := r.Report(result{Path: item.Path, Error: err}); err2 != nil {
|
|
return err2
|
|
}
|
|
continue
|
|
}
|
|
|
|
if err := r.Report(result{Path: item.Path, Result: &rs.Result}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return r.Close()
|
|
}
|
|
|
|
type result struct {
|
|
Path string `json:"path"`
|
|
Error error `json:"error,omitempty"`
|
|
Result *interface{} `json:"result,omitempty"`
|
|
}
|
|
|
|
type jsonReporter struct {
|
|
w io.Writer
|
|
buf []result
|
|
}
|
|
|
|
func (jr *jsonReporter) Report(r result) error {
|
|
jr.buf = append(jr.buf, r)
|
|
return nil
|
|
}
|
|
|
|
func (jr *jsonReporter) Close() error {
|
|
enc := json.NewEncoder(jr.w)
|
|
enc.SetIndent("", " ")
|
|
return enc.Encode(struct {
|
|
Result []result `json:"result"`
|
|
}{
|
|
Result: jr.buf,
|
|
})
|
|
}
|
|
|
|
type fileListItem struct {
|
|
Path string
|
|
Error error
|
|
}
|
|
|
|
func listAllPaths(roots []string) chan fileListItem {
|
|
ch := make(chan fileListItem)
|
|
go func() {
|
|
for _, path := range roots {
|
|
err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
ch <- fileListItem{Path: path}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
ch <- fileListItem{Path: path, Error: err}
|
|
}
|
|
}
|
|
close(ch)
|
|
}()
|
|
return ch
|
|
}
|
|
|
|
var parsers = map[string]parser{
|
|
".json": utilParser{},
|
|
".yaml": utilParser{},
|
|
".yml": utilParser{},
|
|
}
|
|
|
|
type parser interface {
|
|
Parse(io.Reader) (interface{}, error)
|
|
}
|
|
|
|
type utilParser struct {
|
|
}
|
|
|
|
func (utilParser) Parse(r io.Reader) (interface{}, error) {
|
|
bs, err := ioutil.ReadAll(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var x interface{}
|
|
return x, util.Unmarshal(bs, &x)
|
|
}
|
|
|
|
func parse(p string) (*interface{}, error) {
|
|
|
|
parser, ok := parsers[path.Ext(p)]
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
f, err := os.Open(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
val, err := parser.Parse(f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &val, nil
|
|
}
|