feat: kubesphere 4.0 (#6115)

* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

---------

Signed-off-by: ci-bot <ci-bot@kubesphere.io>
Co-authored-by: ks-ci-bot <ks-ci-bot@example.com>
Co-authored-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
KubeSphere CI Bot
2024-09-06 11:05:52 +08:00
committed by GitHub
parent b5015ec7b9
commit 447a51f08b
8557 changed files with 546695 additions and 1146174 deletions

View File

@@ -0,0 +1,43 @@
// 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 rego
import (
"context"
"sync"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/ir"
)
var targetPlugins = map[string]TargetPlugin{}
var pluginMtx sync.Mutex
type TargetPlugin interface {
IsTarget(string) bool
PrepareForEval(context.Context, *ir.Policy, ...PrepareOption) (TargetPluginEval, error)
}
type TargetPluginEval interface {
Eval(context.Context, *EvalContext, ast.Value) (ast.Value, error)
}
func (r *Rego) targetPlugin(tgt string) TargetPlugin {
for _, p := range targetPlugins {
if p.IsTarget(tgt) {
return p
}
}
return nil
}
func RegisterPlugin(name string, p TargetPlugin) {
pluginMtx.Lock()
defer pluginMtx.Unlock()
if _, ok := targetPlugins[name]; ok {
panic("plugin already registered " + name)
}
targetPlugins[name] = p
}

View File

@@ -25,6 +25,7 @@ import (
"github.com/open-policy-agent/opa/ir"
"github.com/open-policy-agent/opa/loader"
"github.com/open-policy-agent/opa/metrics"
"github.com/open-policy-agent/opa/plugins"
"github.com/open-policy-agent/opa/resolver"
"github.com/open-policy-agent/opa/storage"
"github.com/open-policy-agent/opa/storage/inmem"
@@ -122,6 +123,55 @@ type EvalContext struct {
copyMaps bool
printHook print.Hook
capabilities *ast.Capabilities
strictBuiltinErrors bool
}
func (e *EvalContext) RawInput() *interface{} {
return e.rawInput
}
func (e *EvalContext) ParsedInput() ast.Value {
return e.parsedInput
}
func (e *EvalContext) Time() time.Time {
return e.time
}
func (e *EvalContext) Seed() io.Reader {
return e.seed
}
func (e *EvalContext) InterQueryBuiltinCache() cache.InterQueryCache {
return e.interQueryBuiltinCache
}
func (e *EvalContext) PrintHook() print.Hook {
return e.printHook
}
func (e *EvalContext) Metrics() metrics.Metrics {
return e.metrics
}
func (e *EvalContext) StrictBuiltinErrors() bool {
return e.strictBuiltinErrors
}
func (e *EvalContext) NDBCache() builtins.NDBCache {
return e.ndBuiltinCache
}
func (e *EvalContext) CompiledQuery() ast.Body {
return e.compiledQuery.query
}
func (e *EvalContext) Capabilities() *ast.Capabilities {
return e.capabilities
}
func (e *EvalContext) Transaction() storage.Transaction {
return e.txn
}
// EvalOption defines a function to set an option on an EvalConfig
@@ -314,23 +364,24 @@ func (pq preparedQuery) Modules() map[string]*ast.Module {
// been opened.
func (pq preparedQuery) newEvalContext(ctx context.Context, options []EvalOption) (*EvalContext, func(context.Context), error) {
ectx := &EvalContext{
hasInput: false,
rawInput: nil,
parsedInput: nil,
metrics: nil,
txn: nil,
instrument: false,
instrumentation: nil,
partialNamespace: pq.r.partialNamespace,
queryTracers: nil,
unknowns: pq.r.unknowns,
parsedUnknowns: pq.r.parsedUnknowns,
compiledQuery: compiledQuery{},
indexing: true,
earlyExit: true,
resolvers: pq.r.resolvers,
printHook: pq.r.printHook,
capabilities: pq.r.capabilities,
hasInput: false,
rawInput: nil,
parsedInput: nil,
metrics: nil,
txn: nil,
instrument: false,
instrumentation: nil,
partialNamespace: pq.r.partialNamespace,
queryTracers: nil,
unknowns: pq.r.unknowns,
parsedUnknowns: pq.r.parsedUnknowns,
compiledQuery: compiledQuery{},
indexing: true,
earlyExit: true,
resolvers: pq.r.resolvers,
printHook: pq.r.printHook,
capabilities: pq.r.capabilities,
strictBuiltinErrors: pq.r.strictBuiltinErrors,
}
for _, o := range options {
@@ -377,7 +428,9 @@ func (pq preparedQuery) newEvalContext(ctx context.Context, options []EvalOption
// Note that it could still be nil
ectx.rawInput = pq.r.rawInput
}
if pq.r.target != targetWasm {
if pq.r.targetPlugin(pq.r.target) == nil && // no plugin claims this target
pq.r.target != targetWasm {
ectx.parsedInput, err = pq.r.parseRawInput(ectx.rawInput, ectx.metrics)
if err != nil {
return nil, finishFunc, err
@@ -471,10 +524,10 @@ type queryType int
// Define a query type for each of the top level Rego
// API's that compile queries differently.
const (
evalQueryType queryType = iota
partialResultQueryType queryType = iota
partialQueryType queryType = iota
compileQueryType queryType = iota
evalQueryType queryType = iota
partialResultQueryType
partialQueryType
compileQueryType
)
type loadPaths struct {
@@ -538,11 +591,16 @@ type Rego struct {
enablePrintStatements bool
distributedTacingOpts tracing.Options
strict bool
pluginMgr *plugins.Manager
plugins []TargetPlugin
targetPrepState TargetPluginEval
regoVersion ast.RegoVersion
}
// Function represents a built-in function that is callable in Rego.
type Function struct {
Name string
Description string
Decl *types.Function
Memoize bool
Nondeterministic bool
@@ -573,6 +631,7 @@ type (
func RegisterBuiltin1(decl *Function, impl Builtin1) {
ast.RegisterBuiltin(&ast.Builtin{
Name: decl.Name,
Description: decl.Description,
Decl: decl.Decl,
Nondeterministic: decl.Nondeterministic,
})
@@ -586,6 +645,7 @@ func RegisterBuiltin1(decl *Function, impl Builtin1) {
func RegisterBuiltin2(decl *Function, impl Builtin2) {
ast.RegisterBuiltin(&ast.Builtin{
Name: decl.Name,
Description: decl.Description,
Decl: decl.Decl,
Nondeterministic: decl.Nondeterministic,
})
@@ -599,6 +659,7 @@ func RegisterBuiltin2(decl *Function, impl Builtin2) {
func RegisterBuiltin3(decl *Function, impl Builtin3) {
ast.RegisterBuiltin(&ast.Builtin{
Name: decl.Name,
Description: decl.Description,
Decl: decl.Decl,
Nondeterministic: decl.Nondeterministic,
})
@@ -612,6 +673,7 @@ func RegisterBuiltin3(decl *Function, impl Builtin3) {
func RegisterBuiltin4(decl *Function, impl Builtin4) {
ast.RegisterBuiltin(&ast.Builtin{
Name: decl.Name,
Description: decl.Description,
Decl: decl.Decl,
Nondeterministic: decl.Nondeterministic,
})
@@ -625,6 +687,7 @@ func RegisterBuiltin4(decl *Function, impl Builtin4) {
func RegisterBuiltinDyn(decl *Function, impl BuiltinDyn) {
ast.RegisterBuiltin(&ast.Builtin{
Name: decl.Name,
Description: decl.Description,
Decl: decl.Decl,
Nondeterministic: decl.Nondeterministic,
})
@@ -1131,6 +1194,12 @@ func Strict(yes bool) func(r *Rego) {
}
}
func SetRegoVersion(version ast.RegoVersion) func(r *Rego) {
return func(r *Rego) {
r.regoVersion = version
}
}
// New returns a new Rego object.
func New(options ...func(r *Rego)) *Rego {
@@ -1156,7 +1225,13 @@ func New(options ...func(r *Rego)) *Rego {
WithCapabilities(r.capabilities).
WithEnablePrintStatements(r.enablePrintStatements).
WithStrict(r.strict).
WithUseTypeCheckAnnotations(r.schemaSet != nil)
WithUseTypeCheckAnnotations(true)
// topdown could be target "" or "rego", but both could be overridden by
// a target plugin (checked below)
if r.target == targetWasm {
r.compiler = r.compiler.WithEvalMode(ast.EvalModeIR)
}
}
if r.store == nil {
@@ -1188,6 +1263,19 @@ func New(options ...func(r *Rego)) *Rego {
r.generateJSON = generateJSON
}
if r.pluginMgr != nil {
for _, name := range r.pluginMgr.Plugins() {
p := r.pluginMgr.Plugin(name)
if p0, ok := p.(TargetPlugin); ok {
r.plugins = append(r.plugins, p0)
}
}
}
if t := r.targetPlugin(r.target); t != nil {
r.compiler = r.compiler.WithEvalMode(ast.EvalModeIR)
}
return r
}
@@ -1401,64 +1489,32 @@ func (r *Rego) Compile(ctx context.Context, opts ...CompileOption) (*CompileResu
queries = []ast.Body{r.compiledQueries[compileQueryType].query}
}
return r.compileWasm(modules, queries, compileQueryType)
if tgt := r.targetPlugin(r.target); tgt != nil {
return nil, fmt.Errorf("unsupported for rego target plugins")
}
return r.compileWasm(modules, queries, compileQueryType) // TODO(sr) control flow is funky here
}
func (r *Rego) compileWasm(modules []*ast.Module, queries []ast.Body, qType queryType) (*CompileResult, error) {
decls := make(map[string]*ast.Builtin, len(r.builtinDecls)+len(ast.BuiltinMap))
for k, v := range ast.BuiltinMap {
decls[k] = v
}
for k, v := range r.builtinDecls {
decls[k] = v
}
const queryName = "eval" // NOTE(tsandall): the query name is arbitrary
p := planner.New().
WithQueries([]planner.QuerySet{
{
Name: queryName,
Queries: queries,
RewrittenVars: r.compiledQueries[qType].compiler.RewrittenVars(),
},
}).
WithModules(modules).
WithBuiltinDecls(decls).
WithDebug(r.dump)
policy, err := p.Plan()
policy, err := r.planQuery(queries, qType)
if err != nil {
return nil, err
}
if r.dump != nil {
fmt.Fprintln(r.dump, "PLAN:")
fmt.Fprintln(r.dump, "-----")
err = ir.Pretty(r.dump, policy)
if err != nil {
return nil, err
}
fmt.Fprintln(r.dump)
}
m, err := wasm.New().WithPolicy(policy).Compile()
if err != nil {
return nil, err
}
var out bytes.Buffer
if err := encoding.WriteModule(&out, m); err != nil {
return nil, err
}
result := &CompileResult{
return &CompileResult{
Bytes: out.Bytes(),
}
return result, nil
}, nil
}
// PrepareOption defines a function to set an option to control
@@ -1470,6 +1526,7 @@ type PrepareOption func(*PrepareConfig)
type PrepareConfig struct {
doPartialEval bool
disableInlining *[]string
builtinFuncs map[string]*topdown.Builtin
}
// WithPartialEval configures an option for PrepareForEval
@@ -1488,6 +1545,25 @@ func WithNoInline(paths []string) PrepareOption {
}
}
// WithBuiltinFuncs carries the rego.Function{1,2,3} per-query function definitions
// to the target plugins.
func WithBuiltinFuncs(bis map[string]*topdown.Builtin) PrepareOption {
return func(p *PrepareConfig) {
if p.builtinFuncs == nil {
p.builtinFuncs = make(map[string]*topdown.Builtin, len(bis))
}
for k, v := range bis {
p.builtinFuncs[k] = v
}
}
}
// BuiltinFuncs allows retrieving the builtin funcs set via PrepareOption
// WithBuiltinFuncs.
func (p *PrepareConfig) BuiltinFuncs() map[string]*topdown.Builtin {
return p.builtinFuncs
}
// PrepareForEval will parse inputs, modules, and query arguments in preparation
// of evaluating them.
func (r *Rego) PrepareForEval(ctx context.Context, opts ...PrepareOption) (PreparedEvalQuery, error) {
@@ -1541,7 +1617,8 @@ func (r *Rego) PrepareForEval(ctx context.Context, opts ...PrepareOption) (Prepa
return PreparedEvalQuery{}, err
}
if r.target == targetWasm {
switch r.target {
case targetWasm: // TODO(sr): make wasm a target plugin, too
if r.hasWasmModule() {
_ = txnClose(ctx, err) // Ignore error
@@ -1580,6 +1657,22 @@ func (r *Rego) PrepareForEval(ctx context.Context, opts ...PrepareOption) (Prepa
return PreparedEvalQuery{}, err
}
r.opa = o
case targetRego: // do nothing, don't lookup default plugin
default: // either a specific plugin target, or one that is default
if tgt := r.targetPlugin(r.target); tgt != nil {
queries := []ast.Body{r.compiledQueries[evalQueryType].query}
pol, err := r.planQuery(queries, evalQueryType)
if err != nil {
return PreparedEvalQuery{}, err
}
// always add the builtins provided via rego.FunctionN options
opts = append(opts, WithBuiltinFuncs(r.builtinFuncs))
r.targetPrepState, err = tgt.PrepareForEval(ctx, pol, opts...)
if err != nil {
return PreparedEvalQuery{}, err
}
}
}
txnErr := txnClose(ctx, err) // Always call closer
@@ -1689,22 +1782,20 @@ func (r *Rego) prepare(ctx context.Context, qType queryType, extras []extraStage
}
func (r *Rego) parseModules(ctx context.Context, txn storage.Transaction, m metrics.Metrics) error {
if len(r.modules) == 0 {
return nil
}
ids, err := r.store.ListPolicies(ctx, txn)
if err != nil {
return err
}
// if there are no raw modules, nor modules in the store, then there
// is nothing to do.
if len(r.modules) == 0 && len(ids) == 0 {
return nil
}
m.Timer(metrics.RegoModuleParse).Start()
defer m.Timer(metrics.RegoModuleParse).Stop()
var errs Errors
// Parse any modules in the are saved to the store, but only if
// Parse any modules that are saved to the store, but only if
// another compile step is going to occur (ie. we have parsed modules
// that need to be compiled).
for _, id := range ids {
@@ -1719,7 +1810,7 @@ func (r *Rego) parseModules(ctx context.Context, txn storage.Transaction, m metr
return err
}
parsed, err := ast.ParseModule(id, string(bs))
parsed, err := ast.ParseModuleWithOpts(id, string(bs), ast.ParserOptions{RegoVersion: r.regoVersion})
if err != nil {
errs = append(errs, err)
}
@@ -1729,9 +1820,16 @@ func (r *Rego) parseModules(ctx context.Context, txn storage.Transaction, m metr
// Parse any passed in as arguments to the Rego object
for _, module := range r.modules {
p, err := module.Parse()
p, err := module.ParseWithOpts(ast.ParserOptions{RegoVersion: r.regoVersion})
if err != nil {
errs = append(errs, err)
switch errorWithType := err.(type) {
case ast.Errors:
for _, e := range errorWithType {
errs = append(errs, e)
}
default:
errs = append(errs, errorWithType)
}
}
r.parsedModules[module.filename] = p
}
@@ -1753,7 +1851,8 @@ func (r *Rego) loadFiles(ctx context.Context, txn storage.Transaction, m metrics
result, err := loader.NewFileLoader().
WithMetrics(m).
WithProcessAnnotation(r.schemaSet != nil).
WithProcessAnnotation(true).
WithRegoVersion(r.regoVersion).
Filtered(r.loadPaths.paths, r.loadPaths.filter)
if err != nil {
return err
@@ -1782,8 +1881,9 @@ func (r *Rego) loadBundles(ctx context.Context, txn storage.Transaction, m metri
for _, path := range r.bundlePaths {
bndl, err := loader.NewFileLoader().
WithMetrics(m).
WithProcessAnnotation(r.schemaSet != nil).
WithProcessAnnotation(true).
WithSkipBundleVerification(r.skipBundleVerification).
WithRegoVersion(r.regoVersion).
AsBundle(path)
if err != nil {
return fmt.Errorf("loading error: %s", err)
@@ -1849,13 +1949,14 @@ func (r *Rego) compileModules(ctx context.Context, txn storage.Transaction, m me
// Use this as the single-point of compiling everything only a
// single time.
opts := &bundle.ActivateOpts{
Ctx: ctx,
Store: r.store,
Txn: txn,
Compiler: r.compilerForTxn(ctx, r.store, txn),
Metrics: m,
Bundles: r.bundles,
ExtraModules: r.parsedModules,
Ctx: ctx,
Store: r.store,
Txn: txn,
Compiler: r.compilerForTxn(ctx, r.store, txn),
Metrics: m,
Bundles: r.bundles,
ExtraModules: r.parsedModules,
ParserOptions: ast.ParserOptions{RegoVersion: r.regoVersion},
}
err := bundle.Activate(opts)
if err != nil {
@@ -1952,8 +2053,20 @@ func (r *Rego) compileQuery(query ast.Body, imports []*ast.Import, m metrics.Met
}
func (r *Rego) eval(ctx context.Context, ectx *EvalContext) (ResultSet, error) {
if r.opa != nil {
switch {
case r.targetPrepState != nil: // target plugin flow
var val ast.Value
if r.runtime != nil {
val = r.runtime.Value
}
s, err := r.targetPrepState.Eval(ctx, ectx, val)
if err != nil {
return nil, err
}
return r.valueToQueryResult(s, ectx)
case r.target == targetWasm:
return r.evalWasm(ctx, ectx)
case r.target == targetRego: // continue
}
q := topdown.NewQuery(ectx.compiledQuery.query).
@@ -2050,7 +2163,11 @@ func (r *Rego) evalWasm(ctx context.Context, ectx *EvalContext) (ResultSet, erro
return nil, err
}
resultSet, ok := parsed.Value.(ast.Set)
return r.valueToQueryResult(parsed.Value, ectx)
}
func (r *Rego) valueToQueryResult(res ast.Value, ectx *EvalContext) (ResultSet, error) {
resultSet, ok := res.(ast.Set)
if !ok {
return nil, fmt.Errorf("illegal result type")
}
@@ -2060,7 +2177,7 @@ func (r *Rego) evalWasm(ctx context.Context, ectx *EvalContext) (ResultSet, erro
}
var rs ResultSet
err = resultSet.Iter(func(term *ast.Term) error {
err := resultSet.Iter(func(term *ast.Term) error {
obj, ok := term.Value.(ast.Object)
if !ok {
return fmt.Errorf("illegal result type")
@@ -2137,16 +2254,17 @@ func (r *Rego) partialResult(ctx context.Context, pCfg *PrepareConfig) (PartialR
}
ectx := &EvalContext{
parsedInput: r.parsedInput,
metrics: r.metrics,
txn: r.txn,
partialNamespace: r.partialNamespace,
queryTracers: r.queryTracers,
compiledQuery: r.compiledQueries[partialResultQueryType],
instrumentation: r.instrumentation,
indexing: true,
resolvers: r.resolvers,
capabilities: r.capabilities,
parsedInput: r.parsedInput,
metrics: r.metrics,
txn: r.txn,
partialNamespace: r.partialNamespace,
queryTracers: r.queryTracers,
compiledQuery: r.compiledQueries[partialResultQueryType],
instrumentation: r.instrumentation,
indexing: true,
resolvers: r.resolvers,
capabilities: r.capabilities,
strictBuiltinErrors: r.strictBuiltinErrors,
}
disableInlining := r.disableInlining
@@ -2249,7 +2367,7 @@ func (r *Rego) partial(ctx context.Context, ectx *EvalContext) (*PartialQueries,
WithSkipPartialNamespace(r.skipPartialNamespace).
WithShallowInlining(r.shallowInlining).
WithInterQueryBuiltinCache(ectx.interQueryBuiltinCache).
WithStrictBuiltinErrors(r.strictBuiltinErrors).
WithStrictBuiltinErrors(ectx.strictBuiltinErrors).
WithSeed(ectx.seed).
WithPrintHook(ectx.printHook)
@@ -2383,11 +2501,13 @@ func (r *Rego) rewriteEqualsForPartialQueryCompile(_ ast.QueryCompiler, query as
func (r *Rego) generateTermVar() *ast.Term {
r.termVarID++
if r.target == targetWasm {
return ast.VarTerm(wasmVarPrefix + fmt.Sprintf("term%v", r.termVarID))
prefix := ast.WildcardPrefix
if p := r.targetPlugin(r.target); p != nil {
prefix = wasmVarPrefix
} else if r.target == targetWasm {
prefix = wasmVarPrefix
}
return ast.VarTerm(ast.WildcardPrefix + fmt.Sprintf("term%v", r.termVarID))
return ast.VarTerm(fmt.Sprintf("%sterm%v", prefix, r.termVarID))
}
func (r Rego) hasQuery() bool {
@@ -2504,6 +2624,10 @@ func (m rawModule) Parse() (*ast.Module, error) {
return ast.ParseModule(m.filename, m.module)
}
func (m rawModule) ParseWithOpts(opts ast.ParserOptions) (*ast.Module, error) {
return ast.ParseModuleWithOpts(m.filename, m.module, opts)
}
type extraStage struct {
after string
stage ast.QueryCompilerStageDefinition
@@ -2570,17 +2694,19 @@ func finishFunction(name string, bctx topdown.BuiltinContext, result *ast.Term,
if err != nil {
var e *HaltError
if errors.As(err, &e) {
return topdown.Halt{Err: &topdown.Error{
tdErr := &topdown.Error{
Code: topdown.BuiltinErr,
Message: fmt.Sprintf("%v: %v", name, e.Error()),
Location: bctx.Location,
}}
}
return topdown.Halt{Err: tdErr.Wrap(e)}
}
return &topdown.Error{
tdErr := &topdown.Error{
Code: topdown.BuiltinErr,
Message: fmt.Sprintf("%v: %v", name, err.Error()),
Location: bctx.Location,
}
return tdErr.Wrap(err)
}
if result == nil {
return nil
@@ -2610,3 +2736,49 @@ func generateJSON(term *ast.Term, ectx *EvalContext) (interface{}, error) {
CopyMaps: ectx.copyMaps,
})
}
func (r *Rego) planQuery(queries []ast.Body, evalQueryType queryType) (*ir.Policy, error) {
modules := make([]*ast.Module, 0, len(r.compiler.Modules))
for _, module := range r.compiler.Modules {
modules = append(modules, module)
}
decls := make(map[string]*ast.Builtin, len(r.builtinDecls)+len(ast.BuiltinMap))
for k, v := range ast.BuiltinMap {
decls[k] = v
}
for k, v := range r.builtinDecls {
decls[k] = v
}
const queryName = "eval" // NOTE(tsandall): the query name is arbitrary
p := planner.New().
WithQueries([]planner.QuerySet{
{
Name: queryName,
Queries: queries,
RewrittenVars: r.compiledQueries[evalQueryType].compiler.RewrittenVars(),
},
}).
WithModules(modules).
WithBuiltinDecls(decls).
WithDebug(r.dump)
policy, err := p.Plan()
if err != nil {
return nil, err
}
if r.dump != nil {
fmt.Fprintln(r.dump, "PLAN:")
fmt.Fprintln(r.dump, "-----")
err = ir.Pretty(r.dump, policy)
if err != nil {
return nil, err
}
fmt.Fprintln(r.dump)
}
return policy, nil
}