Files
opencloud/vendor/github.com/open-policy-agent/opa/bundle/store.go
dependabot[bot] 1f069c7c00 build(deps): bump github.com/open-policy-agent/opa from 0.51.0 to 0.59.0
Bumps [github.com/open-policy-agent/opa](https://github.com/open-policy-agent/opa) from 0.51.0 to 0.59.0.
- [Release notes](https://github.com/open-policy-agent/opa/releases)
- [Changelog](https://github.com/open-policy-agent/opa/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-policy-agent/opa/compare/v0.51.0...v0.59.0)

---
updated-dependencies:
- dependency-name: github.com/open-policy-agent/opa
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-05 09:47:11 +01:00

1002 lines
28 KiB
Go

// Copyright 2019 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"
"encoding/base64"
"encoding/json"
"fmt"
"path/filepath"
"strings"
"github.com/open-policy-agent/opa/ast"
iCompiler "github.com/open-policy-agent/opa/internal/compiler"
"github.com/open-policy-agent/opa/internal/json/patch"
"github.com/open-policy-agent/opa/metrics"
"github.com/open-policy-agent/opa/storage"
"github.com/open-policy-agent/opa/util"
)
// BundlesBasePath is the storage path used for storing bundle metadata
var BundlesBasePath = storage.MustParsePath("/system/bundles")
// Note: As needed these helpers could be memoized.
// ManifestStoragePath is the storage path used for the given named bundle manifest.
func ManifestStoragePath(name string) storage.Path {
return append(BundlesBasePath, name, "manifest")
}
// EtagStoragePath is the storage path used for the given named bundle etag.
func EtagStoragePath(name string) storage.Path {
return append(BundlesBasePath, name, "etag")
}
func namedBundlePath(name string) storage.Path {
return append(BundlesBasePath, name)
}
func rootsPath(name string) storage.Path {
return append(BundlesBasePath, name, "manifest", "roots")
}
func revisionPath(name string) storage.Path {
return append(BundlesBasePath, name, "manifest", "revision")
}
func wasmModulePath(name string) storage.Path {
return append(BundlesBasePath, name, "wasm")
}
func wasmEntrypointsPath(name string) storage.Path {
return append(BundlesBasePath, name, "manifest", "wasm")
}
func metadataPath(name string) storage.Path {
return append(BundlesBasePath, name, "manifest", "metadata")
}
// ReadBundleNamesFromStore will return a list of bundle names which have had their metadata stored.
func ReadBundleNamesFromStore(ctx context.Context, store storage.Store, txn storage.Transaction) ([]string, error) {
value, err := store.Read(ctx, txn, BundlesBasePath)
if err != nil {
return nil, err
}
bundleMap, ok := value.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("corrupt manifest roots")
}
bundles := make([]string, len(bundleMap))
idx := 0
for name := range bundleMap {
bundles[idx] = name
idx++
}
return bundles, nil
}
// WriteManifestToStore will write the manifest into the storage. This function is called when
// the bundle is activated.
func WriteManifestToStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string, manifest Manifest) error {
return write(ctx, store, txn, ManifestStoragePath(name), manifest)
}
// WriteEtagToStore will write the bundle etag into the storage. This function is called when the bundle is activated.
func WriteEtagToStore(ctx context.Context, store storage.Store, txn storage.Transaction, name, etag string) error {
return write(ctx, store, txn, EtagStoragePath(name), etag)
}
func write(ctx context.Context, store storage.Store, txn storage.Transaction, path storage.Path, value interface{}) error {
if err := util.RoundTrip(&value); err != nil {
return err
}
var dir []string
if len(path) > 1 {
dir = path[:len(path)-1]
}
if err := storage.MakeDir(ctx, store, txn, dir); err != nil {
return err
}
return store.Write(ctx, txn, storage.AddOp, path, value)
}
// EraseManifestFromStore will remove the manifest from storage. This function is called
// when the bundle is deactivated.
func EraseManifestFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) error {
path := namedBundlePath(name)
err := store.Write(ctx, txn, storage.RemoveOp, path, nil)
return suppressNotFound(err)
}
// eraseBundleEtagFromStore will remove the bundle etag from storage. This function is called
// when the bundle is deactivated.
func eraseBundleEtagFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) error {
path := EtagStoragePath(name)
err := store.Write(ctx, txn, storage.RemoveOp, path, nil)
return suppressNotFound(err)
}
func suppressNotFound(err error) error {
if err == nil || storage.IsNotFound(err) {
return nil
}
return err
}
func writeWasmModulesToStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string, b *Bundle) error {
basePath := wasmModulePath(name)
for _, wm := range b.WasmModules {
path := append(basePath, wm.Path)
err := write(ctx, store, txn, path, base64.StdEncoding.EncodeToString(wm.Raw))
if err != nil {
return err
}
}
return nil
}
func eraseWasmModulesFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) error {
path := wasmModulePath(name)
err := store.Write(ctx, txn, storage.RemoveOp, path, nil)
return suppressNotFound(err)
}
// ReadWasmMetadataFromStore will read Wasm module resolver metadata from the store.
func ReadWasmMetadataFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) ([]WasmResolver, error) {
path := wasmEntrypointsPath(name)
value, err := store.Read(ctx, txn, path)
if err != nil {
return nil, err
}
bs, err := json.Marshal(value)
if err != nil {
return nil, fmt.Errorf("corrupt wasm manifest data")
}
var wasmMetadata []WasmResolver
err = util.UnmarshalJSON(bs, &wasmMetadata)
if err != nil {
return nil, fmt.Errorf("corrupt wasm manifest data")
}
return wasmMetadata, nil
}
// ReadWasmModulesFromStore will write Wasm module resolver metadata from the store.
func ReadWasmModulesFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) (map[string][]byte, error) {
path := wasmModulePath(name)
value, err := store.Read(ctx, txn, path)
if err != nil {
return nil, err
}
encodedModules, ok := value.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("corrupt wasm modules")
}
rawModules := map[string][]byte{}
for path, enc := range encodedModules {
encStr, ok := enc.(string)
if !ok {
return nil, fmt.Errorf("corrupt wasm modules")
}
bs, err := base64.StdEncoding.DecodeString(encStr)
if err != nil {
return nil, err
}
rawModules[path] = bs
}
return rawModules, nil
}
// ReadBundleRootsFromStore returns the roots in the specified bundle.
// If the bundle is not activated, this function will return
// storage NotFound error.
func ReadBundleRootsFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) ([]string, error) {
value, err := store.Read(ctx, txn, rootsPath(name))
if err != nil {
return nil, err
}
sl, ok := value.([]interface{})
if !ok {
return nil, fmt.Errorf("corrupt manifest roots")
}
roots := make([]string, len(sl))
for i := range sl {
roots[i], ok = sl[i].(string)
if !ok {
return nil, fmt.Errorf("corrupt manifest root")
}
}
return roots, nil
}
// ReadBundleRevisionFromStore returns the revision in the specified bundle.
// If the bundle is not activated, this function will return
// storage NotFound error.
func ReadBundleRevisionFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) (string, error) {
return readRevisionFromStore(ctx, store, txn, revisionPath(name))
}
func readRevisionFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, path storage.Path) (string, error) {
value, err := store.Read(ctx, txn, path)
if err != nil {
return "", err
}
str, ok := value.(string)
if !ok {
return "", fmt.Errorf("corrupt manifest revision")
}
return str, nil
}
// ReadBundleMetadataFromStore returns the metadata in the specified bundle.
// If the bundle is not activated, this function will return
// storage NotFound error.
func ReadBundleMetadataFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) (map[string]interface{}, error) {
return readMetadataFromStore(ctx, store, txn, metadataPath(name))
}
func readMetadataFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, path storage.Path) (map[string]interface{}, error) {
value, err := store.Read(ctx, txn, path)
if err != nil {
return nil, suppressNotFound(err)
}
data, ok := value.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("corrupt manifest metadata")
}
return data, nil
}
// ReadBundleEtagFromStore returns the etag for the specified bundle.
// If the bundle is not activated, this function will return
// storage NotFound error.
func ReadBundleEtagFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) (string, error) {
return readEtagFromStore(ctx, store, txn, EtagStoragePath(name))
}
func readEtagFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, path storage.Path) (string, error) {
value, err := store.Read(ctx, txn, path)
if err != nil {
return "", err
}
str, ok := value.(string)
if !ok {
return "", fmt.Errorf("corrupt bundle etag")
}
return str, nil
}
// ActivateOpts defines options for the Activate API call.
type ActivateOpts struct {
Ctx context.Context
Store storage.Store
Txn storage.Transaction
TxnCtx *storage.Context
Compiler *ast.Compiler
Metrics metrics.Metrics
Bundles map[string]*Bundle // Optional
ExtraModules map[string]*ast.Module // Optional
AuthorizationDecisionRef ast.Ref
legacy bool
}
// Activate the bundle(s) by loading into the given Store. This will load policies, data, and record
// the manifest in storage. The compiler provided will have had the polices compiled on it.
func Activate(opts *ActivateOpts) error {
opts.legacy = false
return activateBundles(opts)
}
// DeactivateOpts defines options for the Deactivate API call
type DeactivateOpts struct {
Ctx context.Context
Store storage.Store
Txn storage.Transaction
BundleNames map[string]struct{}
}
// Deactivate the bundle(s). This will erase associated data, policies, and the manifest entry from the store.
func Deactivate(opts *DeactivateOpts) error {
erase := map[string]struct{}{}
for name := range opts.BundleNames {
roots, err := ReadBundleRootsFromStore(opts.Ctx, opts.Store, opts.Txn, name)
if suppressNotFound(err) != nil {
return err
}
for _, root := range roots {
erase[root] = struct{}{}
}
}
_, err := eraseBundles(opts.Ctx, opts.Store, opts.Txn, opts.BundleNames, erase)
return err
}
func activateBundles(opts *ActivateOpts) error {
// Build collections of bundle names, modules, and roots to erase
erase := map[string]struct{}{}
names := map[string]struct{}{}
deltaBundles := map[string]*Bundle{}
snapshotBundles := map[string]*Bundle{}
for name, b := range opts.Bundles {
if b.Type() == DeltaBundleType {
deltaBundles[name] = b
} else {
snapshotBundles[name] = b
names[name] = struct{}{}
roots, err := ReadBundleRootsFromStore(opts.Ctx, opts.Store, opts.Txn, name)
if suppressNotFound(err) != nil {
return err
}
for _, root := range roots {
erase[root] = struct{}{}
}
// Erase data at new roots to prepare for writing the new data
for _, root := range *b.Manifest.Roots {
erase[root] = struct{}{}
}
}
}
// Before changing anything make sure the roots don't collide with any
// other bundles that already are activated or other bundles being activated.
err := hasRootsOverlap(opts.Ctx, opts.Store, opts.Txn, opts.Bundles)
if err != nil {
return err
}
if len(deltaBundles) != 0 {
err := activateDeltaBundles(opts, deltaBundles)
if err != nil {
return err
}
}
// Erase data and policies at new + old roots, and remove the old
// manifests before activating a new snapshot bundle.
remaining, err := eraseBundles(opts.Ctx, opts.Store, opts.Txn, names, erase)
if err != nil {
return err
}
// Validate data in bundle does not contain paths outside the bundle's roots.
for _, b := range snapshotBundles {
if b.lazyLoadingMode {
for _, item := range b.Raw {
path := filepath.ToSlash(item.Path)
if filepath.Base(path) == dataFile || filepath.Base(path) == yamlDataFile {
var val map[string]json.RawMessage
err = util.Unmarshal(item.Value, &val)
if err == nil {
err = doDFS(val, filepath.Dir(strings.Trim(path, "/")), *b.Manifest.Roots)
if err != nil {
return err
}
} else {
// Build an object for the value
p := getNormalizedPath(path)
if len(p) == 0 {
return fmt.Errorf("root value must be object")
}
// verify valid YAML or JSON value
var x interface{}
err := util.Unmarshal(item.Value, &x)
if err != nil {
return err
}
value := item.Value
dir := map[string]json.RawMessage{}
for i := len(p) - 1; i > 0; i-- {
dir[p[i]] = value
bs, err := json.Marshal(dir)
if err != nil {
return err
}
value = bs
dir = map[string]json.RawMessage{}
}
dir[p[0]] = value
err = doDFS(dir, filepath.Dir(strings.Trim(path, "/")), *b.Manifest.Roots)
if err != nil {
return err
}
}
}
}
}
}
// Compile the modules all at once to avoid having to re-do work.
remainingAndExtra := make(map[string]*ast.Module)
for name, mod := range remaining {
remainingAndExtra[name] = mod
}
for name, mod := range opts.ExtraModules {
remainingAndExtra[name] = mod
}
err = compileModules(opts.Compiler, opts.Metrics, snapshotBundles, remainingAndExtra, opts.legacy, opts.AuthorizationDecisionRef)
if err != nil {
return err
}
if err := writeDataAndModules(opts.Ctx, opts.Store, opts.Txn, opts.TxnCtx, snapshotBundles, opts.legacy); err != nil {
return err
}
if err := ast.CheckPathConflicts(opts.Compiler, storage.NonEmpty(opts.Ctx, opts.Store, opts.Txn)); len(err) > 0 {
return err
}
for name, b := range snapshotBundles {
if err := writeManifestToStore(opts, name, b.Manifest); err != nil {
return err
}
if err := writeEtagToStore(opts, name, b.Etag); err != nil {
return err
}
if err := writeWasmModulesToStore(opts.Ctx, opts.Store, opts.Txn, name, b); err != nil {
return err
}
}
return nil
}
func doDFS(obj map[string]json.RawMessage, path string, roots []string) error {
if len(roots) == 1 && roots[0] == "" {
return nil
}
for key := range obj {
newPath := filepath.Join(strings.Trim(path, "/"), key)
// Note: filepath.Join can return paths with '\' separators, always use
// filepath.ToSlash to keep them normalized.
newPath = strings.TrimLeft(normalizePath(newPath), "/.")
contains := false
prefix := false
if RootPathsContain(roots, newPath) {
contains = true
} else {
for i := range roots {
if strings.HasPrefix(strings.Trim(roots[i], "/"), newPath) {
prefix = true
break
}
}
}
if !contains && !prefix {
return fmt.Errorf("manifest roots %v do not permit data at path '/%s' (hint: check bundle directory structure)", roots, newPath)
}
if contains {
continue
}
var next map[string]json.RawMessage
err := util.Unmarshal(obj[key], &next)
if err != nil {
return fmt.Errorf("manifest roots %v do not permit data at path '/%s' (hint: check bundle directory structure)", roots, newPath)
}
if err := doDFS(next, newPath, roots); err != nil {
return err
}
}
return nil
}
func activateDeltaBundles(opts *ActivateOpts, bundles map[string]*Bundle) error {
// Check that the manifest roots and wasm resolvers in the delta bundle
// match with those currently in the store
for name, b := range bundles {
value, err := opts.Store.Read(opts.Ctx, opts.Txn, ManifestStoragePath(name))
if err != nil {
if storage.IsNotFound(err) {
continue
}
return err
}
bs, err := json.Marshal(value)
if err != nil {
return fmt.Errorf("corrupt manifest data: %w", err)
}
var manifest Manifest
err = util.UnmarshalJSON(bs, &manifest)
if err != nil {
return fmt.Errorf("corrupt manifest data: %w", err)
}
if !b.Manifest.equalWasmResolversAndRoots(manifest) {
return fmt.Errorf("delta bundle '%s' has wasm resolvers or manifest roots that are different from those in the store", name)
}
}
for _, b := range bundles {
err := applyPatches(opts.Ctx, opts.Store, opts.Txn, b.Patch.Data)
if err != nil {
return err
}
}
if err := ast.CheckPathConflicts(opts.Compiler, storage.NonEmpty(opts.Ctx, opts.Store, opts.Txn)); len(err) > 0 {
return err
}
for name, b := range bundles {
if err := writeManifestToStore(opts, name, b.Manifest); err != nil {
return err
}
if err := writeEtagToStore(opts, name, b.Etag); err != nil {
return err
}
}
return nil
}
// erase bundles by name and roots. This will clear all policies and data at its roots and remove its
// manifest from storage.
func eraseBundles(ctx context.Context, store storage.Store, txn storage.Transaction, names map[string]struct{}, roots map[string]struct{}) (map[string]*ast.Module, error) {
if err := eraseData(ctx, store, txn, roots); err != nil {
return nil, err
}
remaining, err := erasePolicies(ctx, store, txn, roots)
if err != nil {
return nil, err
}
for name := range names {
if err := EraseManifestFromStore(ctx, store, txn, name); suppressNotFound(err) != nil {
return nil, err
}
if err := LegacyEraseManifestFromStore(ctx, store, txn); suppressNotFound(err) != nil {
return nil, err
}
if err := eraseBundleEtagFromStore(ctx, store, txn, name); suppressNotFound(err) != nil {
return nil, err
}
if err := eraseWasmModulesFromStore(ctx, store, txn, name); suppressNotFound(err) != nil {
return nil, err
}
}
return remaining, nil
}
func eraseData(ctx context.Context, store storage.Store, txn storage.Transaction, roots map[string]struct{}) error {
for root := range roots {
path, ok := storage.ParsePathEscaped("/" + root)
if !ok {
return fmt.Errorf("manifest root path invalid: %v", root)
}
if len(path) > 0 {
if err := store.Write(ctx, txn, storage.RemoveOp, path, nil); suppressNotFound(err) != nil {
return err
}
}
}
return nil
}
func erasePolicies(ctx context.Context, store storage.Store, txn storage.Transaction, roots map[string]struct{}) (map[string]*ast.Module, error) {
ids, err := store.ListPolicies(ctx, txn)
if err != nil {
return nil, err
}
remaining := map[string]*ast.Module{}
for _, id := range ids {
bs, err := store.GetPolicy(ctx, txn, id)
if err != nil {
return nil, err
}
module, err := ast.ParseModule(id, string(bs))
if err != nil {
return nil, err
}
path, err := module.Package.Path.Ptr()
if err != nil {
return nil, err
}
deleted := false
for root := range roots {
if RootPathsContain([]string{root}, path) {
if err := store.DeletePolicy(ctx, txn, id); err != nil {
return nil, err
}
deleted = true
break
}
}
if !deleted {
remaining[id] = module
}
}
return remaining, nil
}
func writeManifestToStore(opts *ActivateOpts, name string, manifest Manifest) error {
// Always write manifests to the named location. If the plugin is in the older style config
// then also write to the old legacy unnamed location.
if err := WriteManifestToStore(opts.Ctx, opts.Store, opts.Txn, name, manifest); err != nil {
return err
}
if opts.legacy {
if err := LegacyWriteManifestToStore(opts.Ctx, opts.Store, opts.Txn, manifest); err != nil {
return err
}
}
return nil
}
func writeEtagToStore(opts *ActivateOpts, name, etag string) error {
if err := WriteEtagToStore(opts.Ctx, opts.Store, opts.Txn, name, etag); err != nil {
return err
}
return nil
}
func writeDataAndModules(ctx context.Context, store storage.Store, txn storage.Transaction, txnCtx *storage.Context, bundles map[string]*Bundle, legacy bool) error {
params := storage.WriteParams
params.Context = txnCtx
for name, b := range bundles {
if len(b.Raw) == 0 {
// Write data from each new bundle into the store. Only write under the
// roots contained in their manifest.
if err := writeData(ctx, store, txn, *b.Manifest.Roots, b.Data); err != nil {
return err
}
for _, mf := range b.Modules {
var path string
// For backwards compatibility, in legacy mode, upsert policies to
// the unprefixed path.
if legacy {
path = mf.Path
} else {
path = modulePathWithPrefix(name, mf.Path)
}
if err := store.UpsertPolicy(ctx, txn, path, mf.Raw); err != nil {
return err
}
}
} else {
params.BasePaths = *b.Manifest.Roots
err := store.Truncate(ctx, txn, params, NewIterator(b.Raw))
if err != nil {
return fmt.Errorf("store truncate failed for bundle '%s': %v", name, err)
}
}
}
return nil
}
func writeData(ctx context.Context, store storage.Store, txn storage.Transaction, roots []string, data map[string]interface{}) error {
for _, root := range roots {
path, ok := storage.ParsePathEscaped("/" + root)
if !ok {
return fmt.Errorf("manifest root path invalid: %v", root)
}
if value, ok := lookup(path, data); ok {
if len(path) > 0 {
if err := storage.MakeDir(ctx, store, txn, path[:len(path)-1]); err != nil {
return err
}
}
if err := store.Write(ctx, txn, storage.AddOp, path, value); err != nil {
return err
}
}
}
return nil
}
func compileModules(compiler *ast.Compiler, m metrics.Metrics, bundles map[string]*Bundle, extraModules map[string]*ast.Module, legacy bool, authorizationDecisionRef ast.Ref) error {
m.Timer(metrics.RegoModuleCompile).Start()
defer m.Timer(metrics.RegoModuleCompile).Stop()
modules := map[string]*ast.Module{}
// preserve any modules already on the compiler
for name, module := range compiler.Modules {
modules[name] = module
}
// preserve any modules passed in from the store
for name, module := range extraModules {
modules[name] = module
}
// include all the new bundle modules
for bundleName, b := range bundles {
if legacy {
for _, mf := range b.Modules {
modules[mf.Path] = mf.Parsed
}
} else {
for name, module := range b.ParsedModules(bundleName) {
modules[name] = module
}
}
}
if compiler.Compile(modules); compiler.Failed() {
return compiler.Errors
}
if authorizationDecisionRef.Equal(ast.EmptyRef()) {
return nil
}
return iCompiler.VerifyAuthorizationPolicySchema(compiler, authorizationDecisionRef)
}
func writeModules(ctx context.Context, store storage.Store, txn storage.Transaction, compiler *ast.Compiler, m metrics.Metrics, bundles map[string]*Bundle, extraModules map[string]*ast.Module, legacy bool) error {
m.Timer(metrics.RegoModuleCompile).Start()
defer m.Timer(metrics.RegoModuleCompile).Stop()
modules := map[string]*ast.Module{}
// preserve any modules already on the compiler
for name, module := range compiler.Modules {
modules[name] = module
}
// preserve any modules passed in from the store
for name, module := range extraModules {
modules[name] = module
}
// include all the new bundle modules
for bundleName, b := range bundles {
if legacy {
for _, mf := range b.Modules {
modules[mf.Path] = mf.Parsed
}
} else {
for name, module := range b.ParsedModules(bundleName) {
modules[name] = module
}
}
}
if compiler.Compile(modules); compiler.Failed() {
return compiler.Errors
}
for bundleName, b := range bundles {
for _, mf := range b.Modules {
var path string
// For backwards compatibility, in legacy mode, upsert policies to
// the unprefixed path.
if legacy {
path = mf.Path
} else {
path = modulePathWithPrefix(bundleName, mf.Path)
}
if err := store.UpsertPolicy(ctx, txn, path, mf.Raw); err != nil {
return err
}
}
}
return nil
}
func lookup(path storage.Path, data map[string]interface{}) (interface{}, bool) {
if len(path) == 0 {
return data, true
}
for i := 0; i < len(path)-1; i++ {
value, ok := data[path[i]]
if !ok {
return nil, false
}
obj, ok := value.(map[string]interface{})
if !ok {
return nil, false
}
data = obj
}
value, ok := data[path[len(path)-1]]
return value, ok
}
func hasRootsOverlap(ctx context.Context, store storage.Store, txn storage.Transaction, bundles map[string]*Bundle) error {
collisions := map[string][]string{}
allBundles, err := ReadBundleNamesFromStore(ctx, store, txn)
if suppressNotFound(err) != nil {
return err
}
allRoots := map[string][]string{}
// Build a map of roots for existing bundles already in the system
for _, name := range allBundles {
roots, err := ReadBundleRootsFromStore(ctx, store, txn, name)
if suppressNotFound(err) != nil {
return err
}
allRoots[name] = roots
}
// Add in any bundles that are being activated, overwrite existing roots
// with new ones where bundles are in both groups.
for name, bundle := range bundles {
allRoots[name] = *bundle.Manifest.Roots
}
// Now check for each new bundle if it conflicts with any of the others
for name, bundle := range bundles {
for otherBundle, otherRoots := range allRoots {
if name == otherBundle {
// Skip the current bundle being checked
continue
}
// Compare the "new" roots with other existing (or a different bundles new roots)
for _, newRoot := range *bundle.Manifest.Roots {
for _, otherRoot := range otherRoots {
if RootPathsOverlap(newRoot, otherRoot) {
collisions[otherBundle] = append(collisions[otherBundle], newRoot)
}
}
}
}
}
if len(collisions) > 0 {
var bundleNames []string
for name := range collisions {
bundleNames = append(bundleNames, name)
}
return fmt.Errorf("detected overlapping roots in bundle manifest with: %s", bundleNames)
}
return nil
}
func applyPatches(ctx context.Context, store storage.Store, txn storage.Transaction, patches []PatchOperation) error {
for _, pat := range patches {
// construct patch path
path, ok := patch.ParsePatchPathEscaped("/" + strings.Trim(pat.Path, "/"))
if !ok {
return fmt.Errorf("error parsing patch path")
}
var op storage.PatchOp
switch pat.Op {
case "upsert":
op = storage.AddOp
_, err := store.Read(ctx, txn, path[:len(path)-1])
if err != nil {
if !storage.IsNotFound(err) {
return err
}
if err := storage.MakeDir(ctx, store, txn, path[:len(path)-1]); err != nil {
return err
}
}
case "remove":
op = storage.RemoveOp
case "replace":
op = storage.ReplaceOp
default:
return fmt.Errorf("bad patch operation: %v", pat.Op)
}
// apply the patch
if err := store.Write(ctx, txn, op, path, pat.Value); err != nil {
return err
}
}
return nil
}
// Helpers for the older single (unnamed) bundle style manifest storage.
// LegacyManifestStoragePath is the older unnamed bundle path for manifests to be stored.
// Deprecated: Use ManifestStoragePath and named bundles instead.
var legacyManifestStoragePath = storage.MustParsePath("/system/bundle/manifest")
var legacyRevisionStoragePath = append(legacyManifestStoragePath, "revision")
// LegacyWriteManifestToStore will write the bundle manifest to the older single (unnamed) bundle manifest location.
// Deprecated: Use WriteManifestToStore and named bundles instead.
func LegacyWriteManifestToStore(ctx context.Context, store storage.Store, txn storage.Transaction, manifest Manifest) error {
return write(ctx, store, txn, legacyManifestStoragePath, manifest)
}
// LegacyEraseManifestFromStore will erase the bundle manifest from the older single (unnamed) bundle manifest location.
// Deprecated: Use WriteManifestToStore and named bundles instead.
func LegacyEraseManifestFromStore(ctx context.Context, store storage.Store, txn storage.Transaction) error {
err := store.Write(ctx, txn, storage.RemoveOp, legacyManifestStoragePath, nil)
if err != nil {
return err
}
return nil
}
// LegacyReadRevisionFromStore will read the bundle manifest revision from the older single (unnamed) bundle manifest location.
// Deprecated: Use ReadBundleRevisionFromStore and named bundles instead.
func LegacyReadRevisionFromStore(ctx context.Context, store storage.Store, txn storage.Transaction) (string, error) {
return readRevisionFromStore(ctx, store, txn, legacyRevisionStoragePath)
}
// ActivateLegacy calls Activate for the bundles but will also write their manifest to the older unnamed store location.
// Deprecated: Use Activate with named bundles instead.
func ActivateLegacy(opts *ActivateOpts) error {
opts.legacy = true
return activateBundles(opts)
}