Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions cmd/osv-scanner/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/google/osv-scanner/v2/cmd/osv-scanner/fix"
"github.com/google/osv-scanner/v2/cmd/osv-scanner/internal/cmd"
"github.com/google/osv-scanner/v2/cmd/osv-scanner/mcp"
"github.com/google/osv-scanner/v2/cmd/osv-scanner/plugins"
"github.com/google/osv-scanner/v2/cmd/osv-scanner/scan"
"github.com/google/osv-scanner/v2/cmd/osv-scanner/update"
)
Expand All @@ -15,6 +16,7 @@ func main() {
cmd.Run(os.Args, os.Stdout, os.Stderr, nil, []cmd.CommandBuilder{
scan.Command,
fix.Command,
plugins.Command,
update.Command,
mcp.Command,
}),
Expand Down
45 changes: 45 additions & 0 deletions cmd/osv-scanner/plugins/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package plugins

import (
"context"
"fmt"
"io"
"net/http"
"strings"

"github.com/google/osv-scanner/v2/internal/scalibrplugin"
"github.com/urfave/cli/v3"
)

func Command(stdout, _ io.Writer, _ *http.Client) *cli.Command {
return &cli.Command{
Name: "plugins",
Usage: "lists the available experimental plugin presets and exact plugin names",
Description: "Lists the available experimental plugin presets and exact plugin names that can be passed to --experimental-plugins or --experimental-disable-plugins.",
Commands: []*cli.Command{
{
Name: "list",
Usage: "lists the available plugin presets and exact plugin names",
Description: "Lists the available experimental plugin presets and exact plugin names that can be passed to --experimental-plugins or --experimental-disable-plugins.",
Action: func(_ context.Context, _ *cli.Command) error {
printPluginCatalog(stdout)
return nil
},
},
},
}
}

func printPluginCatalog(stdout io.Writer) {
fmt.Fprintln(stdout, "Available plugin presets:")
fmt.Fprintf(stdout, " extractors: %s\n", strings.Join(scalibrplugin.ExtractorPresetNames(), ", "))
fmt.Fprintf(stdout, " detectors: %s\n", strings.Join(scalibrplugin.DetectorPresetNames(), ", "))
fmt.Fprintf(stdout, " annotators: %s\n", strings.Join(scalibrplugin.AnnotatorPresetNames(), ", "))
fmt.Fprintf(stdout, " enrichers: %s\n", strings.Join(scalibrplugin.EnricherPresetNames(), ", "))
fmt.Fprintln(stdout)

fmt.Fprintln(stdout, "Available exact plugin names:")
for _, name := range scalibrplugin.PluginNames() {
fmt.Fprintf(stdout, " %s\n", name)
}
}
58 changes: 58 additions & 0 deletions cmd/osv-scanner/plugins/command_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package plugins_test

import (
"strings"
"testing"

"github.com/google/osv-scanner/v2/cmd/osv-scanner/internal/testcmd"
)

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

stdout, stderr := testcmd.RunAndNormalize(t, testcmd.Case{
Name: "list",
Args: []string{"", "plugins", "list"},
Exit: 0,
})

if stderr != "" {
t.Fatalf("plugins list wrote to stderr: %q", stderr)
}

for _, want := range []string{
"Available plugin presets:",
"extractors: artifact, directory, lockfile, sbom",
"detectors: cis, govulncheck, untested, weakcreds",
"annotators: artifact",
"enrichers: artifact, licenses, transitive, vulns",
"Available exact plugin names:",
"javascript/packagelockjson",
"os/apk",
} {
if !strings.Contains(stdout, want) {
t.Fatalf("plugins list output missing %q\noutput:\n%s", want, stdout)
}
}
}

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

stdout, stderr := testcmd.RunAndNormalize(t, testcmd.Case{
Name: "help",
Args: []string{"", "plugins", "--help"},
Exit: 127,
})

combined := stdout + "\n" + stderr
for _, want := range []string{
"osv-scanner plugins",
"lists the available experimental plugin presets and exact plugin names",
"list",
} {
if !strings.Contains(combined, want) {
t.Fatalf("plugins help output missing %q\noutput:\n%s", want, combined)
}
}
}
21 changes: 21 additions & 0 deletions cmd/osv-scanner/plugins/testmain_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package plugins_test

