Skip to content

Commit

Permalink
✨ adding incident label selector CLI option (#445)
Browse files Browse the repository at this point in the history
First thing to complete the incident filtering on packages from the UI,
is to have the CLI option.

This CLI option is responsible for filtering incidents based on selectors applied to the stringified version of the variables


Signed-off-by: Shawn Hurley <[email protected]>
  • Loading branch information
shawn-hurley authored Dec 5, 2023
1 parent b8c1df0 commit 391775e
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 15 deletions.
5 changes: 4 additions & 1 deletion cmd/analyzer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ var (
errorOnViolations bool
labelSelector string
depLabelSelector string
incidentSelector string
logLevel int
enableJaeger bool
jaegerEndpoint string
Expand All @@ -54,6 +55,7 @@ func init() {
rootCmd.Flags().BoolVar(&errorOnViolations, "error-on-violation", false, "exit with 3 if any violation are found will also print violations to console")
rootCmd.Flags().StringVar(&labelSelector, "label-selector", "", "an expression to select rules based on labels")
rootCmd.Flags().StringVar(&depLabelSelector, "dep-label-selector", "", "an expression to select dependencies based on labels. This will filter out the violations from these dependencies as well these dependencies when matching dependency conditions")
rootCmd.Flags().StringVar(&incidentSelector, "incident-selector", "", "an expression to select incidents based on custom variables. ex: (!package=io.konveyor.demo.config-utils)")
rootCmd.Flags().IntVar(&logLevel, "verbose", 9, "level for logging output")
rootCmd.Flags().BoolVar(&enableJaeger, "enable-jaeger", false, "enable tracer exports to jaeger endpoint")
rootCmd.Flags().StringVar(&jaegerEndpoint, "jaeger-endpoint", "http://localhost:14268/api/traces", "jaeger endpoint to collect tracing data")
Expand Down Expand Up @@ -139,6 +141,7 @@ func main() {
engine.WithIncidentLimit(limitIncidents),
engine.WithCodeSnipLimit(limitCodeSnips),
engine.WithContextLines(contextLines),
engine.WithIncidentSelector(incidentSelector),
)

providers := map[string]provider.InternalProviderClient{}
Expand Down Expand Up @@ -217,7 +220,7 @@ func main() {
if err != nil {
log.Error(err, "error writing output file", "file", outputViolations)
os.Exit(1) // Treat the error as a fatal error
}
}
}

func validateFlags() error {
Expand Down
61 changes: 51 additions & 10 deletions engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (

"github.com/cbroglie/mustache"
"github.com/go-logr/logr"
"github.com/konveyor/analyzer-lsp/engine/internal"
"github.com/konveyor/analyzer-lsp/engine/labels"
"github.com/konveyor/analyzer-lsp/output/v1/konveyor"
"github.com/konveyor/analyzer-lsp/tracing"
)
Expand Down Expand Up @@ -47,9 +49,10 @@ type ruleEngine struct {

wg *sync.WaitGroup

incidentLimit int
codeSnipLimit int
contextLines int
incidentLimit int
codeSnipLimit int
contextLines int
incidentSelector string
}

type Option func(engine *ruleEngine)
Expand All @@ -72,6 +75,12 @@ func WithCodeSnipLimit(i int) Option {
}
}

func WithIncidentSelector(selector string) Option {
return func(engine *ruleEngine) {
engine.incidentSelector = selector
}
}

func CreateRuleEngine(ctx context.Context, workers int, log logr.Logger, options ...Option) RuleEngine {
// Only allow for 10 rules to be waiting in the buffer at once.
// Adding more workers will increase the number of rules running at once.
Expand Down Expand Up @@ -178,13 +187,21 @@ func (r *ruleEngine) RunRules(ctx context.Context, ruleSets []RuleSet, selectors
if err != nil {
r.logger.Error(err, "unable to create violation from response")
}
atomic.AddInt32(&matchedRules, 1)

rs, ok := mapRuleSets[response.RuleSetName]
if !ok {
r.logger.Info("this should never happen that we don't find the ruleset")
if len(violation.Incidents) == 0 {
r.logger.V(5).Info("rule was evaluated and incidents were filtered out to make it unmatched", "rule", response.Rule.RuleID)
atomic.AddInt32(&unmatchedRules, 1)
if rs, ok := mapRuleSets[response.RuleSetName]; ok {
rs.Unmatched = append(rs.Unmatched, response.Rule.RuleID)
}
} else {
atomic.AddInt32(&matchedRules, 1)

rs, ok := mapRuleSets[response.RuleSetName]
if !ok {
r.logger.Info("this should never happen that we don't find the ruleset")
}
rs.Violations[response.Rule.RuleID] = violation
}
rs.Violations[response.Rule.RuleID] = violation
} else {
atomic.AddInt32(&unmatchedRules, 1)
// Log that rule did not pass
Expand Down Expand Up @@ -390,6 +407,14 @@ func (r *ruleEngine) createViolation(ctx context.Context, conditionResponse Cond
incidents := []konveyor.Incident{}
fileCodeSnipCount := map[string]int{}
incidentsSet := map[string]struct{}{} // Set of incidents
var incidentSelector *labels.LabelSelector[internal.VariableLabelSelector]
var err error
if r.incidentSelector != "" {
incidentSelector, err = labels.NewLabelSelector[internal.VariableLabelSelector](r.incidentSelector)
if err != nil {
return konveyor.Violation{}, err
}
}
for _, m := range conditionResponse.Incidents {
// Exit loop, we don't care about any incidents past the filter.
if r.incidentLimit != 0 && len(incidents) == r.incidentLimit {
Expand All @@ -398,7 +423,9 @@ func (r *ruleEngine) createViolation(ctx context.Context, conditionResponse Cond
incident := konveyor.Incident{
URI: m.FileURI,
LineNumber: m.LineNumber,
Variables: m.Variables,
// This allows us to change m.Variables and it will be set
// because it is a pointer.
Variables: m.Variables,
}
if m.LineNumber != nil {
lineNumber := *m.LineNumber
Expand Down Expand Up @@ -471,13 +498,27 @@ func (r *ruleEngine) createViolation(ctx context.Context, conditionResponse Cond
incidentLineNumber = *incident.LineNumber
}

// Deterime if we can filter out based on incident selector.
if r.incidentSelector != "" {
v := internal.VariableLabelSelector(incident.Variables)
b, err := incidentSelector.Matches(v)
if err != nil {
r.logger.Error(err, "unable to determine if incident should filter out, defautl to adding")
}
if !b {
r.logger.V(8).Info("filtering out incident based on incident selector")
continue
}
}

incidentString := fmt.Sprintf("%s-%s-%d", incident.URI, incident.Message, incidentLineNumber) // Formating a unique string for an incident

// Adding it to list and set if no duplicates found
if _, isDuplicate := incidentsSet[incidentString]; !isDuplicate {
incidents = append(incidents, incident)
incidentsSet[incidentString] = struct{}{}
}

}

rule.Labels = deduplicateLabels(rule.Labels)
Expand Down
18 changes: 18 additions & 0 deletions engine/internal/variable_labels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package internal

import "fmt"

type VariableLabelSelector map[string]interface{}

func (v VariableLabelSelector) GetLabels() []string {
if len(v) == 0 {
// adding a single empty string will allow for Not selectors to match
// incidents that have no variables
return []string{""}
}
s := []string{}
for k, v := range v {
s = append(s, fmt.Sprintf("%s=%s", k, v))
}
return s
}
16 changes: 12 additions & 4 deletions engine/labels/labels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,17 @@ package labels
import (
"testing"

"github.com/konveyor/analyzer-lsp/engine"
"github.com/konveyor/analyzer-lsp/engine/internal"
)

type ruleMeta struct {
Labels []string
}

func (r ruleMeta) GetLabels() []string {
return r.Labels
}

func Test_getBooleanExpression(t *testing.T) {
tests := []struct {
name string
Expand Down Expand Up @@ -272,7 +280,7 @@ func TestNewRuleSelector(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := NewLabelSelector[*engine.RuleMeta](tt.expr)
_, err := NewLabelSelector[internal.VariableLabelSelector](tt.expr)
if (err != nil) != tt.wantErr {
t.Errorf("NewRuleSelector() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down Expand Up @@ -381,8 +389,8 @@ func Test_ruleSelector_Matches(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, _ := NewLabelSelector[*engine.RuleMeta](tt.expr)
if got, _ := s.Matches(&engine.RuleMeta{Labels: tt.ruleLabels}); got != tt.want {
s, _ := NewLabelSelector[Labeled](tt.expr)
if got, _ := s.Matches(ruleMeta{Labels: tt.ruleLabels}); got != tt.want {
t.Errorf("ruleSelector.Matches() = %v, want %v", got, tt.want)
}
})
Expand Down

0 comments on commit 391775e

Please sign in to comment.