mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-17 13:51:48 -06:00
f37b5ccbc2
Signed-off-by: Tyler Schade <tyler.schade@solo.io>
133 lines
3.4 KiB
Go
133 lines
3.4 KiB
Go
// Copyright 2023 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 pathwatcher provides helper functions for creating file and directory watchers
|
|
package pathwatcher
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
|
|
"github.com/fsnotify/fsnotify"
|
|
"github.com/open-policy-agent/opa/ast"
|
|
initload "github.com/open-policy-agent/opa/internal/runtime/init"
|
|
"github.com/open-policy-agent/opa/loader"
|
|
"github.com/open-policy-agent/opa/storage"
|
|
)
|
|
|
|
// CreatePathWatcher creates watchers to monitor for path changes
|
|
func CreatePathWatcher(rootPaths []string) (*fsnotify.Watcher, error) {
|
|
watchPaths, err := getWatchPaths(rootPaths)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
watcher, err := fsnotify.NewWatcher()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, path := range watchPaths {
|
|
if err := watcher.Add(path); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return watcher, nil
|
|
}
|
|
|
|
// ProcessWatcherUpdate handles an occurrence of a watcher event
|
|
func ProcessWatcherUpdate(ctx context.Context, paths []string, removed string, store storage.Store, filter loader.Filter, asBundle bool,
|
|
f func(context.Context, storage.Transaction, *initload.LoadPathsResult) error) error {
|
|
return ProcessWatcherUpdateForRegoVersion(ctx, ast.RegoV0, paths, removed, store, filter, asBundle, f)
|
|
}
|
|
|
|
func ProcessWatcherUpdateForRegoVersion(ctx context.Context, regoVersion ast.RegoVersion, paths []string, removed string, store storage.Store, filter loader.Filter, asBundle bool,
|
|
f func(context.Context, storage.Transaction, *initload.LoadPathsResult) error) error {
|
|
loaded, err := initload.LoadPathsForRegoVersion(regoVersion, paths, filter, asBundle, nil, true, false, false, nil, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
removed = loader.CleanPath(removed)
|
|
|
|
return storage.Txn(ctx, store, storage.WriteParams, func(txn storage.Transaction) error {
|
|
if !asBundle {
|
|
ids, err := store.ListPolicies(ctx, txn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, id := range ids {
|
|
if id == removed {
|
|
if err := store.DeletePolicy(ctx, txn, id); err != nil {
|
|
return err
|
|
}
|
|
} else if _, exists := loaded.Files.Modules[id]; !exists {
|
|
// This branch get hit in two cases.
|
|
// 1. Another piece of code has access to the store and inserts
|
|
// a policy out-of-band.
|
|
// 2. In between FS notification and loader.Filtered() call above, a
|
|
// policy is removed from disk.
|
|
bs, err := store.GetPolicy(ctx, txn, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
module, err := ast.ParseModule(id, string(bs))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
loaded.Files.Modules[id] = &loader.RegoFile{
|
|
Name: id,
|
|
Raw: bs,
|
|
Parsed: module,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return f(ctx, txn, loaded)
|
|
})
|
|
}
|
|
|
|
func getWatchPaths(rootPaths []string) ([]string, error) {
|
|
paths := []string{}
|
|
|
|
for _, path := range rootPaths {
|
|
|
|
_, path = loader.SplitPrefix(path)
|
|
result, err := loader.Paths(path, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
unique := map[string]struct{}{}
|
|
|
|
for _, r := range result {
|
|
fi, err := os.Lstat(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if fi.IsDir() {
|
|
unique[r] = struct{}{}
|
|
} else {
|
|
dir := filepath.Dir(r)
|
|
unique[dir] = struct{}{}
|
|
}
|
|
}
|
|
|
|
u := make([]string, 0, len(unique))
|
|
for k := range unique {
|
|
u = append(u, k)
|
|
}
|
|
sort.Strings(u)
|
|
|
|
paths = append(paths, u...)
|
|
}
|
|
|
|
return paths, nil
|
|
}
|