feat: support plugins (#878)

* fix(log): set the default logger

* feat: support plugins

* feat(plugin): add run command

* feat(plugin): add uninstall command

* test(plugin): add tests

* chore(ci): pin go version

* chore(ci): disable G204

* refactor: fix lint issues

* feat(plugin): skip downloading installed plugins

* feat: add TRIVY_RUN_AS_PLUGIN

* support Ubuntu 20.10 (#876)

* docs(README): update ubuntu versions (#877)

* add MkDocs implementation (#870)

* mkdocs: add top level nav

* mkdocs: add installation nav

* mkdocs: add quick-start nav

* mkdocs: add examples nav

* mkdocs: add CI nav

* mkdocs: add vuln-detection nav

* mkdocs: add comparison nav

* mkdocs: add usage nav

* mkdocs: add migration nav

* mkdocs: add FAQ nav

* mkdocs: add mkdocs.yml

* mkdocs: add github workflow

* docs: update documents

* fix links

* chore(ci): use ORG_GITHUB_TOKEN

* chore(mkdocs): use mike

* chore(ci): support dev

* chore(ci): documentation test

Co-authored-by: knqyf263 <knqyf263@gmail.com>

* docs: add plugins

* chore: remove stale workflow

* refactor: fix lint issues

Co-authored-by: Huang Huang <mozillazg101@gmail.com>
Co-authored-by: aprp <doelaudi@gmail.com>
This commit is contained in:
Teppei Fukuda
2021-03-10 21:44:08 +02:00
committed by GitHub
parent 37edc66418
commit 8b3b5d0290
47 changed files with 1044 additions and 155 deletions

347
pkg/plugin/plugin_test.go Normal file
View File

@@ -0,0 +1,347 @@
package plugin_test
import (
"context"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/plugin"
)
func TestPlugin_Run(t *testing.T) {
type fields struct {
Name string
Repository string
Version string
Usage string
Description string
Platforms []plugin.Platform
GOOS string
GOARCH string
}
type args struct {
args []string
}
tests := []struct {
name string
fields fields
args args
wantErr string
}{
{
name: "happy path",
fields: fields{
Name: "test_plugin",
Repository: "github.com/aquasecurity/trivy-plugin-test",
Version: "0.1.0",
Usage: "test",
Description: "test",
Platforms: []plugin.Platform{
{
Selector: &plugin.Selector{
OS: "linux",
Arch: "amd64",
},
URI: "github.com/aquasecurity/trivy-plugin-test",
Bin: "test.sh",
},
},
GOOS: "linux",
GOARCH: "amd64",
},
},
{
name: "no selector",
fields: fields{
Name: "test_plugin",
Repository: "github.com/aquasecurity/trivy-plugin-test",
Version: "0.1.0",
Usage: "test",
Description: "test",
Platforms: []plugin.Platform{
{
URI: "github.com/aquasecurity/trivy-plugin-test",
Bin: "test.sh",
},
},
},
},
{
name: "no matched platform",
fields: fields{
Name: "test_plugin",
Repository: "github.com/aquasecurity/trivy-plugin-test",
Version: "0.1.0",
Usage: "test",
Description: "test",
Platforms: []plugin.Platform{
{
Selector: &plugin.Selector{
OS: "darwin",
Arch: "amd64",
},
URI: "github.com/aquasecurity/trivy-plugin-test",
Bin: "test.sh",
},
},
GOOS: "linux",
GOARCH: "amd64",
},
wantErr: "platform not found",
},
{
name: "no execution file",
fields: fields{
Name: "test_plugin",
Repository: "github.com/aquasecurity/trivy-plugin-test",
Version: "0.1.0",
Usage: "test",
Description: "test",
Platforms: []plugin.Platform{
{
Selector: &plugin.Selector{
OS: "linux",
Arch: "amd64",
},
URI: "github.com/aquasecurity/trivy-plugin-test",
Bin: "nonexistence.sh",
},
},
GOOS: "linux",
GOARCH: "amd64",
},
wantErr: "no such file or directory",
},
{
name: "plugin exec error",
fields: fields{
Name: "error_plugin",
Repository: "github.com/aquasecurity/trivy-plugin-error",
Version: "0.1.0",
Usage: "test",
Description: "test",
Platforms: []plugin.Platform{
{
Selector: &plugin.Selector{
OS: "linux",
Arch: "amd64",
},
URI: "github.com/aquasecurity/trivy-plugin-test",
Bin: "test.sh",
},
},
GOOS: "linux",
GOARCH: "amd64",
},
wantErr: "plugin exec: exit status 1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
os.Setenv("XDG_DATA_HOME", "testdata")
defer os.Unsetenv("XDG_DATA_HOME")
p := plugin.Plugin{
Name: tt.fields.Name,
Repository: tt.fields.Repository,
Version: tt.fields.Version,
Usage: tt.fields.Usage,
Description: tt.fields.Description,
Platforms: tt.fields.Platforms,
GOOS: tt.fields.GOOS,
GOARCH: tt.fields.GOARCH,
}
err := p.Run(context.Background(), tt.args.args)
if tt.wantErr != "" {
require.NotNil(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
}
assert.NoError(t, err)
})
}
}
func TestInstall(t *testing.T) {
tests := []struct {
name string
url string
want plugin.Plugin
wantFile string
wantErr string
}{
{
name: "happy path",
url: "testdata/test_plugin",
want: plugin.Plugin{
Name: "test_plugin",
Repository: "github.com/aquasecurity/trivy-plugin-test",
Version: "0.1.0",
Usage: "test",
Description: "test",
Platforms: []plugin.Platform{
{
Selector: &plugin.Selector{
OS: "linux",
Arch: "amd64",
},
URI: "./test.sh",
Bin: "./test.sh",
},
},
GOOS: "linux",
GOARCH: "amd64",
},
wantFile: ".trivy/plugins/test_plugin/test.sh",
},
{
name: "plugin not found",
url: "testdata/not_found",
want: plugin.Plugin{
Name: "test_plugin",
Repository: "github.com/aquasecurity/trivy-plugin-test",
Version: "0.1.0",
Usage: "test",
Description: "test",
Platforms: []plugin.Platform{
{
Selector: &plugin.Selector{
OS: "linux",
Arch: "amd64",
},
URI: "./test.sh",
Bin: "./test.sh",
},
},
GOOS: "linux",
GOARCH: "amd64",
},
wantErr: "no such file or directory",
},
{
name: "no plugin.yaml",
url: "testdata/no_yaml",
want: plugin.Plugin{
Name: "no_yaml",
Repository: "github.com/aquasecurity/trivy-plugin-test",
Version: "0.1.0",
Usage: "test",
Description: "test",
Platforms: []plugin.Platform{
{
Selector: &plugin.Selector{
OS: "linux",
Arch: "amd64",
},
URI: "./test.sh",
Bin: "./test.sh",
},
},
GOOS: "linux",
GOARCH: "amd64",
},
wantErr: "file open error",
},
}
log.InitLogger(false, true)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// The test plugin will be installed here
dst := t.TempDir()
os.Setenv("XDG_DATA_HOME", dst)
got, err := plugin.Install(context.Background(), tt.url, false)
if tt.wantErr != "" {
require.NotNil(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
assert.FileExists(t, filepath.Join(dst, tt.wantFile))
})
}
}
func TestUninstall(t *testing.T) {
pluginName := "test_plugin"
tempDir := t.TempDir()
pluginDir := filepath.Join(tempDir, ".trivy", "plugins", pluginName)
// Create the test plugin directory
err := os.MkdirAll(pluginDir, os.ModePerm)
require.NoError(t, err)
// Create the test file
err = os.WriteFile(filepath.Join(pluginDir, "test.sh"), []byte(`foo`), os.ModePerm)
require.NoError(t, err)
// Uninstall the plugin
err = plugin.Uninstall(pluginName)
assert.NoError(t, err)
assert.NoFileExists(t, pluginDir)
}
func TestLoadAll1(t *testing.T) {
tests := []struct {
name string
dir string
want []plugin.Plugin
wantErr string
}{
{
name: "happy path",
dir: "testdata",
want: []plugin.Plugin{
{
Name: "test_plugin",
Repository: "github.com/aquasecurity/trivy-plugin-test",
Version: "0.1.0",
Usage: "test",
Description: "test",
Platforms: []plugin.Platform{
{
Selector: &plugin.Selector{
OS: "linux",
Arch: "amd64",
},
URI: "./test.sh",
Bin: "./test.sh",
},
},
GOOS: "linux",
GOARCH: "amd64",
},
},
},
{
name: "sad path",
dir: "sad",
wantErr: "no such file or directory",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
os.Setenv("XDG_DATA_HOME", tt.dir)
defer os.Unsetenv("XDG_DATA_HOME")
got, err := plugin.LoadAll()
if tt.wantErr != "" {
require.NotNil(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}