From 5b0589e4ad3c21c36123e24b2d150abf4d822aa8 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 19 Oct 2023 16:50:29 +0800 Subject: [PATCH 01/24] added zip suport Signed-off-by: Patrick Zheng --- cmd/notation/main.go | 3 +- cmd/notation/plugin/cmd.go | 30 +++ cmd/notation/plugin/install.go | 221 +++++++++++++++++++++ cmd/notation/{plugin.go => plugin/list.go} | 11 +- internal/osutil/file.go | 68 +++++++ 5 files changed, 322 insertions(+), 11 deletions(-) create mode 100644 cmd/notation/plugin/cmd.go create mode 100644 cmd/notation/plugin/install.go rename cmd/notation/{plugin.go => plugin/list.go} (91%) diff --git a/cmd/notation/main.go b/cmd/notation/main.go index feb103aa3..f4341da28 100644 --- a/cmd/notation/main.go +++ b/cmd/notation/main.go @@ -17,6 +17,7 @@ import ( "os" "github.com/notaryproject/notation/cmd/notation/cert" + "github.com/notaryproject/notation/cmd/notation/plugin" "github.com/notaryproject/notation/cmd/notation/policy" "github.com/spf13/cobra" ) @@ -40,7 +41,7 @@ func main() { cert.Cmd(), policy.Cmd(), keyCommand(), - pluginCommand(), + plugin.Cmd(), loginCommand(nil), logoutCommand(nil), versionCommand(), diff --git a/cmd/notation/plugin/cmd.go b/cmd/notation/plugin/cmd.go new file mode 100644 index 000000000..17f3d3150 --- /dev/null +++ b/cmd/notation/plugin/cmd.go @@ -0,0 +1,30 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package plugin + +import "github.com/spf13/cobra" + +func Cmd() *cobra.Command { + command := &cobra.Command{ + Use: "plugin", + Short: "Manage plugins", + } + + command.AddCommand( + pluginListCommand(), + pluginInstallCommand(nil), + ) + + return command +} diff --git a/cmd/notation/plugin/install.go b/cmd/notation/plugin/install.go new file mode 100644 index 000000000..5729da746 --- /dev/null +++ b/cmd/notation/plugin/install.go @@ -0,0 +1,221 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package plugin + +import ( + "archive/zip" + "context" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/notaryproject/notation-go/dir" + "github.com/notaryproject/notation-go/plugin" + "github.com/notaryproject/notation-go/plugin/proto" + "github.com/notaryproject/notation/internal/osutil" + "github.com/opencontainers/go-digest" + "github.com/spf13/cobra" +) + +const ( + TypeZip = "application/zip" + TypeGzip = "application/x-gzip" +) + +type pluginInstallOpts struct { + inputPath string + inputCheckSum string + forced bool +} + +func pluginInstallCommand(opts *pluginInstallOpts) *cobra.Command { + if opts == nil { + opts = &pluginInstallOpts{} + } + command := &cobra.Command{ + Use: "install [flags] ", + Short: "Install plugin", + Long: `Install a Notation plugin + +Example - Install plugin from file system: + notation plugin install myPlugin.zip +`, + RunE: func(cmd *cobra.Command, args []string) error { + return installPlugin(cmd, opts) + }, + } + command.Flags().StringVar(&opts.inputPath, "file", "", "file path of the plugin to be installed, only supports tar.gz or zip format") + command.Flags().StringVar(&opts.inputCheckSum, "checksum", "", "if set, must match the SHA256 of the plugin tar.gz/zip to be installed") + command.Flags().BoolVar(&opts.forced, "forced", false, "do not force to install and overwrite the plugin") + command.MarkFlagRequired("file") + return command +} + +func installPlugin(command *cobra.Command, opts *pluginInstallOpts) error { + inputPath := opts.inputPath + // sanity check + iputFileStat, err := os.Stat(inputPath) + if err != nil { + return fmt.Errorf("failed to install the plugin, %w", err) + } + if !iputFileStat.Mode().IsRegular() { + return fmt.Errorf("failed to install the plugin, %s is not a regular file", inputPath) + } + // checkSum check + if opts.inputCheckSum != "" { + if err := validateCheckSum(inputPath, opts.inputCheckSum); err != nil { + return fmt.Errorf("failed to install the plugin, %w", err) + } + } + // install the plugin based on file type + fileType, err := osutil.DetectFileType(inputPath) + if err != nil { + return fmt.Errorf("failed to install the plugin, %w", err) + } + switch fileType { + case TypeZip: + if err := installPluginFromZip(command.Context(), inputPath, opts.forced); err != nil { + return fmt.Errorf("failed to install the plugin, %w", err) + } + default: + return errors.New("failed to install the plugin, invalid file type. Only support tar.gz and zip") + } + return nil +} + +// validateCheckSum returns nil if SHA256 of file at path equals to checkSum. +func validateCheckSum(path string, checkSum string) error { + r, err := os.Open(path) + if err != nil { + return err + } + defer r.Close() + dgst, err := digest.FromReader(r) + if err != nil { + return err + } + if dgst.Encoded() != checkSum { + return errors.New("plugin checkSum does not match user input") + } + return nil +} + +// installPluginFromZip extracts a plugin zip file, validate and +// install the plugin +func installPluginFromZip(ctx context.Context, zipPath string, forced bool) error { + archive, err := zip.OpenReader(zipPath) + if err != nil { + return err + } + defer archive.Close() + tmpDir, err := os.MkdirTemp(".", "unzipTmpDir") + if err != nil { + return err + } + defer os.RemoveAll(tmpDir) + for _, f := range archive.File { + fileMode := f.Mode() + // only consider regular executable files in the zip + if fileMode.IsRegular() && osutil.IsOwnerExecutalbeFile(fileMode) { + pluginName, err := getPluginNameFromExecutableFileName(f.Name) + // if error is not nil, continue to next file + // if error is nil, we find the plugin executable file + if err == nil { + // check plugin existence + if !forced { + existed, err := checkPluginExistence(ctx, pluginName) + if err != nil { + return fmt.Errorf("failed to check plugin existence, %w", err) + } + if existed { + return fmt.Errorf("plugin %s already existed", pluginName) + } + } + // extract to tmp dir + tmpFilePath := filepath.Join(tmpDir, filepath.Base(f.Name)) + pluginFile, err := os.OpenFile(tmpFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + fileInArchive, err := f.Open() + if err != nil { + return err + } + defer fileInArchive.Close() + if _, err := io.Copy(pluginFile, fileInArchive); err != nil { + return err + } + if err := pluginFile.Close(); err != nil { + return err + } + // validate plugin metadata + if err := validatePluginMetadata(ctx, pluginName, tmpFilePath); err != nil { + return err + } + // install plugin + pluginPath, err := dir.PluginFS().SysPath(pluginName) + if err != nil { + return nil + } + _, err = osutil.CopyToDir(tmpFilePath, pluginPath) + if err != nil { + return err + } + fmt.Printf("Succussefully installed plugin %s\n", pluginName) + return nil + } + } + } + return errors.New("plugin executable file not found in zip") +} + +// getPluginNameFromExecutableFileName gets plugin name from plugin executable +// file name based on spec: https://github.com/notaryproject/specifications/blob/main/specs/plugin-extensibility.md#installation +func getPluginNameFromExecutableFileName(execFileName string) (string, error) { + fileName := osutil.FileNameWithoutExtension(execFileName) + _, pluginName, found := strings.Cut(fileName, "-") + if !found || !strings.HasPrefix(fileName, proto.Prefix) { + return "", fmt.Errorf("invalid plugin executable file name. file name requires format notation-{plugin-name}, got %s", fileName) + } + return pluginName, nil +} + +// checkPluginExistence returns true if a plugin already exists +func checkPluginExistence(ctx context.Context, pluginName string) (bool, error) { + mgr := plugin.NewCLIManager(dir.PluginFS()) + _, err := mgr.Get(ctx, pluginName) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return false, nil + } + return false, err + } + return true, nil +} + +// validatePluginMetadata validates plugin metadata before installation +func validatePluginMetadata(ctx context.Context, pluginName, path string) error { + plugin, err := plugin.NewCLIPlugin(ctx, pluginName, path) + if err != nil { + return err + } + _, err = plugin.GetMetadata(ctx, &proto.GetMetadataRequest{}) + if err != nil { + return err + } + return nil +} diff --git a/cmd/notation/plugin.go b/cmd/notation/plugin/list.go similarity index 91% rename from cmd/notation/plugin.go rename to cmd/notation/plugin/list.go index be2c2f3ad..a6ae1a504 100644 --- a/cmd/notation/plugin.go +++ b/cmd/notation/plugin/list.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package main +package plugin import ( "fmt" @@ -24,15 +24,6 @@ import ( "github.com/spf13/cobra" ) -func pluginCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "plugin", - Short: "Manage plugins", - } - cmd.AddCommand(pluginListCommand()) - return cmd -} - func pluginListCommand() *cobra.Command { return &cobra.Command{ Use: "list [flags]", diff --git a/internal/osutil/file.go b/internal/osutil/file.go index 70fdfdf8c..0c407f0ae 100644 --- a/internal/osutil/file.go +++ b/internal/osutil/file.go @@ -17,8 +17,10 @@ import ( "fmt" "io" "io/fs" + "net/http" "os" "path/filepath" + "strings" ) // WriteFile writes to a path with all parent directories created. @@ -94,3 +96,69 @@ func IsRegularFile(path string) (bool, error) { return fileStat.Mode().IsRegular(), nil } + +// DetectFileType returns a file's content type given path +func DetectFileType(path string) (string, error) { + f, err := os.ReadFile(path) + if err != nil { + return "", err + } + return http.DetectContentType(f), nil +} + +// ExtractTarGz decompress and untar a tar.gz file to destination directory, +// all files in the tar.gz must be regular files. +// func ExtractTarGz(tarGzPath string, dstDir string) error { +// r, err := os.Open(tarGzPath) +// if err != nil { +// return err +// } +// defer r.Close() +// uncompressedStream, err := gzip.NewReader(r) +// if err != nil { +// return err +// } +// defer uncompressedStream.Close() +// tarReader := tar.NewReader(uncompressedStream) +// if err := os.MkdirAll(dstDir, 0700); err != nil { +// return err +// } +// for { +// header, err := tarReader.Next() +// if err != nil { +// if err == io.EOF { +// break +// } +// return err +// } +// switch header.Typeflag { +// case tar.TypeReg: +// filePath := filepath.Join(dstDir, header.Name) +// dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, header.FileInfo().Mode()) +// if err != nil { +// return err +// } +// defer dstFile.Close() +// if _, err := io.Copy(dstFile, tarReader); err != nil { +// return err +// } +// default: +// return errors.New("regular file required") +// } +// } +// return nil +// } + +// FileNameWithoutExtension returns the file name without extension. +// For example, +// when input is xyz.exe, output is xyz +// when input is xyz.tar.gz, output is xyz.tar +func FileNameWithoutExtension(inputName string) string { + fileName := filepath.Base(inputName) + return strings.TrimSuffix(fileName, filepath.Ext(fileName)) +} + +// IsOwnerExecutalbeFile checks whether file is owner executable +func IsOwnerExecutalbeFile(mode fs.FileMode) bool { + return mode&0100 != 0 +} From 2c4671a4548e5d79ba8d777324088b95adec6324 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 20 Oct 2023 11:39:03 +0800 Subject: [PATCH 02/24] added tar.gz support Signed-off-by: Patrick Zheng --- cmd/notation/plugin/install.go | 93 +++++++++++++++++++++++++++++++--- internal/osutil/file.go | 43 ---------------- 2 files changed, 87 insertions(+), 49 deletions(-) diff --git a/cmd/notation/plugin/install.go b/cmd/notation/plugin/install.go index 5729da746..4337e0071 100644 --- a/cmd/notation/plugin/install.go +++ b/cmd/notation/plugin/install.go @@ -14,7 +14,9 @@ package plugin import ( + "archive/tar" "archive/zip" + "compress/gzip" "context" "errors" "fmt" @@ -91,6 +93,10 @@ func installPlugin(command *cobra.Command, opts *pluginInstallOpts) error { if err := installPluginFromZip(command.Context(), inputPath, opts.forced); err != nil { return fmt.Errorf("failed to install the plugin, %w", err) } + case TypeGzip: + if err := installPluginFromTarGz(command.Context(), inputPath, opts.forced); err != nil { + return fmt.Errorf("failed to install the plugin, %w", err) + } default: return errors.New("failed to install the plugin, invalid file type. Only support tar.gz and zip") } @@ -114,8 +120,8 @@ func validateCheckSum(path string, checkSum string) error { return nil } -// installPluginFromZip extracts a plugin zip file, validate and -// install the plugin +// installPluginFromZip extracts a plugin zip file, validates and +// installs the plugin func installPluginFromZip(ctx context.Context, zipPath string, forced bool) error { archive, err := zip.OpenReader(zipPath) if err != nil { @@ -131,8 +137,7 @@ func installPluginFromZip(ctx context.Context, zipPath string, forced bool) erro fileMode := f.Mode() // only consider regular executable files in the zip if fileMode.IsRegular() && osutil.IsOwnerExecutalbeFile(fileMode) { - pluginName, err := getPluginNameFromExecutableFileName(f.Name) - // if error is not nil, continue to next file + pluginName, err := extractPluginNameFromExecutableFileName(f.Name) // if error is nil, we find the plugin executable file if err == nil { // check plugin existence @@ -183,9 +188,85 @@ func installPluginFromZip(ctx context.Context, zipPath string, forced bool) erro return errors.New("plugin executable file not found in zip") } -// getPluginNameFromExecutableFileName gets plugin name from plugin executable +// installPluginFromTarGz extracts and untar a plugin tar.gz file, validates and +// installs the plugin +func installPluginFromTarGz(ctx context.Context, tarGzPath string, forced bool) error { + r, err := os.Open(tarGzPath) + if err != nil { + return err + } + defer r.Close() + decompressedStream, err := gzip.NewReader(r) + if err != nil { + return err + } + defer decompressedStream.Close() + tarReader := tar.NewReader(decompressedStream) + tmpDir, err := os.MkdirTemp(".", "untarGzTmpDir") + if err != nil { + return err + } + defer os.RemoveAll(tmpDir) + for { + header, err := tarReader.Next() + if err != nil { + if err == io.EOF { + break + } + return err + } + fileMode := header.FileInfo().Mode() + // only consider regular executable files + if fileMode.IsRegular() && osutil.IsOwnerExecutalbeFile(fileMode) { + pluginName, err := extractPluginNameFromExecutableFileName(header.Name) + // if error is nil, we find the plugin executable file + if err == nil { + // check plugin existence + if !forced { + existed, err := checkPluginExistence(ctx, pluginName) + if err != nil { + return fmt.Errorf("failed to check plugin existence, %w", err) + } + if existed { + return fmt.Errorf("plugin %s already existed", pluginName) + } + } + // extract to tmp dir + tmpFilePath := filepath.Join(tmpDir, header.Name) + pluginFile, err := os.OpenFile(tmpFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, header.FileInfo().Mode()) + if err != nil { + return err + } + if _, err := io.Copy(pluginFile, tarReader); err != nil { + return err + } + if err := pluginFile.Close(); err != nil { + return err + } + // validate plugin metadata + if err := validatePluginMetadata(ctx, pluginName, tmpFilePath); err != nil { + return err + } + // install plugin + pluginPath, err := dir.PluginFS().SysPath(pluginName) + if err != nil { + return nil + } + _, err = osutil.CopyToDir(tmpFilePath, pluginPath) + if err != nil { + return err + } + fmt.Printf("Succussefully installed plugin %s\n", pluginName) + return nil + } + } + } + return nil +} + +// extractPluginNameFromExecutableFileName gets plugin name from plugin executable // file name based on spec: https://github.com/notaryproject/specifications/blob/main/specs/plugin-extensibility.md#installation -func getPluginNameFromExecutableFileName(execFileName string) (string, error) { +func extractPluginNameFromExecutableFileName(execFileName string) (string, error) { fileName := osutil.FileNameWithoutExtension(execFileName) _, pluginName, found := strings.Cut(fileName, "-") if !found || !strings.HasPrefix(fileName, proto.Prefix) { diff --git a/internal/osutil/file.go b/internal/osutil/file.go index 0c407f0ae..c75915e7e 100644 --- a/internal/osutil/file.go +++ b/internal/osutil/file.go @@ -106,49 +106,6 @@ func DetectFileType(path string) (string, error) { return http.DetectContentType(f), nil } -// ExtractTarGz decompress and untar a tar.gz file to destination directory, -// all files in the tar.gz must be regular files. -// func ExtractTarGz(tarGzPath string, dstDir string) error { -// r, err := os.Open(tarGzPath) -// if err != nil { -// return err -// } -// defer r.Close() -// uncompressedStream, err := gzip.NewReader(r) -// if err != nil { -// return err -// } -// defer uncompressedStream.Close() -// tarReader := tar.NewReader(uncompressedStream) -// if err := os.MkdirAll(dstDir, 0700); err != nil { -// return err -// } -// for { -// header, err := tarReader.Next() -// if err != nil { -// if err == io.EOF { -// break -// } -// return err -// } -// switch header.Typeflag { -// case tar.TypeReg: -// filePath := filepath.Join(dstDir, header.Name) -// dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, header.FileInfo().Mode()) -// if err != nil { -// return err -// } -// defer dstFile.Close() -// if _, err := io.Copy(dstFile, tarReader); err != nil { -// return err -// } -// default: -// return errors.New("regular file required") -// } -// } -// return nil -// } - // FileNameWithoutExtension returns the file name without extension. // For example, // when input is xyz.exe, output is xyz From 13ca7739d0ae053d7da46a59c507d25568b79805 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Mon, 23 Oct 2023 15:18:33 +0800 Subject: [PATCH 03/24] updated Signed-off-by: Patrick Zheng --- cmd/notation/internal/errors/errors.go | 13 ++ cmd/notation/plugin/install.go | 238 ++++++++++++------------- internal/osutil/file.go | 8 +- 3 files changed, 131 insertions(+), 128 deletions(-) diff --git a/cmd/notation/internal/errors/errors.go b/cmd/notation/internal/errors/errors.go index bb06e6f6f..631e4107e 100644 --- a/cmd/notation/internal/errors/errors.go +++ b/cmd/notation/internal/errors/errors.go @@ -50,3 +50,16 @@ type ErrorExceedMaxSignatures struct { func (e ErrorExceedMaxSignatures) Error() string { return fmt.Sprintf("exceeded configured limit of max signatures %d to examine", e.MaxSignatures) } + +// ErrorInvalidPluginName is used when a plugin executable file name does not +// follow the spec. +type ErrorInvalidPluginName struct { + Msg string +} + +func (e ErrorInvalidPluginName) Error() string { + if e.Msg != "" { + return e.Msg + } + return "invalid plugin file name" +} diff --git a/cmd/notation/plugin/install.go b/cmd/notation/plugin/install.go index 4337e0071..524072d45 100644 --- a/cmd/notation/plugin/install.go +++ b/cmd/notation/plugin/install.go @@ -21,13 +21,17 @@ import ( "errors" "fmt" "io" + "io/fs" "os" "path/filepath" "strings" "github.com/notaryproject/notation-go/dir" + "github.com/notaryproject/notation-go/log" "github.com/notaryproject/notation-go/plugin" "github.com/notaryproject/notation-go/plugin/proto" + notationerrors "github.com/notaryproject/notation/cmd/notation/internal/errors" + "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/osutil" "github.com/opencontainers/go-digest" "github.com/spf13/cobra" @@ -39,9 +43,10 @@ const ( ) type pluginInstallOpts struct { + cmd.LoggingFlagOpts inputPath string inputCheckSum string - forced bool + force bool } func pluginInstallCommand(opts *pluginInstallOpts) *cobra.Command { @@ -54,34 +59,36 @@ func pluginInstallCommand(opts *pluginInstallOpts) *cobra.Command { Long: `Install a Notation plugin Example - Install plugin from file system: - notation plugin install myPlugin.zip + notation plugin install --file myPlugin.tar.gz --checksum 123abcd `, RunE: func(cmd *cobra.Command, args []string) error { return installPlugin(cmd, opts) }, } - command.Flags().StringVar(&opts.inputPath, "file", "", "file path of the plugin to be installed, only supports tar.gz or zip format") + opts.LoggingFlagOpts.ApplyFlags(command.Flags()) + command.Flags().StringVar(&opts.inputPath, "file", "", "file path of the plugin to be installed, only supports tar.gz and zip format") command.Flags().StringVar(&opts.inputCheckSum, "checksum", "", "if set, must match the SHA256 of the plugin tar.gz/zip to be installed") - command.Flags().BoolVar(&opts.forced, "forced", false, "do not force to install and overwrite the plugin") + command.Flags().BoolVar(&opts.force, "force", false, "force to install and overwrite the plugin") command.MarkFlagRequired("file") + command.MarkFlagRequired("checksum") return command } func installPlugin(command *cobra.Command, opts *pluginInstallOpts) error { + // set log level + ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) inputPath := opts.inputPath // sanity check - iputFileStat, err := os.Stat(inputPath) + inputFileStat, err := os.Stat(inputPath) if err != nil { return fmt.Errorf("failed to install the plugin, %w", err) } - if !iputFileStat.Mode().IsRegular() { + if !inputFileStat.Mode().IsRegular() { return fmt.Errorf("failed to install the plugin, %s is not a regular file", inputPath) } // checkSum check - if opts.inputCheckSum != "" { - if err := validateCheckSum(inputPath, opts.inputCheckSum); err != nil { - return fmt.Errorf("failed to install the plugin, %w", err) - } + if err := validateCheckSum(inputPath, opts.inputCheckSum); err != nil { + return fmt.Errorf("failed to install the plugin, %w", err) } // install the plugin based on file type fileType, err := osutil.DetectFileType(inputPath) @@ -90,11 +97,11 @@ func installPlugin(command *cobra.Command, opts *pluginInstallOpts) error { } switch fileType { case TypeZip: - if err := installPluginFromZip(command.Context(), inputPath, opts.forced); err != nil { + if err := installPluginFromZip(ctx, inputPath, opts.force); err != nil { return fmt.Errorf("failed to install the plugin, %w", err) } case TypeGzip: - if err := installPluginFromTarGz(command.Context(), inputPath, opts.forced); err != nil { + if err := installPluginFromTarGz(ctx, inputPath, opts.force); err != nil { return fmt.Errorf("failed to install the plugin, %w", err) } default: @@ -114,83 +121,46 @@ func validateCheckSum(path string, checkSum string) error { if err != nil { return err } - if dgst.Encoded() != checkSum { - return errors.New("plugin checkSum does not match user input") + enc := dgst.Encoded() + if enc != checkSum { + return fmt.Errorf("plugin checkSum does not match user input. User input is %s, got %s", checkSum, enc) } return nil } // installPluginFromZip extracts a plugin zip file, validates and // installs the plugin -func installPluginFromZip(ctx context.Context, zipPath string, forced bool) error { +func installPluginFromZip(ctx context.Context, zipPath string, force bool) error { + logger := log.GetLogger(ctx) archive, err := zip.OpenReader(zipPath) if err != nil { return err } defer archive.Close() - tmpDir, err := os.MkdirTemp(".", "unzipTmpDir") - if err != nil { - return err - } - defer os.RemoveAll(tmpDir) for _, f := range archive.File { - fileMode := f.Mode() + fmode := f.Mode() // only consider regular executable files in the zip - if fileMode.IsRegular() && osutil.IsOwnerExecutalbeFile(fileMode) { - pluginName, err := extractPluginNameFromExecutableFileName(f.Name) - // if error is nil, we find the plugin executable file - if err == nil { - // check plugin existence - if !forced { - existed, err := checkPluginExistence(ctx, pluginName) - if err != nil { - return fmt.Errorf("failed to check plugin existence, %w", err) - } - if existed { - return fmt.Errorf("plugin %s already existed", pluginName) - } - } - // extract to tmp dir - tmpFilePath := filepath.Join(tmpDir, filepath.Base(f.Name)) - pluginFile, err := os.OpenFile(tmpFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) - if err != nil { - return err - } - fileInArchive, err := f.Open() - if err != nil { - return err - } - defer fileInArchive.Close() - if _, err := io.Copy(pluginFile, fileInArchive); err != nil { - return err - } - if err := pluginFile.Close(); err != nil { - return err - } - // validate plugin metadata - if err := validatePluginMetadata(ctx, pluginName, tmpFilePath); err != nil { - return err - } - // install plugin - pluginPath, err := dir.PluginFS().SysPath(pluginName) - if err != nil { - return nil - } - _, err = osutil.CopyToDir(tmpFilePath, pluginPath) - if err != nil { - return err - } - fmt.Printf("Succussefully installed plugin %s\n", pluginName) - return nil + if fmode.IsRegular() && osutil.IsOwnerExecutalbeFile(fmode) { + fileInArchive, err := f.Open() + if err != nil { + return err } + defer fileInArchive.Close() + err = installPluginExecutable(ctx, f.Name, fileInArchive, fmode, force) + if errors.As(err, ¬ationerrors.ErrorInvalidPluginName{}) { + logger.Warnln(err) + continue + } + return err } } - return errors.New("plugin executable file not found in zip") + return errors.New("valid plugin executable file not found in zip") } // installPluginFromTarGz extracts and untar a plugin tar.gz file, validates and // installs the plugin -func installPluginFromTarGz(ctx context.Context, tarGzPath string, forced bool) error { +func installPluginFromTarGz(ctx context.Context, tarGzPath string, force bool) error { + logger := log.GetLogger(ctx) r, err := os.Open(tarGzPath) if err != nil { return err @@ -202,11 +172,6 @@ func installPluginFromTarGz(ctx context.Context, tarGzPath string, forced bool) } defer decompressedStream.Close() tarReader := tar.NewReader(decompressedStream) - tmpDir, err := os.MkdirTemp(".", "untarGzTmpDir") - if err != nil { - return err - } - defer os.RemoveAll(tmpDir) for { header, err := tarReader.Next() if err != nil { @@ -215,62 +180,86 @@ func installPluginFromTarGz(ctx context.Context, tarGzPath string, forced bool) } return err } - fileMode := header.FileInfo().Mode() + fmode := header.FileInfo().Mode() // only consider regular executable files - if fileMode.IsRegular() && osutil.IsOwnerExecutalbeFile(fileMode) { - pluginName, err := extractPluginNameFromExecutableFileName(header.Name) - // if error is nil, we find the plugin executable file - if err == nil { - // check plugin existence - if !forced { - existed, err := checkPluginExistence(ctx, pluginName) - if err != nil { - return fmt.Errorf("failed to check plugin existence, %w", err) - } - if existed { - return fmt.Errorf("plugin %s already existed", pluginName) - } - } - // extract to tmp dir - tmpFilePath := filepath.Join(tmpDir, header.Name) - pluginFile, err := os.OpenFile(tmpFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, header.FileInfo().Mode()) - if err != nil { - return err - } - if _, err := io.Copy(pluginFile, tarReader); err != nil { - return err - } - if err := pluginFile.Close(); err != nil { - return err - } - // validate plugin metadata - if err := validatePluginMetadata(ctx, pluginName, tmpFilePath); err != nil { - return err - } - // install plugin - pluginPath, err := dir.PluginFS().SysPath(pluginName) - if err != nil { - return nil - } - _, err = osutil.CopyToDir(tmpFilePath, pluginPath) - if err != nil { - return err - } - fmt.Printf("Succussefully installed plugin %s\n", pluginName) - return nil + if fmode.IsRegular() && osutil.IsOwnerExecutalbeFile(fmode) { + err := installPluginExecutable(ctx, header.Name, tarReader, fmode, force) + if errors.As(err, ¬ationerrors.ErrorInvalidPluginName{}) { + logger.Warnln(err) + continue } + return err + } + } + return errors.New("valid plugin executable file not found in tar.gz") +} + +// installPluginExecutable extracts, validates, and installs a plugin from +// reader +func installPluginExecutable(ctx context.Context, fileName string, fileReader io.Reader, fmode fs.FileMode, force bool) error { + pluginName, err := extractPluginNameFromExecutableFileName(fileName) + if err != nil { + return err + } + // check plugin existence + if !force { + existed, err := checkPluginExistence(ctx, pluginName) + if err != nil { + return fmt.Errorf("failed to check plugin existence, %w", err) } + if existed { + return fmt.Errorf("plugin %s already installed", pluginName) + } + } + // extract to tmp dir + tmpDir, err := os.MkdirTemp(".", "pluginTmpDir") + if err != nil { + return fmt.Errorf("failed to create pluginTmpDir, %w", err) } + defer os.RemoveAll(tmpDir) + tmpFilePath := filepath.Join(tmpDir, fileName) + pluginFile, err := os.OpenFile(tmpFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fmode) + if err != nil { + return err + } + if _, err := io.Copy(pluginFile, fileReader); err != nil { + return err + } + if err := pluginFile.Close(); err != nil { + return err + } + // validate plugin metadata + pluginVersion, err := validatePluginMetadata(ctx, pluginName, tmpFilePath) + if err != nil { + return err + } + // install plugin + pluginPath, err := dir.PluginFS().SysPath(pluginName) + if err != nil { + return err + } + _, err = osutil.CopyToDir(tmpFilePath, pluginPath) + if err != nil { + return err + } + // plugin is always executable + pluginFilePath := filepath.Join(pluginPath, filepath.Base(tmpFilePath)) + err = os.Chmod(pluginFilePath, 0700) + if err != nil { + return err + } + + fmt.Printf("Succussefully installed plugin %s, version %s\n", pluginName, pluginVersion) return nil } -// extractPluginNameFromExecutableFileName gets plugin name from plugin executable -// file name based on spec: https://github.com/notaryproject/specifications/blob/main/specs/plugin-extensibility.md#installation +// extractPluginNameFromExecutableFileName gets plugin name from plugin +// executable file name based on spec: https://github.com/notaryproject/specifications/blob/main/specs/plugin-extensibility.md#installation func extractPluginNameFromExecutableFileName(execFileName string) (string, error) { fileName := osutil.FileNameWithoutExtension(execFileName) _, pluginName, found := strings.Cut(fileName, "-") if !found || !strings.HasPrefix(fileName, proto.Prefix) { - return "", fmt.Errorf("invalid plugin executable file name. file name requires format notation-{plugin-name}, got %s", fileName) + return "", notationerrors.ErrorInvalidPluginName{Msg: fmt.Sprintf("invalid plugin executable file name. file name requires format notation-{plugin-name}, got %s", fileName)} } return pluginName, nil } @@ -289,14 +278,15 @@ func checkPluginExistence(ctx context.Context, pluginName string) (bool, error) } // validatePluginMetadata validates plugin metadata before installation -func validatePluginMetadata(ctx context.Context, pluginName, path string) error { +// returns the plugin version on success +func validatePluginMetadata(ctx context.Context, pluginName, path string) (string, error) { plugin, err := plugin.NewCLIPlugin(ctx, pluginName, path) if err != nil { - return err + return "", err } - _, err = plugin.GetMetadata(ctx, &proto.GetMetadataRequest{}) + metadata, err := plugin.GetMetadata(ctx, &proto.GetMetadataRequest{}) if err != nil { - return err + return "", err } - return nil + return metadata.Version, nil } diff --git a/internal/osutil/file.go b/internal/osutil/file.go index c75915e7e..d838ad7ed 100644 --- a/internal/osutil/file.go +++ b/internal/osutil/file.go @@ -74,8 +74,8 @@ func CopyToDir(src, dst string) (int64, error) { if err := os.MkdirAll(dst, 0700); err != nil { return 0, err } - certFile := filepath.Join(dst, filepath.Base(src)) - destination, err := os.Create(certFile) + dstFile := filepath.Join(dst, filepath.Base(src)) + destination, err := os.Create(dstFile) if err != nil { return 0, err } @@ -116,6 +116,6 @@ func FileNameWithoutExtension(inputName string) string { } // IsOwnerExecutalbeFile checks whether file is owner executable -func IsOwnerExecutalbeFile(mode fs.FileMode) bool { - return mode&0100 != 0 +func IsOwnerExecutalbeFile(fmode fs.FileMode) bool { + return fmode&0100 != 0 } From 6138e776e0105647bbf826958733a1cdf1063fc0 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Mon, 23 Oct 2023 15:50:47 +0800 Subject: [PATCH 04/24] added uninstall Signed-off-by: Patrick Zheng --- cmd/notation/plugin/cmd.go | 1 + cmd/notation/plugin/install.go | 4 +- cmd/notation/plugin/uninstall.go | 72 ++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 cmd/notation/plugin/uninstall.go diff --git a/cmd/notation/plugin/cmd.go b/cmd/notation/plugin/cmd.go index 17f3d3150..2500463e5 100644 --- a/cmd/notation/plugin/cmd.go +++ b/cmd/notation/plugin/cmd.go @@ -24,6 +24,7 @@ func Cmd() *cobra.Command { command.AddCommand( pluginListCommand(), pluginInstallCommand(nil), + pluginUninstallCommand(nil), ) return command diff --git a/cmd/notation/plugin/install.go b/cmd/notation/plugin/install.go index 524072d45..789b6239e 100644 --- a/cmd/notation/plugin/install.go +++ b/cmd/notation/plugin/install.go @@ -59,7 +59,7 @@ func pluginInstallCommand(opts *pluginInstallOpts) *cobra.Command { Long: `Install a Notation plugin Example - Install plugin from file system: - notation plugin install --file myPlugin.tar.gz --checksum 123abcd + notation plugin install --file myPlugin.tar.gz --checksum abcdef `, RunE: func(cmd *cobra.Command, args []string) error { return installPlugin(cmd, opts) @@ -139,7 +139,7 @@ func installPluginFromZip(ctx context.Context, zipPath string, force bool) error defer archive.Close() for _, f := range archive.File { fmode := f.Mode() - // only consider regular executable files in the zip + // only consider regular executable files if fmode.IsRegular() && osutil.IsOwnerExecutalbeFile(fmode) { fileInArchive, err := f.Open() if err != nil { diff --git a/cmd/notation/plugin/uninstall.go b/cmd/notation/plugin/uninstall.go new file mode 100644 index 000000000..d200f5071 --- /dev/null +++ b/cmd/notation/plugin/uninstall.go @@ -0,0 +1,72 @@ +package plugin + +import ( + "errors" + "fmt" + "os" + + "github.com/notaryproject/notation-go/dir" + "github.com/notaryproject/notation/cmd/notation/internal/cmdutil" + "github.com/spf13/cobra" +) + +type pluginUninstallOpts struct { + pluginName string + confirmed bool +} + +func pluginUninstallCommand(opts *pluginUninstallOpts) *cobra.Command { + if opts == nil { + opts = &pluginUninstallOpts{} + } + command := &cobra.Command{ + Use: "uninstall [flags] ", + Short: "Uninstall plugin", + Long: `Uninstall a Notation plugin + +Example - Uninstall plugin: + notation plugin uninstall my-plugin +`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("plugin name is required") + } + opts.pluginName = args[0] + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + return unInstallPlugin(cmd, opts) + }, + } + + command.Flags().BoolVarP(&opts.confirmed, "yes", "y", false, "do not prompt for confirmation") + return command +} + +func unInstallPlugin(command *cobra.Command, opts *pluginUninstallOpts) error { + pluginName := opts.pluginName + existed, err := checkPluginExistence(command.Context(), pluginName) + if err != nil { + return fmt.Errorf("failed to check plugin existence, %w", err) + } + if !existed { + return fmt.Errorf("plugin %s does not exist", pluginName) + } + pluginPath, err := dir.PluginFS().SysPath(pluginName) + if err != nil { + return err + } + prompt := fmt.Sprintf("Are you sure you want to uninstall plugin %q?", pluginName) + confirmed, err := cmdutil.AskForConfirmation(os.Stdin, prompt, opts.confirmed) + if err != nil { + return err + } + if !confirmed { + return nil + } + err = os.RemoveAll(pluginPath) + if err == nil { + fmt.Printf("Successfully uninstalled plugin %s\n", pluginName) + } + return err +} From 0e9f0951480e55d7f70279d14b696f6a5396c924 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Mon, 23 Oct 2023 16:47:20 +0800 Subject: [PATCH 05/24] added install from URL Signed-off-by: Patrick Zheng --- cmd/notation/plugin/install.go | 68 +++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/cmd/notation/plugin/install.go b/cmd/notation/plugin/install.go index 789b6239e..9381cd6e1 100644 --- a/cmd/notation/plugin/install.go +++ b/cmd/notation/plugin/install.go @@ -22,6 +22,7 @@ import ( "fmt" "io" "io/fs" + "net/http" "os" "path/filepath" "strings" @@ -42,9 +43,12 @@ const ( TypeGzip = "application/x-gzip" ) +const notationPluginTmp = "notationPluginTmp" + type pluginInstallOpts struct { cmd.LoggingFlagOpts inputPath string + inputURL string inputCheckSum string force bool } @@ -54,13 +58,28 @@ func pluginInstallCommand(opts *pluginInstallOpts) *cobra.Command { opts = &pluginInstallOpts{} } command := &cobra.Command{ - Use: "install [flags] ", + Use: "install [flags] ", Short: "Install plugin", Long: `Install a Notation plugin Example - Install plugin from file system: - notation plugin install --file myPlugin.tar.gz --checksum abcdef + notation plugin install --file myPlugin.zip --checksum abcdef + +Example - Install plugin from URL: + notation plugin install https://wabbit-networks.com/intaller/linux/amd64/wabbit-plugin-v1.0.tar.gz --checksum abcxyz `, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 && opts.inputPath == "" { + return errors.New("missing plugin URL or file path") + } + if len(args) != 0 && opts.inputPath != "" { + return errors.New("can install from either plugin URL or file path, got both") + } + if len(args) != 0 { + opts.inputURL = args[0] + } + return nil + }, RunE: func(cmd *cobra.Command, args []string) error { return installPlugin(cmd, opts) }, @@ -69,7 +88,6 @@ Example - Install plugin from file system: command.Flags().StringVar(&opts.inputPath, "file", "", "file path of the plugin to be installed, only supports tar.gz and zip format") command.Flags().StringVar(&opts.inputCheckSum, "checksum", "", "if set, must match the SHA256 of the plugin tar.gz/zip to be installed") command.Flags().BoolVar(&opts.force, "force", false, "force to install and overwrite the plugin") - command.MarkFlagRequired("file") command.MarkFlagRequired("checksum") return command } @@ -77,7 +95,17 @@ Example - Install plugin from file system: func installPlugin(command *cobra.Command, opts *pluginInstallOpts) error { // set log level ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) + inputPath := opts.inputPath + // install from URL + if opts.inputURL != "" { + inputPath = notationPluginTmp + if err := downloadFromURL(ctx, inputPath, opts.inputURL); err != nil { + return err + } + defer os.Remove(inputPath) + } + // sanity check inputFileStat, err := os.Stat(inputPath) if err != nil { @@ -108,6 +136,7 @@ func installPlugin(command *cobra.Command, opts *pluginInstallOpts) error { return errors.New("failed to install the plugin, invalid file type. Only support tar.gz and zip") } return nil + } // validateCheckSum returns nil if SHA256 of file at path equals to checkSum. @@ -212,9 +241,9 @@ func installPluginExecutable(ctx context.Context, fileName string, fileReader io } } // extract to tmp dir - tmpDir, err := os.MkdirTemp(".", "pluginTmpDir") + tmpDir, err := os.MkdirTemp(".", notationPluginTmp) if err != nil { - return fmt.Errorf("failed to create pluginTmpDir, %w", err) + return fmt.Errorf("failed to create notationPluginTmp, %w", err) } defer os.RemoveAll(tmpDir) tmpFilePath := filepath.Join(tmpDir, fileName) @@ -290,3 +319,32 @@ func validatePluginMetadata(ctx context.Context, pluginName, path string) (strin } return metadata.Version, nil } + +func downloadFromURL(ctx context.Context, filePath, url string) error { + // Create the file + out, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return err + } + defer out.Close() + + // Get the data + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + // Check server response + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("bad status: %s", resp.Status) + } + + // Writer the body to file + _, err = io.Copy(out, resp.Body) + if err != nil { + return err + } + + return nil +} From 6de0edb4a8db33e5ce69552896faab3ad64f4d66 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 24 Oct 2023 14:06:25 +0800 Subject: [PATCH 06/24] updated Signed-off-by: Patrick Zheng --- cmd/notation/plugin/install.go | 35 +++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/cmd/notation/plugin/install.go b/cmd/notation/plugin/install.go index 9381cd6e1..54fafc44f 100644 --- a/cmd/notation/plugin/install.go +++ b/cmd/notation/plugin/install.go @@ -43,7 +43,7 @@ const ( TypeGzip = "application/x-gzip" ) -const notationPluginTmp = "notationPluginTmp" +const notationPluginTmpDir = "notationPluginTmpDir" type pluginInstallOpts struct { cmd.LoggingFlagOpts @@ -99,11 +99,15 @@ func installPlugin(command *cobra.Command, opts *pluginInstallOpts) error { inputPath := opts.inputPath // install from URL if opts.inputURL != "" { - inputPath = notationPluginTmp - if err := downloadFromURL(ctx, inputPath, opts.inputURL); err != nil { + tmpDir, err := os.MkdirTemp("", notationPluginTmpDir) + if err != nil { + return fmt.Errorf("failed to create notationPluginTmpDir, %w", err) + } + defer os.RemoveAll(tmpDir) + inputPath, err = downloadPluginFromURL(ctx, opts.inputURL, tmpDir) + if err != nil { return err } - defer os.Remove(inputPath) } // sanity check @@ -183,7 +187,7 @@ func installPluginFromZip(ctx context.Context, zipPath string, force bool) error return err } } - return errors.New("valid plugin executable file not found in zip") + return errors.New("no valid plugin executable file was found in zip") } // installPluginFromTarGz extracts and untar a plugin tar.gz file, validates and @@ -220,7 +224,7 @@ func installPluginFromTarGz(ctx context.Context, tarGzPath string, force bool) e return err } } - return errors.New("valid plugin executable file not found in tar.gz") + return errors.New("no valid plugin executable file was found in tar.gz") } // installPluginExecutable extracts, validates, and installs a plugin from @@ -241,9 +245,9 @@ func installPluginExecutable(ctx context.Context, fileName string, fileReader io } } // extract to tmp dir - tmpDir, err := os.MkdirTemp(".", notationPluginTmp) + tmpDir, err := os.MkdirTemp("", notationPluginTmpDir) if err != nil { - return fmt.Errorf("failed to create notationPluginTmp, %w", err) + return fmt.Errorf("failed to create notationPluginTmpDir, %w", err) } defer os.RemoveAll(tmpDir) tmpFilePath := filepath.Join(tmpDir, fileName) @@ -320,31 +324,32 @@ func validatePluginMetadata(ctx context.Context, pluginName, path string) (strin return metadata.Version, nil } -func downloadFromURL(ctx context.Context, filePath, url string) error { +func downloadPluginFromURL(ctx context.Context, url, dir string) (string, error) { // Create the file - out, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + tmpFilePath := filepath.Join(dir, "notationPluginTmp") + out, err := os.OpenFile(tmpFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { - return err + return "", err } defer out.Close() // Get the data resp, err := http.Get(url) if err != nil { - return err + return "", err } defer resp.Body.Close() // Check server response if resp.StatusCode != http.StatusOK { - return fmt.Errorf("bad status: %s", resp.Status) + return "", fmt.Errorf("bad status: %s", resp.Status) } // Writer the body to file _, err = io.Copy(out, resp.Body) if err != nil { - return err + return "", err } - return nil + return tmpFilePath, err } From 4b90dd5f0a7e50044b084e1223fc14346fa142a5 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 24 Oct 2023 16:27:16 +0800 Subject: [PATCH 07/24] fix Signed-off-by: Patrick Zheng --- cmd/notation/plugin/install.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cmd/notation/plugin/install.go b/cmd/notation/plugin/install.go index 54fafc44f..31645ef2d 100644 --- a/cmd/notation/plugin/install.go +++ b/cmd/notation/plugin/install.go @@ -25,6 +25,7 @@ import ( "net/http" "os" "path/filepath" + "runtime" "strings" "github.com/notaryproject/notation-go/dir" @@ -230,10 +231,18 @@ func installPluginFromTarGz(ctx context.Context, tarGzPath string, force bool) e // installPluginExecutable extracts, validates, and installs a plugin from // reader func installPluginExecutable(ctx context.Context, fileName string, fileReader io.Reader, fmode fs.FileMode, force bool) error { + // sanity check pluginName, err := extractPluginNameFromExecutableFileName(fileName) if err != nil { return err } + if runtime.GOOS == "windows" && filepath.Ext(fileName) != ".exe" { + return fmt.Errorf("on Windows, plugin executable file name %s is missing the '.exe' extension", fileName) + } + if runtime.GOOS != "windows" && filepath.Ext(fileName) == ".exe" { + return fmt.Errorf("on %s, plugin executable file name %s cannot have the '.exe' extension", runtime.GOOS, fileName) + } + // check plugin existence if !force { existed, err := checkPluginExistence(ctx, pluginName) From 3951ee7dd1ccf9fbfed192f25631cdde1219d845 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 24 Oct 2023 17:01:28 +0800 Subject: [PATCH 08/24] updated Signed-off-by: Patrick Zheng --- cmd/notation/plugin/install.go | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/cmd/notation/plugin/install.go b/cmd/notation/plugin/install.go index 31645ef2d..9ebc56205 100644 --- a/cmd/notation/plugin/install.go +++ b/cmd/notation/plugin/install.go @@ -110,7 +110,6 @@ func installPlugin(command *cobra.Command, opts *pluginInstallOpts) error { return err } } - // sanity check inputFileStat, err := os.Stat(inputPath) if err != nil { @@ -141,7 +140,6 @@ func installPlugin(command *cobra.Command, opts *pluginInstallOpts) error { return errors.New("failed to install the plugin, invalid file type. Only support tar.gz and zip") } return nil - } // validateCheckSum returns nil if SHA256 of file at path equals to checkSum. @@ -173,7 +171,8 @@ func installPluginFromZip(ctx context.Context, zipPath string, force bool) error defer archive.Close() for _, f := range archive.File { fmode := f.Mode() - // only consider regular executable files + // requires one and only one executable file, with name in format + // notation-{plugin-name}, exists in the zip file if fmode.IsRegular() && osutil.IsOwnerExecutalbeFile(fmode) { fileInArchive, err := f.Open() if err != nil { @@ -188,7 +187,7 @@ func installPluginFromZip(ctx context.Context, zipPath string, force bool) error return err } } - return errors.New("no valid plugin executable file was found in zip") + return fmt.Errorf("no valid plugin executable file was found in %s", zipPath) } // installPluginFromTarGz extracts and untar a plugin tar.gz file, validates and @@ -215,7 +214,8 @@ func installPluginFromTarGz(ctx context.Context, tarGzPath string, force bool) e return err } fmode := header.FileInfo().Mode() - // only consider regular executable files + // requires one and only one executable file, with name in format + // notation-{plugin-name}, exists in the tar.gz file if fmode.IsRegular() && osutil.IsOwnerExecutalbeFile(fmode) { err := installPluginExecutable(ctx, header.Name, tarReader, fmode, force) if errors.As(err, ¬ationerrors.ErrorInvalidPluginName{}) { @@ -225,7 +225,7 @@ func installPluginFromTarGz(ctx context.Context, tarGzPath string, force bool) e return err } } - return errors.New("no valid plugin executable file was found in tar.gz") + return fmt.Errorf("no valid plugin executable file was found in %s", tarGzPath) } // installPluginExecutable extracts, validates, and installs a plugin from @@ -285,7 +285,7 @@ func installPluginExecutable(ctx context.Context, fileName string, fileReader io return err } // plugin is always executable - pluginFilePath := filepath.Join(pluginPath, filepath.Base(tmpFilePath)) + pluginFilePath := filepath.Join(pluginPath, fileName) err = os.Chmod(pluginFilePath, 0700) if err != nil { return err @@ -319,7 +319,7 @@ func checkPluginExistence(ctx context.Context, pluginName string) (bool, error) return true, nil } -// validatePluginMetadata validates plugin metadata before installation +// validatePluginMetadata validates plugin metadata given plugin name and path // returns the plugin version on success func validatePluginMetadata(ctx context.Context, pluginName, path string) (string, error) { plugin, err := plugin.NewCLIPlugin(ctx, pluginName, path) @@ -333,32 +333,30 @@ func validatePluginMetadata(ctx context.Context, pluginName, path string) (strin return metadata.Version, nil } -func downloadPluginFromURL(ctx context.Context, url, dir string) (string, error) { +// downloadPluginFromURL downloads plugin file from url to a tmp directory +// it returns the tmp file path of the downloaded file +func downloadPluginFromURL(ctx context.Context, url, tmpDir string) (string, error) { // Create the file - tmpFilePath := filepath.Join(dir, "notationPluginTmp") + tmpFilePath := filepath.Join(tmpDir, "notationPluginTmp") out, err := os.OpenFile(tmpFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return "", err } defer out.Close() - // Get the data resp, err := http.Get(url) if err != nil { return "", err } defer resp.Body.Close() - // Check server response if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("bad status: %s", resp.Status) } - // Writer the body to file _, err = io.Copy(out, resp.Body) if err != nil { return "", err } - return tmpFilePath, err } From 031b539035462e39e18c1cdab04582b9e8c7118e Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 31 Oct 2023 13:20:25 +0800 Subject: [PATCH 09/24] notation pluing uninstall Signed-off-by: Patrick Zheng --- cmd/notation/internal/plugin/plugin.go | 23 ++ cmd/notation/plugin/cmd.go | 1 - cmd/notation/plugin/install.go | 362 ------------------------- cmd/notation/plugin/uninstall.go | 3 +- internal/osutil/file.go | 25 -- 5 files changed, 25 insertions(+), 389 deletions(-) create mode 100644 cmd/notation/internal/plugin/plugin.go delete mode 100644 cmd/notation/plugin/install.go diff --git a/cmd/notation/internal/plugin/plugin.go b/cmd/notation/internal/plugin/plugin.go new file mode 100644 index 000000000..dce8ce70b --- /dev/null +++ b/cmd/notation/internal/plugin/plugin.go @@ -0,0 +1,23 @@ +package plugin + +import ( + "context" + "errors" + "os" + + "github.com/notaryproject/notation-go/dir" + "github.com/notaryproject/notation-go/plugin" +) + +// CheckPluginExistence returns true if a plugin already exists +func CheckPluginExistence(ctx context.Context, pluginName string) (bool, error) { + mgr := plugin.NewCLIManager(dir.PluginFS()) + _, err := mgr.Get(ctx, pluginName) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return false, nil + } + return false, err + } + return true, nil +} diff --git a/cmd/notation/plugin/cmd.go b/cmd/notation/plugin/cmd.go index 2500463e5..1b97a023a 100644 --- a/cmd/notation/plugin/cmd.go +++ b/cmd/notation/plugin/cmd.go @@ -23,7 +23,6 @@ func Cmd() *cobra.Command { command.AddCommand( pluginListCommand(), - pluginInstallCommand(nil), pluginUninstallCommand(nil), ) diff --git a/cmd/notation/plugin/install.go b/cmd/notation/plugin/install.go deleted file mode 100644 index 9ebc56205..000000000 --- a/cmd/notation/plugin/install.go +++ /dev/null @@ -1,362 +0,0 @@ -// Copyright The Notary Project Authors. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package plugin - -import ( - "archive/tar" - "archive/zip" - "compress/gzip" - "context" - "errors" - "fmt" - "io" - "io/fs" - "net/http" - "os" - "path/filepath" - "runtime" - "strings" - - "github.com/notaryproject/notation-go/dir" - "github.com/notaryproject/notation-go/log" - "github.com/notaryproject/notation-go/plugin" - "github.com/notaryproject/notation-go/plugin/proto" - notationerrors "github.com/notaryproject/notation/cmd/notation/internal/errors" - "github.com/notaryproject/notation/internal/cmd" - "github.com/notaryproject/notation/internal/osutil" - "github.com/opencontainers/go-digest" - "github.com/spf13/cobra" -) - -const ( - TypeZip = "application/zip" - TypeGzip = "application/x-gzip" -) - -const notationPluginTmpDir = "notationPluginTmpDir" - -type pluginInstallOpts struct { - cmd.LoggingFlagOpts - inputPath string - inputURL string - inputCheckSum string - force bool -} - -func pluginInstallCommand(opts *pluginInstallOpts) *cobra.Command { - if opts == nil { - opts = &pluginInstallOpts{} - } - command := &cobra.Command{ - Use: "install [flags] ", - Short: "Install plugin", - Long: `Install a Notation plugin - -Example - Install plugin from file system: - notation plugin install --file myPlugin.zip --checksum abcdef - -Example - Install plugin from URL: - notation plugin install https://wabbit-networks.com/intaller/linux/amd64/wabbit-plugin-v1.0.tar.gz --checksum abcxyz -`, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 && opts.inputPath == "" { - return errors.New("missing plugin URL or file path") - } - if len(args) != 0 && opts.inputPath != "" { - return errors.New("can install from either plugin URL or file path, got both") - } - if len(args) != 0 { - opts.inputURL = args[0] - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - return installPlugin(cmd, opts) - }, - } - opts.LoggingFlagOpts.ApplyFlags(command.Flags()) - command.Flags().StringVar(&opts.inputPath, "file", "", "file path of the plugin to be installed, only supports tar.gz and zip format") - command.Flags().StringVar(&opts.inputCheckSum, "checksum", "", "if set, must match the SHA256 of the plugin tar.gz/zip to be installed") - command.Flags().BoolVar(&opts.force, "force", false, "force to install and overwrite the plugin") - command.MarkFlagRequired("checksum") - return command -} - -func installPlugin(command *cobra.Command, opts *pluginInstallOpts) error { - // set log level - ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) - - inputPath := opts.inputPath - // install from URL - if opts.inputURL != "" { - tmpDir, err := os.MkdirTemp("", notationPluginTmpDir) - if err != nil { - return fmt.Errorf("failed to create notationPluginTmpDir, %w", err) - } - defer os.RemoveAll(tmpDir) - inputPath, err = downloadPluginFromURL(ctx, opts.inputURL, tmpDir) - if err != nil { - return err - } - } - // sanity check - inputFileStat, err := os.Stat(inputPath) - if err != nil { - return fmt.Errorf("failed to install the plugin, %w", err) - } - if !inputFileStat.Mode().IsRegular() { - return fmt.Errorf("failed to install the plugin, %s is not a regular file", inputPath) - } - // checkSum check - if err := validateCheckSum(inputPath, opts.inputCheckSum); err != nil { - return fmt.Errorf("failed to install the plugin, %w", err) - } - // install the plugin based on file type - fileType, err := osutil.DetectFileType(inputPath) - if err != nil { - return fmt.Errorf("failed to install the plugin, %w", err) - } - switch fileType { - case TypeZip: - if err := installPluginFromZip(ctx, inputPath, opts.force); err != nil { - return fmt.Errorf("failed to install the plugin, %w", err) - } - case TypeGzip: - if err := installPluginFromTarGz(ctx, inputPath, opts.force); err != nil { - return fmt.Errorf("failed to install the plugin, %w", err) - } - default: - return errors.New("failed to install the plugin, invalid file type. Only support tar.gz and zip") - } - return nil -} - -// validateCheckSum returns nil if SHA256 of file at path equals to checkSum. -func validateCheckSum(path string, checkSum string) error { - r, err := os.Open(path) - if err != nil { - return err - } - defer r.Close() - dgst, err := digest.FromReader(r) - if err != nil { - return err - } - enc := dgst.Encoded() - if enc != checkSum { - return fmt.Errorf("plugin checkSum does not match user input. User input is %s, got %s", checkSum, enc) - } - return nil -} - -// installPluginFromZip extracts a plugin zip file, validates and -// installs the plugin -func installPluginFromZip(ctx context.Context, zipPath string, force bool) error { - logger := log.GetLogger(ctx) - archive, err := zip.OpenReader(zipPath) - if err != nil { - return err - } - defer archive.Close() - for _, f := range archive.File { - fmode := f.Mode() - // requires one and only one executable file, with name in format - // notation-{plugin-name}, exists in the zip file - if fmode.IsRegular() && osutil.IsOwnerExecutalbeFile(fmode) { - fileInArchive, err := f.Open() - if err != nil { - return err - } - defer fileInArchive.Close() - err = installPluginExecutable(ctx, f.Name, fileInArchive, fmode, force) - if errors.As(err, ¬ationerrors.ErrorInvalidPluginName{}) { - logger.Warnln(err) - continue - } - return err - } - } - return fmt.Errorf("no valid plugin executable file was found in %s", zipPath) -} - -// installPluginFromTarGz extracts and untar a plugin tar.gz file, validates and -// installs the plugin -func installPluginFromTarGz(ctx context.Context, tarGzPath string, force bool) error { - logger := log.GetLogger(ctx) - r, err := os.Open(tarGzPath) - if err != nil { - return err - } - defer r.Close() - decompressedStream, err := gzip.NewReader(r) - if err != nil { - return err - } - defer decompressedStream.Close() - tarReader := tar.NewReader(decompressedStream) - for { - header, err := tarReader.Next() - if err != nil { - if err == io.EOF { - break - } - return err - } - fmode := header.FileInfo().Mode() - // requires one and only one executable file, with name in format - // notation-{plugin-name}, exists in the tar.gz file - if fmode.IsRegular() && osutil.IsOwnerExecutalbeFile(fmode) { - err := installPluginExecutable(ctx, header.Name, tarReader, fmode, force) - if errors.As(err, ¬ationerrors.ErrorInvalidPluginName{}) { - logger.Warnln(err) - continue - } - return err - } - } - return fmt.Errorf("no valid plugin executable file was found in %s", tarGzPath) -} - -// installPluginExecutable extracts, validates, and installs a plugin from -// reader -func installPluginExecutable(ctx context.Context, fileName string, fileReader io.Reader, fmode fs.FileMode, force bool) error { - // sanity check - pluginName, err := extractPluginNameFromExecutableFileName(fileName) - if err != nil { - return err - } - if runtime.GOOS == "windows" && filepath.Ext(fileName) != ".exe" { - return fmt.Errorf("on Windows, plugin executable file name %s is missing the '.exe' extension", fileName) - } - if runtime.GOOS != "windows" && filepath.Ext(fileName) == ".exe" { - return fmt.Errorf("on %s, plugin executable file name %s cannot have the '.exe' extension", runtime.GOOS, fileName) - } - - // check plugin existence - if !force { - existed, err := checkPluginExistence(ctx, pluginName) - if err != nil { - return fmt.Errorf("failed to check plugin existence, %w", err) - } - if existed { - return fmt.Errorf("plugin %s already installed", pluginName) - } - } - // extract to tmp dir - tmpDir, err := os.MkdirTemp("", notationPluginTmpDir) - if err != nil { - return fmt.Errorf("failed to create notationPluginTmpDir, %w", err) - } - defer os.RemoveAll(tmpDir) - tmpFilePath := filepath.Join(tmpDir, fileName) - pluginFile, err := os.OpenFile(tmpFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fmode) - if err != nil { - return err - } - if _, err := io.Copy(pluginFile, fileReader); err != nil { - return err - } - if err := pluginFile.Close(); err != nil { - return err - } - // validate plugin metadata - pluginVersion, err := validatePluginMetadata(ctx, pluginName, tmpFilePath) - if err != nil { - return err - } - // install plugin - pluginPath, err := dir.PluginFS().SysPath(pluginName) - if err != nil { - return err - } - _, err = osutil.CopyToDir(tmpFilePath, pluginPath) - if err != nil { - return err - } - // plugin is always executable - pluginFilePath := filepath.Join(pluginPath, fileName) - err = os.Chmod(pluginFilePath, 0700) - if err != nil { - return err - } - - fmt.Printf("Succussefully installed plugin %s, version %s\n", pluginName, pluginVersion) - return nil -} - -// extractPluginNameFromExecutableFileName gets plugin name from plugin -// executable file name based on spec: https://github.com/notaryproject/specifications/blob/main/specs/plugin-extensibility.md#installation -func extractPluginNameFromExecutableFileName(execFileName string) (string, error) { - fileName := osutil.FileNameWithoutExtension(execFileName) - _, pluginName, found := strings.Cut(fileName, "-") - if !found || !strings.HasPrefix(fileName, proto.Prefix) { - return "", notationerrors.ErrorInvalidPluginName{Msg: fmt.Sprintf("invalid plugin executable file name. file name requires format notation-{plugin-name}, got %s", fileName)} - } - return pluginName, nil -} - -// checkPluginExistence returns true if a plugin already exists -func checkPluginExistence(ctx context.Context, pluginName string) (bool, error) { - mgr := plugin.NewCLIManager(dir.PluginFS()) - _, err := mgr.Get(ctx, pluginName) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - return false, nil - } - return false, err - } - return true, nil -} - -// validatePluginMetadata validates plugin metadata given plugin name and path -// returns the plugin version on success -func validatePluginMetadata(ctx context.Context, pluginName, path string) (string, error) { - plugin, err := plugin.NewCLIPlugin(ctx, pluginName, path) - if err != nil { - return "", err - } - metadata, err := plugin.GetMetadata(ctx, &proto.GetMetadataRequest{}) - if err != nil { - return "", err - } - return metadata.Version, nil -} - -// downloadPluginFromURL downloads plugin file from url to a tmp directory -// it returns the tmp file path of the downloaded file -func downloadPluginFromURL(ctx context.Context, url, tmpDir string) (string, error) { - // Create the file - tmpFilePath := filepath.Join(tmpDir, "notationPluginTmp") - out, err := os.OpenFile(tmpFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - return "", err - } - defer out.Close() - // Get the data - resp, err := http.Get(url) - if err != nil { - return "", err - } - defer resp.Body.Close() - // Check server response - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("bad status: %s", resp.Status) - } - // Writer the body to file - _, err = io.Copy(out, resp.Body) - if err != nil { - return "", err - } - return tmpFilePath, err -} diff --git a/cmd/notation/plugin/uninstall.go b/cmd/notation/plugin/uninstall.go index d200f5071..72c6eb9f7 100644 --- a/cmd/notation/plugin/uninstall.go +++ b/cmd/notation/plugin/uninstall.go @@ -7,6 +7,7 @@ import ( "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation/cmd/notation/internal/cmdutil" + notationplugin "github.com/notaryproject/notation/cmd/notation/internal/plugin" "github.com/spf13/cobra" ) @@ -45,7 +46,7 @@ Example - Uninstall plugin: func unInstallPlugin(command *cobra.Command, opts *pluginUninstallOpts) error { pluginName := opts.pluginName - existed, err := checkPluginExistence(command.Context(), pluginName) + existed, err := notationplugin.CheckPluginExistence(command.Context(), pluginName) if err != nil { return fmt.Errorf("failed to check plugin existence, %w", err) } diff --git a/internal/osutil/file.go b/internal/osutil/file.go index d838ad7ed..66f1be0fe 100644 --- a/internal/osutil/file.go +++ b/internal/osutil/file.go @@ -17,10 +17,8 @@ import ( "fmt" "io" "io/fs" - "net/http" "os" "path/filepath" - "strings" ) // WriteFile writes to a path with all parent directories created. @@ -96,26 +94,3 @@ func IsRegularFile(path string) (bool, error) { return fileStat.Mode().IsRegular(), nil } - -// DetectFileType returns a file's content type given path -func DetectFileType(path string) (string, error) { - f, err := os.ReadFile(path) - if err != nil { - return "", err - } - return http.DetectContentType(f), nil -} - -// FileNameWithoutExtension returns the file name without extension. -// For example, -// when input is xyz.exe, output is xyz -// when input is xyz.tar.gz, output is xyz.tar -func FileNameWithoutExtension(inputName string) string { - fileName := filepath.Base(inputName) - return strings.TrimSuffix(fileName, filepath.Ext(fileName)) -} - -// IsOwnerExecutalbeFile checks whether file is owner executable -func IsOwnerExecutalbeFile(fmode fs.FileMode) bool { - return fmode&0100 != 0 -} From ce884813073e61a77c826f129c4002c09a9f6084 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 31 Oct 2023 14:24:34 +0800 Subject: [PATCH 10/24] notation plugin uninstall Signed-off-by: Patrick Zheng --- cmd/notation/internal/plugin/plugin_test.go | 25 +++++++++++++++++++ .../plugins/test-plugin/notation-test-plugin | 0 2 files changed, 25 insertions(+) create mode 100644 cmd/notation/internal/plugin/plugin_test.go create mode 100644 cmd/notation/internal/plugin/testdata/plugins/test-plugin/notation-test-plugin diff --git a/cmd/notation/internal/plugin/plugin_test.go b/cmd/notation/internal/plugin/plugin_test.go new file mode 100644 index 000000000..06b8610f5 --- /dev/null +++ b/cmd/notation/internal/plugin/plugin_test.go @@ -0,0 +1,25 @@ +package plugin + +import ( + "context" + "runtime" + "testing" + + "github.com/notaryproject/notation-go/dir" +) + +func TestCheckPluginExistence(t *testing.T) { + dir.UserConfigDir = "testdata" + if runtime.GOOS == "windows" { + t.Skip("skipping test on Windows") + } + exist, err := CheckPluginExistence(context.Background(), "non-exist-plugin") + if exist || err != nil { + t.Fatalf("expected exist to be false with nil err, got: %v, %s", exist, err) + } + + exist, err = CheckPluginExistence(context.Background(), "test-plugin") + if !exist || err != nil { + t.Fatalf("expected exist to be true with nil err, got: %v, %s", exist, err) + } +} diff --git a/cmd/notation/internal/plugin/testdata/plugins/test-plugin/notation-test-plugin b/cmd/notation/internal/plugin/testdata/plugins/test-plugin/notation-test-plugin new file mode 100644 index 000000000..e69de29bb From fe70191d6dcda67dec93f5e722d34f8f3cd8ab41 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 31 Oct 2023 14:37:43 +0800 Subject: [PATCH 11/24] test Signed-off-by: Patrick Zheng --- cmd/notation/internal/plugin/plugin_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/notation/internal/plugin/plugin_test.go b/cmd/notation/internal/plugin/plugin_test.go index 06b8610f5..fa314cbb8 100644 --- a/cmd/notation/internal/plugin/plugin_test.go +++ b/cmd/notation/internal/plugin/plugin_test.go @@ -2,6 +2,7 @@ package plugin import ( "context" + "fmt" "runtime" "testing" @@ -10,6 +11,7 @@ import ( func TestCheckPluginExistence(t *testing.T) { dir.UserConfigDir = "testdata" + fmt.Println(dir.PluginFS()) if runtime.GOOS == "windows" { t.Skip("skipping test on Windows") } From 4ed3a9b0c6c2d6d8f16045ee408dd6f1078d4f98 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 31 Oct 2023 14:47:29 +0800 Subject: [PATCH 12/24] update Signed-off-by: Patrick Zheng --- cmd/notation/internal/plugin/plugin_test.go | 14 -------------- .../plugins/test-plugin/notation-test-plugin | 0 2 files changed, 14 deletions(-) delete mode 100644 cmd/notation/internal/plugin/testdata/plugins/test-plugin/notation-test-plugin diff --git a/cmd/notation/internal/plugin/plugin_test.go b/cmd/notation/internal/plugin/plugin_test.go index fa314cbb8..85d04fdd5 100644 --- a/cmd/notation/internal/plugin/plugin_test.go +++ b/cmd/notation/internal/plugin/plugin_test.go @@ -2,26 +2,12 @@ package plugin import ( "context" - "fmt" - "runtime" "testing" - - "github.com/notaryproject/notation-go/dir" ) func TestCheckPluginExistence(t *testing.T) { - dir.UserConfigDir = "testdata" - fmt.Println(dir.PluginFS()) - if runtime.GOOS == "windows" { - t.Skip("skipping test on Windows") - } exist, err := CheckPluginExistence(context.Background(), "non-exist-plugin") if exist || err != nil { t.Fatalf("expected exist to be false with nil err, got: %v, %s", exist, err) } - - exist, err = CheckPluginExistence(context.Background(), "test-plugin") - if !exist || err != nil { - t.Fatalf("expected exist to be true with nil err, got: %v, %s", exist, err) - } } diff --git a/cmd/notation/internal/plugin/testdata/plugins/test-plugin/notation-test-plugin b/cmd/notation/internal/plugin/testdata/plugins/test-plugin/notation-test-plugin deleted file mode 100644 index e69de29bb..000000000 From d723b8166b74779bb02bd9a9a596d50bfb836385 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Mon, 20 Nov 2023 11:37:32 +0800 Subject: [PATCH 13/24] updated based on spec Signed-off-by: Patrick Zheng --- cmd/notation/internal/plugin/plugin.go | 16 ++++----- cmd/notation/internal/plugin/plugin_test.go | 10 +++--- cmd/notation/plugin/uninstall.go | 40 +++++++++++++-------- test/e2e/internal/notation/init.go | 37 ++++++++++--------- test/e2e/run.sh | 5 +-- test/e2e/suite/plugin/uninstall.go | 37 +++++++++++++++++++ 6 files changed, 97 insertions(+), 48 deletions(-) create mode 100644 test/e2e/suite/plugin/uninstall.go diff --git a/cmd/notation/internal/plugin/plugin.go b/cmd/notation/internal/plugin/plugin.go index dce8ce70b..2fa55ffc6 100644 --- a/cmd/notation/internal/plugin/plugin.go +++ b/cmd/notation/internal/plugin/plugin.go @@ -2,22 +2,18 @@ package plugin import ( "context" - "errors" - "os" "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/plugin" + "github.com/notaryproject/notation-go/plugin/proto" ) -// CheckPluginExistence returns true if a plugin already exists -func CheckPluginExistence(ctx context.Context, pluginName string) (bool, error) { +// GetPluginMetadataIfExist returns plugin's metadata if it exists in Notation +func GetPluginMetadataIfExist(ctx context.Context, pluginName string) (*proto.GetMetadataResponse, error) { mgr := plugin.NewCLIManager(dir.PluginFS()) - _, err := mgr.Get(ctx, pluginName) + plugin, err := mgr.Get(ctx, pluginName) if err != nil { - if errors.Is(err, os.ErrNotExist) { - return false, nil - } - return false, err + return nil, err } - return true, nil + return plugin.GetMetadata(ctx, &proto.GetMetadataRequest{}) } diff --git a/cmd/notation/internal/plugin/plugin_test.go b/cmd/notation/internal/plugin/plugin_test.go index 85d04fdd5..eceb22b9c 100644 --- a/cmd/notation/internal/plugin/plugin_test.go +++ b/cmd/notation/internal/plugin/plugin_test.go @@ -2,12 +2,14 @@ package plugin import ( "context" + "errors" + "os" "testing" ) -func TestCheckPluginExistence(t *testing.T) { - exist, err := CheckPluginExistence(context.Background(), "non-exist-plugin") - if exist || err != nil { - t.Fatalf("expected exist to be false with nil err, got: %v, %s", exist, err) +func TestGetPluginMetadataIfExist(t *testing.T) { + _, err := GetPluginMetadataIfExist(context.Background(), "non-exist-plugin") + if !errors.Is(err, os.ErrNotExist) { + t.Fatalf("expected os.ErrNotExist err, got: %v", err) } } diff --git a/cmd/notation/plugin/uninstall.go b/cmd/notation/plugin/uninstall.go index 72c6eb9f7..823dd3421 100644 --- a/cmd/notation/plugin/uninstall.go +++ b/cmd/notation/plugin/uninstall.go @@ -8,10 +8,12 @@ import ( "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation/cmd/notation/internal/cmdutil" notationplugin "github.com/notaryproject/notation/cmd/notation/internal/plugin" + "github.com/notaryproject/notation/internal/cmd" "github.com/spf13/cobra" ) type pluginUninstallOpts struct { + cmd.LoggingFlagOpts pluginName string confirmed bool } @@ -21,17 +23,21 @@ func pluginUninstallCommand(opts *pluginUninstallOpts) *cobra.Command { opts = &pluginUninstallOpts{} } command := &cobra.Command{ - Use: "uninstall [flags] ", - Short: "Uninstall plugin", - Long: `Uninstall a Notation plugin + Use: "uninstall [flags] ", + Aliases: []string{"remove", "rm"}, + Short: "Uninstall a plugin", + Long: `Uninstall a plugin Example - Uninstall plugin: - notation plugin uninstall my-plugin + notation plugin uninstall wabbit-plugin `, Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("plugin name is required") } + if len(args) > 1 { + return errors.New("can only remove one plugin at a time") + } opts.pluginName = args[0] return nil }, @@ -40,34 +46,38 @@ Example - Uninstall plugin: }, } + opts.LoggingFlagOpts.ApplyFlags(command.Flags()) command.Flags().BoolVarP(&opts.confirmed, "yes", "y", false, "do not prompt for confirmation") return command } func unInstallPlugin(command *cobra.Command, opts *pluginUninstallOpts) error { + // set log level + ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) pluginName := opts.pluginName - existed, err := notationplugin.CheckPluginExistence(command.Context(), pluginName) + _, err := notationplugin.GetPluginMetadataIfExist(ctx, pluginName) if err != nil { - return fmt.Errorf("failed to check plugin existence, %w", err) - } - if !existed { - return fmt.Errorf("plugin %s does not exist", pluginName) + if errors.Is(err, os.ErrNotExist) { // plugin does not exist + return fmt.Errorf("unable to find plugin %s.\nTo view a list of installed plugins, use `notation plugin list`", pluginName) + } + return fmt.Errorf("failed to uninstall %s: %w", pluginName, err) } + // core process pluginPath, err := dir.PluginFS().SysPath(pluginName) if err != nil { - return err + return fmt.Errorf("failed to uninstall %s: %v", pluginName, err) } prompt := fmt.Sprintf("Are you sure you want to uninstall plugin %q?", pluginName) confirmed, err := cmdutil.AskForConfirmation(os.Stdin, prompt, opts.confirmed) if err != nil { - return err + return fmt.Errorf("failed to uninstall %s: %v", pluginName, err) } if !confirmed { return nil } - err = os.RemoveAll(pluginPath) - if err == nil { - fmt.Printf("Successfully uninstalled plugin %s\n", pluginName) + if err := os.RemoveAll(pluginPath); err != nil { + return fmt.Errorf("failed to uninstall %s: %v", pluginName, err) } - return err + fmt.Printf("Successfully uninstalled plugin %s\n", pluginName) + return nil } diff --git a/test/e2e/internal/notation/init.go b/test/e2e/internal/notation/init.go index f323e13b7..2e1659917 100644 --- a/test/e2e/internal/notation/init.go +++ b/test/e2e/internal/notation/init.go @@ -33,17 +33,18 @@ const ( ) const ( - envKeyRegistryHost = "NOTATION_E2E_REGISTRY_HOST" - envKeyRegistryUsername = "NOTATION_E2E_REGISTRY_USERNAME" - envKeyRegistryPassword = "NOTATION_E2E_REGISTRY_PASSWORD" - envKeyDomainRegistryHost = "NOTATION_E2E_DOMAIN_REGISTRY_HOST" - envKeyNotationBinPath = "NOTATION_E2E_BINARY_PATH" - envKeyNotationOldBinPath = "NOTATION_E2E_OLD_BINARY_PATH" - envKeyNotationPluginPath = "NOTATION_E2E_PLUGIN_PATH" - envKeyNotationConfigPath = "NOTATION_E2E_CONFIG_PATH" - envKeyOCILayoutPath = "NOTATION_E2E_OCI_LAYOUT_PATH" - envKeyTestRepo = "NOTATION_E2E_TEST_REPO" - envKeyTestTag = "NOTATION_E2E_TEST_TAG" + envKeyRegistryHost = "NOTATION_E2E_REGISTRY_HOST" + envKeyRegistryUsername = "NOTATION_E2E_REGISTRY_USERNAME" + envKeyRegistryPassword = "NOTATION_E2E_REGISTRY_PASSWORD" + envKeyDomainRegistryHost = "NOTATION_E2E_DOMAIN_REGISTRY_HOST" + envKeyNotationBinPath = "NOTATION_E2E_BINARY_PATH" + envKeyNotationOldBinPath = "NOTATION_E2E_OLD_BINARY_PATH" + envKeyNotationPluginPath = "NOTATION_E2E_PLUGIN_PATH" + envKeyNotationPluginTarGzPath = "NOTATION_E2E_PLUGIN_TAR_GZ_PATH" + envKeyNotationConfigPath = "NOTATION_E2E_CONFIG_PATH" + envKeyOCILayoutPath = "NOTATION_E2E_OCI_LAYOUT_PATH" + envKeyTestRepo = "NOTATION_E2E_TEST_REPO" + envKeyTestTag = "NOTATION_E2E_TEST_TAG" ) var ( @@ -51,12 +52,13 @@ var ( NotationBinPath string // NotationOldBinPath is the path of an old version notation binary for // testing forward compatibility. - NotationOldBinPath string - NotationE2EPluginPath string - NotationE2EConfigPath string - NotationE2ELocalKeysDir string - NotationE2ETrustPolicyDir string - NotationE2EConfigJsonDir string + NotationOldBinPath string + NotationE2EPluginPath string + NotationE2EPluginTarGzPath string + NotationE2EConfigPath string + NotationE2ELocalKeysDir string + NotationE2ETrustPolicyDir string + NotationE2EConfigJsonDir string ) var ( @@ -90,6 +92,7 @@ func setUpNotationValues() { // set Notation e2e-plugin path setPathValue(envKeyNotationPluginPath, &NotationE2EPluginPath) + setPathValue(envKeyNotationPluginTarGzPath, &NotationE2EPluginTarGzPath) // set Notation configuration paths setPathValue(envKeyNotationConfigPath, &NotationE2EConfigPath) diff --git a/test/e2e/run.sh b/test/e2e/run.sh index 7d053aa89..6365793ea 100755 --- a/test/e2e/run.sh +++ b/test/e2e/run.sh @@ -71,9 +71,9 @@ fi # install dependency go install -mod=mod github.com/onsi/ginkgo/v2/ginkgo@v2.9.5 -# build e2e plugin +# build e2e plugin and tar.gz PLUGIN_NAME=e2e-plugin -( cd $CWD/plugin && go build -o ./bin/$PLUGIN_NAME . && echo "e2e plugin built." ) +( cd $CWD/plugin && go build -o ./bin/$PLUGIN_NAME . && echo "e2e plugin built." && tar --transform="flags=r;s|$PLUGIN_NAME|notation-$PLUGIN_NAME|" -czvf ./bin/$PLUGIN_NAME.tar.gz -C ./bin/ $PLUGIN_NAME) # setup registry case $REGISTRY_NAME in @@ -107,6 +107,7 @@ export NOTATION_E2E_OCI_LAYOUT_PATH=$CWD/testdata/registry/oci_layout export NOTATION_E2E_TEST_REPO=e2e export NOTATION_E2E_TEST_TAG=v1 export NOTATION_E2E_PLUGIN_PATH=$CWD/plugin/bin/$PLUGIN_NAME +export NOTATION_E2E_PLUGIN_TAR_GZ_PATH=$CWD/plugin/bin/$PLUGIN_NAME.tar.gz # run tests ginkgo -r -p -v \ No newline at end of file diff --git a/test/e2e/suite/plugin/uninstall.go b/test/e2e/suite/plugin/uninstall.go new file mode 100644 index 000000000..538f7a5fe --- /dev/null +++ b/test/e2e/suite/plugin/uninstall.go @@ -0,0 +1,37 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package plugin + +import ( + . "github.com/notaryproject/notation/test/e2e/internal/notation" + "github.com/notaryproject/notation/test/e2e/internal/utils" + . "github.com/onsi/ginkgo/v2" +) + +var _ = Describe("notation plugin uninstall", func() { + It("with valid plugin name", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.Exec("plugin", "uninstall", "--yes", "e2e-plugin"). + MatchContent("Succussefully uninstalled plugin e2e-plugin\n") + }) + }) + + It("with plugin does not exist", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("plugin", "uninstall", "--yes", "non-exist"). + MatchErrContent("Error: unable to find plugin non-exist.\nTo view a list of installed plugins, use `notation plugin list`\n") + }) + }) + +}) From c985fa5c4fcdcf64443c8fd46cf3dec253741f95 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Mon, 20 Nov 2023 12:47:32 +0800 Subject: [PATCH 14/24] fixing e2e Signed-off-by: Patrick Zheng --- test/e2e/suite/plugin/uninstall.go | 1 + 1 file changed, 1 insertion(+) diff --git a/test/e2e/suite/plugin/uninstall.go b/test/e2e/suite/plugin/uninstall.go index 538f7a5fe..18ff9d5b1 100644 --- a/test/e2e/suite/plugin/uninstall.go +++ b/test/e2e/suite/plugin/uninstall.go @@ -22,6 +22,7 @@ import ( var _ = Describe("notation plugin uninstall", func() { It("with valid plugin name", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("plugin", "uninstall", "--yes", "e2e-plugin"). MatchContent("Succussefully uninstalled plugin e2e-plugin\n") }) From 5b67ef29a41a9f4e858f0e173d7d55e685f83a6c Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Mon, 20 Nov 2023 12:55:23 +0800 Subject: [PATCH 15/24] fixing e2e Signed-off-by: Patrick Zheng --- test/e2e/suite/plugin/uninstall.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/suite/plugin/uninstall.go b/test/e2e/suite/plugin/uninstall.go index 18ff9d5b1..7a4b26ee5 100644 --- a/test/e2e/suite/plugin/uninstall.go +++ b/test/e2e/suite/plugin/uninstall.go @@ -24,7 +24,7 @@ var _ = Describe("notation plugin uninstall", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("plugin", "uninstall", "--yes", "e2e-plugin"). - MatchContent("Succussefully uninstalled plugin e2e-plugin\n") + MatchContent("Successfully uninstalled plugin e2e-plugin\n") }) }) From 0407c97da8a3ffc14cfae6a9ff93bb04b0d16bcc Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Mon, 20 Nov 2023 13:01:18 +0800 Subject: [PATCH 16/24] added license headers Signed-off-by: Patrick Zheng --- cmd/notation/internal/plugin/plugin.go | 13 +++++++++++++ cmd/notation/internal/plugin/plugin_test.go | 13 +++++++++++++ cmd/notation/plugin/uninstall.go | 13 +++++++++++++ 3 files changed, 39 insertions(+) diff --git a/cmd/notation/internal/plugin/plugin.go b/cmd/notation/internal/plugin/plugin.go index 2fa55ffc6..062660d36 100644 --- a/cmd/notation/internal/plugin/plugin.go +++ b/cmd/notation/internal/plugin/plugin.go @@ -1,3 +1,16 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package plugin import ( diff --git a/cmd/notation/internal/plugin/plugin_test.go b/cmd/notation/internal/plugin/plugin_test.go index eceb22b9c..8b3651f9e 100644 --- a/cmd/notation/internal/plugin/plugin_test.go +++ b/cmd/notation/internal/plugin/plugin_test.go @@ -1,3 +1,16 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package plugin import ( diff --git a/cmd/notation/plugin/uninstall.go b/cmd/notation/plugin/uninstall.go index 823dd3421..b2a3a3c40 100644 --- a/cmd/notation/plugin/uninstall.go +++ b/cmd/notation/plugin/uninstall.go @@ -1,3 +1,16 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package plugin import ( From cb7e8e6216a10cd8cce9ce998dbfa63bb6340b7a Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 21 Nov 2023 08:06:04 +0800 Subject: [PATCH 17/24] updated per code review Signed-off-by: Patrick Zheng --- cmd/notation/plugin/cmd.go | 4 ++-- cmd/notation/plugin/list.go | 2 +- cmd/notation/plugin/uninstall.go | 20 +++++++++++--------- go.mod | 4 +++- go.sum | 8 ++++---- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/cmd/notation/plugin/cmd.go b/cmd/notation/plugin/cmd.go index 1b97a023a..39e01bcbf 100644 --- a/cmd/notation/plugin/cmd.go +++ b/cmd/notation/plugin/cmd.go @@ -22,8 +22,8 @@ func Cmd() *cobra.Command { } command.AddCommand( - pluginListCommand(), - pluginUninstallCommand(nil), + listCommand(), + uninstallCommand(nil), ) return command diff --git a/cmd/notation/plugin/list.go b/cmd/notation/plugin/list.go index a6ae1a504..e715ac233 100644 --- a/cmd/notation/plugin/list.go +++ b/cmd/notation/plugin/list.go @@ -24,7 +24,7 @@ import ( "github.com/spf13/cobra" ) -func pluginListCommand() *cobra.Command { +func listCommand() *cobra.Command { return &cobra.Command{ Use: "list [flags]", Aliases: []string{"ls"}, diff --git a/cmd/notation/plugin/uninstall.go b/cmd/notation/plugin/uninstall.go index b2a3a3c40..59c57ae8a 100644 --- a/cmd/notation/plugin/uninstall.go +++ b/cmd/notation/plugin/uninstall.go @@ -19,6 +19,8 @@ import ( "os" "github.com/notaryproject/notation-go/dir" + "github.com/notaryproject/notation-go/log" + "github.com/notaryproject/notation-go/plugin" "github.com/notaryproject/notation/cmd/notation/internal/cmdutil" notationplugin "github.com/notaryproject/notation/cmd/notation/internal/plugin" "github.com/notaryproject/notation/internal/cmd" @@ -31,7 +33,7 @@ type pluginUninstallOpts struct { confirmed bool } -func pluginUninstallCommand(opts *pluginUninstallOpts) *cobra.Command { +func uninstallCommand(opts *pluginUninstallOpts) *cobra.Command { if opts == nil { opts = &pluginUninstallOpts{} } @@ -49,7 +51,7 @@ Example - Uninstall plugin: return errors.New("plugin name is required") } if len(args) > 1 { - return errors.New("can only remove one plugin at a time") + return errors.New("only one plugin can be removed at a time") } opts.pluginName = args[0] return nil @@ -65,21 +67,20 @@ Example - Uninstall plugin: } func unInstallPlugin(command *cobra.Command, opts *pluginUninstallOpts) error { - // set log level + // set logger ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) + logger := log.GetLogger(ctx) + pluginName := opts.pluginName _, err := notationplugin.GetPluginMetadataIfExist(ctx, pluginName) if err != nil { if errors.Is(err, os.ErrNotExist) { // plugin does not exist return fmt.Errorf("unable to find plugin %s.\nTo view a list of installed plugins, use `notation plugin list`", pluginName) } - return fmt.Errorf("failed to uninstall %s: %w", pluginName, err) + // plugin exists, but the binary is corrupted + logger.Warnf("%s plugin binary file is corrupted: %v", pluginName, err) } // core process - pluginPath, err := dir.PluginFS().SysPath(pluginName) - if err != nil { - return fmt.Errorf("failed to uninstall %s: %v", pluginName, err) - } prompt := fmt.Sprintf("Are you sure you want to uninstall plugin %q?", pluginName) confirmed, err := cmdutil.AskForConfirmation(os.Stdin, prompt, opts.confirmed) if err != nil { @@ -88,7 +89,8 @@ func unInstallPlugin(command *cobra.Command, opts *pluginUninstallOpts) error { if !confirmed { return nil } - if err := os.RemoveAll(pluginPath); err != nil { + mgr := plugin.NewCLIManager(dir.PluginFS()) + if err := mgr.Uninstall(ctx, pluginName); err != nil { return fmt.Errorf("failed to uninstall %s: %v", pluginName, err) } fmt.Printf("Successfully uninstalled plugin %s\n", pluginName) diff --git a/go.mod b/go.mod index 5bbb4c412..f44d287d7 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,9 @@ require ( github.com/veraison/go-cose v1.1.0 // indirect github.com/x448/float16 v0.8.4 // indirect golang.org/x/crypto v0.14.0 // indirect - golang.org/x/mod v0.13.0 // indirect + golang.org/x/mod v0.14.0 // indirect golang.org/x/sync v0.4.0 // indirect golang.org/x/sys v0.13.0 // indirect ) + +replace github.com/notaryproject/notation-go => github.com/Two-Hearts/notation-go v0.0.0-20231120090525-20d595da2f1c diff --git a/go.sum b/go.sum index d30a5638e..5ae6d1e85 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/Two-Hearts/notation-go v0.0.0-20231120090525-20d595da2f1c h1:RSFsIS78snn5DECUsvJwLGhHjF9gkKaYETKVyTTpdC8= +github.com/Two-Hearts/notation-go v0.0.0-20231120090525-20d595da2f1c/go.mod h1:RmhTMqfhQQhE2JK8LGZs17KDgW4gPGbK5y6aeRk/bBI= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -20,8 +22,6 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/notaryproject/notation-core-go v1.0.1 h1:01doxjDERbd0vocLQrlJdusKrRLNNn50OJzp0c5I4Cw= github.com/notaryproject/notation-core-go v1.0.1/go.mod h1:rayl8WlKgS4YxOZgDO0iGGB4Ef515ZFZUFaZDmsPXgE= -github.com/notaryproject/notation-go v1.0.1 h1:D3fqG3eaBKVESRySV/Tg//MyTg2Q1nTKPh/t2q9LpSw= -github.com/notaryproject/notation-go v1.0.1/go.mod h1:VonyZsbocRQQNIDq/VPV5jKJOQwDH3gvfK4cXNpUA0U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= @@ -55,8 +55,8 @@ golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= From abe3eb92abf341382c7ac0edfeb4413d4ac6d254 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 21 Nov 2023 09:46:45 +0800 Subject: [PATCH 18/24] updated per code review Signed-off-by: Patrick Zheng --- cmd/notation/internal/errors/errors.go | 13 ------------- cmd/notation/plugin/uninstall.go | 4 ++-- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 4 files changed, 14 insertions(+), 27 deletions(-) diff --git a/cmd/notation/internal/errors/errors.go b/cmd/notation/internal/errors/errors.go index 631e4107e..bb06e6f6f 100644 --- a/cmd/notation/internal/errors/errors.go +++ b/cmd/notation/internal/errors/errors.go @@ -50,16 +50,3 @@ type ErrorExceedMaxSignatures struct { func (e ErrorExceedMaxSignatures) Error() string { return fmt.Sprintf("exceeded configured limit of max signatures %d to examine", e.MaxSignatures) } - -// ErrorInvalidPluginName is used when a plugin executable file name does not -// follow the spec. -type ErrorInvalidPluginName struct { - Msg string -} - -func (e ErrorInvalidPluginName) Error() string { - if e.Msg != "" { - return e.Msg - } - return "invalid plugin file name" -} diff --git a/cmd/notation/plugin/uninstall.go b/cmd/notation/plugin/uninstall.go index 59c57ae8a..77f60430c 100644 --- a/cmd/notation/plugin/uninstall.go +++ b/cmd/notation/plugin/uninstall.go @@ -78,13 +78,13 @@ func unInstallPlugin(command *cobra.Command, opts *pluginUninstallOpts) error { return fmt.Errorf("unable to find plugin %s.\nTo view a list of installed plugins, use `notation plugin list`", pluginName) } // plugin exists, but the binary is corrupted - logger.Warnf("%s plugin binary file is corrupted: %v", pluginName, err) + logger.Infof("Uninstalling...Found plugin %s binary file is corrupted: %v", pluginName, err) } // core process prompt := fmt.Sprintf("Are you sure you want to uninstall plugin %q?", pluginName) confirmed, err := cmdutil.AskForConfirmation(os.Stdin, prompt, opts.confirmed) if err != nil { - return fmt.Errorf("failed to uninstall %s: %v", pluginName, err) + return fmt.Errorf("failed when asking for confirmation: %v", err) } if !confirmed { return nil diff --git a/go.mod b/go.mod index f44d287d7..4adabfde2 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 - golang.org/x/term v0.13.0 + golang.org/x/term v0.14.0 oras.land/oras-go/v2 v2.3.1 ) @@ -25,10 +25,10 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/veraison/go-cose v1.1.0 // indirect github.com/x448/float16 v0.8.4 // indirect - golang.org/x/crypto v0.14.0 // indirect + golang.org/x/crypto v0.15.0 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/sync v0.4.0 // indirect - golang.org/x/sys v0.13.0 // indirect + golang.org/x/sys v0.14.0 // indirect ) -replace github.com/notaryproject/notation-go => github.com/Two-Hearts/notation-go v0.0.0-20231120090525-20d595da2f1c +replace github.com/notaryproject/notation-go => github.com/Two-Hearts/notation-go v0.0.0-20231121014208-8fef0ac0fece diff --git a/go.sum b/go.sum index 5ae6d1e85..4cd9e0144 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/Two-Hearts/notation-go v0.0.0-20231120090525-20d595da2f1c h1:RSFsIS78snn5DECUsvJwLGhHjF9gkKaYETKVyTTpdC8= -github.com/Two-Hearts/notation-go v0.0.0-20231120090525-20d595da2f1c/go.mod h1:RmhTMqfhQQhE2JK8LGZs17KDgW4gPGbK5y6aeRk/bBI= +github.com/Two-Hearts/notation-go v0.0.0-20231121014208-8fef0ac0fece h1:uz3EPkMm3T8OW3+mmifh94IbfxHJWOD9hdGRpLA8+F0= +github.com/Two-Hearts/notation-go v0.0.0-20231121014208-8fef0ac0fece/go.mod h1:tSCFsAdKAtB7AfKS/BaUf8AXzASA+9TEokMDEDutqPM= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -51,8 +51,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= @@ -76,15 +76,15 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= +golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= From 4580723279dae691e20b416c398d2d25a3f9f5fb Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 21 Nov 2023 10:09:12 +0800 Subject: [PATCH 19/24] updated dependency Signed-off-by: Patrick Zheng --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4adabfde2..8b0d1ae0f 100644 --- a/go.mod +++ b/go.mod @@ -31,4 +31,4 @@ require ( golang.org/x/sys v0.14.0 // indirect ) -replace github.com/notaryproject/notation-go => github.com/Two-Hearts/notation-go v0.0.0-20231121014208-8fef0ac0fece +replace github.com/notaryproject/notation-go => github.com/Two-Hearts/notation-go v0.0.0-20231121020218-b33af9da5b32 diff --git a/go.sum b/go.sum index 4cd9e0144..dc0d58024 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/Two-Hearts/notation-go v0.0.0-20231121014208-8fef0ac0fece h1:uz3EPkMm3T8OW3+mmifh94IbfxHJWOD9hdGRpLA8+F0= -github.com/Two-Hearts/notation-go v0.0.0-20231121014208-8fef0ac0fece/go.mod h1:tSCFsAdKAtB7AfKS/BaUf8AXzASA+9TEokMDEDutqPM= +github.com/Two-Hearts/notation-go v0.0.0-20231121020218-b33af9da5b32 h1:Nmfoa9QzURiFw/AwOpc20/2ReYdr8NCMSZNcZer2KwI= +github.com/Two-Hearts/notation-go v0.0.0-20231121020218-b33af9da5b32/go.mod h1:tSCFsAdKAtB7AfKS/BaUf8AXzASA+9TEokMDEDutqPM= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= From 0d8513a668b866cde262b7832ca820752dc38fee Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 23 Nov 2023 11:18:15 +0800 Subject: [PATCH 20/24] updated dependency Signed-off-by: Patrick Zheng --- go.mod | 4 +--- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 8b0d1ae0f..ce90192e9 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( github.com/notaryproject/notation-core-go v1.0.1 - github.com/notaryproject/notation-go v1.0.1 + github.com/notaryproject/notation-go v1.0.2-0.20231123031546-5de0d58b21c1 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0-rc5 github.com/oras-project/oras-credentials-go v0.3.1 @@ -30,5 +30,3 @@ require ( golang.org/x/sync v0.4.0 // indirect golang.org/x/sys v0.14.0 // indirect ) - -replace github.com/notaryproject/notation-go => github.com/Two-Hearts/notation-go v0.0.0-20231121020218-b33af9da5b32 diff --git a/go.sum b/go.sum index dc0d58024..4f1f59a82 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/Two-Hearts/notation-go v0.0.0-20231121020218-b33af9da5b32 h1:Nmfoa9QzURiFw/AwOpc20/2ReYdr8NCMSZNcZer2KwI= -github.com/Two-Hearts/notation-go v0.0.0-20231121020218-b33af9da5b32/go.mod h1:tSCFsAdKAtB7AfKS/BaUf8AXzASA+9TEokMDEDutqPM= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -22,6 +20,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/notaryproject/notation-core-go v1.0.1 h1:01doxjDERbd0vocLQrlJdusKrRLNNn50OJzp0c5I4Cw= github.com/notaryproject/notation-core-go v1.0.1/go.mod h1:rayl8WlKgS4YxOZgDO0iGGB4Ef515ZFZUFaZDmsPXgE= +github.com/notaryproject/notation-go v1.0.2-0.20231123031546-5de0d58b21c1 h1:TuSZ+3Eu3A/XKucl7J95sDT8XoG6t2dEcIipt6ydAls= +github.com/notaryproject/notation-go v1.0.2-0.20231123031546-5de0d58b21c1/go.mod h1:tSCFsAdKAtB7AfKS/BaUf8AXzASA+9TEokMDEDutqPM= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= From 72e668b53f36d57211dcde510627ecc4d064a354 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 23 Nov 2023 14:12:25 +0800 Subject: [PATCH 21/24] updated per code review Signed-off-by: Patrick Zheng --- cmd/notation/plugin/uninstall.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/notation/plugin/uninstall.go b/cmd/notation/plugin/uninstall.go index 77f60430c..e1701a95c 100644 --- a/cmd/notation/plugin/uninstall.go +++ b/cmd/notation/plugin/uninstall.go @@ -78,7 +78,7 @@ func unInstallPlugin(command *cobra.Command, opts *pluginUninstallOpts) error { return fmt.Errorf("unable to find plugin %s.\nTo view a list of installed plugins, use `notation plugin list`", pluginName) } // plugin exists, but the binary is corrupted - logger.Infof("Uninstalling...Found plugin %s binary file is corrupted: %v", pluginName, err) + logger.Infof("Uninstalling...Found plugin %s binary file is malfunctioning: %v", pluginName, err) } // core process prompt := fmt.Sprintf("Are you sure you want to uninstall plugin %q?", pluginName) From 5098fac52ca6198cc8cdce57e66d02bf684a6a8a Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 24 Nov 2023 18:29:33 +0800 Subject: [PATCH 22/24] updated Signed-off-by: Patrick Zheng --- cmd/notation/internal/plugin/plugin.go | 32 --------------------- cmd/notation/internal/plugin/plugin_test.go | 28 ------------------ cmd/notation/plugin/uninstall.go | 17 +++++++++-- 3 files changed, 14 insertions(+), 63 deletions(-) delete mode 100644 cmd/notation/internal/plugin/plugin.go delete mode 100644 cmd/notation/internal/plugin/plugin_test.go diff --git a/cmd/notation/internal/plugin/plugin.go b/cmd/notation/internal/plugin/plugin.go deleted file mode 100644 index 062660d36..000000000 --- a/cmd/notation/internal/plugin/plugin.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright The Notary Project Authors. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package plugin - -import ( - "context" - - "github.com/notaryproject/notation-go/dir" - "github.com/notaryproject/notation-go/plugin" - "github.com/notaryproject/notation-go/plugin/proto" -) - -// GetPluginMetadataIfExist returns plugin's metadata if it exists in Notation -func GetPluginMetadataIfExist(ctx context.Context, pluginName string) (*proto.GetMetadataResponse, error) { - mgr := plugin.NewCLIManager(dir.PluginFS()) - plugin, err := mgr.Get(ctx, pluginName) - if err != nil { - return nil, err - } - return plugin.GetMetadata(ctx, &proto.GetMetadataRequest{}) -} diff --git a/cmd/notation/internal/plugin/plugin_test.go b/cmd/notation/internal/plugin/plugin_test.go deleted file mode 100644 index 8b3651f9e..000000000 --- a/cmd/notation/internal/plugin/plugin_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright The Notary Project Authors. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package plugin - -import ( - "context" - "errors" - "os" - "testing" -) - -func TestGetPluginMetadataIfExist(t *testing.T) { - _, err := GetPluginMetadataIfExist(context.Background(), "non-exist-plugin") - if !errors.Is(err, os.ErrNotExist) { - t.Fatalf("expected os.ErrNotExist err, got: %v", err) - } -} diff --git a/cmd/notation/plugin/uninstall.go b/cmd/notation/plugin/uninstall.go index e1701a95c..732c8f4ad 100644 --- a/cmd/notation/plugin/uninstall.go +++ b/cmd/notation/plugin/uninstall.go @@ -14,6 +14,7 @@ package plugin import ( + "context" "errors" "fmt" "os" @@ -21,8 +22,8 @@ import ( "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/log" "github.com/notaryproject/notation-go/plugin" + "github.com/notaryproject/notation-go/plugin/proto" "github.com/notaryproject/notation/cmd/notation/internal/cmdutil" - notationplugin "github.com/notaryproject/notation/cmd/notation/internal/plugin" "github.com/notaryproject/notation/internal/cmd" "github.com/spf13/cobra" ) @@ -72,12 +73,12 @@ func unInstallPlugin(command *cobra.Command, opts *pluginUninstallOpts) error { logger := log.GetLogger(ctx) pluginName := opts.pluginName - _, err := notationplugin.GetPluginMetadataIfExist(ctx, pluginName) + _, err := getPluginMetadataIfExist(ctx, pluginName) if err != nil { if errors.Is(err, os.ErrNotExist) { // plugin does not exist return fmt.Errorf("unable to find plugin %s.\nTo view a list of installed plugins, use `notation plugin list`", pluginName) } - // plugin exists, but the binary is corrupted + // plugin exists, but the binary is malfunctioning logger.Infof("Uninstalling...Found plugin %s binary file is malfunctioning: %v", pluginName, err) } // core process @@ -96,3 +97,13 @@ func unInstallPlugin(command *cobra.Command, opts *pluginUninstallOpts) error { fmt.Printf("Successfully uninstalled plugin %s\n", pluginName) return nil } + +// getPluginMetadataIfExist returns plugin's metadata if it exists in Notation +func getPluginMetadataIfExist(ctx context.Context, pluginName string) (*proto.GetMetadataResponse, error) { + mgr := plugin.NewCLIManager(dir.PluginFS()) + plugin, err := mgr.Get(ctx, pluginName) + if err != nil { + return nil, err + } + return plugin.GetMetadata(ctx, &proto.GetMetadataRequest{}) +} From d764ebaf88741da237a6437f5ae09d4b48b8f21a Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Mon, 27 Nov 2023 10:35:44 +0800 Subject: [PATCH 23/24] update Signed-off-by: Patrick Zheng --- cmd/notation/plugin/uninstall.go | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/cmd/notation/plugin/uninstall.go b/cmd/notation/plugin/uninstall.go index 732c8f4ad..80822f784 100644 --- a/cmd/notation/plugin/uninstall.go +++ b/cmd/notation/plugin/uninstall.go @@ -20,9 +20,7 @@ import ( "os" "github.com/notaryproject/notation-go/dir" - "github.com/notaryproject/notation-go/log" "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/cmd" "github.com/spf13/cobra" @@ -70,40 +68,40 @@ Example - Uninstall plugin: func unInstallPlugin(command *cobra.Command, opts *pluginUninstallOpts) error { // set logger ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) - logger := log.GetLogger(ctx) - pluginName := opts.pluginName - _, err := getPluginMetadataIfExist(ctx, pluginName) + exist, err := checkPluginExistence(ctx, pluginName) if err != nil { - if errors.Is(err, os.ErrNotExist) { // plugin does not exist - return fmt.Errorf("unable to find plugin %s.\nTo view a list of installed plugins, use `notation plugin list`", pluginName) - } - // plugin exists, but the binary is malfunctioning - logger.Infof("Uninstalling...Found plugin %s binary file is malfunctioning: %v", pluginName, err) + return fmt.Errorf("failed to check plugin existence: %w", err) + } + if !exist { + return fmt.Errorf("unable to find plugin %s.\nTo view a list of installed plugins, use `notation plugin list`", pluginName) } // core process prompt := fmt.Sprintf("Are you sure you want to uninstall plugin %q?", pluginName) confirmed, err := cmdutil.AskForConfirmation(os.Stdin, prompt, opts.confirmed) if err != nil { - return fmt.Errorf("failed when asking for confirmation: %v", err) + return fmt.Errorf("failed when asking for confirmation: %w", err) } if !confirmed { return nil } mgr := plugin.NewCLIManager(dir.PluginFS()) if err := mgr.Uninstall(ctx, pluginName); err != nil { - return fmt.Errorf("failed to uninstall %s: %v", pluginName, err) + return fmt.Errorf("failed to uninstall %s: %w", pluginName, err) } fmt.Printf("Successfully uninstalled plugin %s\n", pluginName) return nil } -// getPluginMetadataIfExist returns plugin's metadata if it exists in Notation -func getPluginMetadataIfExist(ctx context.Context, pluginName string) (*proto.GetMetadataResponse, error) { +// checkPluginExistence returns true if plugin exists in the system +func checkPluginExistence(ctx context.Context, pluginName string) (bool, error) { mgr := plugin.NewCLIManager(dir.PluginFS()) - plugin, err := mgr.Get(ctx, pluginName) + _, err := mgr.Get(ctx, pluginName) if err != nil { - return nil, err + if errors.Is(err, os.ErrNotExist) { // plugin does not exist + return false, nil + } + return false, err } - return plugin.GetMetadata(ctx, &proto.GetMetadataRequest{}) + return true, nil } From 08fce421fd51e731e31e0d8d3f8c535566c859e4 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 28 Nov 2023 16:29:06 +0800 Subject: [PATCH 24/24] updated per code review Signed-off-by: Patrick Zheng --- cmd/notation/plugin/uninstall.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/notation/plugin/uninstall.go b/cmd/notation/plugin/uninstall.go index 80822f784..d60b77e9f 100644 --- a/cmd/notation/plugin/uninstall.go +++ b/cmd/notation/plugin/uninstall.go @@ -56,7 +56,7 @@ Example - Uninstall plugin: return nil }, RunE: func(cmd *cobra.Command, args []string) error { - return unInstallPlugin(cmd, opts) + return uninstallPlugin(cmd, opts) }, } @@ -65,7 +65,7 @@ Example - Uninstall plugin: return command } -func unInstallPlugin(command *cobra.Command, opts *pluginUninstallOpts) error { +func uninstallPlugin(command *cobra.Command, opts *pluginUninstallOpts) error { // set logger ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) pluginName := opts.pluginName