Skip to content

Commit 6297bc7

Browse files
authored
Merge pull request #2639 from joejstuart/EC-1339-3
implement pluggable rule filtering system with pipeline intentions
2 parents 18301a2 + 86b1494 commit 6297bc7

File tree

9 files changed

+1952
-313
lines changed

9 files changed

+1952
-313
lines changed

.cursor/rules/package_filtering_process.mdc

Lines changed: 534 additions & 0 deletions
Large diffs are not rendered by default.

internal/evaluator/conftest_evaluator.go

Lines changed: 85 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -167,17 +167,18 @@ type testRunner interface {
167167
}
168168

169169
const (
170-
effectiveOnFormat = "2006-01-02T15:04:05Z"
171-
effectiveOnTimeout = -90 * 24 * time.Hour // keep effective_on metadata up to 90 days
172-
metadataCode = "code"
173-
metadataCollections = "collections"
174-
metadataDependsOn = "depends_on"
175-
metadataDescription = "description"
176-
metadataSeverity = "severity"
177-
metadataEffectiveOn = "effective_on"
178-
metadataSolution = "solution"
179-
metadataTerm = "term"
180-
metadataTitle = "title"
170+
effectiveOnFormat = "2006-01-02T15:04:05Z"
171+
effectiveOnTimeout = -90 * 24 * time.Hour // keep effective_on metadata up to 90 days
172+
metadataCode = "code"
173+
metadataCollections = "collections"
174+
metadataDependsOn = "depends_on"
175+
metadataDescription = "description"
176+
metadataPipelineIntention = "pipeline_intention"
177+
metadataSeverity = "severity"
178+
metadataEffectiveOn = "effective_on"
179+
metadataSolution = "solution"
180+
metadataTerm = "term"
181+
metadataTitle = "title"
181182
)
182183

