Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7059e54
feat: handling building extractors through a single `scanner.Build` f…
G-Rath Apr 21, 2025
059c96f
refactor: inline some of the extractor builder logic
G-Rath Apr 21, 2025
94e3e66
refactor: introduce building specific extractors by name
G-Rath Apr 21, 2025
72835bc
refactor: start moving presets out of `extractorbuilder`
G-Rath Apr 22, 2025
0c5a9cb
feat: introduce `pomxmlenhanceable` extractor
G-Rath Apr 22, 2025
c045e9e
refactor: remove old code
G-Rath Apr 22, 2025
3bbd43e
feat: add variables for extractor presets
G-Rath Apr 22, 2025
8addf29
feat: add cli flags
G-Rath Apr 22, 2025
ebe716d
fix: fallback to default extractors if none are enabled
G-Rath Apr 22, 2025
78f46e5
fix: error if extractors have been cancelled out in cli
G-Rath Apr 22, 2025
c24983e
feat: add "directories" extractors preset
G-Rath Apr 23, 2025
8d727c0
fix: ensure default extractors are configured correctly
G-Rath Apr 28, 2025
efb844a
fix: include `sbom` extractors in the defaults for `scan source`
G-Rath May 28, 2025
3a77bd9
refactor: improve how extractor falling back is handled
G-Rath Apr 28, 2025
7254fda
test: add some more cases
G-Rath Apr 29, 2025
5421977
feat: rename disable flag
G-Rath Apr 29, 2025
d6a2050
fix: handle when an unknown extractor is provided
G-Rath May 20, 2025
08b2e35
refactor: remove unneeded loop
G-Rath May 20, 2025
c757436
fix: skip empty strings
G-Rath May 20, 2025
600ee8b
refactor: use a map
G-Rath May 20, 2025
a6a9377
refactor: use `slices.Concat`
G-Rath May 20, 2025
f082e84
feat: support passing in extractors themselves
G-Rath May 20, 2025
952f830
build: add generator for extractor presets
G-Rath May 20, 2025
69ae182
docs: add initial page for extractors
G-Rath May 20, 2025
8edae54
chore: remove doc and generator for now
G-Rath May 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions cmd/osv-scanner/internal/helper/extractors_parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package helper

import (
"github.com/google/osv-scalibr/extractor/filesystem"
"github.com/google/osv-scanner/v2/internal/builders"
"github.com/google/osv-scanner/v2/internal/scalibrextract"
)

var presets = map[string][]string{
"sbom": scalibrextract.ExtractorsSBOMs,
"lockfile": scalibrextract.ExtractorsLockfiles,
"directory": scalibrextract.ExtractorsDirectories,
"artifact": scalibrextract.ExtractorsArtifacts,
}

func ResolveEnabledExtractors(enabledExtractors []string, disabledExtractors []string) []filesystem.Extractor {
extractors := make(map[string]bool)

for i, exts := range [][]string{enabledExtractors, disabledExtractors} {
enabled := i == 0

for _, extractorOrPreset := range exts {
if names, ok := presets[extractorOrPreset]; ok {
for _, name := range names {
extractors[name] = enabled
}

continue
}

extractors[extractorOrPreset] = enabled
}
}

asSlice := make([]string, 0, len(extractors))

for name, value := range extractors {
if name != "" && value {
asSlice = append(asSlice, name)
}
}

return builders.BuildExtractors(asSlice)
}
215 changes: 215 additions & 0 deletions cmd/osv-scanner/internal/helper/extractors_parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package helper

import (
"slices"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/google/osv-scalibr/extractor/filesystem/language/dotnet/packageslockjson"
"github.com/google/osv-scalibr/extractor/filesystem/language/golang/gobinary"
"github.com/google/osv-scalibr/extractor/filesystem/language/java/archive"
"github.com/google/osv-scalibr/extractor/filesystem/language/php/composerlock"
"github.com/google/osv-scalibr/extractor/filesystem/language/python/wheelegg"
"github.com/google/osv-scalibr/extractor/filesystem/language/rust/cargoauditable"
"github.com/google/osv-scalibr/extractor/filesystem/os/apk"
"github.com/google/osv-scalibr/extractor/filesystem/os/dpkg"
"github.com/google/osv-scalibr/extractor/filesystem/sbom/cdx"
"github.com/google/osv-scalibr/extractor/filesystem/sbom/spdx"
"github.com/google/osv-scanner/v2/internal/scalibrextract/language/javascript/nodemodules"
)

