diff --git a/cli/command/cli.go b/cli/command/cli.go index 36ac41c56a..9b71517cda 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -290,7 +290,7 @@ func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigF return client.NewClientWithOpts(clientOpts...) } -func resolveDockerEndpoint(s store.Store, contextName string) (docker.Endpoint, error) { +func resolveDockerEndpoint(s store.Reader, contextName string) (docker.Endpoint, error) { ctxMeta, err := s.GetContextMetadata(contextName) if err != nil { return docker.Endpoint{}, err @@ -500,7 +500,7 @@ func UserAgent() string { // - if DOCKER_CONTEXT is set, use this value // - if Config file has a globally set "CurrentContext", use this value // - fallbacks to default HOST, uses TLS config from flags/env vars -func resolveContextName(opts *cliflags.CommonOptions, config *configfile.ConfigFile, contextstore store.Store) (string, error) { +func resolveContextName(opts *cliflags.CommonOptions, config *configfile.ConfigFile, contextstore store.Reader) (string, error) { if opts.Context != "" && len(opts.Hosts) > 0 { return "", errors.New("Conflicting options: either specify --host or --context, not both") } diff --git a/cli/command/context/create.go b/cli/command/context/create.go index 3687aa5313..f337c655d3 100644 --- a/cli/command/context/create.go +++ b/cli/command/context/create.go @@ -87,7 +87,7 @@ func RunCreate(cli command.Cli, o *CreateOptions) error { return createNewContext(o, stackOrchestrator, cli, s) } -func createNewContext(o *CreateOptions, stackOrchestrator command.Orchestrator, cli command.Cli, s store.Store) error { +func createNewContext(o *CreateOptions, stackOrchestrator command.Orchestrator, cli command.Cli, s store.Writer) error { if o.Docker == nil { return errors.New("docker endpoint configuration is required") } @@ -132,7 +132,7 @@ func createNewContext(o *CreateOptions, stackOrchestrator command.Orchestrator, return nil } -func checkContextNameForCreation(s store.Store, name string) error { +func checkContextNameForCreation(s store.Reader, name string) error { if err := validateContextName(name); err != nil { return err } @@ -145,12 +145,12 @@ func checkContextNameForCreation(s store.Store, name string) error { return nil } -func createFromExistingContext(s store.Store, fromContextName string, stackOrchestrator command.Orchestrator, o *CreateOptions) error { +func createFromExistingContext(s store.ReaderWriter, fromContextName string, stackOrchestrator command.Orchestrator, o *CreateOptions) error { if len(o.Docker) != 0 || len(o.Kubernetes) != 0 { return errors.New("cannot use --docker or --kubernetes flags when --from is set") } reader := store.Export(fromContextName, &descriptionAndOrchestratorStoreDecorator{ - Store: s, + Reader: s, description: o.Description, orchestrator: stackOrchestrator, }) @@ -159,13 +159,13 @@ func createFromExistingContext(s store.Store, fromContextName string, stackOrche } type descriptionAndOrchestratorStoreDecorator struct { - store.Store + store.Reader description string orchestrator command.Orchestrator } func (d *descriptionAndOrchestratorStoreDecorator) GetContextMetadata(name string) (store.ContextMetadata, error) { - c, err := d.Store.GetContextMetadata(name) + c, err := d.Reader.GetContextMetadata(name) if err != nil { return c, err } diff --git a/cli/command/context/create_test.go b/cli/command/context/create_test.go index 54bbcbe413..40fd4f5d8f 100644 --- a/cli/command/context/create_test.go +++ b/cli/command/context/create_test.go @@ -156,7 +156,7 @@ func TestCreateOrchestratorEmpty(t *testing.T) { assert.NilError(t, err) } -func validateTestKubeEndpoint(t *testing.T, s store.Store, name string) { +func validateTestKubeEndpoint(t *testing.T, s store.Reader, name string) { t.Helper() ctxMetadata, err := s.GetContextMetadata(name) assert.NilError(t, err) diff --git a/cli/context/docker/load.go b/cli/context/docker/load.go index 5661fa9154..7be6952491 100644 --- a/cli/context/docker/load.go +++ b/cli/context/docker/load.go @@ -31,7 +31,7 @@ type Endpoint struct { } // WithTLSData loads TLS materials for the endpoint -func WithTLSData(s store.Store, contextName string, m EndpointMeta) (Endpoint, error) { +func WithTLSData(s store.Reader, contextName string, m EndpointMeta) (Endpoint, error) { tlsData, err := context.LoadTLSData(s, contextName, DockerEndpoint) if err != nil { return Endpoint{}, err diff --git a/cli/context/kubernetes/endpoint_test.go b/cli/context/kubernetes/endpoint_test.go index da124851a5..d14053b816 100644 --- a/cli/context/kubernetes/endpoint_test.go +++ b/cli/context/kubernetes/endpoint_test.go @@ -104,22 +104,22 @@ func TestSaveLoadContexts(t *testing.T) { rawNoTLSEP, err := rawNoTLS.WithTLSData(store, "raw-notls") assert.NilError(t, err) - checkClientConfig(t, store, rawNoTLSEP, "https://test", "test", nil, nil, nil, false) + checkClientConfig(t, rawNoTLSEP, "https://test", "test", nil, nil, nil, false) rawNoTLSSkipEP, err := rawNoTLSSkip.WithTLSData(store, "raw-notls-skip") assert.NilError(t, err) - checkClientConfig(t, store, rawNoTLSSkipEP, "https://test", "test", nil, nil, nil, true) + checkClientConfig(t, rawNoTLSSkipEP, "https://test", "test", nil, nil, nil, true) rawTLSEP, err := rawTLS.WithTLSData(store, "raw-tls") assert.NilError(t, err) - checkClientConfig(t, store, rawTLSEP, "https://test", "test", []byte("ca"), []byte("cert"), []byte("key"), true) + checkClientConfig(t, rawTLSEP, "https://test", "test", []byte("ca"), []byte("cert"), []byte("key"), true) embededDefaultEP, err := embededDefault.WithTLSData(store, "embed-default-context") assert.NilError(t, err) - checkClientConfig(t, store, embededDefaultEP, "https://server1", "namespace1", nil, []byte("cert"), []byte("key"), true) + checkClientConfig(t, embededDefaultEP, "https://server1", "namespace1", nil, []byte("cert"), []byte("key"), true) embededContext2EP, err := embededContext2.WithTLSData(store, "embed-context2") assert.NilError(t, err) - checkClientConfig(t, store, embededContext2EP, "https://server2", "namespace-override", []byte("ca"), []byte("cert"), []byte("key"), false) + checkClientConfig(t, embededContext2EP, "https://server2", "namespace-override", []byte("ca"), []byte("cert"), []byte("key"), false) } -func checkClientConfig(t *testing.T, s store.Store, ep Endpoint, server, namespace string, ca, cert, key []byte, skipTLSVerify bool) { +func checkClientConfig(t *testing.T, ep Endpoint, server, namespace string, ca, cert, key []byte, skipTLSVerify bool) { config := ep.KubernetesConfig() cfg, err := config.ClientConfig() assert.NilError(t, err) @@ -132,7 +132,7 @@ func checkClientConfig(t *testing.T, s store.Store, ep Endpoint, server, namespa assert.Equal(t, skipTLSVerify, cfg.Insecure) } -func save(s store.Store, ep Endpoint, name string) error { +func save(s store.Writer, ep Endpoint, name string) error { meta := store.ContextMetadata{ Endpoints: map[string]interface{}{ KubernetesEndpoint: ep.EndpointMeta, diff --git a/cli/context/kubernetes/load.go b/cli/context/kubernetes/load.go index 803fd8c812..2920b1e36d 100644 --- a/cli/context/kubernetes/load.go +++ b/cli/context/kubernetes/load.go @@ -25,7 +25,7 @@ type Endpoint struct { } // WithTLSData loads TLS materials for the endpoint -func (c *EndpointMeta) WithTLSData(s store.Store, contextName string) (Endpoint, error) { +func (c *EndpointMeta) WithTLSData(s store.Reader, contextName string) (Endpoint, error) { tlsData, err := context.LoadTLSData(s, contextName, KubernetesEndpoint) if err != nil { return Endpoint{}, err @@ -77,7 +77,7 @@ func EndpointFromContext(metadata store.ContextMetadata) *EndpointMeta { // ConfigFromContext resolves a kubernetes client config for the specified context. // If kubeconfigOverride is specified, use this config file instead of the context defaults.ConfigFromContext // if command.ContextDockerHost is specified as the context name, fallsback to the default user's kubeconfig file -func ConfigFromContext(name string, s store.Store) (clientcmd.ClientConfig, error) { +func ConfigFromContext(name string, s store.Reader) (clientcmd.ClientConfig, error) { ctxMeta, err := s.GetContextMetadata(name) if err != nil { return nil, err diff --git a/cli/context/store/store.go b/cli/context/store/store.go index f3561b3cf6..e2867f9862 100644 --- a/cli/context/store/store.go +++ b/cli/context/store/store.go @@ -18,17 +18,49 @@ import ( // Store provides a context store for easily remembering endpoints configuration type Store interface { - ListContexts() ([]ContextMetadata, error) - CreateOrUpdateContext(meta ContextMetadata) error - RemoveContext(name string) error + Reader + Lister + Writer + StorageInfo +} + +// Reader provides read-only (without list) access to context data +type Reader interface { GetContextMetadata(name string) (ContextMetadata, error) - ResetContextTLSMaterial(name string, data *ContextTLSData) error - ResetContextEndpointTLSMaterial(contextName string, endpointName string, data *EndpointTLSData) error ListContextTLSFiles(name string) (map[string]EndpointFiles, error) GetContextTLSData(contextName, endpointName, fileName string) ([]byte, error) +} + +// Lister provides listing of contexts +type Lister interface { + ListContexts() ([]ContextMetadata, error) +} + +// ReaderLister combines Reader and Lister interfaces +type ReaderLister interface { + Reader + Lister +} + +// StorageInfo provides more information about storage details of contexts +type StorageInfo interface { GetContextStorageInfo(contextName string) ContextStorageInfo } +// Writer provides write access to context data +type Writer interface { + CreateOrUpdateContext(meta ContextMetadata) error + RemoveContext(name string) error + ResetContextTLSMaterial(name string, data *ContextTLSData) error + ResetContextEndpointTLSMaterial(contextName string, endpointName string, data *EndpointTLSData) error +} + +// ReaderWriter combines Reader and Writer interfaces +type ReaderWriter interface { + Reader + Writer +} + // ContextMetadata contains metadata about a context and its endpoints type ContextMetadata struct { Name string `json:",omitempty"` @@ -151,7 +183,7 @@ func (s *store) GetContextStorageInfo(contextName string) ContextStorageInfo { // Export exports an existing namespace into an opaque data stream // This stream is actually a tarball containing context metadata and TLS materials, but it does // not map 1:1 the layout of the context store (don't try to restore it manually without calling store.Import) -func Export(name string, s Store) io.ReadCloser { +func Export(name string, s Reader) io.ReadCloser { reader, writer := io.Pipe() go func() { tw := tar.NewWriter(writer) @@ -228,7 +260,7 @@ func Export(name string, s Store) io.ReadCloser { } // Import imports an exported context into a store -func Import(name string, s Store, reader io.Reader) error { +func Import(name string, s Writer, reader io.Reader) error { tr := tar.NewReader(reader) tlsData := ContextTLSData{ Endpoints: map[string]EndpointTLSData{}, diff --git a/cli/context/tlsdata.go b/cli/context/tlsdata.go index 6bd05fbb78..da62fa33f5 100644 --- a/cli/context/tlsdata.go +++ b/cli/context/tlsdata.go @@ -42,7 +42,7 @@ func (data *TLSData) ToStoreTLSData() *store.EndpointTLSData { } // LoadTLSData loads TLS data from the store -func LoadTLSData(s store.Store, contextName, endpointName string) (*TLSData, error) { +func LoadTLSData(s store.Reader, contextName, endpointName string) (*TLSData, error) { tlsFiles, err := s.ListContextTLSFiles(contextName) if err != nil { return nil, errors.Wrapf(err, "failed to retrieve context tls files for context %q", contextName)