forked from open-policy-agent/opa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
exec.go
185 lines (156 loc) · 4.13 KB
/
exec.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
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
LogTimestampFormat string // log timestamp 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
}