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

feature(backend): Enabling Analyzer Configuration #2842

Merged

Conversation

xoscar
Copy link
Collaborator

@xoscar xoscar commented Jun 30, 2023

This PR updates the architecture for plugins and rules, keeping configuring plugins more straightforward and flexible.

Changes

  • Decouples rules from plugins and allows for a configuration-based setup
  • Moves the linter out of the models to the linter directory
  • Updates plugins and rules interfaces to support an analyzer configuration
  • Moves metadata away from linter execution, leaving it to be config only
  • Enables configuration for error level and weight for rules

Fixes

Checklist

  • tested locally
  • added new dependencies
  • updated the docs
  • added a test

Analyzer Arch Changes
https://www.loom.com/share/03e95872d383496aa81591be9d772123

Features & bug fixes
https://www.loom.com/share/0517b986f19941f3b41c5187d2344500

@xoscar xoscar changed the base branch from main to feat/analyzer-configuration June 30, 2023 19:24
@xoscar xoscar changed the title Feat/analyzer configuration backend feature: Enabling Analyzer Configuration - Backend Jun 30, 2023
@xoscar xoscar self-assigned this Jun 30, 2023
@xoscar xoscar added the backend label Jun 30, 2023

shouldSkip, reason := linter.ShouldSkip()
shouldSkip := lintResource.ShouldSkip()
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The analyzer resource should make the decisions of running or not the linter as it contains the configuration

Description string `json:"-"`
}

