diff --git a/cmd/notation/plugin/list.go b/cmd/notation/plugin/list.go index bfc3ebffe..d42b3aa84 100644 --- a/cmd/notation/plugin/list.go +++ b/cmd/notation/plugin/list.go @@ -16,7 +16,10 @@ package plugin import ( "errors" "fmt" + "io/fs" "os" + "runtime" + "syscall" "text/tabwriter" "github.com/notaryproject/notation-go/dir" @@ -68,7 +71,27 @@ func listPlugins(command *cobra.Command) error { } } fmt.Fprintf(tw, "%s\t%s\t%s\t%v\t%v\t\n", - n, metaData.Description, metaData.Version, metaData.Capabilities, err) + n, metaData.Description, metaData.Version, metaData.Capabilities, userFriendlyError(err)) } return tw.Flush() } + +// userFriendlyError optimizes the error message for the user. +func userFriendlyError(err error) error { + if err == nil { + return nil + } + var pathError *fs.PathError + if errors.As(err, &pathError) { + // for plugin does not exist + if errors.Is(pathError, fs.ErrNotExist) { + return fmt.Errorf("%w. Each plugin executable must be placed in the $PLUGIN_DIRECTORY/{plugin-name} directory, with the executable named as 'notation-{plugin-name}'", pathError) + } + + // for plugin is not executable + if pathError.Op == "fork/exec" && pathError.Err == syscall.Errno(8) { + return fmt.Errorf("%w. Please ensure that the plugin executable file is compatible with %s/%s", pathError, runtime.GOOS, runtime.GOARCH) + } + } + return err +} diff --git a/test/e2e/suite/plugin/list.go b/test/e2e/suite/plugin/list.go index fb6931553..3edd4c454 100644 --- a/test/e2e/suite/plugin/list.go +++ b/test/e2e/suite/plugin/list.go @@ -14,6 +14,9 @@ package plugin import ( + "os" + "runtime" + . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/onsi/ginkgo/v2" @@ -37,4 +40,41 @@ var _ = Describe("notation plugin list", func() { MatchKeyWords("ERROR", "") }) }) + + It("missing plugin binary", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + // create azure-kv plugin directory + pluginDir := vhost.AbsolutePath(NotationDirName, "plugins", "azure-kv") + if err := os.MkdirAll(pluginDir, os.ModePerm); err != nil { + Fail(err.Error()) + } + + notation.Exec("plugin", "list"). + MatchKeyWords("azure-kv"). + MatchKeyWords("no such file or directory") + }) + }) + + It("with invalid binary file", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + // create azure-kv plugin directory + pluginDir := vhost.AbsolutePath(NotationDirName, "plugins", "azure-kv") + if err := os.MkdirAll(pluginDir, os.ModePerm); err != nil { + Fail(err.Error()) + } + + // create invalid plugin binary + invalidPluginBinary := vhost.AbsolutePath(NotationDirName, "plugins", "azure-kv", "notation-azure-kv") + if runtime.GOOS == "windows" { + invalidPluginBinary += ".exe" + } + if err := os.WriteFile(invalidPluginBinary, []byte("invalid"), 0755); err != nil { + Fail(err.Error()) + } + + notation.Exec("plugin", "list"). + MatchKeyWords("azure-kv"). + MatchKeyWords("exec format error") + }) + }) })