Files
releases/v1/hooks/hooks.go
T
Stephan Renatus dce01172d7 ast+rego+topdown: external rule source support (#8600)
External rule sources let wrapping projects inject rules at evaluation
time instead of compile time. The compiler marks external packages in
the rule tree but doesn't index them. When topdown hits an external
node, it calls Lookup to get rules, compiles them on the fly with a
scoped compiler, grafts the result into the tree, and caches it for the
duration of the evaluation.

Sources can be isolated (default, no access to surrounding policy) or
non-isolated (can reference static rules and other external sources).
The ExternalRuleIndexCloser interface handles cleanup after evaluation.
Precompiled rules can skip compiler stages via SkippedStages to avoid
redundant work.

This includes:
* hooks: add BundlePreActivate hook This one is handy when registering
external sources.

* topdown: catch `ir == nil` rule index result
This wouldn't ordinarily happen: the compiler is checking refs before.
But in our use case, the SP rules may be configured to be able to reach
into the surrounding Rego (non-isolated mode). If that happens, the IR
lookup may indeed end up as `nil, nil`.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2026-05-05 09:48:50 +02:00

114 lines
3.6 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 hooks
import (
"context"
"fmt"
"github.com/open-policy-agent/opa/v1/bundle"
"github.com/open-policy-agent/opa/v1/config"
topdown_cache "github.com/open-policy-agent/opa/v1/topdown/cache"
)
// Hook is a hook to be called in some select places in OPA's operation.
//
// The base Hook interface is any, and wherever a hook can occur, the calling code
// will check if your hook implements an appropriate interface. If so, your hook
// is called.
//
// This allows you to only hook in to behavior you care about, and it allows the
// OPA to add more hooks in the future.
//
// All hook interfaces in this package have Hook in the name. Hooks must be safe
// for concurrent use. It is expected that hooks are fast; if a hook needs to take
// time, then copy what you need and ensure the hook is async.
//
// When multiple instances of a hook are provided, they are all going to be executed
// in an unspecified order (it's a map-range call underneath). If you need hooks to
// be run in order, you can wrap them into another hook, and configure that one.
type Hook any
// Hooks is the type used for every struct in OPA that can work with hooks.
type Hooks struct {
m map[Hook]struct{} // we are NOT providing a stable invocation ordering
}
// New creates a new instance of Hooks.
func New(hs ...Hook) Hooks {
h := Hooks{m: make(map[Hook]struct{}, len(hs))}
for i := range hs {
h.m[hs[i]] = struct{}{}
}
return h
}
func (hs *Hooks) Append(h Hook) {
if hs.m == nil {
hs.m = make(map[Hook]struct{})
}
hs.m[h] = struct{}{}
}
func (hs Hooks) Each(fn func(Hook)) {
for h := range hs.m {
fn(h)
}
}
func (hs Hooks) Len() int {
return len(hs.m)
}
// ConfigHook allows inspecting or rewriting the configuration when the plugin
// manager is processing it.
// Note that this hook is not run when the plugin manager is reconfigured. This
// usually only happens when there's a new config from a discovery bundle, and
// for processing _that_, there's `ConfigDiscoveryHook`.
type ConfigHook interface {
OnConfig(context.Context, *config.Config) (*config.Config, error)
}
// ConfigHook allows inspecting or rewriting the discovered configuration when
// the discovery plugin is processing it.
type ConfigDiscoveryHook interface {
OnConfigDiscovery(context.Context, *config.Config) (*config.Config, error)
}
// InterQueryCacheHook allows access to the server's inter-query cache instance.
// It's useful for out-of-tree handlers that also need to evaluate something.
// Using this hook, they can share the caches with the rest of OPA.
type InterQueryCacheHook interface {
OnInterQueryCache(context.Context, topdown_cache.InterQueryCache) error
}
// InterQueryValueCacheHook allows access to the server's inter-query value cache
// instance.
type InterQueryValueCacheHook interface {
OnInterQueryValueCache(context.Context, topdown_cache.InterQueryValueCache) error
}
// BundlePreActivateHook is called before a bundle is activated and its policies
// are compiled. This allows hooks to inspect the bundle manifest (e.g. metadata)
// and register external rule sources that will be available during compilation.
type BundlePreActivateHook interface {
OnBundlePreActivate(ctx context.Context, bundleName string, manifest bundle.Manifest) error
}
func (hs Hooks) Validate() error {
for h := range hs.m {
switch h.(type) {
case InterQueryCacheHook,
InterQueryValueCacheHook,
ConfigHook,
ConfigDiscoveryHook,
BundlePreActivateHook: // OK
default:
return fmt.Errorf("unknown hook type %T", h)
}
}
return nil
}