LinterRule struct {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This a new block, allowing users to configure rules

@@ -40,18 +58,130 @@ func (l Linter) Validate() error {
}

for _, p := range l.Plugins {
if p.Name == "" {
return fmt.Errorf("plugin name cannot be empty")
plugin, ok := findPlugin(p.Id, DefaultPlugins)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Validates that the plugins and rules coming from the new resource instance match what we have in the system, this should be more flexible in the future when we implement custom analyzer profiles

return !l.Enabled
}

func (l Linter) WithMetadata() (Linter, error) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Combines the analyzer configuration (Database, CLI, FE) with the metadata stored within the repo, as it is not available for users to update (today)


var (
// plugins
StandardsId = "standards"
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Names, ids, descriptions, etc

@@ -0,0 +1,94 @@
package analyzer
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The "analyzer" per se, doesn't have results, as those belong to the linter, but having them at the linter level creates an import cycle issue because the linter imports model.Trace for rules and plugins, and at the same time, model.Run requires the linter.Results to persist the output from the analyzer.

Once the test migration changes are done, we can come back to this file and move it to the linter level and everyone will be happy.

At least is not in the models folder anymore 🤷🏾

linter_plugin_security.NewSecurityPlugin(),
linter_plugin_common.NewCommonPlugin(),
}
commonPlugin = plugins.NewPlugin(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Static registration of the linter configuration, we do not have much flexibility today, but in the future, the linter runner could generate a dynamic plugin registry with custom rules and plugins defined by user profile, test or test run levels

return p.ruleRegistry
}

func (p BasePlugin) Execute(ctx context.Context, trace model.Trace, config analyzer.LinterPlugin) (analyzer.PluginResult, error) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

All current plugins have the same execution logic, so instead of having to create a new plugin file for each, we use the default, and if needed, we can create specific plugin files implementing the Plugin interface to override execute method with custom logic

return r.id
}

func (r BaseRule) Evaluate(ctx context.Context, trace model.Trace, config analyzer.LinterRule) (analyzer.RuleResult, error) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Rules on the other hand, have very custom logic, which makes it more difficult to encapsulate in a single default method, for these, we should create an implementation of the Rule interface per rule

Copy link
Collaborator

Choose a reason for hiding this comment

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

I would say there's no need for this BaseRule. it can be replaced by a func (r MyRule) ID() string {return someconstantID} func, satisfying the interface. This also makes the ID immutable, and that's good in this context

@@ -222,6 +222,15 @@ func (m *manager[T]) create(w http.ResponseWriter, r *http.Request) {
targetResource.Spec = m.rh.SetID(targetResource.Spec, m.config.idgen())
}

if err := targetResource.Spec.Validate(); err != nil {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I added this validate step to the resource manager, as it wasn't currently in use, not sure if I should better move this to another PR

@xoscar xoscar marked this pull request as ready for review June 30, 2023 19:47
Copy link
Contributor

@danielbdias danielbdias left a comment

Choose a reason for hiding this comment

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

The code overall is ok! Good job!

One thing to consider in the future is how we can test these rules to guarantee that they are working as intended and are only sensitive to changes in the future without us knowing it.

@xoscar
Copy link
Collaborator Author

xoscar commented Jul 4, 2023

The code overall is ok! Good job!

One thing to consider in the future is how we can test these rules to guarantee that they are working as intended and are only sensitive to changes in the future without us knowing it.

Hey @danielbdias thanks for the review, I have added tests for the analyzer and linter critical points, and will add more for rules in the following PRs!

Copy link
Collaborator

@schoren schoren left a comment

Choose a reason for hiding this comment

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

It looks good in general. I think we should try to embrace go's naming conventions, so it'd be great to rename all the Ids to ID.

I outlined a couple of duplicated loops and bugs, not necessarily related specifically to this work, but since we're here 🤷

Finally, more oriented to the architecutre:

  1. I would remove the BaseRule, it's being used kind of as a base class, but it doesn't make a lot of sense here IMO.
  2. We should try to reduce the amount of interfaces that are not strictly required. "Accept interfaces, return structs". You can leverage go's duck typing and declare interfaces where you want to avoid direct references to a package.

The rest is looking really good. I like the amount of tests that are included here!

Name string `json:"name"`
Enabled bool `json:"enabled"`
Required bool `json:"required"`
Id string `json:"id"`
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
Id string `json:"id"`
ID string `json:"id"`

return LinterPlugin{}, false
}

func findRule(Id string, rules []LinterRule) (LinterRule, bool) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
func findRule(Id string, rules []LinterRule) (LinterRule, bool) {
func findRule(id string, rules []LinterRule) (LinterRule, bool) {


var (
// plugins
StandardsId = "standards"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
StandardsId = "standards"
StandardsID = "standards"

var (
// plugins
StandardsId = "standards"
CommonId = "common"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
CommonId = "common"
CommonID = "common"

// plugins
StandardsId = "standards"
CommonId = "common"
SecurityId = "security"
Copy link
Collaborator

Choose a reason for hiding this comment

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

for consistency, and as per Go's recommendation the casing for ID should be ID and not Id

Comment on lines 25 to 33
for _, span := range trace.Flat {
res = append(res, r.validateSpan(span))
}

for _, result := range res {
if !result.Passed {
allPassed = false
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

same here

Suggested change
for _, span := range trace.Flat {
res = append(res, r.validateSpan(span))
}
for _, result := range res {
if !result.Passed {
allPassed = false
}
}
for _, span := range trace.Flat {
analyzerResult := r.validateSpan(span)
res = append(res, analyzerResult)
if !analyzerResult.Passed {
allPassed = false
}
}

return r.id
}

func (r BaseRule) Evaluate(ctx context.Context, trace model.Trace, config analyzer.LinterRule) (analyzer.RuleResult, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would say there's no need for this BaseRule. it can be replaced by a func (r MyRule) ID() string {return someconstantID} func, satisfying the interface. This also makes the ID immutable, and that's good in this context

Evaluate(context.Context, model.Trace, analyzer.LinterRule) (analyzer.RuleResult, error)
}

type RuleRegistry interface {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Who depends on this interface? can the dependees directly reference a struct? What benefits do we get from this interface?

If a client depends on this interface, it will be requiring this package. Given that we only support one implementation of the RuleRegistry, and it's in memory without any dependencies, doesn't look like we would need to mock it, for example, or replace it by another implementation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Makes sense, I think the best case is just to remove the interface

@@ -255,6 +255,15 @@ func (m *manager[T]) doCreate(w http.ResponseWriter, r *http.Request, encoder En
specs = m.rh.SetID(specs, m.config.idgen())
}

if err := specs.Validate(); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

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

niceeeee

@@ -288,6 +297,15 @@ func (m *manager[T]) upsert(w http.ResponseWriter, r *http.Request) {
return
}

if err := targetResource.Spec.Validate(); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is already handled on doCreate, and if you move this to happen within the doUpdate method, we can have both cases covered and remove the need for extra work here

@xoscar
Copy link
Collaborator Author

xoscar commented Jul 5, 2023

Hey @schoren thanks for the review it was really insightful! I think I addressed all of your comments and updated the code accordingly, let me know if there is something else I could do

@xoscar xoscar requested a review from schoren July 5, 2023 16:08
@xoscar xoscar changed the title feature: Enabling Analyzer Configuration - Backend feature(backend): Enabling Analyzer Configuration Jul 5, 2023
Copy link
Collaborator

@schoren schoren left a comment

Choose a reason for hiding this comment

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

Thanks @xoscar! this looks very consistent now. I would strongly suggest to remove the remaining duplicated loops (here and here) and remove the BaseRule, since it's now unused (I think?)

Other than that, LGTM

@xoscar xoscar merged commit bc5c6cb into feat/analyzer-configuration Jul 5, 2023
@xoscar xoscar deleted the feat/analyzer-configuration-backend branch July 5, 2023 18:51
xoscar added a commit that referenced this pull request Jul 10, 2023
* feature(backend): Enabling Analyzer Configuration (#2842)

* feature: Enabling Analyzer Configuration

* feature: Enabling Analyzer Configuration

* cleanup and improvements

* cleanup and improvements

* improvements, upgrades and simplification

* improvements, upgrades and simplification

* adding tests for the analyzer and linter

* fixing linter runner

* updates and fixes based on PR review comments

* removing unnecessary loop

* feature(frontend): Analyzer Configuration (#2850)

* feature: Enabling Analyzer Configuration

* feature: Enabling Analyzer Configuration

* cleanup and improvements

* cleanup and improvements

* improvements, upgrades and simplification

* improvements, upgrades and simplification

* adding tests for the analyzer and linter

* fixing linter runner

* feature(frontend): Analyzer Configuration

* feature(frontend): Analyzer Configuration

* updates and fixes based on PR review comments

* clean up changes

* fix(frontend): fix analyzer score styles (#2860)

* feature(cli): Analyzer Configuration (#2873)

* remove file with conflict

* fix disabled rule logic

---------

Co-authored-by: Jorge Padilla <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants