diff --git a/cli/command/cli.go b/cli/command/cli.go index c2b5bcc7ab..b31c35785d 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -28,6 +28,7 @@ import ( "github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" + "github.com/docker/docker/errdefs" "github.com/docker/go-connections/tlsconfig" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -462,8 +463,8 @@ func resolveContextName(opts *cliflags.CommonOptions, config *configfile.ConfigF } if config != nil && config.CurrentContext != "" { _, err := contextstore.GetMetadata(config.CurrentContext) - if store.IsErrContextDoesNotExist(err) { - return "", errors.Errorf("Current context %q is not found on the file system, please check your config file at %s", config.CurrentContext, config.Filename) + if errdefs.IsNotFound(err) { + return "", errors.Errorf("current context %q is not found on the file system, please check your config file at %s", config.CurrentContext, config.Filename) } return config.CurrentContext, err } diff --git a/cli/command/context/create.go b/cli/command/context/create.go index 647295e308..7765a9490e 100644 --- a/cli/command/context/create.go +++ b/cli/command/context/create.go @@ -10,6 +10,7 @@ import ( "github.com/docker/cli/cli/command/formatter/tabwriter" "github.com/docker/cli/cli/context/docker" "github.com/docker/cli/cli/context/store" + "github.com/docker/docker/errdefs" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -127,7 +128,7 @@ func checkContextNameForCreation(s store.Reader, name string) error { if err := store.ValidateContextName(name); err != nil { return err } - if _, err := s.GetMetadata(name); !store.IsErrContextDoesNotExist(err) { + if _, err := s.GetMetadata(name); !errdefs.IsNotFound(err) { if err != nil { return errors.Wrap(err, "error while getting existing contexts") } diff --git a/cli/command/context/remove.go b/cli/command/context/remove.go index 434e53e8fa..9ba405d588 100644 --- a/cli/command/context/remove.go +++ b/cli/command/context/remove.go @@ -40,7 +40,7 @@ func RunRemove(dockerCli command.Cli, opts RemoveOptions, names []string) error if name == "default" { errs = append(errs, `default: context "default" cannot be removed`) } else if err := doRemove(dockerCli, name, name == currentCtx, opts.Force); err != nil { - errs = append(errs, fmt.Sprintf("%s: %s", name, err)) + errs = append(errs, err.Error()) } else { fmt.Fprintln(dockerCli.Out(), name) } @@ -54,7 +54,7 @@ func RunRemove(dockerCli command.Cli, opts RemoveOptions, names []string) error func doRemove(dockerCli command.Cli, name string, isCurrent, force bool) error { if isCurrent { if !force { - return errors.New("context is in use, set -f flag to force remove") + return errors.Errorf("context %q is in use, set -f flag to force remove", name) } // fallback to DOCKER_HOST cfg := dockerCli.ConfigFile() diff --git a/cli/command/context/remove_test.go b/cli/command/context/remove_test.go index 300d3d2fdc..35e27781b2 100644 --- a/cli/command/context/remove_test.go +++ b/cli/command/context/remove_test.go @@ -6,8 +6,9 @@ import ( "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config/configfile" - "github.com/docker/cli/cli/context/store" + "github.com/docker/docker/errdefs" "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" ) func TestRemove(t *testing.T) { @@ -18,7 +19,7 @@ func TestRemove(t *testing.T) { _, err := cli.ContextStore().GetMetadata("current") assert.NilError(t, err) _, err = cli.ContextStore().GetMetadata("other") - assert.Check(t, store.IsErrContextDoesNotExist(err)) + assert.Check(t, is.ErrorType(err, errdefs.IsNotFound)) } func TestRemoveNotAContext(t *testing.T) { @@ -38,7 +39,7 @@ func TestRemoveCurrent(t *testing.T) { createTestContext(t, cli, "other") cli.SetCurrentContext("current") err := RunRemove(cli, RemoveOptions{}, []string{"current"}) - assert.ErrorContains(t, err, "current: context is in use, set -f flag to force remove") + assert.ErrorContains(t, err, `context "current" is in use, set -f flag to force remove`) } func TestRemoveCurrentForce(t *testing.T) { diff --git a/cli/command/context/use_test.go b/cli/command/context/use_test.go index 13be26425f..d7521abcdb 100644 --- a/cli/command/context/use_test.go +++ b/cli/command/context/use_test.go @@ -11,8 +11,8 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config/configfile" - "github.com/docker/cli/cli/context/store" "github.com/docker/cli/cli/flags" + "github.com/docker/docker/errdefs" "github.com/docker/docker/pkg/homedir" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" @@ -47,7 +47,7 @@ func TestUse(t *testing.T) { func TestUseNoExist(t *testing.T) { cli := makeFakeCli(t) err := newUseCommand(cli).RunE(nil, []string{"test"}) - assert.Check(t, store.IsErrContextDoesNotExist(err)) + assert.Check(t, is.ErrorType(err, errdefs.IsNotFound)) } // TestUseDefaultWithoutConfigFile verifies that the CLI does not create diff --git a/cli/command/defaultcontextstore.go b/cli/command/defaultcontextstore.go index a0dd1b1865..3677f711e5 100644 --- a/cli/command/defaultcontextstore.go +++ b/cli/command/defaultcontextstore.go @@ -1,11 +1,10 @@ package command import ( - "fmt" - "github.com/docker/cli/cli/context/docker" "github.com/docker/cli/cli/context/store" cliflags "github.com/docker/cli/cli/flags" + "github.com/docker/docker/errdefs" "github.com/pkg/errors" ) @@ -107,7 +106,7 @@ func (s *ContextStoreWithDefault) List() ([]store.Metadata, error) { // CreateOrUpdate is not allowed for the default context and fails func (s *ContextStoreWithDefault) CreateOrUpdate(meta store.Metadata) error { if meta.Name == DefaultContextName { - return errors.New("default context cannot be created nor updated") + return errdefs.InvalidParameter(errors.New("default context cannot be created nor updated")) } return s.Store.CreateOrUpdate(meta) } @@ -115,7 +114,7 @@ func (s *ContextStoreWithDefault) CreateOrUpdate(meta store.Metadata) error { // Remove is not allowed for the default context and fails func (s *ContextStoreWithDefault) Remove(name string) error { if name == DefaultContextName { - return errors.New("default context cannot be removed") + return errdefs.InvalidParameter(errors.New("default context cannot be removed")) } return s.Store.Remove(name) } @@ -135,7 +134,7 @@ func (s *ContextStoreWithDefault) GetMetadata(name string) (store.Metadata, erro // ResetTLSMaterial is not implemented for default context and fails func (s *ContextStoreWithDefault) ResetTLSMaterial(name string, data *store.ContextTLSData) error { if name == DefaultContextName { - return errors.New("The default context store does not support ResetTLSMaterial") + return errdefs.InvalidParameter(errors.New("default context cannot be edited")) } return s.Store.ResetTLSMaterial(name, data) } @@ -143,7 +142,7 @@ func (s *ContextStoreWithDefault) ResetTLSMaterial(name string, data *store.Cont // ResetEndpointTLSMaterial is not implemented for default context and fails func (s *ContextStoreWithDefault) ResetEndpointTLSMaterial(contextName string, endpointName string, data *store.EndpointTLSData) error { if contextName == DefaultContextName { - return errors.New("The default context store does not support ResetEndpointTLSMaterial") + return errdefs.InvalidParameter(errors.New("default context cannot be edited")) } return s.Store.ResetEndpointTLSMaterial(contextName, endpointName, data) } @@ -176,29 +175,13 @@ func (s *ContextStoreWithDefault) GetTLSData(contextName, endpointName, fileName return nil, err } if defaultContext.TLS.Endpoints[endpointName].Files[fileName] == nil { - return nil, &noDefaultTLSDataError{endpointName: endpointName, fileName: fileName} + return nil, errdefs.NotFound(errors.Errorf("TLS data for %s/%s/%s does not exist", DefaultContextName, endpointName, fileName)) } return defaultContext.TLS.Endpoints[endpointName].Files[fileName], nil - } return s.Store.GetTLSData(contextName, endpointName, fileName) } -type noDefaultTLSDataError struct { - endpointName string - fileName string -} - -func (e *noDefaultTLSDataError) Error() string { - return fmt.Sprintf("tls data for %s/%s/%s does not exist", DefaultContextName, e.endpointName, e.fileName) -} - -// NotFound satisfies interface github.com/docker/docker/errdefs.ErrNotFound -func (e *noDefaultTLSDataError) NotFound() {} - -// IsTLSDataDoesNotExist satisfies github.com/docker/cli/cli/context/store.tlsDataDoesNotExist -func (e *noDefaultTLSDataError) IsTLSDataDoesNotExist() {} - // GetStorageInfo implements store.Store's GetStorageInfo func (s *ContextStoreWithDefault) GetStorageInfo(contextName string) store.StorageInfo { if contextName == DefaultContextName { diff --git a/cli/command/defaultcontextstore_test.go b/cli/command/defaultcontextstore_test.go index 4b00b49f9e..a3b1bff583 100644 --- a/cli/command/defaultcontextstore_test.go +++ b/cli/command/defaultcontextstore_test.go @@ -8,8 +8,10 @@ import ( "github.com/docker/cli/cli/context/docker" "github.com/docker/cli/cli/context/store" cliflags "github.com/docker/cli/cli/flags" + "github.com/docker/docker/errdefs" "github.com/docker/go-connections/tlsconfig" "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/golden" ) @@ -153,6 +155,7 @@ func TestErrCreateDefault(t *testing.T) { Metadata: testContext{Bar: "baz"}, Name: "default", }) + assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter)) assert.Error(t, err, "default context cannot be created nor updated") } @@ -160,6 +163,7 @@ func TestErrRemoveDefault(t *testing.T) { meta := testDefaultMetadata() s := testStore(t, meta, store.ContextTLSData{}) err := s.Remove("default") + assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter)) assert.Error(t, err, "default context cannot be removed") } @@ -167,5 +171,5 @@ func TestErrTLSDataError(t *testing.T) { meta := testDefaultMetadata() s := testStore(t, meta, store.ContextTLSData{}) _, err := s.GetTLSData("default", "noop", "noop") - assert.Check(t, store.IsErrTLSDataDoesNotExist(err)) + assert.Check(t, is.ErrorType(err, errdefs.IsNotFound)) } diff --git a/cli/context/store/metadata_test.go b/cli/context/store/metadata_test.go index 9de17408b0..fcb46c54f5 100644 --- a/cli/context/store/metadata_test.go +++ b/cli/context/store/metadata_test.go @@ -5,6 +5,7 @@ import ( "path/filepath" "testing" + "github.com/docker/docker/errdefs" "gotest.tools/v3/assert" "gotest.tools/v3/assert/cmp" ) @@ -22,7 +23,7 @@ func testMetadata(name string) Metadata { func TestMetadataGetNotExisting(t *testing.T) { testee := metadataStore{root: t.TempDir(), config: testCfg} _, err := testee.get("noexist") - assert.Assert(t, IsErrContextDoesNotExist(err)) + assert.ErrorType(t, err, errdefs.IsNotFound) } func TestMetadataCreateGetRemove(t *testing.T) { @@ -41,7 +42,7 @@ func TestMetadataCreateGetRemove(t *testing.T) { assert.NilError(t, err) // create a new instance to check it does not depend on some sort of state testee = metadataStore{root: testDir, config: testCfg} - meta, err := testee.get(contextdirOf("test-context")) + meta, err := testee.get("test-context") assert.NilError(t, err) assert.DeepEqual(t, meta, testMeta) @@ -49,14 +50,14 @@ func TestMetadataCreateGetRemove(t *testing.T) { err = testee.createOrUpdate(expected2) assert.NilError(t, err) - meta, err = testee.get(contextdirOf("test-context")) + meta, err = testee.get("test-context") assert.NilError(t, err) assert.DeepEqual(t, meta, expected2) - assert.NilError(t, testee.remove(contextdirOf("test-context"))) - assert.NilError(t, testee.remove(contextdirOf("test-context"))) // support duplicate remove - _, err = testee.get(contextdirOf("test-context")) - assert.Assert(t, IsErrContextDoesNotExist(err)) + assert.NilError(t, testee.remove("test-context")) + assert.NilError(t, testee.remove("test-context")) // support duplicate remove + _, err = testee.get("test-context") + assert.ErrorType(t, err, errdefs.IsNotFound) } func TestMetadataRespectJsonAnnotation(t *testing.T) { @@ -121,7 +122,7 @@ func TestWithEmbedding(t *testing.T) { }, } assert.NilError(t, testee.createOrUpdate(Metadata{Metadata: testCtxMeta, Name: "test"})) - res, err := testee.get(contextdirOf("test")) + res, err := testee.get("test") assert.NilError(t, err) assert.Equal(t, testCtxMeta, res.Metadata) } diff --git a/cli/context/store/metadatastore.go b/cli/context/store/metadatastore.go index 151852fa1b..5222897f39 100644 --- a/cli/context/store/metadatastore.go +++ b/cli/context/store/metadatastore.go @@ -2,13 +2,14 @@ package store import ( "encoding/json" - "fmt" "os" "path/filepath" "reflect" "sort" + "github.com/docker/docker/errdefs" "github.com/fvbommel/sortorder" + "github.com/pkg/errors" ) const ( @@ -55,11 +56,21 @@ func parseTypedOrMap(payload []byte, getter TypeGetter) (interface{}, error) { return reflect.ValueOf(typed).Elem().Interface(), nil } -func (s *metadataStore) get(id contextdir) (Metadata, error) { - contextDir := s.contextDir(id) - bytes, err := os.ReadFile(filepath.Join(contextDir, metaFile)) +func (s *metadataStore) get(name string) (Metadata, error) { + m, err := s.getByID(contextdirOf(name)) if err != nil { - return Metadata{}, convertContextDoesNotExist(err) + return m, errors.Wrapf(err, "load context %q", name) + } + return m, nil +} + +func (s *metadataStore) getByID(id contextdir) (Metadata, error) { + bytes, err := os.ReadFile(filepath.Join(s.contextDir(id), metaFile)) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return Metadata{}, errdefs.NotFound(errors.Wrap(err, "context does not exist")) + } + return Metadata{}, err } var untyped untypedContextMetadata r := Metadata{ @@ -80,9 +91,11 @@ func (s *metadataStore) get(id contextdir) (Metadata, error) { return r, err } -func (s *metadataStore) remove(id contextdir) error { - contextDir := s.contextDir(id) - return os.RemoveAll(contextDir) +func (s *metadataStore) remove(name string) error { + if err := os.RemoveAll(s.contextDir(contextdirOf(name))); err != nil { + return errors.Wrapf(err, "failed to remove metadata") + } + return nil } func (s *metadataStore) list() ([]Metadata, error) { @@ -95,9 +108,12 @@ func (s *metadataStore) list() ([]Metadata, error) { } var res []Metadata for _, dir := range ctxDirs { - c, err := s.get(contextdir(dir)) + c, err := s.getByID(contextdir(dir)) if err != nil { - return nil, err + if os.IsNotExist(err) { + continue + } + return nil, errors.Wrap(err, "failed to read metadata") } res = append(res, c) } @@ -131,20 +147,13 @@ func listRecursivelyMetadataDirs(root string) ([]string, error) { return nil, err } for _, s := range subs { - result = append(result, fmt.Sprintf("%s/%s", fi.Name(), s)) + result = append(result, filepath.Join(fi.Name(), s)) } } } return result, nil } -func convertContextDoesNotExist(err error) error { - if os.IsNotExist(err) { - return &contextDoesNotExistError{} - } - return err -} - type untypedContextMetadata struct { Metadata json.RawMessage `json:"metadata,omitempty"` Endpoints map[string]json.RawMessage `json:"endpoints,omitempty"` diff --git a/cli/context/store/store.go b/cli/context/store/store.go index 19ad980a47..6042c2af7d 100644 --- a/cli/context/store/store.go +++ b/cli/context/store/store.go @@ -7,7 +7,6 @@ import ( "bytes" _ "crypto/sha256" // ensure ids can be computed "encoding/json" - "fmt" "io" "net/http" "path" @@ -94,11 +93,11 @@ type ContextTLSData struct { // New creates a store from a given directory. // If the directory does not exist or is empty, initialize it -func New(dir string, cfg Config) Store { +func New(dir string, cfg Config) *ContextStore { metaRoot := filepath.Join(dir, metadataDir) tlsRoot := filepath.Join(dir, tlsDir) - return &store{ + return &ContextStore{ meta: &metadataStore{ root: metaRoot, config: cfg, @@ -109,12 +108,14 @@ func New(dir string, cfg Config) Store { } } -type store struct { +// ContextStore implements Store. +type ContextStore struct { meta *metadataStore tls *tlsStore } -func (s *store) List() ([]Metadata, error) { +// List return all contexts. +func (s *ContextStore) List() ([]Metadata, error) { return s.meta.list() } @@ -131,73 +132,82 @@ func Names(s Lister) ([]string, error) { return names, nil } -func (s *store) CreateOrUpdate(meta Metadata) error { +// CreateOrUpdate creates or updates metadata for the context. +func (s *ContextStore) CreateOrUpdate(meta Metadata) error { return s.meta.createOrUpdate(meta) } -func (s *store) Remove(name string) error { - id := contextdirOf(name) - if err := s.meta.remove(id); err != nil { - return patchErrContextName(err, name) +// Remove deletes the context with the given name, if found. +func (s *ContextStore) Remove(name string) error { + if err := s.meta.remove(name); err != nil { + return errors.Wrapf(err, "failed to remove context %s", name) } - return patchErrContextName(s.tls.removeAllContextData(id), name) + if err := s.tls.remove(name); err != nil { + return errors.Wrapf(err, "failed to remove context %s", name) + } + return nil } -func (s *store) GetMetadata(name string) (Metadata, error) { - res, err := s.meta.get(contextdirOf(name)) - patchErrContextName(err, name) - return res, err +// GetMetadata returns the metadata for the context with the given name. +// It returns an errdefs.ErrNotFound if the context was not found. +func (s *ContextStore) GetMetadata(name string) (Metadata, error) { + return s.meta.get(name) } -func (s *store) ResetTLSMaterial(name string, data *ContextTLSData) error { - id := contextdirOf(name) - if err := s.tls.removeAllContextData(id); err != nil { - return patchErrContextName(err, name) +// ResetTLSMaterial removes TLS data for all endpoints in the context and replaces +// it with the new data. +func (s *ContextStore) ResetTLSMaterial(name string, data *ContextTLSData) error { + if err := s.tls.remove(name); err != nil { + return err } if data == nil { return nil } for ep, files := range data.Endpoints { for fileName, data := range files.Files { - if err := s.tls.createOrUpdate(id, ep, fileName, data); err != nil { - return patchErrContextName(err, name) + if err := s.tls.createOrUpdate(name, ep, fileName, data); err != nil { + return err } } } return nil } -func (s *store) ResetEndpointTLSMaterial(contextName string, endpointName string, data *EndpointTLSData) error { - id := contextdirOf(contextName) - if err := s.tls.removeAllEndpointData(id, endpointName); err != nil { - return patchErrContextName(err, contextName) +// ResetEndpointTLSMaterial removes TLS data for the given context and endpoint, +// and replaces it with the new data. +func (s *ContextStore) ResetEndpointTLSMaterial(contextName string, endpointName string, data *EndpointTLSData) error { + if err := s.tls.removeEndpoint(contextName, endpointName); err != nil { + return err } if data == nil { return nil } for fileName, data := range data.Files { - if err := s.tls.createOrUpdate(id, endpointName, fileName, data); err != nil { - return patchErrContextName(err, contextName) + if err := s.tls.createOrUpdate(contextName, endpointName, fileName, data); err != nil { + return err } } return nil } -func (s *store) ListTLSFiles(name string) (map[string]EndpointFiles, error) { - res, err := s.tls.listContextData(contextdirOf(name)) - return res, patchErrContextName(err, name) +// ListTLSFiles returns the list of TLS files present for each endpoint in the +// context. +func (s *ContextStore) ListTLSFiles(name string) (map[string]EndpointFiles, error) { + return s.tls.listContextData(name) } -func (s *store) GetTLSData(contextName, endpointName, fileName string) ([]byte, error) { - res, err := s.tls.getData(contextdirOf(contextName), endpointName, fileName) - return res, patchErrContextName(err, contextName) +// GetTLSData reads, and returns the content of the given fileName for an endpoint. +// It returns an errdefs.ErrNotFound if the file was not found. +func (s *ContextStore) GetTLSData(contextName, endpointName, fileName string) ([]byte, error) { + return s.tls.getData(contextName, endpointName, fileName) } -func (s *store) GetStorageInfo(contextName string) StorageInfo { - dir := contextdirOf(contextName) +// GetStorageInfo returns the paths where the Metadata and TLS data are stored +// for the context. +func (s *ContextStore) GetStorageInfo(contextName string) StorageInfo { return StorageInfo{ - MetadataPath: s.meta.contextDir(dir), - TLSPath: s.tls.contextDir(dir), + MetadataPath: s.meta.contextDir(contextdirOf(contextName)), + TLSPath: s.tls.contextDir(contextName), } } @@ -210,7 +220,7 @@ func ValidateContextName(name string) error { return errors.New(`"default" is a reserved context name`) } if !restrictedNameRegEx.MatchString(name) { - return fmt.Errorf("context name %q is invalid, names are validated against regexp %q", name, restrictedNamePattern) + return errors.Errorf("context name %q is invalid, names are validated against regexp %q", name, restrictedNamePattern) } return nil } @@ -484,58 +494,18 @@ func importEndpointTLS(tlsData *ContextTLSData, path string, data []byte) error return nil } -type setContextName interface { - setContext(name string) -} - -type contextDoesNotExistError struct { - name string -} - -func (e *contextDoesNotExistError) Error() string { - return fmt.Sprintf("context %q does not exist", e.name) -} - -func (e *contextDoesNotExistError) setContext(name string) { - e.name = name -} - -// NotFound satisfies interface github.com/docker/docker/errdefs.ErrNotFound -func (e *contextDoesNotExistError) NotFound() {} - -type tlsDataDoesNotExist interface { - errdefs.ErrNotFound - IsTLSDataDoesNotExist() -} - -type tlsDataDoesNotExistError struct { - context, endpoint, file string -} - -func (e *tlsDataDoesNotExistError) Error() string { - return fmt.Sprintf("tls data for %s/%s/%s does not exist", e.context, e.endpoint, e.file) -} - -func (e *tlsDataDoesNotExistError) setContext(name string) { - e.context = name -} - -// NotFound satisfies interface github.com/docker/docker/errdefs.ErrNotFound -func (e *tlsDataDoesNotExistError) NotFound() {} - -// IsTLSDataDoesNotExist satisfies tlsDataDoesNotExist -func (e *tlsDataDoesNotExistError) IsTLSDataDoesNotExist() {} - -// IsErrContextDoesNotExist checks if the given error is a "context does not exist" condition +// IsErrContextDoesNotExist checks if the given error is a "context does not exist" condition. +// +// Deprecated: use github.com/docker/docker/errdefs.IsNotFound() func IsErrContextDoesNotExist(err error) bool { - _, ok := err.(*contextDoesNotExistError) - return ok + return errdefs.IsNotFound(err) } // IsErrTLSDataDoesNotExist checks if the given error is a "context does not exist" condition +// +// Deprecated: use github.com/docker/docker/errdefs.IsNotFound() func IsErrTLSDataDoesNotExist(err error) bool { - _, ok := err.(tlsDataDoesNotExist) - return ok + return errdefs.IsNotFound(err) } type contextdir string @@ -543,10 +513,3 @@ type contextdir string func contextdirOf(name string) contextdir { return contextdir(digest.FromString(name).Encoded()) } - -func patchErrContextName(err error, name string) error { - if typed, ok := err.(setContextName); ok { - typed.setContext(name) - } - return err -} diff --git a/cli/context/store/store_test.go b/cli/context/store/store_test.go index 3c9ddd6b7a..310065dc64 100644 --- a/cli/context/store/store_test.go +++ b/cli/context/store/store_test.go @@ -12,7 +12,9 @@ import ( "path" "testing" + "github.com/docker/docker/errdefs" "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" ) type endpoint struct { @@ -100,7 +102,7 @@ func TestRemove(t *testing.T) { })) assert.NilError(t, s.Remove("source")) _, err = s.GetMetadata("source") - assert.Check(t, IsErrContextDoesNotExist(err)) + assert.Check(t, is.ErrorType(err, errdefs.IsNotFound)) f, err := s.ListTLSFiles("source") assert.NilError(t, err) assert.Equal(t, 0, len(f)) @@ -115,7 +117,7 @@ func TestListEmptyStore(t *testing.T) { func TestErrHasCorrectContext(t *testing.T) { _, err := New(t.TempDir(), testCfg).GetMetadata("no-exists") assert.ErrorContains(t, err, "no-exists") - assert.Check(t, IsErrContextDoesNotExist(err)) + assert.Check(t, is.ErrorType(err, errdefs.IsNotFound)) } func TestDetectImportContentType(t *testing.T) { @@ -173,7 +175,7 @@ func TestImportZip(t *testing.T) { Name: "source", }) assert.NilError(t, err) - var files = []struct { + files := []struct { Name, Body string }{ {"meta.json", string(meta)}, diff --git a/cli/context/store/storeconfig.go b/cli/context/store/storeconfig.go index 2a4bc57c1c..9c93ecbab2 100644 --- a/cli/context/store/storeconfig.go +++ b/cli/context/store/storeconfig.go @@ -19,7 +19,7 @@ func EndpointTypeGetter(name string, getter TypeGetter) NamedTypeGetter { } } -// Config is used to configure the metadata marshaler of the context store +// Config is used to configure the metadata marshaler of the context ContextStore type Config struct { contextType TypeGetter endpointTypes map[string]TypeGetter diff --git a/cli/context/store/storeconfig_test.go b/cli/context/store/storeconfig_test.go index 353d093650..e5b8c75686 100644 --- a/cli/context/store/storeconfig_test.go +++ b/cli/context/store/storeconfig_test.go @@ -6,10 +6,12 @@ import ( "gotest.tools/v3/assert" ) -type testCtx struct{} -type testEP1 struct{} -type testEP2 struct{} -type testEP3 struct{} +type ( + testCtx struct{} + testEP1 struct{} + testEP2 struct{} + testEP3 struct{} +) func TestConfigModification(t *testing.T) { cfg := NewConfig(func() interface{} { return &testCtx{} }, EndpointTypeGetter("ep1", func() interface{} { return &testEP1{} })) diff --git a/cli/context/store/tlsstore.go b/cli/context/store/tlsstore.go index 8267e87964..ec46e7e58a 100644 --- a/cli/context/store/tlsstore.go +++ b/cli/context/store/tlsstore.go @@ -3,6 +3,9 @@ package store import ( "os" "path/filepath" + + "github.com/docker/docker/errdefs" + "github.com/pkg/errors" ) const tlsDir = "tls" @@ -11,69 +14,70 @@ type tlsStore struct { root string } -func (s *tlsStore) contextDir(id contextdir) string { - return filepath.Join(s.root, string(id)) +func (s *tlsStore) contextDir(name string) string { + return filepath.Join(s.root, string(contextdirOf(name))) } -func (s *tlsStore) endpointDir(contextID contextdir, name string) string { - return filepath.Join(s.root, string(contextID), name) +func (s *tlsStore) endpointDir(name, endpointName string) string { + return filepath.Join(s.contextDir(name), endpointName) } -func (s *tlsStore) filePath(contextID contextdir, endpointName, filename string) string { - return filepath.Join(s.root, string(contextID), endpointName, filename) -} - -func (s *tlsStore) createOrUpdate(contextID contextdir, endpointName, filename string, data []byte) error { - epdir := s.endpointDir(contextID, endpointName) +func (s *tlsStore) createOrUpdate(name, endpointName, filename string, data []byte) error { parentOfRoot := filepath.Dir(s.root) if err := os.MkdirAll(parentOfRoot, 0755); err != nil { return err } - if err := os.MkdirAll(epdir, 0700); err != nil { + endpointDir := s.endpointDir(name, endpointName) + if err := os.MkdirAll(endpointDir, 0700); err != nil { return err } - return os.WriteFile(s.filePath(contextID, endpointName, filename), data, 0600) + return os.WriteFile(filepath.Join(endpointDir, filename), data, 0600) } -func (s *tlsStore) getData(contextID contextdir, endpointName, filename string) ([]byte, error) { - data, err := os.ReadFile(s.filePath(contextID, endpointName, filename)) +func (s *tlsStore) getData(name, endpointName, filename string) ([]byte, error) { + data, err := os.ReadFile(filepath.Join(s.endpointDir(name, endpointName), filename)) if err != nil { - return nil, convertTLSDataDoesNotExist(endpointName, filename, err) + if os.IsNotExist(err) { + return nil, errdefs.NotFound(errors.Errorf("TLS data for %s/%s/%s does not exist", name, endpointName, filename)) + } + return nil, errors.Wrapf(err, "failed to read TLS data for endpoint %s", endpointName) } return data, nil } -func (s *tlsStore) remove(contextID contextdir, endpointName, filename string) error { - err := os.Remove(s.filePath(contextID, endpointName, filename)) - if os.IsNotExist(err) { - return nil +// remove deletes all TLS data for the given context. +func (s *tlsStore) remove(name string) error { + if err := os.RemoveAll(s.contextDir(name)); err != nil { + return errors.Wrapf(err, "failed to remove TLS data") } - return err + return nil } -func (s *tlsStore) removeAllEndpointData(contextID contextdir, endpointName string) error { - return os.RemoveAll(s.endpointDir(contextID, endpointName)) +func (s *tlsStore) removeEndpoint(name, endpointName string) error { + if err := os.RemoveAll(s.endpointDir(name, endpointName)); err != nil { + return errors.Wrapf(err, "failed to remove TLS data for endpoint %s", endpointName) + } + return nil } -func (s *tlsStore) removeAllContextData(contextID contextdir) error { - return os.RemoveAll(s.contextDir(contextID)) -} - -func (s *tlsStore) listContextData(contextID contextdir) (map[string]EndpointFiles, error) { - epFSs, err := os.ReadDir(s.contextDir(contextID)) +func (s *tlsStore) listContextData(name string) (map[string]EndpointFiles, error) { + contextDir := s.contextDir(name) + epFSs, err := os.ReadDir(contextDir) if err != nil { if os.IsNotExist(err) { return map[string]EndpointFiles{}, nil } - return nil, err + return nil, errors.Wrapf(err, "failed to list TLS files for context %s", name) } r := make(map[string]EndpointFiles) for _, epFS := range epFSs { if epFS.IsDir() { - epDir := s.endpointDir(contextID, epFS.Name()) - fss, err := os.ReadDir(epDir) + fss, err := os.ReadDir(filepath.Join(contextDir, epFS.Name())) + if os.IsNotExist(err) { + continue + } if err != nil { - return nil, err + return nil, errors.Wrapf(err, "failed to list TLS files for endpoint %s", epFS.Name()) } var files EndpointFiles for _, fs := range fss { @@ -89,10 +93,3 @@ func (s *tlsStore) listContextData(contextID contextdir) (map[string]EndpointFil // EndpointFiles is a slice of strings representing file names type EndpointFiles []string - -func convertTLSDataDoesNotExist(endpoint, file string, err error) error { - if os.IsNotExist(err) { - return &tlsDataDoesNotExistError{endpoint: endpoint, file: file} - } - return err -} diff --git a/cli/context/store/tlsstore_test.go b/cli/context/store/tlsstore_test.go index 5af9acda3b..511ba1d2f3 100644 --- a/cli/context/store/tlsstore_test.go +++ b/cli/context/store/tlsstore_test.go @@ -3,32 +3,33 @@ package store import ( "testing" + "github.com/docker/docker/errdefs" "gotest.tools/v3/assert" ) func TestTlsCreateUpdateGetRemove(t *testing.T) { testee := tlsStore{root: t.TempDir()} - _, err := testee.getData("test-ctx", "test-ep", "test-data") - assert.Equal(t, true, IsErrTLSDataDoesNotExist(err)) - err = testee.createOrUpdate("test-ctx", "test-ep", "test-data", []byte("data")) + const contextName = "test-ctx" + + _, err := testee.getData(contextName, "test-ep", "test-data") + assert.ErrorType(t, err, errdefs.IsNotFound) + + err = testee.createOrUpdate(contextName, "test-ep", "test-data", []byte("data")) assert.NilError(t, err) - data, err := testee.getData("test-ctx", "test-ep", "test-data") + data, err := testee.getData(contextName, "test-ep", "test-data") assert.NilError(t, err) assert.Equal(t, string(data), "data") - err = testee.createOrUpdate("test-ctx", "test-ep", "test-data", []byte("data2")) + err = testee.createOrUpdate(contextName, "test-ep", "test-data", []byte("data2")) assert.NilError(t, err) - data, err = testee.getData("test-ctx", "test-ep", "test-data") + data, err = testee.getData(contextName, "test-ep", "test-data") assert.NilError(t, err) assert.Equal(t, string(data), "data2") - err = testee.remove("test-ctx", "test-ep", "test-data") + err = testee.removeEndpoint(contextName, "test-ep") assert.NilError(t, err) - err = testee.remove("test-ctx", "test-ep", "test-data") - assert.NilError(t, err) - - _, err = testee.getData("test-ctx", "test-ep", "test-data") - assert.Equal(t, true, IsErrTLSDataDoesNotExist(err)) + _, err = testee.getData(contextName, "test-ep", "test-data") + assert.ErrorType(t, err, errdefs.IsNotFound) } func TestTlsListAndBatchRemove(t *testing.T) { @@ -45,26 +46,27 @@ func TestTlsListAndBatchRemove(t *testing.T) { "ep2": {"f1", "f2", "f3"}, } + const contextName = "test-ctx" for name, files := range all { for _, file := range files { - err := testee.createOrUpdate("test-ctx", name, file, []byte("data")) + err := testee.createOrUpdate(contextName, name, file, []byte("data")) assert.NilError(t, err) } } - resAll, err := testee.listContextData("test-ctx") + resAll, err := testee.listContextData(contextName) assert.NilError(t, err) assert.DeepEqual(t, resAll, all) - err = testee.removeAllEndpointData("test-ctx", "ep3") + err = testee.removeEndpoint(contextName, "ep3") assert.NilError(t, err) - resEp1ep2, err := testee.listContextData("test-ctx") + resEp1ep2, err := testee.listContextData(contextName) assert.NilError(t, err) assert.DeepEqual(t, resEp1ep2, ep1ep2) - err = testee.removeAllContextData("test-ctx") + err = testee.remove(contextName) assert.NilError(t, err) - resEmpty, err := testee.listContextData("test-ctx") + resEmpty, err := testee.listContextData(contextName) assert.NilError(t, err) assert.DeepEqual(t, resEmpty, map[string]EndpointFiles{}) } diff --git a/cli/context/tlsdata.go b/cli/context/tlsdata.go index f8459fd406..c758612a1d 100644 --- a/cli/context/tlsdata.go +++ b/cli/context/tlsdata.go @@ -45,14 +45,14 @@ func (data *TLSData) ToStoreTLSData() *store.EndpointTLSData { func LoadTLSData(s store.Reader, contextName, endpointName string) (*TLSData, error) { tlsFiles, err := s.ListTLSFiles(contextName) if err != nil { - return nil, errors.Wrapf(err, "failed to retrieve context tls files for context %q", contextName) + return nil, errors.Wrapf(err, "failed to retrieve TLS files for context %q", contextName) } if epTLSFiles, ok := tlsFiles[endpointName]; ok { var tlsData TLSData for _, f := range epTLSFiles { data, err := s.GetTLSData(contextName, endpointName, f) if err != nil { - return nil, errors.Wrapf(err, "failed to retrieve context tls data for file %q of context %q", f, contextName) + return nil, errors.Wrapf(err, "failed to retrieve TLS data (%s) for context %q", f, contextName) } switch f { case caKey: @@ -62,7 +62,7 @@ func LoadTLSData(s store.Reader, contextName, endpointName string) (*TLSData, er case keyKey: tlsData.Key = data default: - logrus.Warnf("unknown file %s in context %s tls bundle", f, contextName) + logrus.Warnf("unknown file in context %s TLS bundle: %s", contextName, f) } } return &tlsData, nil