Skip to content

Commit

Permalink
first pass at adding exclude support for builtin provider
Browse files Browse the repository at this point in the history
Signed-off-by: Pranav Gaikwad <pgaikwad@redhat.com>
  • Loading branch information
pranavgaikwad committed Jan 10, 2025
1 parent bdda7bf commit a73c9c7
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 10 deletions.
5 changes: 3 additions & 2 deletions engine/conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ func gatherChain(start ConditionEntry, entries []ConditionEntry) []ConditionEntr

// Chain Templates are used by rules and providers to pass context around during rule execution.
type ChainTemplate struct {
Filepaths []string `yaml:"filepaths"`
Extras map[string]interface{} `yaml:"extras"`
Filepaths []string `yaml:"filepaths,omitempty"`
Extras map[string]interface{} `yaml:"extras,omitempty"`
ExcludedPaths []string `yaml:"excludedPaths,omitempty"`
}
26 changes: 26 additions & 0 deletions engine/scopes.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,29 @@ func IncludedPathsScope(paths []string, log logr.Logger) Scope {
log: log,
}
}

type excludedPathsScope struct {
paths []string
}

var _ Scope = &excludedPathsScope{}

Check failure on line 114 in engine/scopes.go

View workflow job for this annotation

GitHub Actions / test

cannot use &excludedPathsScope{} (value of type *excludedPathsScope) as Scope value in variable declaration: *excludedPathsScope does not implement Scope (missing method FilterResponse)

func (e *excludedPathsScope) Name() string {
return "ExcludedPathsScope"
}

func (e *excludedPathsScope) AddToContext(conditionCtx *ConditionContext) error {
templ := ChainTemplate{}
if existingTempl, ok := conditionCtx.Template[TemplateContextPathScopeKey]; ok {
templ = existingTempl
}
templ.ExcludedPaths = e.paths
conditionCtx.Template[TemplateContextPathScopeKey] = templ
return nil
}

func ExcludedPathsScope(paths []string) Scope {
return &excludedPathsScope{

Check failure on line 131 in engine/scopes.go

View workflow job for this annotation

GitHub Actions / test

cannot use &excludedPathsScope{…} (value of type *excludedPathsScope) as Scope value in return statement: *excludedPathsScope does not implement Scope (missing method FilterResponse)
paths: paths,
}
}
4 changes: 4 additions & 0 deletions provider/internal/builtin/service_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ func (p *builtinServiceClient) Evaluate(ctx context.Context, cap string, conditi
if err != nil {
return response, fmt.Errorf("unable to find files using pattern `%s`: %v", c.Pattern, err)
}
_, matchingFiles = cond.ProviderContext.GetScopedFilepaths(matchingFiles...)
}

