mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-25 01:35:18 -06:00
cca81970d1
This 1. changes the extension.Handler type to make it more flexible 2. simplifies the extension usage -- it used to be called in many places, but it could all be handled through util.Unmarshal and util.UnmarshalJSON instead We've previously marked it as "EXPERIMENTAL", so we should have enough leeway to change this now. NOTE: As a consequence of (2.), we're no longer accepting trailing data for json files loaded with OPA. I believe it wasn't intentional to ignore bad data before -- now, it'll be an error. Signed-off-by: Stephan Renatus <stephan@styra.com>
4936 lines
113 KiB
Go
4936 lines
113 KiB
Go
package bundle
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"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/ast"
|
|
"github.com/open-policy-agent/opa/logging"
|
|
"github.com/open-policy-agent/opa/metrics"
|
|
"github.com/open-policy-agent/opa/util"
|
|
"github.com/open-policy-agent/opa/util/test"
|
|
|
|
"github.com/open-policy-agent/opa/storage"
|
|
"github.com/open-policy-agent/opa/storage/disk"
|
|
inmem "github.com/open-policy-agent/opa/storage/inmem/test"
|
|
)
|
|
|
|
func TestManifestStoreLifecycleSingleBundle(t *testing.T) {
|
|
store := inmem.New()
|
|
ctx := context.Background()
|
|
tb := Manifest{
|
|
Revision: "abc123",
|
|
Roots: &[]string{"/a/b", "/a/c"},
|
|
}
|
|
name := "test_bundle"
|
|
verifyWriteManifests(ctx, t, store, map[string]Manifest{name: tb}) // write one
|
|
verifyReadBundleNames(ctx, t, store, []string{name}) // read one
|
|
verifyDeleteManifest(ctx, t, store, name) // delete it
|
|
verifyReadBundleNames(ctx, t, store, []string{}) // ensure it was removed
|
|
}
|
|
|
|
func TestManifestStoreLifecycleMultiBundle(t *testing.T) {
|
|
store := inmem.New()
|
|
ctx := context.Background()
|
|
|
|
bundles := map[string]Manifest{
|
|
"bundle1": {
|
|
Revision: "abc123",
|
|
Roots: &[]string{"/a/b", "/a/c"},
|
|
},
|
|
"bundle2": {
|
|
Revision: "def123",
|
|
Roots: &[]string{"/x/y", "/z"},
|
|
},
|
|
}
|
|
|
|
verifyWriteManifests(ctx, t, store, bundles) // write multiple
|
|
verifyReadBundleNames(ctx, t, store, []string{"bundle1", "bundle2"}) // read them
|
|
verifyDeleteManifest(ctx, t, store, "bundle1") // delete one
|
|
verifyReadBundleNames(ctx, t, store, []string{"bundle2"}) // ensure it was removed
|
|
verifyDeleteManifest(ctx, t, store, "bundle2") // delete the last one
|
|
verifyReadBundleNames(ctx, t, store, []string{}) // ensure it was removed
|
|
}
|
|
|
|
func TestLegacyManifestStoreLifecycle(t *testing.T) {
|
|
store := inmem.New()
|
|
ctx := context.Background()
|
|
tb := Manifest{
|
|
Revision: "abc123",
|
|
Roots: &[]string{"/a/b", "/a/c"},
|
|
}
|
|
|
|
// write a "legacy" manifest
|
|
err := storage.Txn(ctx, store, storage.WriteParams, func(txn storage.Transaction) error {
|
|
if err := LegacyWriteManifestToStore(ctx, store, txn, tb); err != nil {
|
|
t.Fatalf("Failed to write manifest to store: %s", err)
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error finishing transaction: %s", err)
|
|
}
|
|
|
|
// make sure it can be retrieved
|
|
verifyReadLegacyRevision(ctx, t, store, tb.Revision)
|
|
|
|
// delete it
|
|
err = storage.Txn(ctx, store, storage.WriteParams, func(txn storage.Transaction) error {
|
|
if err := LegacyEraseManifestFromStore(ctx, store, txn); err != nil {
|
|
t.Fatalf("Failed to erase manifest from store: %s", err)
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error finishing transaction: %s", err)
|
|
}
|
|
|
|
verifyReadLegacyRevision(ctx, t, store, "")
|
|
}
|
|
|
|
func TestMixedManifestStoreLifecycle(t *testing.T) {
|
|
store := inmem.New()
|
|
ctx := context.Background()
|
|
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
|
|
err := storage.Txn(ctx, store, storage.WriteParams, func(txn storage.Transaction) error {
|
|
if err := LegacyWriteManifestToStore(ctx, store, txn, bundles["bundle1"]); err != nil {
|
|
t.Fatalf("Failed to write manifest to store: %s", err)
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error finishing transaction: %s", err)
|
|
}
|
|
|
|
verifyReadBundleNames(ctx, t, store, []string{})
|
|
|
|
// Write both new ones
|
|
verifyWriteManifests(ctx, t, store, bundles)
|
|
verifyReadBundleNames(ctx, t, store, []string{"bundle1", "bundle2"})
|
|
|
|
// Ensure the original legacy one is still there
|
|
verifyReadLegacyRevision(ctx, t, store, bundles["bundle1"].Revision)
|
|
}
|
|
|
|
func verifyDeleteManifest(ctx context.Context, t *testing.T, store storage.Store, name string) {
|
|
err := storage.Txn(ctx, store, storage.WriteParams, func(txn storage.Transaction) error {
|
|
err := EraseManifestFromStore(ctx, store, txn, name)
|
|
if err != nil {
|
|
t.Fatalf("Failed to delete manifest from store: %s", err)
|
|
}
|
|
return err
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error finishing transaction: %s", err)
|
|
}
|
|
}
|
|
|
|
func verifyWriteManifests(ctx context.Context, t *testing.T, store storage.Store, bundles map[string]Manifest) {
|
|
t.Helper()
|
|
for name, manifest := range bundles {
|
|
err := storage.Txn(ctx, store, storage.WriteParams, func(txn storage.Transaction) error {
|
|
err := WriteManifestToStore(ctx, store, txn, name, manifest)
|
|
if err != nil {
|
|
t.Fatalf("Failed to write manifest to store: %s", err)
|
|
}
|
|
return err
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error finishing transaction: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func verifyReadBundleNames(ctx context.Context, t *testing.T, store storage.Store, expected []string) {
|
|
t.Helper()
|
|
var actualNames []string
|
|
err := storage.Txn(ctx, store, storage.WriteParams, func(txn storage.Transaction) error {
|
|
var err error
|
|
actualNames, err = ReadBundleNamesFromStore(ctx, store, txn)
|
|
if err != nil && !storage.IsNotFound(err) {
|
|
t.Fatalf("Failed to read manifest names from store: %s", err)
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error finishing transaction: %s", err)
|
|
}
|
|
|
|
if len(actualNames) != len(expected) {
|
|
t.Fatalf("Expected %d name, found %d \n\t\tActual: %v\n", len(expected), len(actualNames), actualNames)
|
|
}
|
|
|
|
for _, actualName := range actualNames {
|
|
found := false
|
|
for _, expectedName := range expected {
|
|
if actualName == expectedName {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("Found unexpecxted bundle name %s, expected names: %+v", actualName, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func verifyReadLegacyRevision(ctx context.Context, t *testing.T, store storage.Store, expected string) {
|
|
t.Helper()
|
|
var actual string
|
|
err := storage.Txn(ctx, store, storage.WriteParams, func(txn storage.Transaction) error {
|
|
var err error
|
|
if actual, err = LegacyReadRevisionFromStore(ctx, store, txn); err != nil && !storage.IsNotFound(err) {
|
|
t.Fatalf("Failed to read manifest revision from store: %s", err)
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error finishing transaction: %s", err)
|
|
}
|
|
|
|
if actual != expected {
|
|
t.Fatalf("Expected revision %s, got %s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestBundleLazyModeNoPolicyOrData(t *testing.T) {
|
|
ctx := context.Background()
|
|
mockStore := mock.New()
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": {
|
|
Manifest: Manifest{
|
|
Roots: &[]string{"a"},
|
|
Revision: "foo",
|
|
},
|
|
Etag: "foo",
|
|
lazyLoadingMode: true,
|
|
},
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err := Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the bundle was activated
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
names, err := ReadBundleNamesFromStore(ctx, mockStore, txn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(names) != len(bundles) {
|
|
t.Fatalf("expected %d bundles in store, found %d", len(bundles), len(names))
|
|
}
|
|
|
|
for _, name := range names {
|
|
if _, ok := bundles[name]; !ok {
|
|
t.Fatalf("unexpected bundle name found in store: %s", name)
|
|
}
|
|
}
|
|
|
|
actual, err := mockStore.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw := `
|
|
{
|
|
"system": {
|
|
"bundles": {
|
|
"bundle1": {
|
|
"manifest": {
|
|
"revision": "foo",
|
|
"roots": ["a"]
|
|
},
|
|
"etag": "foo"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
}
|
|
|
|
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`},
|
|
{"/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"}]}`},
|
|
}
|
|
|
|
buf := archive.MustWriteTarGz(files)
|
|
loader := NewTarballLoaderWithBaseURL(buf, "")
|
|
br := NewCustomReader(loader).WithBundleEtag("bar").WithLazyLoadingMode(true)
|
|
|
|
bundle, err := br.Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
mockStore := mock.New()
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
extraMods := map[string]*ast.Module{
|
|
"mod1": ast.MustParseModule("package x\np = true"),
|
|
}
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": &bundle,
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
ExtraModules: extraMods,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the bundle was activated
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
names, err := ReadBundleNamesFromStore(ctx, mockStore, txn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(names) != len(bundles) {
|
|
t.Fatalf("expected %d bundles in store, found %d", len(bundles), len(names))
|
|
}
|
|
for _, name := range names {
|
|
if _, ok := bundles[name]; !ok {
|
|
t.Fatalf("unexpected bundle name found in store: %s", name)
|
|
}
|
|
}
|
|
|
|
for bundleName, bundle := range bundles {
|
|
for modName := range bundle.ParsedModules(bundleName) {
|
|
if _, ok := compiler.Modules[modName]; !ok {
|
|
t.Fatalf("expected module %s from bundle %s to have been compiled", modName, bundleName)
|
|
}
|
|
}
|
|
}
|
|
|
|
actual, err := mockStore.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
expectedRaw := `
|
|
{
|
|
"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"
|
|
}
|
|
]
|
|
},
|
|
"etag": "bar",
|
|
"wasm": {
|
|
"/authz/allow/policy.wasm": "d2FzbS1tb2R1bGU="
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Ensure that the extra module was included
|
|
if _, ok := compiler.Modules["mod1"]; !ok {
|
|
t.Fatalf("expected extra module to be compiled")
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err = Deactivate(&DeactivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
BundleNames: map[string]struct{}{"bundle1": {}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Expect the store to have been cleared out after deactivating the bundle
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
names, err = ReadBundleNamesFromStore(ctx, mockStore, txn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(names) != 0 {
|
|
t.Fatalf("expected 0 bundles in store, found %d", len(names))
|
|
}
|
|
|
|
actual, err = mockStore.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
expectedRaw = `{"system": {"bundles": {}}}`
|
|
expected = loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
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]`}}, fmt.Errorf("root value must be object")},
|
|
"invalid yaml": {[][2]string{{"/a/b/data.yaml", `"foo`}}, fmt.Errorf("yaml: found unexpected end of stream")},
|
|
}
|
|
|
|
for name, tc := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
buf := archive.MustWriteTarGz(tc.files)
|
|
loader := NewTarballLoaderWithBaseURL(buf, "")
|
|
br := NewCustomReader(loader).WithBundleEtag("bar").WithLazyLoadingMode(true)
|
|
|
|
bundle, err := br.Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
mockStore := mock.New()
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": &bundle,
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
|
|
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) {
|
|
ctx := context.Background()
|
|
mockStore := mock.New()
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
extraMods := map[string]*ast.Module{
|
|
"mod1": ast.MustParseModule("package x\np = true"),
|
|
}
|
|
|
|
mod1 := "package a\np = true"
|
|
mod2 := "package b\np = true"
|
|
|
|
b1Files := [][2]string{
|
|
{"/.manifest", `{"roots": ["a"]}`},
|
|
{"a/policy.rego", mod1},
|
|
{"/data.json", `{"a": {"b": "foo"}}`},
|
|
}
|
|
|
|
buf := archive.MustWriteTarGz(b1Files)
|
|
loader := NewTarballLoaderWithBaseURL(buf, "")
|
|
br := NewCustomReader(loader).WithBundleEtag("foo").WithLazyLoadingMode(true).WithBundleName("bundle1")
|
|
|
|
bundle1, err := br.Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
b2Files := [][2]string{
|
|
{"/.manifest", `{"roots": ["b", "c"]}`},
|
|
{"b/policy.rego", mod2},
|
|
{"/data.json", `{}`},
|
|
}
|
|
|
|
buf = archive.MustWriteTarGz(b2Files)
|
|
loader = NewTarballLoaderWithBaseURL(buf, "")
|
|
br = NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle2")
|
|
|
|
bundle2, err := br.Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": &bundle1,
|
|
"bundle2": &bundle2,
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
ExtraModules: extraMods,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the bundle was activated
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
names, err := ReadBundleNamesFromStore(ctx, mockStore, txn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(names) != len(bundles) {
|
|
t.Fatalf("expected %d bundles in store, found %d", len(bundles), len(names))
|
|
}
|
|
for _, name := range names {
|
|
if _, ok := bundles[name]; !ok {
|
|
t.Fatalf("unexpected bundle name found in store: %s", name)
|
|
}
|
|
}
|
|
|
|
for bundleName, bundle := range bundles {
|
|
for modName := range bundle.ParsedModules(bundleName) {
|
|
if _, ok := compiler.Modules[modName]; !ok {
|
|
t.Fatalf("expected module %s from bundle %s to have been compiled", modName, bundleName)
|
|
}
|
|
}
|
|
}
|
|
|
|
actual, err := mockStore.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
expectedRaw := `
|
|
{
|
|
"a": {
|
|
"b": "foo"
|
|
},
|
|
"system": {
|
|
"bundles": {
|
|
"bundle1": {
|
|
"manifest": {
|
|
"revision": "",
|
|
"roots": ["a"]
|
|
},
|
|
"etag": "foo"
|
|
},
|
|
"bundle2": {
|
|
"manifest": {
|
|
"revision": "",
|
|
"roots": ["b", "c"]
|
|
},
|
|
"etag": ""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Ensure that the extra module was included
|
|
if _, ok := compiler.Modules["mod1"]; !ok {
|
|
t.Fatalf("expected extra module to be compiled")
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err = Deactivate(&DeactivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
BundleNames: map[string]struct{}{"bundle1": {}, "bundle2": {}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Expect the store to have been cleared out after deactivating the bundles
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
names, err = ReadBundleNamesFromStore(ctx, mockStore, txn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(names) != 0 {
|
|
t.Fatalf("expected 0 bundles in store, found %d", len(names))
|
|
}
|
|
|
|
actual, err = mockStore.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
expectedRaw = `{"system": {"bundles": {}}}`
|
|
expected = loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
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"}`},
|
|
}
|
|
|
|
buf := archive.MustWriteTarGz(files)
|
|
loader := NewTarballLoaderWithBaseURL(buf, "")
|
|
br := NewCustomReader(loader).WithBundleEtag("foo").WithLazyLoadingMode(true)
|
|
|
|
bundle, err := br.Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
mockStore := mock.New()
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": &bundle,
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the bundle was activated
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
names, err := ReadBundleNamesFromStore(ctx, mockStore, txn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(names) != len(bundles) {
|
|
t.Fatalf("expected %d bundles in store, found %d", len(bundles), len(names))
|
|
}
|
|
for _, name := range names {
|
|
if _, ok := bundles[name]; !ok {
|
|
t.Fatalf("unexpected bundle name found in store: %s", name)
|
|
}
|
|
}
|
|
|
|
for bundleName, bundle := range bundles {
|
|
for modName := range bundle.ParsedModules(bundleName) {
|
|
if _, ok := compiler.Modules[modName]; !ok {
|
|
t.Fatalf("expected module %s from bundle %s to have been compiled", modName, bundleName)
|
|
}
|
|
}
|
|
}
|
|
|
|
actual, err := mockStore.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw := `
|
|
{
|
|
"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"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
|
|
files = [][2]string{
|
|
{"/c/data.json", `{"hello": "world"}`},
|
|
{"/.manifest", `{"revision": "rev-2"}`},
|
|
}
|
|
|
|
buf = archive.MustWriteTarGz(files)
|
|
loader = NewTarballLoaderWithBaseURL(buf, "")
|
|
br = NewCustomReader(loader).WithBundleEtag("bar").WithLazyLoadingMode(true)
|
|
|
|
bundle, err = br.Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles = map[string]*Bundle{
|
|
"bundle1": &bundle,
|
|
}
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
|
|
actual, err = mockStore.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw = `
|
|
{
|
|
"c": {
|
|
"hello": "world"
|
|
},
|
|
"system": {
|
|
"bundles": {
|
|
"bundle1": {
|
|
"manifest": {
|
|
"revision": "rev-2",
|
|
"roots": [""]
|
|
},
|
|
"etag": "bar"
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
expected = loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
|
|
}
|
|
|
|
func TestBundleLazyModeLifecycleRawNoBundleRootsDiskStorage(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
test.WithTempFS(nil, func(dir string) {
|
|
store, err := disk.New(ctx, logging.NewNoOpLogger(), nil, disk.Options{
|
|
Dir: dir,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
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"}`},
|
|
}
|
|
|
|
buf := archive.MustWriteTarGz(files)
|
|
loader := NewTarballLoaderWithBaseURL(buf, "")
|
|
br := NewCustomReader(loader).WithBundleEtag("foo").WithLazyLoadingMode(true)
|
|
|
|
bundle, err := br.Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": &bundle,
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: store,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = store.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the bundle was activated
|
|
txn = storage.NewTransactionOrDie(ctx, store)
|
|
names, err := ReadBundleNamesFromStore(ctx, store, txn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(names) != len(bundles) {
|
|
t.Fatalf("expected %d bundles in store, found %d", len(bundles), len(names))
|
|
}
|
|
for _, name := range names {
|
|
if _, ok := bundles[name]; !ok {
|
|
t.Fatalf("unexpected bundle name found in store: %s", name)
|
|
}
|
|
}
|
|
|
|
for bundleName, bundle := range bundles {
|
|
for modName := range bundle.ParsedModules(bundleName) {
|
|
if _, ok := compiler.Modules[modName]; !ok {
|
|
t.Fatalf("expected module %s from bundle %s to have been compiled", modName, bundleName)
|
|
}
|
|
}
|
|
}
|
|
|
|
actual, err := store.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw := `
|
|
{
|
|
"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"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
store.Abort(ctx, txn)
|
|
|
|
files = [][2]string{
|
|
{"/c/data.json", `{"hello": "world"}`},
|
|
{"/.manifest", `{"revision": "rev-2"}`},
|
|
}
|
|
|
|
buf = archive.MustWriteTarGz(files)
|
|
loader = NewTarballLoaderWithBaseURL(buf, "")
|
|
br = NewCustomReader(loader).WithBundleEtag("bar").WithLazyLoadingMode(true)
|
|
|
|
bundle, err = br.Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles = map[string]*Bundle{
|
|
"bundle1": &bundle,
|
|
}
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: store,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = store.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, store)
|
|
|
|
actual, err = store.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw = `
|
|
{
|
|
"c": {
|
|
"hello": "world"
|
|
},
|
|
"system": {
|
|
"bundles": {
|
|
"bundle1": {
|
|
"manifest": {
|
|
"revision": "rev-2",
|
|
"roots": [""]
|
|
},
|
|
"etag": "bar"
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
expected = loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
store.Abort(ctx, txn)
|
|
})
|
|
}
|
|
|
|
func TestBundleLazyModeLifecycleNoBundleRoots(t *testing.T) {
|
|
ctx := context.Background()
|
|
mockStore := mock.New()
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
mod1 := "package a\np = true"
|
|
|
|
b := Bundle{
|
|
Manifest: Manifest{Revision: "rev-1"},
|
|
Data: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"b": "foo",
|
|
"e": map[string]interface{}{
|
|
"f": "bar",
|
|
},
|
|
"x": []map[string]string{{"name": "john"}, {"name": "jane"}},
|
|
},
|
|
},
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "a/policy.rego",
|
|
Raw: []byte(mod1),
|
|
Parsed: ast.MustParseModule(mod1),
|
|
},
|
|
},
|
|
Etag: "foo",
|
|
}
|
|
|
|
var buf1 bytes.Buffer
|
|
if err := NewWriter(&buf1).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
loader := NewTarballLoaderWithBaseURL(&buf1, "")
|
|
bundle1, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle1").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": &bundle1,
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the patches were applied
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
|
|
actual, err := mockStore.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw := `
|
|
{
|
|
"a": {
|
|
"b": "foo",
|
|
"e": {
|
|
"f": "bar"
|
|
},
|
|
"x": [{"name": "john"}, {"name": "jane"}]
|
|
},
|
|
"system": {
|
|
"bundles": {
|
|
"bundle1": {
|
|
"manifest": {
|
|
"revision": "rev-1",
|
|
"roots": [""]
|
|
},
|
|
"etag": ""
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
|
|
// add a new bundle with no roots. this means all the data from the currently activated should be removed
|
|
b = Bundle{
|
|
Manifest: Manifest{Revision: "rev-2"},
|
|
Data: map[string]interface{}{
|
|
"c": map[string]interface{}{
|
|
"hello": "world",
|
|
},
|
|
},
|
|
Etag: "bar",
|
|
}
|
|
|
|
var buf2 bytes.Buffer
|
|
if err := NewWriter(&buf2).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
|
|
loader = NewTarballLoaderWithBaseURL(&buf2, "")
|
|
bundle2, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle1").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles = map[string]*Bundle{
|
|
"bundle1": &bundle2,
|
|
}
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the patches were applied
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
|
|
actual, err = mockStore.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw = `
|
|
{
|
|
"c": {
|
|
"hello": "world"
|
|
},
|
|
"system": {
|
|
"bundles": {
|
|
"bundle1": {
|
|
"manifest": {
|
|
"revision": "rev-2",
|
|
"roots": [""]
|
|
},
|
|
"etag": ""
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
expected = loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
}
|
|
|
|
func TestBundleLazyModeLifecycleNoBundleRootsDiskStorage(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
test.WithTempFS(nil, func(dir string) {
|
|
store, err := disk.New(ctx, logging.NewNoOpLogger(), nil, disk.Options{
|
|
Dir: dir,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
mod1 := "package a\np = true"
|
|
|
|
b := Bundle{
|
|
Manifest: Manifest{Revision: "rev-1"},
|
|
Data: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"b": "foo",
|
|
"e": map[string]interface{}{
|
|
"f": "bar",
|
|
},
|
|
"x": []map[string]string{{"name": "john"}, {"name": "jane"}},
|
|
},
|
|
},
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "a/policy.rego",
|
|
Raw: []byte(mod1),
|
|
Parsed: ast.MustParseModule(mod1),
|
|
},
|
|
},
|
|
Etag: "foo",
|
|
}
|
|
|
|
var buf1 bytes.Buffer
|
|
if err := NewWriter(&buf1).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
loader := NewTarballLoaderWithBaseURL(&buf1, "")
|
|
bundle1, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle1").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": &bundle1,
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: store,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = store.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the snapshot bundle was activated
|
|
txn = storage.NewTransactionOrDie(ctx, store)
|
|
|
|
names, err := ReadBundleNamesFromStore(ctx, store, txn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(names) != len(bundles) {
|
|
t.Fatalf("expected %d bundles in store, found %d", len(bundles), len(names))
|
|
}
|
|
for _, name := range names {
|
|
if _, ok := bundles[name]; !ok {
|
|
t.Fatalf("unexpected bundle name found in store: %s", name)
|
|
}
|
|
}
|
|
|
|
for bundleName, bundle := range bundles {
|
|
for modName := range bundle.ParsedModules(bundleName) {
|
|
if _, ok := compiler.Modules[modName]; !ok {
|
|
t.Fatalf("expected module %s from bundle %s to have been compiled", modName, bundleName)
|
|
}
|
|
}
|
|
}
|
|
|
|
actual, err := store.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw := `
|
|
{
|
|
"a": {
|
|
"b": "foo",
|
|
"e": {
|
|
"f": "bar"
|
|
},
|
|
"x": [{"name": "john"}, {"name": "jane"}]
|
|
},
|
|
"system": {
|
|
"bundles": {
|
|
"bundle1": {
|
|
"manifest": {
|
|
"revision": "rev-1",
|
|
"roots": [""]
|
|
},
|
|
"etag": ""
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
store.Abort(ctx, txn)
|
|
|
|
// add a new bundle with no roots. this means all the data from the currently activated should be removed
|
|
b = Bundle{
|
|
Manifest: Manifest{Revision: "rev-2"},
|
|
Data: map[string]interface{}{
|
|
"c": map[string]interface{}{
|
|
"hello": "world",
|
|
},
|
|
},
|
|
Etag: "bar",
|
|
}
|
|
|
|
var buf2 bytes.Buffer
|
|
if err := NewWriter(&buf2).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
|
|
loader = NewTarballLoaderWithBaseURL(&buf2, "")
|
|
bundle2, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle1").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles = map[string]*Bundle{
|
|
"bundle1": &bundle2,
|
|
}
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: store,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = store.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the snapshot bundle was activated
|
|
txn = storage.NewTransactionOrDie(ctx, store)
|
|
|
|
actual, err = store.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw = `
|
|
{
|
|
"c": {
|
|
"hello": "world"
|
|
},
|
|
"system": {
|
|
"bundles": {
|
|
"bundle1": {
|
|
"manifest": {
|
|
"revision": "rev-2",
|
|
"roots": [""]
|
|
},
|
|
"etag": ""
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
expected = loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
store.Abort(ctx, txn)
|
|
|
|
})
|
|
}
|
|
|
|
func TestBundleLazyModeLifecycleMixBundleTypeActivationDiskStorage(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
test.WithTempFS(nil, func(dir string) {
|
|
store, err := disk.New(ctx, logging.NewNoOpLogger(), nil, disk.Options{
|
|
Dir: dir,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
mod1 := "package a\np = true"
|
|
|
|
b := Bundle{
|
|
Manifest: Manifest{
|
|
Revision: "snap-1",
|
|
Roots: &[]string{"a"},
|
|
},
|
|
Data: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"b": "foo",
|
|
"e": map[string]interface{}{
|
|
"f": "bar",
|
|
},
|
|
"x": []map[string]string{{"name": "john"}, {"name": "jane"}},
|
|
},
|
|
},
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "a/policy.rego",
|
|
Raw: []byte(mod1),
|
|
Parsed: ast.MustParseModule(mod1),
|
|
},
|
|
},
|
|
Etag: "foo",
|
|
}
|
|
|
|
var buf1 bytes.Buffer
|
|
if err := NewWriter(&buf1).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
loader := NewTarballLoaderWithBaseURL(&buf1, "")
|
|
bundle1, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle1").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// create a delta bundle and activate it
|
|
|
|
// add a new object member
|
|
p1 := PatchOperation{
|
|
Op: "upsert",
|
|
Path: "/x/y",
|
|
Value: []string{"foo", "bar"},
|
|
}
|
|
|
|
b = Bundle{
|
|
Manifest: Manifest{
|
|
Revision: "delta-1",
|
|
Roots: &[]string{"x"},
|
|
},
|
|
Patch: Patch{Data: []PatchOperation{p1}},
|
|
Etag: "bar",
|
|
}
|
|
|
|
var buf2 bytes.Buffer
|
|
if err := NewWriter(&buf2).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
|
|
loader = NewTarballLoaderWithBaseURL(&buf2, "")
|
|
bundle2, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle2").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": &bundle1,
|
|
"bundle2": &bundle2,
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: store,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = store.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the patches were applied
|
|
txn = storage.NewTransactionOrDie(ctx, store)
|
|
|
|
actual, err := store.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw := `
|
|
{
|
|
"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": ""
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
store.Abort(ctx, txn)
|
|
})
|
|
}
|
|
|
|
func TestBundleLazyModeLifecycleOldBundleEraseDiskStorage(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
test.WithTempFS(nil, func(dir string) {
|
|
store, err := disk.New(ctx, logging.NewNoOpLogger(), nil, disk.Options{
|
|
Dir: dir,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
mod1 := "package a\np = true"
|
|
|
|
b := Bundle{
|
|
Manifest: Manifest{Revision: "rev-1", Roots: &[]string{"a"}},
|
|
Data: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"b": "foo",
|
|
"e": map[string]interface{}{
|
|
"f": "bar",
|
|
},
|
|
"x": []map[string]string{{"name": "john"}, {"name": "jane"}},
|
|
},
|
|
},
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "a/policy.rego",
|
|
Raw: []byte(mod1),
|
|
Parsed: ast.MustParseModule(mod1),
|
|
},
|
|
},
|
|
Etag: "foo",
|
|
}
|
|
|
|
var buf1 bytes.Buffer
|
|
if err := NewWriter(&buf1).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
loader := NewTarballLoaderWithBaseURL(&buf1, "")
|
|
bundle1, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle1").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": &bundle1,
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: store,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = store.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the snapshot bundle was activated
|
|
txn = storage.NewTransactionOrDie(ctx, store)
|
|
|
|
names, err := ReadBundleNamesFromStore(ctx, store, txn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(names) != len(bundles) {
|
|
t.Fatalf("expected %d bundles in store, found %d", len(bundles), len(names))
|
|
}
|
|
for _, name := range names {
|
|
if _, ok := bundles[name]; !ok {
|
|
t.Fatalf("unexpected bundle name found in store: %s", name)
|
|
}
|
|
}
|
|
|
|
for bundleName, bundle := range bundles {
|
|
for modName := range bundle.ParsedModules(bundleName) {
|
|
if _, ok := compiler.Modules[modName]; !ok {
|
|
t.Fatalf("expected module %s from bundle %s to have been compiled", modName, bundleName)
|
|
}
|
|
}
|
|
}
|
|
|
|
actual, err := store.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw := `
|
|
{
|
|
"a": {
|
|
"b": "foo",
|
|
"e": {
|
|
"f": "bar"
|
|
},
|
|
"x": [{"name": "john"}, {"name": "jane"}]
|
|
},
|
|
"system": {
|
|
"bundles": {
|
|
"bundle1": {
|
|
"manifest": {
|
|
"revision": "rev-1",
|
|
"roots": ["a"]
|
|
},
|
|
"etag": ""
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
store.Abort(ctx, txn)
|
|
|
|
// add a new bundle and verify data from the currently activated is removed
|
|
b = Bundle{
|
|
Manifest: Manifest{Revision: "rev-2", Roots: &[]string{"c"}},
|
|
Data: map[string]interface{}{
|
|
"c": map[string]interface{}{
|
|
"hello": "world",
|
|
},
|
|
},
|
|
Etag: "bar",
|
|
}
|
|
|
|
var buf2 bytes.Buffer
|
|
if err := NewWriter(&buf2).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
|
|
loader = NewTarballLoaderWithBaseURL(&buf2, "")
|
|
bundle2, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle1").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles = map[string]*Bundle{
|
|
"bundle1": &bundle2,
|
|
}
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: store,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = store.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the snapshot bundle was activated
|
|
txn = storage.NewTransactionOrDie(ctx, store)
|
|
|
|
actual, err = store.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw = `
|
|
{
|
|
"c": {
|
|
"hello": "world"
|
|
},
|
|
"system": {
|
|
"bundles": {
|
|
"bundle1": {
|
|
"manifest": {
|
|
"revision": "rev-2",
|
|
"roots": ["c"]
|
|
},
|
|
"etag": ""
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
expected = loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
store.Abort(ctx, txn)
|
|
|
|
})
|
|
}
|
|
|
|
func TestBundleLazyModeLifecycleRestoreBackupDB(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
test.WithTempFS(nil, func(dir string) {
|
|
store, err := disk.New(ctx, logging.NewNoOpLogger(), nil, disk.Options{
|
|
Dir: dir,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
mod1 := "package a\np = true"
|
|
|
|
b := Bundle{
|
|
Manifest: Manifest{Revision: "rev-1", Roots: &[]string{"a"}},
|
|
Data: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"b": "foo",
|
|
"e": map[string]interface{}{
|
|
"f": "bar",
|
|
},
|
|
"x": []map[string]string{{"name": "john"}, {"name": "jane"}},
|
|
},
|
|
},
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "a/policy.rego",
|
|
Raw: []byte(mod1),
|
|
Parsed: ast.MustParseModule(mod1),
|
|
},
|
|
},
|
|
Etag: "foo",
|
|
}
|
|
|
|
var buf1 bytes.Buffer
|
|
if err := NewWriter(&buf1).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
loader := NewTarballLoaderWithBaseURL(&buf1, "")
|
|
bundle1, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle1").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": &bundle1,
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: store,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = store.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the snapshot bundle was activated
|
|
txn = storage.NewTransactionOrDie(ctx, store)
|
|
|
|
names, err := ReadBundleNamesFromStore(ctx, store, txn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(names) != len(bundles) {
|
|
t.Fatalf("expected %d bundles in store, found %d", len(bundles), len(names))
|
|
}
|
|
for _, name := range names {
|
|
if _, ok := bundles[name]; !ok {
|
|
t.Fatalf("unexpected bundle name found in store: %s", name)
|
|
}
|
|
}
|
|
|
|
for bundleName, bundle := range bundles {
|
|
for modName := range bundle.ParsedModules(bundleName) {
|
|
if _, ok := compiler.Modules[modName]; !ok {
|
|
t.Fatalf("expected module %s from bundle %s to have been compiled", modName, bundleName)
|
|
}
|
|
}
|
|
}
|
|
|
|
actual, err := store.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw := `
|
|
{
|
|
"a": {
|
|
"b": "foo",
|
|
"e": {
|
|
"f": "bar"
|
|
},
|
|
"x": [{"name": "john"}, {"name": "jane"}]
|
|
},
|
|
"system": {
|
|
"bundles": {
|
|
"bundle1": {
|
|
"manifest": {
|
|
"revision": "rev-1",
|
|
"roots": ["a"]
|
|
},
|
|
"etag": ""
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
store.Abort(ctx, txn)
|
|
|
|
// add a new bundle but abort the transaction and verify only old the bundle data is kept in store
|
|
b = Bundle{
|
|
Manifest: Manifest{Revision: "rev-2", Roots: &[]string{"c"}},
|
|
Data: map[string]interface{}{
|
|
"c": map[string]interface{}{
|
|
"hello": "world",
|
|
},
|
|
},
|
|
Etag: "bar",
|
|
}
|
|
|
|
var buf2 bytes.Buffer
|
|
if err := NewWriter(&buf2).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
|
|
loader = NewTarballLoaderWithBaseURL(&buf2, "")
|
|
bundle2, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle1").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles = map[string]*Bundle{
|
|
"bundle1": &bundle2,
|
|
}
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: store,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
store.Abort(ctx, txn)
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, store)
|
|
|
|
actual, err = store.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw = `
|
|
{
|
|
"a": {
|
|
"b": "foo",
|
|
"e": {
|
|
"f": "bar"
|
|
},
|
|
"x": [{"name": "john"}, {"name": "jane"}]
|
|
},
|
|
"system": {
|
|
"bundles": {
|
|
"bundle1": {
|
|
"manifest": {
|
|
"revision": "rev-1",
|
|
"roots": ["a"]
|
|
},
|
|
"etag": ""
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
expected = loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
store.Abort(ctx, txn)
|
|
|
|
// check symlink is created
|
|
symlink := filepath.Join(dir, "active")
|
|
_, err = os.Lstat(symlink)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// check symlink target
|
|
_, err = filepath.EvalSymlinks(symlink)
|
|
if err != nil {
|
|
t.Fatalf("eval symlinks: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestDeltaBundleLazyModeLifecycleDiskStorage(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
test.WithTempFS(nil, func(dir string) {
|
|
store, err := disk.New(ctx, logging.NewNoOpLogger(), nil, disk.Options{
|
|
Dir: dir,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
mod1 := "package a\np = true"
|
|
mod2 := "package b\np = true"
|
|
|
|
b := Bundle{
|
|
Manifest: Manifest{
|
|
Roots: &[]string{"a"},
|
|
},
|
|
Data: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"b": "foo",
|
|
"e": map[string]interface{}{
|
|
"f": "bar",
|
|
},
|
|
"x": []map[string]string{{"name": "john"}, {"name": "jane"}},
|
|
},
|
|
},
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "a/policy.rego",
|
|
Raw: []byte(mod1),
|
|
Parsed: ast.MustParseModule(mod1),
|
|
},
|
|
},
|
|
Etag: "foo",
|
|
}
|
|
|
|
var buf1 bytes.Buffer
|
|
if err := NewWriter(&buf1).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
loader := NewTarballLoaderWithBaseURL(&buf1, "")
|
|
bundle1, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle1").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
b = Bundle{
|
|
Manifest: Manifest{
|
|
Roots: &[]string{"b", "c"},
|
|
},
|
|
Data: nil,
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "b/policy.rego",
|
|
Raw: []byte(mod2),
|
|
Parsed: ast.MustParseModule(mod2),
|
|
},
|
|
},
|
|
Etag: "foo",
|
|
}
|
|
|
|
var buf2 bytes.Buffer
|
|
if err := NewWriter(&buf2).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
|
|
loader = NewTarballLoaderWithBaseURL(&buf2, "")
|
|
bundle2, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle2").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": &bundle1,
|
|
"bundle2": &bundle2,
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: store,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = store.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the snapshot bundles were activated
|
|
txn = storage.NewTransactionOrDie(ctx, store)
|
|
names, err := ReadBundleNamesFromStore(ctx, store, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
if len(names) != len(bundles) {
|
|
t.Fatalf("expected %d bundles in store, found %d", len(bundles), len(names))
|
|
}
|
|
for _, name := range names {
|
|
if _, ok := bundles[name]; !ok {
|
|
t.Fatalf("unexpected bundle name found in store: %s", name)
|
|
}
|
|
}
|
|
|
|
for bundleName, bundle := range bundles {
|
|
for modName := range bundle.ParsedModules(bundleName) {
|
|
if _, ok := compiler.Modules[modName]; !ok {
|
|
t.Fatalf("expected module %s from bundle %s to have been compiled", modName, bundleName)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
store.Abort(ctx, txn)
|
|
|
|
// 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",
|
|
}
|
|
|
|
// replace a value
|
|
p3 := PatchOperation{
|
|
Op: "replace",
|
|
Path: "a/b",
|
|
Value: "bar",
|
|
}
|
|
|
|
// add a new object root
|
|
p4 := 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}},
|
|
Etag: "bar",
|
|
},
|
|
"bundle2": {
|
|
Manifest: Manifest{
|
|
Revision: "delta-2",
|
|
Roots: &[]string{"b", "c"},
|
|
},
|
|
Patch: Patch{Data: []PatchOperation{p4}},
|
|
Etag: "baz",
|
|
},
|
|
"bundle3": {
|
|
Manifest: Manifest{
|
|
Roots: &[]string{"d"},
|
|
},
|
|
Data: map[string]interface{}{
|
|
"d": map[string]interface{}{
|
|
"e": "foo",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: store,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: deltaBundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = store.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// check the modules from the snapshot bundles are on the compiler
|
|
for bundleName, bundle := range bundles {
|
|
for modName := range bundle.ParsedModules(bundleName) {
|
|
if _, ok := compiler.Modules[modName]; !ok {
|
|
t.Fatalf("expected module %s from bundle %s to have been compiled", modName, bundleName)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure the patches were applied
|
|
txn = storage.NewTransactionOrDie(ctx, store)
|
|
|
|
actual, err := store.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw := `
|
|
{
|
|
"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": ""
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
store.Abort(ctx, txn)
|
|
})
|
|
}
|
|
|
|
func TestBundleLazyModeLifecycleOverlappingBundleRoots(t *testing.T) {
|
|
ctx := context.Background()
|
|
mockStore := mock.New()
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
b := Bundle{
|
|
Manifest: Manifest{
|
|
Revision: "foo",
|
|
Roots: &[]string{"a/b", "a/c", "a/d"},
|
|
},
|
|
Data: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"b": "foo",
|
|
"c": map[string]interface{}{
|
|
"d": "bar",
|
|
},
|
|
"d": []map[string]string{{"name": "john"}, {"name": "jane"}},
|
|
},
|
|
},
|
|
}
|
|
|
|
var buf1 bytes.Buffer
|
|
if err := NewWriter(&buf1).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
loader := NewTarballLoaderWithBaseURL(&buf1, "")
|
|
bundle1, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle1").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
b = Bundle{
|
|
Manifest: Manifest{
|
|
Revision: "bar",
|
|
Roots: &[]string{"a/e"},
|
|
},
|
|
Data: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"e": map[string]interface{}{
|
|
"f": "bar",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
var buf2 bytes.Buffer
|
|
if err := NewWriter(&buf2).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
loader = NewTarballLoaderWithBaseURL(&buf2, "")
|
|
bundle2, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle2").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": &bundle1,
|
|
"bundle2": &bundle2,
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the snapshot bundles were activated
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
names, err := ReadBundleNamesFromStore(ctx, mockStore, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
if len(names) != len(bundles) {
|
|
t.Fatalf("expected %d bundles in store, found %d", len(bundles), len(names))
|
|
}
|
|
for _, name := range names {
|
|
if _, ok := bundles[name]; !ok {
|
|
t.Fatalf("unexpected bundle name found in store: %s", name)
|
|
}
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
|
|
// Ensure the patches were applied
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
|
|
actual, err := mockStore.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw := `
|
|
{
|
|
"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": ""
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
}
|
|
|
|
func TestBundleLazyModeLifecycleOverlappingBundleRootsDiskStorage(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
test.WithTempFS(nil, func(dir string) {
|
|
store, err := disk.New(ctx, logging.NewNoOpLogger(), nil, disk.Options{
|
|
Dir: dir,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
b := Bundle{
|
|
Manifest: Manifest{
|
|
Revision: "foo",
|
|
Roots: &[]string{"a/b/c", "a/b/d", "a/b/e"},
|
|
},
|
|
Data: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"b": map[string]interface{}{
|
|
"c": "bar",
|
|
"d": []map[string]string{{"name": "john"}, {"name": "jane"}},
|
|
"e": []string{"foo", "bar"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
var buf1 bytes.Buffer
|
|
if err := NewWriter(&buf1).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
loader := NewTarballLoaderWithBaseURL(&buf1, "")
|
|
bundle1, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle1").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
b = Bundle{
|
|
Manifest: Manifest{
|
|
Revision: "bar",
|
|
Roots: &[]string{"a/b/f"},
|
|
},
|
|
Data: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"b": map[string]interface{}{
|
|
"f": map[string]interface{}{
|
|
"hello": "world",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
var buf2 bytes.Buffer
|
|
if err := NewWriter(&buf2).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
loader = NewTarballLoaderWithBaseURL(&buf2, "")
|
|
bundle2, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle2").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": &bundle1,
|
|
"bundle2": &bundle2,
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: store,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = store.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the snapshot bundles were activated
|
|
txn = storage.NewTransactionOrDie(ctx, store)
|
|
names, err := ReadBundleNamesFromStore(ctx, store, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
if len(names) != len(bundles) {
|
|
t.Fatalf("expected %d bundles in store, found %d", len(bundles), len(names))
|
|
}
|
|
for _, name := range names {
|
|
if _, ok := bundles[name]; !ok {
|
|
t.Fatalf("unexpected bundle name found in store: %s", name)
|
|
}
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
store.Abort(ctx, txn)
|
|
|
|
// Ensure the patches were applied
|
|
txn = storage.NewTransactionOrDie(ctx, store)
|
|
|
|
actual, err := store.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw := `
|
|
{
|
|
"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": ""
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
store.Abort(ctx, txn)
|
|
})
|
|
}
|
|
|
|
func TestBundleLazyModeLifecycleRawOverlappingBundleRoots(t *testing.T) {
|
|
ctx := context.Background()
|
|
mockStore := mock.New()
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
files := [][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"]}`},
|
|
}
|
|
|
|
buf := archive.MustWriteTarGz(files)
|
|
loader := NewTarballLoaderWithBaseURL(buf, "")
|
|
bundle1, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle1").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
files = [][2]string{
|
|
{"/a/e/x/data.json", "[4,5,6]"},
|
|
{"/data.json", `{"a": {"e": {"f": true}}}`},
|
|
{"/.manifest", `{"revision": "bar", "roots": ["a/e"]}`},
|
|
}
|
|
|
|
buf = archive.MustWriteTarGz(files)
|
|
loader = NewTarballLoaderWithBaseURL(buf, "")
|
|
bundle2, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle2").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": &bundle1,
|
|
"bundle2": &bundle2,
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the snapshot bundles were activated
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
names, err := ReadBundleNamesFromStore(ctx, mockStore, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
if len(names) != len(bundles) {
|
|
t.Fatalf("expected %d bundles in store, found %d", len(bundles), len(names))
|
|
}
|
|
for _, name := range names {
|
|
if _, ok := bundles[name]; !ok {
|
|
t.Fatalf("unexpected bundle name found in store: %s", name)
|
|
}
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
|
|
// Ensure the patches were applied
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
|
|
actual, err := mockStore.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw := `
|
|
{
|
|
"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": ""
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
}
|
|
|
|
func TestBundleLazyModeLifecycleRawOverlappingBundleRootsDiskStorage(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
test.WithTempFS(nil, func(dir string) {
|
|
store, err := disk.New(ctx, logging.NewNoOpLogger(), nil, disk.Options{
|
|
Dir: dir,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
files := [][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"]}`},
|
|
}
|
|
|
|
buf := archive.MustWriteTarGz(files)
|
|
loader := NewTarballLoaderWithBaseURL(buf, "")
|
|
bundle1, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle1").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
files = [][2]string{
|
|
{"/a/c/x/data.json", "[4,5,6]"},
|
|
{"/data.json", `{"a": {"c": {"y": true}}}`},
|
|
{"/.manifest", `{"revision": "bar", "roots": ["a/c"]}`},
|
|
}
|
|
|
|
buf = archive.MustWriteTarGz(files)
|
|
loader = NewTarballLoaderWithBaseURL(buf, "")
|
|
bundle2, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle2").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": &bundle1,
|
|
"bundle2": &bundle2,
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: store,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = store.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the snapshot bundles were activated
|
|
txn = storage.NewTransactionOrDie(ctx, store)
|
|
names, err := ReadBundleNamesFromStore(ctx, store, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
if len(names) != len(bundles) {
|
|
t.Fatalf("expected %d bundles in store, found %d", len(bundles), len(names))
|
|
}
|
|
for _, name := range names {
|
|
if _, ok := bundles[name]; !ok {
|
|
t.Fatalf("unexpected bundle name found in store: %s", name)
|
|
}
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
store.Abort(ctx, txn)
|
|
|
|
// Ensure the patches were applied
|
|
txn = storage.NewTransactionOrDie(ctx, store)
|
|
|
|
actual, err := store.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw := `
|
|
{
|
|
"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": ""
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
store.Abort(ctx, txn)
|
|
})
|
|
}
|
|
|
|
func TestDeltaBundleLazyModeLifecycle(t *testing.T) {
|
|
ctx := context.Background()
|
|
mockStore := mock.New()
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
mod1 := "package a\np = true"
|
|
mod2 := "package b\np = true"
|
|
|
|
b := Bundle{
|
|
Manifest: Manifest{
|
|
Roots: &[]string{"a"},
|
|
},
|
|
Data: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"b": "foo",
|
|
"e": map[string]interface{}{
|
|
"f": "bar",
|
|
},
|
|
"x": []map[string]string{{"name": "john"}, {"name": "jane"}},
|
|
},
|
|
},
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "policy.rego",
|
|
Raw: []byte(mod1),
|
|
Parsed: ast.MustParseModule(mod1),
|
|
},
|
|
},
|
|
Etag: "foo",
|
|
}
|
|
|
|
var buf1 bytes.Buffer
|
|
if err := NewWriter(&buf1).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
loader := NewTarballLoaderWithBaseURL(&buf1, "")
|
|
bundle1, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle1").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
b = Bundle{
|
|
Manifest: Manifest{
|
|
Roots: &[]string{"b", "c"},
|
|
},
|
|
Data: nil,
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "policy.rego",
|
|
Raw: []byte(mod2),
|
|
Parsed: ast.MustParseModule(mod2),
|
|
},
|
|
},
|
|
Etag: "foo",
|
|
lazyLoadingMode: true,
|
|
sizeLimitBytes: DefaultSizeLimitBytes + 1,
|
|
}
|
|
|
|
var buf2 bytes.Buffer
|
|
if err := NewWriter(&buf2).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
loader = NewTarballLoaderWithBaseURL(&buf2, "")
|
|
bundle2, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle2").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": &bundle1,
|
|
"bundle2": &bundle2,
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the snapshot bundles were activated
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
names, err := ReadBundleNamesFromStore(ctx, mockStore, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
if len(names) != len(bundles) {
|
|
t.Fatalf("expected %d bundles in store, found %d", len(bundles), len(names))
|
|
}
|
|
for _, name := range names {
|
|
if _, ok := bundles[name]; !ok {
|
|
t.Fatalf("unexpected bundle name found in store: %s", name)
|
|
}
|
|
}
|
|
|
|
for bundleName, bundle := range bundles {
|
|
for modName := range bundle.ParsedModules(bundleName) {
|
|
if _, ok := compiler.Modules[modName]; !ok {
|
|
t.Fatalf("expected module %s from bundle %s to have been compiled", modName, bundleName)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
|
|
// 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: map[string]interface{}{
|
|
"d": map[string]interface{}{
|
|
"e": "foo",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: deltaBundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// check the modules from the snapshot bundles are on the compiler
|
|
for bundleName, bundle := range bundles {
|
|
for modName := range bundle.ParsedModules(bundleName) {
|
|
if _, ok := compiler.Modules[modName]; !ok {
|
|
t.Fatalf("expected module %s from bundle %s to have been compiled", modName, bundleName)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure the patches were applied
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
|
|
actual, err := mockStore.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
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": ""
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
|
|
mockStore.AssertValid(t)
|
|
}
|
|
|
|
func TestDeltaBundleLazyModeWithDefaultRules(t *testing.T) {
|
|
ctx := context.Background()
|
|
mockStore := mock.New()
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
mod1 := "package a\ndefault p = true"
|
|
mod2 := "package b\ndefault p = true"
|
|
|
|
b := Bundle{
|
|
Manifest: Manifest{
|
|
Roots: &[]string{"a"},
|
|
},
|
|
Data: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"b": "foo",
|
|
"e": map[string]interface{}{
|
|
"f": "bar",
|
|
},
|
|
"x": []map[string]string{{"name": "john"}, {"name": "jane"}},
|
|
},
|
|
},
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "policy.rego",
|
|
Raw: []byte(mod1),
|
|
Parsed: ast.MustParseModule(mod1),
|
|
},
|
|
},
|
|
Etag: "foo",
|
|
}
|
|
|
|
var buf1 bytes.Buffer
|
|
if err := NewWriter(&buf1).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
loader := NewTarballLoaderWithBaseURL(&buf1, "")
|
|
bundle1, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle1").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
b = Bundle{
|
|
Manifest: Manifest{
|
|
Roots: &[]string{"b", "c"},
|
|
},
|
|
Data: nil,
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "policy.rego",
|
|
Raw: []byte(mod2),
|
|
Parsed: ast.MustParseModule(mod2),
|
|
},
|
|
},
|
|
Etag: "foo",
|
|
lazyLoadingMode: true,
|
|
sizeLimitBytes: DefaultSizeLimitBytes + 1,
|
|
}
|
|
|
|
var buf2 bytes.Buffer
|
|
if err := NewWriter(&buf2).UseModulePath(true).Write(b); err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
loader = NewTarballLoaderWithBaseURL(&buf2, "")
|
|
bundle2, err := NewCustomReader(loader).WithLazyLoadingMode(true).WithBundleName("bundle2").Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": &bundle1,
|
|
"bundle2": &bundle2,
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the snapshot bundles were activated
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
names, err := ReadBundleNamesFromStore(ctx, mockStore, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
if len(names) != len(bundles) {
|
|
t.Fatalf("expected %d bundles in store, found %d", len(bundles), len(names))
|
|
}
|
|
for _, name := range names {
|
|
if _, ok := bundles[name]; !ok {
|
|
t.Fatalf("unexpected bundle name found in store: %s", name)
|
|
}
|
|
}
|
|
|
|
for bundleName, bundle := range bundles {
|
|
for modName := range bundle.ParsedModules(bundleName) {
|
|
if _, ok := compiler.Modules[modName]; !ok {
|
|
t.Fatalf("expected module %s from bundle %s to have been compiled", modName, bundleName)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
|
|
// 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: map[string]interface{}{
|
|
"d": map[string]interface{}{
|
|
"e": "foo",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
expectedModuleCount := len(compiler.Modules)
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: deltaBundles,
|
|
})
|
|
if expectedModuleCount != len(compiler.Modules) {
|
|
t.Fatalf("Expected %d modules, got %d", expectedModuleCount, len(compiler.Modules))
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// check the modules from the snapshot bundles are on the compiler
|
|
for bundleName, bundle := range bundles {
|
|
for modName := range bundle.ParsedModules(bundleName) {
|
|
if _, ok := compiler.Modules[modName]; !ok {
|
|
t.Fatalf("expected module %s from bundle %s to have been compiled", modName, bundleName)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure the patches were applied
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
|
|
actual, err := mockStore.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
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": ""
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
|
|
mockStore.AssertValid(t)
|
|
}
|
|
|
|
func TestBundleLifecycle(t *testing.T) {
|
|
ctx := context.Background()
|
|
mockStore := mock.New()
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
extraMods := map[string]*ast.Module{
|
|
"mod1": ast.MustParseModule("package x\np = true"),
|
|
}
|
|
|
|
const mod2 = "package a\np = true"
|
|
mod3 := "package b\np = true"
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": {
|
|
Manifest: Manifest{
|
|
Roots: &[]string{"a"},
|
|
},
|
|
Data: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"b": "foo",
|
|
},
|
|
},
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "a/policy.rego",
|
|
Raw: []byte(mod2),
|
|
Parsed: ast.MustParseModule(mod2),
|
|
},
|
|
},
|
|
Etag: "foo"},
|
|
"bundle2": {
|
|
Manifest: Manifest{
|
|
Roots: &[]string{"b", "c"},
|
|
},
|
|
Data: nil,
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "b/policy.rego",
|
|
Raw: []byte(mod3),
|
|
Parsed: ast.MustParseModule(mod3),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err := Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
ExtraModules: extraMods,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the bundle was activated
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
names, err := ReadBundleNamesFromStore(ctx, mockStore, txn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(names) != len(bundles) {
|
|
t.Fatalf("expected %d bundles in store, found %d", len(bundles), len(names))
|
|
}
|
|
for _, name := range names {
|
|
if _, ok := bundles[name]; !ok {
|
|
t.Fatalf("unexpected bundle name found in store: %s", name)
|
|
}
|
|
}
|
|
|
|
for bundleName, bundle := range bundles {
|
|
for modName := range bundle.ParsedModules(bundleName) {
|
|
if _, ok := compiler.Modules[modName]; !ok {
|
|
t.Fatalf("expected module %s from bundle %s to have been compiled", modName, bundleName)
|
|
}
|
|
}
|
|
}
|
|
|
|
actual, err := mockStore.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
expectedRaw := `
|
|
{
|
|
"a": {
|
|
"b": "foo"
|
|
},
|
|
"system": {
|
|
"bundles": {
|
|
"bundle1": {
|
|
"manifest": {
|
|
"revision": "",
|
|
"roots": ["a"]
|
|
},
|
|
"etag": "foo"
|
|
},
|
|
"bundle2": {
|
|
"manifest": {
|
|
"revision": "",
|
|
"roots": ["b", "c"]
|
|
},
|
|
"etag": ""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Ensure that the extra module was included
|
|
if _, ok := compiler.Modules["mod1"]; !ok {
|
|
t.Fatalf("expected extra module to be compiled")
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err = Deactivate(&DeactivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
BundleNames: map[string]struct{}{"bundle1": {}, "bundle2": {}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Expect the store to have been cleared out after deactivating the bundles
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
names, err = ReadBundleNamesFromStore(ctx, mockStore, txn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(names) != 0 {
|
|
t.Fatalf("expected 0 bundles in store, found %d", len(names))
|
|
}
|
|
|
|
actual, err = mockStore.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
expectedRaw = `{"system": {"bundles": {}}}`
|
|
expected = loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
mockStore.AssertValid(t)
|
|
}
|
|
|
|
func TestDeltaBundleLifecycle(t *testing.T) {
|
|
ctx := context.Background()
|
|
mockStore := mock.New()
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
mod1 := "package a\np = true"
|
|
mod2 := "package b\np = true"
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": {
|
|
Manifest: Manifest{
|
|
Roots: &[]string{"a"},
|
|
},
|
|
Data: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"b": "foo",
|
|
"e": map[string]interface{}{
|
|
"f": "bar",
|
|
},
|
|
"x": []map[string]string{{"name": "john"}, {"name": "jane"}},
|
|
},
|
|
},
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "a/policy.rego",
|
|
Raw: []byte(mod1),
|
|
Parsed: ast.MustParseModule(mod1),
|
|
},
|
|
},
|
|
Etag: "foo",
|
|
},
|
|
"bundle2": {
|
|
Manifest: Manifest{
|
|
Roots: &[]string{"b", "c"},
|
|
},
|
|
Data: nil,
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "b/policy.rego",
|
|
Raw: []byte(mod2),
|
|
Parsed: ast.MustParseModule(mod2),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err := Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the snapshot bundles were activated
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
names, err := ReadBundleNamesFromStore(ctx, mockStore, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
if len(names) != len(bundles) {
|
|
t.Fatalf("expected %d bundles in store, found %d", len(bundles), len(names))
|
|
}
|
|
for _, name := range names {
|
|
if _, ok := bundles[name]; !ok {
|
|
t.Fatalf("unexpected bundle name found in store: %s", name)
|
|
}
|
|
}
|
|
|
|
for bundleName, bundle := range bundles {
|
|
for modName := range bundle.ParsedModules(bundleName) {
|
|
if _, ok := compiler.Modules[modName]; !ok {
|
|
t.Fatalf("expected module %s from bundle %s to have been compiled", modName, bundleName)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
|
|
// 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: map[string]interface{}{
|
|
"d": map[string]interface{}{
|
|
"e": "foo",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: deltaBundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// check the modules from the snapshot bundles are on the compiler
|
|
for bundleName, bundle := range bundles {
|
|
for modName := range bundle.ParsedModules(bundleName) {
|
|
if _, ok := compiler.Modules[modName]; !ok {
|
|
t.Fatalf("expected module %s from bundle %s to have been compiled", modName, bundleName)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure the patches were applied
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
|
|
actual, err := mockStore.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
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": ""
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
|
|
mockStore.AssertValid(t)
|
|
}
|
|
|
|
func TestDeltaBundleActivate(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
mockStore := mock.New()
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
// 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",
|
|
},
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err := Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: deltaBundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the delta bundle was activated
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
names, err := ReadBundleNamesFromStore(ctx, mockStore, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
if len(names) != len(deltaBundles) {
|
|
t.Fatalf("expected %d bundles in store, found %d", len(deltaBundles), len(names))
|
|
}
|
|
|
|
for _, name := range names {
|
|
if _, ok := deltaBundles[name]; !ok {
|
|
t.Fatalf("unexpected bundle name found in store: %s", name)
|
|
}
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
|
|
// Ensure the patches were applied
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
|
|
actual, err := mockStore.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedRaw := `
|
|
{
|
|
"a": {
|
|
"c": {
|
|
"d": ["foo", "bar"]
|
|
}
|
|
},
|
|
"system": {
|
|
"bundles": {
|
|
"bundle1": {
|
|
"manifest": {
|
|
"revision": "delta",
|
|
"roots": ["a"]
|
|
},
|
|
"etag": "foo"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`
|
|
expected := loadExpectedSortedResult(expectedRaw)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", expectedRaw, string(util.MustMarshalJSON(actual)))
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
|
|
mockStore.AssertValid(t)
|
|
}
|
|
|
|
func TestDeltaBundleBadManifest(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
mockStore := mock.New()
|
|
|
|
compiler := ast.NewCompiler()
|
|
m := metrics.New()
|
|
|
|
mod1 := "package a\np = true"
|
|
|
|
bundles := map[string]*Bundle{
|
|
"bundle1": {
|
|
Manifest: Manifest{
|
|
Roots: &[]string{"a"},
|
|
},
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "a/policy.rego",
|
|
Raw: []byte(mod1),
|
|
Parsed: ast.MustParseModule(mod1),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
txn := storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err := Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
Bundles: bundles,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Ensure the snapshot bundle was activated
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
names, err := ReadBundleNamesFromStore(ctx, mockStore, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
if len(names) != len(bundles) {
|
|
t.Fatalf("expected %d bundles in store, found %d", len(bundles), len(names))
|
|
}
|
|
for _, name := range names {
|
|
if _, ok := bundles[name]; !ok {
|
|
t.Fatalf("unexpected bundle name found in store: %s", name)
|
|
}
|
|
}
|
|
|
|
// Stop the "read" transaction
|
|
mockStore.Abort(ctx, txn)
|
|
|
|
// create a delta bundle with a different manifest from the snapshot bundle
|
|
|
|
p1 := PatchOperation{
|
|
Op: "upsert",
|
|
Path: "/a/c/d",
|
|
Value: []string{"foo", "bar"},
|
|
}
|
|
|
|
deltaBundles := map[string]*Bundle{
|
|
"bundle1": {
|
|
Manifest: Manifest{
|
|
Roots: &[]string{"b"},
|
|
},
|
|
Patch: Patch{Data: []PatchOperation{p1}},
|
|
},
|
|
}
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err = Activate(&ActivateOpts{
|
|
Ctx: ctx,
|
|
Store: mockStore,
|
|
Txn: txn,
|
|
Compiler: compiler,
|
|
Metrics: m,
|
|
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) {
|
|
ctx := context.Background()
|
|
cases := []struct {
|
|
note string
|
|
initialData map[string]interface{}
|
|
roots []string
|
|
expectErr bool
|
|
expected string
|
|
}{
|
|
{
|
|
note: "erase all",
|
|
initialData: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"b": "foo",
|
|
},
|
|
"b": "bar",
|
|
},
|
|
roots: []string{"a", "b"},
|
|
expectErr: false,
|
|
expected: `{}`,
|
|
},
|
|
{
|
|
note: "erase none",
|
|
initialData: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"b": "foo",
|
|
},
|
|
"b": "bar",
|
|
},
|
|
roots: []string{},
|
|
expectErr: false,
|
|
expected: `{"a": {"b": "foo"}, "b": "bar"}`,
|
|
},
|
|
{
|
|
note: "erase partial",
|
|
initialData: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"b": "foo",
|
|
},
|
|
"b": "bar",
|
|
},
|
|
roots: []string{"a"},
|
|
expectErr: false,
|
|
expected: `{"b": "bar"}`,
|
|
},
|
|
{
|
|
note: "erase partial path",
|
|
initialData: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"b": "foo",
|
|
"c": map[string]interface{}{
|
|
"d": 123,
|
|
},
|
|
},
|
|
},
|
|
roots: []string{"a/c/d"},
|
|
expectErr: false,
|
|
expected: `{"a": {"b": "foo", "c":{}}}`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
mockStore := mock.NewWithData(tc.initialData)
|
|
txn := storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
roots := map[string]struct{}{}
|
|
for _, root := range tc.roots {
|
|
roots[root] = struct{}{}
|
|
}
|
|
|
|
err := eraseData(ctx, mockStore, txn, 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)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
mockStore.AssertValid(t)
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
actual, err := mockStore.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
expected := loadExpectedSortedResult(tc.expected)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", tc.expected, actual)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestErasePolicies(t *testing.T) {
|
|
ctx := context.Background()
|
|
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{""},
|
|
expectErr: false,
|
|
expectedRemaining: []string{},
|
|
},
|
|
{
|
|
note: "erase none",
|
|
initialPolicies: map[string][]byte{
|
|
"mod1": []byte("package a\np = true"),
|
|
"mod2": []byte("package b\np = true"),
|
|
},
|
|
roots: []string{"c"},
|
|
expectErr: false,
|
|
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"},
|
|
expectErr: false,
|
|
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"},
|
|
expectErr: false,
|
|
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(ctx, mockStore, storage.WriteParams)
|
|
|
|
for name, mod := range tc.initialPolicies {
|
|
err := mockStore.UpsertPolicy(ctx, txn, name, mod)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
}
|
|
|
|
roots := map[string]struct{}{}
|
|
for _, root := range tc.roots {
|
|
roots[root] = struct{}{}
|
|
}
|
|
remaining, err := erasePolicies(ctx, mockStore, txn, 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)
|
|
}
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
mockStore.AssertValid(t)
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
actualRemaining, err := mockStore.ListPolicies(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
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 := false
|
|
for _, actualName := range actualRemaining {
|
|
if expectedName == actualName {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("expected remaining module %s not found", expectedName)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWriteData(t *testing.T) {
|
|
ctx := context.Background()
|
|
cases := []struct {
|
|
note string
|
|
existingData map[string]interface{}
|
|
roots []string
|
|
data map[string]interface{}
|
|
expected string
|
|
expectErr bool
|
|
}{
|
|
{
|
|
note: "single root",
|
|
roots: []string{"a"},
|
|
data: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"b": map[string]interface{}{
|
|
"c": 123,
|
|
},
|
|
},
|
|
},
|
|
expected: `{"a": {"b": {"c": 123}}}`,
|
|
expectErr: false,
|
|
},
|
|
{
|
|
note: "multiple roots",
|
|
roots: []string{"a", "b/c/d"},
|
|
data: map[string]interface{}{
|
|
"a": "foo",
|
|
"b": map[string]interface{}{
|
|
"c": map[string]interface{}{
|
|
"d": "bar",
|
|
},
|
|
},
|
|
},
|
|
expected: `{"a": "foo","b": {"c": {"d": "bar"}}}`,
|
|
expectErr: false,
|
|
},
|
|
{
|
|
note: "data not in roots",
|
|
roots: []string{"a"},
|
|
data: map[string]interface{}{
|
|
"a": "foo",
|
|
"b": map[string]interface{}{
|
|
"c": map[string]interface{}{
|
|
"d": "bar",
|
|
},
|
|
},
|
|
},
|
|
expected: `{"a": "foo"}`,
|
|
expectErr: false,
|
|
},
|
|
{
|
|
note: "no data",
|
|
roots: []string{"a"},
|
|
existingData: map[string]interface{}{},
|
|
data: map[string]interface{}{},
|
|
expected: `{}`,
|
|
expectErr: false,
|
|
},
|
|
{
|
|
note: "no new data",
|
|
roots: []string{"a"},
|
|
existingData: map[string]interface{}{
|
|
"a": "foo",
|
|
},
|
|
data: map[string]interface{}{},
|
|
expected: `{"a": "foo"}`,
|
|
expectErr: false,
|
|
},
|
|
{
|
|
note: "overwrite data",
|
|
roots: []string{"a"},
|
|
existingData: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"b": "foo",
|
|
},
|
|
},
|
|
data: map[string]interface{}{
|
|
"a": "bar",
|
|
},
|
|
expected: `{"a": "bar"}`,
|
|
expectErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
mockStore := mock.NewWithData(tc.existingData)
|
|
txn := storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
err := writeData(ctx, mockStore, txn, tc.roots, 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)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
mockStore.AssertValid(t)
|
|
|
|
txn = storage.NewTransactionOrDie(ctx, mockStore)
|
|
actual, err := mockStore.Read(ctx, txn, storage.MustParsePath("/"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
expected := loadExpectedSortedResult(tc.expected)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Errorf("expected %v, got %v", tc.expected, actual)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func loadExpectedResult(input string) interface{} {
|
|
if len(input) == 0 {
|
|
return nil
|
|
}
|
|
var data interface{}
|
|
if err := util.UnmarshalJSON([]byte(input), &data); err != nil {
|
|
panic(err)
|
|
}
|
|
return data
|
|
}
|
|
|
|
func loadExpectedSortedResult(input string) interface{} {
|
|
data := loadExpectedResult(input)
|
|
switch data := data.(type) {
|
|
case []interface{}:
|
|
return data
|
|
default:
|
|
return data
|
|
}
|
|
}
|
|
|
|
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]interface{}
|
|
expectErr bool
|
|
}
|
|
|
|
func TestWriteModules(t *testing.T) {
|
|
|
|
cases := []testWriteModuleCase{
|
|
{
|
|
note: "module files only",
|
|
bundles: map[string]*Bundle{
|
|
"bundle1": {
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "mod1",
|
|
Raw: []byte("package a\np = true"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expectErr: false,
|
|
},
|
|
{
|
|
note: "extra modules only",
|
|
extraMods: map[string]*ast.Module{
|
|
"mod1": ast.MustParseModule("package a\np = true"),
|
|
},
|
|
expectErr: false,
|
|
},
|
|
{
|
|
note: "compiler modules only",
|
|
compilerMods: map[string]*ast.Module{
|
|
"mod1": ast.MustParseModule("package a\np = true"),
|
|
},
|
|
expectErr: false,
|
|
},
|
|
{
|
|
note: "module files and extra modules",
|
|
bundles: map[string]*Bundle{
|
|
"bundle1": {
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "mod1",
|
|
Raw: []byte("package a\np = true"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
extraMods: map[string]*ast.Module{
|
|
"mod2": ast.MustParseModule("package b\np = false"),
|
|
},
|
|
expectErr: false,
|
|
},
|
|
{
|
|
note: "module files and compiler modules",
|
|
bundles: map[string]*Bundle{
|
|
"bundle1": {
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "mod1",
|
|
Raw: []byte("package a\np = true"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
compilerMods: map[string]*ast.Module{
|
|
"mod2": ast.MustParseModule("package b\np = false"),
|
|
},
|
|
expectErr: false,
|
|
},
|
|
{
|
|
note: "extra modules and compiler modules",
|
|
extraMods: map[string]*ast.Module{
|
|
"mod1": ast.MustParseModule("package a\np = true"),
|
|
},
|
|
compilerMods: map[string]*ast.Module{
|
|
"mod2": ast.MustParseModule("package b\np = false"),
|
|
},
|
|
expectErr: false,
|
|
},
|
|
{
|
|
note: "compile error: path conflict",
|
|
bundles: map[string]*Bundle{
|
|
"bundle1": {
|
|
Modules: []ModuleFile{
|
|
{
|
|
Path: "mod1",
|
|
Raw: []byte("package a\np = true"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
storeData: map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"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) {
|
|
|
|
ctx := context.Background()
|
|
mockStore := mock.NewWithData(tc.storeData)
|
|
txn := storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
compiler := ast.NewCompiler().WithPathConflictsCheck(storage.NonEmpty(ctx, mockStore, txn))
|
|
m := metrics.New()
|
|
|
|
// if supplied, pre-parse the module files
|
|
|
|
for _, b := range tc.bundles {
|
|
var parsedMods []ModuleFile
|
|
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 {
|
|
compiler.Compile(tc.compilerMods)
|
|
if len(compiler.Errors) > 0 {
|
|
t.Fatalf("unexpected error: %s", compiler.Errors)
|
|
}
|
|
}
|
|
|
|
err := writeModules(ctx, mockStore, txn, compiler, m, 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
|
|
policies, err := mockStore.ListPolicies(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
expectedNumMods := 0
|
|
for _, b := range tc.bundles {
|
|
expectedNumMods += len(b.Modules)
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
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",
|
|
input: nil,
|
|
path: "/",
|
|
roots: []string{""},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
note: "data within roots root case",
|
|
input: map[string]json.RawMessage{"a": json.RawMessage(`true`)},
|
|
path: "",
|
|
roots: []string{"a"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
note: "data within roots nested 1",
|
|
input: map[string]json.RawMessage{"d": json.RawMessage(`true`)},
|
|
path: filepath.Dir(strings.Trim("a/b/c/data.json", "/")),
|
|
roots: []string{"a/b/c"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
note: "data within roots nested 2",
|
|
input: map[string]json.RawMessage{"d": json.RawMessage(`{"hello": "world"}`)},
|
|
path: filepath.Dir(strings.Trim("a/b/c/data.json", "/")),
|
|
roots: []string{"a/b/c"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
note: "data within roots nested 3",
|
|
input: map[string]json.RawMessage{"d": json.RawMessage(`{"hello": "world"}`)},
|
|
path: filepath.Dir(strings.Trim("a/data.json", "/")),
|
|
roots: []string{"a/d"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
note: "data within multiple roots 1",
|
|
input: map[string]json.RawMessage{"a": json.RawMessage(`{"b": "c"}`), "c": json.RawMessage(`true`)},
|
|
path: filepath.Dir(strings.Trim("/data.json", "/")),
|
|
roots: []string{"a/b", "c"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
note: "data within multiple roots 2",
|
|
input: map[string]json.RawMessage{"a": json.RawMessage(`{"b": "c"}`), "c": []byte(`{"d": {"e": {"f": true}}}`)},
|
|
path: filepath.Dir(strings.Trim("/data.json", "/")),
|
|
roots: []string{"a/b", "c/d/e"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
note: "data outside roots 1",
|
|
input: map[string]json.RawMessage{"d": json.RawMessage(`{"hello": "world"}`)},
|
|
path: filepath.Dir(strings.Trim("/data.json", "/")),
|
|
roots: []string{"a/d"},
|
|
wantErr: true,
|
|
err: fmt.Errorf("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(strings.Trim("/x/data.json", "/")),
|
|
roots: []string{"x/a/b/c/d"},
|
|
wantErr: true,
|
|
err: fmt.Errorf("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: filepath.Dir(strings.Trim("/data.json", "/")),
|
|
roots: []string{"a/b/c/d"},
|
|
wantErr: true,
|
|
err: fmt.Errorf("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: filepath.Dir(strings.Trim("/data.json", "/")),
|
|
roots: []string{"a/b", "c"},
|
|
wantErr: true,
|
|
err: fmt.Errorf("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: filepath.Dir(strings.Trim("/data.json", "/")),
|
|
roots: []string{"a/b", "c/d/e"},
|
|
wantErr: true,
|
|
err: fmt.Errorf("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) {
|
|
|
|
err := doDFS(tc.input, tc.path, tc.roots)
|
|
if 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) {
|
|
ctx := context.Background()
|
|
|
|
cases := []struct {
|
|
note string
|
|
storeRoots map[string]*[]string
|
|
bundleRoots map[string]*[]string
|
|
overlaps bool
|
|
}{
|
|
{
|
|
note: "no overlap with existing roots",
|
|
storeRoots: map[string]*[]string{"bundle1": {"a", "b"}},
|
|
bundleRoots: map[string]*[]string{"bundle2": {"c"}},
|
|
overlaps: false,
|
|
},
|
|
{
|
|
note: "no overlap with existing roots multiple bundles",
|
|
storeRoots: map[string]*[]string{"bundle1": {"a", "b"}},
|
|
bundleRoots: map[string]*[]string{"bundle2": {"c"}, "bundle3": {"d"}},
|
|
overlaps: false,
|
|
},
|
|
{
|
|
note: "no overlap no existing roots",
|
|
storeRoots: map[string]*[]string{},
|
|
bundleRoots: map[string]*[]string{"bundle1": {"a", "b"}},
|
|
overlaps: false,
|
|
},
|
|
{
|
|
note: "no overlap without existing roots multiple bundles",
|
|
storeRoots: map[string]*[]string{},
|
|
bundleRoots: map[string]*[]string{"bundle1": {"a", "b"}, "bundle2": {"c"}},
|
|
overlaps: false,
|
|
},
|
|
{
|
|
note: "overlap without existing roots multiple bundles",
|
|
storeRoots: map[string]*[]string{},
|
|
bundleRoots: map[string]*[]string{"bundle1": {"a", "b"}, "bundle2": {"a", "c"}},
|
|
overlaps: true,
|
|
},
|
|
{
|
|
note: "overlap with existing roots",
|
|
storeRoots: map[string]*[]string{"bundle1": {"a", "b"}},
|
|
bundleRoots: map[string]*[]string{"bundle2": {"c", "a"}},
|
|
overlaps: true,
|
|
},
|
|
{
|
|
note: "overlap with existing roots multiple bundles",
|
|
storeRoots: map[string]*[]string{"bundle1": {"a", "b"}},
|
|
bundleRoots: map[string]*[]string{"bundle2": {"c", "a"}, "bundle3": {"a"}},
|
|
overlaps: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
mockStore := mock.New()
|
|
txn := storage.NewTransactionOrDie(ctx, mockStore, storage.WriteParams)
|
|
|
|
for name, roots := range tc.storeRoots {
|
|
err := WriteManifestToStore(ctx, mockStore, txn, name, Manifest{Roots: roots})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
}
|
|
|
|
bundles := map[string]*Bundle{}
|
|
for name, roots := range tc.bundleRoots {
|
|
bundles[name] = &Bundle{
|
|
Manifest: Manifest{
|
|
Roots: roots,
|
|
},
|
|
}
|
|
}
|
|
|
|
err := hasRootsOverlap(ctx, mockStore, txn, bundles)
|
|
if !tc.overlaps && err != nil {
|
|
t.Fatalf("unepected error: %s", err)
|
|
} else if tc.overlaps && (err == nil || !strings.Contains(err.Error(), "detected overlapping roots in bundle manifest")) {
|
|
t.Fatalf("expected overlapping roots error, got: %s", err)
|
|
}
|
|
|
|
err = mockStore.Commit(ctx, txn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
mockStore.AssertValid(t)
|
|
})
|
|
}
|
|
}
|