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

Add context rm command
This commit is contained in:
Guillaume Tardif 2020-05-12 13:19:08 +02:00 committed by GitHub
commit 102116315a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 187 additions and 5 deletions

View File

@ -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()
}

View File

@ -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

View File

@ -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
errdefs/errors.go Normal file
View File

@ -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
errdefs/errors_test.go Normal file
View File

@ -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
go.mod
View File

@ -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
go.sum
View File

@ -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=

View File

@ -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()