mirror of https://github.com/docker/cli.git
Split the context store interface
This is to make it easier to implement support for exporting contexts in 3rd party code, or to create mocks in tests. 2 exemples where it simplify things: - docker-app desktop-specific context decorator (which rewrites parts of the docker context to simplify UX when using on Docker Desktop contexts) - ucp for including a context in the connection bundle Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>
This commit is contained in:
parent
c61435b9c7
commit
225c9b189a
|
@ -290,7 +290,7 @@ func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigF
|
||||||
return client.NewClientWithOpts(clientOpts...)
|
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)
|
ctxMeta, err := s.GetContextMetadata(contextName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return docker.Endpoint{}, err
|
return docker.Endpoint{}, err
|
||||||
|
@ -500,7 +500,7 @@ func UserAgent() string {
|
||||||
// - if DOCKER_CONTEXT is set, use this value
|
// - if DOCKER_CONTEXT is set, use this value
|
||||||
// - if Config file has a globally set "CurrentContext", 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
|
// - 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 {
|
if opts.Context != "" && len(opts.Hosts) > 0 {
|
||||||
return "", errors.New("Conflicting options: either specify --host or --context, not both")
|
return "", errors.New("Conflicting options: either specify --host or --context, not both")
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,7 @@ func RunCreate(cli command.Cli, o *CreateOptions) error {
|
||||||
return createNewContext(o, stackOrchestrator, cli, s)
|
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 {
|
if o.Docker == nil {
|
||||||
return errors.New("docker endpoint configuration is required")
|
return errors.New("docker endpoint configuration is required")
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ func createNewContext(o *CreateOptions, stackOrchestrator command.Orchestrator,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkContextNameForCreation(s store.Store, name string) error {
|
func checkContextNameForCreation(s store.Reader, name string) error {
|
||||||
if err := validateContextName(name); err != nil {
|
if err := validateContextName(name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -145,12 +145,12 @@ func checkContextNameForCreation(s store.Store, name string) error {
|
||||||
return nil
|
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 {
|
if len(o.Docker) != 0 || len(o.Kubernetes) != 0 {
|
||||||
return errors.New("cannot use --docker or --kubernetes flags when --from is set")
|
return errors.New("cannot use --docker or --kubernetes flags when --from is set")
|
||||||
}
|
}
|
||||||
reader := store.Export(fromContextName, &descriptionAndOrchestratorStoreDecorator{
|
reader := store.Export(fromContextName, &descriptionAndOrchestratorStoreDecorator{
|
||||||
Store: s,
|
Reader: s,
|
||||||
description: o.Description,
|
description: o.Description,
|
||||||
orchestrator: stackOrchestrator,
|
orchestrator: stackOrchestrator,
|
||||||
})
|
})
|
||||||
|
@ -159,13 +159,13 @@ func createFromExistingContext(s store.Store, fromContextName string, stackOrche
|
||||||
}
|
}
|
||||||
|
|
||||||
type descriptionAndOrchestratorStoreDecorator struct {
|
type descriptionAndOrchestratorStoreDecorator struct {
|
||||||
store.Store
|
store.Reader
|
||||||
description string
|
description string
|
||||||
orchestrator command.Orchestrator
|
orchestrator command.Orchestrator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *descriptionAndOrchestratorStoreDecorator) GetContextMetadata(name string) (store.ContextMetadata, error) {
|
func (d *descriptionAndOrchestratorStoreDecorator) GetContextMetadata(name string) (store.ContextMetadata, error) {
|
||||||
c, err := d.Store.GetContextMetadata(name)
|
c, err := d.Reader.GetContextMetadata(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,7 +156,7 @@ func TestCreateOrchestratorEmpty(t *testing.T) {
|
||||||
assert.NilError(t, err)
|
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()
|
t.Helper()
|
||||||
ctxMetadata, err := s.GetContextMetadata(name)
|
ctxMetadata, err := s.GetContextMetadata(name)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
|
@ -31,7 +31,7 @@ type Endpoint struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithTLSData loads TLS materials for the endpoint
|
// 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)
|
tlsData, err := context.LoadTLSData(s, contextName, DockerEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Endpoint{}, err
|
return Endpoint{}, err
|
||||||
|
|
|
@ -104,22 +104,22 @@ func TestSaveLoadContexts(t *testing.T) {
|
||||||
|
|
||||||
rawNoTLSEP, err := rawNoTLS.WithTLSData(store, "raw-notls")
|
rawNoTLSEP, err := rawNoTLS.WithTLSData(store, "raw-notls")
|
||||||
assert.NilError(t, err)
|
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")
|
rawNoTLSSkipEP, err := rawNoTLSSkip.WithTLSData(store, "raw-notls-skip")
|
||||||
assert.NilError(t, err)
|
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")
|
rawTLSEP, err := rawTLS.WithTLSData(store, "raw-tls")
|
||||||
assert.NilError(t, err)
|
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")
|
embededDefaultEP, err := embededDefault.WithTLSData(store, "embed-default-context")
|
||||||
assert.NilError(t, err)
|
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")
|
embededContext2EP, err := embededContext2.WithTLSData(store, "embed-context2")
|
||||||
assert.NilError(t, err)
|
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()
|
config := ep.KubernetesConfig()
|
||||||
cfg, err := config.ClientConfig()
|
cfg, err := config.ClientConfig()
|
||||||
assert.NilError(t, err)
|
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)
|
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{
|
meta := store.ContextMetadata{
|
||||||
Endpoints: map[string]interface{}{
|
Endpoints: map[string]interface{}{
|
||||||
KubernetesEndpoint: ep.EndpointMeta,
|
KubernetesEndpoint: ep.EndpointMeta,
|
||||||
|
|
|
@ -25,7 +25,7 @@ type Endpoint struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithTLSData loads TLS materials for the endpoint
|
// 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)
|
tlsData, err := context.LoadTLSData(s, contextName, KubernetesEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Endpoint{}, err
|
return Endpoint{}, err
|
||||||
|
@ -77,7 +77,7 @@ func EndpointFromContext(metadata store.ContextMetadata) *EndpointMeta {
|
||||||
// ConfigFromContext resolves a kubernetes client config for the specified context.
|
// 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 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
|
// 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)
|
ctxMeta, err := s.GetContextMetadata(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -18,17 +18,49 @@ import (
|
||||||
|
|
||||||
// Store provides a context store for easily remembering endpoints configuration
|
// Store provides a context store for easily remembering endpoints configuration
|
||||||
type Store interface {
|
type Store interface {
|
||||||
ListContexts() ([]ContextMetadata, error)
|
Reader
|
||||||
CreateOrUpdateContext(meta ContextMetadata) error
|
Lister
|
||||||
RemoveContext(name string) error
|
Writer
|
||||||
|
StorageInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reader provides read-only (without list) access to context data
|
||||||
|
type Reader interface {
|
||||||
GetContextMetadata(name string) (ContextMetadata, error)
|
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)
|
ListContextTLSFiles(name string) (map[string]EndpointFiles, error)
|
||||||
GetContextTLSData(contextName, endpointName, fileName string) ([]byte, 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
|
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
|
// ContextMetadata contains metadata about a context and its endpoints
|
||||||
type ContextMetadata struct {
|
type ContextMetadata struct {
|
||||||
Name string `json:",omitempty"`
|
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
|
// 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
|
// 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)
|
// 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()
|
reader, writer := io.Pipe()
|
||||||
go func() {
|
go func() {
|
||||||
tw := tar.NewWriter(writer)
|
tw := tar.NewWriter(writer)
|
||||||
|
@ -228,7 +260,7 @@ func Export(name string, s Store) io.ReadCloser {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Import imports an exported context into a store
|
// 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)
|
tr := tar.NewReader(reader)
|
||||||
tlsData := ContextTLSData{
|
tlsData := ContextTLSData{
|
||||||
Endpoints: map[string]EndpointTLSData{},
|
Endpoints: map[string]EndpointTLSData{},
|
||||||
|
|
|
@ -42,7 +42,7 @@ func (data *TLSData) ToStoreTLSData() *store.EndpointTLSData {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadTLSData loads TLS data from the store
|
// 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)
|
tlsFiles, err := s.ListContextTLSFiles(contextName)
|
||||||
if err != nil {
|
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 context tls files for context %q", contextName)
|
||||||
|
|
Loading…
Reference in New Issue