Add deps subcommand

These changes add a new subcommand that analyzes policies and prints
base and virtual document dependencies.

Signed-off-by: Torin Sandall <torinsandall@gmail.com>
This commit is contained in:
Torin Sandall
2018-08-20 16:12:06 -07:00
parent c70b67977d
commit a400fbab78
2 changed files with 188 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
// Copyright 2018 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 cmd
import (
"fmt"
"os"
"github.com/open-policy-agent/opa/dependencies"
"github.com/open-policy-agent/opa/internal/presentation"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/loader"
"github.com/open-policy-agent/opa/util"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
type depsCommandParams struct {
dataPaths repeatedStringFlag
format *util.EnumFlag
ignore []string
}
const (
depsFormatPretty = "pretty"
depsFormatJSON = "json"
)
func init() {
var params depsCommandParams
params.format = util.NewEnumFlag(depsFormatPretty, []string{
depsFormatPretty, depsFormatJSON,
})
depsCommand := &cobra.Command{
Use: "deps <query>",
Short: "Analyze Rego query dependencies",
PreRunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("specify exactly one query argument")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
if err := deps(args, params); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
},
}
depsCommand.Flags().VarP(params.format, "format", "f", "set output format")
depsCommand.Flags().VarP(&params.dataPaths, "data", "d", "set data file(s) or directory path(s)")
setIgnore(depsCommand.Flags(), &params.ignore)
RootCommand.AddCommand(depsCommand)
}
func deps(args []string, params depsCommandParams) error {
query, err := ast.ParseBody(args[0])
if err != nil {
return err
}
f := loaderFilter{
Ignore: params.ignore,
}
result, err := loader.Filtered(params.dataPaths.v, f.Apply)
if err != nil {
return err
}
modules := map[string]*ast.Module{}
for _, m := range result.Modules {
modules[m.Name] = m.Parsed
}
compiler := ast.NewCompiler()
compiler.Compile(modules)
if compiler.Failed() {
return compiler.Errors
}
brs, err := dependencies.Base(compiler, query)
if err != nil {
return err
}
vrs, err := dependencies.Virtual(compiler, query)
if err != nil {
return err
}
output := presentation.DepAnalysisOutput{
Base: brs,
Virtual: vrs,
}
switch params.format.String() {
case depsFormatJSON:
return presentation.JSON(os.Stdout, output)
default:
return output.Pretty(os.Stdout)
}
}
+75
View File
@@ -25,6 +25,81 @@ import (
"github.com/open-policy-agent/opa/topdown"
)
// DepAnalysisOutput contains the result of dependency analysis to be presented.
type DepAnalysisOutput struct {
Base []ast.Ref `json:"base,omitempty"`
Virtual []ast.Ref `json:"virtual,omitempty"`
}
// JSON outputs o to w as JSON.
func (o DepAnalysisOutput) JSON(w io.Writer) error {
o.sort()
return JSON(w, o)
}
// Pretty outputs o to w in a human-readable format.
func (o DepAnalysisOutput) Pretty(w io.Writer) error {
var headers []string
var rows [][]string
// Fill two columns if results have base and virtual docs. Else fill one column.
if len(o.Base) > 0 && len(o.Virtual) > 0 {
maxLen := len(o.Base)
if len(o.Virtual) > maxLen {
maxLen = len(o.Virtual)
}
headers = []string{"Base Documents", "Virtual Documents"}
rows = make([][]string, maxLen)
for i := range rows {
rows[i] = make([]string, 2)
if i < len(o.Base) {
rows[i][0] = o.Base[i].String()
}
if i < len(o.Virtual) {
rows[i][1] = o.Virtual[i].String()
}
}
} else if len(o.Base) > 0 {
headers = []string{"Base Documents"}
rows = make([][]string, len(o.Base))
for i := range rows {
rows[i] = []string{o.Base[i].String()}
}
} else if len(o.Virtual) > 0 {
headers = []string{"Virtual Documents"}
rows = make([][]string, len(o.Base))
for i := range rows {
rows[i] = []string{o.Virtual[i].String()}
}
}
if len(rows) == 0 {
return nil
}
table := tablewriter.NewWriter(w)
table.SetHeader(headers)
table.SetAutoWrapText(false)
for i := range rows {
table.Append(rows[i])
}
table.Render()
return nil
}
func (o DepAnalysisOutput) sort() {
sort.Slice(o.Base, func(i, j int) bool {
return o.Base[i].Compare(o.Base[j]) < 0
})
sort.Slice(o.Virtual, func(i, j int) bool {
return o.Virtual[i].Compare(o.Virtual[j]) < 0
})
}
// Output contains the result of evaluation to be presented.
type Output struct {
Error error `json:"error,omitempty"`