Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: notation plugin uninstall command #832

Closed
wants to merge 36 commits into from
Closed
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
fb763d9
added zip suport
Two-Hearts Oct 19, 2023
dd75b5d
Merge branch 'notaryproject:main' into plugin-1.1.0
Two-Hearts Oct 19, 2023
c326db2
added tar.gz support
Two-Hearts Oct 20, 2023
d19ec82
updated
Two-Hearts Oct 23, 2023
bf3d6e9
added uninstall
Two-Hearts Oct 23, 2023
a6379c1
added install from URL
Two-Hearts Oct 23, 2023
4b5989c
updated
Two-Hearts Oct 24, 2023
1550236
fix
Two-Hearts Oct 24, 2023
ea75514
updated
Two-Hearts Oct 24, 2023
ce9439f
Merge branch 'notaryproject:main' into plugin-1.1.0
Two-Hearts Oct 31, 2023
f7c80b8
notation pluing uninstall
Two-Hearts Oct 31, 2023
d6d16f4
notation plugin uninstall
Two-Hearts Oct 31, 2023
ab59eec
test
Two-Hearts Oct 31, 2023
1e173a4
update
Two-Hearts Oct 31, 2023
695fbb3
Merge branch 'notaryproject:main' into plugin-uninstall
Two-Hearts Nov 20, 2023
5ca1d44
updated based on spec
Two-Hearts Nov 20, 2023
eae5b25
fixing e2e
Two-Hearts Nov 20, 2023
8a37514
fixing e2e
Two-Hearts Nov 20, 2023
68fd585
added license headers
Two-Hearts Nov 20, 2023
49bedec
Merge branch 'notaryproject:main' into plugin-uninstall
Two-Hearts Nov 20, 2023
e540d16
Merge branch 'notaryproject:main' into plugin-uninstall
Two-Hearts Nov 21, 2023
b8083ad
updated per code review
Two-Hearts Nov 21, 2023
57d1b9b
Merge branch 'plugin-uninstall' of https://github.com/Two-Hearts/nota…
Two-Hearts Nov 21, 2023
72c4a42
updated per code review
Two-Hearts Nov 21, 2023
d1b32b5
updated dependency
Two-Hearts Nov 21, 2023
6f6d9c8
updated dependency
Two-Hearts Nov 23, 2023
d35f513
updated per code review
Two-Hearts Nov 23, 2023
4d9639b
Merge branch 'notaryproject:main' into plugin-uninstall
Two-Hearts Nov 24, 2023
51caed0
updated
Two-Hearts Nov 24, 2023
8a17628
update
Two-Hearts Nov 27, 2023
b6a233e
Merge branch 'notaryproject:main' into plugin-uninstall
Two-Hearts Nov 28, 2023
039e139
updated per code review
Two-Hearts Nov 28, 2023
0329eae
Merge branch 'plugin-uninstall' of https://github.com/Two-Hearts/nota…
Two-Hearts Nov 28, 2023
5a50e9f
updated per code review
Two-Hearts Nov 28, 2023
d452f13
Merge branch 'main' into plugin-uninstall
Two-Hearts Nov 28, 2023
2741c85
Merge branch 'plugin-uninstall' of https://github.com/Two-Hearts/nota…
Two-Hearts Nov 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions cmd/notation/internal/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Two-Hearts marked this conversation as resolved.
Show resolved Hide resolved
}
32 changes: 32 additions & 0 deletions cmd/notation/internal/plugin/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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) {
Two-Hearts marked this conversation as resolved.
Show resolved Hide resolved
mgr := plugin.NewCLIManager(dir.PluginFS())
plugin, err := mgr.Get(ctx, pluginName)
if err != nil {
return nil, err
}
return plugin.GetMetadata(ctx, &proto.GetMetadataRequest{})
}
28 changes: 28 additions & 0 deletions cmd/notation/internal/plugin/plugin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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)
}
}
3 changes: 2 additions & 1 deletion cmd/notation/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -40,7 +41,7 @@ func main() {
cert.Cmd(),
policy.Cmd(),
keyCommand(),
pluginCommand(),
plugin.Cmd(),
loginCommand(nil),
logoutCommand(nil),
versionCommand(),
Expand Down
30 changes: 30 additions & 0 deletions cmd/notation/plugin/cmd.go
Original file line number Diff line number Diff line change
@@ -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(),
pluginUninstallCommand(nil),
Two-Hearts marked this conversation as resolved.
Show resolved Hide resolved
)

return command
}
11 changes: 1 addition & 10 deletions cmd/notation/plugin.go → cmd/notation/plugin/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package main
package plugin

import (
"fmt"
Expand All @@ -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]",
Expand Down
96 changes: 96 additions & 0 deletions cmd/notation/plugin/uninstall.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// 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 (
"errors"
"fmt"
"os"

"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
}

func pluginUninstallCommand(opts *pluginUninstallOpts) *cobra.Command {
if opts == nil {
opts = &pluginUninstallOpts{}
}
command := &cobra.Command{
Use: "uninstall [flags] <plugin_name>",
Aliases: []string{"remove", "rm"},
Short: "Uninstall a plugin",
Long: `Uninstall a plugin

Example - Uninstall 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")
Two-Hearts marked this conversation as resolved.
Show resolved Hide resolved
}
opts.pluginName = args[0]
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return unInstallPlugin(cmd, opts)
},
}

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 {
Two-Hearts marked this conversation as resolved.
Show resolved Hide resolved
// set log level
ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context())
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)
Two-Hearts marked this conversation as resolved.
Show resolved Hide resolved
}
// 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 {
return fmt.Errorf("failed to uninstall %s: %v", pluginName, err)
Two-Hearts marked this conversation as resolved.
Show resolved Hide resolved
}
if !confirmed {
return nil
}
if err := os.RemoveAll(pluginPath); err != nil {
return fmt.Errorf("failed to uninstall %s: %v", pluginName, err)
}
Two-Hearts marked this conversation as resolved.
Show resolved Hide resolved
fmt.Printf("Successfully uninstalled plugin %s\n", pluginName)
return nil
}
4 changes: 2 additions & 2 deletions internal/osutil/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,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
}
Expand Down
37 changes: 20 additions & 17 deletions test/e2e/internal/notation/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,30 +33,32 @@ 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 (
// NotationBinPath is the notation binary path.
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 (
Expand Down Expand Up @@ -90,6 +92,7 @@ func setUpNotationValues() {

// set Notation e2e-plugin path
setPathValue(envKeyNotationPluginPath, &NotationE2EPluginPath)
setPathValue(envKeyNotationPluginTarGzPath, &NotationE2EPluginTarGzPath)

// set Notation configuration paths
setPathValue(envKeyNotationConfigPath, &NotationE2EConfigPath)
Expand Down
5 changes: 3 additions & 2 deletions test/e2e/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
38 changes: 38 additions & 0 deletions test/e2e/suite/plugin/uninstall.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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) {
vhost.SetOption(AddPlugin(NotationE2EPluginPath))
notation.Exec("plugin", "uninstall", "--yes", "e2e-plugin").
MatchContent("Successfully 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")
})
})

})
Loading