Skip to content

Commit d6dc567

Browse files
feat(plugin): specify plugin version (#6683)
Signed-off-by: knqyf263 <[email protected]> Co-authored-by: DmitriyLewen <[email protected]>
1 parent a944f0e commit d6dc567

26 files changed

+368
-154
lines changed

docs/docs/plugin/developer-guide.md

+10
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,16 @@ The following rules will apply in deciding which platform to select:
130130
After determining platform, Trivy will download the execution file from `uri` and store it in the plugin cache.
131131
When the plugin is called via Trivy CLI, `bin` command will be executed.
132132

133+
#### Tagging plugin repositories
134+
If you are hosting your plugin in a Git repository, it is strongly recommended to tag your releases with a version number.
135+
By tagging your releases, Trivy can install specific versions of your plugin.
136+
137+
```bash
138+
$ trivy plugin install [email protected]
139+
```
140+
141+
When tagging versions, you must follow [the Semantic Versioning][semver] and prefix the tag with `v`, like `v1.2.3`.
142+
133143
#### Plugin arguments/flags
134144
The plugin is responsible for handling flags and arguments.
135145
Any arguments are passed to the plugin from the `trivy` command.

docs/docs/plugin/user-guide.md

+11
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ $ trivy plugin install referrer
4040

4141
This command will download the plugin and install it in the plugin cache.
4242

43+
44+
4345
Trivy adheres to the XDG specification, so the location depends on whether XDG_DATA_HOME is set.
4446
Trivy will now search XDG_DATA_HOME for the location of the Trivy plugins cache.
4547
The preference order is as follows:
@@ -56,6 +58,15 @@ $ trivy plugin install github.com/aquasecurity/trivy-plugin-kubectl
5658
$ trivy plugin install myplugin.tar.gz
5759
```
5860

61+
If the plugin's Git repository is [properly tagged](./developer-guide.md#tagging-plugin-repositories), you can specify the version to install like this:
62+
63+
```bash
64+
$ trivy plugin install [email protected]
65+
```
66+
67+
!!! note
68+
The leading `v` in the version is required. Also, the version must follow the [Semantic Versioning](https://semver.org/).
69+
5970
Under the hood Trivy leverages [go-getter][go-getter] to download plugins.
6071
This means the following protocols are supported for downloading plugins:
6172

docs/docs/references/configuration/cli/trivy_plugin_install.md

+13
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@ Install a plugin
66
trivy plugin install NAME | URL | FILE_PATH
77
```
88

9+
### Examples
10+
11+
```
12+
# Install a plugin from the plugin index
13+
$ trivy plugin install referrer
14+
15+
# Specify the version of the plugin to install
16+
$ trivy plugin install [email protected]
17+
18+
# Install a plugin from a URL
19+
$ trivy plugin install github.com/aquasecurity/trivy-plugin-referrer
20+
```
21+
922
### Options
1023

1124
```

pkg/commands/app.go

+11-3
Original file line numberDiff line numberDiff line change
@@ -724,9 +724,17 @@ func NewPluginCommand() *cobra.Command {
724724
}
725725
cmd.AddCommand(
726726
&cobra.Command{
727-
Use: "install NAME | URL | FILE_PATH",
728-
Aliases: []string{"i"},
729-
Short: "Install a plugin",
727+
Use: "install NAME | URL | FILE_PATH",
728+
Aliases: []string{"i"},
729+
Short: "Install a plugin",
730+
Example: ` # Install a plugin from the plugin index
731+
$ trivy plugin install referrer
732+
733+
# Specify the version of the plugin to install
734+
$ trivy plugin install [email protected]
735+
736+
# Install a plugin from a URL
737+
$ trivy plugin install github.com/aquasecurity/trivy-plugin-referrer`,
730738
SilenceErrors: true,
731739
SilenceUsage: true,
732740
DisableFlagsInUseLine: true,

pkg/fanal/artifact/repo/git_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ package repo
44

55
import (
66
"context"
7-
"github.com/aquasecurity/trivy/pkg/fanal/walker"
87
"net/http/httptest"
98
"testing"
109

@@ -16,6 +15,7 @@ import (
1615
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/secret"
1716
"github.com/aquasecurity/trivy/pkg/fanal/artifact"
1817
"github.com/aquasecurity/trivy/pkg/fanal/cache"
18+
"github.com/aquasecurity/trivy/pkg/fanal/walker"
1919
)
2020

2121
func setupGitServer() (*httptest.Server, error) {

pkg/plugin/index.go

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type Index struct {
2424
Version int `yaml:"version"`
2525
Plugins []struct {
2626
Name string `yaml:"name"`
27+
Version string `yaml:"version"`
2728
Maintainer string `yaml:"maintainer"`
2829
Summary string `yaml:"summary"`
2930
Repository string `yaml:"repository"`

pkg/plugin/index_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func TestManager_Search(t *testing.T) {
5151
want: `NAME DESCRIPTION MAINTAINER OUTPUT
5252
foo A foo plugin aquasecurity ✓
5353
bar A bar plugin aquasecurity
54-
test A test plugin aquasecurity
54+
test_plugin A test plugin aquasecurity
5555
`,
5656
},
5757
{

pkg/plugin/manager.go

+50-12
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"golang.org/x/xerrors"
1414
"gopkg.in/yaml.v3"
1515

16+
"github.com/aquasecurity/go-version/pkg/semver"
1617
"github.com/aquasecurity/trivy/pkg/downloader"
1718
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
1819
"github.com/aquasecurity/trivy/pkg/log"
@@ -30,14 +31,20 @@ var (
3031
type ManagerOption func(indexer *Manager)
3132

3233
func WithWriter(w io.Writer) ManagerOption {
33-
return func(indexer *Manager) {
34-
indexer.w = w
34+
return func(manager *Manager) {
35+
manager.w = w
36+
}
37+
}
38+
39+
func WithLogger(logger *log.Logger) ManagerOption {
40+
return func(manager *Manager) {
41+
manager.logger = logger
3542
}
3643
}
3744

3845
func WithIndexURL(indexURL string) ManagerOption {
39-
return func(indexer *Manager) {
40-
indexer.indexURL = indexURL
46+
return func(manager *Manager) {
47+
manager.indexURL = indexURL
4148
}
4249
}
4350

@@ -88,17 +95,18 @@ func Update(ctx context.Context) error { return defaultManager(
8895
func Search(ctx context.Context, keyword string) error { return defaultManager().Search(ctx, keyword) }
8996

9097
// Install installs a plugin
91-
func (m *Manager) Install(ctx context.Context, name string, opts Options) (Plugin, error) {
92-
src := m.tryIndex(ctx, name)
98+
func (m *Manager) Install(ctx context.Context, arg string, opts Options) (Plugin, error) {
99+
input := m.parseArg(ctx, arg)
100+
input.name = m.tryIndex(ctx, input.name)
93101

94102
// If the plugin is already installed, it skips installing the plugin.
95-
if p, installed := m.isInstalled(ctx, src); installed {
103+
if p, installed := m.isInstalled(ctx, input.name, input.version); installed {
96104
m.logger.InfoContext(ctx, "The plugin is already installed", log.String("name", p.Name))
97105
return p, nil
98106
}
99107

100-
m.logger.InfoContext(ctx, "Installing the plugin...", log.String("src", src))
101-
return m.install(ctx, src, opts)
108+
m.logger.InfoContext(ctx, "Installing the plugin...", log.String("src", input.name))
109+
return m.install(ctx, input.String(), opts)
102110
}
103111

104112
func (m *Manager) install(ctx context.Context, src string, opts Options) (Plugin, error) {
@@ -129,7 +137,8 @@ func (m *Manager) install(ctx context.Context, src string, opts Options) (Plugin
129137
return Plugin{}, xerrors.Errorf("yaml encode error: %w", err)
130138
}
131139

132-
m.logger.InfoContext(ctx, "Plugin successfully installed", log.String("name", plugin.Name))
140+
m.logger.InfoContext(ctx, "Plugin successfully installed",
141+
log.String("name", plugin.Name), log.String("version", plugin.Version))
133142

134143
return plugin, nil
135144
}
@@ -340,16 +349,45 @@ func (m *Manager) loadMetadata(dir string) (Plugin, error) {
340349
return plugin, nil
341350
}
342351

343-
func (m *Manager) isInstalled(ctx context.Context, url string) (Plugin, bool) {
352+
func (m *Manager) isInstalled(ctx context.Context, url, version string) (Plugin, bool) {
344353
installedPlugins, err := m.LoadAll(ctx)
345354
if err != nil {
346355
return Plugin{}, false
347356
}
348357

349358
for _, plugin := range installedPlugins {
350-
if plugin.Repository == url {
359+
if plugin.Repository == url && (version == "" || plugin.Version == version) {
351360
return plugin, true
352361
}
353362
}
354363
return Plugin{}, false
355364
}
365+
366+
// Input represents the user-specified Input.
367+
type Input struct {
368+
name string
369+
version string
370+
}
371+
372+
func (i *Input) String() string {
373+
if i.version != "" {
374+
// cf. https://github.com/hashicorp/go-getter/blob/268c11cae8cf0d9374783e06572679796abe9ce9/README.md#git-git
375+
return i.name + "?ref=v" + i.version
376+
}
377+
return i.name
378+
}
379+
380+
func (m *Manager) parseArg(ctx context.Context, arg string) Input {
381+
before, after, found := strings.Cut(arg, "@v")
382+
if !found {
383+
return Input{name: arg}
384+
} else if _, err := semver.Parse(after); err != nil {
385+
m.logger.DebugContext(ctx, "Unable to identify the plugin version", log.String("name", arg), log.Err(err))
386+
return Input{name: arg}
387+
}
388+
// cf. https://github.com/hashicorp/go-getter/blob/268c11cae8cf0d9374783e06572679796abe9ce9/README.md#git-git
389+
return Input{
390+
name: before,
391+
version: after,
392+
}
393+
}

0 commit comments

Comments
 (0)