mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-21 07:51:19 -06:00
16300529a2
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>
141 lines
4.1 KiB
Go
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
|
|
}
|