فهرست منبع

Merge pull request #79 from chris-crone/context-rm

Add context rm command
Guillaume Tardif 5 سال پیش
والد
کامیت
102116315a
8فایلهای تغییر یافته به همراه187 افزوده شده و 5 حذف شده
  1. 26 0
      cli/cmd/context.go
  2. 23 3
      context/store/store.go
  3. 27 1
      context/store/store_test.go
  4. 56 0
      errdefs/errors.go
  5. 49 0
      errdefs/errors_test.go
  6. 1 0
      go.mod
  7. 4 0
      go.sum
  8. 1 1
      tests/e2e/e2e.go

+ 26 - 0
cli/cmd/context.go

@@ -33,6 +33,7 @@ import (
 	"os"
 	"text/tabwriter"
 
+	"github.com/hashicorp/go-multierror"
 	"github.com/spf13/cobra"
 
 	"github.com/docker/api/context/store"
@@ -48,6 +49,7 @@ func ContextCommand() *cobra.Command {
 	cmd.AddCommand(
 		createCommand(),
 		listCommand(),
+		removeCommand(),
 	)
 
 	return cmd
@@ -91,6 +93,17 @@ func listCommand() *cobra.Command {
 	return cmd
 }
 
+func removeCommand() *cobra.Command {
+	return &cobra.Command{
+		Use:     "rm",
+		Aliases: []string{"remove"},
+		Args:    cobra.MinimumNArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runRemove(cmd.Context(), args)
+		},
+	}
+}
+
 func runCreate(ctx context.Context, opts createOpts, name string, contextType string) error {
 	switch contextType {
 	case "aci":
@@ -134,3 +147,16 @@ func runList(ctx context.Context) error {
 
 	return w.Flush()
 }
+
+func runRemove(ctx context.Context, args []string) error {
+	s := store.ContextStore(ctx)
+	var errs *multierror.Error
+	for _, n := range args {
+		if err := s.Remove(n); err != nil {
+			errs = multierror.Append(errs, err)
+		} else {
+			fmt.Println(n)
+		}
+	}
+	return errs.ErrorOrNil()
+}

+ 23 - 3
context/store/store.go

@@ -36,7 +36,9 @@ import (
 	"path/filepath"
 	"reflect"
 
+	"github.com/docker/api/errdefs"
 	"github.com/opencontainers/go-digest"
+	"github.com/pkg/errors"
 )
 
 const (
@@ -63,13 +65,15 @@ type Store interface {
 	// Get returns the context with name, it returns an error if the  context
 	// doesn't exist
 	Get(name string, getter func() interface{}) (*Metadata, error)
-	// GetType reurns the type of the context (docker, aci etc)
+	// GetType returns the type of the context (docker, aci etc)
 	GetType(meta *Metadata) string
 	// Create creates a new context, it returns an error if a context with the
 	// same name exists already.
 	Create(name string, data TypedContext) error
 	// List returns the list of created contexts
 	List() ([]*Metadata, error)
+	// Remove removes a context by name from the context store
+	Remove(name string) error
 }
 
 type store struct {
@@ -118,7 +122,7 @@ func (s *store) Get(name string, getter func() interface{}) (*Metadata, error) {
 	meta := filepath.Join(s.root, contextsDir, metadataDir, contextdirOf(name), metaFile)
 	m, err := read(meta, getter)
 	if os.IsNotExist(err) {
-		return nil, fmt.Errorf("unknown context %q", name)
+		return nil, errors.Wrap(errdefs.ErrNotFound, objectName(name))
 	} else if err != nil {
 		return nil, err
 	}
@@ -186,7 +190,7 @@ func (s *store) Create(name string, data TypedContext) error {
 	dir := contextdirOf(name)
 	metaDir := filepath.Join(s.root, contextsDir, metadataDir, dir)
 	if _, err := os.Stat(metaDir); !os.IsNotExist(err) {
-		return fmt.Errorf("context %q already exists", name)
+		return errors.Wrap(errdefs.ErrAlreadyExists, objectName(name))
 	}
 
 	err := os.Mkdir(metaDir, 0755)
@@ -237,10 +241,26 @@ func (s *store) List() ([]*Metadata, error) {
 	return result, nil
 }
 
+func (s *store) Remove(name string) error {
+	dir := filepath.Join(s.root, contextsDir, metadataDir, contextdirOf(name))
+	// Check if directory exists because os.RemoveAll returns nil if it doesn't
+	if _, err := os.Stat(dir); os.IsNotExist(err) {
+		return errors.Wrap(errdefs.ErrNotFound, objectName(name))
+	}
+	if err := os.RemoveAll(dir); err != nil {
+		return errors.Wrapf(errdefs.ErrUnknown, "unable to remove %s: %s", objectName(name), err)
+	}
+	return nil
+}
+
 func contextdirOf(name string) string {
 	return digest.FromString(name).Encoded()
 }
 
+func objectName(name string) string {
+	return fmt.Sprintf("context %q", name)
+}
+
 type dummyContext struct{}
 
 // Metadata represents the docker context metadata

+ 27 - 1
context/store/store_test.go

@@ -33,6 +33,7 @@ import (
 	"os"
 	"testing"
 
+	"github.com/docker/api/errdefs"
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/suite"
 )
@@ -62,12 +63,17 @@ func (suite *StoreTestSuite) AfterTest(suiteName, testName string) {
 func (suite *StoreTestSuite) TestCreate() {
 	err := suite.store.Create("test", TypedContext{})
 	require.Nil(suite.T(), err)
+
+	err = suite.store.Create("test", TypedContext{})
+	require.EqualError(suite.T(), err, `context "test": already exists`)
+	require.True(suite.T(), errdefs.IsAlreadyExistsError(err))
 }
 
 func (suite *StoreTestSuite) TestGetUnknown() {
 	meta, err := suite.store.Get("unknown", nil)
 	require.Nil(suite.T(), meta)
-	require.Error(suite.T(), err)
+	require.EqualError(suite.T(), err, `context "unknown": not found`)
+	require.True(suite.T(), errdefs.IsNotFoundError(err))
 }
 
 func (suite *StoreTestSuite) TestGet() {
@@ -101,6 +107,26 @@ func (suite *StoreTestSuite) TestList() {
 	require.Equal(suite.T(), contexts[1].Name, "test2")
 }
 
+func (suite *StoreTestSuite) TestRemoveNotFound() {
+	err := suite.store.Remove("notfound")
+	require.EqualError(suite.T(), err, `context "notfound": not found`)
+	require.True(suite.T(), errdefs.IsNotFoundError(err))
+}
+
+func (suite *StoreTestSuite) TestRemove() {
+	err := suite.store.Create("testremove", TypedContext{})
+	require.Nil(suite.T(), err)
+	contexts, err := suite.store.List()
+	require.Nil(suite.T(), err)
+	require.Equal(suite.T(), len(contexts), 1)
+
+	err = suite.store.Remove("testremove")
+	require.Nil(suite.T(), err)
+	contexts, err = suite.store.List()
+	require.Nil(suite.T(), err)
+	require.Equal(suite.T(), len(contexts), 0)
+}
+
 func TestExampleTestSuite(t *testing.T) {
 	suite.Run(t, new(StoreTestSuite))
 }

+ 56 - 0
errdefs/errors.go

@@ -0,0 +1,56 @@
+/*
+	Copyright (c) 2020 Docker Inc.
+
+	Permission is hereby granted, free of charge, to any person
+	obtaining a copy of this software and associated documentation
+	files (the "Software"), to deal in the Software without
+	restriction, including without limitation the rights to use, copy,
+	modify, merge, publish, distribute, sublicense, and/or sell copies
+	of the Software, and to permit persons to whom the Software is
+	furnished to do so, subject to the following conditions:
+
+	The above copyright notice and this permission notice shall be
+	included in all copies or substantial portions of the Software.
+
+	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+	EXPRESS OR IMPLIED,
+	INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+	IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+	HOLDERS BE LIABLE FOR ANY CLAIM,
+	DAMAGES OR OTHER LIABILITY,
+	WHETHER IN AN ACTION OF CONTRACT,
+	TORT OR OTHERWISE,
+	ARISING FROM, OUT OF OR IN CONNECTION WITH
+	THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+package errdefs
+
+import (
+	"github.com/pkg/errors"
+)
+
+var (
+	// ErrNotFound is returned when an object is not found
+	ErrNotFound = errors.New("not found")
+	// ErrAlreadyExists is returned when an object already exists
+	ErrAlreadyExists = errors.New("already exists")
+	// ErrUnknown is returned when the error type is unmapped
+	ErrUnknown = errors.New("unknown")
+)
+
+// IsNotFoundError returns true if the unwrapped error is ErrNotFound
+func IsNotFoundError(err error) bool {
+	return errors.Is(err, ErrNotFound)
+}
+
+// IsAlreadyExistsError returns true if the unwrapped error is ErrAlreadyExists
+func IsAlreadyExistsError(err error) bool {
+	return errors.Is(err, ErrAlreadyExists)
+}
+
+// IsUnknownError returns true if the unwrapped error is ErrUnknown
+func IsUnknownError(err error) bool {
+	return errors.Is(err, ErrUnknown)
+}

+ 49 - 0
errdefs/errors_test.go

@@ -0,0 +1,49 @@
+/*
+	Copyright (c) 2020 Docker Inc.
+
+	Permission is hereby granted, free of charge, to any person
+	obtaining a copy of this software and associated documentation
+	files (the "Software"), to deal in the Software without
+	restriction, including without limitation the rights to use, copy,
+	modify, merge, publish, distribute, sublicense, and/or sell copies
+	of the Software, and to permit persons to whom the Software is
+	furnished to do so, subject to the following conditions:
+
+	The above copyright notice and this permission notice shall be
+	included in all copies or substantial portions of the Software.
+
+	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+	EXPRESS OR IMPLIED,
+	INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+	IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+	HOLDERS BE LIABLE FOR ANY CLAIM,
+	DAMAGES OR OTHER LIABILITY,
+	WHETHER IN AN ACTION OF CONTRACT,
+	TORT OR OTHERWISE,
+	ARISING FROM, OUT OF OR IN CONNECTION WITH
+	THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+package errdefs
+
+import (
+	"testing"
+
+	"github.com/pkg/errors"
+	"github.com/stretchr/testify/require"
+)
+
+func TestIsNotFound(t *testing.T) {
+	err := errors.Wrap(ErrNotFound, `object "name"`)
+	require.True(t, IsNotFoundError(err))
+
+	require.False(t, IsNotFoundError(errors.New("another error")))
+}
+
+func TestIsAlreadyExists(t *testing.T) {
+	err := errors.Wrap(ErrAlreadyExists, `object "name"`)
+	require.True(t, IsAlreadyExistsError(err))
+
+	require.False(t, IsAlreadyExistsError(errors.New("another error")))
+}

+ 1 - 0
go.mod

@@ -17,6 +17,7 @@ require (
 	github.com/gobwas/ws v1.0.3
 	github.com/golang/protobuf v1.4.0
 	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
+	github.com/hashicorp/go-multierror v1.1.0
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/onsi/gomega v1.9.0
 	github.com/opencontainers/go-digest v1.0.0-rc1

+ 4 - 0
go.sum

@@ -119,6 +119,10 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmg
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
+github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=

+ 1 - 1
tests/e2e/e2e.go

@@ -50,7 +50,7 @@ func main() {
 		NewDockerCommand("context", "create", "test-example", "example").ExecOrDie()
 		//Expect(output).To(ContainSubstring("test-example context acitest created"))
 	})
-	defer NewCommand("docker", "context", "rm", "test-example", "-f").ExecOrDie()
+	defer NewDockerCommand("context", "rm", "test-example").ExecOrDie()
 
 	It("uses the test context", func() {
 		currentContext := NewCommand("docker", "context", "use", "test-example").ExecOrDie()