mirror of https://github.com/docker/cli.git
Make default context behaves like a real context:
- when using "--context default" parameter - when printing the list of contexts - when exporting the default context to a tarball Signed-off-by: Jean-Christophe Sirot <jean-christophe.sirot@docker.com> (+1 squashed commit) Squashed commits: [20670495] Fix CLI initialization for the `docker stack deploy --help` command and ensure that the dockerCli.CurrentContext() always returns a non empty context name (default as a fallback) Remove now obsolete code handling empty string context name Minor code cleanup Signed-off-by: Jean-Christophe Sirot <jean-christophe.sirot@docker.com>
This commit is contained in:
parent
86a5a489f7
commit
b3aa17187f
|
@ -3,6 +3,7 @@ package command
|
|||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
@ -209,12 +210,18 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...Initialize
|
|||
|
||||
cli.configFile = cliconfig.LoadDefaultConfigFile(cli.err)
|
||||
|
||||
cli.contextStore = store.New(cliconfig.ContextStoreDir(), cli.contextStoreConfig)
|
||||
baseContextSore := store.New(cliconfig.ContextStoreDir(), cli.contextStoreConfig)
|
||||
cli.contextStore = &ContextStoreWithDefault{
|
||||
Store: baseContextSore,
|
||||
Resolver: func() (*DefaultContext, error) {
|
||||
return resolveDefaultContext(opts.Common, cli.ConfigFile(), cli.Err())
|
||||
},
|
||||
}
|
||||
cli.currentContext, err = resolveContextName(opts.Common, cli.configFile, cli.contextStore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli.dockerEndpoint, err = resolveDockerEndpoint(cli.contextStore, cli.currentContext, opts.Common)
|
||||
cli.dockerEndpoint, err = resolveDockerEndpoint(cli.contextStore, cli.currentContext)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to resolve docker endpoint")
|
||||
}
|
||||
|
@ -252,12 +259,17 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...Initialize
|
|||
|
||||
// NewAPIClientFromFlags creates a new APIClient from command line flags
|
||||
func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) {
|
||||
store := store.New(cliconfig.ContextStoreDir(), defaultContextStoreConfig())
|
||||
store := &ContextStoreWithDefault{
|
||||
Store: store.New(cliconfig.ContextStoreDir(), defaultContextStoreConfig()),
|
||||
Resolver: func() (*DefaultContext, error) {
|
||||
return resolveDefaultContext(opts, configFile, ioutil.Discard)
|
||||
},
|
||||
}
|
||||
contextName, err := resolveContextName(opts, configFile, store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endpoint, err := resolveDockerEndpoint(store, contextName, opts)
|
||||
endpoint, err := resolveDockerEndpoint(store, contextName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to resolve docker endpoint")
|
||||
}
|
||||
|
@ -278,8 +290,7 @@ func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigF
|
|||
return client.NewClientWithOpts(clientOpts...)
|
||||
}
|
||||
|
||||
func resolveDockerEndpoint(s store.Store, contextName string, opts *cliflags.CommonOptions) (docker.Endpoint, error) {
|
||||
if contextName != "" {
|
||||
func resolveDockerEndpoint(s store.Store, contextName string) (docker.Endpoint, error) {
|
||||
ctxMeta, err := s.GetContextMetadata(contextName)
|
||||
if err != nil {
|
||||
return docker.Endpoint{}, err
|
||||
|
@ -290,6 +301,9 @@ func resolveDockerEndpoint(s store.Store, contextName string, opts *cliflags.Com
|
|||
}
|
||||
return docker.WithTLSData(s, contextName, epMeta)
|
||||
}
|
||||
|
||||
// Resolve the Docker endpoint for the default context (based on config, env vars and CLI flags)
|
||||
func resolveDefaultDockerEndpoint(opts *cliflags.CommonOptions) (docker.Endpoint, error) {
|
||||
host, err := getServerHost(opts.Hosts, opts.TLSOptions)
|
||||
if err != nil {
|
||||
return docker.Endpoint{}, err
|
||||
|
@ -384,26 +398,11 @@ func (cli *DockerCli) CurrentContext() string {
|
|||
|
||||
// StackOrchestrator resolves which stack orchestrator is in use
|
||||
func (cli *DockerCli) StackOrchestrator(flagValue string) (Orchestrator, error) {
|
||||
var ctxOrchestrator string
|
||||
|
||||
configFile := cli.configFile
|
||||
if configFile == nil {
|
||||
configFile = cliconfig.LoadDefaultConfigFile(cli.Err())
|
||||
}
|
||||
|
||||
currentContext := cli.CurrentContext()
|
||||
if currentContext == "" {
|
||||
currentContext = configFile.CurrentContext
|
||||
}
|
||||
if currentContext != "" {
|
||||
contextstore := cli.contextStore
|
||||
if contextstore == nil {
|
||||
contextstore = store.New(cliconfig.ContextStoreDir(), cli.contextStoreConfig)
|
||||
}
|
||||
ctxRaw, err := contextstore.GetContextMetadata(currentContext)
|
||||
ctxRaw, err := cli.ContextStore().GetContextMetadata(currentContext)
|
||||
if store.IsErrContextDoesNotExist(err) {
|
||||
// case where the currentContext has been removed (CLI behavior is to fallback to using DOCKER_HOST based resolution)
|
||||
return GetStackOrchestrator(flagValue, "", configFile.StackOrchestrator, cli.Err())
|
||||
return GetStackOrchestrator(flagValue, "", cli.ConfigFile().StackOrchestrator, cli.Err())
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -412,10 +411,8 @@ func (cli *DockerCli) StackOrchestrator(flagValue string) (Orchestrator, error)
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ctxOrchestrator = string(ctxMeta.StackOrchestrator)
|
||||
}
|
||||
|
||||
return GetStackOrchestrator(flagValue, ctxOrchestrator, configFile.StackOrchestrator, cli.Err())
|
||||
ctxOrchestrator := string(ctxMeta.StackOrchestrator)
|
||||
return GetStackOrchestrator(flagValue, ctxOrchestrator, cli.ConfigFile().StackOrchestrator, cli.Err())
|
||||
}
|
||||
|
||||
// DockerEndpoint returns the current docker endpoint
|
||||
|
@ -511,10 +508,10 @@ func resolveContextName(opts *cliflags.CommonOptions, config *configfile.ConfigF
|
|||
return opts.Context, nil
|
||||
}
|
||||
if len(opts.Hosts) > 0 {
|
||||
return "", nil
|
||||
return DefaultContextName, nil
|
||||
}
|
||||
if _, present := os.LookupEnv("DOCKER_HOST"); present {
|
||||
return "", nil
|
||||
return DefaultContextName, nil
|
||||
}
|
||||
if ctxName, ok := os.LookupEnv("DOCKER_CONTEXT"); ok {
|
||||
return ctxName, nil
|
||||
|
@ -526,7 +523,7 @@ func resolveContextName(opts *cliflags.CommonOptions, config *configfile.ConfigF
|
|||
}
|
||||
return config.CurrentContext, err
|
||||
}
|
||||
return "", nil
|
||||
return DefaultContextName, nil
|
||||
}
|
||||
|
||||
func defaultContextStoreConfig() store.Config {
|
||||
|
|
|
@ -23,7 +23,26 @@ func makeFakeCli(t *testing.T, opts ...func(*test.FakeCli)) (*test.FakeCli, func
|
|||
store.EndpointTypeGetter(docker.DockerEndpoint, func() interface{} { return &docker.EndpointMeta{} }),
|
||||
store.EndpointTypeGetter(kubernetes.KubernetesEndpoint, func() interface{} { return &kubernetes.EndpointMeta{} }),
|
||||
)
|
||||
store := store.New(dir, storeConfig)
|
||||
store := &command.ContextStoreWithDefault{
|
||||
Store: store.New(dir, storeConfig),
|
||||
Resolver: func() (*command.DefaultContext, error) {
|
||||
return &command.DefaultContext{
|
||||
Meta: store.ContextMetadata{
|
||||
Endpoints: map[string]interface{}{
|
||||
docker.DockerEndpoint: docker.EndpointMeta{
|
||||
Host: "unix:///var/run/docker.sock",
|
||||
},
|
||||
},
|
||||
Metadata: command.DockerContext{
|
||||
Description: "",
|
||||
StackOrchestrator: command.OrchestratorSwarm,
|
||||
},
|
||||
Name: command.DefaultContextName,
|
||||
},
|
||||
TLS: store.ContextTLSData{},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
cleanup := func() {
|
||||
os.RemoveAll(dir)
|
||||
}
|
||||
|
@ -52,6 +71,12 @@ func TestCreateInvalids(t *testing.T) {
|
|||
{
|
||||
expecterErr: `context name cannot be empty`,
|
||||
},
|
||||
{
|
||||
options: CreateOptions{
|
||||
Name: "default",
|
||||
},
|
||||
expecterErr: `"default" is a reserved context name`,
|
||||
},
|
||||
{
|
||||
options: CreateOptions{
|
||||
Name: " ",
|
||||
|
|
|
@ -77,7 +77,7 @@ func writeTo(dockerCli command.Cli, reader io.Reader, dest string) error {
|
|||
|
||||
// RunExport exports a Docker context
|
||||
func RunExport(dockerCli command.Cli, opts *ExportOptions) error {
|
||||
if err := validateContextName(opts.ContextName); err != nil {
|
||||
if err := validateContextName(opts.ContextName); err != nil && opts.ContextName != command.DefaultContextName {
|
||||
return err
|
||||
}
|
||||
ctxMeta, err := dockerCli.ContextStore().GetContextMetadata(opts.ContextName)
|
||||
|
|
|
@ -40,9 +40,6 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
|||
|
||||
func runInspect(dockerCli command.Cli, opts inspectOptions) error {
|
||||
getRefFunc := func(ref string) (interface{}, []byte, error) {
|
||||
if ref == "default" {
|
||||
return nil, nil, errors.New(`context "default" cannot be inspected`)
|
||||
}
|
||||
c, err := dockerCli.ContextStore().GetContextMetadata(ref)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/cli/context/docker"
|
||||
kubecontext "github.com/docker/cli/cli/context/kubernetes"
|
||||
"github.com/docker/cli/kubernetes"
|
||||
"github.com/spf13/cobra"
|
||||
"vbom.ml/util/sortorder"
|
||||
)
|
||||
|
@ -61,6 +60,9 @@ func runList(dockerCli command.Cli, opts *listOptions) error {
|
|||
if kubernetesEndpoint != nil {
|
||||
kubEndpointText = fmt.Sprintf("%s (%s)", kubernetesEndpoint.Host, kubernetesEndpoint.DefaultNamespace)
|
||||
}
|
||||
if rawMeta.Name == command.DefaultContextName {
|
||||
meta.Description = "Current DOCKER_HOST based configuration"
|
||||
}
|
||||
desc := formatter.ClientContext{
|
||||
Name: rawMeta.Name,
|
||||
Current: rawMeta.Name == curContext,
|
||||
|
@ -71,29 +73,6 @@ func runList(dockerCli command.Cli, opts *listOptions) error {
|
|||
}
|
||||
contexts = append(contexts, &desc)
|
||||
}
|
||||
if !opts.quiet {
|
||||
desc := &formatter.ClientContext{
|
||||
Name: "default",
|
||||
Description: "Current DOCKER_HOST based configuration",
|
||||
}
|
||||
if dockerCli.CurrentContext() == "" {
|
||||
orchestrator, _ := dockerCli.StackOrchestrator("")
|
||||
kubEndpointText := ""
|
||||
kubeconfig := kubernetes.NewKubernetesConfig("")
|
||||
if cfg, err := kubeconfig.ClientConfig(); err == nil {
|
||||
ns, _, _ := kubeconfig.Namespace()
|
||||
if ns == "" {
|
||||
ns = "default"
|
||||
}
|
||||
kubEndpointText = fmt.Sprintf("%s (%s)", cfg.Host, ns)
|
||||
}
|
||||
desc.Current = true
|
||||
desc.StackOrchestrator = string(orchestrator)
|
||||
desc.DockerEndpoint = dockerCli.DockerEndpoint().Host
|
||||
desc.KubernetesEndpoint = kubEndpointText
|
||||
}
|
||||
contexts = append(contexts, desc)
|
||||
}
|
||||
sort.Slice(contexts, func(i, j int) bool {
|
||||
return sortorder.NaturalLess(contexts[i].Name, contexts[j].Name)
|
||||
})
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/context/docker"
|
||||
"gotest.tools/assert"
|
||||
"gotest.tools/env"
|
||||
"gotest.tools/golden"
|
||||
|
@ -36,20 +35,6 @@ func TestList(t *testing.T) {
|
|||
golden.Assert(t, cli.OutBuffer().String(), "list.golden")
|
||||
}
|
||||
|
||||
func TestListNoContext(t *testing.T) {
|
||||
cli, cleanup := makeFakeCli(t)
|
||||
defer cleanup()
|
||||
defer env.Patch(t, "KUBECONFIG", "./testdata/test-kubeconfig")()
|
||||
cli.SetDockerEndpoint(docker.Endpoint{
|
||||
EndpointMeta: docker.EndpointMeta{
|
||||
Host: "https://someswarmserver",
|
||||
},
|
||||
})
|
||||
cli.OutBuffer().Reset()
|
||||
assert.NilError(t, runList(cli, &listOptions{}))
|
||||
golden.Assert(t, cli.OutBuffer().String(), "list.no-context.golden")
|
||||
}
|
||||
|
||||
func TestListQuiet(t *testing.T) {
|
||||
cli, cleanup := makeFakeCli(t)
|
||||
defer cleanup()
|
||||
|
|
|
@ -62,3 +62,12 @@ func TestRemoveCurrentForce(t *testing.T) {
|
|||
assert.NilError(t, err)
|
||||
assert.Equal(t, "", reloadedConfig.CurrentContext)
|
||||
}
|
||||
|
||||
func TestRemoveDefault(t *testing.T) {
|
||||
cli, cleanup := makeFakeCli(t)
|
||||
defer cleanup()
|
||||
createTestContextWithKubeAndSwarm(t, cli, "other", "all")
|
||||
cli.SetCurrentContext("current")
|
||||
err := RunRemove(cli, RemoveOptions{}, []string{"default"})
|
||||
assert.ErrorContains(t, err, `default: context "default" cannot be removed`)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
NAME DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHESTRATOR
|
||||
current * description of current https://someswarmserver https://someserver (default) all
|
||||
default Current DOCKER_HOST based configuration
|
||||
default Current DOCKER_HOST based configuration unix:///var/run/docker.sock swarm
|
||||
other description of other https://someswarmserver https://someserver (default) all
|
||||
unset description of unset https://someswarmserver https://someserver (default)
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
NAME DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHESTRATOR
|
||||
default * Current DOCKER_HOST based configuration https://someswarmserver https://someserver (default) swarm
|
|
@ -1,2 +1,3 @@
|
|||
current
|
||||
default
|
||||
other
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/context/docker"
|
||||
"github.com/docker/cli/cli/context/kubernetes"
|
||||
"github.com/docker/cli/cli/context/store"
|
||||
cliflags "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/docker/pkg/homedir"
|
||||
"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 enpoints
|
||||
type DefaultContext struct {
|
||||
Meta store.ContextMetadata
|
||||
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
|
||||
}
|
||||
|
||||
// resolveDefaultContext creates a ContextMetadata for the current CLI invocation parameters
|
||||
func resolveDefaultContext(opts *cliflags.CommonOptions, config *configfile.ConfigFile, 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.ContextMetadata{
|
||||
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()
|
||||
}
|
||||
|
||||
// Default context uses env-based kubeconfig for Kubernetes endpoint configuration
|
||||
kubeconfig := os.Getenv("KUBECONFIG")
|
||||
if kubeconfig == "" {
|
||||
kubeconfig = filepath.Join(homedir.Get(), ".kube/config")
|
||||
}
|
||||
kubeEP, err := kubernetes.FromKubeConfig(kubeconfig, "", "")
|
||||
if (stackOrchestrator == OrchestratorKubernetes || stackOrchestrator == OrchestratorAll) && err != nil {
|
||||
return nil, errors.Wrapf(err, "default orchestrator is %s but kubernetes endpoint could not be found", stackOrchestrator)
|
||||
}
|
||||
if err == nil {
|
||||
contextMetadata.Endpoints[kubernetes.KubernetesEndpoint] = kubeEP.EndpointMeta
|
||||
if kubeEP.TLSData != nil {
|
||||
contextTLSData.Endpoints[kubernetes.KubernetesEndpoint] = *kubeEP.TLSData.ToStoreTLSData()
|
||||
}
|
||||
}
|
||||
|
||||
return &DefaultContext{Meta: contextMetadata, TLS: contextTLSData}, nil
|
||||
}
|
||||
|
||||
// ListContexts implements store.Store's ListContexts
|
||||
func (s *ContextStoreWithDefault) ListContexts() ([]store.ContextMetadata, error) {
|
||||
contextList, err := s.Store.ListContexts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defaultContext, err := s.Resolver()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(contextList, defaultContext.Meta), nil
|
||||
}
|
||||
|
||||
// CreateOrUpdateContext is not allowed for the default context and fails
|
||||
func (s *ContextStoreWithDefault) CreateOrUpdateContext(meta store.ContextMetadata) error {
|
||||
if meta.Name == DefaultContextName {
|
||||
return errors.New("default context cannot be created nor updated")
|
||||
}
|
||||
return s.Store.CreateOrUpdateContext(meta)
|
||||
}
|
||||
|
||||
// RemoveContext is not allowed for the default context and fails
|
||||
func (s *ContextStoreWithDefault) RemoveContext(name string) error {
|
||||
if name == DefaultContextName {
|
||||
return errors.New("default context cannot be removed")
|
||||
}
|
||||
return s.Store.RemoveContext(name)
|
||||
}
|
||||
|
||||
// GetContextMetadata implements store.Store's GetContextMetadata
|
||||
func (s *ContextStoreWithDefault) GetContextMetadata(name string) (store.ContextMetadata, error) {
|
||||
if name == DefaultContextName {
|
||||
defaultContext, err := s.Resolver()
|
||||
if err != nil {
|
||||
return store.ContextMetadata{}, err
|
||||
}
|
||||
return defaultContext.Meta, nil
|
||||
}
|
||||
return s.Store.GetContextMetadata(name)
|
||||
}
|
||||
|
||||
// ResetContextTLSMaterial is not implemented for default context and fails
|
||||
func (s *ContextStoreWithDefault) ResetContextTLSMaterial(name string, data *store.ContextTLSData) error {
|
||||
if name == DefaultContextName {
|
||||
return errors.New("The default context store does not support ResetContextTLSMaterial")
|
||||
}
|
||||
return s.Store.ResetContextTLSMaterial(name, data)
|
||||
}
|
||||
|
||||
// ResetContextEndpointTLSMaterial is not implemented for default context and fails
|
||||
func (s *ContextStoreWithDefault) ResetContextEndpointTLSMaterial(contextName string, endpointName string, data *store.EndpointTLSData) error {
|
||||
if contextName == DefaultContextName {
|
||||
return errors.New("The default context store does not support ResetContextEndpointTLSMaterial")
|
||||
}
|
||||
return s.Store.ResetContextEndpointTLSMaterial(contextName, endpointName, data)
|
||||
}
|
||||
|
||||
// ListContextTLSFiles implements store.Store's ListContextTLSFiles
|
||||
func (s *ContextStoreWithDefault) ListContextTLSFiles(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.ListContextTLSFiles(name)
|
||||
}
|
||||
|
||||
// GetContextTLSData implements store.Store's GetContextTLSData
|
||||
func (s *ContextStoreWithDefault) GetContextTLSData(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.GetContextTLSData(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() {}
|
||||
|
||||
// GetContextStorageInfo implements store.Store's GetContextStorageInfo
|
||||
func (s *ContextStoreWithDefault) GetContextStorageInfo(contextName string) store.ContextStorageInfo {
|
||||
if contextName == DefaultContextName {
|
||||
return store.ContextStorageInfo{MetadataPath: "<IN MEMORY>", TLSPath: "<IN MEMORY>"}
|
||||
}
|
||||
return s.Store.GetContextStorageInfo(contextName)
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/context/docker"
|
||||
"github.com/docker/cli/cli/context/kubernetes"
|
||||
"github.com/docker/cli/cli/context/store"
|
||||
cliflags "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"gotest.tools/assert"
|
||||
"gotest.tools/env"
|
||||
"gotest.tools/golden"
|
||||
)
|
||||
|
||||
type endpoint struct {
|
||||
Foo string `json:"a_very_recognizable_field_name"`
|
||||
}
|
||||
|
||||
type testContext struct {
|
||||
Bar string `json:"another_very_recognizable_field_name"`
|
||||
}
|
||||
|
||||
var testCfg = store.NewConfig(func() interface{} { return &testContext{} },
|
||||
store.EndpointTypeGetter("ep1", func() interface{} { return &endpoint{} }),
|
||||
store.EndpointTypeGetter("ep2", func() interface{} { return &endpoint{} }),
|
||||
)
|
||||
|
||||
func testDefaultMetadata() store.ContextMetadata {
|
||||
return store.ContextMetadata{
|
||||
Endpoints: map[string]interface{}{
|
||||
"ep1": endpoint{Foo: "bar"},
|
||||
},
|
||||
Metadata: testContext{Bar: "baz"},
|
||||
Name: DefaultContextName,
|
||||
}
|
||||
}
|
||||
|
||||
func testStore(t *testing.T, meta store.ContextMetadata, tls store.ContextTLSData) (store.Store, func()) {
|
||||
//meta := testDefaultMetadata()
|
||||
testDir, err := ioutil.TempDir("", t.Name())
|
||||
assert.NilError(t, err)
|
||||
//defer os.RemoveAll(testDir)
|
||||
store := &ContextStoreWithDefault{
|
||||
Store: store.New(testDir, testCfg),
|
||||
Resolver: func() (*DefaultContext, error) {
|
||||
return &DefaultContext{
|
||||
Meta: meta,
|
||||
TLS: tls,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
return store, func() {
|
||||
os.RemoveAll(testDir)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultContextInitializer(t *testing.T) {
|
||||
cli, err := NewDockerCli()
|
||||
assert.NilError(t, err)
|
||||
defer env.Patch(t, "DOCKER_HOST", "ssh://someswarmserver")()
|
||||
defer env.Patch(t, "KUBECONFIG", "./testdata/test-kubeconfig")()
|
||||
cli.configFile = &configfile.ConfigFile{
|
||||
StackOrchestrator: "all",
|
||||
}
|
||||
ctx, err := resolveDefaultContext(&cliflags.CommonOptions{
|
||||
TLS: true,
|
||||
TLSOptions: &tlsconfig.Options{
|
||||
CAFile: "./testdata/ca.pem",
|
||||
},
|
||||
}, cli.ConfigFile(), cli.Err())
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, "default", ctx.Meta.Name)
|
||||
assert.Equal(t, OrchestratorAll, ctx.Meta.Metadata.(DockerContext).StackOrchestrator)
|
||||
assert.DeepEqual(t, "ssh://someswarmserver", ctx.Meta.Endpoints[docker.DockerEndpoint].(docker.EndpointMeta).Host)
|
||||
golden.Assert(t, string(ctx.TLS.Endpoints[docker.DockerEndpoint].Files["ca.pem"]), "ca.pem")
|
||||
assert.DeepEqual(t, "zoinx", ctx.Meta.Endpoints[kubernetes.KubernetesEndpoint].(kubernetes.EndpointMeta).DefaultNamespace)
|
||||
}
|
||||
|
||||
func TestExportDefaultImport(t *testing.T) {
|
||||
file1 := make([]byte, 1500)
|
||||
rand.Read(file1)
|
||||
file2 := make([]byte, 3700)
|
||||
rand.Read(file2)
|
||||
s, cleanup := testStore(t, testDefaultMetadata(), store.ContextTLSData{
|
||||
Endpoints: map[string]store.EndpointTLSData{
|
||||
"ep2": {
|
||||
Files: map[string][]byte{
|
||||
"file1": file1,
|
||||
"file2": file2,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
defer cleanup()
|
||||
r := store.Export("default", s)
|
||||
defer r.Close()
|
||||
err := store.Import("dest", s, r)
|
||||
assert.NilError(t, err)
|
||||
|
||||
srcMeta, err := s.GetContextMetadata("default")
|
||||
assert.NilError(t, err)
|
||||
destMeta, err := s.GetContextMetadata("dest")
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, destMeta.Metadata, srcMeta.Metadata)
|
||||
assert.DeepEqual(t, destMeta.Endpoints, srcMeta.Endpoints)
|
||||
|
||||
srcFileList, err := s.ListContextTLSFiles("default")
|
||||
assert.NilError(t, err)
|
||||
destFileList, err := s.ListContextTLSFiles("dest")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, 1, len(destFileList))
|
||||
assert.Equal(t, 1, len(srcFileList))
|
||||
assert.Equal(t, 2, len(destFileList["ep2"]))
|
||||
assert.Equal(t, 2, len(srcFileList["ep2"]))
|
||||
|
||||
srcData1, err := s.GetContextTLSData("default", "ep2", "file1")
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, file1, srcData1)
|
||||
srcData2, err := s.GetContextTLSData("default", "ep2", "file2")
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, file2, srcData2)
|
||||
|
||||
destData1, err := s.GetContextTLSData("dest", "ep2", "file1")
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, file1, destData1)
|
||||
destData2, err := s.GetContextTLSData("dest", "ep2", "file2")
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, file2, destData2)
|
||||
}
|
||||
|
||||
func TestListDefaultContext(t *testing.T) {
|
||||
meta := testDefaultMetadata()
|
||||
s, cleanup := testStore(t, meta, store.ContextTLSData{})
|
||||
defer cleanup()
|
||||
result, err := s.ListContexts()
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, 1, len(result))
|
||||
assert.DeepEqual(t, meta, result[0])
|
||||
}
|
||||
|
||||
func TestGetDefaultContextStorageInfo(t *testing.T) {
|
||||
s, cleanup := testStore(t, testDefaultMetadata(), store.ContextTLSData{})
|
||||
defer cleanup()
|
||||
result := s.GetContextStorageInfo(DefaultContextName)
|
||||
assert.Equal(t, "<IN MEMORY>", result.MetadataPath)
|
||||
assert.Equal(t, "<IN MEMORY>", result.TLSPath)
|
||||
}
|
||||
|
||||
func TestGetDefaultContextMetadata(t *testing.T) {
|
||||
meta := testDefaultMetadata()
|
||||
s, cleanup := testStore(t, meta, store.ContextTLSData{})
|
||||
defer cleanup()
|
||||
result, err := s.GetContextMetadata(DefaultContextName)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, DefaultContextName, result.Name)
|
||||
assert.DeepEqual(t, meta.Metadata, result.Metadata)
|
||||
assert.DeepEqual(t, meta.Endpoints, result.Endpoints)
|
||||
}
|
||||
|
||||
func TestErrCreateDefault(t *testing.T) {
|
||||
meta := testDefaultMetadata()
|
||||
s, cleanup := testStore(t, meta, store.ContextTLSData{})
|
||||
defer cleanup()
|
||||
err := s.CreateOrUpdateContext(store.ContextMetadata{
|
||||
Endpoints: map[string]interface{}{
|
||||
"ep1": endpoint{Foo: "bar"},
|
||||
},
|
||||
Metadata: testContext{Bar: "baz"},
|
||||
Name: "default",
|
||||
})
|
||||
assert.Error(t, err, "default context cannot be created nor updated")
|
||||
}
|
||||
|
||||
func TestErrRemoveDefault(t *testing.T) {
|
||||
meta := testDefaultMetadata()
|
||||
s, cleanup := testStore(t, meta, store.ContextTLSData{})
|
||||
defer cleanup()
|
||||
err := s.RemoveContext("default")
|
||||
assert.Error(t, err, "default context cannot be removed")
|
||||
}
|
||||
|
||||
func TestErrTLSDataError(t *testing.T) {
|
||||
meta := testDefaultMetadata()
|
||||
s, cleanup := testStore(t, meta, store.ContextTLSData{})
|
||||
defer cleanup()
|
||||
_, err := s.GetContextTLSData("default", "noop", "noop")
|
||||
assert.Check(t, store.IsErrTLSDataDoesNotExist(err))
|
||||
}
|
|
@ -48,6 +48,10 @@ func NewStackCommand(dockerCli command.Cli) *cobra.Command {
|
|||
}
|
||||
defaultHelpFunc := cmd.HelpFunc()
|
||||
cmd.SetHelpFunc(func(c *cobra.Command, args []string) {
|
||||
if err := cmd.Root().PersistentPreRunE(c, args); err != nil {
|
||||
fmt.Fprintln(dockerCli.Err(), err)
|
||||
return
|
||||
}
|
||||
if err := cmd.PersistentPreRunE(c, args); err != nil {
|
||||
fmt.Fprintln(dockerCli.Err(), err)
|
||||
return
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIC+TCCAeGgAwIBAgIBATANBgkqhkiG9w0BAQsFADAeMQswCQYDVQQGEwJGUjEP
|
||||
MA0GA1UEChMGRG9ja2VyMB4XDTE5MDMwMzIzMDAwMFoXDTI0MDMwMTIzMDAwMFow
|
||||
HjELMAkGA1UEBhMCRlIxDzANBgNVBAoTBkRvY2tlcjCCASIwDQYJKoZIhvcNAQEB
|
||||
BQADggEPADCCAQoCggEBAMkifL8Ne9B9LQ8+pKD20meVuV34Ol/xUcH/OfxbiBMa
|
||||
HrlIKsGIaO9GraBLq1DJyaZ6sP6ntfwXqwBYQrAN2fQL1AmwMetqpNjby307XqRa
|
||||
GUQekjG710LfAFKsS/yD/R8L944MFmTbYwGyjROExs8ZAA4fkA8SATzRXhM3a8dE
|
||||
YcrXacZQqd5dwFFS/UyJQbMoNx7IgzrXySqpt3rV8qD8MAUebgshd2p9CQO6zzoU
|
||||
ImOJImMc/15LFZymemm2KvzXTM4J9UYdibXZGzpxcnyGNCb4FVV0HF0Ya+NMDwvY
|
||||
nNpW5rea64ppS8McejePRCmLS8DxMxKTLB7eW97LuDECAwEAAaNCMEAwDwYDVR0T
|
||||
AQH/BAUwAwEB/zAdBgNVHQ4EFgQU6zuJSXHxniajbNcc4SHoM+fatvMwDgYDVR0P
|
||||
AQH/BAQDAgE+MA0GCSqGSIb3DQEBCwUAA4IBAQB2l46NnKzoTOCuUjxGmUv3s1np
|
||||
rENRWlq0mHjCzoYSocg/IcwY7fz41XkwTVV8O3h/Jm25YGnj4lqaXlEKYJ63W8eI
|
||||
wGLcirUAORSspcf+jd7OOjluzYCtuvVtOKR8w22pp5oE/AooGaO5y0ysefZBopr4
|
||||
CNUNsEYhDKFg7tfj6Govi6+0PNxvB53we4nU7NhJNMaClhh/pi8zbeaEf67S6eKn
|
||||
Z3DFqO+8FW4wEePLwhCftESCTwx6Q24v/WIYnzYOXC5mb2B9MwkyJXJIJxxPIeSs
|
||||
PycNQ2kw7gk/TKkLMNQbX4fgFB0zfdofidTAkqOIqFHq/8iD2DYEZQFgCD3v
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,20 @@
|
|||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority-data: dGhlLWNh
|
||||
server: https://someserver
|
||||
name: test-cluster
|
||||
contexts:
|
||||
- context:
|
||||
cluster: test-cluster
|
||||
user: test-user
|
||||
namespace: zoinx
|
||||
name: test
|
||||
current-context: test
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: test-user
|
||||
user:
|
||||
client-certificate-data: dGhlLWNlcnQ=
|
||||
client-key-data: dGhlLWtleQ==
|
|
@ -12,7 +12,8 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/docker/docker/errdefs"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// Store provides a context store for easily remembering endpoints configuration
|
||||
|
@ -299,6 +300,11 @@ func (e *contextDoesNotExistError) setContext(name string) {
|
|||
// 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
|
||||
}
|
||||
|
@ -314,6 +320,9 @@ func (e *tlsDataDoesNotExistError) setContext(name string) {
|
|||
// 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
|
||||
func IsErrContextDoesNotExist(err error) bool {
|
||||
_, ok := err.(*contextDoesNotExistError)
|
||||
|
@ -322,7 +331,7 @@ func IsErrContextDoesNotExist(err error) bool {
|
|||
|
||||
// IsErrTLSDataDoesNotExist checks if the given error is a "context does not exist" condition
|
||||
func IsErrTLSDataDoesNotExist(err error) bool {
|
||||
_, ok := err.(*tlsDataDoesNotExistError)
|
||||
_, ok := err.(tlsDataDoesNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
@ -35,9 +36,14 @@ func TestExportImport(t *testing.T) {
|
|||
Name: "source",
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
file1 := make([]byte, 1500)
|
||||
rand.Read(file1)
|
||||
file2 := make([]byte, 3700)
|
||||
rand.Read(file2)
|
||||
err = s.ResetContextEndpointTLSMaterial("source", "ep1", &EndpointTLSData{
|
||||
Files: map[string][]byte{
|
||||
"file1": []byte("test-data"),
|
||||
"file1": file1,
|
||||
"file2": file2,
|
||||
},
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
|
@ -55,13 +61,22 @@ func TestExportImport(t *testing.T) {
|
|||
assert.NilError(t, err)
|
||||
destFileList, err := s.ListContextTLSFiles("dest")
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, srcFileList, destFileList)
|
||||
srcData, err := s.GetContextTLSData("source", "ep1", "file1")
|
||||
assert.Equal(t, 1, len(destFileList))
|
||||
assert.Equal(t, 1, len(srcFileList))
|
||||
assert.Equal(t, 2, len(destFileList["ep1"]))
|
||||
assert.Equal(t, 2, len(srcFileList["ep1"]))
|
||||
srcData1, err := s.GetContextTLSData("source", "ep1", "file1")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, "test-data", string(srcData))
|
||||
destData, err := s.GetContextTLSData("dest", "ep1", "file1")
|
||||
assert.DeepEqual(t, file1, srcData1)
|
||||
srcData2, err := s.GetContextTLSData("source", "ep1", "file2")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, "test-data", string(destData))
|
||||
assert.DeepEqual(t, file2, srcData2)
|
||||
destData1, err := s.GetContextTLSData("dest", "ep1", "file1")
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, file1, destData1)
|
||||
destData2, err := s.GetContextTLSData("dest", "ep1", "file2")
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, file2, destData2)
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
|
|
|
@ -224,3 +224,24 @@ func (c *FakeCli) NewContainerizedEngineClient(sockPath string) (clitypes.Contai
|
|||
func (c *FakeCli) SetContainerizedEngineClient(containerizedEngineClientFunc containerizedEngineFuncType) {
|
||||
c.containerizedEngineClientFunc = containerizedEngineClientFunc
|
||||
}
|
||||
|
||||
// StackOrchestrator return the selected stack orchestrator
|
||||
func (c *FakeCli) StackOrchestrator(flagValue string) (command.Orchestrator, error) {
|
||||
configOrchestrator := ""
|
||||
if c.configfile != nil {
|
||||
configOrchestrator = c.configfile.StackOrchestrator
|
||||
}
|
||||
ctxOrchestrator := ""
|
||||
if c.currentContext != "" && c.contextStore != nil {
|
||||
meta, err := c.contextStore.GetContextMetadata(c.currentContext)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
context, err := command.GetDockerContext(meta)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ctxOrchestrator = string(context.StackOrchestrator)
|
||||
}
|
||||
return command.GetStackOrchestrator(flagValue, ctxOrchestrator, configOrchestrator, c.err)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue