Files
releases/internal/bundle/utils.go
T
Ashutosh Narkar 16300529a2 plugins/discovery: Support to persist and load discovery bundle from disk
This commit adds support to persist and load discovery bundle from disk.
Only the discovery bundle itself is persisted and not the configuration produced
by the discovery bundle. A new field is introduced in OPA's discovery
configuration that can be optionally set to enable OPA to write and
read the discovery bundle from disk. This feature would enable OPA to evaluate
the discovery bundle in scenarios where it is unable to communicate with the
bundle server on start-up.

Fixes #2886

Signed-off-by: Ashutosh Narkar <anarkar4387@gmail.com>
2023-02-06 13:43:02 -08:00

141 lines
4.1 KiB
Go

// Copyright 2020 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package bundle
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/bundle"
"github.com/open-policy-agent/opa/resolver/wasm"
"github.com/open-policy-agent/opa/storage"
)
// LoadWasmResolversFromStore will lookup all Wasm modules from the store along with the
// associated bundle manifest configuration and instantiate the respective resolvers.
func LoadWasmResolversFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, otherBundles map[string]*bundle.Bundle) ([]*wasm.Resolver, error) {
bundleNames, err := bundle.ReadBundleNamesFromStore(ctx, store, txn)
if err != nil && !storage.IsNotFound(err) {
return nil, err
}
var resolversToLoad []*bundle.WasmModuleFile
for _, bundleName := range bundleNames {
var wasmResolverConfigs []bundle.WasmResolver
rawModules := map[string][]byte{}
// Save round-tripping the bundle that was just activated
if _, ok := otherBundles[bundleName]; ok {
wasmResolverConfigs = otherBundles[bundleName].Manifest.WasmResolvers
for _, wmf := range otherBundles[bundleName].WasmModules {
rawModules[wmf.Path] = wmf.Raw
}
} else {
wasmResolverConfigs, err = bundle.ReadWasmMetadataFromStore(ctx, store, txn, bundleName)
if err != nil && !storage.IsNotFound(err) {
return nil, fmt.Errorf("failed to read wasm module manifest from store: %s", err)
}
rawModules, err = bundle.ReadWasmModulesFromStore(ctx, store, txn, bundleName)
if err != nil && !storage.IsNotFound(err) {
return nil, fmt.Errorf("failed to read wasm modules from store: %s", err)
}
}
for path, raw := range rawModules {
wmf := &bundle.WasmModuleFile{
URL: path,
Path: path,
Raw: raw,
}
for _, resolverConf := range wasmResolverConfigs {
if resolverConf.Module == path {
ref, err := ast.PtrRef(ast.DefaultRootDocument, resolverConf.Entrypoint)
if err != nil {
return nil, fmt.Errorf("failed to parse wasm module entrypoint '%s': %s", resolverConf.Entrypoint, err)
}
wmf.Entrypoints = append(wmf.Entrypoints, ref)
}
}
if len(wmf.Entrypoints) > 0 {
resolversToLoad = append(resolversToLoad, wmf)
}
}
}
var resolvers []*wasm.Resolver
if len(resolversToLoad) > 0 {
// Get a full snapshot of the current data (including any from "outside" the bundles)
data, err := store.Read(ctx, txn, storage.Path{})
if err != nil {
return nil, fmt.Errorf("failed to initialize wasm runtime: %s", err)
}
for _, wmf := range resolversToLoad {
resolver, err := wasm.New(wmf.Entrypoints, wmf.Raw, data)
if err != nil {
return nil, fmt.Errorf("failed to initialize wasm module for entrypoints '%s': %s", wmf.Entrypoints, err)
}
resolvers = append(resolvers, resolver)
}
}
return resolvers, nil
}
// LoadBundleFromDisk loads a previously persisted activated bundle from disk
func LoadBundleFromDisk(path, name string, bvc *bundle.VerificationConfig) (*bundle.Bundle, error) {
bundlePath := filepath.Join(path, name, "bundle.tar.gz")
if _, err := os.Stat(bundlePath); err == nil {
f, err := os.Open(filepath.Join(bundlePath))
if err != nil {
return nil, err
}
defer f.Close()
r := bundle.NewCustomReader(bundle.NewTarballLoaderWithBaseURL(f, ""))
if bvc != nil {
r = r.WithBundleVerificationConfig(bvc)
}
b, err := r.Read()
if err != nil {
return nil, err
}
return &b, nil
} else if os.IsNotExist(err) {
return nil, nil
} else {
return nil, err
}
}
// SaveBundleToDisk saves the given raw bytes representing the bundle's content to disk
func SaveBundleToDisk(path string, raw io.Reader) (string, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
err = os.MkdirAll(path, os.ModePerm)
if err != nil {
return "", err
}
}
if raw == nil {
return "", fmt.Errorf("no raw bundle bytes to persist to disk")
}
dest, err := os.CreateTemp(path, ".bundle.tar.gz.*.tmp")
if err != nil {
return "", err
}
defer dest.Close()
_, err = io.Copy(dest, raw)
return dest.Name(), err
}