-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement data source interfaces & registration in rego engine (#4997)
* Implement data source interfaces & registration in rego engine This implements the basic interfaces that data sources must fulfil in order to be called and used within Minder. This is: * Listing the functions they provide * Validating updates Each function itself must provide a unique key for the engine to use, as well as argument validation and the data source call itself. Finally, this implements an engine option that allows data sources to be dynamically registered into the rego engine. Signed-off-by: Juan Antonio Osorio <[email protected]> * Create mocks for data sources and use it in tests Signed-off-by: Juan Antonio Osorio <[email protected]> * Implement data source registry Signed-off-by: Juan Antonio Osorio <[email protected]> * Update internal/engine/eval/rego/datasources.go Co-authored-by: Eleftheria Stein-Kousathana <[email protected]> --------- Signed-off-by: Juan Antonio Osorio <[email protected]> Co-authored-by: Eleftheria Stein-Kousathana <[email protected]>
- Loading branch information
1 parent
7f13a58
commit 77b1991
Showing
6 changed files
with
405 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// SPDX-FileCopyrightText: Copyright 2023 The Minder Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package rego | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/open-policy-agent/opa/ast" | ||
"github.com/open-policy-agent/opa/rego" | ||
"github.com/open-policy-agent/opa/types" | ||
|
||
v1datasources "github.com/mindersec/minder/pkg/datasources/v1" | ||
) | ||
|
||
// RegisterDataSources implements the Eval interface. | ||
func (e *Evaluator) RegisterDataSources(dsr *v1datasources.DataSourceRegistry) { | ||
for key, dsf := range dsr.GetFuncs() { | ||
fmt.Printf("Registering data source %s\n", key) | ||
e.regoOpts = append(e.regoOpts, buildFromDataSource(key, dsf)) | ||
} | ||
} | ||
|
||
// buildFromDataSource builds a rego function from a data source function. | ||
// It takes a DataSourceFuncDef and returns a function that can be used to | ||
// register the function with the rego engine. | ||
func buildFromDataSource(key v1datasources.DataSourceFuncKey, dsf v1datasources.DataSourceFuncDef) func(*rego.Rego) { | ||
k := normalizeKey(key) | ||
return rego.Function1( | ||
®o.Function{ | ||
Name: k, | ||
Decl: types.NewFunction(types.Args(types.A), types.A), | ||
}, | ||
func(_ rego.BuiltinContext, obj *ast.Term) (*ast.Term, error) { | ||
// Convert the AST value back to a Go interface{} | ||
jsonObj, err := ast.JSON(obj.Value) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if err := dsf.ValidateArgs(obj); err != nil { | ||
return nil, err | ||
} | ||
|
||
// Call the data source function | ||
ret, err := dsf.Call(jsonObj) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
val, err := ast.InterfaceToValue(ret) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return ast.NewTerm(val), nil | ||
}, | ||
) | ||
} | ||
|
||
// This converts the data source function key into a format that can be used in the rego query. | ||
// For example, if the key is "aws.ec2.instances", it will | ||
// be converted to "minder.data.aws.ec2.instances". | ||
// It also normalizes the key to lowercase (which should have already been done) | ||
// and converts any "-" to "_", finally it removes any special characters. | ||
func normalizeKey(key v1datasources.DataSourceFuncKey) string { | ||
low := strings.ToLower(key.String()) | ||
underscore := strings.ReplaceAll(low, "-", "_") | ||
// Remove any special characters | ||
norm := strings.Map(func(r rune) rune { | ||
if r >= 'a' && r <= 'z' || r >= '0' && r <= '9' || r == '_' || r == '.' { | ||
return r | ||
} | ||
return -1 | ||
}, underscore) | ||
return fmt.Sprintf("minder.datasource.%s", norm) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// SPDX-FileCopyrightText: Copyright 2024 The Minder Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
// Package v1 provides the interfaces and types for the data sources. | ||
package v1 | ||
|
||
//go:generate go run go.uber.org/mock/mockgen -package mock_$GOPACKAGE -destination=./mock/$GOFILE -source=./$GOFILE | ||
|
||
// DataSourceFuncKey is the key that uniquely identifies a data source function. | ||
type DataSourceFuncKey string | ||
|
||
// String returns the string representation of the data source function key. | ||
func (k DataSourceFuncKey) String() string { | ||
return string(k) | ||
} | ||
|
||
// DataSourceFuncDef is the definition of a data source function. | ||
// It contains the key that uniquely identifies the function and the arguments | ||
// that the function can take. | ||
type DataSourceFuncDef interface { | ||
// ValidateArgs validates the arguments of the function. | ||
ValidateArgs(obj any) error | ||
// ValidateUpdate validates the update to the data source. | ||
// The data source implementation should respect the update and return an error | ||
// if the update is invalid. | ||
ValidateUpdate(obj any) error | ||
// Call calls the function with the given arguments. | ||
// It is the responsibility of the data source implementation to handle the call. | ||
// It is also the responsibility of the caller to validate the arguments | ||
// before calling the function. | ||
Call(args any) (any, error) | ||
} | ||
|
||
// DataSource is the interface that a data source must implement. | ||
// It implements several functions that will be used by the engine to | ||
// interact with external systems. These get taken into used by the Evaluator. | ||
// Moreover, a data source must be able to validate an update to itself. | ||
type DataSource interface { | ||
// Returns the registered name of the data source. | ||
GetName() string | ||
|
||
// GetFuncs returns the functions that the data source provides. | ||
GetFuncs() map[DataSourceFuncKey]DataSourceFuncDef | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.