docker-cli/cli/command/defaultcontextstore.go
Ian Campbell 520be05c49 Dynamically register kubernetes context store endpoint type.
This removes the need for the core context code to import
`github.com/docker/cli/cli/context/kubernetes` which in turn reduces the
transitive import tree in this file to not pull in all of Kubernetes.

Note that this means that any calling code which is interested in the
kubernetes endpoint must import `github.com/docker/cli/cli/context/kubernetes`
itself somewhere in order to trigger the dynamic registration. In practice
anything which is interested in Kubernetes must import that package (e.g.
`./cli/command/context.list` does for the `EndpointFromContext` function) to do
anything useful, so this restriction is not too onerous.

As a special case a small amount of Kubernetes related logic remains in
`ResolveDefaultContext` to handle error handling when the stack orchestrator
includes Kubernetes. In order to avoid a circular import loop this hardcodes
the kube endpoint name.

Similarly to avoid an import loop the existing `TestDefaultContextInitializer`
cannot continue to unit test for the Kubernetes case, so that aspect of the
test is carved off into a very similar test in the kubernetes context package.

Lastly, note that the kubernetes endpoint is now modifiable via
`WithContextEndpointType`.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-05-20 13:28:11 +01:00

220 lines
7.3 KiB
Go

package command
import (
"fmt"
"io"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/context/docker"
"github.com/docker/cli/cli/context/store"
cliflags "github.com/docker/cli/cli/flags"
"github.com/pkg/errors"
)
const (
// DefaultContextName is the name reserved for the default context (config & env based)
DefaultContextName = "default"
)
// DefaultContext contains the default context data for all endpoints
type DefaultContext struct {
Meta store.Metadata
TLS store.ContextTLSData
}
// DefaultContextResolver is a function which resolves the default context base on the configuration and the env variables
type DefaultContextResolver func() (*DefaultContext, error)
// ContextStoreWithDefault implements the store.Store interface with a support for the default context
type ContextStoreWithDefault struct {
store.Store
Resolver DefaultContextResolver
}
// EndpointDefaultResolver is implemented by any EndpointMeta object
// which wants to be able to populate the store with whatever their default is.
type EndpointDefaultResolver interface {
// ResolveDefault returns values suitable for storing in store.Metadata.Endpoints
// and store.ContextTLSData.Endpoints. If there is no default then returns nil, nil.
ResolveDefault() (interface{}, *store.EndpointTLSData)
}
// ResolveDefaultContext creates a Metadata for the current CLI invocation parameters
func ResolveDefaultContext(opts *cliflags.CommonOptions, config *configfile.ConfigFile, storeconfig store.Config, stderr io.Writer) (*DefaultContext, error) {
stackOrchestrator, err := GetStackOrchestrator("", "", config.StackOrchestrator, stderr)
if err != nil {
return nil, err
}
contextTLSData := store.ContextTLSData{
Endpoints: make(map[string]store.EndpointTLSData),
}
contextMetadata := store.Metadata{
Endpoints: make(map[string]interface{}),
Metadata: DockerContext{
Description: "",
StackOrchestrator: stackOrchestrator,
},
Name: DefaultContextName,
}
dockerEP, err := resolveDefaultDockerEndpoint(opts)
if err != nil {
return nil, err
}
contextMetadata.Endpoints[docker.DockerEndpoint] = dockerEP.EndpointMeta
if dockerEP.TLSData != nil {
contextTLSData.Endpoints[docker.DockerEndpoint] = *dockerEP.TLSData.ToStoreTLSData()
}
// We open code the string "kubernetes" below because we
// cannot import KubernetesEndpoint from the corresponding
// package due to import loops.
wantKubernetesEP := stackOrchestrator == OrchestratorKubernetes || stackOrchestrator == OrchestratorAll
if err := storeconfig.ForeachEndpointType(func(n string, get store.TypeGetter) error {
if n == docker.DockerEndpoint { // handled above
return nil
}
ep := get()
if i, ok := ep.(EndpointDefaultResolver); ok {
meta, tls := i.ResolveDefault()
if meta == nil {
if wantKubernetesEP && n == "kubernetes" {
return errors.Errorf("default orchestrator is %s but unable to resolve kubernetes endpoint", stackOrchestrator)
}
return nil
}
contextMetadata.Endpoints[n] = meta
if tls != nil {
contextTLSData.Endpoints[n] = *tls
}
}
// Nothing to be done
return nil
}); err != nil {
return nil, err
}
if _, ok := contextMetadata.Endpoints["kubernetes"]; wantKubernetesEP && !ok {
return nil, errors.Errorf("default orchestrator is %s but kubernetes endpoint could not be found", stackOrchestrator)
}
return &DefaultContext{Meta: contextMetadata, TLS: contextTLSData}, nil
}
// List implements store.Store's List
func (s *ContextStoreWithDefault) List() ([]store.Metadata, error) {
contextList, err := s.Store.List()
if err != nil {
return nil, err
}
defaultContext, err := s.Resolver()
if err != nil {
return nil, err
}
return append(contextList, defaultContext.Meta), nil
}
// 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 s.Store.CreateOrUpdate(meta)
}
// 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 s.Store.Remove(name)
}
// GetMetadata implements store.Store's GetMetadata
func (s *ContextStoreWithDefault) GetMetadata(name string) (store.Metadata, error) {
if name == DefaultContextName {
defaultContext, err := s.Resolver()
if err != nil {
return store.Metadata{}, err
}
return defaultContext.Meta, nil
}
return s.Store.GetMetadata(name)
}
// 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 s.Store.ResetTLSMaterial(name, data)
}
// 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 s.Store.ResetEndpointTLSMaterial(contextName, endpointName, data)
}
// ListTLSFiles implements store.Store's ListTLSFiles
func (s *ContextStoreWithDefault) ListTLSFiles(name string) (map[string]store.EndpointFiles, error) {
if name == DefaultContextName {
defaultContext, err := s.Resolver()
if err != nil {
return nil, err
}
tlsfiles := make(map[string]store.EndpointFiles)
for epName, epTLSData := range defaultContext.TLS.Endpoints {
var files store.EndpointFiles
for filename := range epTLSData.Files {
files = append(files, filename)
}
tlsfiles[epName] = files
}
return tlsfiles, nil
}
return s.Store.ListTLSFiles(name)
}
// GetTLSData implements store.Store's GetTLSData
func (s *ContextStoreWithDefault) GetTLSData(contextName, endpointName, fileName string) ([]byte, error) {
if contextName == DefaultContextName {
defaultContext, err := s.Resolver()
if err != nil {
return nil, err
}
if defaultContext.TLS.Endpoints[endpointName].Files[fileName] == nil {
return nil, &noDefaultTLSDataError{endpointName: endpointName, fileName: 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 {
return store.StorageInfo{MetadataPath: "<IN MEMORY>", TLSPath: "<IN MEMORY>"}
}
return s.Store.GetStorageInfo(contextName)
}