mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-13 03:42:35 -06:00
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:
+113
@@ -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(¶ms.dataPaths, "data", "d", "set data file(s) or directory path(s)")
|
||||
setIgnore(depsCommand.Flags(), ¶ms.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)
|
||||
}
|
||||
}
|
||||
@@ -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"`
|
||||
|
||||
Reference in New Issue
Block a user