-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
runtime.go
232 lines (185 loc) · 5.02 KB
/
runtime.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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
// Copyright 2016 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package runtime
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/golang/glog"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/repl"
"github.com/open-policy-agent/opa/storage"
"github.com/pkg/errors"
)
// Params stores the configuration for an OPA instance.
type Params struct {
// Addr is the listening address that the OPA server will bind to.
Addr string
// HistoryPath is the filename to store the interactive shell user
// input history.
HistoryPath string
// Output format controls how the REPL will print query results.
// Default: "pretty".
OutputFormat string
// Paths contains filenames of base documents and policy modules to
// load on startup.
Paths []string
// PolicyDir is the filename of the directory to persist policy
// definitions in. Policy definitions stored in this directory
// are automatically loaded on startup.
PolicyDir string
// Server flag controls whether the OPA instance will start a server.
// By default, the OPA instance acts as an interactive shell.
Server bool
}
// Runtime represents a single OPA instance.
type Runtime struct {
DataStore *storage.DataStore
PolicyStore *storage.PolicyStore
}
// Init initializes the OPA instance.
func (rt *Runtime) Init(params *Params) error {
if len(params.PolicyDir) > 0 {
if err := os.MkdirAll(params.PolicyDir, 0755); err != nil {
return errors.Wrap(err, "unable to make --policy-dir")
}
}
parsed, err := parseInputs(params.Paths)
if err != nil {
return errors.Wrapf(err, "parse error")
}
// Open data store and load base documents.
ds := storage.NewDataStore()
for _, doc := range parsed.docs {
for k, v := range doc {
if err := ds.Patch(storage.AddOp, []interface{}{k}, v); err != nil {
return errors.Wrap(err, "unable to open data store")
}
}
}
// Open policy store and load existing policies.
ps := storage.NewPolicyStore(ds, params.PolicyDir)
if err := ps.Open(storage.LoadPolicies); err != nil {
return errors.Wrap(err, "unable to open policy store")
}
// Load policies provided via input.
if err := compileAndStoreInputs(parsed.modules, ps); err != nil {
return errors.Wrapf(err, "compile error")
}
rt.PolicyStore = ps
rt.DataStore = ds
return nil
}
// Start is the entry point of an OPA instance.
func (rt *Runtime) Start(params *Params) {
if err := rt.Init(params); err != nil {
fmt.Println("error initializing runtime:", err)
os.Exit(1)
}
if !params.Server {
rt.startRepl(params)
} else {
rt.startServer(params)
}
}
func (rt *Runtime) startServer(params *Params) {
glog.Infof("First line of log stream.")
glog.V(2).Infof("Server listening address: %v.", params.Addr)
persist := len(params.PolicyDir) > 0
server := NewServer(rt, params.Addr, persist)
if err := server.Loop(); err != nil {
glog.Errorf("Server exiting: %v", err)
os.Exit(1)
}
}
func (rt *Runtime) startRepl(params *Params) {
repl := repl.New(rt.DataStore, rt.PolicyStore, params.HistoryPath, os.Stdout, params.OutputFormat)
repl.Loop()
}
func compileAndStoreInputs(parsed map[string]*parsedModule, policyStore *storage.PolicyStore) error {
mods := policyStore.List()
for _, p := range parsed {
mods[p.id] = p.mod
}
c := ast.NewCompiler()
if c.Compile(mods); c.Failed() {
// TODO(tsandall): add another call on compiler to flatten into error type
return c.Errors[0]
}
for id := range parsed {
mod := c.Modules[id]
if err := policyStore.Add(id, mod, parsed[id].raw, false); err != nil {
return err
}
}
return nil
}
type parsedModule struct {
id string
mod *ast.Module
raw []byte
}
type parsedInput struct {
docs []map[string]interface{}
modules map[string]*parsedModule
}
func parseInputs(paths []string) (*parsedInput, error) {
parsedDocs := []map[string]interface{}{}
parsedModules := map[string]*parsedModule{}
for _, file := range paths {
info, err := os.Stat(file)
if err != nil {
return nil, err
}
if info.IsDir() {
continue
}
bs, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
m, astErr := ast.ParseModule(file, string(bs))
if astErr == nil {
parsedModules[file] = &parsedModule{
id: file,
mod: m,
raw: bs,
}
continue
}
d, jsonErr := parseJSONObjectFile(file)
if jsonErr == nil {
parsedDocs = append(parsedDocs, d)
continue
}
switch filepath.Ext(file) {
case ".json":
return nil, jsonErr
case ".rego":
return nil, astErr
default:
return nil, fmt.Errorf("unrecognizable file: %v", file)
}
}
r := &parsedInput{
docs: parsedDocs,
modules: parsedModules,
}
return r, nil
}
func parseJSONObjectFile(file string) (map[string]interface{}, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()
reader := json.NewDecoder(f)
var data map[string]interface{}
if err := reader.Decode(&data); err != nil {
return nil, err
}
return data, nil
}