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
190 changes: 190 additions & 0 deletions cmd/notation/plugin.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package main

import (
"errors"
"fmt"
"os"
"path/filepath"
"text/tabwriter"
"strings"

"github.com/notaryproject/notation-go/dir"
"github.com/notaryproject/notation-go/plugin"
"github.com/notaryproject/notation-go/plugin/proto"
"github.com/notaryproject/notation/cmd/notation/internal/cmdutil"
"github.com/notaryproject/notation/internal/osutil"
"github.com/spf13/cobra"
"golang.org/x/mod/semver"
)

func pluginCommand() *cobra.Command {
Expand All @@ -17,6 +23,8 @@ func pluginCommand() *cobra.Command {
Short: "Manage plugins",
}
cmd.AddCommand(pluginListCommand())
cmd.AddCommand(pluginInstallCommand())
cmd.AddCommand(pluginRemoveCommand())
return cmd
}

Expand All @@ -36,6 +44,56 @@ Example - List installed Notation plugins:
}
}

func pluginInstallCommand() *cobra.Command {
var force bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make a struct instead of a single variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any other config settings we'd like to add to the struct? If so, it might be worthwhile to look into adding viper for configuration.


cmd := &cobra.Command{
Use: "install [flags] <plugin package>",
Aliases: []string{"add"},
Short: "Install a plugin",
Long: `Install a plugin

Example - Install a Notation plugin:
notation plugin install <path to plugin executable>
`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("missing plugin package")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return installPlugin(cmd, args, force)
},
}

cmd.Flags().BoolVarP(&force, "force", "f", false, "overwrite existing plugin files without prompting")

return cmd
}

func pluginRemoveCommand() *cobra.Command {
return &cobra.Command{
Use: "remove [flags] <plugin>",
Aliases: []string{"rm", "uninstall", "delete"},
Short: "Remove a plugin",
Long: `Remove a plugin

Example - Remove a Notation plugin:
notation plugin remove <plugin>
`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("missing plugin name")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return removePlugin(cmd, args)
},
}
}

func listPlugins(command *cobra.Command) error {
mgr := plugin.NewCLIManager(dir.PluginFS())
pluginNames, err := mgr.List(command.Context())
Expand All @@ -62,3 +120,135 @@ func listPlugins(command *cobra.Command) error {
}
return tw.Flush()
}

func installPlugin(command *cobra.Command, args []string, force bool) error {

pluginSrcPath := args[0]
pluginBinary := filepath.Base(pluginSrcPath)
pluginName := splitPluginName(pluginBinary)

// get plugin metadata
pl, err := plugin.NewCLIPlugin(command.Context(), pluginName, pluginSrcPath)
newPluginMetadata := &proto.GetMetadataResponse{}
resp, err := pl.GetMetadata(command.Context(), &proto.GetMetadataRequest{})
if err == nil {
newPluginMetadata = resp
}

// get plugin directory
pluginDir, err := dir.PluginFS().SysPath(pluginName)
if err != nil {
return err
}
//pluginDestPath := pluginDir + "\\" + pluginBinary

pluginExists, err := exists(pluginDir+"/"+pluginBinary)
if err != nil {
return err
}

if pluginExists {
// if force == true, overwrite plugin
if force {
fmt.Printf("Overwriting plugin %s in directory %s\n", pluginBinary, pluginDir)
if _, err := osutil.CopyToDir(pluginSrcPath,pluginDir); err != nil {
return err
}
return nil
}
// get existing plugin metadata
mgr := plugin.NewCLIManager(dir.PluginFS())
currentPlugin, err := mgr.Get(command.Context(), pluginName)

currentPluginMetadata := &proto.GetMetadataResponse{}
if err == nil {
resp, err := currentPlugin.GetMetadata(command.Context(), &proto.GetMetadataRequest{})
if err == nil {
currentPluginMetadata = resp
}
}

// Compare plugin versions
compare := semver.Compare("v"+newPluginMetadata.Version, "v"+currentPluginMetadata.Version)

// copy plugin, if new plugin version is greater than current plugin version
if compare == 1 {
prompt := fmt.Sprintf("Are you sure you want to overwrite plugin %s_v%s with v%s?", pluginName, currentPluginMetadata.Version, newPluginMetadata.Version)
confimred, err := cmdutil.AskForConfirmation(os.Stdin, prompt, false)
if err != nil {
return err
}

if !confimred {
return nil
}

fmt.Printf("Copying plugin %s to directory %s...\n", pluginName, pluginDir)
if _, err := osutil.CopyToDir(pluginSrcPath,pluginDir); err != nil {
return err
}
}

// do not copy plugin, if new plugin version is less than or equal to current plugin version
if compare == -1 || compare == 0 {
fmt.Println("Skipping plugin installation. The current version is equal to or higher than the new version.\nTo overwrite the plugin, use the --force flag.")
}
}

if !pluginExists {
fmt.Printf("Copying plugin %s to directory %s...\n", pluginName, pluginDir)
_, err :=osutil.CopyToDir(pluginSrcPath,pluginDir)
if err != nil {
return err
}
}

return nil
}

func removePlugin(command *cobra.Command, args []string) error {

pluginName := args[0]

// get plugin directory
pluginDir, err := dir.PluginFS().SysPath(pluginName)
if err != nil {
return err
}

// Check if plugin directory exists
pluginExists, err := exists(pluginDir)
if err != nil {
return err
}

if !pluginExists {
return errors.New("plugin does not exist")
}

// remove plugin directory
return os.RemoveAll(pluginDir)
}

func splitPluginName (p string) string {
parts := strings.Split(p, "-")
result := strings.Join(parts[1:3], "-")
ext := filepath.Ext(p)

if ext != "" {
result = strings.TrimSuffix(result, ".exe")
}

return result
}

func exists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5
golang.org/x/mod v0.8.0
oras.land/oras-go/v2 v2.0.2
golang.org/x/term v0.8.0
oras.land/oras-go/v2 v2.2.0
)

require (
Expand Down