Skip to content

Commit

Permalink
ast+cmd+rego: Adding --rego-v1 flag to opa eval
Browse files Browse the repository at this point in the history
Fixes: open-policy-agent#6463
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
  • Loading branch information
johanfylling committed Dec 11, 2023
1 parent 4eb95c5 commit 51fcc08
Show file tree
Hide file tree
Showing 13 changed files with 420 additions and 39 deletions.
54 changes: 42 additions & 12 deletions ast/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"encoding/json"
"fmt"
"io"
"maps"
"math/big"
"net/url"
"regexp"
Expand All @@ -27,6 +28,14 @@ import (

var RegoV1CompatibleRef = Ref{VarTerm("rego"), StringTerm("v1")}

type RegoVersion int

const (
RegoV0 RegoVersion = iota
RegoV0CompatV1
RegoV1
)

// Note: This state is kept isolated from the parser so that we
// can do efficient shallow copies of these values when doing a
// save() and restore().
Expand Down Expand Up @@ -105,8 +114,8 @@ type ParserOptions struct {
FutureKeywords []string
SkipRules bool
JSONOptions *astJSON.Options
RegoVersion RegoVersion
unreleasedKeywords bool // TODO(sr): cleanup
RegoV1Compatible bool
}

// NewParser creates and initializes a Parser.
Expand Down Expand Up @@ -189,6 +198,11 @@ func (p *Parser) WithJSONOptions(jsonOptions *astJSON.Options) *Parser {
return p
}

func (p *Parser) WithRegoVersion(version RegoVersion) *Parser {
p.po.RegoVersion = version
return p
}

func (p *Parser) parsedTermCacheLookup() (*Term, *state) {
l := p.s.loc.Offset
// stop comparing once the cached offsets are lower than l
Expand Down Expand Up @@ -257,16 +271,21 @@ func (p *Parser) Parse() ([]Statement, []*Comment, Errors) {

allowedFutureKeywords := map[string]tokens.Token{}

for _, kw := range p.po.Capabilities.FutureKeywords {
var ok bool
allowedFutureKeywords[kw], ok = futureKeywords[kw]
if !ok {
return nil, nil, Errors{
&Error{
Code: ParseErr,
Message: fmt.Sprintf("illegal capabilities: unknown keyword: %v", kw),
Location: nil,
},
if p.po.RegoVersion == RegoV1 {
// RegoV1 includes all future keywords in the default language definition
allowedFutureKeywords = maps.Clone(futureKeywords)
} else {
for _, kw := range p.po.Capabilities.FutureKeywords {
var ok bool
allowedFutureKeywords[kw], ok = futureKeywords[kw]
if !ok {
return nil, nil, Errors{
&Error{
Code: ParseErr,
Message: fmt.Sprintf("illegal capabilities: unknown keyword: %v", kw),
Location: nil,
},
}
}
}
}
Expand All @@ -284,7 +303,7 @@ func (p *Parser) Parse() ([]Statement, []*Comment, Errors) {
}

selected := map[string]tokens.Token{}
if p.po.AllFutureKeywords {
if p.po.AllFutureKeywords || p.po.RegoVersion == RegoV1 {
for kw, tok := range allowedFutureKeywords {
selected[kw] = tok
}
Expand All @@ -305,6 +324,12 @@ func (p *Parser) Parse() ([]Statement, []*Comment, Errors) {
}
p.s.s = p.s.s.WithKeywords(selected)

if p.po.RegoVersion == RegoV1 {
for kw, tok := range allowedFutureKeywords {
p.s.s.AddKeyword(kw, tok)
}
}

// read the first token to initialize the parser
p.scan()

Expand Down Expand Up @@ -2567,6 +2592,11 @@ func (p *Parser) regoV1Import(imp *Import) {
return
}

if p.po.RegoVersion == RegoV1 {
// We're parsing for Rego v1, where the 'rego.v1' import is a no-op.
return
}

path := imp.Path.Value.(Ref)

if len(path) == 1 || !path[1].Equal(RegoV1CompatibleRef[1]) || len(path) > 2 {
Expand Down
7 changes: 4 additions & 3 deletions ast/parser_ext.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ func ParseModuleWithOpts(filename, input string, popts ParserOptions) (*Module,
if err != nil {
return nil, err
}
return parseModule(filename, stmts, comments, popts.RegoV1Compatible)
return parseModule(filename, stmts, comments, popts.RegoVersion)
}

// ParseBody returns exactly one body.
Expand Down Expand Up @@ -626,6 +626,7 @@ func ParseStatementsWithOpts(filename, input string, popts ParserOptions) ([]Sta
WithCapabilities(popts.Capabilities).
WithSkipRules(popts.SkipRules).
WithJSONOptions(popts.JSONOptions).
WithRegoVersion(popts.RegoVersion).
withUnreleasedKeywords(popts.unreleasedKeywords)

stmts, comments, errs := parser.Parse()
Expand All @@ -637,7 +638,7 @@ func ParseStatementsWithOpts(filename, input string, popts ParserOptions) ([]Sta
return stmts, comments, nil
}

func parseModule(filename string, stmts []Statement, comments []*Comment, regoV1Compatible bool) (*Module, error) {
func parseModule(filename string, stmts []Statement, comments []*Comment, regoCompatibilityMode RegoVersion) (*Module, error) {

if len(stmts) == 0 {
return nil, NewError(ParseErr, &Location{File: filename}, "empty module")
Expand All @@ -658,7 +659,7 @@ func parseModule(filename string, stmts []Statement, comments []*Comment, regoV1

// The comments slice only holds comments that were not their own statements.
mod.Comments = append(mod.Comments, comments...)
mod.regoV1Compatible = regoV1Compatible
mod.regoV1Compatible = regoCompatibilityMode == RegoV1 || regoCompatibilityMode == RegoV0CompatV1

for i, stmt := range stmts[1:] {
switch stmt := stmt.(type) {
Expand Down
7 changes: 7 additions & 0 deletions bundle/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ type Reader struct {
lazyLoadingMode bool
name string
persist bool
regoVersion ast.RegoVersion
}

// NewReader is deprecated. Use NewCustomReader instead.
Expand Down Expand Up @@ -503,11 +504,17 @@ func (r *Reader) WithBundlePersistence(persist bool) *Reader {
return r
}

func (r *Reader) WithRegoVersion(version ast.RegoVersion) *Reader {
r.regoVersion = version
return r
}

func (r *Reader) ParserOptions() ast.ParserOptions {
return ast.ParserOptions{
ProcessAnnotation: r.processAnnotations,
Capabilities: r.capabilities,
JSONOptions: r.jsonOptions,
RegoVersion: r.regoVersion,
}
}

Expand Down
22 changes: 12 additions & 10 deletions bundle/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ type ActivateOpts struct {
Bundles map[string]*Bundle // Optional
ExtraModules map[string]*ast.Module // Optional
AuthorizationDecisionRef ast.Ref
ParserOptions ast.ParserOptions

legacy bool
}
Expand All @@ -314,10 +315,11 @@ func Activate(opts *ActivateOpts) error {

// DeactivateOpts defines options for the Deactivate API call
type DeactivateOpts struct {
Ctx context.Context
Store storage.Store
Txn storage.Transaction
BundleNames map[string]struct{}
Ctx context.Context
Store storage.Store
Txn storage.Transaction
BundleNames map[string]struct{}
ParserOptions ast.ParserOptions
}

// Deactivate the bundle(s). This will erase associated data, policies, and the manifest entry from the store.
Expand All @@ -332,7 +334,7 @@ func Deactivate(opts *DeactivateOpts) error {
erase[root] = struct{}{}
}
}
_, err := eraseBundles(opts.Ctx, opts.Store, opts.Txn, opts.BundleNames, erase)
_, err := eraseBundles(opts.Ctx, opts.Store, opts.Txn, opts.ParserOptions, opts.BundleNames, erase)
return err
}

Expand Down Expand Up @@ -382,7 +384,7 @@ func activateBundles(opts *ActivateOpts) error {

// Erase data and policies at new + old roots, and remove the old
// manifests before activating a new snapshot bundle.
remaining, err := eraseBundles(opts.Ctx, opts.Store, opts.Txn, names, erase)
remaining, err := eraseBundles(opts.Ctx, opts.Store, opts.Txn, opts.ParserOptions, names, erase)
if err != nil {
return err
}
Expand Down Expand Up @@ -585,13 +587,13 @@ func activateDeltaBundles(opts *ActivateOpts, bundles map[string]*Bundle) error

// erase bundles by name and roots. This will clear all policies and data at its roots and remove its
// manifest from storage.
func eraseBundles(ctx context.Context, store storage.Store, txn storage.Transaction, names map[string]struct{}, roots map[string]struct{}) (map[string]*ast.Module, error) {
func eraseBundles(ctx context.Context, store storage.Store, txn storage.Transaction, parserOpts ast.ParserOptions, names map[string]struct{}, roots map[string]struct{}) (map[string]*ast.Module, error) {

if err := eraseData(ctx, store, txn, roots); err != nil {
return nil, err
}

remaining, err := erasePolicies(ctx, store, txn, roots)
remaining, err := erasePolicies(ctx, store, txn, parserOpts, roots)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -633,7 +635,7 @@ func eraseData(ctx context.Context, store storage.Store, txn storage.Transaction
return nil
}

func erasePolicies(ctx context.Context, store storage.Store, txn storage.Transaction, roots map[string]struct{}) (map[string]*ast.Module, error) {
func erasePolicies(ctx context.Context, store storage.Store, txn storage.Transaction, parserOpts ast.ParserOptions, roots map[string]struct{}) (map[string]*ast.Module, error) {

ids, err := store.ListPolicies(ctx, txn)
if err != nil {
Expand All @@ -647,7 +649,7 @@ func erasePolicies(ctx context.Context, store storage.Store, txn storage.Transac
if err != nil {
return nil, err
}
module, err := ast.ParseModule(id, string(bs))
module, err := ast.ParseModuleWithOpts(id, string(bs), parserOpts)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion bundle/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4294,7 +4294,7 @@ func TestErasePolicies(t *testing.T) {
for _, root := range tc.roots {
roots[root] = struct{}{}
}
remaining, err := erasePolicies(ctx, mockStore, txn, roots)
remaining, err := erasePolicies(ctx, mockStore, txn, ast.ParserOptions{}, roots)
if !tc.expectErr && err != nil {
t.Fatalf("unepected error: %s", err)
} else if tc.expectErr && err == nil {
Expand Down
3 changes: 2 additions & 1 deletion cmd/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ func init() {
addCapabilitiesFlag(checkCommand.Flags(), checkParams.capabilities)
addSchemaFlags(checkCommand.Flags(), checkParams.schema)
addStrictFlag(checkCommand.Flags(), &checkParams.strict, false)
checkCommand.Flags().BoolVar(&checkParams.regoV1, "rego-v1", false, "check for Rego v1 compatibility (policies must also be compatible with current OPA version)")
addRegoV1FlagWithDescription(checkCommand.Flags(), &checkParams.regoV1, false,
"check for Rego v1 compatibility (policies must also be compatible with current OPA version)")
RootCommand.AddCommand(checkCommand)
}
6 changes: 6 additions & 0 deletions cmd/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type evalCommandParams struct {
optimizationLevel int
entrypoints repeatedStringFlag
strict bool
regoV1 bool
}

func newEvalCommandParams() evalCommandParams {
Expand Down Expand Up @@ -326,6 +327,7 @@ access.
addTargetFlag(evalCommand.Flags(), params.target)
addCountFlag(evalCommand.Flags(), &params.count, "benchmark")
addStrictFlag(evalCommand.Flags(), &params.strict, false)
addRegoV1Flag(evalCommand.Flags(), &params.regoV1, false)

RootCommand.AddCommand(evalCommand)
}
Expand Down Expand Up @@ -663,6 +665,10 @@ func setupEval(args []string, params evalCommandParams) (*evalContext, error) {
regoArgs = append(regoArgs, rego.Strict(params.strict))
}

if params.regoV1 {
regoArgs = append(regoArgs, rego.RegoVersion(ast.RegoV1))
}

evalCtx := &evalContext{
params: params,
metrics: m,
Expand Down
Loading

0 comments on commit 51fcc08

Please sign in to comment.