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>
220 lines
7.3 KiB
Go
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)
|
|
}
|