Skip to content

Commit

Permalink
feat: add notation plugin uninstall command (#842)
Browse files Browse the repository at this point in the history
This PR is based on the spec PR: #809. It adds the notation plugin uninstall command.

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
  • Loading branch information
Two-Hearts committed Dec 5, 2023
1 parent acb9707 commit 49d48bc
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 48 deletions.
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(
listCommand(),
uninstallCommand(nil),
)

return command
}
13 changes: 2 additions & 11 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,16 +24,7 @@ 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 {
func listCommand() *cobra.Command {
return &cobra.Command{
Use: "list [flags]",
Aliases: []string{"ls"},
Expand Down
107 changes: 107 additions & 0 deletions cmd/notation/plugin/uninstall.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// 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"
"fmt"
"os"

"github.com/notaryproject/notation-go/dir"
"github.com/notaryproject/notation-go/plugin"
"github.com/notaryproject/notation/cmd/notation/internal/cmdutil"
"github.com/notaryproject/notation/internal/cmd"
"github.com/spf13/cobra"
)

type pluginUninstallOpts struct {
cmd.LoggingFlagOpts
pluginName string
confirmed bool
}

func uninstallCommand(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("only one plugin can be removed at a time")
}
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 {
// set logger
ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context())
pluginName := opts.pluginName
exist, err := checkPluginExistence(ctx, pluginName)
if err != nil {
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: %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: %w", pluginName, err)
}
fmt.Printf("Successfully uninstalled plugin %s\n", pluginName)
return nil
}

// checkPluginExistence returns true if plugin exists in the system
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) { // plugin does not exist
return false, nil
}
return false, err
}
return true, nil
}
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ 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
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
)

Expand All @@ -25,8 +25,8 @@ 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/mod v0.13.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
)
20 changes: 10 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +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.1 h1:D3fqG3eaBKVESRySV/Tg//MyTg2Q1nTKPh/t2q9LpSw=
github.com/notaryproject/notation-go v1.0.1/go.mod h1:VonyZsbocRQQNIDq/VPV5jKJOQwDH3gvfK4cXNpUA0U=
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=
Expand Down Expand Up @@ -51,12 +51,12 @@ 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.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=
Expand All @@ -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=
Expand Down
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")
})
})

})

0 comments on commit 49d48bc

Please sign in to comment.