import (
"log/slog"
"testing"

"github.com/google/osv-scanner/v2/cmd/osv-scanner/internal/cmd"
"github.com/google/osv-scanner/v2/cmd/osv-scanner/internal/testcmd"
"github.com/google/osv-scanner/v2/cmd/osv-scanner/plugins"
"github.com/google/osv-scanner/v2/internal/config"
"github.com/google/osv-scanner/v2/internal/testlogger"
"github.com/google/osv-scanner/v2/internal/testutility"
)

func TestMain(m *testing.M) {
config.OSVScannerConfigName = "osv-scanner-test.toml"
slog.SetDefault(slog.New(testlogger.New()))
testcmd.CommandsUnderTest = []cmd.CommandBuilder{plugins.Command}
m.Run()
testutility.CleanSnapshots(m)
}
2 changes: 2 additions & 0 deletions cmd/osv-scanner/testmain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/google/osv-scanner/v2/cmd/osv-scanner/fix"
"github.com/google/osv-scanner/v2/cmd/osv-scanner/internal/cmd"
"github.com/google/osv-scanner/v2/cmd/osv-scanner/internal/testcmd"
"github.com/google/osv-scanner/v2/cmd/osv-scanner/plugins"
"github.com/google/osv-scanner/v2/cmd/osv-scanner/scan"
"github.com/google/osv-scanner/v2/cmd/osv-scanner/update"
"github.com/google/osv-scanner/v2/internal/config"
Expand All @@ -29,6 +30,7 @@ func TestMain(m *testing.M) {
testcmd.CommandsUnderTest = []cmd.CommandBuilder{
scan.Command,
fix.Command,
plugins.Command,
update.Command,
}
m.Run()
Expand Down
61 changes: 61 additions & 0 deletions internal/scalibrplugin/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package scalibrplugin

import (
"fmt"
"maps"
"slices"

cpb "github.com/google/osv-scalibr/binary/proto/config_go_proto"
Expand Down Expand Up @@ -123,3 +124,63 @@ func filterPluginsMissingRequiredPlugins(pluginStatues map[string]bool, loaded [

return plugins
}

func sortedPresetNames[T any](presets map[string]T) []string {
names := slices.Collect(maps.Keys(presets))
slices.Sort(names)

return names
}

func sortedNestedPluginNames[T any](presets map[string]map[string]T) []string {
seen := map[string]struct{}{}

for _, preset := range presets {
for name := range preset {
seen[name] = struct{}{}
}
}

names := slices.Collect(maps.Keys(seen))
slices.Sort(names)

return names
}

func ExtractorPresetNames() []string {
return sortedPresetNames(ExtractorPresets)
}

func DetectorPresetNames() []string {
return sortedPresetNames(detectorPresets)
}

func AnnotatorPresetNames() []string {
return sortedPresetNames(annotatorPresets)
}

func EnricherPresetNames() []string {
return sortedPresetNames(enricherPresets)
}

func PluginNames() []string {
seen := map[string]struct{}{}

for _, name := range sortedNestedPluginNames(ExtractorPresets) {
seen[name] = struct{}{}
}
for _, name := range sortedNestedPluginNames(detectorPresets) {
seen[name] = struct{}{}
}
for _, name := range sortedNestedPluginNames(annotatorPresets) {
seen[name] = struct{}{}
}
for _, name := range sortedNestedPluginNames(enricherPresets) {
seen[name] = struct{}{}
}

names := slices.Collect(maps.Keys(seen))
slices.Sort(names)

return names
}
28 changes: 28 additions & 0 deletions internal/scalibrplugin/resolve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,34 @@ func TestResolve(t *testing.T) {
}
}

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

got := scalibrplugin.ExtractorPresetNames()
want := []string{"artifact", "directory", "lockfile", "sbom"}

if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("ExtractorPresetNames() mismatch (-want +got):\n%s", diff)
}
}

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

got := scalibrplugin.PluginNames()

for _, want := range []string{
composerlock.Name,
apk.Name,
transitivedependencypomxml.Name,
codeserver.Name,
} {
if !slices.Contains(got, want) {
t.Fatalf("PluginNames() missing %q", want)
}
}
}

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

Expand Down