183184
const (
@@ -205,6 +206,7 @@ type conftestEvaluator struct {
205206
exclude *Criteria
206207
fs afero.Fs
207208
namespace []string
209+
source ecc.Source
208210
}
209211

210212
type conftestRunner struct {
@@ -285,7 +287,7 @@ func (r conftestRunner) Run(ctx context.Context, fileList []string) (result []Ou
285287
// NewConftestEvaluator returns initialized conftestEvaluator implementing
286288
// Evaluator interface
287289
func NewConftestEvaluator(ctx context.Context, policySources []source.PolicySource, p ConfigProvider, source ecc.Source) (Evaluator, error) {
288-
return NewConftestEvaluatorWithNamespace(ctx, policySources, p, source, nil)
290+
return NewConftestEvaluatorWithNamespace(ctx, policySources, p, source, []string{})
289291
}
290292

291293
// set the policy namespace
@@ -302,9 +304,11 @@ func NewConftestEvaluatorWithNamespace(ctx context.Context, policySources []sour
302304
policy: p,
303305
fs: fs,
304306
namespace: namespace,
307+
source: source,
305308
}
306309

307310
c.include, c.exclude = computeIncludeExclude(source, p)
311+
308312
dir, err := utils.CreateWorkDir(fs)
309313
if err != nil {
310314
log.Debug("Failed to create work dir!")
@@ -341,6 +345,9 @@ func (c conftestEvaluator) CapabilitiesPath() string {
341345

342346
type policyRules map[string]rule.Info
343347

348+
// Add a new type to track non-annotated rules separately
349+
type nonAnnotatedRules map[string]bool
350+
344351
func (r *policyRules) collect(a *ast.AnnotationsRef) error {
345352
if a.Annotations == nil {
346353
return nil
@@ -377,6 +384,8 @@ func (c conftestEvaluator) Evaluate(ctx context.Context, target EvaluationTarget
377384
// exist with the same code in two separate sources the collected rule
378385
// information is not deterministic
379386
rules := policyRules{}
387+
// Track non-annotated rules separately for filtering purposes only
388+
nonAnnotatedRules := nonAnnotatedRules{}
380389
// Download all sources
381390
for _, s := range c.policySources {
382391
dir, err := s.GetPolicy(ctx, c.workDir, false)
@@ -414,32 +423,85 @@ func (c conftestEvaluator) Evaluate(ctx context.Context, target EvaluationTarget
414423
}
415424
}
416425

426+
// Collect ALL rules for filtering purposes - both with and without annotations
427+
// This ensures that rules without metadata (like fail_with_data.rego) are properly included
417428
for _, a := range annotations {
418-
if a.Annotations == nil {
419-
continue
429+
if a.Annotations != nil {
430+
// Rules with annotations - collect full metadata
431+
if err := rules.collect(a); err != nil {
432+
return nil, err
433+
}
434+
} else {
435+
// Rules without annotations - track for filtering only, not for success computation
436+
ruleRef := a.GetRule()
437+
if ruleRef != nil {
438+
// Extract package name from the rule path
439+
packageName := ""
440+
if len(a.Path) > 1 {
441+
// Path format is typically ["data", "package", "rule"]
442+
// We want the package part (index 1)
443+
if len(a.Path) >= 2 {
444+
packageName = strings.ReplaceAll(a.Path[1].String(), `"`, "")
445+
}
446+
}
447+
448+
// Extract short name from the rule head
449+
shortName := ruleRef.Head.Name.String()
450+
451+
// Generate code for filtering purposes
452+
code := fmt.Sprintf("%s.%s", packageName, shortName)
453+
454+
// Track for filtering but don't add to rules map for success computation
455+
nonAnnotatedRules[code] = true
456+
}
420457
}
421-
if err := rules.collect(a); err != nil {
422-
return nil, err
458+
}
459+
}
460+
461+
// Filter namespaces using the new pluggable filtering system
462+
filterFactory := NewIncludeFilterFactory()
463+
filters := filterFactory.CreateFilters(c.source)
464+
// Combine annotated and non-annotated rules for filtering
465+
allRules := make(policyRules)
466+
for code, rule := range rules {
467+
allRules[code] = rule
468+
}
469+
// Add non-annotated rules as minimal rule.Info for filtering
470+
for code := range nonAnnotatedRules {
471+
parts := strings.Split(code, ".")
472+
if len(parts) >= 2 {
473+
packageName := parts[len(parts)-2]
474+
shortName := parts[len(parts)-1]
475+
allRules[code] = rule.Info{
476+
Code: code,
477+
Package: packageName,
478+
ShortName: shortName,
423479
}
424480
}
425481
}
482+
filteredNamespaces := filterNamespaces(allRules, filters...)
426483

427484
var r testRunner
428485
var ok bool
429486
if r, ok = ctx.Value(runnerKey).(testRunner); r == nil || !ok {
430487

431-
// should there be a namespace defined or not
432-
allNamespaces := true
433-
if len(c.namespace) > 0 {
434-
allNamespaces = false
488+
// Determine which namespaces to use
489+
namespacesToUse := c.namespace
490+
491+
// If we have filtered namespaces from the filtering system, use those
492+
if len(filteredNamespaces) > 0 {
493+
namespacesToUse = filteredNamespaces
435494
}
436495

496+
// log the namespaces to use
497+
log.Debugf("Namespaces to use: %v", namespacesToUse)
498+
437499
r = &conftestRunner{
438500
runner.TestRunner{
439501
Data: []string{c.dataDir},
440502
Policy: []string{c.policyDir},
441-
Namespace: c.namespace,
442-
AllNamespaces: allNamespaces,
503+
Namespace: namespacesToUse,
504+
AllNamespaces: false, // Always false to prevent bypassing filtering
443505
NoFail: true,
444506
Output: c.outputFormat,
445507
Capabilities: c.CapabilitiesPath(),
@@ -504,6 +566,7 @@ func (c conftestEvaluator) Evaluate(ctx context.Context, target EvaluationTarget
504566

505567
for i := range result.Failures {
506568
failure := result.Failures[i]
569+
// log the failure
507570
addRuleMetadata(ctx, &failure, rules)
508571

509572
if !c.isResultIncluded(failure, target.Target, missingIncludes) {

0 commit comments

Comments
 (0)