Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement data source interfaces & registration in rego engine #4997

Merged
merged 4 commits into from
Nov 19, 2024

Conversation

JAORMX
Copy link
Contributor

@JAORMX JAORMX commented Nov 19, 2024

Summary

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.

Change Type

Mark the type of change your PR introduces:

  • Bug fix (resolves an issue without affecting existing features)
  • Feature (adds new functionality without breaking changes)
  • Breaking change (may impact existing functionalities or require documentation updates)
  • Documentation (updates or additions to documentation)
  • Refactoring or test improvements (no bug fixes or new functionality)

Testing

Outline how the changes were tested, including steps to reproduce and any relevant configurations.
Attach screenshots if helpful.

Review Checklist:

  • Reviewed my own code for quality and clarity.
  • Added comments to complex or tricky code sections.
  • Updated any affected documentation.
  • Included tests that validate the fix or feature.
  • Checked that related changes are merged.

@JAORMX JAORMX requested a review from a team as a code owner November 19, 2024 08:41
@JAORMX JAORMX marked this pull request as draft November 19, 2024 08:42
@JAORMX JAORMX force-pushed the datasource-reg branch 3 times, most recently from bf93fe0 to cf465e9 Compare November 19, 2024 11:33
@JAORMX JAORMX marked this pull request as ready for review November 19, 2024 11:35
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]>
@coveralls
Copy link

coveralls commented Nov 19, 2024

Coverage Status

coverage: 54.843% (+0.07%) from 54.77%
when pulling 1722eb2 on JAORMX:datasource-reg
into 7f13a58 on mindersec:main.

blkt
blkt previously approved these changes Nov 19, 2024
Copy link
Contributor

@blkt blkt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Signed-off-by: Juan Antonio Osorio <[email protected]>
eleftherias
eleftherias previously approved these changes Nov 19, 2024
Co-authored-by: Eleftheria Stein-Kousathana <[email protected]>
@JAORMX JAORMX merged commit 77b1991 into mindersec:main Nov 19, 2024
24 checks passed
Copy link
Member

@evankanderson evankanderson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very promising! A couple comments you can take or leave as you please.

// 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to leave in a Printf?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not

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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: does it make sense for dsf from dsr.GetFuncs() to actually have a func(*rego.Rego) method (i.e. to move the buildFromDataSource into a function on DataSourceFuncDef)?

It pushes the Rego types into a slighly larger area of the code, but it helps avoid having a big ball of code in eval/rego (and if we name the method Rego or RegoOpt, we would have a pattern for any other future evaluation engines).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to have data sources to be quite independent from the eval engine. The eval engine would then be in charge of interpreting the data source and making sense of it. It brings a lot more responsibility into the eval engine, but it allows for us to include more engines with ease in the future. e.g. if we'd introduce a WASM engine, we wouldn't need to mess around with rego structures.

Comment on lines +42 to +47
if err := dsf.ValidateArgs(obj); err != nil {
return nil, err
}

// Call the data source function
ret, err := dsf.Call(jsonObj)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you move buildFromDataSource to DataSourceFuncDef, you might not need to expose ValidateArgs or Call in the interface.

Comment on lines +510 to +515
err = e.Eval(context.Background(), emptyPol, nil, &interfaces.Result{
Object: map[string]any{
"data": "bar",
},
})
require.ErrorIs(t, err, engerrors.ErrEvaluationFailed, "should have failed the evaluation")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if it would make sense to use a real registry and DataSource here, to provide an example for future users (and to verify the lifecycle more deeply than Mocks).

(You'll also get a bit more test coverage of registry.go for "free")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have a good point, I'll add a testing implementation

return nil
}

// GetFuncs returns all functions that data sources provide globally.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could eventually pull from the database to get project-specific data sources, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right, the idea is that we'd do an initial parsing from the db, turn this into in-memory objects, and then pass these around.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants