Skip to content

Commit 273eeea

Browse files
Merge pull request #690 from google:return-result
PiperOrigin-RevId: 750779834
2 parents 52ccb1a + 4c73084 commit 273eeea

File tree

7 files changed

+105
-58
lines changed

7 files changed

+105
-58
lines changed

detector/detector.go

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,11 @@ package detector
1717

1818
import (
1919
"context"
20-
"fmt"
21-
"reflect"
22-
"time"
2320

2421
"github.com/google/osv-scalibr/extractor"
2522
scalibrfs "github.com/google/osv-scalibr/fs"
2623
"github.com/google/osv-scalibr/packageindex"
2724
"github.com/google/osv-scalibr/plugin"
28-
"github.com/google/osv-scalibr/stats"
2925
)
3026

3127
// Detector is the interface for a security detector plugin, used to scan for security findings
@@ -132,47 +128,3 @@ type TargetDetails struct {
132128
}
133129

134130
// LINT.ThenChange(/binary/proto/scan_result.proto)
135-
136-
// Run runs the specified detectors and returns their findings,
137-
// as well as info about whether the plugin runs completed successfully.
138-
func Run(ctx context.Context, c stats.Collector, detectors []Detector, scanRoot *scalibrfs.ScanRoot, index *packageindex.PackageIndex) ([]*Finding, []*plugin.Status, error) {
139-
findings := []*Finding{}
140-
status := []*plugin.Status{}
141-
for _, d := range detectors {
142-
if ctx.Err() != nil {
143-
return nil, nil, ctx.Err()
144-
}
145-
start := time.Now()
146-
results, err := d.Scan(ctx, scanRoot, index)
147-
c.AfterDetectorRun(d.Name(), time.Since(start), err)
148-
for _, f := range results {
149-
f.Detectors = []string{d.Name()}
150-
}
151-
findings = append(findings, results...)
152-
status = append(status, plugin.StatusFromErr(d, false, err))
153-
}
154-
if err := validateAdvisories(findings); err != nil {
155-
return []*Finding{}, status, err
156-
}
157-
return findings, status, nil
158-
}
159-
160-
func validateAdvisories(findings []*Finding) error {
161-
// Check that findings with the same advisory ID have identical advisories.
162-
ids := make(map[AdvisoryID]Advisory)
163-
for _, f := range findings {
164-
if f.Adv == nil {
165-
return fmt.Errorf("finding has no advisory set: %v", f)
166-
}
167-
if f.Adv.ID == nil {
168-
return fmt.Errorf("finding has no advisory ID set: %v", f)
169-
}
170-
if adv, ok := ids[*f.Adv.ID]; ok {
171-
if !reflect.DeepEqual(adv, *f.Adv) {
172-
return fmt.Errorf("multiple non-identical advisories with ID %v", f.Adv.ID)
173-
}
174-
}
175-
ids[*f.Adv.ID] = *f.Adv
176-
}
177-
return nil
178-
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package detectorrunner provides a Run function to help with running detectors
16+
package detectorrunner
17+
18+
import (
19+
"context"
20+
"fmt"
21+
"reflect"
22+
"time"
23+
24+
"github.com/google/osv-scalibr/detector"
25+
scalibrfs "github.com/google/osv-scalibr/fs"
26+
"github.com/google/osv-scalibr/packageindex"
27+
"github.com/google/osv-scalibr/plugin"
28+
"github.com/google/osv-scalibr/stats"
29+
)
30+
31+
// Run runs the specified detectors and returns their findings,
32+
// as well as info about whether the plugin runs completed successfully.
33+
func Run(ctx context.Context, c stats.Collector, detectors []detector.Detector, scanRoot *scalibrfs.ScanRoot, index *packageindex.PackageIndex) ([]*detector.Finding, []*plugin.Status, error) {
34+
findings := []*detector.Finding{}
35+
status := []*plugin.Status{}
36+
for _, d := range detectors {
37+
if ctx.Err() != nil {
38+
return nil, nil, ctx.Err()
39+
}
40+
start := time.Now()
41+
results, err := d.Scan(ctx, scanRoot, index)
42+
c.AfterDetectorRun(d.Name(), time.Since(start), err)
43+
for _, f := range results {
44+
f.Detectors = []string{d.Name()}
45+
}
46+
findings = append(findings, results...)
47+
status = append(status, plugin.StatusFromErr(d, false, err))
48+
}
49+
if err := validateAdvisories(findings); err != nil {
50+
return []*detector.Finding{}, status, err
51+
}
52+
return findings, status, nil
53+
}
54+
55+
func validateAdvisories(findings []*detector.Finding) error {
56+
// Check that findings with the same advisory ID have identical advisories.
57+
ids := make(map[detector.AdvisoryID]detector.Advisory)
58+
for _, f := range findings {
59+
if f.Adv == nil {
60+
return fmt.Errorf("finding has no advisory set: %v", f)
61+
}
62+
if f.Adv.ID == nil {
63+
return fmt.Errorf("finding has no advisory ID set: %v", f)
64+
}
65+
if adv, ok := ids[*f.Adv.ID]; ok {
66+
if !reflect.DeepEqual(adv, *f.Adv) {
67+
return fmt.Errorf("multiple non-identical advisories with ID %v", f.Adv.ID)
68+
}
69+
}
70+
ids[*f.Adv.ID] = *f.Adv
71+
}
72+
return nil
73+
}

detector/detector_test.go renamed to detector/detectorrunner/detectorrunner_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
package detector_test
15+
package detectorrunner_test
1616

1717
import (
1818
"context"
@@ -22,6 +22,7 @@ import (
2222
"github.com/google/go-cmp/cmp"
2323
"github.com/google/go-cmp/cmp/cmpopts"
2424
"github.com/google/osv-scalibr/detector"
25+
"github.com/google/osv-scalibr/detector/detectorrunner"
2526
"github.com/google/osv-scalibr/extractor"
2627
scalibrfs "github.com/google/osv-scalibr/fs"
2728
"github.com/google/osv-scalibr/packageindex"
@@ -152,17 +153,17 @@ func TestRun(t *testing.T) {
152153
t.Run(tc.desc, func(t *testing.T) {
153154
px, _ := packageindex.New([]*extractor.Package{})
154155
tmp := t.TempDir()
155-
gotFindings, gotStatus, err := detector.Run(
156+
gotFindings, gotStatus, err := detectorrunner.Run(
156157
context.Background(), stats.NoopCollector{}, tc.det, scalibrfs.RealFSScanRoot(tmp), px,
157158
)
158159
if diff := cmp.Diff(tc.wantErr, err, cmpopts.EquateErrors()); diff != "" {
159-
t.Errorf("detector.Run(%v): unexpected error (-want +got):\n%s", tc.det, diff)
160+
t.Errorf("detectorrunner.Run(%v): unexpected error (-want +got):\n%s", tc.det, diff)
160161
}
161162
if diff := cmp.Diff(tc.wantFindings, gotFindings); diff != "" {
162-
t.Errorf("detector.Run(%v): unexpected findings (-want +got):\n%s", tc.det, diff)
163+
t.Errorf("detectorrunner.Run(%v): unexpected findings (-want +got):\n%s", tc.det, diff)
163164
}
164165
if diff := cmp.Diff(tc.wantStatus, gotStatus); diff != "" {
165-
t.Errorf("detector.Run(%v): unexpected status (-want +got):\n%s", tc.det, diff)
166+
t.Errorf("detectorrunner.Run(%v): unexpected status (-want +got):\n%s", tc.det, diff)
166167
}
167168
})
168169
}

extractor/filesystem/filesystem.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,12 @@ func (wc *walkContext) runExtractor(ex Extractor, path string) {
479479
Info: info,
480480
Reader: rc,
481481
})
482-
wc.stats.AfterExtractorRun(ex.Name(), time.Since(start), err)
482+
wc.stats.AfterExtractorRun(ex.Name(), &stats.AfterExtractorStats{
483+
Path: path,
484+
Runtime: time.Since(start),
485+
Inventory: &results,
486+
Error: err,
487+
})
483488

484489
if err != nil {
485490
addErrToMap(wc.errors, ex.Name(), fmt.Errorf("%s: %w", path, err))

scalibr.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/google/osv-scalibr/artifact/image/layerscanning/image"
3131
"github.com/google/osv-scalibr/artifact/image/layerscanning/trace"
3232
"github.com/google/osv-scalibr/detector"
33+
"github.com/google/osv-scalibr/detector/detectorrunner"
3334
"github.com/google/osv-scalibr/extractor"
3435
"github.com/google/osv-scalibr/extractor/filesystem"
3536
"github.com/google/osv-scalibr/extractor/standalone"
@@ -248,7 +249,7 @@ func (Scanner) Scan(ctx context.Context, config *ScanConfig) (sr *ScanResult) {
248249
return newScanResult(sro)
249250
}
250251

251-
findings, detectorStatus, err := detector.Run(
252+
findings, detectorStatus, err := detectorrunner.Run(
252253
ctx, config.Stats, config.Detectors, &scalibrfs.ScanRoot{FS: sysroot.FS, Path: sysroot.Path}, px,
253254
)
254255
sro.Inventory.Findings = append(sro.Inventory.Findings, findings...)
@@ -283,7 +284,7 @@ func (s Scanner) ScanContainer(ctx context.Context, img *image.Image, config *Sc
283284
}
284285
// Overwrite the scan roots with the chain layer filesystem.
285286
config.ScanRoots = []*scalibrfs.ScanRoot{
286-
&scalibrfs.ScanRoot{
287+
{
287288
FS: chainfs,
288289
},
289290
}

stats/collector.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import (
2626
// different metric backends to enable monitoring of Scalibr.
2727
type Collector interface {
2828
AfterInodeVisited(path string)
29-
AfterExtractorRun(name string, runtime time.Duration, err error)
29+
AfterExtractorRun(pluginName string, extractorstats *AfterExtractorStats)
3030
AfterDetectorRun(name string, runtime time.Duration, err error)
3131
AfterScan(runtime time.Duration, status *plugin.ScanStatus)
3232

@@ -60,7 +60,7 @@ type NoopCollector struct{}
6060
func (c NoopCollector) AfterInodeVisited(path string) {}
6161

6262
// AfterExtractorRun implements Collector by doing nothing.
63-
func (c NoopCollector) AfterExtractorRun(name string, runtime time.Duration, err error) {}
63+
func (c NoopCollector) AfterExtractorRun(pluginName string, extractorstats *AfterExtractorStats) {}
6464

6565
// AfterDetectorRun implements Collector by doing nothing.
6666
func (c NoopCollector) AfterDetectorRun(name string, runtime time.Duration, err error) {}

stats/types.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,21 @@
1414

1515
package stats
1616

17+
import (
18+
"time"
19+
20+
"github.com/google/osv-scalibr/inventory"
21+
)
22+
23+
// AfterExtractorStats is a struct containing stats about the results of a file extraction run.
24+
type AfterExtractorStats struct {
25+
Path string
26+
Runtime time.Duration
27+
28+
Inventory *inventory.Inventory
29+
Error error
30+
}
31+
1732
// FileRequiredStats is a struct containing stats about a file that was
1833
// required or skipped by a plugin.
1934
type FileRequiredStats struct {

0 commit comments

Comments
 (0)