mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
Add rego_version attribute to bundle manifest (#6579)
Adding a global `rego_version` attribute to bundle manifest, to inform OPA runtime about what rego-version (v0/v1) to use to parse/compile contained Rego files. The rego-version of individual Rego files can be overridden through the `file_rego_versions` manifest attribute. Implements: #6578 Signed-off-by: Johan Fylling <johan.dev@fylling.se>
This commit is contained in:
@@ -43,6 +43,20 @@ const (
|
||||
RegoV1
|
||||
)
|
||||
|
||||
func (v RegoVersion) Int() int {
|
||||
if v == RegoV1 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func RegoVersionFromInt(i int) RegoVersion {
|
||||
if i == 1 {
|
||||
return RegoV1
|
||||
}
|
||||
return RegoV0
|
||||
}
|
||||
|
||||
// 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().
|
||||
|
||||
+225
-23
@@ -15,10 +15,12 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
astJSON "github.com/open-policy-agent/opa/ast/json"
|
||||
"github.com/open-policy-agent/opa/format"
|
||||
@@ -120,10 +122,28 @@ func NewFile(name, hash, alg string) FileInfo {
|
||||
// Manifest represents the manifest from a bundle. The manifest may contain
|
||||
// metadata such as the bundle revision.
|
||||
type Manifest struct {
|
||||
Revision string `json:"revision"`
|
||||
Roots *[]string `json:"roots,omitempty"`
|
||||
WasmResolvers []WasmResolver `json:"wasm,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
Revision string `json:"revision"`
|
||||
Roots *[]string `json:"roots,omitempty"`
|
||||
WasmResolvers []WasmResolver `json:"wasm,omitempty"`
|
||||
// RegoVersion is the global Rego version for the bundle described by this Manifest.
|
||||
// The Rego version of individual files can be overridden in FileRegoVersions.
|
||||
// We don't use ast.RegoVersion here, as this iota type's order isn't guaranteed to be stable over time.
|
||||
// We use a pointer so that we can support hand-made bundles that don't have an explicit version appropriately.
|
||||
// E.g. in OPA 0.x if --v1-compatible is used when consuming the bundle, and there is no specified version,
|
||||
// we should default to v1; if --v1-compatible isn't used, we should default to v0. In OPA 1.0, no --x-compatible
|
||||
// flag and no explicit bundle version should default to v1.
|
||||
RegoVersion *int `json:"rego_version,omitempty"`
|
||||
// FileRegoVersions is a map from file paths to Rego versions.
|
||||
// This allows individual files to override the global Rego version specified by RegoVersion.
|
||||
FileRegoVersions map[string]int `json:"file_rego_versions,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
|
||||
compiledFileRegoVersions []fileRegoVersion
|
||||
}
|
||||
|
||||
type fileRegoVersion struct {
|
||||
path glob.Glob
|
||||
version int
|
||||
}
|
||||
|
||||
// WasmResolver maps a wasm module to an entrypoint ref.
|
||||
@@ -150,6 +170,15 @@ func (m *Manifest) AddRoot(r string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manifest) SetRegoVersion(v ast.RegoVersion) {
|
||||
m.Init()
|
||||
regoVersion := 0
|
||||
if v == ast.RegoV1 {
|
||||
regoVersion = 1
|
||||
}
|
||||
m.RegoVersion = ®oVersion
|
||||
}
|
||||
|
||||
// Equal returns true if m is semantically equivalent to other.
|
||||
func (m Manifest) Equal(other Manifest) bool {
|
||||
|
||||
@@ -161,6 +190,19 @@ func (m Manifest) Equal(other Manifest) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if m.RegoVersion == nil && other.RegoVersion != nil {
|
||||
return false
|
||||
}
|
||||
if m.RegoVersion != nil && other.RegoVersion == nil {
|
||||
return false
|
||||
}
|
||||
if m.RegoVersion != nil && other.RegoVersion != nil && *m.RegoVersion != *other.RegoVersion {
|
||||
return false
|
||||
}
|
||||
if !reflect.DeepEqual(m.FileRegoVersions, other.FileRegoVersions) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(m.Metadata, other.Metadata) {
|
||||
return false
|
||||
}
|
||||
@@ -197,7 +239,12 @@ func (m Manifest) Copy() Manifest {
|
||||
|
||||
func (m Manifest) String() string {
|
||||
m.Init()
|
||||
return fmt.Sprintf("<revision: %q, roots: %v, wasm: %+v, metadata: %+v>", m.Revision, *m.Roots, m.WasmResolvers, m.Metadata)
|
||||
if m.RegoVersion != nil {
|
||||
return fmt.Sprintf("<revision: %q, rego_version: %d, roots: %v, wasm: %+v, metadata: %+v>",
|
||||
m.Revision, *m.RegoVersion, *m.Roots, m.WasmResolvers, m.Metadata)
|
||||
}
|
||||
return fmt.Sprintf("<revision: %q, roots: %v, wasm: %+v, metadata: %+v>",
|
||||
m.Revision, *m.Roots, m.WasmResolvers, m.Metadata)
|
||||
}
|
||||
|
||||
func (m Manifest) rootSet() stringSet {
|
||||
@@ -358,10 +405,11 @@ func (m *Manifest) validateAndInjectDefaults(b Bundle) error {
|
||||
|
||||
// ModuleFile represents a single module contained in a bundle.
|
||||
type ModuleFile struct {
|
||||
URL string
|
||||
Path string
|
||||
Raw []byte
|
||||
Parsed *ast.Module
|
||||
URL string
|
||||
Path string
|
||||
RelativePath string
|
||||
Raw []byte
|
||||
Parsed *ast.Module
|
||||
}
|
||||
|
||||
// WasmModuleFile represents a single wasm module contained in a bundle.
|
||||
@@ -543,6 +591,7 @@ func (r *Reader) Read() (Bundle, error) {
|
||||
bundle.Data = map[string]interface{}{}
|
||||
}
|
||||
|
||||
var modules []ModuleFile
|
||||
for _, f := range descriptors {
|
||||
buf, err := readFile(f, r.sizeLimitBytes)
|
||||
if err != nil {
|
||||
@@ -583,20 +632,14 @@ func (r *Reader) Read() (Bundle, error) {
|
||||
raw = append(raw, Raw{Path: p, Value: bs})
|
||||
}
|
||||
|
||||
r.metrics.Timer(metrics.RegoModuleParse).Start()
|
||||
module, err := ast.ParseModuleWithOpts(fullPath, buf.String(), r.ParserOptions())
|
||||
r.metrics.Timer(metrics.RegoModuleParse).Stop()
|
||||
if err != nil {
|
||||
return bundle, err
|
||||
}
|
||||
|
||||
// Modules are parsed after we've had a chance to read the manifest
|
||||
mf := ModuleFile{
|
||||
URL: f.URL(),
|
||||
Path: fullPath,
|
||||
Raw: bs,
|
||||
Parsed: module,
|
||||
URL: f.URL(),
|
||||
Path: fullPath,
|
||||
RelativePath: path,
|
||||
Raw: bs,
|
||||
}
|
||||
bundle.Modules = append(bundle.Modules, mf)
|
||||
modules = append(modules, mf)
|
||||
} else if filepath.Base(path) == WasmFile {
|
||||
bundle.WasmModules = append(bundle.WasmModules, WasmModuleFile{
|
||||
URL: f.URL(),
|
||||
@@ -656,6 +699,23 @@ func (r *Reader) Read() (Bundle, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Parse modules
|
||||
popts := r.ParserOptions()
|
||||
popts.RegoVersion = bundle.RegoVersion(popts.RegoVersion)
|
||||
for _, mf := range modules {
|
||||
modulePopts := popts
|
||||
if modulePopts.RegoVersion, err = bundle.RegoVersionForFile(mf.RelativePath, popts.RegoVersion); err != nil {
|
||||
return bundle, err
|
||||
}
|
||||
r.metrics.Timer(metrics.RegoModuleParse).Start()
|
||||
mf.Parsed, err = ast.ParseModuleWithOpts(mf.Path, string(mf.Raw), modulePopts)
|
||||
r.metrics.Timer(metrics.RegoModuleParse).Stop()
|
||||
if err != nil {
|
||||
return bundle, err
|
||||
}
|
||||
bundle.Modules = append(bundle.Modules, mf)
|
||||
}
|
||||
|
||||
if bundle.Type() == DeltaBundleType {
|
||||
if len(bundle.Data) != 0 {
|
||||
return bundle, fmt.Errorf("delta bundle expected to contain only patch file but data files found")
|
||||
@@ -1012,7 +1072,7 @@ func hashBundleFiles(hash SignatureHasher, b *Bundle) ([]FileInfo, error) {
|
||||
}
|
||||
|
||||
// FormatModules formats Rego modules
|
||||
// Modules will be formatted to comply with rego-v1, but Rego compatibility of individual parsed modules will be respected (e.g. if 'rego.v1' is imported).
|
||||
// Modules will be formatted to comply with rego-v0, but Rego compatibility of individual parsed modules will be respected (e.g. if 'rego.v1' is imported).
|
||||
func (b *Bundle) FormatModules(useModulePath bool) error {
|
||||
return b.FormatModulesForRegoVersion(ast.RegoV0, true, useModulePath)
|
||||
}
|
||||
@@ -1111,6 +1171,65 @@ func (b *Bundle) ParsedModules(bundleName string) map[string]*ast.Module {
|
||||
return mods
|
||||
}
|
||||
|
||||
func (b *Bundle) RegoVersion(def ast.RegoVersion) ast.RegoVersion {
|
||||
if v := b.Manifest.RegoVersion; v != nil {
|
||||
if *v == 0 {
|
||||
return ast.RegoV0
|
||||
} else if *v == 1 {
|
||||
return ast.RegoV1
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func (b *Bundle) SetRegoVersion(v ast.RegoVersion) {
|
||||
b.Manifest.SetRegoVersion(v)
|
||||
}
|
||||
|
||||
// RegoVersionForFile returns the rego-version for the specified file path.
|
||||
// If there is no defined version for the given path, the default version def is returned.
|
||||
// If the version does not correspond to ast.RegoV0 or ast.RegoV1, an error is returned.
|
||||
func (b *Bundle) RegoVersionForFile(path string, def ast.RegoVersion) (ast.RegoVersion, error) {
|
||||
if version, err := b.Manifest.numericRegoVersionForFile(path); err != nil {
|
||||
return def, err
|
||||
} else if version == nil {
|
||||
return def, nil
|
||||
} else if *version == 0 {
|
||||
return ast.RegoV0, nil
|
||||
} else if *version == 1 {
|
||||
return ast.RegoV1, nil
|
||||
} else {
|
||||
return def, fmt.Errorf("unknown bundle rego-version %d for file '%s'", *version, path)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manifest) numericRegoVersionForFile(path string) (*int, error) {
|
||||
var version *int
|
||||
|
||||
if len(m.FileRegoVersions) != len(m.compiledFileRegoVersions) {
|
||||
m.compiledFileRegoVersions = make([]fileRegoVersion, 0, len(m.FileRegoVersions))
|
||||
for pattern, v := range m.FileRegoVersions {
|
||||
compiled, err := glob.Compile(pattern)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compile glob pattern %s: %s", pattern, err)
|
||||
}
|
||||
m.compiledFileRegoVersions = append(m.compiledFileRegoVersions, fileRegoVersion{compiled, v})
|
||||
}
|
||||
}
|
||||
|
||||
for _, fv := range m.compiledFileRegoVersions {
|
||||
if fv.path.Match(path) {
|
||||
version = &fv.version
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if version == nil {
|
||||
version = m.RegoVersion
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
// Equal returns true if this bundle's contents equal the other bundle's
|
||||
// contents.
|
||||
func (b Bundle) Equal(other Bundle) bool {
|
||||
@@ -1261,13 +1380,33 @@ func mktree(path []string, value interface{}) (map[string]interface{}, error) {
|
||||
// will have an empty revision except in the special case where a single bundle is provided
|
||||
// (and in that case the bundle is just returned unmodified.)
|
||||
func Merge(bundles []*Bundle) (*Bundle, error) {
|
||||
return MergeWithRegoVersion(bundles, ast.RegoV0, false)
|
||||
}
|
||||
|
||||
// MergeWithRegoVersion creates a merged bundle from the provided bundles, similar to Merge.
|
||||
// If more than one bundle is provided, the rego version of the result bundle is set to the provided regoVersion.
|
||||
// Any Rego files in a bundle of conflicting rego version will be marked in the result's manifest with the rego version
|
||||
// of its original bundle. If the Rego file already had an overriding rego version, it will be preserved.
|
||||
// If a single bundle is provided, it will retain any rego version information it already had. If it has none, the
|
||||
// provided regoVersion will be applied to it.
|
||||
// If usePath is true, per-file rego-versions will be calculated using the file's ModuleFile.Path; otherwise, the file's
|
||||
// ModuleFile.URL will be used.
|
||||
func MergeWithRegoVersion(bundles []*Bundle, regoVersion ast.RegoVersion, usePath bool) (*Bundle, error) {
|
||||
|
||||
if len(bundles) == 0 {
|
||||
return nil, errors.New("expected at least one bundle")
|
||||
}
|
||||
|
||||
if len(bundles) == 1 {
|
||||
return bundles[0], nil
|
||||
result := bundles[0]
|
||||
// We respect the bundle rego-version, defaulting to the provided rego version if not set.
|
||||
result.SetRegoVersion(result.RegoVersion(regoVersion))
|
||||
fileRegoVersions, err := bundleRegoVersions(result, result.RegoVersion(regoVersion), usePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.Manifest.FileRegoVersions = fileRegoVersions
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var roots []string
|
||||
@@ -1296,8 +1435,24 @@ func Merge(bundles []*Bundle) (*Bundle, error) {
|
||||
result.WasmModules = append(result.WasmModules, b.WasmModules...)
|
||||
result.PlanModules = append(result.PlanModules, b.PlanModules...)
|
||||
|
||||
if b.Manifest.RegoVersion != nil || len(b.Manifest.FileRegoVersions) > 0 {
|
||||
if result.Manifest.FileRegoVersions == nil {
|
||||
result.Manifest.FileRegoVersions = map[string]int{}
|
||||
}
|
||||
|
||||
fileRegoVersions, err := bundleRegoVersions(b, regoVersion, usePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range fileRegoVersions {
|
||||
result.Manifest.FileRegoVersions[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We respect the bundle rego-version, defaulting to the provided rego version if not set.
|
||||
result.SetRegoVersion(result.RegoVersion(regoVersion))
|
||||
|
||||
if result.Data == nil {
|
||||
result.Data = map[string]interface{}{}
|
||||
}
|
||||
@@ -1311,6 +1466,53 @@ func Merge(bundles []*Bundle) (*Bundle, error) {
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func bundleRegoVersions(bundle *Bundle, regoVersion ast.RegoVersion, usePath bool) (map[string]int, error) {
|
||||
fileRegoVersions := map[string]int{}
|
||||
|
||||
// we drop the bundle-global rego versions and record individual rego versions for each module.
|
||||
for _, m := range bundle.Modules {
|
||||
// We fetch rego-version by the path relative to the bundle root, as the complete path of the module might
|
||||
// contain the path between OPA working directory and the bundle root.
|
||||
v, err := bundle.RegoVersionForFile(bundleRelativePath(m, usePath), bundle.RegoVersion(regoVersion))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// only record the rego version if it's different from one applied globally to the result bundle
|
||||
if v != regoVersion {
|
||||
// We store the rego version by the absolute path to the bundle root, as this will be the - possibly new - path
|
||||
// to the module inside the merged bundle.
|
||||
fileRegoVersions[bundleAbsolutePath(m, usePath)] = v.Int()
|
||||
}
|
||||
}
|
||||
|
||||
return fileRegoVersions, nil
|
||||
}
|
||||
|
||||
func bundleRelativePath(m ModuleFile, usePath bool) string {
|
||||
p := m.RelativePath
|
||||
if p == "" {
|
||||
if usePath {
|
||||
p = m.Path
|
||||
} else {
|
||||
p = m.URL
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func bundleAbsolutePath(m ModuleFile, usePath bool) string {
|
||||
var p string
|
||||
if usePath {
|
||||
p = m.Path
|
||||
} else {
|
||||
p = m.URL
|
||||
}
|
||||
if !path.IsAbs(p) {
|
||||
p = "/" + p
|
||||
}
|
||||
return path.Clean(p)
|
||||
}
|
||||
|
||||
// RootPathsOverlap takes in two bundle root paths and returns true if they overlap.
|
||||
func RootPathsOverlap(pathA string, pathB string) bool {
|
||||
a := rootPathSegments(pathA)
|
||||
|
||||
+92
-4
@@ -80,6 +80,82 @@ func TestManifestEqual(t *testing.T) {
|
||||
"foo": "bar",
|
||||
}
|
||||
assertEqual()
|
||||
|
||||
// rego-version
|
||||
|
||||
n.RegoVersion = pointTo(1)
|
||||
assertNotEqual()
|
||||
|
||||
m.RegoVersion = pointTo(0)
|
||||
assertNotEqual()
|
||||
|
||||
m.RegoVersion = pointTo(1)
|
||||
assertEqual()
|
||||
|
||||
n.FileRegoVersions = map[string]int{
|
||||
"foo": 1,
|
||||
}
|
||||
assertNotEqual()
|
||||
|
||||
m.FileRegoVersions = map[string]int{
|
||||
"foo": 1,
|
||||
}
|
||||
assertEqual()
|
||||
|
||||
n.FileRegoVersions["*/bar"] = 0
|
||||
assertNotEqual()
|
||||
|
||||
m.FileRegoVersions["*/bar"] = 0
|
||||
assertEqual()
|
||||
}
|
||||
|
||||
func TestBundleRegoVersion(t *testing.T) {
|
||||
b := Bundle{}
|
||||
|
||||
if b.Manifest.RegoVersion != nil {
|
||||
t.Fatal("expected nil")
|
||||
}
|
||||
|
||||
// No rego-version set, expect default
|
||||
if b.RegoVersion(ast.RegoV0) != ast.RegoV0 {
|
||||
t.Fatal("expected v0")
|
||||
}
|
||||
if b.RegoVersion(ast.RegoV1) != ast.RegoV1 {
|
||||
t.Fatal("expected v1")
|
||||
}
|
||||
|
||||
// Set rego-version to v0
|
||||
b.SetRegoVersion(ast.RegoV0)
|
||||
|
||||
if b.Manifest.RegoVersion == nil || *b.Manifest.RegoVersion != 0 {
|
||||
t.Fatal("expected v0")
|
||||
}
|
||||
|
||||
if b.RegoVersion(ast.RegoV1) != ast.RegoV0 {
|
||||
t.Fatal("expected v0")
|
||||
}
|
||||
|
||||
// Set rego-version to v1
|
||||
b.SetRegoVersion(ast.RegoV1)
|
||||
|
||||
if b.Manifest.RegoVersion == nil || *b.Manifest.RegoVersion != 1 {
|
||||
t.Fatal("expected v1")
|
||||
}
|
||||
|
||||
if b.RegoVersion(ast.RegoV0) != ast.RegoV1 {
|
||||
t.Fatal("expected v1")
|
||||
}
|
||||
|
||||
// Set rego-version to v0-compat1
|
||||
b.SetRegoVersion(ast.RegoV0CompatV1)
|
||||
|
||||
if b.Manifest.RegoVersion == nil || *b.Manifest.RegoVersion != 0 {
|
||||
t.Fatal("expected v0")
|
||||
}
|
||||
|
||||
if b.RegoVersion(ast.RegoV1) != ast.RegoV0 {
|
||||
t.Fatal("expected v0")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
@@ -1227,7 +1303,7 @@ func TestWriterSkipEmptyManifest(t *testing.T) {
|
||||
}
|
||||
|
||||
if f.Name != "/data.json" {
|
||||
t.Fatal("expected only /data.json but got:", f.Name)
|
||||
t.Fatal("expected only /data.json and /.manifest but got:", f.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1633,8 +1709,10 @@ func TestMerge(t *testing.T) {
|
||||
},
|
||||
wantBundle: &Bundle{
|
||||
Manifest: Manifest{
|
||||
Revision: "abcdef",
|
||||
Roots: &[]string{""},
|
||||
Revision: "abcdef",
|
||||
Roots: &[]string{""},
|
||||
RegoVersion: pointTo(0), // Default rego-version
|
||||
FileRegoVersions: map[string]int{},
|
||||
},
|
||||
Modules: []ModuleFile{
|
||||
{
|
||||
@@ -1671,6 +1749,7 @@ func TestMerge(t *testing.T) {
|
||||
"foo",
|
||||
"bar",
|
||||
},
|
||||
RegoVersion: pointTo(0), // Default rego-version
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
},
|
||||
@@ -1715,6 +1794,7 @@ func TestMerge(t *testing.T) {
|
||||
"logs",
|
||||
"authz",
|
||||
},
|
||||
RegoVersion: pointTo(0), // Default rego-version
|
||||
},
|
||||
WasmModules: []WasmModuleFile{
|
||||
{
|
||||
@@ -1771,6 +1851,7 @@ func TestMerge(t *testing.T) {
|
||||
"foo",
|
||||
"baz",
|
||||
},
|
||||
RegoVersion: pointTo(0), // Default rego-version
|
||||
},
|
||||
Modules: []ModuleFile{
|
||||
{
|
||||
@@ -1819,6 +1900,7 @@ func TestMerge(t *testing.T) {
|
||||
"foo/bar",
|
||||
"baz",
|
||||
},
|
||||
RegoVersion: pointTo(0), // Default rego-version
|
||||
},
|
||||
Data: map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
@@ -1854,6 +1936,7 @@ func TestMerge(t *testing.T) {
|
||||
"foo/bar",
|
||||
"baz",
|
||||
},
|
||||
RegoVersion: pointTo(0), // Default rego-version
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
},
|
||||
@@ -1889,7 +1972,8 @@ func TestMerge(t *testing.T) {
|
||||
wantBundle: &Bundle{
|
||||
Data: map[string]interface{}{},
|
||||
Manifest: Manifest{
|
||||
Roots: &[]string{"a", "b"},
|
||||
Roots: &[]string{"a", "b"},
|
||||
RegoVersion: pointTo(0), // Default rego-version
|
||||
},
|
||||
PlanModules: []PlanModuleFile{
|
||||
{
|
||||
@@ -1951,3 +2035,7 @@ func TestMerge(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func pointTo[T any](x T) *T {
|
||||
return &x
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -808,6 +809,205 @@ a contains 4 if {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBenchMainWithBundleRegoVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
bundleRegoVersion int
|
||||
bundleFileRegoVersions map[string]int
|
||||
modules map[string]string
|
||||
query string
|
||||
expErrs []string
|
||||
}{
|
||||
// These tests are slow, so we're not being completely exhaustive here.
|
||||
{
|
||||
note: "v0 bundle",
|
||||
bundleRegoVersion: 0,
|
||||
modules: map[string]string{
|
||||
"test.rego": `package test
|
||||
a[4] {
|
||||
1 == 1
|
||||
}`,
|
||||
},
|
||||
query: `data.test.a`,
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, no keywords imported",
|
||||
bundleRegoVersion: 0,
|
||||
modules: map[string]string{
|
||||
"test.rego": `package test
|
||||
a contains 4 if {
|
||||
1 == 1
|
||||
}`,
|
||||
},
|
||||
query: `data.test.a`,
|
||||
expErrs: []string{
|
||||
"rego_parse_error: var cannot be used for rule name",
|
||||
"rego_parse_error: number cannot be used for rule name",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override",
|
||||
bundleRegoVersion: 0,
|
||||
bundleFileRegoVersions: map[string]int{
|
||||
"*/test2.rego": 1,
|
||||
},
|
||||
modules: map[string]string{
|
||||
"test1.rego": `package test
|
||||
a[4] {
|
||||
1 == 1
|
||||
}`,
|
||||
"test2.rego": `package test
|
||||
b contains 4 if {
|
||||
1 == 1
|
||||
}`,
|
||||
},
|
||||
query: `data.test.a`,
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, keywords not used",
|
||||
bundleRegoVersion: 1,
|
||||
modules: map[string]string{
|
||||
"test.rego": `package test
|
||||
a[4] {
|
||||
1 == 1
|
||||
}`,
|
||||
},
|
||||
query: `data.test.a`,
|
||||
expErrs: []string{
|
||||
"rego_parse_error: `if` keyword is required before rule body",
|
||||
"rego_parse_error: `contains` keyword is required for partial set rules",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1, no keywords imported",
|
||||
bundleRegoVersion: 1,
|
||||
modules: map[string]string{
|
||||
"test.rego": `package test
|
||||
a contains 4 if {
|
||||
1 == 1
|
||||
}`,
|
||||
},
|
||||
query: `data.test.a`,
|
||||
},
|
||||
}
|
||||
|
||||
bundleTypeCases := []struct {
|
||||
note string
|
||||
tar bool
|
||||
}{
|
||||
{
|
||||
"bundle dir", false,
|
||||
},
|
||||
{
|
||||
"bundle tar", true,
|
||||
},
|
||||
}
|
||||
|
||||
modes := []struct {
|
||||
name string
|
||||
e2e bool
|
||||
}{
|
||||
{
|
||||
name: "run",
|
||||
},
|
||||
{
|
||||
name: "e2e",
|
||||
e2e: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, bundleType := range bundleTypeCases {
|
||||
for _, mode := range modes {
|
||||
for _, tc := range tests {
|
||||
t.Run(fmt.Sprintf("%s, %s, %s", bundleType.note, tc.note, mode.name), func(t *testing.T) {
|
||||
files := map[string]string{}
|
||||
|
||||
if bundleType.tar {
|
||||
files["bundle.tar.gz"] = ""
|
||||
} else {
|
||||
for k, v := range tc.modules {
|
||||
files[k] = v
|
||||
}
|
||||
|
||||
manifest := bundle.Manifest{
|
||||
RegoVersion: &tc.bundleRegoVersion,
|
||||
FileRegoVersions: tc.bundleFileRegoVersions,
|
||||
}
|
||||
manifest.Init()
|
||||
if b, err := json.Marshal(manifest); err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
} else {
|
||||
files[".manifest"] = string(b)
|
||||
}
|
||||
}
|
||||
|
||||
test.WithTempFS(files, func(root string) {
|
||||
p := root
|
||||
if bundleType.tar {
|
||||
b := bundle.Bundle{
|
||||
Manifest: bundle.Manifest{
|
||||
RegoVersion: &tc.bundleRegoVersion,
|
||||
FileRegoVersions: tc.bundleFileRegoVersions,
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
}
|
||||
for k, v := range tc.modules {
|
||||
b.Modules = append(b.Modules, bundle.ModuleFile{
|
||||
Path: k,
|
||||
Raw: []byte(v),
|
||||
})
|
||||
}
|
||||
p = filepath.Join(root, "bundle.tar.gz")
|
||||
f, err := os.OpenFile(p, os.O_WRONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
err = bundle.Write(f, b)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
params := testBenchParams()
|
||||
_ = params.outputFormat.Set(evalPrettyOutput)
|
||||
|
||||
params.e2e = mode.e2e
|
||||
err := params.bundlePaths.Set(p)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
|
||||
args := []string{tc.query}
|
||||
|
||||
var buf bytes.Buffer
|
||||
rc, err := benchMain(args, params, &buf, &goBenchRunner{})
|
||||
|
||||
if len(tc.expErrs) > 0 {
|
||||
if rc == 0 {
|
||||
t.Fatalf("Expected non-zero return code")
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
for _, expErr := range tc.expErrs {
|
||||
if !strings.Contains(output, expErr) {
|
||||
t.Fatalf("Expected error:\n\n%s\n\ngot:\n\n%s", expErr, output)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
if rc != 0 {
|
||||
t.Fatalf("Unexpected return code %d, expected 0", rc)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderBenchmarkResultJSONOutput(t *testing.T) {
|
||||
params := testBenchParams()
|
||||
err := params.outputFormat.Set(evalJSONOutput)
|
||||
|
||||
+645
-8
@@ -14,6 +14,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/open-policy-agent/opa/internal/file/archive"
|
||||
"github.com/open-policy-agent/opa/loader"
|
||||
"github.com/open-policy-agent/opa/util"
|
||||
"github.com/open-policy-agent/opa/util/test"
|
||||
@@ -63,7 +64,7 @@ func TestBuildProducesBundle(t *testing.T) {
|
||||
} else if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if f.Name == "/data.json" || strings.HasSuffix(f.Name, "/test.rego") {
|
||||
if f.Name == "/.manifest" || f.Name == "/data.json" || strings.HasSuffix(f.Name, "/test.rego") {
|
||||
continue
|
||||
}
|
||||
t.Fatal("unexpected file:", f.Name)
|
||||
@@ -382,7 +383,7 @@ func TestBuildPlanWithPruneUnused(t *testing.T) {
|
||||
switch {
|
||||
case f.Name == "/plan.json":
|
||||
found = true
|
||||
case f.Name == "/data.json" || strings.HasSuffix(f.Name, "/test.rego"): // expected
|
||||
case f.Name == "/.manifest" || f.Name == "/data.json" || strings.HasSuffix(f.Name, "/test.rego"): // expected
|
||||
default:
|
||||
t.Errorf("unexpected file: %s", f.Name)
|
||||
}
|
||||
@@ -646,6 +647,7 @@ p2 := 2
|
||||
manifest: `
|
||||
{
|
||||
"revision":"",
|
||||
"rego_version": 0,
|
||||
"roots":[""],
|
||||
"wasm":[{
|
||||
"entrypoint":"test/p2",
|
||||
@@ -678,6 +680,7 @@ p2 := 2
|
||||
manifest: `
|
||||
{
|
||||
"revision":"",
|
||||
"rego_version": 0,
|
||||
"roots":[""],
|
||||
"wasm":[{
|
||||
"entrypoint":"test/p2",
|
||||
@@ -729,6 +732,7 @@ bar := "baz"
|
||||
manifest: `
|
||||
{
|
||||
"revision":"",
|
||||
"rego_version": 0,
|
||||
"roots":[""],
|
||||
"wasm":[{
|
||||
"entrypoint":"test/p3",
|
||||
@@ -773,6 +777,7 @@ p := 1
|
||||
manifest: `
|
||||
{
|
||||
"revision":"",
|
||||
"rego_version": 0,
|
||||
"roots":[""],
|
||||
"wasm":[{
|
||||
"entrypoint":"test/p",
|
||||
@@ -812,6 +817,7 @@ p2 := 2
|
||||
manifest: `
|
||||
{
|
||||
"revision":"",
|
||||
"rego_version": 0,
|
||||
"roots":[""],
|
||||
"wasm":[{
|
||||
"entrypoint":"test",
|
||||
@@ -839,6 +845,7 @@ p2 := 2
|
||||
manifest: `
|
||||
{
|
||||
"revision":"",
|
||||
"rego_version": 0,
|
||||
"roots":[""],
|
||||
"wasm":[{
|
||||
"entrypoint":"test",
|
||||
@@ -970,13 +977,602 @@ func TestBuildBundleModeIgnoreFlag(t *testing.T) {
|
||||
files = append(files, filepath.Base(f.Name))
|
||||
}
|
||||
|
||||
expected := 4
|
||||
// We additionally expect a manifest file
|
||||
expected := 5
|
||||
if len(files) != expected {
|
||||
t.Fatalf("expected %v files but got %v", expected, len(files))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildBundleModeWithManifestRegoVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
roots []string
|
||||
files map[string]string
|
||||
expManifest string
|
||||
expErrs []string
|
||||
}{
|
||||
{
|
||||
note: "v0 bundle rego-version",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"test.rego": `package test
|
||||
|
||||
p[42] {
|
||||
input.x == 1
|
||||
}`,
|
||||
},
|
||||
expManifest: `{"revision":"","roots":[""],"rego_version":0}`,
|
||||
},
|
||||
{
|
||||
note: "v1 bundle rego-version",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"test.rego": `package test
|
||||
|
||||
p contains 42 if {
|
||||
input.x == 1
|
||||
}`,
|
||||
},
|
||||
expManifest: `{"revision":"","roots":[""],"rego_version":1}`,
|
||||
},
|
||||
{
|
||||
note: "v0 bundle rego-version, v1 per-file override",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"*/test2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"test1.rego": `package test
|
||||
|
||||
p[1] {
|
||||
input.x == 1
|
||||
}`,
|
||||
"test2.rego": `package test
|
||||
|
||||
p contains 2 if {
|
||||
input.x == 1
|
||||
}`,
|
||||
},
|
||||
expManifest: `{"revision":"","roots":[""],"rego_version":0,"file_rego_versions":{"%ROOT%/test2.rego":1}}`,
|
||||
},
|
||||
{
|
||||
note: "v0 bundle rego-version, v1 per-file override, missing v1 keywords in v1 file",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"*/test2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"test1.rego": `package test
|
||||
|
||||
p[1] {
|
||||
input.x == 1
|
||||
}`,
|
||||
"test2.rego": `package test
|
||||
|
||||
p[2] {
|
||||
input.x == 1
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: `if` keyword is required before rule body",
|
||||
"rego_parse_error: `contains` keyword is required for partial set rules",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle rego-version, v1 per-file override, v1 keywords but no v1 imports in v0 file",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"*/test2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"test1.rego": `package test
|
||||
|
||||
p contains 1 if {
|
||||
input.x == 1
|
||||
}`,
|
||||
"test2.rego": `package test
|
||||
|
||||
p contains 2 if {
|
||||
input.x == 1
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: var cannot be used for rule name",
|
||||
"rego_parse_error: number cannot be used for rule name",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "multiple bundles with different rego-versions",
|
||||
roots: []string{"bundle1", "bundle2"},
|
||||
files: map[string]string{
|
||||
"bundle1/.manifest": `{
|
||||
"roots": ["test1"],
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"*/test2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"bundle1/test1.rego": `package test1
|
||||
p[1] {
|
||||
input.x == 1
|
||||
}`,
|
||||
"bundle1/test2.rego": `package test1
|
||||
p contains 2 if {
|
||||
input.x == 1
|
||||
}`,
|
||||
"bundle2/.manifest": `{
|
||||
"roots": ["test2"],
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"*/test4.rego": 0
|
||||
}
|
||||
}`,
|
||||
"bundle2/test3.rego": `package test2
|
||||
p contains 3 if {
|
||||
input.x == 1
|
||||
}`,
|
||||
"bundle2/test4.rego": `package test2
|
||||
p[4] {
|
||||
input.x == 1
|
||||
}`,
|
||||
},
|
||||
expManifest: `{"revision":"","roots":["test1","test2"],"rego_version":0,"file_rego_versions":{"%ROOT%/bundle1/test2.rego":1,"%ROOT%/bundle2/test3.rego":1}}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.note, func(t *testing.T) {
|
||||
test.WithTempFS(tc.files, func(root string) {
|
||||
params := newBuildParams()
|
||||
params.outputFile = path.Join(root, "bundle.tar.gz")
|
||||
params.bundleMode = true
|
||||
|
||||
var roots []string
|
||||
if len(tc.roots) == 0 {
|
||||
roots = []string{root}
|
||||
} else {
|
||||
for _, r := range tc.roots {
|
||||
roots = append(roots, path.Join(root, r))
|
||||
}
|
||||
}
|
||||
err := dobuild(params, roots)
|
||||
if tc.expErrs != nil {
|
||||
if err == nil {
|
||||
t.Fatal("expected error but got none")
|
||||
}
|
||||
for _, expErr := range tc.expErrs {
|
||||
if !strings.Contains(err.Error(), expErr) {
|
||||
t.Fatalf("expected error:\n\n%q\n\nbut got:\n\n%v", expErr, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = loader.NewFileLoader().AsBundle(params.outputFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
f, err := os.Open(params.outputFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
|
||||
gr, err := gzip.NewReader(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tr := tar.NewReader(gr)
|
||||
|
||||
for {
|
||||
f, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if f.Name == "/.manifest" {
|
||||
b, err := io.ReadAll(tr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expManifest := strings.ReplaceAll(tc.expManifest, "%ROOT%", root)
|
||||
if !strings.Contains(string(b), expManifest) {
|
||||
t.Fatalf("expected manifest:\n\n%v\n\nbut got:\n\n%v", expManifest, string(b))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildBundleFromOtherBundles(t *testing.T) {
|
||||
type bundleInfo map[string]string
|
||||
|
||||
tests := []struct {
|
||||
note string
|
||||
v1Compatible bool
|
||||
bundles map[string]bundleInfo
|
||||
expBundle bundleInfo
|
||||
expErrs []string
|
||||
}{
|
||||
{
|
||||
note: "single bundle",
|
||||
bundles: map[string]bundleInfo{
|
||||
"bundle.tar.gz": {
|
||||
"policy.rego": `package test
|
||||
p {
|
||||
input.x == 1
|
||||
}`,
|
||||
},
|
||||
},
|
||||
expBundle: bundleInfo{
|
||||
"/data.json": `{}
|
||||
`,
|
||||
"/.manifest": `{"revision":"","roots":[""],"rego_version":0}
|
||||
`,
|
||||
"%ROOT%/bundle.tar.gz/policy.rego": `package test
|
||||
|
||||
p {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "single bundle, --v1-compatible",
|
||||
v1Compatible: true,
|
||||
bundles: map[string]bundleInfo{
|
||||
"bundle.tar.gz": {
|
||||
"policy.rego": `package test
|
||||
p if {
|
||||
input.x == 1
|
||||
}`,
|
||||
},
|
||||
},
|
||||
expBundle: bundleInfo{
|
||||
"/data.json": `{}
|
||||
`,
|
||||
"/.manifest": `{"revision":"","roots":[""],"rego_version":1}
|
||||
`,
|
||||
"%ROOT%/bundle.tar.gz/policy.rego": `package test
|
||||
|
||||
p if {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "single v0 bundle",
|
||||
bundles: map[string]bundleInfo{
|
||||
"bundle.tar.gz": {
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
p {
|
||||
input.x == 1
|
||||
}`,
|
||||
},
|
||||
},
|
||||
expBundle: bundleInfo{
|
||||
"/data.json": `{}
|
||||
`,
|
||||
"/.manifest": `{"revision":"","roots":[""],"rego_version":0}
|
||||
`,
|
||||
"%ROOT%/bundle.tar.gz/policy.rego": `package test
|
||||
|
||||
p {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "single v0 bundle, --v1-compatible",
|
||||
v1Compatible: true,
|
||||
bundles: map[string]bundleInfo{
|
||||
"bundle.tar.gz": {
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
p {
|
||||
input.x == 1
|
||||
}`,
|
||||
},
|
||||
},
|
||||
// We don't expect parse/compile errors, as the bundle rego-version is 0, which overrides the --v1-compatible flag.
|
||||
expBundle: bundleInfo{
|
||||
"/data.json": `{}
|
||||
`,
|
||||
"/.manifest": `{"revision":"","roots":[""],"rego_version":0}
|
||||
`,
|
||||
"%ROOT%/bundle.tar.gz/policy.rego": `package test
|
||||
|
||||
p {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "single v0 bundle, v1 per-file override",
|
||||
bundles: map[string]bundleInfo{
|
||||
"bundle.tar.gz": {
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/policy_1.rego": 1
|
||||
}
|
||||
}`,
|
||||
"policy_0.rego": `package test
|
||||
p {
|
||||
input.x == 1
|
||||
}`,
|
||||
"policy_1.rego": `package test
|
||||
q contains 1 if {
|
||||
input.x == 1
|
||||
}`,
|
||||
},
|
||||
},
|
||||
expBundle: bundleInfo{
|
||||
"/data.json": `{}
|
||||
`,
|
||||
"/.manifest": `{"revision":"","roots":[""],"rego_version":0,"file_rego_versions":{"%ROOT%/bundle.tar.gz/policy_1.rego":1}}
|
||||
`,
|
||||
"%ROOT%/bundle.tar.gz/policy_0.rego": `package test
|
||||
|
||||
p {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
"%ROOT%/bundle.tar.gz/policy_1.rego": `package test
|
||||
|
||||
q contains 1 if {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "single v0 bundle, v1 per-file override, --v1-compatible",
|
||||
v1Compatible: true,
|
||||
bundles: map[string]bundleInfo{
|
||||
"bundle.tar.gz": {
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/policy_1.rego": 1
|
||||
}
|
||||
}`,
|
||||
"policy_0.rego": `package test
|
||||
p {
|
||||
input.x == 1
|
||||
}`,
|
||||
"policy_1.rego": `package test
|
||||
q contains 1 if {
|
||||
input.x == 1
|
||||
}`,
|
||||
},
|
||||
},
|
||||
expBundle: bundleInfo{
|
||||
"/data.json": `{}
|
||||
`,
|
||||
"/.manifest": `{"revision":"","roots":[""],"rego_version":0,"file_rego_versions":{"%ROOT%/bundle.tar.gz/policy_1.rego":1}}
|
||||
`,
|
||||
"%ROOT%/bundle.tar.gz/policy_0.rego": `package test
|
||||
|
||||
p {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
"%ROOT%/bundle.tar.gz/policy_1.rego": `package test
|
||||
|
||||
q contains 1 if {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle + v1 bundle",
|
||||
bundles: map[string]bundleInfo{
|
||||
"bundle_v0.tar.gz": {
|
||||
".manifest": `{"roots": ["test1"], "rego_version": 0}`,
|
||||
"policy.rego": `package test1
|
||||
p {
|
||||
input.x == 1
|
||||
}`,
|
||||
},
|
||||
"bundle_v1.tar.gz": {
|
||||
".manifest": `{"roots": ["test2"], "rego_version": 1}`,
|
||||
"policy.rego": `package test2
|
||||
q contains 1 if {
|
||||
input.x == 1
|
||||
}`,
|
||||
},
|
||||
},
|
||||
expBundle: bundleInfo{
|
||||
"/data.json": `{}
|
||||
`,
|
||||
"/.manifest": `{"revision":"","roots":["test1","test2"],"rego_version":0,"file_rego_versions":{"%ROOT%/bundle_v1.tar.gz/policy.rego":1}}
|
||||
`,
|
||||
"%ROOT%/bundle_v0.tar.gz/policy.rego": `package test1
|
||||
|
||||
p {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
"%ROOT%/bundle_v1.tar.gz/policy.rego": `package test2
|
||||
|
||||
q contains 1 if {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle + v1 bundle, --v1-compatible",
|
||||
v1Compatible: true,
|
||||
bundles: map[string]bundleInfo{
|
||||
"bundle_v0.tar.gz": {
|
||||
".manifest": `{"roots": ["test1"], "rego_version": 0}`,
|
||||
"policy.rego": `package test1
|
||||
p {
|
||||
input.x == 1
|
||||
}`,
|
||||
},
|
||||
"bundle_v1.tar.gz": {
|
||||
".manifest": `{"roots": ["test2"], "rego_version": 1}`,
|
||||
"policy.rego": `package test2
|
||||
q contains 1 if {
|
||||
input.x == 1
|
||||
}`,
|
||||
},
|
||||
},
|
||||
expBundle: bundleInfo{
|
||||
"/data.json": `{}
|
||||
`,
|
||||
// We get a v1 bundle with a v0 per-file override
|
||||
"/.manifest": `{"revision":"","roots":["test1","test2"],"rego_version":1,"file_rego_versions":{"%ROOT%/bundle_v0.tar.gz/policy.rego":0}}
|
||||
`,
|
||||
"%ROOT%/bundle_v0.tar.gz/policy.rego": `package test1
|
||||
|
||||
p {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
"%ROOT%/bundle_v1.tar.gz/policy.rego": `package test2
|
||||
|
||||
q contains 1 if {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.note, func(t *testing.T) {
|
||||
test.WithTempFS(nil, func(root string) {
|
||||
var roots []string
|
||||
for name, files := range tc.bundles {
|
||||
p := filepath.Join(root, name)
|
||||
roots = append(roots, p)
|
||||
filePairs := make([][2]string, 0, len(files))
|
||||
for k, v := range files {
|
||||
filePairs = append(filePairs, [2]string{k, v})
|
||||
}
|
||||
buf := archive.MustWriteTarGz(filePairs)
|
||||
bf, err := os.Create(p)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
_, err = bf.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
params := newBuildParams()
|
||||
params.outputFile = path.Join(root, "bundle.tar.gz")
|
||||
params.bundleMode = true
|
||||
params.v1Compatible = tc.v1Compatible
|
||||
|
||||
err := dobuild(params, roots)
|
||||
if tc.expErrs != nil {
|
||||
if err == nil {
|
||||
t.Fatal("expected error but got none")
|
||||
}
|
||||
for _, expErr := range tc.expErrs {
|
||||
if !strings.Contains(err.Error(), expErr) {
|
||||
t.Fatalf("expected error:\n\n%q\n\nbut got:\n\n%v", expErr, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = loader.NewFileLoader().AsBundle(params.outputFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
f, err := os.Open(params.outputFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
|
||||
gr, err := gzip.NewReader(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tr := tar.NewReader(gr)
|
||||
|
||||
for {
|
||||
f, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
found := false
|
||||
for expName, expVal := range tc.expBundle {
|
||||
expName = strings.ReplaceAll(expName, "%ROOT%", root)
|
||||
if f.Name == expName {
|
||||
found = true
|
||||
b, err := io.ReadAll(tr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expVal = strings.ReplaceAll(expVal, "%ROOT%", root)
|
||||
if string(b) != expVal {
|
||||
t.Fatalf("expected %v:\n\n%v\n\nbut got:\n\n%v", expName, expVal, string(b))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("unexpected file in bundle: %v", f.Name)
|
||||
}
|
||||
|
||||
//if f.Name == "/policy.rego" {
|
||||
// b, err := io.ReadAll(tr)
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// if string(b) != tc.expBundle["policy.rego"] {
|
||||
// t.Fatalf("expected policy.rego:\n\n%v\n\nbut got:\n\n%v", tc.expBundle["policy.rego"], string(b))
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildWithV1CompatibleFlag(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
@@ -1006,6 +1602,8 @@ func TestBuildWithV1CompatibleFlag(t *testing.T) {
|
||||
},
|
||||
// Imports are preserved
|
||||
expectedFiles: map[string]string{
|
||||
".manifest": `{"revision":"","roots":[""],"rego_version":0}
|
||||
`,
|
||||
"test.rego": `package test
|
||||
|
||||
import rego.v1
|
||||
@@ -1027,6 +1625,8 @@ allow if {
|
||||
},
|
||||
// Imports are preserved
|
||||
expectedFiles: map[string]string{
|
||||
".manifest": `{"revision":"","roots":[""],"rego_version":0}
|
||||
`,
|
||||
"test.rego": `package test
|
||||
|
||||
import future.keywords.if
|
||||
@@ -1048,6 +1648,8 @@ allow if {
|
||||
},
|
||||
// Imports are not added in
|
||||
expectedFiles: map[string]string{
|
||||
".manifest": `{"revision":"","roots":[""],"rego_version":1}
|
||||
`,
|
||||
"test.rego": `package test
|
||||
|
||||
allow if {
|
||||
@@ -1068,6 +1670,8 @@ allow if {
|
||||
},
|
||||
// the rego.v1 import is obsolete in rego-v1, and is removed
|
||||
expectedFiles: map[string]string{
|
||||
".manifest": `{"revision":"","roots":[""],"rego_version":1}
|
||||
`,
|
||||
"test.rego": `package test
|
||||
|
||||
allow if {
|
||||
@@ -1088,6 +1692,8 @@ allow if {
|
||||
},
|
||||
// future.keywords imports are obsolete in rego-v1, and are removed
|
||||
expectedFiles: map[string]string{
|
||||
".manifest": `{"revision":"","roots":[""],"rego_version":1}
|
||||
`,
|
||||
"test.rego": `package test
|
||||
|
||||
allow if {
|
||||
@@ -1096,6 +1702,17 @@ allow if {
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "1.0 compatibility: missing keywords",
|
||||
v1Compatible: true,
|
||||
files: map[string]string{
|
||||
"test.rego": `package test
|
||||
allow[1] {
|
||||
1 < 2
|
||||
}`,
|
||||
},
|
||||
expectedErr: "rego_parse_error",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
@@ -1142,6 +1759,7 @@ allow if {
|
||||
|
||||
tr := tar.NewReader(gr)
|
||||
|
||||
foundFiles := map[string]struct{}{}
|
||||
for {
|
||||
f, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
@@ -1149,6 +1767,7 @@ allow if {
|
||||
} else if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
foundFiles[path.Base(f.Name)] = struct{}{}
|
||||
expectedFile := tc.expectedFiles[path.Base(f.Name)]
|
||||
if expectedFile != "" {
|
||||
data, err := io.ReadAll(tr)
|
||||
@@ -1157,10 +1776,16 @@ allow if {
|
||||
}
|
||||
actualFile := string(data)
|
||||
if actualFile != expectedFile {
|
||||
t.Fatalf("expected optimized module:\n\n%v\n\ngot:\n\n%v", expectedFile, actualFile)
|
||||
t.Fatalf("expected file %s to be:\n\n%v\n\ngot:\n\n%v", f.Name, expectedFile, actualFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for expectedFile := range tc.expectedFiles {
|
||||
if _, ok := foundFiles[expectedFile]; !ok {
|
||||
t.Fatalf("expected file %s not found in bundle, got: %v", expectedFile, foundFiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1186,6 +1811,8 @@ p[k] contains v if {
|
||||
`,
|
||||
},
|
||||
expectedFiles: map[string]string{
|
||||
"/.manifest": `{"revision":"","roots":[""],"rego_version":1}
|
||||
`,
|
||||
"/optimized/test/p.rego": `package test.p
|
||||
|
||||
foo contains __local1__1 if {
|
||||
@@ -1209,10 +1836,10 @@ p[k] contains v if {
|
||||
},
|
||||
// Note: the rego.v1 import isn't added to the optimized module.
|
||||
// This is ok, as the bundle was built with the --v1-compatible flag,
|
||||
// and is therefore only guaranteed to work with OPA 1.0 or when
|
||||
// OPA 0.x is run with the --rego-v1 flag.
|
||||
// TODO: add rego-v1 flag to bundle, so `opa run` etc. doesn't need the --rego-v1 flag to consume it.
|
||||
// and is tagged with a rego-version to inform the consumer.
|
||||
expectedFiles: map[string]string{
|
||||
"/.manifest": `{"revision":"","roots":[""],"rego_version":1}
|
||||
`,
|
||||
"/optimized/test/p.rego": `package test.p
|
||||
|
||||
foo contains __local1__1 if {
|
||||
@@ -1235,6 +1862,8 @@ p[k] contains v if {
|
||||
`,
|
||||
},
|
||||
expectedFiles: map[string]string{
|
||||
"/.manifest": `{"revision":"","roots":[""],"rego_version":1}
|
||||
`,
|
||||
"/optimized/test/p.rego": `package test.p
|
||||
|
||||
foo contains __local1__1 if {
|
||||
@@ -1272,6 +1901,7 @@ foo contains __local1__1 if {
|
||||
|
||||
tr := tar.NewReader(gr)
|
||||
|
||||
foundFiles := map[string]struct{}{}
|
||||
for {
|
||||
f, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
@@ -1279,6 +1909,7 @@ foo contains __local1__1 if {
|
||||
} else if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
foundFiles[f.Name] = struct{}{}
|
||||
expectedFile := tc.expectedFiles[f.Name]
|
||||
if expectedFile != "" {
|
||||
data, err := io.ReadAll(tr)
|
||||
@@ -1287,10 +1918,16 @@ foo contains __local1__1 if {
|
||||
}
|
||||
actualFile := string(data)
|
||||
if actualFile != expectedFile {
|
||||
t.Fatalf("expected optimized module:\n\n%v\n\ngot:\n\n%v", expectedFile, actualFile)
|
||||
t.Fatalf("expected file %s to be:\n\n%v\n\ngot:\n\n%v", f.Name, expectedFile, actualFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for expectedFile := range tc.expectedFiles {
|
||||
if _, ok := foundFiles[expectedFile]; !ok {
|
||||
t.Fatalf("expected file %s not found in bundle, got: %v", expectedFile, foundFiles)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,11 +6,15 @@ package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/open-policy-agent/opa/internal/file/archive"
|
||||
"github.com/open-policy-agent/opa/util/test"
|
||||
)
|
||||
|
||||
@@ -494,3 +498,419 @@ p contains x if {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckWithBundleRegoVersion(t *testing.T) {
|
||||
cases := []struct {
|
||||
note string
|
||||
files map[string]string
|
||||
expErrs []string
|
||||
}{
|
||||
{
|
||||
note: "v0.x bundle, illegal keywords",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
p contains x if {
|
||||
x := [1,2,3]
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: var cannot be used for rule name",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, rego.v1 imported, v1 compliant",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
import rego.v1
|
||||
p contains x if {
|
||||
x := [1,2,3]
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, rego.v1 imported, NOT v1 compliant (parser)",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
import rego.v1
|
||||
p contains x {
|
||||
x := [1,2,3]
|
||||
}
|
||||
|
||||
q.r`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: `if` keyword is required before rule body",
|
||||
"rego_parse_error: `contains` keyword is required for partial set rules",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, rego.v1 imported, NOT v1 compliant (compiler)",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
import rego.v1
|
||||
|
||||
import data.foo
|
||||
import data.bar as foo
|
||||
`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_compile_error: import must not shadow import data.foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, keywords imported, v1 compliant",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
import future.keywords.if
|
||||
import future.keywords.contains
|
||||
p contains x if {
|
||||
x := [1,2,3]
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, no imports, v1 compliant",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
p := 1
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file overrides, compliant",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/policy2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p[x] {
|
||||
x := [1,2,3]
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
q contains x if {
|
||||
x := [1,2,3]
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file overrides (glob), compliant",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"*/policy2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p[x] {
|
||||
x := [1,2,3]
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
q contains x if {
|
||||
x := [1,2,3]
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file overrides, incompliant",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/policy2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p[x] {
|
||||
x := [1,2,3]
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
q[x] {
|
||||
x := [1,2,3]
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: `if` keyword is required before rule body",
|
||||
"rego_parse_error: `contains` keyword is required for partial set rules",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
note: "v1.0 bundle, keywords used but not imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
p contains x if {
|
||||
x := [1,2,3]
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, rego.v1 imported, v1 compliant",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
import rego.v1
|
||||
p contains x if {
|
||||
x := [1,2,3]
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, rego.v1 imported, NOT v1 compliant (parser)",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
import rego.v1
|
||||
p contains x {
|
||||
x := [1,2,3]
|
||||
}
|
||||
|
||||
q.r`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: `if` keyword is required before rule body",
|
||||
"rego_parse_error: `contains` keyword is required for partial set rules",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, rego.v1 imported, NOT v1 compliant (compiler)",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
import rego.v1
|
||||
|
||||
import data.foo
|
||||
import data.bar as foo
|
||||
`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_compile_error: import must not shadow import data.foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, keywords imported, v1 compliant",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
import future.keywords.if
|
||||
import future.keywords.contains
|
||||
p contains x if {
|
||||
x := [1,2,3]
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, keywords imported, NOT v1 compliant",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
import future.keywords.contains
|
||||
p contains x {
|
||||
x := [1,2,3]
|
||||
}
|
||||
|
||||
q.r`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: `if` keyword is required before rule body",
|
||||
"rego_parse_error: `contains` keyword is required for partial set rules",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, keywords imported, NOT v1 compliant (compiler)",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
import future.keywords.if
|
||||
|
||||
input := 1 if {
|
||||
1 == 2
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_compile_error: rules must not shadow input (use a different rule name)",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, no imports, v1 compliant",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
p := 1
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, no imports, NOT v1 compliant but v0 compliant (compiler)",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
p.x`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: `contains` keyword is required for partial set rules",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, no imports, v1 compliant but NOT v0 compliant",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
p contains x if {
|
||||
x := [1,2,3]
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file overrides, compliant",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"/policy1.rego": 0
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p[x] {
|
||||
x := [1,2,3]
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
q contains x if {
|
||||
x := [1,2,3]
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file overrides (glob), compliant",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"*/policy1.rego": 0
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p[x] {
|
||||
x := [1,2,3]
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
q contains x if {
|
||||
x := [1,2,3]
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file overrides, incompliant",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"/policy1.rego": 0
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p contains x if {
|
||||
x := [1,2,3]
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
q contains x if {
|
||||
x := [1,2,3]
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: var cannot be used for rule name",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
bundleTypeCases := []struct {
|
||||
note string
|
||||
tar bool
|
||||
}{
|
||||
{
|
||||
"bundle dir", false,
|
||||
},
|
||||
{
|
||||
"bundle tar", true,
|
||||
},
|
||||
}
|
||||
|
||||
v1CompatibleFlagCases := []struct {
|
||||
note string
|
||||
used bool
|
||||
}{
|
||||
{
|
||||
"no --v1-compatible", false,
|
||||
},
|
||||
{
|
||||
"--v1-compatible", true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, bundleType := range bundleTypeCases {
|
||||
for _, v1CompatibleFlag := range v1CompatibleFlagCases {
|
||||
for _, tc := range cases {
|
||||
t.Run(fmt.Sprintf("%s, %s, %s", bundleType.note, v1CompatibleFlag.note, tc.note), func(t *testing.T) {
|
||||
files := map[string]string{}
|
||||
|
||||
if bundleType.tar {
|
||||
files["bundle.tar.gz"] = ""
|
||||
} else {
|
||||
for k, v := range tc.files {
|
||||
files[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
test.WithTempFS(files, func(root string) {
|
||||
p := root
|
||||
if bundleType.tar {
|
||||
p = filepath.Join(root, "bundle.tar.gz")
|
||||
files := make([][2]string, 0, len(tc.files))
|
||||
for k, v := range tc.files {
|
||||
files = append(files, [2]string{k, v})
|
||||
}
|
||||
buf := archive.MustWriteTarGz(files)
|
||||
bf, err := os.Create(p)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
_, err = bf.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
params := newCheckParams()
|
||||
params.bundleMode = true
|
||||
params.v1Compatible = v1CompatibleFlag.used
|
||||
|
||||
err := checkModules(params, []string{p})
|
||||
switch {
|
||||
case err != nil && len(tc.expErrs) > 0:
|
||||
for _, expErr := range tc.expErrs {
|
||||
if !strings.Contains(err.Error(), expErr) {
|
||||
t.Fatalf("expected err:\n\n%v\n\ngot:\n\n%v", expErr, err)
|
||||
}
|
||||
}
|
||||
return // don't read back bundle below
|
||||
case err != nil && len(tc.expErrs) == 0:
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
case err == nil && len(tc.expErrs) > 0:
|
||||
t.Fatalf("expected error:\n\n%v\n\ngot: none", tc.expErrs)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,14 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/file/archive"
|
||||
"github.com/open-policy-agent/opa/util/test"
|
||||
)
|
||||
|
||||
@@ -138,3 +141,322 @@ p contains 3 if {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDepsV1WithBundleRegoVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
files map[string]string
|
||||
query string
|
||||
expErrs []string
|
||||
}{
|
||||
{
|
||||
note: "v0.x bundle, no keywords",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
p[3] {
|
||||
input.x = 1
|
||||
}`,
|
||||
},
|
||||
query: `data.test.p`,
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, keywords not imported, but used",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
p contains 3 if {
|
||||
input.x = 1
|
||||
}`,
|
||||
},
|
||||
query: `data.test.p`,
|
||||
expErrs: []string{
|
||||
"rego_parse_error: var cannot be used for rule name",
|
||||
"rego_parse_error: number cannot be used for rule name",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, keywords imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
import future.keywords
|
||||
p contains 3 if {
|
||||
input.x = 1
|
||||
}`,
|
||||
},
|
||||
query: `data.test.p`,
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, rego.v1 imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
import rego.v1
|
||||
p contains 3 if {
|
||||
input.x = 1
|
||||
}`,
|
||||
},
|
||||
query: `data.test.p`,
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/policy2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p[3] {
|
||||
input.x = 1
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
p contains 4 if {
|
||||
input.x = 1
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override (glob)",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/bar/*.rego": 1
|
||||
}
|
||||
}`,
|
||||
"foo/policy1.rego": `package test
|
||||
p[3] {
|
||||
input.x = 1
|
||||
}`,
|
||||
"bar/policy2.rego": `package test
|
||||
p contains 4 if {
|
||||
input.x = 1
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override, incompliant",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/policy2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p[3] {
|
||||
input.x = 1
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
p[4] {
|
||||
input.x = 1
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: `if` keyword is required before rule body",
|
||||
"rego_parse_error: `contains` keyword is required for partial set rules",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, no keywords",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
p[3] {
|
||||
input.x = 1
|
||||
}`,
|
||||
},
|
||||
query: `data.test.p`,
|
||||
expErrs: []string{
|
||||
"rego_parse_error: `if` keyword is required before rule body",
|
||||
"rego_parse_error: `contains` keyword is required for partial set rules",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, no keyword imports",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
p contains 3 if {
|
||||
input.x = 1
|
||||
}`,
|
||||
},
|
||||
query: `data.test.p`,
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, keywords imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
import future.keywords
|
||||
p contains 3 if {
|
||||
input.x = 1
|
||||
}`,
|
||||
},
|
||||
query: `data.test.p`,
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, rego.v1 imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
import rego.v1
|
||||
p contains 3 if {
|
||||
input.x = 1
|
||||
}`,
|
||||
},
|
||||
query: `data.test.p`,
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file override",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"/policy1.rego": 0
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p[3] {
|
||||
input.x = 1
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
p contains 4 if {
|
||||
input.x = 1
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file override (glob)",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"/foo/*.rego": 0
|
||||
}
|
||||
}`,
|
||||
"foo/policy1.rego": `package test
|
||||
p[3] {
|
||||
input.x = 1
|
||||
}`,
|
||||
"bar/policy2.rego": `package test
|
||||
p contains 4 if {
|
||||
input.x = 1
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file override, incompliant",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"/policy1.rego": 0
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p contains 3 if {
|
||||
input.x = 1
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
p contains 4 if {
|
||||
input.x = 1
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: var cannot be used for rule name",
|
||||
"rego_parse_error: number cannot be used for rule name",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
bundleTypeCases := []struct {
|
||||
note string
|
||||
tar bool
|
||||
}{
|
||||
{
|
||||
"bundle dir", false,
|
||||
},
|
||||
{
|
||||
"bundle tar", true,
|
||||
},
|
||||
}
|
||||
|
||||
v1CompatibleFlagCases := []struct {
|
||||
note string
|
||||
used bool
|
||||
}{
|
||||
{
|
||||
"no --v1-compatible", false,
|
||||
},
|
||||
{
|
||||
"--v1-compatible", true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, bundleType := range bundleTypeCases {
|
||||
for _, v1CompatibleFlag := range v1CompatibleFlagCases {
|
||||
for _, tc := range tests {
|
||||
t.Run(fmt.Sprintf("%s, %s, %s", bundleType.note, v1CompatibleFlag.note, tc.note), func(t *testing.T) {
|
||||
files := map[string]string{}
|
||||
|
||||
if bundleType.tar {
|
||||
files["bundle.tar.gz"] = ""
|
||||
} else {
|
||||
for k, v := range tc.files {
|
||||
files[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
test.WithTempFS(files, func(root string) {
|
||||
p := root
|
||||
if bundleType.tar {
|
||||
p = filepath.Join(root, "bundle.tar.gz")
|
||||
files := make([][2]string, 0, len(tc.files))
|
||||
for k, v := range tc.files {
|
||||
files = append(files, [2]string{k, v})
|
||||
}
|
||||
buf := archive.MustWriteTarGz(files)
|
||||
bf, err := os.Create(p)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
_, err = bf.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
params := newDepsCommandParams()
|
||||
if err := params.bundlePaths.Set(p); err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
|
||||
params.v1Compatible = v1CompatibleFlag.used
|
||||
_ = params.outputFormat.Set(depsFormatPretty)
|
||||
|
||||
err := deps([]string{tc.query}, params, io.Discard)
|
||||
|
||||
if len(tc.expErrs) > 0 {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error but got nil")
|
||||
}
|
||||
for _, expErr := range tc.expErrs {
|
||||
if !strings.Contains(err.Error(), expErr) {
|
||||
t.Fatalf("Expected error:\n\n%s\n\ngot:\n\n%s", expErr, err.Error())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/open-policy-agent/opa/internal/file/archive"
|
||||
"github.com/open-policy-agent/opa/internal/presentation"
|
||||
"github.com/open-policy-agent/opa/loader"
|
||||
"github.com/open-policy-agent/opa/rego"
|
||||
@@ -1990,3 +1991,306 @@ func TestEvalPolicyWithV1CompatibleFlag(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalPolicyWithBundleRegoVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
files map[string]string
|
||||
query string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
note: "v0.x bundle, no rego.v1 or future.keywords imports",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
allow if {
|
||||
1 < 2
|
||||
}`,
|
||||
},
|
||||
query: "data.test.allow",
|
||||
expectedErr: "rego_parse_error",
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/policy2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p[1] {
|
||||
1 < 2
|
||||
}
|
||||
`,
|
||||
"policy2.rego": `package test
|
||||
p contains 2 if {
|
||||
1 < 2
|
||||
}
|
||||
`,
|
||||
},
|
||||
query: "data.test.p",
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override (glob)",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/bar/*.rego": 1
|
||||
}
|
||||
}`,
|
||||
"foo/policy1.rego": `package test
|
||||
p[1] {
|
||||
1 < 2
|
||||
}
|
||||
`,
|
||||
"bar/policy1.rego": `package test
|
||||
p contains 2 if {
|
||||
1 < 2
|
||||
}
|
||||
`,
|
||||
"bar/policy2.rego": `package test
|
||||
p contains 3 if {
|
||||
1 < 2
|
||||
}
|
||||
`,
|
||||
},
|
||||
query: "data.test.p",
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override, incompliant",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/policy2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p[1] {
|
||||
1 < 2
|
||||
}
|
||||
`,
|
||||
"policy2.rego": `package test
|
||||
p[2] {
|
||||
1 < 2
|
||||
}
|
||||
`,
|
||||
},
|
||||
query: "data.test.p",
|
||||
expectedErr: "rego_parse_error",
|
||||
},
|
||||
|
||||
{
|
||||
note: "v1.0 bundle, no rego.v1 or future.keywords imports",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
allow if {
|
||||
1 < 2
|
||||
}`,
|
||||
},
|
||||
query: "data.test.allow",
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, policy with rego.v1 import",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
import rego.v1
|
||||
allow if {
|
||||
1 < 2
|
||||
}`,
|
||||
},
|
||||
query: "data.test.allow",
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, future.keywords import",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
import future.keywords.if
|
||||
allow if {
|
||||
1 < 2
|
||||
}`,
|
||||
},
|
||||
query: "data.test.allow",
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, keywords not used",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
allow {
|
||||
1 < 2
|
||||
}`,
|
||||
},
|
||||
query: "data.test.allow",
|
||||
expectedErr: "rego_parse_error",
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file override",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"/policy1.rego": 0
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p[1] {
|
||||
1 < 2
|
||||
}
|
||||
`,
|
||||
"policy2.rego": `package test
|
||||
p contains 2 if {
|
||||
1 < 2
|
||||
}
|
||||
`,
|
||||
},
|
||||
query: "data.test.p",
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file override (glob)",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"/foo/*.rego": 0
|
||||
}
|
||||
}`,
|
||||
"foo/policy1.rego": `package test
|
||||
p[1] {
|
||||
1 < 2
|
||||
}
|
||||
`,
|
||||
"foo/policy2.rego": `package test
|
||||
p[2] {
|
||||
1 < 2
|
||||
}
|
||||
`,
|
||||
"bar/policy1.rego": `package test
|
||||
p contains 3 if {
|
||||
1 < 2
|
||||
}
|
||||
`,
|
||||
},
|
||||
query: "data.test.p",
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file override, incompliant",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"*/policy2.rego": 0
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p contains 1 if {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
"policy2.rego": `package test
|
||||
p contains 2 if {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
query: "data.test.p",
|
||||
expectedErr: "rego_parse_error",
|
||||
},
|
||||
}
|
||||
|
||||
bundleTypeCases := []struct {
|
||||
note string
|
||||
tar bool
|
||||
}{
|
||||
{
|
||||
"bundle dir", false,
|
||||
},
|
||||
{
|
||||
"bundle tar", true,
|
||||
},
|
||||
}
|
||||
|
||||
v1CompatibleFlagCases := []struct {
|
||||
note string
|
||||
used bool
|
||||
}{
|
||||
{
|
||||
"no --v1-compatible", false,
|
||||
},
|
||||
{
|
||||
"--v1-compatible", true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, bundleType := range bundleTypeCases {
|
||||
for _, v1CompatibleFlag := range v1CompatibleFlagCases {
|
||||
for _, tc := range tests {
|
||||
t.Run(fmt.Sprintf("%s, %s, %s", bundleType.note, v1CompatibleFlag.note, tc.note), func(t *testing.T) {
|
||||
files := map[string]string{}
|
||||
|
||||
if bundleType.tar {
|
||||
files["bundle.tar.gz"] = ""
|
||||
} else {
|
||||
for k, v := range tc.files {
|
||||
files[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
test.WithTempFS(files, func(root string) {
|
||||
p := root
|
||||
if bundleType.tar {
|
||||
p = filepath.Join(root, "bundle.tar.gz")
|
||||
files := make([][2]string, 0, len(tc.files))
|
||||
for k, v := range tc.files {
|
||||
files = append(files, [2]string{k, v})
|
||||
}
|
||||
buf := archive.MustWriteTarGz(files)
|
||||
bf, err := os.Create(p)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
_, err = bf.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
params := newEvalCommandParams()
|
||||
params.v1Compatible = v1CompatibleFlag.used
|
||||
if err := params.bundlePaths.Set(p); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
defined, err := eval([]string{tc.query}, params, &buf)
|
||||
|
||||
if tc.expectedErr == "" {
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v, buf: %s", err, buf.String())
|
||||
} else if !defined {
|
||||
t.Fatal("expected result to be defined")
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got none")
|
||||
}
|
||||
|
||||
actual := buf.String()
|
||||
if !strings.Contains(actual, tc.expectedErr) {
|
||||
t.Fatalf("expected error:\n\n%v\n\ngot\n\n%v", tc.expectedErr, actual)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,16 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/open-policy-agent/opa/cmd/internal/exec"
|
||||
"github.com/open-policy-agent/opa/internal/file/archive"
|
||||
loggingtest "github.com/open-policy-agent/opa/logging/test"
|
||||
sdk_test "github.com/open-policy-agent/opa/sdk/test"
|
||||
"github.com/open-policy-agent/opa/util"
|
||||
@@ -314,6 +318,351 @@ main contains "hello" if {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecWithBundleRegoVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
files map[string]string
|
||||
expErrs []string
|
||||
}{
|
||||
{
|
||||
note: "v0.x bundle, no keywords used",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package system
|
||||
main["hello"] {
|
||||
input.foo == "bar"
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, no keywords imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package system
|
||||
main contains "hello" if {
|
||||
input.foo == "bar"
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: var cannot be used for rule name",
|
||||
"rego_parse_error: string cannot be used for rule name",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, keywords imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package system
|
||||
import future.keywords
|
||||
main contains "hello" if {
|
||||
input.foo == "bar"
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, rego.v1 imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package system
|
||||
import rego.v1
|
||||
main contains "hello" if {
|
||||
input.foo == "bar"
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/policy2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package system
|
||||
p[42] {
|
||||
input.foo == "bar"
|
||||
}`,
|
||||
"policy2.rego": `package system
|
||||
main contains "hello" if {
|
||||
42 in p
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override (glob)",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"*/policy2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package system
|
||||
p[42] {
|
||||
input.foo == "bar"
|
||||
}`,
|
||||
"policy2.rego": `package system
|
||||
main contains "hello" if {
|
||||
42 in p
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override, incompatible",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/policy2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package system
|
||||
p[42] {
|
||||
input.foo == "bar"
|
||||
}`,
|
||||
"policy2.rego": `package system
|
||||
main["hello"] {
|
||||
p[_] == 42
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: `if` keyword is required before rule body",
|
||||
"rego_parse_error: `contains` keyword is required for partial set rules",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
note: "v1.0 bundle, no keywords used",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package system
|
||||
main["hello"] {
|
||||
input.foo == "bar"
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: `if` keyword is required before rule body",
|
||||
"rego_parse_error: `contains` keyword is required for partial set rules",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, no keywords imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package system
|
||||
main contains "hello" if {
|
||||
input.foo == "bar"
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, keywords imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package system
|
||||
import future.keywords
|
||||
main contains "hello" if {
|
||||
input.foo == "bar"
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, rego.v1 imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package system
|
||||
import rego.v1
|
||||
main contains "hello" if {
|
||||
input.foo == "bar"
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file override",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"/policy1.rego": 0
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package system
|
||||
p[42] {
|
||||
input.foo == "bar"
|
||||
}`,
|
||||
"policy2.rego": `package system
|
||||
main contains "hello" if {
|
||||
42 in p
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file override (glob)",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"*/policy1.rego": 0
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package system
|
||||
p[42] {
|
||||
input.foo == "bar"
|
||||
}`,
|
||||
"policy2.rego": `package system
|
||||
main contains "hello" if {
|
||||
42 in p
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file override, incompatible",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"/policy1.rego": 0
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package system
|
||||
p contains 42 {
|
||||
input.foo == "bar"
|
||||
}`,
|
||||
"policy2.rego": `package system
|
||||
main contains "hello" if {
|
||||
42 in p
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: var cannot be used for rule name",
|
||||
"rego_parse_error: number cannot be used for rule name",
|
||||
"rego_parse_error: set cannot be used for rule name",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
bundleTypeCases := []struct {
|
||||
note string
|
||||
tar bool
|
||||
}{
|
||||
{
|
||||
"bundle dir", false,
|
||||
},
|
||||
{
|
||||
"bundle tar", true,
|
||||
},
|
||||
}
|
||||
|
||||
v1CompatibleFlagCases := []struct {
|
||||
note string
|
||||
used bool
|
||||
}{
|
||||
{
|
||||
"no --v1-compatible", false,
|
||||
},
|
||||
{
|
||||
"--v1-compatible", true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, bundleType := range bundleTypeCases {
|
||||
for _, v1CompatibleFlag := range v1CompatibleFlagCases {
|
||||
for _, tc := range tests {
|
||||
t.Run(fmt.Sprintf("%s, %s, %s", bundleType.note, v1CompatibleFlag.note, tc.note), func(t *testing.T) {
|
||||
files := map[string]string{
|
||||
"files/test.json": `{"foo": "bar"}`,
|
||||
}
|
||||
if bundleType.tar {
|
||||
files["bundle.tar.gz"] = ""
|
||||
} else {
|
||||
for k, v := range tc.files {
|
||||
files[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
test.WithTempFS(files, func(root string) {
|
||||
p := root
|
||||
if bundleType.tar {
|
||||
p = filepath.Join(root, "bundle.tar.gz")
|
||||
files := make([][2]string, 0, len(tc.files))
|
||||
for k, v := range tc.files {
|
||||
files = append(files, [2]string{k, v})
|
||||
}
|
||||
buf := archive.MustWriteTarGz(files)
|
||||
bf, err := os.Create(p)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
_, err = bf.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
params := exec.NewParams(&buf)
|
||||
params.Paths = append(params.Paths, root+"/files/")
|
||||
params.BundlePaths = []string{p}
|
||||
params.V1Compatible = v1CompatibleFlag.used
|
||||
_ = params.OutputFormat.Set("json")
|
||||
|
||||
if len(tc.expErrs) > 0 {
|
||||
testLogger := loggingtest.New()
|
||||
params.Logger = testLogger
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go func() {
|
||||
err := runExecWithContext(ctx, params)
|
||||
// we cancelled the context, so we expect that error
|
||||
if err != nil && err.Error() != "context canceled" {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
if !test.Eventually(t, 5*time.Second, func() bool {
|
||||
for _, expErr := range tc.expErrs {
|
||||
found := false
|
||||
for _, e := range testLogger.Entries() {
|
||||
if strings.Contains(e.Message, expErr) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}) {
|
||||
t.Fatalf("timed out waiting for logged errors:\n\n%v\n\ngot\n\n%v:", tc.expErrs, testLogger.Entries())
|
||||
}
|
||||
} else {
|
||||
err := runExec(params)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
output := util.MustUnmarshalJSON(bytes.ReplaceAll(buf.Bytes(), []byte(root), nil))
|
||||
|
||||
exp := util.MustUnmarshalJSON([]byte(`{"result": [{
|
||||
"path": "/files/test.json",
|
||||
"result": ["hello"]
|
||||
}]}`))
|
||||
|
||||
if !reflect.DeepEqual(output, exp) {
|
||||
t.Fatal("Expected:", exp, "Got:", output)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidConfig(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
params := exec.NewParams(&buf)
|
||||
|
||||
+7
-1
@@ -10,6 +10,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
@@ -110,7 +111,8 @@ func doInspect(params inspectCommandParams, path string, out io.Writer) error {
|
||||
return pr.JSON(out, info)
|
||||
|
||||
default:
|
||||
if info.Manifest.Revision != "" || len(*info.Manifest.Roots) != 0 || len(info.Manifest.Metadata) != 0 {
|
||||
if info.Manifest.Revision != "" || len(*info.Manifest.Roots) != 0 || len(info.Manifest.Metadata) != 0 ||
|
||||
info.Manifest.RegoVersion != nil {
|
||||
if err := populateManifest(out, info.Manifest); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -148,6 +150,10 @@ func populateManifest(out io.Writer, m bundle.Manifest) error {
|
||||
t := generateTableWithKeys(out, "field", "value")
|
||||
var lines [][]string
|
||||
|
||||
if m.RegoVersion != nil {
|
||||
lines = append(lines, []string{"Rego Version", truncateTableStr(strconv.Itoa(*m.RegoVersion))})
|
||||
}
|
||||
|
||||
if m.Revision != "" {
|
||||
lines = append(lines, []string{"Revision", truncateTableStr(m.Revision)})
|
||||
}
|
||||
|
||||
@@ -693,6 +693,341 @@ p contains v if {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoInspectWithBundleRegoVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
bundleRegoVersion int
|
||||
files map[string]string
|
||||
expErrs []string
|
||||
}{
|
||||
{
|
||||
note: "v0.x bundle, keywords not used",
|
||||
bundleRegoVersion: 0,
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
p[v] {
|
||||
v := input.x
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, no keywords imported, but used",
|
||||
bundleRegoVersion: 0,
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
p contains v if {
|
||||
v := input.x
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: var cannot be used for rule name",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, keywords imported",
|
||||
bundleRegoVersion: 0,
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
import future.keywords
|
||||
p contains v if {
|
||||
v := input.x
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, rego.v1 imported",
|
||||
bundleRegoVersion: 0,
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
import rego.v1
|
||||
p contains v if {
|
||||
v := input.x
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override",
|
||||
bundleRegoVersion: 0,
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/policy2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p[1] {
|
||||
v := input.x
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
p contains 2 if {
|
||||
v := input.x
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override (glob)",
|
||||
bundleRegoVersion: 0,
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/bar/*.rego": 1
|
||||
}
|
||||
}`,
|
||||
"foo/policy1.rego": `package test
|
||||
p[1] {
|
||||
v := input.x
|
||||
}`,
|
||||
"bar/policy2.rego": `package test
|
||||
p contains 2 if {
|
||||
v := input.x
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override, incompatible",
|
||||
bundleRegoVersion: 0,
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/policy2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p[1] {
|
||||
v := input.x
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
p[2] {
|
||||
v := input.x
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: `if` keyword is required before rule body",
|
||||
"rego_parse_error: `contains` keyword is required for partial set rules",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, keywords not used",
|
||||
bundleRegoVersion: 1,
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
p[v] {
|
||||
v := input.x
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: `if` keyword is required before rule body",
|
||||
"rego_parse_error: `contains` keyword is required for partial set rules",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, no keywords imported",
|
||||
bundleRegoVersion: 1,
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
p contains v if {
|
||||
v := input.x
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, keywords imported",
|
||||
bundleRegoVersion: 1,
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
import future.keywords
|
||||
p contains v if {
|
||||
v := input.x
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, rego.v1 imported",
|
||||
bundleRegoVersion: 1,
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
import rego.v1
|
||||
p contains v if {
|
||||
v := input.x
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file override",
|
||||
bundleRegoVersion: 1,
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"/policy1.rego": 0
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p[1] {
|
||||
v := input.x
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
p contains 2 if {
|
||||
v := input.x
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file override",
|
||||
bundleRegoVersion: 1,
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"/foo/*.rego": 0
|
||||
}
|
||||
}`,
|
||||
"foo/policy1.rego": `package test
|
||||
p[1] {
|
||||
v := input.x
|
||||
}`,
|
||||
"bar/policy2.rego": `package test
|
||||
p contains 2 if {
|
||||
v := input.x
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file override, incompatible",
|
||||
bundleRegoVersion: 1,
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"/policy1.rego": 0
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p contains 1 if {
|
||||
v := input.x
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
p contains 2 if {
|
||||
v := input.x
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: var cannot be used for rule name",
|
||||
"rego_parse_error: number cannot be used for rule name",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
bundleTypeCases := []struct {
|
||||
note string
|
||||
tar bool
|
||||
}{
|
||||
{
|
||||
"bundle dir", false,
|
||||
},
|
||||
{
|
||||
"bundle tar", true,
|
||||
},
|
||||
}
|
||||
|
||||
v1CompatibleFlagCases := []struct {
|
||||
note string
|
||||
used bool
|
||||
}{
|
||||
{
|
||||
"no --v1-compatible", false,
|
||||
},
|
||||
{
|
||||
"--v1-compatible", true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, bundleType := range bundleTypeCases {
|
||||
for _, v1CompatibleFlag := range v1CompatibleFlagCases {
|
||||
for _, tc := range tests {
|
||||
t.Run(fmt.Sprintf("%s, %s, %s", bundleType.note, v1CompatibleFlag.note, tc.note), func(t *testing.T) {
|
||||
files := map[string]string{}
|
||||
if bundleType.tar {
|
||||
files["bundle.tar.gz"] = ""
|
||||
} else {
|
||||
for k, v := range tc.files {
|
||||
files[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
test.WithTempFS(files, func(root string) {
|
||||
p := root
|
||||
if bundleType.tar {
|
||||
p = filepath.Join(root, "bundle.tar.gz")
|
||||
files := make([][2]string, 0, len(tc.files))
|
||||
for k, v := range tc.files {
|
||||
files = append(files, [2]string{k, v})
|
||||
}
|
||||
buf := archive.MustWriteTarGz(files)
|
||||
bf, err := os.Create(p)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
_, err = bf.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
params := newInspectCommandParams()
|
||||
params.v1Compatible = v1CompatibleFlag.used
|
||||
err := params.outputFormat.Set(evalPrettyOutput)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
|
||||
err = doInspect(params, p, &out)
|
||||
|
||||
if len(tc.expErrs) > 0 {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error but got output: %s", out.String())
|
||||
}
|
||||
|
||||
for _, expErr := range tc.expErrs {
|
||||
if !strings.Contains(err.Error(), expErr) {
|
||||
t.Fatalf("Expected error:\n\n%v\n\nbut got:\n\n%v", expErr, err.Error())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %v", err)
|
||||
}
|
||||
|
||||
expOut := fmt.Sprintf(`MANIFEST:
|
||||
+--------------+-------+
|
||||
| FIELD | VALUE |
|
||||
+--------------+-------+
|
||||
| Rego Version | %d |
|
||||
+--------------+-------+`,
|
||||
tc.bundleRegoVersion)
|
||||
if !strings.Contains(out.String(), expOut) {
|
||||
t.Fatalf("Expected output to contain:\n\n%s\n\nbut got:\n\n%s", expOut, out.String())
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallToUnknownBuiltInFunction(t *testing.T) {
|
||||
files := [][2]string{
|
||||
{"/policy.rego", `package test
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/open-policy-agent/opa/bundle"
|
||||
"github.com/open-policy-agent/opa/internal/file/archive"
|
||||
"github.com/open-policy-agent/opa/rego"
|
||||
"github.com/open-policy-agent/opa/topdown"
|
||||
"github.com/open-policy-agent/opa/util/test"
|
||||
@@ -1213,3 +1214,336 @@ test_l if {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithBundleRegoVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
files map[string]string
|
||||
expErr string
|
||||
}{
|
||||
{
|
||||
note: "v0.x bundle, no imports",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
|
||||
l1 := {1, 3, 5}
|
||||
l2 contains v if {
|
||||
v := l1[_]
|
||||
}
|
||||
|
||||
test_l if {
|
||||
l1 == l2
|
||||
}`,
|
||||
},
|
||||
expErr: "rego_parse_error",
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, rego.v1 imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
|
||||
import rego.v1
|
||||
|
||||
l1 := {1, 3, 5}
|
||||
l2 contains v if {
|
||||
v := l1[_]
|
||||
}
|
||||
|
||||
test_l if {
|
||||
l1 == l2
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, future.keywords imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
|
||||
import future.keywords
|
||||
|
||||
l1 := {1, 3, 5}
|
||||
l2 contains v if {
|
||||
v := l1[_]
|
||||
}
|
||||
|
||||
test_l if {
|
||||
l1 == l2
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/policy2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
l1 := {1, 3, 5}
|
||||
l2[v] {
|
||||
v := l1[_]
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
test_l if {
|
||||
l1 == l2
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override (glob)",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"*/policy2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
l1 := {1, 3, 5}
|
||||
l2[v] {
|
||||
v := l1[_]
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
test_l if {
|
||||
l1 == l2
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override, incompatible",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/policy2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
l1 := {1, 3, 5}
|
||||
l2[v] {
|
||||
v := l1[_]
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
test_l {
|
||||
l1 == l2
|
||||
}`,
|
||||
},
|
||||
expErr: "rego_parse_error",
|
||||
},
|
||||
|
||||
{
|
||||
note: "v1.0 bundle, no imports",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
|
||||
l1 := {1, 3, 5}
|
||||
l2 contains v if {
|
||||
v := l1[_]
|
||||
}
|
||||
|
||||
test_l if {
|
||||
l1 == l2
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, rego.v1 imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
|
||||
import rego.v1
|
||||
|
||||
l1 := {1, 3, 5}
|
||||
l2 contains v if {
|
||||
v := l1[_]
|
||||
}
|
||||
|
||||
test_l if {
|
||||
l1 == l2
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, future.keywords imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
|
||||
import future.keywords
|
||||
|
||||
l1 := {1, 3, 5}
|
||||
l2 contains v if {
|
||||
v := l1[_]
|
||||
}
|
||||
|
||||
test_l if {
|
||||
l1 == l2
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file override",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"/policy1.rego": 0
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
l1 := {1, 3, 5}
|
||||
l2[v] {
|
||||
v := l1[_]
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
test_l if {
|
||||
l1 == l2
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file override (glob)",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"*/policy1.rego": 0
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
l1 := {1, 3, 5}
|
||||
l2[v] {
|
||||
v := l1[_]
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
test_l if {
|
||||
l1 == l2
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file override, incompatible",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"/policy1.rego": 0
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
l1 := {1, 3, 5}
|
||||
l2 contains v if {
|
||||
v := l1[_]
|
||||
}`,
|
||||
"policy2.rego": `package test
|
||||
test_l if {
|
||||
l1 == l2
|
||||
}`,
|
||||
},
|
||||
expErr: "rego_parse_error",
|
||||
},
|
||||
}
|
||||
|
||||
bundleTypeCases := []struct {
|
||||
note string
|
||||
tar bool
|
||||
}{
|
||||
{
|
||||
"bundle dir", false,
|
||||
},
|
||||
{
|
||||
"bundle tar", true,
|
||||
},
|
||||
}
|
||||
|
||||
v1CompatibleFlagCases := []struct {
|
||||
note string
|
||||
used bool
|
||||
}{
|
||||
{
|
||||
"no --v1-compatible", false,
|
||||
},
|
||||
{
|
||||
"--v1-compatible", true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, bundleType := range bundleTypeCases {
|
||||
for _, v1CompatibleFlag := range v1CompatibleFlagCases {
|
||||
for _, tc := range tests {
|
||||
|
||||
t.Run(fmt.Sprintf("%s, %s, %s", bundleType.note, v1CompatibleFlag.note, tc.note), func(t *testing.T) {
|
||||
files := map[string]string{}
|
||||
if bundleType.tar {
|
||||
files["bundle.tar.gz"] = ""
|
||||
} else {
|
||||
for k, v := range tc.files {
|
||||
files[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
test.WithTempFS(files, func(root string) {
|
||||
p := root
|
||||
if bundleType.tar {
|
||||
p = filepath.Join(root, "bundle.tar.gz")
|
||||
files := make([][2]string, 0, len(tc.files))
|
||||
for k, v := range tc.files {
|
||||
files = append(files, [2]string{k, v})
|
||||
}
|
||||
buf := archive.MustWriteTarGz(files)
|
||||
bf, err := os.Create(p)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
_, err = bf.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
var errBuf bytes.Buffer
|
||||
|
||||
testParams := newTestCommandParams()
|
||||
testParams.v1Compatible = v1CompatibleFlag.used
|
||||
testParams.bundleMode = true
|
||||
testParams.count = 1
|
||||
testParams.output = &buf
|
||||
testParams.errOutput = &errBuf
|
||||
|
||||
exitCode, _ := opaTest([]string{p}, testParams)
|
||||
if tc.expErr != "" {
|
||||
if exitCode == 0 {
|
||||
t.Fatalf("expected non-zero exit code")
|
||||
}
|
||||
|
||||
if actual := errBuf.String(); !strings.Contains(actual, tc.expErr) {
|
||||
t.Fatalf("expected error output to contain:\n\n%q\n\nbut got:\n\n%q", tc.expErr, actual)
|
||||
}
|
||||
} else {
|
||||
if exitCode != 0 {
|
||||
t.Fatalf("unexpected exit code: %d", exitCode)
|
||||
}
|
||||
|
||||
if errBuf.Len() > 0 {
|
||||
t.Fatalf("expected no error output but got:\n\n%q", buf.String())
|
||||
}
|
||||
|
||||
expected := "PASS: 1/1"
|
||||
if actual := buf.String(); !strings.Contains(actual, expected) {
|
||||
t.Fatalf("expected output to contain:\n\n%s\n\nbut got:\n\n%q", expected, actual)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-4
@@ -298,7 +298,7 @@ func (c *Compiler) Build(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.initBundle(); err != nil {
|
||||
if err := c.initBundle(false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -369,7 +369,7 @@ func (c *Compiler) Build(ctx context.Context) error {
|
||||
}
|
||||
|
||||
if c.regoVersion == ast.RegoV1 {
|
||||
if err := c.bundle.FormatModulesForRegoVersion(c.regoVersion, false, false); err != nil {
|
||||
if err := c.bundle.FormatModulesForRegoVersion(c.regoVersion, true, false); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@@ -462,7 +462,7 @@ func (c *Compiler) Bundle() *bundle.Bundle {
|
||||
return c.bundle
|
||||
}
|
||||
|
||||
func (c *Compiler) initBundle() error {
|
||||
func (c *Compiler) initBundle(usePath bool) error {
|
||||
// If the bundle is already set, skip file loading.
|
||||
if c.bundle != nil {
|
||||
return nil
|
||||
@@ -490,7 +490,7 @@ func (c *Compiler) initBundle() error {
|
||||
bundles = append(bundles, load.Bundles[k])
|
||||
}
|
||||
|
||||
result, err := bundle.Merge(bundles)
|
||||
result, err := bundle.MergeWithRegoVersion(bundles, c.regoVersion, usePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bundle merge failed: %v", err)
|
||||
}
|
||||
@@ -503,6 +503,7 @@ func (c *Compiler) initBundle() error {
|
||||
// contents. That would require changes to the loader to preserve the
|
||||
// locations where base documents were mounted under data.
|
||||
result := &bundle.Bundle{}
|
||||
result.SetRegoVersion(c.regoVersion)
|
||||
if len(c.roots) > 0 {
|
||||
result.Manifest.Roots = &c.roots
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/open-policy-agent/opa/bundle"
|
||||
"github.com/open-policy-agent/opa/format"
|
||||
"github.com/open-policy-agent/opa/internal/file/archive"
|
||||
"github.com/open-policy-agent/opa/internal/ref"
|
||||
"github.com/open-policy-agent/opa/ir"
|
||||
"github.com/open-policy-agent/opa/loader"
|
||||
@@ -155,6 +157,7 @@ func TestCompilerLoadAsBundleSuccess(t *testing.T) {
|
||||
expManifest := bundle.Manifest{
|
||||
Roots: &expRoots,
|
||||
}
|
||||
expManifest.SetRegoVersion(ast.RegoV0)
|
||||
|
||||
if !compiler.bundle.Manifest.Equal(expManifest) {
|
||||
t.Fatalf("expected %v but got %v", compiler.bundle.Manifest, expManifest)
|
||||
@@ -163,6 +166,668 @@ func TestCompilerLoadAsBundleSuccess(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompilerLoadAsBundleWithBundleRegoVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
files map[string]string
|
||||
expErrs []string
|
||||
}{
|
||||
{
|
||||
note: "No bundle rego version",
|
||||
files: map[string]string{
|
||||
".manifest": `{}`,
|
||||
"test.rego": `package test
|
||||
p[1] {
|
||||
input.x == 2
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle rego version",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"test.rego": `package test
|
||||
p[1] {
|
||||
input.x == 2
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle rego version, missing keyword imports",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"test.rego": `package test
|
||||
p contains 1 if {
|
||||
input.x == 2
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: var cannot be used for rule name",
|
||||
"rego_parse_error: number cannot be used for rule name",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle rego version",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"test.rego": `package test
|
||||
p contains 1 if {
|
||||
input.x == 2
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle rego version, no keywords",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"test.rego": `package test
|
||||
p[1] {
|
||||
input.x == 2
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: `if` keyword is required before rule body",
|
||||
"rego_parse_error: `contains` keyword is required for partial set rules",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle rego version, duplicate imports",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"test.rego": `package test
|
||||
import data.foo
|
||||
import data.foo
|
||||
|
||||
p contains 1 if {
|
||||
input.x == 2
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_compile_error: import must not shadow import data.foo",
|
||||
},
|
||||
},
|
||||
// file overrides
|
||||
{
|
||||
note: "v0 bundle rego version, v1 file override",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"*/test2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"test1.rego": `package test
|
||||
p["A"] {
|
||||
input.x == 1
|
||||
}`,
|
||||
"test2.rego": `package test
|
||||
p contains "B" if {
|
||||
input.x == 2
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle rego version, v1 file override, missing file",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"*/test2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"test1.rego": `package test
|
||||
p["A"] {
|
||||
input.x == 1
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle rego version, v1 file override, no keywords",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"*/test2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"test1.rego": `package test
|
||||
p["A"] {
|
||||
input.x == 1
|
||||
}`,
|
||||
"test2.rego": `package test
|
||||
p["B"] {
|
||||
input.x == 2
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: `if` keyword is required before rule body",
|
||||
"rego_parse_error: `contains` keyword is required for partial set rules",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle rego version, v1 file override, duplicate imports",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"*/test2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"test1.rego": `package test
|
||||
p["A"] {
|
||||
input.x == 1
|
||||
}`,
|
||||
"test2.rego": `package test
|
||||
import data.foo
|
||||
import data.foo
|
||||
|
||||
p contains "B" if {
|
||||
input.x == 2
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_compile_error: import must not shadow import data.foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle rego version, v0 file override",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"*/test1.rego": 0
|
||||
}
|
||||
}`,
|
||||
"test1.rego": `package test
|
||||
p["A"] {
|
||||
input.x == 1
|
||||
}`,
|
||||
"test2.rego": `package test
|
||||
p contains "B" if {
|
||||
input.x == 2
|
||||
}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle rego version, v0 file override, no import",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"*/test1.rego": 0
|
||||
}
|
||||
}`,
|
||||
"test1.rego": `package test
|
||||
p contains "A" if {
|
||||
input.x == 1
|
||||
}`,
|
||||
"test2.rego": `package test
|
||||
p contains "B" if {
|
||||
input.x == 2
|
||||
}`,
|
||||
},
|
||||
expErrs: []string{
|
||||
"rego_parse_error: var cannot be used for rule name",
|
||||
"rego_parse_error: string cannot be used for rule name",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
bundleTypeCases := []struct {
|
||||
note string
|
||||
tar bool
|
||||
}{
|
||||
{
|
||||
"bundle dir", false,
|
||||
},
|
||||
{
|
||||
"bundle tar", true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, bundleType := range bundleTypeCases {
|
||||
for _, tc := range tests {
|
||||
ctx := context.Background()
|
||||
t.Run(fmt.Sprintf("%s, %s", bundleType.note, tc.note), func(t *testing.T) {
|
||||
files := map[string]string{}
|
||||
if bundleType.tar {
|
||||
files["bundle.tar"] = ""
|
||||
} else {
|
||||
for k, v := range tc.files {
|
||||
files[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
test.WithTestFS(tc.files, false, func(root string, fsys fs.FS) {
|
||||
var path string
|
||||
if bundleType.tar {
|
||||
path = filepath.Join(root, "bundle.tar.gz")
|
||||
files := make([][2]string, 0, len(tc.files))
|
||||
for k, v := range tc.files {
|
||||
files = append(files, [2]string{k, v})
|
||||
}
|
||||
buf := archive.MustWriteTarGz(files)
|
||||
|
||||
bf, err := os.Create(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
_, err = bf.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
} else {
|
||||
path = root
|
||||
}
|
||||
|
||||
compiler := New().
|
||||
WithFS(fsys).
|
||||
WithPaths(path).
|
||||
WithAsBundle(true)
|
||||
|
||||
err := compiler.Build(ctx)
|
||||
|
||||
if len(tc.expErrs) > 0 {
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got none")
|
||||
}
|
||||
for _, expErr := range tc.expErrs {
|
||||
if !strings.Contains(err.Error(), expErr) {
|
||||
t.Fatalf("expected error to contain:\n\n%s\n\ngot:\n\n%v", expErr, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func pointTo[T any](x T) *T {
|
||||
return &x
|
||||
}
|
||||
|
||||
func TestCompilerBundleMergeWithBundleRegoVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
bundles []*bundle.Bundle
|
||||
regoVersion ast.RegoVersion
|
||||
expGlobalRegoVersion *int
|
||||
expFileRegoVersions map[string]int
|
||||
}{
|
||||
{
|
||||
note: "single bundle, no bundle rego version",
|
||||
bundles: []*bundle.Bundle{
|
||||
{
|
||||
Manifest: bundle.Manifest{
|
||||
Roots: &[]string{"a"},
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
Modules: []bundle.ModuleFile{},
|
||||
},
|
||||
},
|
||||
expGlobalRegoVersion: pointTo(0),
|
||||
expFileRegoVersions: map[string]int{},
|
||||
},
|
||||
{
|
||||
note: "single bundle, global rego version",
|
||||
bundles: []*bundle.Bundle{
|
||||
{
|
||||
Manifest: bundle.Manifest{
|
||||
Roots: &[]string{"a"},
|
||||
RegoVersion: pointTo(1),
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
Modules: []bundle.ModuleFile{},
|
||||
},
|
||||
},
|
||||
expGlobalRegoVersion: pointTo(1),
|
||||
expFileRegoVersions: map[string]int{},
|
||||
},
|
||||
{
|
||||
note: "no global rego versions",
|
||||
bundles: []*bundle.Bundle{
|
||||
{
|
||||
Manifest: bundle.Manifest{
|
||||
Roots: &[]string{"a"},
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
Modules: []bundle.ModuleFile{},
|
||||
},
|
||||
{
|
||||
Manifest: bundle.Manifest{
|
||||
Roots: &[]string{"b"},
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
Modules: []bundle.ModuleFile{},
|
||||
},
|
||||
},
|
||||
regoVersion: ast.RegoV1,
|
||||
expGlobalRegoVersion: pointTo(1),
|
||||
},
|
||||
{
|
||||
note: "global rego versions, v1 bundles, v0 provided",
|
||||
bundles: []*bundle.Bundle{
|
||||
{
|
||||
Manifest: bundle.Manifest{
|
||||
Roots: &[]string{"a"},
|
||||
RegoVersion: pointTo(1),
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
Modules: []bundle.ModuleFile{
|
||||
{
|
||||
Path: "a/test1.rego",
|
||||
URL: "a/test1.rego",
|
||||
RelativePath: "/test1.rego",
|
||||
Raw: []byte("package a"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Manifest: bundle.Manifest{
|
||||
Roots: &[]string{"b"},
|
||||
RegoVersion: pointTo(1),
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
Modules: []bundle.ModuleFile{
|
||||
{
|
||||
Path: "b/test1.rego",
|
||||
URL: "b/test1.rego",
|
||||
RelativePath: "/test1.rego",
|
||||
Raw: []byte("package b"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
regoVersion: ast.RegoV0,
|
||||
// global rego-version in bundles are dropped in favor of the provided rego-version
|
||||
expGlobalRegoVersion: pointTo(0),
|
||||
expFileRegoVersions: map[string]int{
|
||||
"/a/test1.rego": 1,
|
||||
"/b/test1.rego": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "global rego versions, v0 bundles, v1 provided",
|
||||
bundles: []*bundle.Bundle{
|
||||
{
|
||||
Manifest: bundle.Manifest{
|
||||
Roots: &[]string{"a"},
|
||||
RegoVersion: pointTo(0),
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
Modules: []bundle.ModuleFile{
|
||||
{
|
||||
Path: "a/test1.rego",
|
||||
URL: "a/test1.rego",
|
||||
RelativePath: "/test1.rego",
|
||||
Raw: []byte("package a"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Manifest: bundle.Manifest{
|
||||
Roots: &[]string{"b"},
|
||||
RegoVersion: pointTo(0),
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
Modules: []bundle.ModuleFile{
|
||||
{
|
||||
Path: "b/test1.rego",
|
||||
URL: "b/test1.rego",
|
||||
RelativePath: "/test1.rego",
|
||||
Raw: []byte("package b"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
regoVersion: ast.RegoV1,
|
||||
// global rego-version in bundles are dropped in favor of the provided rego-version
|
||||
expGlobalRegoVersion: pointTo(1),
|
||||
expFileRegoVersions: map[string]int{
|
||||
"/a/test1.rego": 0,
|
||||
"/b/test1.rego": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "different global rego versions",
|
||||
bundles: []*bundle.Bundle{
|
||||
{
|
||||
Manifest: bundle.Manifest{
|
||||
Roots: &[]string{"a"},
|
||||
RegoVersion: pointTo(0),
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
Modules: []bundle.ModuleFile{
|
||||
{
|
||||
Path: "a/test1.rego",
|
||||
URL: "a/test1.rego",
|
||||
RelativePath: "/test1.rego",
|
||||
Raw: []byte("package a"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Manifest: bundle.Manifest{
|
||||
Roots: &[]string{"b"},
|
||||
RegoVersion: pointTo(1),
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
Modules: []bundle.ModuleFile{
|
||||
{
|
||||
Path: "b/test1.rego",
|
||||
URL: "b/test1.rego",
|
||||
RelativePath: "/test1.rego",
|
||||
Raw: []byte("package b"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
regoVersion: ast.RegoV0,
|
||||
// global rego-version in bundles are dropped in favor of the provided rego-version
|
||||
expGlobalRegoVersion: pointTo(0),
|
||||
expFileRegoVersions: map[string]int{
|
||||
"/b/test1.rego": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "different global rego versions, per-file overrides",
|
||||
bundles: []*bundle.Bundle{
|
||||
{
|
||||
Manifest: bundle.Manifest{
|
||||
Roots: &[]string{"a"},
|
||||
RegoVersion: pointTo(1),
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
Modules: []bundle.ModuleFile{
|
||||
{
|
||||
Path: "a/test1.rego",
|
||||
URL: "a/test1.rego",
|
||||
RelativePath: "/test1.rego",
|
||||
Raw: []byte("package a"),
|
||||
},
|
||||
{
|
||||
Path: "a/test2.rego",
|
||||
URL: "a/test2.rego",
|
||||
RelativePath: "/test2.rego",
|
||||
Raw: []byte("package a"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Manifest: bundle.Manifest{
|
||||
Roots: &[]string{"b"},
|
||||
RegoVersion: pointTo(1),
|
||||
FileRegoVersions: map[string]int{
|
||||
"/test1.rego": 0,
|
||||
},
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
Modules: []bundle.ModuleFile{
|
||||
{
|
||||
// we don't expect this file to get an individual rego-version in the result, as
|
||||
// it has the same rego-version as the global rego-version
|
||||
Path: "b/test1.rego",
|
||||
URL: "b/test1.rego",
|
||||
RelativePath: "/test1.rego",
|
||||
Raw: []byte("package b"),
|
||||
},
|
||||
{
|
||||
Path: "b/test2.rego",
|
||||
URL: "b/test2.rego",
|
||||
RelativePath: "/test2.rego",
|
||||
Raw: []byte("package b"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Manifest: bundle.Manifest{
|
||||
RegoVersion: pointTo(0),
|
||||
Roots: &[]string{"c"},
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
Modules: []bundle.ModuleFile{
|
||||
// we don't expect these files to get individual rego-versions in the result,
|
||||
// as they have the same rego-version as the global rego-version
|
||||
{
|
||||
Path: "c/test1.rego",
|
||||
URL: "c/test1.rego",
|
||||
RelativePath: "test1.rego",
|
||||
Raw: []byte("package c"),
|
||||
},
|
||||
{
|
||||
Path: "c/test2.rego",
|
||||
URL: "c/test2.rego",
|
||||
RelativePath: "test2.rego",
|
||||
Raw: []byte("package c"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
regoVersion: ast.RegoV0,
|
||||
// global rego-version in bundles are dropped in favor of the provided rego-version
|
||||
expGlobalRegoVersion: pointTo(0),
|
||||
// rego-versions is expected for all modules with different rego-version than the global rego-version
|
||||
expFileRegoVersions: map[string]int{
|
||||
"/a/test1.rego": 1,
|
||||
"/a/test2.rego": 1,
|
||||
"/b/test2.rego": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "glob per-file overrides",
|
||||
bundles: []*bundle.Bundle{
|
||||
{
|
||||
Manifest: bundle.Manifest{
|
||||
Roots: &[]string{"a"},
|
||||
RegoVersion: pointTo(0),
|
||||
FileRegoVersions: map[string]int{
|
||||
"a/*": 1,
|
||||
},
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
Modules: []bundle.ModuleFile{
|
||||
{
|
||||
Path: "a/foo/test.rego",
|
||||
URL: "a/foo/test.rego",
|
||||
Raw: []byte("package a"),
|
||||
},
|
||||
{
|
||||
Path: "a/bar/test.rego",
|
||||
URL: "a/bar/test.rego",
|
||||
Raw: []byte("package a"),
|
||||
},
|
||||
{
|
||||
Path: "a/baz/test.rego",
|
||||
URL: "a/baz/test.rego",
|
||||
Raw: []byte("package a"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Manifest: bundle.Manifest{
|
||||
Roots: &[]string{"b"},
|
||||
RegoVersion: pointTo(1),
|
||||
FileRegoVersions: map[string]int{
|
||||
// glob should not affect files with matching path in the other bundle
|
||||
"*/bar/*": 0,
|
||||
},
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
Modules: []bundle.ModuleFile{
|
||||
{
|
||||
Path: "b/foo/test.rego",
|
||||
URL: "b/foo/test.rego",
|
||||
Raw: []byte("package b"),
|
||||
},
|
||||
{
|
||||
Path: "b/bar/test.rego",
|
||||
URL: "b/bar/test.rego",
|
||||
Raw: []byte("package b"),
|
||||
},
|
||||
{
|
||||
Path: "b/baz/test.rego",
|
||||
URL: "b/baz/test.rego",
|
||||
Raw: []byte("package b"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
regoVersion: ast.RegoV0,
|
||||
expGlobalRegoVersion: pointTo(0),
|
||||
expFileRegoVersions: map[string]int{
|
||||
"/a/foo/test.rego": 1,
|
||||
"/a/bar/test.rego": 1,
|
||||
"/a/baz/test.rego": 1,
|
||||
"/b/foo/test.rego": 1,
|
||||
"/b/baz/test.rego": 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.note, func(t *testing.T) {
|
||||
for _, b := range tc.bundles {
|
||||
b.Manifest.Init()
|
||||
for i, m := range b.Modules {
|
||||
b.Modules[i].Parsed = ast.MustParseModule(string(m.Raw))
|
||||
}
|
||||
}
|
||||
|
||||
result, err := bundle.MergeWithRegoVersion(tc.bundles, tc.regoVersion, false)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
compareRegoVersions(t, tc.expGlobalRegoVersion, result.Manifest.RegoVersion)
|
||||
|
||||
if !reflect.DeepEqual(tc.expFileRegoVersions, result.Manifest.FileRegoVersions) {
|
||||
t.Fatalf("expected file rego versions to be:\n\n%v\n\nbut got:\n\n%v", tc.expFileRegoVersions, result.Manifest.FileRegoVersions)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func compareRegoVersions(t *testing.T, exp, act *int) {
|
||||
t.Helper()
|
||||
if exp == nil {
|
||||
if act != nil {
|
||||
t.Errorf("expected no rego version, but got %v", *act)
|
||||
}
|
||||
} else {
|
||||
if act == nil {
|
||||
t.Errorf("expected rego version to be %v, but got none", *exp)
|
||||
} else if *act != *exp {
|
||||
t.Errorf("expected rego version to be %v, but got %v", *exp, *act)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompilerLoadAsBundleMergeError(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
@@ -1122,6 +1787,7 @@ func TestCompilerWasmTargetMultipleEntrypoints(t *testing.T) {
|
||||
|
||||
expManifest := bundle.Manifest{}
|
||||
expManifest.Init()
|
||||
expManifest.SetRegoVersion(ast.RegoV0)
|
||||
expManifest.WasmResolvers = []bundle.WasmResolver{
|
||||
{
|
||||
Entrypoint: "test/p",
|
||||
@@ -1261,6 +1927,7 @@ func TestCompilerWasmTargetEntrypointDependents(t *testing.T) {
|
||||
|
||||
expManifest := bundle.Manifest{}
|
||||
expManifest.Init()
|
||||
expManifest.SetRegoVersion(ast.RegoV0)
|
||||
expManifest.WasmResolvers = []bundle.WasmResolver{
|
||||
{
|
||||
Entrypoint: "test/r",
|
||||
@@ -1859,6 +2526,7 @@ func TestCompilerOutput(t *testing.T) {
|
||||
|
||||
p { input.x = data.foo }`))),
|
||||
"data.json": `{"foo": 1}`,
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
}
|
||||
|
||||
for _, useMemoryFS := range []bool{false, true} {
|
||||
|
||||
@@ -589,6 +589,135 @@ func TestOneShotV1Compatible(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOneShotWithBundleRegoVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
bundleRegoVersion int
|
||||
module string
|
||||
expErrs []string
|
||||
}{
|
||||
{
|
||||
note: "v0.x bundle, keywords not used",
|
||||
bundleRegoVersion: 0,
|
||||
module: `package test
|
||||
p[1] {
|
||||
input.x == 2
|
||||
}`,
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, keywords used but not imported",
|
||||
bundleRegoVersion: 0,
|
||||
module: `package test
|
||||
p contains 1 if {
|
||||
input.x == 2
|
||||
}`,
|
||||
expErrs: []string{
|
||||
"rego_parse_error: var cannot be used for rule name",
|
||||
"rego_parse_error: number cannot be used for rule name",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, keywords used, rego.v1 imported",
|
||||
bundleRegoVersion: 0,
|
||||
module: `package test
|
||||
import rego.v1
|
||||
p contains 1 if {
|
||||
input.x == 2
|
||||
}`,
|
||||
},
|
||||
|
||||
{
|
||||
note: "v1.0 bundle, keywords not used",
|
||||
bundleRegoVersion: 1,
|
||||
module: `package test
|
||||
p[1] {
|
||||
input.x == 2
|
||||
}`,
|
||||
expErrs: []string{
|
||||
"rego_parse_error: `if` keyword is required before rule body",
|
||||
"rego_parse_error: `contains` keyword is required for partial set rules",
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, keywords used, not imported",
|
||||
bundleRegoVersion: 1,
|
||||
module: `package test
|
||||
p contains 1 if {
|
||||
input.x == 2
|
||||
}`,
|
||||
},
|
||||
{
|
||||
note: "v1.0, keywords used, rego.v1 imported",
|
||||
bundleRegoVersion: 1,
|
||||
module: `package test
|
||||
import rego.v1
|
||||
p contains 1 if {
|
||||
input.x == 2
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.note, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fixture := newTestFixture(t)
|
||||
fixture.d = New(Config{}, fixture.client, "bundles/custom").
|
||||
WithCallback(fixture.oneShot)
|
||||
fixture.server.expEtag = "some etag value"
|
||||
|
||||
fixture.server.bundles["custom"] = bundle.Bundle{
|
||||
Manifest: bundle.Manifest{RegoVersion: &tc.bundleRegoVersion},
|
||||
Data: map[string]interface{}{},
|
||||
Modules: []bundle.ModuleFile{
|
||||
{
|
||||
Path: "test.rego",
|
||||
Raw: []byte(tc.module),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
defer fixture.server.stop()
|
||||
|
||||
// check etag on the downloader is empty
|
||||
if fixture.d.etag != "" {
|
||||
t.Fatalf("Expected empty downloader ETag but got %v", fixture.d.etag)
|
||||
}
|
||||
|
||||
// simulate successful bundle activation and check updated etag on the downloader
|
||||
fixture.server.expCode = 0
|
||||
err := fixture.d.oneShot(ctx)
|
||||
|
||||
if tc.expErrs != nil {
|
||||
if err == nil {
|
||||
t.Fatal("Expected error but got nil")
|
||||
}
|
||||
for _, expErr := range tc.expErrs {
|
||||
if !strings.Contains(err.Error(), expErr) {
|
||||
t.Fatalf("Expected error to contain:\n\n%v\n\nbut got\n\n%v", expErr, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected:", err)
|
||||
}
|
||||
|
||||
if fixture.d.etag != fixture.server.expEtag {
|
||||
t.Fatalf("Expected downloader ETag %v but got %v", fixture.server.expEtag, fixture.d.etag)
|
||||
}
|
||||
|
||||
if fixture.updates[0].Bundle == nil {
|
||||
// 200 response on first request, bundle should be present
|
||||
t.Errorf("Expected bundle in response")
|
||||
}
|
||||
|
||||
if fixture.updates[0].Bundle.Etag != fixture.server.expEtag {
|
||||
t.Fatalf("Expected bundle ETag %v but got %v", fixture.server.expEtag, fixture.updates[0].Bundle.Etag)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailureAuthn(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -7,13 +7,14 @@ import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/open-policy-agent/opa/bundle"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/open-policy-agent/opa/bundle"
|
||||
|
||||
"github.com/open-policy-agent/opa/keys"
|
||||
"github.com/open-policy-agent/opa/plugins/rest"
|
||||
)
|
||||
@@ -67,9 +68,9 @@ func TestOCIDownloaderWithRegoV1Bundle(t *testing.T) {
|
||||
regoVersion ast.RegoVersion
|
||||
expErr string
|
||||
}{
|
||||
// The bundle contains a v1 rego_version attr, so we expect no errors regardless of parser regoVersion.
|
||||
{
|
||||
note: "non-1.0 compatible OCI downloader",
|
||||
expErr: "rego_parse_error",
|
||||
note: "non-1.0 compatible OCI downloader",
|
||||
},
|
||||
{
|
||||
note: "1.0 compatible OCI downloader",
|
||||
|
||||
Vendored
+2
-2
@@ -8,8 +8,8 @@
|
||||
"layers":[
|
||||
{
|
||||
"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip",
|
||||
"digest":"sha256:cc09b0f5ac97b11637c96ff1b0fbbc287c5ba0169813edaa71fe58424e95f0b7",
|
||||
"size":695,
|
||||
"digest":"sha256:0f93a2c5964d7c8b676e3b507b6bc3b771c086428dece6f84256b9948cb3f256",
|
||||
"size":830,
|
||||
"annotations":{
|
||||
"org.opencontainers.image.created":"2022-02-11T09:00:07Z",
|
||||
"org.opencontainers.image.title":"dani/testpol"
|
||||
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+2
-2
@@ -8,8 +8,8 @@
|
||||
"layers":[
|
||||
{
|
||||
"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip",
|
||||
"digest":"sha256:e060c7b9558fad3ec85df5ffa19d0d019f839c36d7ec146977c871dcbc70885e",
|
||||
"size":629,
|
||||
"digest":"sha256:7fccf82798e6e627afd04144889570d966583788473db2888f0d0d325904d273",
|
||||
"size":764,
|
||||
"annotations":{
|
||||
"org.opencontainers.image.created":"2022-02-11T09:00:07Z",
|
||||
"org.opencontainers.image.title":"dani/testpol"
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1380
-5
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -1064,6 +1065,176 @@ bundles.authz.service := v if {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadAndActivateBundleFromDiskWithBundleRegoVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
bundleRegoVersion int
|
||||
modules map[string]versionedModule
|
||||
}{
|
||||
{
|
||||
note: "v0 bundle",
|
||||
bundleRegoVersion: 0,
|
||||
modules: map[string]versionedModule{
|
||||
"policy.rego": {-1, `package config
|
||||
|
||||
labels.x := "label value changed"
|
||||
default_decision := "bar/baz"
|
||||
default_authorization_decision := "baz/qux"
|
||||
plugins.test_plugin := v {
|
||||
v := {"a": "b"}
|
||||
}
|
||||
|
||||
services.acmecorp.url := v {
|
||||
v := "http://localhost:8181"
|
||||
}
|
||||
|
||||
bundles.authz.service := v {
|
||||
v := "localhost"
|
||||
}`},
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override",
|
||||
bundleRegoVersion: 0,
|
||||
modules: map[string]versionedModule{
|
||||
"policy1.rego": {-1, `package config
|
||||
|
||||
labels.x := "label value changed"
|
||||
default_decision := "bar/baz"
|
||||
default_authorization_decision := "baz/qux"
|
||||
plugins.test_plugin := v {
|
||||
v := {"a": "b"}
|
||||
}`},
|
||||
"policy2.rego": {1, `package config
|
||||
|
||||
services.acmecorp.url := v if {
|
||||
v := "http://localhost:8181"
|
||||
}
|
||||
|
||||
bundles.authz.service := v if {
|
||||
v := "localhost"
|
||||
}`},
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle",
|
||||
bundleRegoVersion: 1,
|
||||
// no future.keywords import
|
||||
modules: map[string]versionedModule{
|
||||
"policy.rego": {-1, `package config
|
||||
labels.x := "label value changed"
|
||||
default_decision := "bar/baz"
|
||||
default_authorization_decision := "baz/qux"
|
||||
plugins.test_plugin := v if {
|
||||
v := {"a": "b"}
|
||||
}
|
||||
services.acmecorp.url := v if {
|
||||
v := "http://localhost:8181"
|
||||
}
|
||||
bundles.authz.service := v if {
|
||||
v := "localhost"
|
||||
}`},
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file override",
|
||||
bundleRegoVersion: 1,
|
||||
modules: map[string]versionedModule{
|
||||
"policy1.rego": {0, `package config
|
||||
|
||||
labels.x := "label value changed"
|
||||
default_decision := "bar/baz"
|
||||
default_authorization_decision := "baz/qux"
|
||||
plugins.test_plugin := v {
|
||||
v := {"a": "b"}
|
||||
}`},
|
||||
"policy2.rego": {-1, `package config
|
||||
|
||||
services.acmecorp.url := v if {
|
||||
v := "http://localhost:8181"
|
||||
}
|
||||
|
||||
bundles.authz.service := v if {
|
||||
v := "localhost"
|
||||
}`},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.note, func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
manager, err := plugins.New([]byte(`{
|
||||
"labels": {"x": "y"},
|
||||
"services": {
|
||||
"localhost": {
|
||||
"url": "http://localhost:9999"
|
||||
}
|
||||
},
|
||||
"discovery": {"name": "config", "persist": true},
|
||||
}`), "test-id",
|
||||
inmem.New())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testPlugin := &reconfigureTestPlugin{counts: map[string]int{}}
|
||||
testFactory := testFactory{p: testPlugin}
|
||||
|
||||
disco, err := New(manager, Factories(map[string]plugins.Factory{"test_plugin": testFactory}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
disco.bundlePersistPath = filepath.Join(dir, ".opa")
|
||||
|
||||
ensurePluginState(t, disco, plugins.StateNotReady)
|
||||
|
||||
// persist a bundle to disk and then load it
|
||||
initialBundle := makeBundleWithRegoVersion(1, tc.bundleRegoVersion, tc.modules)
|
||||
|
||||
initialBundle.Manifest.Init()
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := bundleApi.NewWriter(&buf).Write(*initialBundle); err != nil {
|
||||
t.Fatal("unexpected error:", err)
|
||||
}
|
||||
|
||||
err = disco.saveBundleToDisk(&buf)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
|
||||
disco.loadAndActivateBundleFromDisk(ctx)
|
||||
|
||||
ensurePluginState(t, disco, plugins.StateOK)
|
||||
|
||||
// verify the test plugin was registered on the manager
|
||||
if plugin := manager.Plugin("test_plugin"); plugin == nil {
|
||||
t.Fatalf("expected \"test_plugin\" to be regsitered with the plugin manager")
|
||||
}
|
||||
|
||||
// verify the test plugin was started
|
||||
count, ok := testPlugin.counts["start"]
|
||||
if !ok {
|
||||
t.Fatal("expected test plugin to have start counter")
|
||||
}
|
||||
|
||||
if count != 1 {
|
||||
t.Fatalf("expected test plugin to have a start count of 1 but got %v", count)
|
||||
}
|
||||
|
||||
// verify the bundle plugin was registered on the manager
|
||||
if plugin := bundlePlugin.Lookup(disco.manager); plugin == nil {
|
||||
t.Fatalf("expected bundle plugin to be regsitered with the plugin manager")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveBundleToDiskNew(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
@@ -1379,6 +1550,147 @@ plugins.test_plugin := v if {
|
||||
if !reflect.DeepEqual(testPlugin.counts, map[string]int{"start": 1, "reconfig": 1}) {
|
||||
t.Errorf("Expected one plugin start and one reconfig but got %v", testPlugin)
|
||||
}
|
||||
|
||||
regoV0Bundle := makeModuleBundleWithRegoVersion(2, `package config
|
||||
labels := v {
|
||||
v := {"a": "zero"}
|
||||
}
|
||||
default_decision := "bar/baz"
|
||||
default_authorization_decision := "baz/qux"`, 0)
|
||||
|
||||
disco.oneShot(ctx, download.Update{Bundle: regoV0Bundle})
|
||||
|
||||
if disco.status == nil {
|
||||
t.Fatal("Expected to find status, found nil")
|
||||
} else if disco.status.Type != bundleApi.SnapshotBundleType {
|
||||
t.Fatalf("expected snapshot bundle but got %v", disco.status.Type)
|
||||
}
|
||||
|
||||
expLabel := "zero"
|
||||
actLabel := manager.Labels()["a"]
|
||||
if actLabel != expLabel {
|
||||
t.Errorf(`Expected label "a" to be: %v, got: %v`, expLabel, actLabel)
|
||||
}
|
||||
|
||||
regoV1Bundle := makeModuleBundleWithRegoVersion(2, `package config
|
||||
labels := v if {
|
||||
v := {"a": "one"}
|
||||
}
|
||||
default_decision := "bar/baz"
|
||||
default_authorization_decision := "baz/qux"`, 1)
|
||||
|
||||
disco.oneShot(ctx, download.Update{Bundle: regoV1Bundle})
|
||||
|
||||
if disco.status == nil {
|
||||
t.Fatal("Expected to find status, found nil")
|
||||
} else if disco.status.Type != bundleApi.SnapshotBundleType {
|
||||
t.Fatalf("expected snapshot bundle but got %v", disco.status.Type)
|
||||
}
|
||||
|
||||
expLabel = "one"
|
||||
actLabel = manager.Labels()["a"]
|
||||
if actLabel != expLabel {
|
||||
t.Errorf(`Expected label "a" to be: %v, got: %v`, expLabel, actLabel)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReconfigureWithBundleRegoVersion(t *testing.T) {
|
||||
popts := ast.ParserOptions{RegoVersion: ast.RegoV1}
|
||||
|
||||
manager, err := plugins.New([]byte(`{
|
||||
"labels": {"x": "y"},
|
||||
"services": {
|
||||
"localhost": {
|
||||
"url": "http://localhost:9999"
|
||||
}
|
||||
},
|
||||
"discovery": {"name": "config"},
|
||||
}`), "test-id",
|
||||
inmem.New(),
|
||||
plugins.WithParserOptions(popts))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testPlugin := &reconfigureTestPlugin{counts: map[string]int{}}
|
||||
testFactory := testFactory{p: testPlugin}
|
||||
|
||||
disco, err := New(manager, Factories(map[string]plugins.Factory{"test_plugin": testFactory}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
initialBundle := makeModuleBundleWithRegoVersion(1, `package config
|
||||
labels := v if {
|
||||
v := {"x": "label value changed", "y": "new label"}
|
||||
}
|
||||
default_decision := "bar/baz"
|
||||
default_authorization_decision := "baz/qux"
|
||||
plugins.test_plugin := v if {
|
||||
v := {"a": "b"}
|
||||
}`, 1)
|
||||
|
||||
disco.oneShot(ctx, download.Update{Bundle: initialBundle, Size: snapshotBundleSize})
|
||||
|
||||
if disco.status == nil {
|
||||
t.Fatal("Expected to find status, found nil")
|
||||
} else if disco.status.Type != bundleApi.SnapshotBundleType {
|
||||
t.Fatalf("expected snapshot bundle but got %v", disco.status.Type)
|
||||
} else if disco.status.Size != snapshotBundleSize {
|
||||
t.Fatalf("expected snapshot bundle size %d but got %d", snapshotBundleSize, disco.status.Size)
|
||||
}
|
||||
|
||||
// Verify labels are unchanged but allow additions
|
||||
exp := map[string]string{"x": "y", "y": "new label", "id": "test-id", "version": version.Version}
|
||||
if !reflect.DeepEqual(manager.Labels(), exp) {
|
||||
t.Errorf("Expected labels to be unchanged (%v) but got %v", exp, manager.Labels())
|
||||
}
|
||||
|
||||
// Verify decision ids set
|
||||
expDecision := ast.MustParseTerm("data.bar.baz")
|
||||
expAuthzDecision := ast.MustParseTerm("data.baz.qux")
|
||||
if !manager.Config.DefaultDecisionRef().Equal(expDecision.Value) {
|
||||
t.Errorf("Expected default decision to be %v but got %v", expDecision, manager.Config.DefaultDecisionRef())
|
||||
}
|
||||
if !manager.Config.DefaultAuthorizationDecisionRef().Equal(expAuthzDecision.Value) {
|
||||
t.Errorf("Expected default authz decision to be %v but got %v", expAuthzDecision, manager.Config.DefaultAuthorizationDecisionRef())
|
||||
}
|
||||
|
||||
// Verify plugins started
|
||||
if !reflect.DeepEqual(testPlugin.counts, map[string]int{"start": 1}) {
|
||||
t.Errorf("Expected exactly one plugin start but got %v", testPlugin)
|
||||
}
|
||||
|
||||
// Verify plugins reconfigured
|
||||
updatedBundle := makeModuleBundleWithRegoVersion(2, `package config
|
||||
labels := v if {
|
||||
v := {"x": "label value changed", "z": "another added label"}
|
||||
}
|
||||
default_decision := "bar/baz"
|
||||
default_authorization_decision := "baz/qux"
|
||||
plugins.test_plugin := v if {
|
||||
v := {"a": "plugin parameter value changed"}
|
||||
}`, 1)
|
||||
|
||||
disco.oneShot(ctx, download.Update{Bundle: updatedBundle})
|
||||
|
||||
// Verify label additions are always on top of bootstrap config with multiple discovery documents
|
||||
exp = map[string]string{"x": "y", "z": "another added label", "id": "test-id", "version": version.Version}
|
||||
if !reflect.DeepEqual(manager.Labels(), exp) {
|
||||
t.Errorf("Expected labels to be unchanged (%v) but got %v", exp, manager.Labels())
|
||||
}
|
||||
|
||||
if disco.status == nil {
|
||||
t.Fatal("Expected to find status, found nil")
|
||||
} else if disco.status.Type != bundleApi.SnapshotBundleType {
|
||||
t.Fatalf("expected snapshot bundle but got %v", disco.status.Type)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(testPlugin.counts, map[string]int{"start": 1, "reconfig": 1}) {
|
||||
t.Errorf("Expected one plugin start and one reconfig but got %v", testPlugin)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReconfigureWithUpdates(t *testing.T) {
|
||||
@@ -2281,6 +2593,65 @@ func makeModuleBundle(n int, s string, popts ast.ParserOptions) *bundleApi.Bundl
|
||||
}
|
||||
}
|
||||
|
||||
func makeModuleBundleWithRegoVersion(revision int, bundle string, regoVersion int) *bundleApi.Bundle {
|
||||
popts := ast.ParserOptions{}
|
||||
if regoVersion == 0 {
|
||||
popts.RegoVersion = ast.RegoV0
|
||||
} else {
|
||||
popts.RegoVersion = ast.RegoV1
|
||||
}
|
||||
return &bundleApi.Bundle{
|
||||
Manifest: bundleApi.Manifest{
|
||||
Revision: fmt.Sprintf("test-revision-%v", revision),
|
||||
RegoVersion: ®oVersion,
|
||||
},
|
||||
Modules: []bundleApi.ModuleFile{
|
||||
{
|
||||
URL: `policy.rego`,
|
||||
Path: `/policy.rego`,
|
||||
Raw: []byte(bundle),
|
||||
Parsed: ast.MustParseModuleWithOpts(bundle, popts),
|
||||
},
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
}
|
||||
}
|
||||
|
||||
type versionedModule struct {
|
||||
version int
|
||||
module string
|
||||
}
|
||||
|
||||
func makeBundleWithRegoVersion(revision int, bundleRegoVersion int, modules map[string]versionedModule) *bundleApi.Bundle {
|
||||
b := bundleApi.Bundle{
|
||||
Manifest: bundleApi.Manifest{
|
||||
Revision: fmt.Sprintf("test-revision-%v", revision),
|
||||
RegoVersion: &bundleRegoVersion,
|
||||
FileRegoVersions: map[string]int{},
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
}
|
||||
|
||||
for k, v := range modules {
|
||||
p := path.Join("/", k)
|
||||
popts := ast.ParserOptions{}
|
||||
if v.version >= 0 {
|
||||
b.Manifest.FileRegoVersions[p] = v.version
|
||||
popts.RegoVersion = ast.RegoVersionFromInt(v.version)
|
||||
} else {
|
||||
popts.RegoVersion = ast.RegoVersionFromInt(bundleRegoVersion)
|
||||
}
|
||||
b.Modules = append(b.Modules, bundleApi.ModuleFile{
|
||||
URL: k,
|
||||
Path: p,
|
||||
Raw: []byte(v.module),
|
||||
Parsed: ast.MustParseModuleWithOpts(v.module, popts),
|
||||
})
|
||||
}
|
||||
|
||||
return &b
|
||||
}
|
||||
|
||||
func getTestManager(t *testing.T, conf string) *plugins.Manager {
|
||||
t.Helper()
|
||||
store := inmem.New()
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/file/archive"
|
||||
"github.com/open-policy-agent/opa/loader"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/report"
|
||||
@@ -914,6 +915,326 @@ func TestServerInitializedWithRegoV1(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerInitializedWithBundleRegoVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
files map[string]string
|
||||
expErr string
|
||||
}{
|
||||
{
|
||||
note: "v0.x bundle, keywords not imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
p if {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
expErr: "rego_parse_error: var cannot be used for rule name",
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, rego.v1 imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
import rego.v1
|
||||
p if {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, future.keywords imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
import future.keywords.if
|
||||
p if {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0.x bundle, no keywords used",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 0}`,
|
||||
"policy.rego": `package test
|
||||
p {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/policy2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p[1] {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
"policy2.rego": `package test
|
||||
q contains 2 if {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override (glob)",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/bar/*.rego": 1
|
||||
}
|
||||
}`,
|
||||
"foo/policy1.rego": `package test
|
||||
p[1] {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
"bar/policy2.rego": `package test
|
||||
q contains 2 if {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v0 bundle, v1 per-file override, incompatible",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 0,
|
||||
"file_rego_versions": {
|
||||
"/policy2.rego": 1
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p[1] {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
"policy2.rego": `package test
|
||||
q[2] {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
expErr: "rego_parse_error",
|
||||
},
|
||||
|
||||
{
|
||||
note: "v1.0 bundle, keywords not imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
p if {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, rego.v1 imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
import rego.v1
|
||||
p if {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, future.keywords imported",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
import future.keywords.if
|
||||
p if {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1.0 bundle, no keywords used",
|
||||
files: map[string]string{
|
||||
".manifest": `{"rego_version": 1}`,
|
||||
"policy.rego": `package test
|
||||
p {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
expErr: "rego_parse_error: `if` keyword is required before rule body",
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file override",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"/policy1.rego": 0
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p[1] {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
"policy2.rego": `package test
|
||||
q contains 2 if {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file override (glob)",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"/foo/*.rego": 0
|
||||
}
|
||||
}`,
|
||||
"foo/policy1.rego": `package test
|
||||
p[1] {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
"bar/policy2.rego": `package test
|
||||
q contains 2 if {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "v1 bundle, v0 per-file override, incompatible",
|
||||
files: map[string]string{
|
||||
".manifest": `{
|
||||
"rego_version": 1,
|
||||
"file_rego_versions": {
|
||||
"/policy1.rego": 0
|
||||
}
|
||||
}`,
|
||||
"policy1.rego": `package test
|
||||
p contains 1 if {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
"policy2.rego": `package test
|
||||
q contains 2 if {
|
||||
input.x == 1
|
||||
}
|
||||
`,
|
||||
},
|
||||
expErr: "rego_parse_error",
|
||||
},
|
||||
}
|
||||
|
||||
bundleTypeCases := []struct {
|
||||
note string
|
||||
tar bool
|
||||
}{
|
||||
{
|
||||
"bundle dir", false,
|
||||
},
|
||||
{
|
||||
"bundle tar", true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, bundleType := range bundleTypeCases {
|
||||
for _, tc := range tests {
|
||||
t.Run(fmt.Sprintf("%s, %s", bundleType.note, tc.note), func(t *testing.T) {
|
||||
files := map[string]string{}
|
||||
if bundleType.tar {
|
||||
files["bundle.tar.gz"] = ""
|
||||
} else {
|
||||
for k, v := range tc.files {
|
||||
files[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
test.WithTempFS(files, func(root string) {
|
||||
p := root
|
||||
if bundleType.tar {
|
||||
p = filepath.Join(root, "bundle.tar.gz")
|
||||
files := make([][2]string, 0, len(tc.files))
|
||||
for k, v := range tc.files {
|
||||
files = append(files, [2]string{k, v})
|
||||
}
|
||||
buf := archive.MustWriteTarGz(files)
|
||||
bf, err := os.Create(p)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
_, err = bf.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond)
|
||||
defer cancel()
|
||||
var output bytes.Buffer
|
||||
|
||||
params := NewParams()
|
||||
params.Output = &output
|
||||
params.Paths = []string{p}
|
||||
params.BundleMode = true
|
||||
params.Addrs = &[]string{"localhost:0"}
|
||||
params.GracefulShutdownPeriod = 1
|
||||
params.Logger = logging.NewNoOpLogger()
|
||||
|
||||
rt, err := NewRuntime(ctx, params)
|
||||
|
||||
if tc.expErr != "" {
|
||||
if err == nil {
|
||||
t.Fatal("Expected error but got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), tc.expErr) {
|
||||
t.Fatalf("Expected error:\n\n%v\n\ngot:\n\n%v", tc.expErr, err.Error())
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %v", err)
|
||||
}
|
||||
|
||||
initChannel := rt.Manager.ServerInitializedChannel()
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
rt.StartServer(ctx)
|
||||
close(done)
|
||||
}()
|
||||
<-done
|
||||
select {
|
||||
case <-initChannel:
|
||||
return
|
||||
default:
|
||||
t.Fatal("expected ServerInitializedChannel to be closed")
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUrlPathToConfigOverride(t *testing.T) {
|
||||
params := NewParams()
|
||||
params.Paths = []string{"https://www.example.com/bundles/bundle.tar.gz"}
|
||||
|
||||
Reference in New Issue
Block a user