func TestResolveEnabledExtractors(t *testing.T) {
t.Parallel()

type args struct {
enabledExtractors []string
disabledExtractors []string
}
tests := []struct {
name string
args args
want []string
}{
{
name: "nothing_enabled_or_disabled",
args: args{
enabledExtractors: nil,
disabledExtractors: nil,
},
want: []string{},
},
{
name: "empty_strings_are_ignored",
args: args{
enabledExtractors: []string{""},
disabledExtractors: []string{""},
},
want: []string{},
},
//
{
name: "one_extractor_enabled_and_nothing_disabled",
args: args{
enabledExtractors: []string{composerlock.Name},
disabledExtractors: nil,
},
want: []string{composerlock.Name},
},
{
name: "one_extractor_enabled_and_different_extractor_disabled",
args: args{
enabledExtractors: []string{composerlock.Name},
disabledExtractors: []string{packageslockjson.Name},
},
want: []string{composerlock.Name},
},
{
name: "one_extractor_enabled_and_same_extractor_disabled",
args: args{
enabledExtractors: []string{composerlock.Name},
disabledExtractors: []string{composerlock.Name},
},
want: []string{},
},
//
{
name: "one_preset_enabled_and_nothing_disabled",
args: args{
enabledExtractors: []string{"artifact"},
disabledExtractors: nil,
},
want: []string{
wheelegg.Name,
archive.Name,
gobinary.Name,
nodemodules.Name,
cargoauditable.Name,
apk.Name,
dpkg.Name,
},
},
{
name: "one_preset_enabled_and_different_preset_disabled",
args: args{
enabledExtractors: []string{"artifact"},
disabledExtractors: []string{"sbom"},
},
want: []string{
wheelegg.Name,
archive.Name,
gobinary.Name,
nodemodules.Name,
cargoauditable.Name,
apk.Name,
dpkg.Name,
},
},
{
name: "one_preset_enabled_and_same_preset_disabled",
args: args{
enabledExtractors: []string{"artifact"},
disabledExtractors: []string{"artifact"},
},
want: []string{},
},
{
name: "one_preset_enabled_and_some_extractors_disabled",
args: args{
enabledExtractors: []string{"artifact"},
disabledExtractors: []string{wheelegg.Name, archive.Name, cargoauditable.Name},
},
want: []string{
gobinary.Name,
nodemodules.Name,
apk.Name,
dpkg.Name,
},
},
//
{
name: "multiple_presets_enabled_and_nothing_disabled",
args: args{
enabledExtractors: []string{"artifact", "sbom"},
disabledExtractors: []string{},
},
want: []string{
spdx.Name,
cdx.Name,
wheelegg.Name,
archive.Name,
gobinary.Name,
nodemodules.Name,
cargoauditable.Name,
apk.Name,
dpkg.Name,
},
},
//
{
name: "multiple_extractors_enabled_and_one_disabled_preset",
args: args{
enabledExtractors: []string{
spdx.Name,
archive.Name,
gobinary.Name,
},
disabledExtractors: []string{"sbom"},
},
want: []string{
archive.Name,
gobinary.Name,
},
},
{
name: "multiple_extractors_enabled_and_disabled",
args: args{
enabledExtractors: []string{
spdx.Name,
archive.Name,
gobinary.Name,
cargoauditable.Name,
},
disabledExtractors: []string{
cdx.Name,
wheelegg.Name,
gobinary.Name,
apk.Name,
},
},
want: []string{
spdx.Name,
archive.Name,
cargoauditable.Name,
},
},
//
{
name: "extractor_that_does_not_exist",
args: args{
enabledExtractors: []string{"???"},
disabledExtractors: nil,
},
want: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

got := ResolveEnabledExtractors(tt.args.enabledExtractors, tt.args.disabledExtractors)

slices.Sort(tt.want)

gotNames := make([]string, 0, len(got))
for _, extractor := range got {
gotNames = append(gotNames, extractor.Name())
}

slices.Sort(gotNames)

if diff := cmp.Diff(tt.want, gotNames); diff != "" {
t.Errorf("replaceJSONInput() diff (-want +got): %s", diff)
}
})
}
}
15 changes: 14 additions & 1 deletion cmd/osv-scanner/internal/helper/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (g *allowedLicencesFlag) String() string {
return strings.Join(g.allowlist, ",")
}

