mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
Add a --format=raw option for eval (#3207)
This is a very simple addition but it's proven extremely useful to us since it means you can now use `opa` easily in bash scripts, e.g.: ```bash ACCOUNT_ID="$(opa eval -d accounts.rego --format raw "data.accounts.account")" ``` This means `accounts.rego` can be used by policies as well as e.g. deploy scripts. The naming and functionality is inspired by the `-r` flag from `jq`. In fact, `jq` can be used to replicate this behaviour, but it is a bit nicer to not rely on that system dependency. When multiple queries and expressions are given, they are printed in a simple table format using single newlines and spaces since that is consistent and plays nice with `bash`. Signed-off-by: Jasper Van der Jeugt <jasper@fugue.co>
This commit is contained in:
committed by
GitHub
parent
0557c5f4d4
commit
ef9814c9ab
@@ -71,6 +71,7 @@ func newEvalCommandParams() evalCommandParams {
|
||||
evalBindingsOutput,
|
||||
evalPrettyOutput,
|
||||
evalSourceOutput,
|
||||
evalRawOutput,
|
||||
}),
|
||||
explain: newExplainFlag([]string{explainModeOff, explainModeFull, explainModeNotes, explainModeFails}),
|
||||
target: util.NewEnumFlag(compile.TargetRego, []string{compile.TargetRego, compile.TargetWasm}),
|
||||
@@ -118,6 +119,7 @@ const (
|
||||
evalBindingsOutput = "bindings"
|
||||
evalPrettyOutput = "pretty"
|
||||
evalSourceOutput = "source"
|
||||
evalRawOutput = "raw"
|
||||
|
||||
// number of profile results to return by default
|
||||
defaultProfileLimit = 10
|
||||
@@ -335,6 +337,8 @@ func eval(args []string, params evalCommandParams, w io.Writer) (bool, error) {
|
||||
err = pr.Pretty(w, result)
|
||||
case evalSourceOutput:
|
||||
err = pr.Source(w, result)
|
||||
case evalRawOutput:
|
||||
err = pr.Raw(w, result)
|
||||
default:
|
||||
err = pr.JSON(w, result)
|
||||
}
|
||||
|
||||
@@ -343,6 +343,39 @@ func Source(w io.Writer, r Output) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Raw prints the values from r to w. Each result is written on a separate
|
||||
// line, and the expressions are separated by spaces. If the values are
|
||||
// strings, they are written directly rather than formatted as compact
|
||||
// JSON strings. This output format makes OPA useful in a scripting context.
|
||||
func Raw(w io.Writer, r Output) error {
|
||||
if r.Errors != nil {
|
||||
return prettyError(w, r.Errors)
|
||||
}
|
||||
|
||||
for _, rs := range r.Result {
|
||||
for i, expr := range rs.Expressions {
|
||||
if str, ok := expr.Value.(string); ok {
|
||||
fmt.Fprint(w, str)
|
||||
} else {
|
||||
bytes, err := json.Marshal(expr.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprint(w, string(bytes))
|
||||
}
|
||||
|
||||
if i+1 >= len(rs.Expressions) {
|
||||
fmt.Fprintln(w, "")
|
||||
} else {
|
||||
fmt.Fprint(w, " ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func prettyError(w io.Writer, err error) error {
|
||||
_, err = fmt.Fprintln(w, err)
|
||||
return err
|
||||
|
||||
@@ -388,3 +388,78 @@ p = 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRaw(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
output Output
|
||||
want string
|
||||
}{
|
||||
{
|
||||
note: "simple single string",
|
||||
output: Output{
|
||||
Result: []rego.Result{
|
||||
{
|
||||
Expressions: []*rego.ExpressionValue{
|
||||
{Value: "Hello world"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "Hello world\n",
|
||||
},
|
||||
{
|
||||
note: "table format",
|
||||
output: Output{
|
||||
Result: []rego.Result{
|
||||
{
|
||||
Expressions: []*rego.ExpressionValue{
|
||||
{Value: "one"},
|
||||
{Value: 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
Expressions: []*rego.ExpressionValue{
|
||||
{Value: "two"},
|
||||
{Value: 2},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "one 1\ntwo 2\n",
|
||||
},
|
||||
{
|
||||
note: "compound values",
|
||||
output: Output{
|
||||
Result: []rego.Result{
|
||||
{
|
||||
Expressions: []*rego.ExpressionValue{
|
||||
{Value: []interface{}{"one"}},
|
||||
{Value: map[string]interface{}{
|
||||
"key": []interface{}{},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "[\"one\"] {\"key\":[]}\n",
|
||||
},
|
||||
{
|
||||
note: "error",
|
||||
output: Output{
|
||||
Errors: NewOutputErrors(fmt.Errorf("boom")),
|
||||
},
|
||||
want: "1 error occurred: boom\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.note, func(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
Raw(buf, tc.output)
|
||||
if buf.String() != tc.want {
|
||||
t.Fatalf("Expected:\n\n%v\n\nGot:\n\n%v", tc.want, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user