response.TemplateContext = map[string]interface{}{"filepaths": matchingFiles}
Expand Down Expand Up @@ -201,6 +202,7 @@ func (p *builtinServiceClient) Evaluate(ctx context.Context, cap string, conditi
if err != nil {
return response, fmt.Errorf("unable to find XML files: %v", err)
}
_, xmlFiles = cond.ProviderContext.GetScopedFilepaths(xmlFiles...)
for _, file := range xmlFiles {
nodes, err := queryXMLFile(file, query)
if err != nil {
Expand Down Expand Up @@ -279,6 +281,7 @@ func (p *builtinServiceClient) Evaluate(ctx context.Context, cap string, conditi
if err != nil {
return response, fmt.Errorf("unable to find XML files: %v", err)
}
_, xmlFiles = cond.ProviderContext.GetScopedFilepaths(xmlFiles...)
for _, file := range xmlFiles {
nodes, err := queryXMLFile(file, query)
if err != nil {
Expand Down Expand Up @@ -331,6 +334,7 @@ func (p *builtinServiceClient) Evaluate(ctx context.Context, cap string, conditi
if err != nil {
return response, fmt.Errorf("unable to find files using pattern `%s`: %v", pattern, err)
}
_, jsonFiles = cond.ProviderContext.GetScopedFilepaths(jsonFiles...)
for _, file := range jsonFiles {
f, err := os.Open(file)
if err != nil {
Expand Down
40 changes: 32 additions & 8 deletions provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,16 +330,40 @@ type ExternalLinks struct {
type ProviderContext struct {
Tags map[string]interface{} `yaml:"tags"`
Template map[string]engine.ChainTemplate `yaml:"template"`
RuleID string `yaml:ruleID`
}

func (p *ProviderContext) GetScopedFilepaths() (bool, []string) {
for key, value := range p.Template {
if key == engine.TemplateContextPathScopeKey {
return true, value.Filepaths
RuleID string `yaml:"ruleID"`
}

// GetScopedFilepaths returns a list of filepaths based on either included or excluded paths in context
// when tmpl.Filepaths is set, it is including specific files. we return the value of tmpl.Filepaths as-is
// when tmpl.ExcludedPaths is set, it will exclude the files but we don't know set of all files to exclude from
// as a result, we need an input list of paths to exclude files from. this is upto providers how to pass that list
// when both are set, exclusion happens on union of input paths and included paths.
func (p *ProviderContext) GetScopedFilepaths(paths ...string) (bool, []string) {
if value, ok := p.Template[engine.TemplateContextPathScopeKey]; ok {
includedPaths := []string{}
if (value.Filepaths != nil) && len(value.Filepaths) > 0 {
includedPaths = value.Filepaths
}
includedPaths = append(includedPaths, paths...)
if len(includedPaths) == 0 {
return false, includedPaths
}
filtered := []string{}
for _, path := range includedPaths {
excluded := false
for _, excldPattern := range value.ExcludedPaths {
if pattern, err := regexp.Compile(excldPattern); err == nil &&
pattern.MatchString(path) {
excluded = true
}
}
if !excluded {
filtered = append(filtered, path)
}
}
return true, filtered
}
return false, nil
return false, paths
}

func HasCapability(caps []Capability, name string) bool {
Expand Down
81 changes: 81 additions & 0 deletions provider/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,3 +412,84 @@ func Test_GetConfigs(t *testing.T) {
})
}
}

func TestProviderContext_GetScopedFilepaths(t *testing.T) {
tests := []struct {
name string
template map[string]engine.ChainTemplate
inputPaths []string
want []string
}{
{
name: "tc-0: only included filepaths present in context, must return that list as-is",
template: map[string]engine.ChainTemplate{
engine.TemplateContextPathScopeKey: {
Filepaths: []string{"a/", "b/", "c/"},
},
},
inputPaths: []string{},
want: []string{"a/", "b/", "c/"},
},
{
name: "tc-1: included paths present in context, a list of additional paths provided as input, no exclusion, must return union of two lists",
template: map[string]engine.ChainTemplate{
engine.TemplateContextPathScopeKey: {
Filepaths: []string{"a/", "b/"},
},
},
inputPaths: []string{"c/"},
want: []string{"a/", "b/", "c/"},
},
{
name: "tc-2: included paths present in context, a list of additional paths provided as input, and an exclusion list present, must return correctly filtered list",
template: map[string]engine.ChainTemplate{
engine.TemplateContextPathScopeKey: {
Filepaths: []string{"a/", "b/c/", "b/c/a.java", "d/e/f.py"},
ExcludedPaths: []string{
"b/c/",
},
},
},
inputPaths: []string{"c/p.xml"},
want: []string{"a/", "d/e/f.py", "c/p.xml"},
},
{
name: "tc-3: no included or excluded paths present in context, must return input paths as-is",
template: map[string]engine.ChainTemplate{},
inputPaths: []string{"a/", "b/", "c/"},
want: []string{"a/", "b/", "c/"},
},
{
name: "tc-4: no included or excluded paths, must return input paths as-is",
template: map[string]engine.ChainTemplate{
engine.TemplateContextPathScopeKey: {
ExcludedPaths: []string{},
},
},
inputPaths: []string{"a/", "b/", "c/"},
want: []string{"a/", "b/", "c/"},
},
{
name: "tc-5: included and excluded paths given but no input paths, must return correct list of included paths with excluded ones removed",
template: map[string]engine.ChainTemplate{
engine.TemplateContextPathScopeKey: {
Filepaths: []string{"a/b.py", "c/d/e/f.java", "l/m/n/p.py"},
ExcludedPaths: []string{".*e.*"},
},
},
inputPaths: []string{},
want: []string{"a/b.py", "l/m/n/p.py"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &ProviderContext{
Template: tt.template,
RuleID: "test",
}
if _, got := p.GetScopedFilepaths(tt.inputPaths...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("ProviderContext.FilterExcludedPaths() = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit a73c9c7

Please sign in to comment.