Skip to content

Commit a6bc629

Browse files
bundle: Add info about the correct rego version to parse modules on the store (#7278)
Fixing an issue where the rego-version for individual modules was lost during bundle deactivation (bundle lifecycle) if this version diverged from the active runtime rego-version. This could cause reloading of v0 bundles to fail when OPA was not running with the `--v0-compatible` flag. Signed-off-by: Ashutosh Narkar <[email protected]> Co-authored-by: Johan Fylling <[email protected]> (cherry picked from commit 5d5329d)
1 parent 7341942 commit a6bc629

File tree

5 files changed

+1452
-70
lines changed

5 files changed

+1452
-70
lines changed

v1/bundle/bundle.go

+25-11
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ type Bundle struct {
6767

6868
// Raw contains raw bytes representing the bundle's content
6969
type Raw struct {
70-
Path string
71-
Value []byte
70+
Path string
71+
Value []byte
72+
module *ModuleFile
7273
}
7374

7475
// Patch contains an array of objects wherein each object represents the patch operation to be
@@ -633,15 +634,6 @@ func (r *Reader) Read() (Bundle, error) {
633634
fullPath := r.fullPath(path)
634635
bs := buf.Bytes()
635636

636-
if r.lazyLoadingMode {
637-
p := fullPath
638-
if r.name != "" {
639-
p = modulePathWithPrefix(r.name, fullPath)
640-
}
641-
642-
raw = append(raw, Raw{Path: p, Value: bs})
643-
}
644-
645637
// Modules are parsed after we've had a chance to read the manifest
646638
mf := ModuleFile{
647639
URL: f.URL(),
@@ -650,6 +642,15 @@ func (r *Reader) Read() (Bundle, error) {
650642
Raw: bs,
651643
}
652644
modules = append(modules, mf)
645+
646+
if r.lazyLoadingMode {
647+
p := fullPath
648+
if r.name != "" {
649+
p = modulePathWithPrefix(r.name, fullPath)
650+
}
651+
652+
raw = append(raw, Raw{Path: p, Value: bs, module: &mf})
653+
}
653654
} else if filepath.Base(path) == WasmFile {
654655
bundle.WasmModules = append(bundle.WasmModules, WasmModuleFile{
655656
URL: f.URL(),
@@ -1220,6 +1221,19 @@ func (b *Bundle) RegoVersionForFile(path string, def ast.RegoVersion) (ast.RegoV
12201221
return def, fmt.Errorf("unknown bundle rego-version %d for file '%s'", *version, path)
12211222
}
12221223

1224+
func (m *Manifest) RegoVersionForFile(path string) (ast.RegoVersion, error) {
1225+
v, err := m.numericRegoVersionForFile(path)
1226+
if err != nil {
1227+
return ast.RegoUndefined, err
1228+
}
1229+
1230+
if v == nil {
1231+
return ast.RegoUndefined, nil
1232+
}
1233+
1234+
return ast.RegoVersionFromInt(*v), nil
1235+
}
1236+
12231237
func (m *Manifest) numericRegoVersionForFile(path string) (*int, error) {
12241238
var version *int
12251239

v1/bundle/file.go

+16-7
Original file line numberDiff line numberDiff line change
@@ -438,15 +438,11 @@ func (it *iterator) Next() (*storage.Update, error) {
438438
for _, item := range it.raw {
439439
f := file{name: item.Path}
440440

441-
fpath := strings.TrimLeft(normalizePath(filepath.Dir(f.name)), "/.")
442-
if strings.HasSuffix(f.name, RegoExt) {
443-
fpath = strings.Trim(normalizePath(f.name), "/")
441+
p, err := getFileStoragePath(f.name)
442+
if err != nil {
443+
return nil, err
444444
}
445445

446-
p, ok := storage.ParsePathEscaped("/" + fpath)
447-
if !ok {
448-
return nil, fmt.Errorf("storage path invalid: %v", f.name)
449-
}
450446
f.path = p
451447

452448
f.raw = item.Value
@@ -506,3 +502,16 @@ func getdepth(path string, isDir bool) int {
506502
basePath := strings.Trim(filepath.Dir(filepath.ToSlash(path)), "/")
507503
return len(strings.Split(basePath, "/"))
508504
}
505+
506+
func getFileStoragePath(path string) (storage.Path, error) {
507+
fpath := strings.TrimLeft(normalizePath(filepath.Dir(path)), "/.")
508+
if strings.HasSuffix(path, RegoExt) {
509+
fpath = strings.Trim(normalizePath(path), "/")
510+
}
511+
512+
p, ok := storage.ParsePathEscaped("/" + fpath)
513+
if !ok {
514+
return nil, fmt.Errorf("storage path invalid: %v", path)
515+
}
516+
return p, nil
517+
}

v1/bundle/store.go

+119-10
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
// BundlesBasePath is the storage path used for storing bundle metadata
2424
var BundlesBasePath = storage.MustParsePath("/system/bundles")
2525

26+
var ModulesInfoBasePath = storage.MustParsePath("/system/modules")
27+
2628
// Note: As needed these helpers could be memoized.
2729

2830
// ManifestStoragePath is the storage path used for the given named bundle manifest.
@@ -59,6 +61,14 @@ func metadataPath(name string) storage.Path {
5961
return append(BundlesBasePath, name, "manifest", "metadata")
6062
}
6163

64+
func moduleRegoVersionPath(id string) storage.Path {
65+
return append(ModulesInfoBasePath, strings.Trim(id, "/"), "rego_version")
66+
}
67+
68+
func moduleInfoPath(id string) storage.Path {
69+
return append(ModulesInfoBasePath, strings.Trim(id, "/"))
70+
}
71+
6272
func read(ctx context.Context, store storage.Store, txn storage.Transaction, path storage.Path) (interface{}, error) {
6373
value, err := store.Read(ctx, txn, path)
6474
if err != nil {
@@ -166,6 +176,16 @@ func eraseWasmModulesFromStore(ctx context.Context, store storage.Store, txn sto
166176
return suppressNotFound(err)
167177
}
168178

179+
func eraseModuleRegoVersionsFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, modules []string) error {
180+
for _, module := range modules {
181+
err := store.Write(ctx, txn, storage.RemoveOp, moduleInfoPath(module), nil)
182+
if err := suppressNotFound(err); err != nil {
183+
return err
184+
}
185+
}
186+
return nil
187+
}
188+
169189
// ReadWasmMetadataFromStore will read Wasm module resolver metadata from the store.
170190
func ReadWasmMetadataFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) ([]WasmResolver, error) {
171191
path := wasmEntrypointsPath(name)
@@ -626,7 +646,7 @@ func eraseBundles(ctx context.Context, store storage.Store, txn storage.Transact
626646
return nil, err
627647
}
628648

629-
remaining, err := erasePolicies(ctx, store, txn, parserOpts, roots)
649+
remaining, removed, err := erasePolicies(ctx, store, txn, parserOpts, roots)
630650
if err != nil {
631651
return nil, err
632652
}
@@ -649,6 +669,11 @@ func eraseBundles(ctx context.Context, store storage.Store, txn storage.Transact
649669
}
650670
}
651671

672+
err = eraseModuleRegoVersionsFromStore(ctx, store, txn, removed)
673+
if err != nil {
674+
return nil, err
675+
}
676+
652677
return remaining, nil
653678
}
654679

@@ -668,44 +693,103 @@ func eraseData(ctx context.Context, store storage.Store, txn storage.Transaction
668693
return nil
669694
}
670695

671-
func erasePolicies(ctx context.Context, store storage.Store, txn storage.Transaction, parserOpts ast.ParserOptions, roots map[string]struct{}) (map[string]*ast.Module, error) {
696+
type moduleInfo struct {
697+
RegoVersion ast.RegoVersion `json:"rego_version"`
698+
}
699+
700+
func readModuleInfoFromStore(ctx context.Context, store storage.Store, txn storage.Transaction) (map[string]moduleInfo, error) {
701+
value, err := read(ctx, store, txn, ModulesInfoBasePath)
702+
if suppressNotFound(err) != nil {
703+
return nil, err
704+
}
705+
706+
if value == nil {
707+
return nil, nil
708+
}
709+
710+
if m, ok := value.(map[string]any); ok {
711+
versions := make(map[string]moduleInfo, len(m))
712+
713+
for k, v := range m {
714+
if m0, ok := v.(map[string]any); ok {
715+
if ver, ok := m0["rego_version"]; ok {
716+
if vs, ok := ver.(json.Number); ok {
717+
i, err := vs.Int64()
718+
if err != nil {
719+
return nil, fmt.Errorf("corrupt rego version")
720+
}
721+
versions[k] = moduleInfo{RegoVersion: ast.RegoVersionFromInt(int(i))}
722+
}
723+
}
724+
}
725+
}
726+
return versions, nil
727+
}
728+
729+
return nil, fmt.Errorf("corrupt rego version")
730+
}
731+
732+
func erasePolicies(ctx context.Context, store storage.Store, txn storage.Transaction, parserOpts ast.ParserOptions, roots map[string]struct{}) (map[string]*ast.Module, []string, error) {
672733

673734
ids, err := store.ListPolicies(ctx, txn)
674735
if err != nil {
675-
return nil, err
736+
return nil, nil, err
737+
}
738+
739+
modulesInfo, err := readModuleInfoFromStore(ctx, store, txn)
740+
if err != nil {
741+
return nil, nil, fmt.Errorf("failed to read module info from store: %w", err)
742+
}
743+
744+
getRegoVersion := func(modId string) (ast.RegoVersion, bool) {
745+
info, ok := modulesInfo[modId]
746+
if !ok {
747+
return ast.RegoUndefined, false
748+
}
749+
return info.RegoVersion, true
676750
}
677751

678752
remaining := map[string]*ast.Module{}
753+
var removed []string
679754

680755
for _, id := range ids {
681756
bs, err := store.GetPolicy(ctx, txn, id)
682757
if err != nil {
683-
return nil, err
758+
return nil, nil, err
684759
}
685-
module, err := ast.ParseModuleWithOpts(id, string(bs), parserOpts)
760+
761+
parserOptsCpy := parserOpts
762+
if regoVersion, ok := getRegoVersion(id); ok {
763+
parserOptsCpy.RegoVersion = regoVersion
764+
}
765+
766+
module, err := ast.ParseModuleWithOpts(id, string(bs), parserOptsCpy)
686767
if err != nil {
687-
return nil, err
768+
return nil, nil, err
688769
}
689770
path, err := module.Package.Path.Ptr()
690771
if err != nil {
691-
return nil, err
772+
return nil, nil, err
692773
}
693774
deleted := false
694775
for root := range roots {
695776
if RootPathsContain([]string{root}, path) {
696777
if err := store.DeletePolicy(ctx, txn, id); err != nil {
697-
return nil, err
778+
return nil, nil, err
698779
}
699780
deleted = true
700781
break
701782
}
702783
}
703-
if !deleted {
784+
785+
if deleted {
786+
removed = append(removed, id)
787+
} else {
704788
remaining[id] = module
705789
}
706790
}
707791

708-
return remaining, nil
792+
return remaining, removed, nil
709793
}
710794

711795
func writeManifestToStore(opts *ActivateOpts, name string, manifest Manifest) error {
@@ -758,6 +842,12 @@ func writeDataAndModules(ctx context.Context, store storage.Store, txn storage.T
758842
if err := store.UpsertPolicy(ctx, txn, path, mf.Raw); err != nil {
759843
return err
760844
}
845+
846+
if regoVersion, err := b.RegoVersionForFile(mf.Path, ast.RegoUndefined); err == nil && regoVersion != ast.RegoUndefined {
847+
if err := write(ctx, store, txn, moduleRegoVersionPath(path), regoVersion.Int()); err != nil {
848+
return fmt.Errorf("failed to write rego version for '%s' in bundle '%s': %w", mf.Path, name, err)
849+
}
850+
}
761851
}
762852
} else {
763853
params.BasePaths = *b.Manifest.Roots
@@ -766,6 +856,25 @@ func writeDataAndModules(ctx context.Context, store storage.Store, txn storage.T
766856
if err != nil {
767857
return fmt.Errorf("store truncate failed for bundle '%s': %v", name, err)
768858
}
859+
860+
for _, f := range b.Raw {
861+
if strings.HasSuffix(f.Path, RegoExt) {
862+
p, err := getFileStoragePath(f.Path)
863+
if err != nil {
864+
return fmt.Errorf("failed get storage path for module '%s' in bundle '%s': %w", f.Path, name, err)
865+
}
866+
867+
if m := f.module; m != nil {
868+
// 'f.module.Path' contains the module's path as it relates to the bundle root, and can be used for looking up the rego-version.
869+
// 'f.Path' can differ, based on how the bundle reader was initialized.
870+
if regoVersion, err := b.RegoVersionForFile(f.module.Path, ast.RegoUndefined); err == nil && regoVersion != ast.RegoUndefined {
871+
if err := write(ctx, store, txn, moduleRegoVersionPath(p.String()), regoVersion.Int()); err != nil {
872+
return fmt.Errorf("failed to write rego version for '%s' in bundle '%s': %w", f.Path, name, err)
873+
}
874+
}
875+
}
876+
}
877+
}
769878
}
770879
}
771880

0 commit comments

Comments
 (0)