func GetScanGlobalFlags() []cli.Flag {
func GetScanGlobalFlags(defaultExtractors []string) []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "config",
Expand Down Expand Up @@ -172,6 +172,15 @@ func GetScanGlobalFlags() []cli.Flag {
Usage: "report on licenses based on an allowlist",
Value: &allowedLicencesFlag{},
},
&cli.StringSliceFlag{
Name: "experimental-extractors",
Usage: "list of specific extractors and ExtractorPresets of extractors to use",
Value: defaultExtractors,
},
&cli.StringSliceFlag{
Name: "experimental-disable-extractors",
Usage: "list of specific extractors and ExtractorPresets of extractors to not use",
},
}
}

Expand Down Expand Up @@ -251,5 +260,9 @@ func GetExperimentalScannerActions(cmd *cli.Command, scanLicensesAllowlist []str
ShowAllPackages: cmd.Bool("all-packages"),
ScanLicensesSummary: cmd.IsSet("licenses"),
ScanLicensesAllowlist: scanLicensesAllowlist,
Extractors: ResolveEnabledExtractors(
cmd.StringSlice("experimental-extractors"),
cmd.StringSlice("experimental-disable-extractors"),
),
}
}
39 changes: 39 additions & 0 deletions cmd/osv-scanner/scan/image/__snapshots__/command_test.snap
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,45 @@ No package sources found, --help for usage information.

---

[TestCommand_Docker/real_alpine_image_without_apk_extractor_enabled - 1]
Checking if docker image ("alpine:3.18.9") exists locally...
Saving docker image ("alpine:3.18.9") to temporary file...
Scanning image "alpine:3.18.9"

---

[TestCommand_Docker/real_alpine_image_without_apk_extractor_enabled - 2]
No package sources found, --help for usage information.

---

[TestCommand_ExplicitExtractors/extractors_cancelled_out - 1]

---

[TestCommand_ExplicitExtractors/extractors_cancelled_out - 2]
at least one extractor must be enabled

---

[TestCommand_ExplicitExtractors/extractors_cancelled_out#01 - 1]

---

[TestCommand_ExplicitExtractors/extractors_cancelled_out#01 - 2]
at least one extractor must be enabled

---

[TestCommand_ExplicitExtractors/extractors_cancelled_out_with_presets - 1]

---

[TestCommand_ExplicitExtractors/extractors_cancelled_out_with_presets - 2]
at least one extractor must be enabled

---

[TestCommand_OCIImage/Alpine_3.10_image_tar_with_3.18_version_file - 1]
Scanning local image tarball "../../../../internal/image/fixtures/test-alpine.tar"

Expand Down
6 changes: 5 additions & 1 deletion cmd/osv-scanner/scan/image/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func Command(stdout, stderr io.Writer) *cli.Command {
Name: "archive",
Usage: "input a local archive image (e.g. a tar file)",
},
}, helper.GetScanGlobalFlags()...),
}, helper.GetScanGlobalFlags([]string{"artifact"})...),
ArgsUsage: "[image imageNameWithTag]",
Action: func(ctx context.Context, cmd *cli.Command) error {
return action(ctx, cmd, stdout, stderr)
Expand Down Expand Up @@ -77,6 +77,10 @@ func action(_ context.Context, cmd *cli.Command, stdout, stderr io.Writer) error
ExperimentalScannerActions: helper.GetExperimentalScannerActions(cmd, scanLicensesAllowlist),
}

if len(scannerAction.Extractors) == 0 {
return errors.New("at least one extractor must be enabled")
}

var vulnResult models.VulnerabilityResults
//nolint:contextcheck // passing the context in would be a breaking change
vulnResult, err = osvscanner.DoContainerScan(scannerAction)
Expand Down
Loading
Loading