Files
releases/v1/bundle/store_test.go
T

4661 lines
129 KiB
Go

package bundle
import (
"bytes"
"cmp"
"encoding/json"
"errors"
"os"
"path/filepath"
"reflect"
"slices"
"strings"
"testing"
"github.com/open-policy-agent/opa/internal/file/archive"
"github.com/open-policy-agent/opa/internal/storage/mock"
"github.com/open-policy-agent/opa/v1/ast"
"github.com/open-policy-agent/opa/v1/logging"
"github.com/open-policy-agent/opa/v1/metrics"
"github.com/open-policy-agent/opa/v1/util"
"github.com/open-policy-agent/opa/v1/util/test"
"github.com/open-policy-agent/opa/v1/storage"
"github.com/open-policy-agent/opa/v1/storage/disk"
"github.com/open-policy-agent/opa/v1/storage/inmem"
inmemtst "github.com/open-policy-agent/opa/v1/storage/inmem/test"
)
func TestManifestStoreLifecycleSingleBundle(t *testing.T) {
store := inmemtst.New()
tb := Manifest{Revision: "abc123", Roots: &[]string{"/a/b", "/a/c"}}
verifyWriteManifests(t, store, map[string]Manifest{"test_bundle": tb}) // write one
verifyReadBundleNames(t, store, nil, "test_bundle") // read one
verifyDeleteManifest(t, store, "test_bundle") // delete it
verifyReadBundleNames(t, store, nil) // ensure it was removed
}
func TestManifestStoreLifecycleMultiBundle(t *testing.T) {
store := inmemtst.New()
bundles := map[string]Manifest{
"bundle1": {
Revision: "abc123",
Roots: &[]string{"/a/b", "/a/c"},
},
"bundle2": {
Revision: "def123",
Roots: &[]string{"/x/y", "/z"},
},
}
verifyWriteManifests(t, store, bundles) // write multiple
verifyReadBundleNames(t, store, nil, "bundle1", "bundle2") // read them
verifyDeleteManifest(t, store, "bundle1") // delete one
verifyReadBundleNames(t, store, nil, "bundle2") // ensure it was removed
verifyDeleteManifest(t, store, "bundle2") // delete the last one
verifyReadBundleNames(t, store, nil) // ensure it was removed
}
func TestLegacyManifestStoreLifecycle(t *testing.T) {
store := inmemtst.New()
tb := Manifest{Revision: "abc123", Roots: &[]string{"/a/b", "/a/c"}}
// write a "legacy" manifest
if err := storage.Txn(t.Context(), store, storage.WriteParams, func(txn storage.Transaction) error {
return LegacyWriteManifestToStore(t.Context(), store, txn, tb)
}); err != nil {
t.Fatalf("Failed to write manifest to store: %s", err)
}
// make sure it can be retrieved
verifyReadLegacyRevision(t, store, tb.Revision)
// delete it
if err := storage.Txn(t.Context(), store, storage.WriteParams, func(txn storage.Transaction) error {
return LegacyEraseManifestFromStore(t.Context(), store, txn)
}); err != nil {
t.Fatalf("Failed to erase manifest from store: %s", err)
}
verifyReadLegacyRevision(t, store, "")
}
func TestMixedManifestStoreLifecycle(t *testing.T) {
store := inmemtst.New()
bundles := map[string]Manifest{
"bundle1": {
Revision: "abc123",
Roots: &[]string{"/a/b", "/a/c"},
},
"bundle2": {
Revision: "def123",
Roots: &[]string{"/x/y", "/z"},
},
}
// Write the legacy one first
if err := storage.Txn(t.Context(), store, storage.WriteParams, func(txn storage.Transaction) error {
return LegacyWriteManifestToStore(t.Context(), store, txn, bundles["bundle1"])
}); err != nil {
t.Fatalf("Failed to write manifest to store: %s", err)
}
verifyReadBundleNames(t, store, nil)
// Write both new ones
verifyWriteManifests(t, store, bundles)
verifyReadBundleNames(t, store, nil, "bundle1", "bundle2")
// Ensure the original legacy one is still there
verifyReadLegacyRevision(t, store, bundles["bundle1"].Revision)
}
func TestBundleLazyModeNoPolicyOrData(t *testing.T) {
mockStore := mock.New()
bundles := map[string]*Bundle{"bundle1": {
Manifest: Manifest{
Roots: &[]string{"a"},
Revision: "foo",
},
Etag: "foo",
lazyLoadingMode: true,
}}
mustActivate(t, mockStore, &ActivateOpts{Bundles: bundles})
// Ensure the bundle was activated
verifyReadBundleNames(t, mockStore, nil, util.Keys(bundles)...)
verifyResultRead(t, mockStore, `{
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "foo",
"roots": ["a"]
},
"etag": "foo"
}
}
}
}`)
}
func TestBundleLifecycle_ModuleRegoVersions(t *testing.T) {
type (
files [][2]string
bundles map[string]files
deactivation struct {
bundles map[string]struct{}
expData string
}
activation struct {
bundles bundles
lazy bool
readWithBundleName bool
expData string
}
)
tests := []struct {
note string
updates []any
runtimeRegoVersion ast.RegoVersion
}{
// single v0 bundle
{
note: "v0 bundle, lazy, read with bundle name",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 0}`},
{"a/policy.rego", `package a
p[42] { true }`},
},
},
lazy: true,
readWithBundleName: true,
// Lazy mode, bundle reader decides if module name should be prefixed with bundle name; reader initialized with bundle name, so prefix is expected.
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["a"]}}},
"modules":{"bundle1/a/policy.rego":{"rego_version":0}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{},"modules":{}}}`,
},
},
},
{
note: "v0 bundle, not lazy, read with bundle name",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 0}`},
{"a/policy.rego", `package a
p[42] { true }`},
},
},
readWithBundleName: true,
// Not lazy mode, bundle store decides that module name should be prefixed with bundle name.
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["a"]}}},
"modules":{"bundle1/a/policy.rego":{"rego_version":0}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{},"modules":{}}}`,
},
},
},
{
note: "v0 bundle, lazy, read with NO bundle name",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 0}`},
{"a/policy.rego", `package a
p[42] { true }`},
},
},
lazy: true,
// Lazy mode, bundle reader decides if module name should be prefixed with bundle name; reader not initialized with bundle name, so prefix not expected.
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["a"]}}},
"modules":{"a/policy.rego":{"rego_version":0}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{},"modules":{}}}`,
},
},
},
{
note: "v0 bundle, not lazy, read with NO bundle name",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 0}`},
{"a/policy.rego", `package a
p[42] { true }`},
},
},
// Not lazy mode, bundle store decides that module name should be prefixed with bundle name.
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["a"]}}},
"modules":{"bundle1/a/policy.rego":{"rego_version":0}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{},"modules":{}}}`,
},
},
},
{
note: "v0 bundle, not lazy, --v0-compatible",
runtimeRegoVersion: ast.RegoV0,
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 0}`},
{"a/policy.rego", `package a
p[42] { true }`},
},
},
readWithBundleName: true,
// Lazy mode, bundle reader decides if module name should be prefixed with bundle name; reader initialized with bundle name, so prefix is expected.
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["a"]}}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{}}}`,
},
},
},
{
note: "v0 bundle, lazy, read with bundle name, --v0-compatible",
runtimeRegoVersion: ast.RegoV0,
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 0}`},
{"a/policy.rego", `package a
p[42] { true }`},
},
},
lazy: true,
readWithBundleName: true,
// Lazy mode, bundle reader decides if module name should be prefixed with bundle name; reader initialized with bundle name, so prefix is expected.
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["a"]}}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{}}}`,
},
},
},
{
note: "v0 bundle, lazy, read with NO bundle name, --v0-compatible",
runtimeRegoVersion: ast.RegoV0,
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 0}`},
{"a/policy.rego", `package a
p[42] { true }`},
},
},
lazy: true,
// Lazy mode, bundle reader decides if module name should be prefixed with bundle name; reader initialized with bundle name, so prefix is expected.
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["a"]}}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{}}}`,
},
},
},
// single v1 bundle
{
note: "v1 bundle, lazy, read with bundle name",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 1}`},
{"a/policy.rego", `package a
p contains 42 if { true }`},
},
},
lazy: true,
readWithBundleName: true,
// Lazy mode, bundle reader decides if module name should be prefixed with bundle name; reader initialized with bundle name, so prefix is expected.
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":1,"revision":"","roots":["a"]}}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{}}}`,
},
},
},
{
note: "v1 bundle, not lazy, read with bundle name",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 1}`},
{"a/policy.rego", `package a
p contains 42 if { true }`},
},
},
readWithBundleName: true,
// Not lazy mode, bundle store decides that module name should be prefixed with bundle name.
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":1,"revision":"","roots":["a"]}}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{}}}`,
},
},
},
{
note: "v1 bundle, lazy, read with NO bundle name",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 1}`},
{"a/policy.rego", `package a
p contains 42 if { true }`},
},
},
lazy: true,
// Lazy mode, bundle reader decides if module name should be prefixed with bundle name; reader not initialized with bundle name, so prefix not expected.
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":1,"revision":"","roots":["a"]}}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{}}}`,
},
},
},
{
note: "v1 bundle, not lazy, read with NO bundle name",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 1}`},
{"a/policy.rego", `package a
p contains 42 if { true }`},
},
},
// Not lazy mode, bundle store decides that module name should be prefixed with bundle name.
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":1,"revision":"","roots":["a"]}}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{}}}`,
},
},
},
{
note: "v1 bundle, not lazy, --v0-compatible",
runtimeRegoVersion: ast.RegoV0,
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 1}`},
{"a/policy.rego", `package a
p contains 42 if { true }`},
},
},
readWithBundleName: true,
// Lazy mode, bundle reader decides if module name should be prefixed with bundle name; reader initialized with bundle name, so prefix is expected.
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":1,"revision":"","roots":["a"]}}},
"modules":{"bundle1/a/policy.rego":{"rego_version":1}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{}, "modules":{}}}`,
},
},
},
{
note: "v1 bundle, lazy, read with bundle name, --v0-compatible",
runtimeRegoVersion: ast.RegoV0,
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 1}`},
{"a/policy.rego", `package a
p contains 42 if { true }`},
},
},
lazy: true,
readWithBundleName: true,
// Lazy mode, bundle reader decides if module name should be prefixed with bundle name; reader initialized with bundle name, so prefix is expected.
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":1,"revision":"","roots":["a"]}}},
"modules":{"bundle1/a/policy.rego":{"rego_version":1}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{}, "modules":{}}}`,
},
},
},
{
note: "v1 bundle, lazy, read with NO bundle name, --v0-compatible",
runtimeRegoVersion: ast.RegoV0,
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 1}`},
{"a/policy.rego", `package a
p contains 42 if { true }`},
},
},
lazy: true,
// Lazy mode, bundle reader decides if module name should be prefixed with bundle name; reader initialized with bundle name, so prefix is expected.
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":1,"revision":"","roots":["a"]}}},
"modules":{"a/policy.rego":{"rego_version":1}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{}, "modules":{}}}`,
},
},
},
{
note: "custom bundle without rego-version, lazy",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"]}`},
{"a/policy.rego", `package a
p contains 42 if { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"revision":"","roots":["a"]}}}
}
}`,
},
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"]}`},
{"a/policy.rego", `package a
p contains 1337 if { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"revision":"","roots":["a"]}}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{}}}`,
},
},
},
{
note: "custom bundle without rego-version, lazy, v1 runtime (explicit)",
runtimeRegoVersion: ast.RegoV1,
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"]}`},
{"a/policy.rego", `package a
p contains 42 if { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"revision":"","roots":["a"]}}}
}
}`,
},
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"]}`},
{"a/policy.rego", `package a
p contains 1337 if { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"revision":"","roots":["a"]}}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{}}}`,
},
},
},
{
note: "custom bundle without rego-version, lazy, --v0-compatible",
runtimeRegoVersion: ast.RegoV0,
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"]}`},
{"a/policy.rego", `package a
p[42] { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"revision":"","roots":["a"]}}}
}
}`,
},
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"]}`},
{"a/policy.rego", `package a
p[1337] { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"revision":"","roots":["a"]}}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{}}}`,
},
},
},
{
note: "custom bundle without rego-version, not lazy",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"]}`},
{"a/policy.rego", `package a
p contains 42 if { true }`},
},
},
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"revision":"","roots":["a"]}}}
}
}`,
},
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"]}`},
{"a/policy.rego", `package a
p contains 1337 if { true }`},
},
},
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"revision":"","roots":["a"]}}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{}}}`,
},
},
},
{
note: "custom bundle without rego-version, not lazy, v1 runtime (explicit)",
runtimeRegoVersion: ast.RegoV1,
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"]}`},
{"a/policy.rego", `package a
p contains 42 if { true }`},
},
},
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"revision":"","roots":["a"]}}}
}
}`,
},
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"]}`},
{"a/policy.rego", `package a
p contains 1337 if { true }`},
},
},
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"revision":"","roots":["a"]}}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{}}}`,
},
},
},
{
note: "custom bundle without rego-version, not lazy, --v0-compatible",
runtimeRegoVersion: ast.RegoV0,
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"]}`},
{"a/policy.rego", `package a
p[42] { true }`},
},
},
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"revision":"","roots":["a"]}}}
}
}`,
},
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"]}`},
{"a/policy.rego", `package a
p[1337] { true }`},
},
},
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"revision":"","roots":["a"]}}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{}}}`,
},
},
},
{
note: "v0, lazy replaced by non-lazy",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 0}`},
{"a/policy.rego", `package a
p[42] { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["a"]}}},
"modules":{"bundle1/a/policy.rego":{"rego_version":0}}
}
}`,
},
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 1}`},
{"a/policy.rego", `package a
p contains 42 if { true }`},
},
},
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":1,"revision":"","roots":["a"]}}},
"modules":{}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{},"modules":{}}}`,
},
},
},
{
note: "v0 bundle replaced by v1 bundle, lazy",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 0}`},
{"a/policy.rego", `package a
p[42] { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["a"]}}},
"modules":{"bundle1/a/policy.rego":{"rego_version":0}}
}
}`,
},
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 1}`},
{"a/policy.rego", `package a
p contains 42 if { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":1,"revision":"","roots":["a"]}}},
"modules":{}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{},"modules":{}}}`,
},
},
},
{
note: "v0 bundle replaced by v1 bundle, not lazy",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 0}`},
{"a/policy.rego", `package a
p[42] { true }`},
},
},
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["a"]}}},
"modules":{"bundle1/a/policy.rego":{"rego_version":0}}
}
}`,
},
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 1}`},
{"a/policy.rego", `package a
p contains 42 if { true }`},
},
},
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":1,"revision":"","roots":["a"]}}},
"modules":{}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{},"modules":{}}}`,
},
},
},
{
note: "v0 bundle replaced by custom bundle, not lazy",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 0}`},
{"a/policy.rego", `package a
p[42] { true }`},
},
},
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["a"]}}},
"modules":{"bundle1/a/policy.rego":{"rego_version":0}}
}
}`,
},
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"]}`}, // no rego-version
{"a/policy.rego", `package a
p contains 42 if { true }`},
},
},
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"revision":"","roots":["a"]}}},
"modules":{}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{},"modules":{}}}`,
},
},
},
{
note: "v1 bundle replaced by v0 bundle, lazy",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 1}`},
{"a/policy.rego", `package a
p contains 42 if { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":1,"revision":"","roots":["a"]}}}
}
}`,
},
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 0}`},
{"a/policy.rego", `package a
p[42] { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["a"]}}},
"modules":{"bundle1/a/policy.rego":{"rego_version":0}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{},"modules":{}}}`,
},
},
},
{
note: "v1 bundle replaced by v0 bundle, not lazy",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 1}`},
{"a/policy.rego", `package a
p contains 42 if { true }`},
},
},
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":1,"revision":"","roots":["a"]}}}
}
}`,
},
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 0}`},
{"a/policy.rego", `package a
p[42] { true }`},
},
},
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["a"]}}},
"modules":{"bundle1/a/policy.rego":{"rego_version":0}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{},"modules":{}}}`,
},
},
},
{
note: "custom bundle replaced by v0 bundle, lazy",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"]}`}, // no rego-version
{"a/policy.rego", `package a
p contains 42 if { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"revision":"","roots":["a"]}}}
}
}`,
},
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 0}`},
{"a/policy.rego", `package a
p[42] { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["a"]}}},
"modules":{"bundle1/a/policy.rego":{"rego_version":0}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{"system":{"bundles":{},"modules":{}}}`,
},
},
},
{
note: "multiple v0 bundles, all dropped",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 0}`},
{"a/policy.rego", `package a
p[42] { true }`},
},
"bundle2": {
{"/.manifest", `{"roots": ["b"], "rego_version": 0}`},
{"b/policy.rego", `package b
p[42] { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{
"bundle1":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["a"]}},
"bundle2":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["b"]}}
},
"modules":{"bundle1/a/policy.rego":{"rego_version":0},"bundle2/b/policy.rego":{"rego_version":0}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}, "bundle2": {}},
expData: `{"system":{"bundles":{},"modules":{}}}`,
},
},
},
{
note: "multiple v0 bundles, one dropped",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 0}`},
{"a/policy.rego", `package a
p[42] { true }`},
},
"bundle2": {
{"/.manifest", `{"roots": ["b"], "rego_version": 0}`},
{"b/policy.rego", `package b
p[42] { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{
"bundle1":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["a"]}},
"bundle2":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["b"]}}
},
"modules":{"bundle1/a/policy.rego":{"rego_version":0},"bundle2/b/policy.rego":{"rego_version":0}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}},
expData: `{
"system":{
"bundles":{
"bundle2":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["b"]}}
},
"modules":{"bundle2/b/policy.rego":{"rego_version":0}}
}
}`,
},
},
},
{
note: "v0 bundle with v1 bundle added",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a"], "rego_version": 0}`},
{"a/policy.rego", `package a
p[42] { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{"bundle1":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["a"]}}},
"modules":{"bundle1/a/policy.rego":{"rego_version":0}}
}
}`,
},
activation{
bundles: bundles{
"bundle2": {
{"/.manifest", `{"roots": ["b"], "rego_version": 1}`},
{"b/policy.rego", `package b
p contains 42 if { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{
"bundle1":{"etag":"bar","manifest":{"rego_version":0,"revision":"","roots":["a"]}},
"bundle2":{"etag":"bar","manifest":{"rego_version":1,"revision":"","roots":["b"]}}
},
"modules":{"bundle1/a/policy.rego":{"rego_version":0}}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}, "bundle2": {}},
expData: `{"system":{"bundles":{},"modules":{}}}`,
},
},
},
{
note: "mixed-version bundles, lazy",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a", "b"], "rego_version": 0, "file_rego_versions": {"/b/policy.rego": 1}}`},
{"a/policy.rego", `package a
p[42] { true }`},
{"b/policy.rego", `package b
p contains 42 if { true }`},
},
"bundle2": {
{"/.manifest", `{"roots": ["c", "d"], "rego_version": 1, "file_rego_versions": {"/d/policy.rego": 0}}`},
{"c/policy.rego", `package c
p contains 42 if { true }`},
{"d/policy.rego", `package d
p[42] { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{
"bundle1":{"etag":"bar","manifest":{"file_rego_versions":{"/b/policy.rego":1},"rego_version":0,"revision":"","roots":["a","b"]}},
"bundle2":{"etag":"bar","manifest":{"file_rego_versions":{"/d/policy.rego":0},"rego_version":1,"revision":"","roots":["c","d"]}}
},
"modules":{
"bundle1/a/policy.rego":{"rego_version":0},
"bundle2/d/policy.rego":{"rego_version":0}
}
}
}`,
},
// replacing bundles
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a", "b"], "rego_version": 0, "file_rego_versions": {"/b/policy2.rego": 1}}`},
{"a/policy2.rego", `package a
q[42] { true }`},
{"b/policy2.rego", `package b
q contains 42 if { true }`},
},
"bundle2": {
{"/.manifest", `{"roots": ["c", "d"], "rego_version": 1, "file_rego_versions": {"/d/policy2.rego": 0}}`},
{"c/policy2.rego", `package c
q contains 42 if { true }`},
{"d/policy2.rego", `package d
q[42] { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{
"bundle1":{"etag":"bar","manifest":{"file_rego_versions":{"/b/policy2.rego":1},"rego_version":0,"revision":"","roots":["a","b"]}},
"bundle2":{"etag":"bar","manifest":{"file_rego_versions":{"/d/policy2.rego":0},"rego_version":1,"revision":"","roots":["c","d"]}}
},
"modules":{
"bundle1/a/policy2.rego":{"rego_version":0},
"bundle2/d/policy2.rego":{"rego_version":0}
}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}, "bundle2": {}},
expData: `{"system":{"bundles":{},"modules":{}}}`,
},
},
},
{
note: "mixed-version bundles, lazy, read with NO bundle name",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a", "b"], "rego_version": 0, "file_rego_versions": {"/b/policy.rego": 1}}`},
{"a/policy.rego", `package a
p[42] { true }`},
{"b/policy.rego", `package b
p contains 42 if { true }`},
},
"bundle2": {
{"/.manifest", `{"roots": ["c", "d"], "rego_version": 1, "file_rego_versions": {"/d/policy.rego": 0}}`},
{"c/policy.rego", `package c
p contains 42 if { true }`},
{"d/policy.rego", `package d
p[42] { true }`},
},
},
lazy: true,
expData: `{
"system":{
"bundles":{
"bundle1":{"etag":"bar","manifest":{"file_rego_versions":{"/b/policy.rego":1},"rego_version":0,"revision":"","roots":["a","b"]}},
"bundle2":{"etag":"bar","manifest":{"file_rego_versions":{"/d/policy.rego":0},"rego_version":1,"revision":"","roots":["c","d"]}}
},
"modules":{
"a/policy.rego":{"rego_version":0},
"d/policy.rego":{"rego_version":0}
}
}
}`,
},
// replacing bundles
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a", "b"], "rego_version": 0, "file_rego_versions": {"/b/policy2.rego": 1}}`},
{"a/policy2.rego", `package a
q[42] { true }`},
{"b/policy2.rego", `package b
q contains 42 if { true }`},
},
"bundle2": {
{"/.manifest", `{"roots": ["c", "d"], "rego_version": 1, "file_rego_versions": {"/d/policy2.rego": 0}}`},
{"c/policy2.rego", `package c
q contains 42 if { true }`},
{"d/policy2.rego", `package d
q[42] { true }`},
},
},
lazy: true,
expData: `{
"system":{
"bundles":{
"bundle1":{"etag":"bar","manifest":{"file_rego_versions":{"/b/policy2.rego":1},"rego_version":0,"revision":"","roots":["a","b"]}},
"bundle2":{"etag":"bar","manifest":{"file_rego_versions":{"/d/policy2.rego":0},"rego_version":1,"revision":"","roots":["c","d"]}}
},
"modules":{
"a/policy2.rego":{"rego_version":0},
"d/policy2.rego":{"rego_version":0}
}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}, "bundle2": {}},
expData: `{"system":{"bundles":{},"modules":{}}}`,
},
},
},
{
note: "mixed-version bundles, not lazy",
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a", "b"], "rego_version": 0, "file_rego_versions": {"/b/policy.rego": 1}}`},
{"a/policy.rego", `package a
p[42] { true }`},
{"b/policy.rego", `package b
p contains 42 if { true }`},
},
"bundle2": {
{"/.manifest", `{"roots": ["c", "d"], "rego_version": 1, "file_rego_versions": {"/d/policy.rego": 0}}`},
{"c/policy.rego", `package c
p contains 42 if { true }`},
{"d/policy.rego", `package d
p[42] { true }`},
},
},
readWithBundleName: true,
expData: `{
"system":{
"bundles":{
"bundle1":{"etag":"bar","manifest":{"file_rego_versions":{"/b/policy.rego":1},"rego_version":0,"revision":"","roots":["a","b"]}},
"bundle2":{"etag":"bar","manifest":{"file_rego_versions":{"/d/policy.rego":0},"rego_version":1,"revision":"","roots":["c","d"]}}
},
"modules":{
"bundle1/a/policy.rego":{"rego_version":0},
"bundle2/d/policy.rego":{"rego_version":0}
}
}
}`,
},
// replacing bundles
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a", "b"], "rego_version": 0, "file_rego_versions": {"/b/policy2.rego": 1}}`},
{"a/policy2.rego", `package a
q[42] { true }`},
{"b/policy2.rego", `package b
q contains 42 if { true }`},
},
"bundle2": {
{"/.manifest", `{"roots": ["c", "d"], "rego_version": 1, "file_rego_versions": {"/d/policy2.rego": 0}}`},
{"c/policy2.rego", `package c
q contains 42 if { true }`},
{"d/policy2.rego", `package d
q[42] { true }`},
},
},
readWithBundleName: true,
expData: `{
"system":{
"bundles":{
"bundle1":{"etag":"bar","manifest":{"file_rego_versions":{"/b/policy2.rego":1},"rego_version":0,"revision":"","roots":["a","b"]}},
"bundle2":{"etag":"bar","manifest":{"file_rego_versions":{"/d/policy2.rego":0},"rego_version":1,"revision":"","roots":["c","d"]}}
},
"modules":{
"bundle1/a/policy2.rego":{"rego_version":0},
"bundle2/d/policy2.rego":{"rego_version":0}
}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}, "bundle2": {}},
expData: `{"system":{"bundles":{},"modules":{}}}`,
},
},
},
{
note: "mixed-version bundles, lazy, --v0-compatible",
runtimeRegoVersion: ast.RegoV0,
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a", "b"], "rego_version": 0, "file_rego_versions": {"/b/policy.rego": 1}}`},
{"a/policy.rego", `package a
p[42] { true }`},
{"b/policy.rego", `package b
p contains 42 if { true }`},
},
"bundle2": {
{"/.manifest", `{"roots": ["c", "d"], "rego_version": 1, "file_rego_versions": {"/d/policy.rego": 0}}`},
{"c/policy.rego", `package c
p contains 42 if { true }`},
{"d/policy.rego", `package d
p[42] { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{
"bundle1":{"etag":"bar","manifest":{"file_rego_versions":{"/b/policy.rego":1},"rego_version":0,"revision":"","roots":["a","b"]}},
"bundle2":{"etag":"bar","manifest":{"file_rego_versions":{"/d/policy.rego":0},"rego_version":1,"revision":"","roots":["c","d"]}}
},
"modules":{
"bundle1/b/policy.rego":{"rego_version":1},
"bundle2/c/policy.rego":{"rego_version":1}
}
}
}`,
},
// replacing bundles
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a", "b"], "rego_version": 0, "file_rego_versions": {"/b/policy2.rego": 1}}`},
{"a/policy2.rego", `package a
q[42] { true }`},
{"b/policy2.rego", `package b
q contains 42 if { true }`},
},
"bundle2": {
{"/.manifest", `{"roots": ["c", "d"], "rego_version": 1, "file_rego_versions": {"/d/policy2.rego": 0}}`},
{"c/policy2.rego", `package c
q contains 42 if { true }`},
{"d/policy2.rego", `package d
q[42] { true }`},
},
},
lazy: true,
readWithBundleName: true,
expData: `{
"system":{
"bundles":{
"bundle1":{"etag":"bar","manifest":{"file_rego_versions":{"/b/policy2.rego":1},"rego_version":0,"revision":"","roots":["a","b"]}},
"bundle2":{"etag":"bar","manifest":{"file_rego_versions":{"/d/policy2.rego":0},"rego_version":1,"revision":"","roots":["c","d"]}}
},
"modules":{
"bundle1/b/policy2.rego":{"rego_version":1},
"bundle2/c/policy2.rego":{"rego_version":1}
}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}, "bundle2": {}},
expData: `{"system":{"bundles":{},"modules":{}}}`,
},
},
},
{
note: "mixed-version bundles, lazy, read with NO bundle name, --v0-compatible",
runtimeRegoVersion: ast.RegoV0,
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a", "b"], "rego_version": 0, "file_rego_versions": {"/b/policy.rego": 1}}`},
{"a/policy.rego", `package a
p[42] { true }`},
{"b/policy.rego", `package b
p contains 42 if { true }`},
},
"bundle2": {
{"/.manifest", `{"roots": ["c", "d"], "rego_version": 1, "file_rego_versions": {"/d/policy.rego": 0}}`},
{"c/policy.rego", `package c
p contains 42 if { true }`},
{"d/policy.rego", `package d
p[42] { true }`},
},
},
lazy: true,
expData: `{
"system":{
"bundles":{
"bundle1":{"etag":"bar","manifest":{"file_rego_versions":{"/b/policy.rego":1},"rego_version":0,"revision":"","roots":["a","b"]}},
"bundle2":{"etag":"bar","manifest":{"file_rego_versions":{"/d/policy.rego":0},"rego_version":1,"revision":"","roots":["c","d"]}}
},
"modules":{
"b/policy.rego":{"rego_version":1},
"c/policy.rego":{"rego_version":1}
}
}
}`,
},
// replacing bundles
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a", "b"], "rego_version": 0, "file_rego_versions": {"/b/policy2.rego": 1}}`},
{"a/policy2.rego", `package a
q[42] { true }`},
{"b/policy2.rego", `package b
q contains 42 if { true }`},
},
"bundle2": {
{"/.manifest", `{"roots": ["c", "d"], "rego_version": 1, "file_rego_versions": {"/d/policy2.rego": 0}}`},
{"c/policy2.rego", `package c
q contains 42 if { true }`},
{"d/policy2.rego", `package d
q[42] { true }`},
},
},
lazy: true,
expData: `{
"system":{
"bundles":{
"bundle1":{"etag":"bar","manifest":{"file_rego_versions":{"/b/policy2.rego":1},"rego_version":0,"revision":"","roots":["a","b"]}},
"bundle2":{"etag":"bar","manifest":{"file_rego_versions":{"/d/policy2.rego":0},"rego_version":1,"revision":"","roots":["c","d"]}}
},
"modules":{
"b/policy2.rego":{"rego_version":1},
"c/policy2.rego":{"rego_version":1}
}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}, "bundle2": {}},
expData: `{"system":{"bundles":{},"modules":{}}}`,
},
},
},
{
note: "mixed-version bundles, not lazy, --v0-compatible",
runtimeRegoVersion: ast.RegoV0,
updates: []any{
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a", "b"], "rego_version": 0, "file_rego_versions": {"/b/policy.rego": 1}}`},
{"a/policy.rego", `package a
p[42] { true }`},
{"b/policy.rego", `package b
p contains 42 if { true }`},
},
"bundle2": {
{"/.manifest", `{"roots": ["c", "d"], "rego_version": 1, "file_rego_versions": {"/d/policy.rego": 0}}`},
{"c/policy.rego", `package c
p contains 42 if { true }`},
{"d/policy.rego", `package d
p[42] { true }`},
},
},
readWithBundleName: true,
expData: `{
"system":{
"bundles":{
"bundle1":{"etag":"bar","manifest":{"file_rego_versions":{"/b/policy.rego":1},"rego_version":0,"revision":"","roots":["a","b"]}},
"bundle2":{"etag":"bar","manifest":{"file_rego_versions":{"/d/policy.rego":0},"rego_version":1,"revision":"","roots":["c","d"]}}
},
"modules":{
"bundle1/b/policy.rego":{"rego_version":1},
"bundle2/c/policy.rego":{"rego_version":1}
}
}
}`,
},
// replacing bundles
activation{
bundles: bundles{
"bundle1": {
{"/.manifest", `{"roots": ["a", "b"], "rego_version": 0, "file_rego_versions": {"/b/policy2.rego": 1}}`},
{"a/policy2.rego", `package a
q[42] { true }`},
{"b/policy2.rego", `package b
q contains 42 if { true }`},
},
"bundle2": {
{"/.manifest", `{"roots": ["c", "d"], "rego_version": 1, "file_rego_versions": {"/d/policy2.rego": 0}}`},
{"c/policy2.rego", `package c
q contains 42 if { true }`},
{"d/policy2.rego", `package d
q[42] { true }`},
},
},
readWithBundleName: true,
expData: `{
"system":{
"bundles":{
"bundle1":{"etag":"bar","manifest":{"file_rego_versions":{"/b/policy2.rego":1},"rego_version":0,"revision":"","roots":["a","b"]}},
"bundle2":{"etag":"bar","manifest":{"file_rego_versions":{"/d/policy2.rego":0},"rego_version":1,"revision":"","roots":["c","d"]}}
},
"modules":{
"bundle1/b/policy2.rego":{"rego_version":1},
"bundle2/c/policy2.rego":{"rego_version":1}
}
}
}`,
},
deactivation{
bundles: map[string]struct{}{"bundle1": {}, "bundle2": {}},
expData: `{"system":{"bundles":{},"modules":{}}}`,
},
},
},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
mockStore := mock.New()
runtimeRegoVersion := cmp.Or(tc.runtimeRegoVersion, ast.DefaultRegoVersion)
for _, update := range tc.updates {
if act, ok := update.(activation); ok {
bundles := map[string]*Bundle{}
for bundleName, files := range act.bundles {
br := NewCustomReader(NewTarballLoaderWithBaseURL(archive.MustWriteTarGz(files), "")).
WithBundleEtag("bar").
WithLazyLoadingMode(act.lazy).
WithRegoVersion(runtimeRegoVersion)
if act.readWithBundleName {
br = br.WithBundleName(bundleName)
}
bundle := must(br.Read())(t)
bundles[bundleName] = &bundle
}
mustActivate(t, mockStore, &ActivateOpts{
Bundles: bundles,
ParserOptions: ast.ParserOptions{RegoVersion: runtimeRegoVersion},
})
verifyResultRead(t, mockStore, act.expData)
} else if deact, ok := update.(deactivation); ok {
mustDeactivate(t, mockStore, &DeactivateOpts{
BundleNames: deact.bundles,
ParserOptions: ast.ParserOptions{RegoVersion: runtimeRegoVersion},
})
verifyResultRead(t, mockStore, deact.expData)
}
}
})
}
}
func TestBundleLazyModeLifecycleRaw(t *testing.T) {
files := [][2]string{
{"/a/b/c/data.json", "[1,2,3]"},
{"/a/b/d/data.json", "true"},
{"/a/b/y/data.yaml", `foo: 1`},
{"/example/example.rego", `package example
p contains 42 if { true }
`},
{"/example/example_v0.rego", `package example
q[42] { true }
`},
{"/authz/allow/policy.wasm", `wasm-module`},
{"/data.json", `{"x": {"y": true}, "a": {"b": {"z": true}}}`},
{"/.manifest", `{
"revision": "foo",
"roots": ["a", "example", "x", "authz"],
"wasm":[{"entrypoint": "authz/allow", "module": "/authz/allow/policy.wasm"}],
"rego_version": 1,
"file_rego_versions": {"/example/example_v0.rego": 0}
}`},
}
bundle := must(NewCustomReader(NewTarballLoaderWithBaseURL(archive.MustWriteTarGz(files), "")).
WithBundleEtag("bar").
WithLazyLoadingMode(true).
Read())(t)
mockStore := mock.New()
compiler := ast.NewCompiler()
bundles := map[string]*Bundle{"bundle1": &bundle}
mustActivate(t, mockStore, &ActivateOpts{
Compiler: compiler,
Bundles: bundles,
ExtraModules: map[string]*ast.Module{"mod1": ast.MustParseModule("package x\np = true")},
})
// Ensure the bundle was activated
verifyReadBundleNames(t, mockStore, nil, util.Keys(bundles)...)
verifyBundleModulesCompiled(t, compiler, bundles)
verifyResultRead(t, mockStore, `{
"a": {
"b": {
"c": [1,2,3],
"d": true,
"y": {
"foo": 1
},
"z": true
}
},
"x": {
"y": true
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "foo",
"roots": ["a", "example", "x", "authz"],
"wasm": [
{
"entrypoint": "authz/allow",
"module": "/authz/allow/policy.wasm"
}
],
"rego_version": 1,
"file_rego_versions": {
"/example/example_v0.rego": 0
}
},
"etag": "bar",
"wasm": {
"/authz/allow/policy.wasm": "d2FzbS1tb2R1bGU="
}
}
},
"modules":{
"example/example.rego":{
"rego_version":1
},
"example/example_v0.rego":{
"rego_version":0
}
}
}
}`)
// Ensure that the extra module was included
if _, ok := compiler.Modules["mod1"]; !ok {
t.Fatalf("expected extra module to be compiled")
}
mustDeactivate(t, mockStore, &DeactivateOpts{BundleNames: map[string]struct{}{"bundle1": {}}})
// Expect the store to have been cleared out after deactivating the bundle
verifyReadBundleNames(t, mockStore, nil)
verifyResultRead(t, mockStore, `{"system": {"bundles": {}, "modules": {}}}`)
mockStore.AssertValid(t)
}
func TestBundleLazyModeLifecycleRawInvalidData(t *testing.T) {
tests := map[string]struct {
files [][2]string
err error
}{
"non-object root": {[][2]string{{"/data.json", `[1,2,3]`}}, errors.New("root value must be object")},
"invalid yaml": {[][2]string{{"/a/b/data.yaml", `"foo`}}, errors.New("yaml: found unexpected end of stream")},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
bundle := must(NewCustomReader(NewTarballLoaderWithBaseURL(archive.MustWriteTarGz(tc.files), "")).
WithBundleEtag("bar").
WithLazyLoadingMode(true).
Read())(t)
mockStore := mock.New()
txn := storage.NewTransactionOrDie(t.Context(), mockStore, storage.WriteParams)
err := Activate(&ActivateOpts{
Ctx: t.Context(),
Store: mockStore,
Txn: txn,
Compiler: ast.NewCompiler(),
Metrics: metrics.NoOp(),
Bundles: map[string]*Bundle{"bundle1": &bundle},
})
if err == nil {
t.Fatal("Expected error but got none")
}
if tc.err != nil && tc.err.Error() != err.Error() {
t.Fatalf("Expected error message %v but got %v", tc.err.Error(), err.Error())
}
})
}
}
func TestBundleLazyModeLifecycle(t *testing.T) {
mockStore := mock.New()
compiler := ast.NewCompiler()
extraMods := map[string]*ast.Module{"mod1": ast.MustParseModule("package x\np = true")}
// v1 bundle
b1Files := [][2]string{
{"/.manifest", `{"roots": ["a"], "rego_version": 1}`},
{"a/policy.rego", "package a\np contains 42 if { true }"},
{"/data.json", `{"a": {"b": "foo"}}`},
}
bundle1 := must(NewCustomReader(NewTarballLoaderWithBaseURL(archive.MustWriteTarGz(b1Files), "")).
WithBundleEtag("foo").
WithLazyLoadingMode(true).
WithBundleName("bundle1").
Read())(t)
// v0 bundle
bundle2 := bundleFromFiles(t, "bundle2", [][2]string{
{"/.manifest", `{"roots": ["b", "c"], "rego_version": 0}`},
{"b/policy.rego", `package b
p[42] { true }
`},
{"/data.json", `{}`},
})
bundles := map[string]*Bundle{"bundle1": &bundle1, "bundle2": &bundle2}
mustActivate(t, mockStore, &ActivateOpts{Compiler: compiler, Bundles: bundles, ExtraModules: extraMods})
// Ensure the bundle was activated
verifyReadBundleNames(t, mockStore, nil, util.Keys(bundles)...)
verifyBundleModulesCompiled(t, compiler, bundles)
verifyResultRead(t, mockStore, `{
"a": {
"b": "foo"
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "",
"roots": ["a"],
"rego_version": 1
},
"etag": "foo"
},
"bundle2": {
"manifest": {
"revision": "",
"roots": ["b", "c"],
"rego_version": 0
},
"etag": ""
}
},
"modules":{
"bundle1/a/policy.rego":{
"rego_version":1
},
"bundle2/b/policy.rego":{
"rego_version":0
}
}
}
}`)
// Ensure that the extra module was included
if _, ok := compiler.Modules["mod1"]; !ok {
t.Fatalf("expected extra module to be compiled")
}
mustDeactivate(t, mockStore, &DeactivateOpts{BundleNames: map[string]struct{}{"bundle1": {}, "bundle2": {}}})
// Expect the store to have been cleared out after deactivating the bundles
verifyReadBundleNames(t, mockStore, nil)
verifyResultRead(t, mockStore, `{"system": {"bundles": {}, "modules": {}}}`)
mockStore.AssertValid(t)
}
func TestBundleLazyModeLifecycleRawNoBundleRoots(t *testing.T) {
files := [][2]string{
{"/a/b/c/data.json", "[1,2,3]"},
{"/a/b/d/data.json", "true"},
{"/a/b/y/data.yaml", `foo: 1`},
{"/example/example.rego", `package example`},
{"/data.json", `{"x": {"y": true}, "a": {"b": {"z": true}}}`},
{"/.manifest", `{"revision": "rev-1"}`},
}
bundle := must(NewCustomReader(NewTarballLoaderWithBaseURL(archive.MustWriteTarGz(files), "")).
WithBundleEtag("foo").
WithLazyLoadingMode(true).
Read())(t)
compiler := ast.NewCompiler()
mockStore := mock.New()
bundles := map[string]*Bundle{"bundle1": &bundle}
mustActivate(t, mockStore, &ActivateOpts{Bundles: bundles, Compiler: compiler})
// Ensure the bundle was activated
verifyReadBundleNames(t, mockStore, nil, util.Keys(bundles)...)
verifyBundleModulesCompiled(t, compiler, bundles)
verifyResultRead(t, mockStore, `{
"a": {
"b": {
"c": [1,2,3],
"d": true,
"y": {
"foo": 1
},
"z": true
}
},
"x": {
"y": true
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "rev-1",
"roots": [""]
},
"etag": "foo"
}
}
}
}`)
files = [][2]string{
{"/c/data.json", `{"hello": "world"}`},
{"/.manifest", `{"revision": "rev-2"}`},
}
bundle = must(NewCustomReader(NewTarballLoaderWithBaseURL(archive.MustWriteTarGz(files), "")).
WithBundleEtag("bar").
WithLazyLoadingMode(true).
Read())(t)
mustActivate(t, mockStore, &ActivateOpts{Compiler: compiler, Bundles: map[string]*Bundle{"bundle1": &bundle}})
verifyResultRead(t, mockStore, `{
"c": {
"hello": "world"
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "rev-2",
"roots": [""]
},
"etag": "bar"
}
}
}
}`)
}
func TestBundleLazyModeLifecycleRawNoBundleRootsDiskStorage(t *testing.T) {
test.WithTempFS(nil, func(dir string) {
store := must(disk.New(t.Context(), logging.NewNoOpLogger(), nil, disk.Options{Dir: dir}))(t)
compiler := ast.NewCompiler()
files := [][2]string{
{"/a/b/c/data.json", "[1,2,3]"},
{"/a/b/d/data.json", "true"},
{"/a/b/y/data.yaml", `foo: 1`},
{"/example/example.rego", `package example`},
{"/data.json", `{"x": {"y": true}, "a": {"b": {"z": true}}}`},
{"/.manifest", `{"revision": "rev-1"}`},
}
bundle := must(NewCustomReader(NewTarballLoaderWithBaseURL(archive.MustWriteTarGz(files), "")).
WithBundleEtag("foo").
WithLazyLoadingMode(true).
Read())(t)
bundles := map[string]*Bundle{"bundle1": &bundle}
mustActivate(t, store, &ActivateOpts{Compiler: compiler, Bundles: bundles})
// Ensure the bundle was activated
verifyReadBundleNames(t, store, nil, util.Keys(bundles)...)
verifyBundleModulesCompiled(t, compiler, bundles)
verifyResultRead(t, store, `{
"a": {
"b": {
"c": [1,2,3],
"d": true,
"y": {
"foo": 1
},
"z": true
}
},
"x": {
"y": true
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "rev-1",
"roots": [""]
},
"etag": "foo"
}
}
}
}`)
files = [][2]string{
{"/c/data.json", `{"hello": "world"}`},
{"/.manifest", `{"revision": "rev-2"}`},
}
bundle = must(NewCustomReader(NewTarballLoaderWithBaseURL(archive.MustWriteTarGz(files), "")).
WithBundleEtag("bar").
WithLazyLoadingMode(true).
Read())(t)
bundles = map[string]*Bundle{"bundle1": &bundle}
mustActivate(t, store, &ActivateOpts{Compiler: compiler, Bundles: bundles})
verifyResultRead(t, store, `{
"c": {
"hello": "world"
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "rev-2",
"roots": [""]
},
"etag": "bar"
}
}
}
}`)
})
}
func TestBundleLazyModeLifecycleNoBundleRoots(t *testing.T) {
mockStore := mock.New()
compiler := ast.NewCompiler()
mod1 := "package a\np = true"
b := Bundle{
Manifest: Manifest{Revision: "rev-1"},
Data: unpack(map[string]any{
"a.b": "foo",
"a.e.f": "bar",
"a.x": []map[string]string{{"name": "john"}, {"name": "jane"}},
}),
Modules: []ModuleFile{moduleFile("a/policy.rego", mod1)},
Etag: "foo",
}
bundle1 := bundleFromRoundtrip(t, "bundle1", b)
bundles := map[string]*Bundle{"bundle1": &bundle1}
mustActivate(t, mockStore, &ActivateOpts{Compiler: compiler, Bundles: bundles})
verifyResultRead(t, mockStore, `{
"a": {
"b": "foo",
"e": {
"f": "bar"
},
"x": [{"name": "john"}, {"name": "jane"}]
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "rev-1",
"roots": [""]
},
"etag": ""
}
}
}
}`)
// add a new bundle with no roots. this means all the data from the currently activated should be removed
bundle2 := bundleFromRoundtrip(t, "bundle1", Bundle{
Manifest: Manifest{Revision: "rev-2"},
Data: unpack(map[string]any{"c.hello": "world"}),
Etag: "bar",
})
mustActivate(t, mockStore, &ActivateOpts{Compiler: compiler, Bundles: map[string]*Bundle{"bundle1": &bundle2}})
verifyResultRead(t, mockStore, `{
"c": {
"hello": "world"
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "rev-2",
"roots": [""]
},
"etag": ""
}
}
}
}`)
}
func TestBundleLazyModeLifecycleNoBundleRootsDiskStorage(t *testing.T) {
test.WithTempFS(nil, func(dir string) {
store := must(disk.New(t.Context(), logging.NewNoOpLogger(), nil, disk.Options{Dir: dir}))(t)
compiler := ast.NewCompiler()
mod1 := "package a\np = true"
b := Bundle{
Manifest: Manifest{Revision: "rev-1"},
Data: unpack(map[string]any{
"a.b": "foo",
"a.e.f": "bar",
"a.x": []map[string]string{{"name": "john"}, {"name": "jane"}},
}),
Modules: []ModuleFile{moduleFile("a/policy.rego", mod1)},
Etag: "foo",
}
bundle1 := bundleFromRoundtrip(t, "bundle1", b)
bundles := map[string]*Bundle{"bundle1": &bundle1}
mustActivate(t, store, &ActivateOpts{Compiler: compiler, Bundles: bundles})
// Ensure the snapshot bundle was activated
verifyReadBundleNames(t, store, nil, util.Keys(bundles)...)
verifyBundleModulesCompiled(t, compiler, bundles)
verifyResultRead(t, store, `{
"a": {
"b": "foo",
"e": {
"f": "bar"
},
"x": [{"name": "john"}, {"name": "jane"}]
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "rev-1",
"roots": [""]
},
"etag": ""
}
}
}
}`)
// add a new bundle with no roots. this means all the data from the currently activated should be removed
bundle2 := bundleFromRoundtrip(t, "bundle1", Bundle{
Manifest: Manifest{Revision: "rev-2"},
Data: unpack(map[string]any{"c.hello": "world"}),
Etag: "bar",
})
mustActivate(t, store, &ActivateOpts{Compiler: compiler, Bundles: map[string]*Bundle{"bundle1": &bundle2}})
verifyResultRead(t, store, `{
"c": {
"hello": "world"
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "rev-2",
"roots": [""]
},
"etag": ""
}
}
}
}`)
})
}
func TestBundleLazyModeLifecycleMixBundleTypeActivationDiskStorage(t *testing.T) {
test.WithTempFS(nil, func(dir string) {
store := must(disk.New(t.Context(), logging.NewNoOpLogger(), nil, disk.Options{Dir: dir}))(t)
compiler := ast.NewCompiler()
mod1 := "package a\np = true"
bundle1 := bundleFromRoundtrip(t, "bundle1", Bundle{
Manifest: Manifest{Revision: "snap-1", Roots: &[]string{"a"}},
Data: unpack(map[string]any{
"a.b": "foo",
"a.e.f": "bar",
"a.x": []map[string]string{{"name": "john"}, {"name": "jane"}},
}),
Modules: []ModuleFile{moduleFile("a/policy.rego", mod1)},
Etag: "foo",
})
// create a delta bundle and activate it
// add a new object member
bundle2 := bundleFromRoundtrip(t, "bundle2", Bundle{
Manifest: Manifest{Revision: "delta-1", Roots: &[]string{"x"}},
Patch: Patch{Data: []PatchOperation{{Op: "upsert", Path: "/x/y", Value: []string{"foo", "bar"}}}},
Etag: "bar",
})
bundles := map[string]*Bundle{"bundle1": &bundle1, "bundle2": &bundle2}
mustActivate(t, store, &ActivateOpts{Compiler: compiler, Bundles: bundles})
// Ensure the patches were applied
verifyResultRead(t, store, `{
"a": {
"b": "foo",
"e": {
"f": "bar"
},
"x": [{"name": "john"}, {"name": "jane"}]
},
"x": {
"y": ["foo","bar"]
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "snap-1",
"roots": ["a"]
},
"etag": ""
},
"bundle2": {
"manifest": {
"revision": "delta-1",
"roots": ["x"]
},
"etag": ""
}
}
}
}`)
})
}
func TestBundleLazyModeLifecycleOldBundleEraseDiskStorage(t *testing.T) {
test.WithTempFS(nil, func(dir string) {
store := must(disk.New(t.Context(), logging.NewNoOpLogger(), nil, disk.Options{Dir: dir}))(t)
compiler := ast.NewCompiler()
mod1 := "package a\np = true"
b := Bundle{
Manifest: Manifest{Revision: "rev-1", Roots: &[]string{"a"}},
Data: unpack(map[string]any{
"a.b": "foo",
"a.e.f": "bar",
"a.x": []map[string]string{{"name": "john"}, {"name": "jane"}},
}),
Modules: []ModuleFile{moduleFile("a/policy.rego", mod1)},
Etag: "foo",
}
bundle1 := bundleFromRoundtrip(t, "bundle1", b)
bundles := map[string]*Bundle{"bundle1": &bundle1}
mustActivate(t, store, &ActivateOpts{Compiler: compiler, Bundles: bundles})
// Ensure the snapshot bundle was activated
verifyReadBundleNames(t, store, nil, util.Keys(bundles)...)
verifyBundleModulesCompiled(t, compiler, bundles)
verifyResultRead(t, store, `{
"a": {
"b": "foo",
"e": {
"f": "bar"
},
"x": [{"name": "john"}, {"name": "jane"}]
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "rev-1",
"roots": ["a"]
},
"etag": ""
}
}
}
}`)
// add a new bundle and verify data from the currently activated is removed
bundle2 := bundleFromRoundtrip(t, "bundle1", Bundle{
Manifest: Manifest{Revision: "rev-2", Roots: &[]string{"c"}},
Data: unpack(map[string]any{"c.hello": "world"}),
Etag: "bar",
})
bundles = map[string]*Bundle{"bundle1": &bundle2}
mustActivate(t, store, &ActivateOpts{Compiler: compiler, Bundles: bundles})
// Ensure the snapshot bundle was activated
verifyResultRead(t, store, `{
"c": {
"hello": "world"
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "rev-2",
"roots": ["c"]
},
"etag": ""
}
}
}
}`)
})
}
func TestBundleLazyModeLifecycleRestoreBackupDB(t *testing.T) {
test.WithTempFS(nil, func(dir string) {
store := must(disk.New(t.Context(), logging.NewNoOpLogger(), nil, disk.Options{Dir: dir}))(t)
compiler := ast.NewCompiler()
b := Bundle{
Manifest: Manifest{Revision: "rev-1", Roots: &[]string{"a"}},
Data: unpack(map[string]any{
"a.b": "foo",
"a.e.f": "bar",
"a.x": []map[string]string{{"name": "john"}, {"name": "jane"}},
}),
Modules: []ModuleFile{moduleFile("a/policy.rego", "package a\np = true")},
Etag: "foo",
}
bundle1 := bundleFromRoundtrip(t, "bundle1", b)
bundles := map[string]*Bundle{"bundle1": &bundle1}
mustActivate(t, store, &ActivateOpts{Compiler: compiler, Bundles: bundles})
// Ensure the snapshot bundle was activated
verifyReadBundleNames(t, store, nil, util.Keys(bundles)...)
verifyBundleModulesCompiled(t, compiler, bundles)
verifyResultRead(t, store, `{
"a": {
"b": "foo",
"e": {
"f": "bar"
},
"x": [{"name": "john"}, {"name": "jane"}]
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "rev-1",
"roots": ["a"]
},
"etag": ""
}
}
}
}`)
// add a new bundle but abort the transaction and verify only old the bundle data is kept in store
bundle2 := bundleFromRoundtrip(t, "bundle1", Bundle{
Manifest: Manifest{Revision: "rev-2", Roots: &[]string{"c"}},
Data: unpack(map[string]any{"c.hello": "world"}),
Etag: "bar",
})
// can't use mustActivate here because we want to abort the txn!
txn := storage.NewTransactionOrDie(t.Context(), store, storage.WriteParams)
err := Activate(&ActivateOpts{
Ctx: t.Context(),
Store: store,
Txn: txn,
Metrics: metrics.NoOp(),
Compiler: compiler,
Bundles: map[string]*Bundle{"bundle1": &bundle2},
})
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
store.Abort(t.Context(), txn)
verifyResultRead(t, store, `{
"a": {
"b": "foo",
"e": {
"f": "bar"
},
"x": [{"name": "john"}, {"name": "jane"}]
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "rev-1",
"roots": ["a"]
},
"etag": ""
}
}
}
}`)
// check symlink is created
symlink := filepath.Join(dir, "active")
if _, err := os.Lstat(symlink); err != nil {
t.Fatal(err)
}
// check symlink target
if _, err := filepath.EvalSymlinks(symlink); err != nil {
t.Fatalf("eval symlinks: %v", err)
}
})
}
func TestDeltaBundleLazyModeLifecycleDiskStorage(t *testing.T) {
test.WithTempFS(nil, func(dir string) {
store := must(disk.New(t.Context(), logging.NewNoOpLogger(), nil, disk.Options{Dir: dir}))(t)
compiler := ast.NewCompiler()
mod1 := "package a\np = true"
mod2 := "package b\np = true"
bundle1 := bundleFromRoundtrip(t, "bundle1", Bundle{
Manifest: Manifest{Roots: &[]string{"a"}},
Data: unpack(map[string]any{
"a.b": "foo",
"a.e.f": "bar",
"a.x": []map[string]string{{"name": "john"}, {"name": "jane"}},
}),
Modules: []ModuleFile{moduleFile("a/policy.rego", mod1)},
Etag: "foo",
})
bundle2 := bundleFromRoundtrip(t, "bundle2", Bundle{
Manifest: Manifest{Roots: &[]string{"b", "c"}},
Modules: []ModuleFile{moduleFile("b/policy.rego", mod2)},
Etag: "foo",
})
bundles := map[string]*Bundle{"bundle1": &bundle1, "bundle2": &bundle2}
mustActivate(t, store, &ActivateOpts{Compiler: compiler, Bundles: bundles})
// Ensure the snapshot bundles were activated
verifyReadBundleNames(t, store, nil, util.Keys(bundles)...)
verifyBundleModulesCompiled(t, compiler, bundles)
// create a delta bundle and activate it
deltaBundles := map[string]*Bundle{
"bundle1": {
Manifest: Manifest{Revision: "delta-1", Roots: &[]string{"a"}},
Patch: Patch{Data: []PatchOperation{
{Op: "upsert", Path: "/a/c/d", Value: []string{"foo", "bar"}},
{Op: "upsert", Path: "/a/c/d/-", Value: "baz"}, // append value to array
{Op: "replace", Path: "a/b", Value: "bar"}, // replace a value
}},
Etag: "bar",
},
"bundle2": {
Manifest: Manifest{Revision: "delta-2", Roots: &[]string{"b", "c"}},
Patch: Patch{Data: []PatchOperation{
{Op: "upsert", Path: "/c/d", Value: []string{"foo", "bar"}},
}},
Etag: "baz",
},
"bundle3": {
Manifest: Manifest{Roots: &[]string{"d"}},
Data: unpack(map[string]any{"d.e": "foo"}),
},
}
mustActivate(t, store, &ActivateOpts{Compiler: compiler, Bundles: deltaBundles})
// check the modules from the snapshot bundles are on the compiler
verifyBundleModulesCompiled(t, compiler, bundles)
verifyResultRead(t, store, `{
"a": {
"b": "bar",
"c": {
"d": ["foo", "bar", "baz"]
},
"e": {
"f": "bar"
},
"x": [{"name": "john"}, {"name": "jane"}]
},
"c": {"d": ["foo", "bar"]},
"d": {"e": "foo"},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "delta-1",
"roots": ["a"]
},
"etag": "bar"
},
"bundle2": {
"manifest": {
"revision": "delta-2",
"roots": ["b", "c"]
},
"etag": "baz"
},
"bundle3": {
"manifest": {
"revision": "",
"roots": ["d"]
},
"etag": ""
}
}
}
}`)
})
}
func TestBundleLazyModeLifecycleOverlappingBundleRoots(t *testing.T) {
mockStore := mock.New()
bundle1 := bundleFromRoundtrip(t, "bundle1", Bundle{
Manifest: Manifest{Revision: "foo", Roots: &[]string{"a/b", "a/c", "a/d"}},
Data: unpack(map[string]any{
"a.b": "foo",
"a.c.d": "bar",
"a.d": []map[string]string{{"name": "john"}, {"name": "jane"}},
}),
})
bundle2 := bundleFromRoundtrip(t, "bundle2", Bundle{
Manifest: Manifest{Revision: "bar", Roots: &[]string{"a/e"}},
Data: unpack(map[string]any{"a.e.f": "bar"}),
})
bundles := map[string]*Bundle{"bundle1": &bundle1, "bundle2": &bundle2}
mustActivate(t, mockStore, &ActivateOpts{Bundles: bundles})
// Ensure the snapshot bundles were activated
verifyReadBundleNames(t, mockStore, nil, util.Keys(bundles)...)
verifyResultRead(t, mockStore, `{
"a": {
"b": "foo",
"c": {
"d": "bar"
},
"e": {
"f": "bar"
},
"d": [{"name": "john"}, {"name": "jane"}]
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "foo",
"roots": ["a/b", "a/c", "a/d"]
},
"etag": ""
},
"bundle2": {
"manifest": {
"revision": "bar",
"roots": ["a/e"]
},
"etag": ""
}
}
}
}`)
}
func TestBundleLazyModeLifecycleOverlappingBundleRootsDiskStorage(t *testing.T) {
test.WithTempFS(nil, func(dir string) {
store := must(disk.New(t.Context(), logging.NewNoOpLogger(), nil, disk.Options{Dir: dir}))(t)
compiler := ast.NewCompiler()
bundle1 := bundleFromRoundtrip(t, "bundle1", Bundle{
Manifest: Manifest{Revision: "foo", Roots: &[]string{"a/b/c", "a/b/d", "a/b/e"}},
Data: unpack(map[string]any{
"a.b.c": "bar",
"a.b.d": []map[string]string{{"name": "john"}, {"name": "jane"}},
"a.b.e": []string{"foo", "bar"},
}),
})
bundle2 := bundleFromRoundtrip(t, "bundle2", Bundle{
Manifest: Manifest{Revision: "bar", Roots: &[]string{"a/b/f"}},
Data: unpack(map[string]any{"a.b.f.hello": "world"}),
})
bundles := map[string]*Bundle{"bundle1": &bundle1, "bundle2": &bundle2}
mustActivate(t, store, &ActivateOpts{Compiler: compiler, Bundles: bundles})
// Ensure the snapshot bundles were activated
verifyReadBundleNames(t, store, nil, util.Keys(bundles)...)
// Ensure the patches were applied
verifyResultRead(t, store, `{
"a": {
"b": {
"c": "bar",
"d": [{"name": "john"}, {"name": "jane"}],
"e": ["foo", "bar"],
"f": {"hello": "world"}
}
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "foo",
"roots": ["a/b/c", "a/b/d", "a/b/e"]
},
"etag": ""
},
"bundle2": {
"manifest": {
"revision": "bar",
"roots": ["a/b/f"]
},
"etag": ""
}
}
}
}`)
})
}
func TestBundleLazyModeLifecycleRawOverlappingBundleRoots(t *testing.T) {
bundle1 := bundleFromFiles(t, "bundle1", [][2]string{
{"/a/b/x/data.json", "[1,2,3]"},
{"/a/c/y/data.json", "true"},
{"/a/d/z/data.yaml", `foo: 1`},
{"/data.json", `{"a": {"b": {"z": true}}}`},
{"/.manifest", `{"revision": "foo", "roots": ["a/b", "a/c", "a/d"]}`},
})
bundle2 := bundleFromFiles(t, "bundle2", [][2]string{
{"/a/e/x/data.json", "[4,5,6]"},
{"/data.json", `{"a": {"e": {"f": true}}}`},
{"/.manifest", `{"revision": "bar", "roots": ["a/e"]}`},
})
bundles := map[string]*Bundle{"bundle1": &bundle1, "bundle2": &bundle2}
mockStore := mock.New()
mustActivate(t, mockStore, &ActivateOpts{Bundles: bundles})
// Ensure the snapshot bundles were activated
verifyReadBundleNames(t, mockStore, nil, util.Keys(bundles)...)
// Ensure the patches were applied
verifyResultRead(t, mockStore, `{
"a": {
"b": {
"x": [1,2,3],
"z": true
},
"c": {
"y": true
},
"d": {
"z": {"foo": 1}
},
"e": {
"x": [4,5,6],
"f": true
}
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "foo",
"roots": ["a/b", "a/c", "a/d"]
},
"etag": ""
},
"bundle2": {
"manifest": {
"revision": "bar",
"roots": ["a/e"]
},
"etag": ""
}
}
}
}`)
}
func TestBundleLazyModeLifecycleRawOverlappingBundleRootsDiskStorage(t *testing.T) {
test.WithTempFS(nil, func(dir string) {
store := must(disk.New(t.Context(), logging.NewNoOpLogger(), nil, disk.Options{Dir: dir}))(t)
compiler := ast.NewCompiler()
bundle1 := bundleFromFiles(t, "bundle1", [][2]string{
{"/a/b/u/data.json", "[1,2,3]"},
{"/a/b/v/data.json", "true"},
{"/a/b/w/data.yaml", `foo: 1`},
{"/data.json", `{"a": {"b": {"x": true}}}`},
{"/.manifest", `{"revision": "foo", "roots": ["a/b"]}`},
})
bundle2 := bundleFromFiles(t, "bundle2", [][2]string{
{"/a/c/x/data.json", "[4,5,6]"},
{"/data.json", `{"a": {"c": {"y": true}}}`},
{"/.manifest", `{"revision": "bar", "roots": ["a/c"]}`},
})
bundles := map[string]*Bundle{"bundle1": &bundle1, "bundle2": &bundle2}
mustActivate(t, store, &ActivateOpts{Compiler: compiler, Bundles: bundles})
// Ensure the snapshot bundles were activated
verifyReadBundleNames(t, store, nil, util.Keys(bundles)...)
// Ensure the patches were applied
verifyResultRead(t, store, `{
"a": {
"b": {
"u": [1,2,3],
"v": true,
"w": {"foo": 1},
"x": true
},
"c": {
"x": [4,5,6],
"y": true
}
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "foo",
"roots": ["a/b"]
},
"etag": ""
},
"bundle2": {
"manifest": {
"revision": "bar",
"roots": ["a/c"]
},
"etag": ""
}
}
}
}`)
})
}
func TestDeltaBundleLazyModeLifecycle(t *testing.T) {
mockStore := mock.New()
compiler := ast.NewCompiler()
mod1 := "package a\np = true"
mod2 := "package b\np = true"
bundle1 := bundleFromRoundtrip(t, "bundle1", Bundle{
Manifest: Manifest{Roots: &[]string{"a"}},
Data: unpack(map[string]any{
"a.b": "foo",
"a.e.f": "bar",
"a.x": []map[string]string{{"name": "john"}, {"name": "jane"}},
}),
Modules: []ModuleFile{moduleFile("policy.rego", mod1)},
Etag: "foo",
})
bundle2 := bundleFromRoundtrip(t, "bundle2", Bundle{
Manifest: Manifest{Roots: &[]string{"b", "c"}},
Modules: []ModuleFile{moduleFile("policy.rego", mod2)},
Etag: "foo",
lazyLoadingMode: true,
sizeLimitBytes: DefaultSizeLimitBytes + 1,
})
bundles := map[string]*Bundle{"bundle1": &bundle1, "bundle2": &bundle2}
mustActivate(t, mockStore, &ActivateOpts{Compiler: compiler, Bundles: bundles})
// Ensure the snapshot bundles were activated
verifyReadBundleNames(t, mockStore, nil, util.Keys(bundles)...)
verifyBundleModulesCompiled(t, compiler, bundles)
// create a delta bundle and activate it
deltaBundles := map[string]*Bundle{
"bundle1": {
Manifest: Manifest{Revision: "delta-1", Roots: &[]string{"a"}},
Patch: Patch{Data: []PatchOperation{
{Op: "upsert", Path: "/a/c/d", Value: []string{"foo", "bar"}},
{Op: "upsert", Path: "/a/c/d/-", Value: "baz"},
{Op: "upsert", Path: "/a/x/1", Value: map[string]string{"name": "alice"}},
{Op: "replace", Path: "a/b", Value: "bar"},
{Op: "remove", Path: "a/e"},
{Op: "upsert", Path: "a/y/~0z", Value: []int{1, 2, 3}},
}},
Etag: "bar",
},
"bundle2": {
Manifest: Manifest{Revision: "delta-2", Roots: &[]string{"b", "c"}},
Patch: Patch{Data: []PatchOperation{{Op: "upsert", Path: "/c/d", Value: []string{"foo", "bar"}}}},
Etag: "baz",
},
"bundle3": {
Manifest: Manifest{Roots: &[]string{"d"}},
Data: unpack(map[string]any{"d.e": "foo"}),
},
}
mustActivate(t, mockStore, &ActivateOpts{Compiler: compiler, Bundles: deltaBundles})
// check the modules from the snapshot bundles are on the compiler
verifyBundleModulesCompiled(t, compiler, bundles)
// Ensure the patches were applied
verifyResultRead(t, mockStore, `{
"a": {
"b": "bar",
"c": {
"d": ["foo", "bar", "baz"]
},
"x": [{"name": "john"}, {"name": "alice"}, {"name": "jane"}],
"y": {"~z": [1, 2, 3]}
},
"c": {"d": ["foo", "bar"]},
"d": {"e": "foo"},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "delta-1",
"roots": ["a"]
},
"etag": "bar"
},
"bundle2": {
"manifest": {
"revision": "delta-2",
"roots": ["b", "c"]
},
"etag": "baz"
},
"bundle3": {
"manifest": {
"revision": "",
"roots": ["d"]
},
"etag": ""
}
}
}
}`)
mockStore.AssertValid(t)
}
func TestDeltaBundleLazyModeWithDefaultRules(t *testing.T) {
mockStore := mock.New()
compiler := ast.NewCompiler()
bundle1 := bundleFromRoundtrip(t, "bundle1", Bundle{
Manifest: Manifest{Roots: &[]string{"a"}},
Data: unpack(map[string]any{
"a.b": "foo",
"a.e.f": "bar",
"a.x": []map[string]string{{"name": "john"}, {"name": "jane"}},
}),
Modules: []ModuleFile{moduleFile("policy.rego", "package a\ndefault p = true")},
Etag: "foo",
})
bundle2 := bundleFromRoundtrip(t, "bundle2", Bundle{
Manifest: Manifest{Roots: &[]string{"b", "c"}},
Modules: []ModuleFile{moduleFile("policy.rego", "package b\ndefault p = true")},
Etag: "foo",
lazyLoadingMode: true,
sizeLimitBytes: DefaultSizeLimitBytes + 1,
})
bundles := map[string]*Bundle{"bundle1": &bundle1, "bundle2": &bundle2}
mustActivate(t, mockStore, &ActivateOpts{Compiler: compiler, Bundles: bundles})
// Ensure the snapshot bundles were activated
verifyReadBundleNames(t, mockStore, nil, util.Keys(bundles)...)
verifyBundleModulesCompiled(t, compiler, bundles)
// create a delta bundle and activate it
// add a new object member
p1 := PatchOperation{Op: "upsert", Path: "/a/c/d", Value: []string{"foo", "bar"}}
// append value to array
p2 := PatchOperation{Op: "upsert", Path: "/a/c/d/-", Value: "baz"}
// insert value in array
p3 := PatchOperation{Op: "upsert", Path: "/a/x/1", Value: map[string]string{"name": "alice"}}
// replace a value
p4 := PatchOperation{Op: "replace", Path: "a/b", Value: "bar"}
// remove a value
p5 := PatchOperation{Op: "remove", Path: "a/e"}
// add a new object with an escaped character in the path
p6 := PatchOperation{Op: "upsert", Path: "a/y/~0z", Value: []int{1, 2, 3}}
// add a new object root
p7 := PatchOperation{Op: "upsert", Path: "/c/d", Value: []string{"foo", "bar"}}
deltaBundles := map[string]*Bundle{
"bundle1": {
Manifest: Manifest{Revision: "delta-1", Roots: &[]string{"a"}},
Patch: Patch{Data: []PatchOperation{p1, p2, p3, p4, p5, p6}},
Etag: "bar",
},
"bundle2": {
Manifest: Manifest{Revision: "delta-2", Roots: &[]string{"b", "c"}},
Patch: Patch{Data: []PatchOperation{p7}},
Etag: "baz",
},
"bundle3": {
Manifest: Manifest{Roots: &[]string{"d"}},
Data: unpack(map[string]any{"d.e": "foo"}),
},
}
expectedModuleCount := len(compiler.Modules)
mustActivate(t, mockStore, &ActivateOpts{Compiler: compiler, Bundles: deltaBundles})
if expectedModuleCount != len(compiler.Modules) {
t.Fatalf("Expected %d modules, got %d", expectedModuleCount, len(compiler.Modules))
}
// check the modules from the snapshot bundles are on the compiler
verifyBundleModulesCompiled(t, compiler, bundles)
// Ensure the patches were applied
verifyResultRead(t, mockStore, `{
"a": {
"b": "bar",
"c": {
"d": ["foo", "bar", "baz"]
},
"x": [{"name": "john"}, {"name": "alice"}, {"name": "jane"}],
"y": {"~z": [1, 2, 3]}
},
"c": {"d": ["foo", "bar"]},
"d": {"e": "foo"},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "delta-1",
"roots": ["a"]
},
"etag": "bar"
},
"bundle2": {
"manifest": {
"revision": "delta-2",
"roots": ["b", "c"]
},
"etag": "baz"
},
"bundle3": {
"manifest": {
"revision": "",
"roots": ["d"]
},
"etag": ""
}
}
}
}`)
mockStore.AssertValid(t)
}
func TestBundleLifecycle(t *testing.T) {
tests := []struct {
note string
readAst bool
}{
{note: "read raw", readAst: false},
{note: "read ast", readAst: true},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
mockStore := mock.New(inmem.OptReturnASTValuesOnRead(tc.readAst))
compiler := ast.NewCompiler()
extraMods := map[string]*ast.Module{
"mod1": ast.MustParseModule("package x\np = true"),
}
bundles := map[string]*Bundle{
"bundle1": {
Manifest: Manifest{Roots: &[]string{"a"}},
Data: unpack(map[string]any{"a.b": "foo"}),
Modules: []ModuleFile{moduleFile("a/policy.rego", "package a\np = true")},
Etag: "foo",
},
"bundle2": {
Manifest: Manifest{Roots: &[]string{"b", "c"}},
Modules: []ModuleFile{moduleFile("b/policy.rego", "package b\np = true")},
},
}
mustActivate(t, mockStore, &ActivateOpts{Compiler: compiler, Bundles: bundles, ExtraModules: extraMods})
// Ensure the bundle was activated
verifyReadBundleNames(t, mockStore, nil, util.Keys(bundles)...)
verifyBundleModulesCompiled(t, compiler, bundles)
actual := mustRead(t, mockStore, nil, storage.RootPath)
expectedRaw := `{
"a": {
"b": "foo"
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "",
"roots": ["a"]
},
"etag": "foo"
},
"bundle2": {
"manifest": {
"revision": "",
"roots": ["b", "c"]
},
"etag": ""
}
},
"modules": {
"bundle1/a/policy.rego": {
"rego_version": 1
},
"bundle2/b/policy.rego": {
"rego_version": 1
}
}
}
}`
assertEqual(t, tc.readAst, expectedRaw, actual)
// Ensure that the extra module was included
if _, ok := compiler.Modules["mod1"]; !ok {
t.Fatalf("expected extra module to be compiled")
}
mustDeactivate(t, mockStore, &DeactivateOpts{
BundleNames: map[string]struct{}{"bundle1": {}, "bundle2": {}},
})
// Expect the store to have been cleared out after deactivating the bundles
txn := storage.NewTransactionOrDie(t.Context(), mockStore)
verifyReadBundleNames(t, mockStore, txn)
expectedRaw = `{"system": {"bundles": {}, "modules": {}}}`
assertEqual(t, tc.readAst, expectedRaw, mustRead(t, mockStore, txn, storage.RootPath))
mockStore.Abort(t.Context(), txn)
mockStore.AssertValid(t)
})
}
}
func TestDeltaBundleLifecycle(t *testing.T) {
tests := []struct {
note string
readAst bool
}{
{note: "read raw", readAst: false},
{note: "read ast", readAst: true},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
mockStore := mock.New(inmem.OptReturnASTValuesOnRead(tc.readAst))
compiler := ast.NewCompiler()
bundles := map[string]*Bundle{
"bundle1": {
Manifest: Manifest{Roots: &[]string{"a"}},
Data: unpack(map[string]any{
"a.b": "foo",
"a.e.f": "bar",
"a.x": []map[string]string{{"name": "john"}, {"name": "jane"}},
}),
Modules: []ModuleFile{moduleFile("a/policy.rego", "package a\ndefault p = true")},
Etag: "foo",
},
"bundle2": {
Manifest: Manifest{Roots: &[]string{"b", "c"}},
Modules: []ModuleFile{moduleFile("b/policy.rego", "package b\ndefault p = true")},
},
}
mustActivate(t, mockStore, &ActivateOpts{Compiler: compiler, Bundles: bundles})
// Ensure the snapshot bundles were activated
verifyReadBundleNames(t, mockStore, nil, util.Keys(bundles)...)
verifyBundleModulesCompiled(t, compiler, bundles)
// create a delta bundle and activate it
// add a new object member
p1 := PatchOperation{Op: "upsert", Path: "/a/c/d", Value: []string{"foo", "bar"}}
// append value to array
p2 := PatchOperation{Op: "upsert", Path: "/a/c/d/-", Value: "baz"}
// insert value in array
p3 := PatchOperation{Op: "upsert", Path: "/a/x/1", Value: map[string]string{"name": "alice"}}
// replace a value
p4 := PatchOperation{Op: "replace", Path: "a/b", Value: "bar"}
// remove a value
p5 := PatchOperation{Op: "remove", Path: "a/e"}
// add a new object with an escaped character in the path
p6 := PatchOperation{Op: "upsert", Path: "a/y/~0z", Value: []int{1, 2, 3}}
// add a new object root
p7 := PatchOperation{Op: "upsert", Path: "/c/d", Value: []string{"foo", "bar"}}
deltaBundles := map[string]*Bundle{
"bundle1": {
Manifest: Manifest{Revision: "delta-1", Roots: &[]string{"a"}},
Patch: Patch{Data: []PatchOperation{p1, p2, p3, p4, p5, p6}},
Etag: "bar",
},
"bundle2": {
Manifest: Manifest{Revision: "delta-2", Roots: &[]string{"b", "c"}},
Patch: Patch{Data: []PatchOperation{p7}},
Etag: "baz",
},
"bundle3": {
Manifest: Manifest{Roots: &[]string{"d"}},
Data: unpack(map[string]any{"d.e": "foo"}),
},
}
mustActivate(t, mockStore, &ActivateOpts{Compiler: compiler, Bundles: deltaBundles})
// check the modules from the snapshot bundles are on the compiler
verifyBundleModulesCompiled(t, compiler, bundles)
// Ensure the patches were applied
actual := mustRead(t, mockStore, nil, storage.RootPath)
expectedRaw := `{
"a": {
"b": "bar",
"c": {
"d": ["foo", "bar", "baz"]
},
"x": [{"name": "john"}, {"name": "alice"}, {"name": "jane"}],
"y": {"~z": [1, 2, 3]}
},
"c": {"d": ["foo", "bar"]},
"d": {"e": "foo"},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "delta-1",
"roots": ["a"]
},
"etag": "bar"
},
"bundle2": {
"manifest": {
"revision": "delta-2",
"roots": ["b", "c"]
},
"etag": "baz"
},
"bundle3": {
"manifest": {
"revision": "",
"roots": ["d"]
},
"etag": ""
}
},
"modules":{
"bundle1/a/policy.rego":{
"rego_version":1
},
"bundle2/b/policy.rego":{
"rego_version":1
}
}
}
}`
assertEqual(t, tc.readAst, expectedRaw, actual)
mockStore.AssertValid(t)
})
}
}
func TestDeltaBundleActivate(t *testing.T) {
tests := []struct {
note string
readAst bool
}{
{note: "read raw", readAst: false},
{note: "read ast", readAst: true},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
mockStore := mock.New(inmem.OptReturnASTValuesOnRead(tc.readAst))
// create a delta bundle
p1 := PatchOperation{Op: "upsert", Path: "/a/c/d", Value: []string{"foo", "bar"}}
deltaBundles := map[string]*Bundle{"bundle1": {
Manifest: Manifest{Revision: "delta", Roots: &[]string{"a"}},
Patch: Patch{Data: []PatchOperation{p1}},
Etag: "foo",
}}
mustActivate(t, mockStore, &ActivateOpts{Bundles: deltaBundles})
txn := storage.NewTransactionOrDie(t.Context(), mockStore)
// Ensure the delta bundle was activated
verifyReadBundleNames(t, mockStore, txn, util.Keys(deltaBundles)...)
// Ensure the patches were applied
actual := mustRead(t, mockStore, txn, storage.RootPath)
expectedRaw := `{
"a": {
"c": {
"d": ["foo", "bar"]
}
},
"system": {
"bundles": {
"bundle1": {
"manifest": {
"revision": "delta",
"roots": ["a"]
},
"etag": "foo"
}
}
}
}`
assertEqual(t, tc.readAst, expectedRaw, actual)
// Stop the "read" transaction
mockStore.Abort(t.Context(), txn)
mockStore.AssertValid(t)
})
}
}
func TestDeltaBundleBadManifest(t *testing.T) {
mockStore := mock.New()
bundles := map[string]*Bundle{"bundle1": {
Manifest: Manifest{Roots: &[]string{"a"}},
Modules: []ModuleFile{moduleFile("a/policy.rego", "package a\np = true")},
}}
mustActivate(t, mockStore, &ActivateOpts{Bundles: bundles})
// Ensure the snapshot bundle was activated
verifyReadBundleNames(t, mockStore, nil, util.Keys(bundles)...)
// create a delta bundle with a different manifest from the snapshot bundle
deltaBundles := map[string]*Bundle{"bundle1": {
Manifest: Manifest{Roots: &[]string{"b"}},
Patch: Patch{Data: []PatchOperation{{Op: "upsert", Path: "/a/c/d", Value: []string{"foo", "bar"}}}},
}}
txn := storage.NewTransactionOrDie(t.Context(), mockStore, storage.WriteParams)
err := Activate(&ActivateOpts{
Ctx: t.Context(),
Store: mockStore,
Txn: txn,
Compiler: ast.NewCompiler(),
Metrics: metrics.NoOp(),
Bundles: deltaBundles,
})
if err == nil {
t.Fatal("expected error but got nil")
}
expected := "delta bundle 'bundle1' has wasm resolvers or manifest roots that are different from those in the store"
if err.Error() != expected {
t.Fatalf("Expected error %v but got %v", expected, err.Error())
}
mockStore.AssertValid(t)
}
func TestEraseData(t *testing.T) {
storeReadModes := []struct {
note string
readAst bool
}{
{note: "read raw", readAst: false},
{note: "read ast", readAst: true},
}
cases := []struct {
note string
initialData map[string]any
roots []string
expectErr bool
expected string
}{
{
note: "erase all",
initialData: map[string]any{
"a.b": "foo",
"b": "bar",
},
roots: []string{"a", "b"},
expected: `{}`,
},
{
note: "erase none",
initialData: map[string]any{
"a.b": "foo",
"b": "bar",
},
roots: []string{},
expected: `{"a": {"b": "foo"}, "b": "bar"}`,
},
{
note: "erase partial",
initialData: map[string]any{
"a.b": "foo",
"b": "bar",
},
roots: []string{"a"},
expected: `{"b": "bar"}`,
},
{
note: "erase partial path",
initialData: map[string]any{
"a.b": "foo",
"a.c.d": 123,
},
roots: []string{"a/c/d"},
expected: `{"a": {"b": "foo", "c":{}}}`,
},
}
for _, rm := range storeReadModes {
t.Run(rm.note, func(t *testing.T) {
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
mockStore := mock.NewWithData(unpack(tc.initialData), inmem.OptReturnASTValuesOnRead(rm.readAst))
txn := storage.NewTransactionOrDie(t.Context(), mockStore, storage.WriteParams)
roots := map[string]struct{}{}
for _, root := range tc.roots {
roots[root] = struct{}{}
}
if err := eraseData(t.Context(), mockStore, txn, roots); !tc.expectErr && err != nil {
t.Fatalf("unepected error: %s", err)
} else if tc.expectErr && err == nil {
t.Fatalf("expected error, got: %s", err)
}
mustCommit(t, mockStore, txn)
mockStore.AssertValid(t)
txn = storage.NewTransactionOrDie(t.Context(), mockStore)
assertEqual(t, rm.readAst, tc.expected, mustRead(t, mockStore, txn, storage.RootPath))
})
}
})
}
}
func TestErasePolicies(t *testing.T) {
cases := []struct {
note string
initialPolicies map[string][]byte
roots []string
expectErr bool
expectedRemaining []string
}{
{
note: "erase all",
initialPolicies: map[string][]byte{
"mod1": []byte("package a\np = true"),
},
roots: []string{""},
expectedRemaining: []string{},
},
{
note: "erase none",
initialPolicies: map[string][]byte{
"mod1": []byte("package a\np = true"),
"mod2": []byte("package b\np = true"),
},
roots: []string{"c"},
expectedRemaining: []string{"mod1", "mod2"},
},
{
note: "erase correct paths",
initialPolicies: map[string][]byte{
"mod1": []byte("package a.test\np = true"),
"mod2": []byte("package a.test_v2\np = true"),
},
roots: []string{"a/test"},
expectedRemaining: []string{"mod2"},
},
{
note: "erase some",
initialPolicies: map[string][]byte{
"mod1": []byte("package a\np = true"),
"mod2": []byte("package b\np = true"),
},
roots: []string{"b"},
expectedRemaining: []string{"mod1"},
},
{
note: "error: parsing module",
initialPolicies: map[string][]byte{
"mod1": []byte("package a\np = true"),
"mod2": []byte("bad-policy-syntax"),
},
roots: []string{"b"},
expectErr: true,
expectedRemaining: []string{"mod1"},
},
}
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
mockStore := mock.New()
txn := storage.NewTransactionOrDie(t.Context(), mockStore, storage.WriteParams)
for name, mod := range tc.initialPolicies {
if err := mockStore.UpsertPolicy(t.Context(), txn, name, mod); err != nil {
t.Fatalf("unexpected error: %s", err)
}
}
roots := map[string]struct{}{}
for _, root := range tc.roots {
roots[root] = struct{}{}
}
remaining, _, err := erasePolicies(t.Context(), mockStore, txn, ast.ParserOptions{}, roots)
if !tc.expectErr && err != nil {
t.Fatalf("unepected error: %s", err)
} else if tc.expectErr && err == nil {
t.Fatalf("expected error, got: %s", err)
}
if !tc.expectErr {
if len(remaining) != len(tc.expectedRemaining) {
t.Fatalf("expected %d modules remaining, got %d", len(tc.expectedRemaining), len(remaining))
}
for _, name := range tc.expectedRemaining {
if _, ok := remaining[name]; !ok {
t.Fatalf("expected remaining module %s not found", name)
}
}
mustCommit(t, mockStore, txn)
mockStore.AssertValid(t)
txn = storage.NewTransactionOrDie(t.Context(), mockStore)
actualRemaining := must(mockStore.ListPolicies(t.Context(), txn))(t)
if len(actualRemaining) != len(tc.expectedRemaining) {
t.Fatalf("expected %d modules remaining in the store, got %d", len(tc.expectedRemaining), len(actualRemaining))
}
for _, expectedName := range tc.expectedRemaining {
found := slices.Contains(actualRemaining, expectedName)
if !found {
t.Fatalf("expected remaining module %s not found", expectedName)
}
}
}
})
}
}
func TestWriteData(t *testing.T) {
storeReadModes := []struct {
note string
readAst bool
}{
{note: "read raw", readAst: false},
{note: "read ast", readAst: true},
}
cases := []struct {
note string
existingData map[string]any
roots []string
data map[string]any
expected string
expectErr bool
}{
{
note: "single root",
roots: []string{"a"},
data: map[string]any{"a.b.c": 123},
expected: `{"a": {"b": {"c": 123}}}`,
},
{
note: "multiple roots",
roots: []string{"a", "b/c/d"},
data: map[string]any{
"a": "foo",
"b.c.d": "bar",
},
expected: `{"a": "foo","b": {"c": {"d": "bar"}}}`,
},
{
note: "data not in roots",
roots: []string{"a"},
data: map[string]any{
"a": "foo",
"b.c.d": "bar",
},
expected: `{"a": "foo"}`,
},
{
note: "no data",
roots: []string{"a"},
existingData: map[string]any{},
data: map[string]any{},
expected: `{}`,
},
{
note: "no new data",
roots: []string{"a"},
existingData: map[string]any{"a": "foo"},
data: map[string]any{},
expected: `{"a": "foo"}`,
},
{
note: "overwrite data",
roots: []string{"a"},
existingData: map[string]any{"a.b": "foo"},
data: map[string]any{"a": "bar"},
expected: `{"a": "bar"}`,
},
}
for _, rm := range storeReadModes {
t.Run(rm.note, func(t *testing.T) {
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
mockStore := mock.NewWithData(unpack(tc.existingData), inmem.OptReturnASTValuesOnRead(rm.readAst))
txn := storage.NewTransactionOrDie(t.Context(), mockStore, storage.WriteParams)
err := writeData(t.Context(), mockStore, txn, tc.roots, unpack(tc.data))
if !tc.expectErr && err != nil {
t.Fatalf("unepected error: %s", err)
} else if tc.expectErr && err == nil {
t.Fatalf("expected error, got: %s", err)
}
mustCommit(t, mockStore, txn)
mockStore.AssertValid(t)
txn = storage.NewTransactionOrDie(t.Context(), mockStore)
assertEqual(t, rm.readAst, tc.expected, mustRead(t, mockStore, txn, storage.RootPath))
})
}
})
}
}
type testWriteModuleCase struct {
note string
bundles map[string]*Bundle // Only need to give raw text and path for modules
extraMods map[string]*ast.Module
compilerMods map[string]*ast.Module
storeData map[string]any
expectErr bool
}
func TestWriteModules(t *testing.T) {
mod1 := ast.MustParseModule("package a\np = true")
mod2 := ast.MustParseModule("package b\np = false")
bundles := map[string]*Bundle{"bundle1": {Modules: []ModuleFile{moduleFile("mod1", "package a\np = true")}}}
cases := []testWriteModuleCase{
{
note: "module files only",
bundles: bundles,
},
{
note: "extra modules only",
extraMods: map[string]*ast.Module{"mod1": mod1},
},
{
note: "compiler modules only",
compilerMods: map[string]*ast.Module{"mod1": mod1},
},
{
note: "module files and extra modules",
bundles: bundles,
extraMods: map[string]*ast.Module{"mod2": mod2},
},
{
note: "module files and compiler modules",
bundles: bundles,
compilerMods: map[string]*ast.Module{"mod2": mod2},
},
{
note: "extra modules and compiler modules",
extraMods: map[string]*ast.Module{"mod1": mod1},
compilerMods: map[string]*ast.Module{"mod2": mod2},
},
{
note: "compile error: path conflict",
bundles: bundles,
storeData: unpack(map[string]any{"a.p": "foo"}),
expectErr: true,
},
}
for _, tc := range cases {
testWriteData(t, tc, false)
testWriteData(t, tc, true)
}
}
func testWriteData(t *testing.T, tc testWriteModuleCase, legacy bool) {
t.Helper()
testName := tc.note
if legacy {
testName += "_legacy"
}
t.Run(testName, func(t *testing.T) {
mockStore := mock.NewWithData(tc.storeData)
txn := storage.NewTransactionOrDie(t.Context(), mockStore, storage.WriteParams)
compiler := ast.NewCompiler().WithPathConflictsCheck(storage.NonEmpty(t.Context(), mockStore, txn))
// if supplied, pre-parse the module files
for _, b := range tc.bundles {
parsedMods := make([]ModuleFile, 0, len(b.Modules))
for _, mf := range b.Modules {
parsedMods = append(parsedMods, ModuleFile{
Path: mf.Path,
Raw: mf.Raw,
Parsed: ast.MustParseModule(string(mf.Raw)),
})
}
b.Modules = parsedMods
}
// if supplied, setup the compiler with modules already compiled on it
if len(tc.compilerMods) > 0 {
if compiler.Compile(tc.compilerMods); len(compiler.Errors) > 0 {
t.Fatalf("unexpected error: %s", compiler.Errors)
}
}
err := writeModules(t.Context(), mockStore, txn, compiler, metrics.NoOp(), tc.bundles, tc.extraMods, legacy)
if !tc.expectErr && err != nil {
t.Fatalf("unepected error: %s", err)
} else if tc.expectErr && err == nil {
t.Fatalf("expected error, got: %s", err)
}
if !tc.expectErr {
// ensure all policy files were saved to storage
expectedNumMods := 0
for _, b := range tc.bundles {
expectedNumMods += len(b.Modules)
}
policies := must(mockStore.ListPolicies(t.Context(), txn))(t)
if len(policies) != expectedNumMods {
t.Fatalf("expected %d policies in storage, found %d", expectedNumMods, len(policies))
}
for bundleName, b := range tc.bundles {
for _, mf := range b.Modules {
found := false
for _, p := range policies {
var expectedPath string
if legacy {
expectedPath = mf.Path
} else {
expectedPath = filepath.Join(bundleName, mf.Path)
}
if p == expectedPath {
found = true
break
}
}
if !found {
t.Fatalf("policy %s not found in storage", mf.Path)
}
}
}
// ensure all the modules were compiled together and we aren't missing any
expectedModCount := expectedNumMods + len(tc.extraMods) + len(tc.compilerMods)
if len(compiler.Modules) != expectedModCount {
t.Fatalf("expected %d modules on compiler, found %d", expectedModCount, len(compiler.Modules))
}
for moduleName := range compiler.Modules {
found := false
if _, ok := tc.extraMods[moduleName]; ok {
continue
}
if _, ok := tc.compilerMods[moduleName]; ok {
continue
}
for bundleName, b := range tc.bundles {
if legacy {
for _, mf := range b.Modules {
if moduleName == mf.Path {
found = true
break
}
}
} else {
for bundleModuleName := range b.ParsedModules(bundleName) {
if moduleName == bundleModuleName {
found = true
break
}
}
}
}
if found {
continue
}
t.Errorf("unexpected module %s on compiler", moduleName)
}
}
mustCommit(t, mockStore, txn)
mockStore.AssertValid(t)
})
}
func TestDoDFS(t *testing.T) {
cases := []struct {
note string
input map[string]json.RawMessage
path string
roots []string
wantErr bool
err error
}{
{
note: "bundle owns all",
path: "/",
roots: []string{""},
},
{
note: "data within roots root case",
input: map[string]json.RawMessage{"a": json.RawMessage(`true`)},
path: "",
roots: []string{"a"},
},
{
note: "data within roots nested 1",
input: map[string]json.RawMessage{"d": json.RawMessage(`true`)},
path: filepath.Dir("a/b/c/data.json"),
roots: []string{"a/b/c"},
},
{
note: "data within roots nested 2",
input: map[string]json.RawMessage{"d": json.RawMessage(`{"hello": "world"}`)},
path: filepath.Dir("a/b/c/data.json"),
roots: []string{"a/b/c"},
},
{
note: "data within roots nested 3",
input: map[string]json.RawMessage{"d": json.RawMessage(`{"hello": "world"}`)},
path: filepath.Dir("a/data.json"),
roots: []string{"a/d"},
},
{
note: "data within multiple roots 1",
input: map[string]json.RawMessage{"a": json.RawMessage(`{"b": "c"}`), "c": json.RawMessage(`true`)},
path: ".",
roots: []string{"a/b", "c"},
},
{
note: "data within multiple roots 2",
input: map[string]json.RawMessage{"a": json.RawMessage(`{"b": "c"}`), "c": []byte(`{"d": {"e": {"f": true}}}`)},
path: ".",
roots: []string{"a/b", "c/d/e"},
},
{
note: "data outside roots 1",
input: map[string]json.RawMessage{"d": json.RawMessage(`{"hello": "world"}`)},
path: ".",
roots: []string{"a/d"},
wantErr: true,
err: errors.New("manifest roots [a/d] do not permit data at path '/d' (hint: check bundle directory structure)"),
},
{
note: "data outside roots 2",
input: map[string]json.RawMessage{"a": []byte(`{"b": {"c": {"e": true}}}`)},
path: filepath.Dir("x/data.json"),
roots: []string{"x/a/b/c/d"},
wantErr: true,
err: errors.New("manifest roots [x/a/b/c/d] do not permit data at path '/x/a/b/c/e' (hint: check bundle directory structure)"),
},
{
note: "data outside roots 3",
input: map[string]json.RawMessage{"a": []byte(`{"b": {"c": true}}`)},
path: ".",
roots: []string{"a/b/c/d"},
wantErr: true,
err: errors.New("manifest roots [a/b/c/d] do not permit data at path '/a/b/c' (hint: check bundle directory structure)"),
},
{
note: "data outside multiple roots",
input: map[string]json.RawMessage{"a": json.RawMessage(`{"b": "c"}`), "e": []byte(`{"b": {"c": true}}`)},
path: ".",
roots: []string{"a/b", "c"},
wantErr: true,
err: errors.New("manifest roots [a/b c] do not permit data at path '/e' (hint: check bundle directory structure)"),
},
{
note: "data outside multiple roots 2",
input: map[string]json.RawMessage{"a": json.RawMessage(`{"b": "c"}`), "c": []byte(`{"d": true}`)},
path: ".",
roots: []string{"a/b", "c/d/e"},
wantErr: true,
err: errors.New("manifest roots [a/b c/d/e] do not permit data at path '/c/d' (hint: check bundle directory structure)"),
},
}
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
if err := doDFS(tc.input, tc.path, tc.roots); tc.wantErr {
if err == nil {
t.Fatal("Expected error but got nil")
}
if tc.err != nil && tc.err.Error() != err.Error() {
t.Fatalf("Expected error message %v but got %v", tc.err.Error(), err.Error())
}
} else if err != nil {
t.Fatalf("Unexpected error %v", err)
}
})
}
}
func TestHasRootsOverlap(t *testing.T) {
cases := []struct {
note string
storeRoots map[string]*[]string
newBundleRoots map[string]*[]string
expectedError string
}{
{
note: "no overlap between store and new bundles",
storeRoots: map[string]*[]string{"bundle1": {"a", "b"}},
newBundleRoots: map[string]*[]string{"bundle2": {"c"}},
},
{
note: "no overlap between store and multiple new bundles",
storeRoots: map[string]*[]string{"bundle1": {"a", "b"}},
newBundleRoots: map[string]*[]string{"bundle2": {"c"}, "bundle3": {"d"}},
},
{
note: "no overlap with empty store",
storeRoots: map[string]*[]string{},
newBundleRoots: map[string]*[]string{"bundle1": {"a", "b"}},
},
{
note: "no overlap between multiple new bundles with empty store",
storeRoots: map[string]*[]string{},
newBundleRoots: map[string]*[]string{"bundle1": {"a", "b"}, "bundle2": {"c"}},
},
{
note: "overlap between multiple new bundles with empty store",
storeRoots: map[string]*[]string{},
newBundleRoots: map[string]*[]string{"bundle1": {"a", "b"}, "bundle2": {"a", "c"}},
expectedError: "detected overlapping roots in manifests for these bundles: [bundle1, bundle2] (root a is in multiple bundles)",
},
{
note: "overlap between store and new bundle",
storeRoots: map[string]*[]string{"bundle1": {"a", "b"}},
newBundleRoots: map[string]*[]string{"bundle2": {"c", "a"}},
expectedError: "detected overlapping roots in manifests for these bundles: [bundle1, bundle2] (root a is in multiple bundles)",
},
{
note: "overlap between store and multiple new bundles",
storeRoots: map[string]*[]string{"bundle1": {"a", "b"}},
newBundleRoots: map[string]*[]string{"bundle2": {"c", "a"}, "bundle3": {"a"}},
expectedError: "detected overlapping roots in manifests for these bundles: [bundle1, bundle2, bundle3] (root a is in multiple bundles)",
},
{
note: "overlap between store bundle and new empty root bundle",
storeRoots: map[string]*[]string{"bundle1": {"a", "b"}},
newBundleRoots: map[string]*[]string{"bundle2": {""}},
expectedError: "bundles [bundle1, bundle2] have overlapping roots and cannot be activated simultaneously because bundle(s) [bundle2] specify empty root paths ('') which overlap with any other bundle root",
},
{
note: "overlap between multiple new empty root bundles",
storeRoots: map[string]*[]string{},
newBundleRoots: map[string]*[]string{"bundle1": {""}, "bundle2": {""}},
expectedError: "bundles [bundle1, bundle2] have overlapping roots and cannot be activated simultaneously because bundle(s) [bundle1, bundle2] specify empty root paths ('') which overlap with any other bundle root",
},
{
note: "overlap between new empty root and new regular root bundles",
storeRoots: map[string]*[]string{},
newBundleRoots: map[string]*[]string{"bundle1": {"a"}, "bundle2": {""}},
expectedError: "bundles [bundle1, bundle2] have overlapping roots and cannot be activated simultaneously because bundle(s) [bundle2] specify empty root paths ('') which overlap with any other bundle root",
},
{
note: "overlap between nested paths",
storeRoots: map[string]*[]string{},
newBundleRoots: map[string]*[]string{"bundle1": {"a"}, "bundle2": {"a/b"}},
expectedError: "detected overlapping roots in manifests for these bundles: [bundle1, bundle2] (a overlaps a/b)",
},
{
note: "overlap between store nested path and new bundle path",
storeRoots: map[string]*[]string{"bundle1": {"a/b"}},
newBundleRoots: map[string]*[]string{"bundle2": {"a"}},
expectedError: "detected overlapping roots in manifests for these bundles: [bundle1, bundle2] (a overlaps a/b)",
},
}
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
mockStore := mock.New()
txn := storage.NewTransactionOrDie(t.Context(), mockStore, storage.WriteParams)
for name, roots := range tc.storeRoots {
if err := WriteManifestToStore(t.Context(), mockStore, txn, name, Manifest{Roots: roots}); err != nil {
t.Fatalf("unexpected error: %s", err)
}
}
bundles := map[string]*Bundle{}
for name, roots := range tc.newBundleRoots {
bundles[name] = &Bundle{Manifest: Manifest{Roots: roots}}
}
if err := hasRootsOverlap(t.Context(), mockStore, txn, bundles); tc.expectedError != "" {
if err == nil {
t.Fatalf("expected error %q, got nil", tc.expectedError)
}
if err.Error() != tc.expectedError {
t.Fatalf("expected error message %q, got %q", tc.expectedError, err.Error())
}
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
mustCommit(t, mockStore, txn)
mockStore.AssertValid(t)
})
}
}
func TestBundleStoreHelpers(t *testing.T) {
storeReadModes := []struct {
note string
readAst bool
}{
{note: "read raw", readAst: false},
{note: "read ast", readAst: true},
}
bundles := map[string]*Bundle{
"bundle1": {
Manifest: Manifest{Roots: &[]string{}},
},
"bundle2": {
Manifest: Manifest{
Roots: &[]string{"a"},
Revision: "foo",
Metadata: map[string]any{"a": "b"},
WasmResolvers: []WasmResolver{{
Entrypoint: "foo/bar",
Module: "m.wasm",
}},
},
Etag: "bar",
WasmModules: []WasmModuleFile{{
Path: "/m.wasm",
Raw: []byte("d2FzbS1tb2R1bGU="),
}},
},
}
for _, srm := range storeReadModes {
t.Run(srm.note, func(t *testing.T) {
mockStore := mock.NewWithData(nil, inmem.OptReturnASTValuesOnRead(srm.readAst))
mustActivate(t, mockStore, &ActivateOpts{Bundles: bundles})
txn := storage.NewTransactionOrDie(t.Context(), mockStore)
verifyReadBundleNames(t, mockStore, txn, util.Keys(bundles)...)
// Etag
if etag, err := ReadBundleEtagFromStore(t.Context(), mockStore, txn, "bundle1"); err != nil {
t.Fatalf("unexpected error: %s", err)
} else if etag != "" {
t.Errorf("expected empty etag but got %s", etag)
}
if etag, err := ReadBundleEtagFromStore(t.Context(), mockStore, txn, "bundle2"); err != nil {
t.Fatalf("unexpected error: %s", err)
} else if exp := "bar"; etag != exp {
t.Errorf("expected etag %s but got %s", exp, etag)
}
// Revision
if rev, err := ReadBundleRevisionFromStore(t.Context(), mockStore, txn, "bundle1"); err != nil {
t.Fatalf("unexpected error: %s", err)
} else if rev != "" {
t.Errorf("expected empty revision but got %s", rev)
}
if rev, err := ReadBundleRevisionFromStore(t.Context(), mockStore, txn, "bundle2"); err != nil {
t.Fatalf("unexpected error: %s", err)
} else if exp := "foo"; rev != exp {
t.Errorf("expected revision %s but got %s", exp, rev)
}
// Roots
if roots, err := ReadBundleRootsFromStore(t.Context(), mockStore, txn, "bundle1"); err != nil {
t.Fatalf("unexpected error: %s", err)
} else if len(roots) != 0 {
t.Errorf("expected empty roots but got %v", roots)
}
if roots, err := ReadBundleRootsFromStore(t.Context(), mockStore, txn, "bundle2"); err != nil {
t.Fatalf("unexpected error: %s", err)
} else if exp := *bundles["bundle2"].Manifest.Roots; !reflect.DeepEqual(exp, roots) {
t.Errorf("expected roots %v but got %v", exp, roots)
}
// Bundle metadata
if meta, err := ReadBundleMetadataFromStore(t.Context(), mockStore, txn, "bundle1"); err != nil {
t.Fatalf("unexpected error: %s", err)
} else if len(meta) != 0 {
t.Errorf("expected empty metadata but got %v", meta)
}
if meta, err := ReadBundleMetadataFromStore(t.Context(), mockStore, txn, "bundle2"); err != nil {
t.Fatalf("unexpected error: %s", err)
} else if exp := bundles["bundle2"].Manifest.Metadata; !reflect.DeepEqual(exp, meta) {
t.Errorf("expected metadata %v but got %v", exp, meta)
}
// Wasm metadata
if _, err := ReadWasmMetadataFromStore(t.Context(), mockStore, txn, "bundle1"); err == nil {
t.Fatalf("expected error but got nil")
} else if exp, act := "storage_not_found_error: /bundles/bundle1/manifest/wasm: document does not exist", err.Error(); !strings.Contains(act, exp) {
t.Fatalf("expected error:\n\n%s\n\nbut got:\n\n%v", exp, act)
}
if resolvers, err := ReadWasmMetadataFromStore(t.Context(), mockStore, txn, "bundle2"); err != nil {
t.Fatalf("unexpected error: %s", err)
} else if exp := bundles["bundle2"].Manifest.WasmResolvers; !reflect.DeepEqual(exp, resolvers) {
t.Errorf("expected wasm metadata:\n\n%v\n\nbut got:\n\n%v", exp, resolvers)
}
// Wasm modules
if _, err := ReadWasmModulesFromStore(t.Context(), mockStore, txn, "bundle1"); err == nil {
t.Fatalf("expected error but got nil")
} else if exp, act := "storage_not_found_error: /bundles/bundle1/wasm: document does not exist", err.Error(); !strings.Contains(act, exp) {
t.Fatalf("expected error:\n\n%s\n\nbut got:\n\n%v", exp, act)
}
if modules, err := ReadWasmModulesFromStore(t.Context(), mockStore, txn, "bundle2"); err != nil {
t.Fatalf("unexpected error: %s", err)
} else if exp := bundles["bundle2"].WasmModules; len(exp) != len(modules) {
t.Errorf("expected wasm modules:\n\n%v\n\nbut got:\n\n%v", exp, modules)
} else {
for _, exp := range bundles["bundle2"].WasmModules {
act := modules[exp.Path]
if act == nil {
t.Errorf("expected wasm module %s but got nil", exp.Path)
}
if !bytes.Equal(exp.Raw, act) {
t.Errorf("expected wasm module %s to have raw data:\n\n%v\n\nbut got:\n\n%v", exp.Path, exp.Raw, act)
}
}
}
})
}
}
func TestActivate_DefaultRegoVersion(t *testing.T) {
tests := []struct {
note string
module string
customRegoVersion ast.RegoVersion
expErrs []string
}{
// NOT default rego-version
{
note: "v0 module",
module: `package test
p[x] {
x = "a"
}`,
expErrs: []string{
"rego_parse_error: `if` keyword is required before rule body",
"rego_parse_error: `contains` keyword is required for partial set rules",
},
},
// cross-rego-version
{
note: "rego.v1 import, no v1 parse-time violations",
module: `package test
import rego.v1
p contains x if {
x = "a"
}`,
},
{
note: "rego.v1 import, v1 parse-time violations",
module: `package test
import rego.v1
p contains x {
x = "a"
}`,
expErrs: []string{
"rego_parse_error: `if` keyword is required before rule body",
},
},
// default rego-version
{
note: "v1 module, no v1 parse-time violations",
module: `package test
p contains x if {
x = "a"
}`,
},
{
note: "v1 module, v1 parse-time violations",
module: `package test
p contains x {
x = "a"
}`,
expErrs: []string{
"rego_parse_error: `if` keyword is required before rule body",
},
},
// custom rego-version
{
note: "v0 module, v0 custom rego-version",
module: `package test
p[x] {
x = "a"
}`,
customRegoVersion: ast.RegoV0,
},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
store := mock.New()
txn := storage.NewTransactionOrDie(t.Context(), store, storage.WriteParams)
modulePath := "test/policy.rego"
// We want to make assert that the default rego-version is used, which it is when a module is erased from storage and we don't know what version it has.
// Therefore, we add a module to the store, which is the replaced by the Activate() call, causing an erase.
err := store.UpsertPolicy(t.Context(), txn, modulePathWithPrefix("bundle1", modulePath), []byte(tc.module))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
bundles := map[string]*Bundle{"bundle1": {
Manifest: Manifest{Roots: &[]string{"test"}},
Modules: []ModuleFile{moduleFile(modulePath, "package test")},
}}
opts := ActivateOpts{
Ctx: t.Context(),
Txn: txn,
Store: store,
Compiler: ast.NewCompiler().WithDefaultRegoVersion(ast.RegoV0CompatV1),
Metrics: metrics.NoOp(),
Bundles: bundles,
}
if tc.customRegoVersion != ast.RegoUndefined {
opts.ParserOptions.RegoVersion = tc.customRegoVersion
}
if err = Activate(&opts); len(tc.expErrs) > 0 {
if err == nil {
t.Fatalf("Expected error but got nil for test: %s", tc.note)
}
for _, expErr := range tc.expErrs {
if err := err.Error(); !strings.Contains(err, expErr) {
t.Fatalf("Expected error to contain:\n\n%s\n\nbut got:\n\n%s", expErr, err)
}
}
} else if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
})
}
}
func TestDeactivate_DefaultRegoVersion(t *testing.T) {
tests := []struct {
note string
module string
customRegoVersion ast.RegoVersion
expErrs []string
}{
// NOT default rego-version
{
note: "v0 module",
module: `package test
p[x] {
x = "a"
}`,
expErrs: []string{
"rego_parse_error: `if` keyword is required before rule body",
"rego_parse_error: `contains` keyword is required for partial set rules",
},
},
// cross-rego-version
{
note: "rego.v1 import, no v1 parse-time violations",
module: `package test
import rego.v1
p contains x if {
x = "a"
}`,
},
{
note: "rego.v1 import, v1 parse-time violations",
module: `package test
import rego.v1
p contains x {
x = "a"
}`,
expErrs: []string{
"rego_parse_error: `if` keyword is required before rule body",
},
},
// default rego-version
{
note: "v1 module, no v1 parse-time violations",
module: `package test
p contains x if {
x = "a"
}`,
},
{
note: "v1 module, v1 parse-time violations",
module: `package test
p contains x {
x = "a"
}`,
expErrs: []string{
"rego_parse_error: `if` keyword is required before rule body",
},
},
// custom rego-version
{
note: "v0 module, v0 custom rego-version",
module: `package test
p[x] {
x = "a"
}`,
customRegoVersion: ast.RegoV0,
},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
store := mock.New()
txn := storage.NewTransactionOrDie(t.Context(), store, storage.WriteParams)
modulePath := "test/policy.rego"
// We want to make assert that the default rego-version is used, which it is when a module is erased from storage and we don't know what version it has.
// Therefore, we add a module to the store, which is the replaced by the Activate() call, causing an erase.
err := store.UpsertPolicy(t.Context(), txn, modulePathWithPrefix("bundle1", modulePath), []byte(tc.module))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
opts := DeactivateOpts{
Ctx: t.Context(),
Txn: txn,
Store: store,
BundleNames: map[string]struct{}{modulePathWithPrefix("bundle1", modulePath): {}},
}
if tc.customRegoVersion != ast.RegoUndefined {
opts.ParserOptions.RegoVersion = tc.customRegoVersion
}
if err := Deactivate(&opts); len(tc.expErrs) > 0 {
if err == nil {
t.Fatalf("Expected error but got nil for test: %s", tc.note)
}
for _, expErr := range tc.expErrs {
if err := err.Error(); !strings.Contains(err, expErr) {
t.Fatalf("Expected error to contain:\n\n%s\n\nbut got:\n\n%s", expErr, err)
}
}
} else if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
})
}
}
func mustActivate(tb testing.TB, store storage.Store, opts *ActivateOpts) {
tb.Helper()
txn := storage.NewTransactionOrDie(tb.Context(), store, storage.WriteParams)
base := &ActivateOpts{
Ctx: tb.Context(),
Store: store,
Txn: txn,
Metrics: metrics.NoOp(),
Bundles: opts.Bundles,
Compiler: opts.Compiler,
ParserOptions: opts.ParserOptions,
ExtraModules: opts.ExtraModules,
}
if opts.Compiler == nil {
base.Compiler = ast.NewCompiler()
}
if err := Activate(base); err != nil {
tb.Fatalf("unexpected error activating bundles: %s", err)
}
if err := store.Commit(tb.Context(), txn); err != nil {
tb.Fatalf("unexpected error committing transaction: %s", err)
}
}
func mustDeactivate(tb testing.TB, store storage.Store, opts *DeactivateOpts) {
tb.Helper()
txn := storage.NewTransactionOrDie(tb.Context(), store, storage.WriteParams)
base := &DeactivateOpts{
Ctx: tb.Context(),
Store: store,
Txn: txn,
BundleNames: opts.BundleNames,
ParserOptions: opts.ParserOptions,
}
if err := Deactivate(base); err != nil {
tb.Fatalf("unexpected error deactivating bundles: %s", err)
}
if err := store.Commit(tb.Context(), txn); err != nil {
tb.Fatalf("unexpected error committing transaction: %s", err)
}
}
func verifyDeleteManifest(tb testing.TB, store storage.Store, name string) {
tb.Helper()
if err := storage.Txn(tb.Context(), store, storage.WriteParams, func(txn storage.Transaction) error {
return EraseManifestFromStore(tb.Context(), store, txn, name)
}); err != nil {
tb.Fatalf("Unexpected error deleting manifest: %s", err)
}
}
func verifyWriteManifests(tb testing.TB, store storage.Store, bundles map[string]Manifest) {
tb.Helper()
for name, manifest := range bundles {
err := storage.Txn(tb.Context(), store, storage.WriteParams, func(txn storage.Transaction) error {
err := WriteManifestToStore(tb.Context(), store, txn, name, manifest)
if err != nil {
tb.Fatalf("Failed to write manifest to store: %s", err)
}
return err
})
if err != nil {
tb.Fatalf("Unexpected error finishing transaction: %s", err)
}
}
}
func verifyReadBundleNames(tb testing.TB, store storage.Store, txn storage.Transaction, expected ...string) {
tb.Helper()
if txn == nil {
txn = storage.NewTransactionOrDie(tb.Context(), store)
defer store.Abort(tb.Context(), txn)
}
names, err := ReadBundleNamesFromStore(tb.Context(), store, txn)
if err != nil {
if storage.IsNotFound(err) && len(expected) == 0 {
return
}
tb.Fatalf("unexpected error: %s", err)
}
if len(names) != len(expected) {
tb.Fatalf("expected %d bundles in store, found %d", len(expected), len(names))
}
expMap := map[string]struct{}{}
for _, name := range expected {
expMap[name] = struct{}{}
}
for _, name := range names {
if _, ok := expMap[name]; !ok {
tb.Fatalf("unexpected bundle %s to be found in store, found %v", name, names)
}
}
}
func verifyReadLegacyRevision(tb testing.TB, store storage.Store, expected string) {
tb.Helper()
if err := storage.Txn(tb.Context(), store, storage.WriteParams, func(txn storage.Transaction) error {
actual, err := LegacyReadRevisionFromStore(tb.Context(), store, txn)
if err != nil && !storage.IsNotFound(err) {
tb.Fatalf("Failed to read manifest revision from store: %s", err)
}
if actual != expected {
tb.Fatalf("Expected revision %s, got %s", expected, actual)
}
return nil
}); err != nil {
tb.Fatalf("Unexpected error finishing transaction: %s", err)
}
}
func verifyBundleModulesCompiled(tb testing.TB, compiler *ast.Compiler, bundles map[string]*Bundle) {
tb.Helper()
for bundleName, bundle := range bundles {
for modName := range bundle.ParsedModules(bundleName) {
if _, ok := compiler.Modules[modName]; !ok {
tb.Fatalf("expected module %s from bundle %s to have been compiled", modName, bundleName)
}
}
}
}
func verifyResultRead(tb testing.TB, store storage.Store, expected string) {
tb.Helper()
txn := storage.NewTransactionOrDie(tb.Context(), store)
defer store.Abort(tb.Context(), txn)
act, err := store.Read(tb.Context(), txn, storage.RootPath)
if err != nil {
tb.Fatalf("unexpected error: %s", err)
}
if exp := loadExpectedResult(tb, expected); !reflect.DeepEqual(exp, act) {
tb.Errorf("expected %v, got %v", exp, act)
}
}
func assertEqual(tb testing.TB, expectAst bool, expected string, actual any) {
tb.Helper()
if expectAst {
if exp := ast.MustParseTerm(expected); ast.Compare(exp, actual) != 0 {
tb.Errorf("expected:\n\n%v\n\ngot:\n\n%v", expected, actual)
}
} else if exp := loadExpectedResult(tb, expected); !reflect.DeepEqual(exp, actual) {
tb.Errorf("expected:\n\n%v\n\ngot:\n\n%v", expected, actual)
}
}
func loadExpectedResult(tb testing.TB, input string) (data any) {
if len(input) > 0 {
if err := util.UnmarshalJSON(util.StringToByteSlice(input), &data); err != nil {
tb.Fatalf("failed to unmarshal expected result: %s", err)
}
}
return data
}
func bundleFromFiles(tb testing.TB, name string, files [][2]string) Bundle {
tb.Helper()
return bundleFromBuffer(tb, name, archive.MustWriteTarGz(files))
}
func bundleFromRoundtrip(tb testing.TB, name string, b Bundle) Bundle {
tb.Helper()
var buf bytes.Buffer
if err := NewWriter(&buf).UseModulePath(true).Write(b); err != nil {
tb.Fatal("Unexpected error:", err)
}
return bundleFromBuffer(tb, name, &buf)
}
func bundleFromBuffer(tb testing.TB, name string, buf *bytes.Buffer) Bundle {
tb.Helper()
return must(NewCustomReader(NewTarballLoaderWithBaseURL(buf, "")).
WithLazyLoadingMode(true).
WithBundleName(name).
Read())(tb)
}
func must[T any](val T, err error) func(tb testing.TB) T {
return func(tb testing.TB) T {
tb.Helper()
if err != nil {
tb.Fatalf("unexpected error: %s", err)
}
return val
}
}
func mustRead(tb testing.TB, s storage.Store, txn storage.Transaction, path storage.Path) any {
tb.Helper()
if txn == nil {
txn = storage.NewTransactionOrDie(tb.Context(), s)
defer s.Abort(tb.Context(), txn)
}
return must(s.Read(tb.Context(), txn, path))(tb)
}
func mustCommit(tb testing.TB, s storage.Store, txn storage.Transaction) {
tb.Helper()
if err := s.Commit(tb.Context(), txn); err != nil {
tb.Fatalf("unexpected error trying to commit to store: %s", err)
}
}
func moduleFile(path, raw string) ModuleFile {
return ModuleFile{
Path: path,
Raw: util.StringToByteSlice(raw),
Parsed: ast.MustParseModule(raw),
}
}
// unpack takes flat map[string]any where nested keys are dot-separated,
// and returns a "unpacked" map[string]any with proper nesting.
func unpack(m map[string]any) map[string]any {
result := make(map[string]any, len(m))
for k, v := range m {
var ok bool
currMap := result
curr, rest, _ := strings.Cut(k, ".")
for rest != "" {
if _, ok = currMap[curr]; !ok {
currMap[curr] = make(map[string]any)
}
if currMap, ok = currMap[curr].(map[string]any); !ok {
panic("key conflict")
}
curr, rest, _ = strings.Cut(rest, ".")
}
currMap[curr] = v
}
return result
}