mirror of https://github.com/docker/cli.git
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>
This commit is contained in:
parent
f820766f6a
commit
520be05c49
|
@ -14,7 +14,6 @@ import (
|
||||||
"github.com/docker/cli/cli/config/configfile"
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
dcontext "github.com/docker/cli/cli/context"
|
dcontext "github.com/docker/cli/cli/context"
|
||||||
"github.com/docker/cli/cli/context/docker"
|
"github.com/docker/cli/cli/context/docker"
|
||||||
kubecontext "github.com/docker/cli/cli/context/kubernetes"
|
|
||||||
"github.com/docker/cli/cli/context/store"
|
"github.com/docker/cli/cli/context/store"
|
||||||
"github.com/docker/cli/cli/debug"
|
"github.com/docker/cli/cli/debug"
|
||||||
cliflags "github.com/docker/cli/cli/flags"
|
cliflags "github.com/docker/cli/cli/flags"
|
||||||
|
@ -529,7 +528,6 @@ func resolveContextName(opts *cliflags.CommonOptions, config *configfile.ConfigF
|
||||||
|
|
||||||
var defaultStoreEndpoints = []store.NamedTypeGetter{
|
var defaultStoreEndpoints = []store.NamedTypeGetter{
|
||||||
store.EndpointTypeGetter(docker.DockerEndpoint, func() interface{} { return &docker.EndpointMeta{} }),
|
store.EndpointTypeGetter(docker.DockerEndpoint, func() interface{} { return &docker.EndpointMeta{} }),
|
||||||
store.EndpointTypeGetter(kubecontext.KubernetesEndpoint, func() interface{} { return &kubecontext.EndpointMeta{} }),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterDefaultStoreEndpoints registers a new named endpoint
|
// RegisterDefaultStoreEndpoints registers a new named endpoint
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/context/docker"
|
"github.com/docker/cli/cli/context/docker"
|
||||||
"github.com/docker/cli/cli/context/kubernetes"
|
|
||||||
"github.com/docker/cli/cli/context/store"
|
"github.com/docker/cli/cli/context/store"
|
||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
clitypes "github.com/docker/cli/types"
|
clitypes "github.com/docker/cli/types"
|
||||||
|
@ -97,7 +96,7 @@ func WithContainerizedClient(containerizedFn func(string) (clitypes.Containerize
|
||||||
func WithContextEndpointType(endpointName string, endpointType store.TypeGetter) DockerCliOption {
|
func WithContextEndpointType(endpointName string, endpointType store.TypeGetter) DockerCliOption {
|
||||||
return func(cli *DockerCli) error {
|
return func(cli *DockerCli) error {
|
||||||
switch endpointName {
|
switch endpointName {
|
||||||
case docker.DockerEndpoint, kubernetes.KubernetesEndpoint:
|
case docker.DockerEndpoint:
|
||||||
return fmt.Errorf("cannot change %q endpoint type", endpointName)
|
return fmt.Errorf("cannot change %q endpoint type", endpointName)
|
||||||
}
|
}
|
||||||
cli.contextStoreConfig.SetEndpoint(endpointName, endpointType)
|
cli.contextStoreConfig.SetEndpoint(endpointName, endpointType)
|
||||||
|
|
|
@ -3,15 +3,11 @@ package command
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli/config/configfile"
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
"github.com/docker/cli/cli/context/docker"
|
"github.com/docker/cli/cli/context/docker"
|
||||||
"github.com/docker/cli/cli/context/kubernetes"
|
|
||||||
"github.com/docker/cli/cli/context/store"
|
"github.com/docker/cli/cli/context/store"
|
||||||
cliflags "github.com/docker/cli/cli/flags"
|
cliflags "github.com/docker/cli/cli/flags"
|
||||||
"github.com/docker/docker/pkg/homedir"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -70,30 +66,22 @@ func ResolveDefaultContext(opts *cliflags.CommonOptions, config *configfile.Conf
|
||||||
contextTLSData.Endpoints[docker.DockerEndpoint] = *dockerEP.TLSData.ToStoreTLSData()
|
contextTLSData.Endpoints[docker.DockerEndpoint] = *dockerEP.TLSData.ToStoreTLSData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default context uses env-based kubeconfig for Kubernetes endpoint configuration
|
// We open code the string "kubernetes" below because we
|
||||||
kubeconfig := os.Getenv("KUBECONFIG")
|
// cannot import KubernetesEndpoint from the corresponding
|
||||||
if kubeconfig == "" {
|
// package due to import loops.
|
||||||
kubeconfig = filepath.Join(homedir.Get(), ".kube/config")
|
wantKubernetesEP := stackOrchestrator == OrchestratorKubernetes || stackOrchestrator == OrchestratorAll
|
||||||
}
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := storeconfig.ForeachEndpointType(func(n string, get store.TypeGetter) error {
|
if err := storeconfig.ForeachEndpointType(func(n string, get store.TypeGetter) error {
|
||||||
if n == docker.DockerEndpoint || n == kubernetes.KubernetesEndpoint { // handled above
|
if n == docker.DockerEndpoint { // handled above
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
ep := get()
|
ep := get()
|
||||||
if i, ok := ep.(EndpointDefaultResolver); ok {
|
if i, ok := ep.(EndpointDefaultResolver); ok {
|
||||||
meta, tls := i.ResolveDefault()
|
meta, tls := i.ResolveDefault()
|
||||||
if meta == nil {
|
if meta == nil {
|
||||||
|
if wantKubernetesEP && n == "kubernetes" {
|
||||||
|
return errors.Errorf("default orchestrator is %s but unable to resolve kubernetes endpoint", stackOrchestrator)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
contextMetadata.Endpoints[n] = meta
|
contextMetadata.Endpoints[n] = meta
|
||||||
|
@ -107,6 +95,10 @@ func ResolveDefaultContext(opts *cliflags.CommonOptions, config *configfile.Conf
|
||||||
return nil, err
|
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
|
return &DefaultContext{Meta: contextMetadata, TLS: contextTLSData}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/cli/config/configfile"
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
"github.com/docker/cli/cli/context/docker"
|
"github.com/docker/cli/cli/context/docker"
|
||||||
"github.com/docker/cli/cli/context/kubernetes"
|
|
||||||
"github.com/docker/cli/cli/context/store"
|
"github.com/docker/cli/cli/context/store"
|
||||||
cliflags "github.com/docker/cli/cli/flags"
|
cliflags "github.com/docker/cli/cli/flags"
|
||||||
"github.com/docker/go-connections/tlsconfig"
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
|
@ -63,9 +62,8 @@ func TestDefaultContextInitializer(t *testing.T) {
|
||||||
cli, err := NewDockerCli()
|
cli, err := NewDockerCli()
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
defer env.Patch(t, "DOCKER_HOST", "ssh://someswarmserver")()
|
defer env.Patch(t, "DOCKER_HOST", "ssh://someswarmserver")()
|
||||||
defer env.Patch(t, "KUBECONFIG", "./testdata/test-kubeconfig")()
|
|
||||||
cli.configFile = &configfile.ConfigFile{
|
cli.configFile = &configfile.ConfigFile{
|
||||||
StackOrchestrator: "all",
|
StackOrchestrator: "swarm",
|
||||||
}
|
}
|
||||||
ctx, err := ResolveDefaultContext(&cliflags.CommonOptions{
|
ctx, err := ResolveDefaultContext(&cliflags.CommonOptions{
|
||||||
TLS: true,
|
TLS: true,
|
||||||
|
@ -75,10 +73,9 @@ func TestDefaultContextInitializer(t *testing.T) {
|
||||||
}, cli.ConfigFile(), DefaultContextStoreConfig(), cli.Err())
|
}, cli.ConfigFile(), DefaultContextStoreConfig(), cli.Err())
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Equal(t, "default", ctx.Meta.Name)
|
assert.Equal(t, "default", ctx.Meta.Name)
|
||||||
assert.Equal(t, OrchestratorAll, ctx.Meta.Metadata.(DockerContext).StackOrchestrator)
|
assert.Equal(t, OrchestratorSwarm, ctx.Meta.Metadata.(DockerContext).StackOrchestrator)
|
||||||
assert.DeepEqual(t, "ssh://someswarmserver", ctx.Meta.Endpoints[docker.DockerEndpoint].(docker.EndpointMeta).Host)
|
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")
|
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) {
|
func TestExportDefaultImport(t *testing.T) {
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
package kubernetes
|
package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/context"
|
"github.com/docker/cli/cli/context"
|
||||||
"github.com/docker/cli/cli/context/store"
|
"github.com/docker/cli/cli/context/store"
|
||||||
api "github.com/docker/compose-on-kubernetes/api"
|
api "github.com/docker/compose-on-kubernetes/api"
|
||||||
|
"github.com/docker/docker/pkg/homedir"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||||
)
|
)
|
||||||
|
@ -17,6 +22,8 @@ type EndpointMeta struct {
|
||||||
Exec *clientcmdapi.ExecConfig `json:",omitempty"`
|
Exec *clientcmdapi.ExecConfig `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ command.EndpointDefaultResolver = &EndpointMeta{}
|
||||||
|
|
||||||
// Endpoint is a typed wrapper around a context-store generic endpoint describing
|
// Endpoint is a typed wrapper around a context-store generic endpoint describing
|
||||||
// a Kubernetes endpoint, with TLS data
|
// a Kubernetes endpoint, with TLS data
|
||||||
type Endpoint struct {
|
type Endpoint struct {
|
||||||
|
@ -24,6 +31,12 @@ type Endpoint struct {
|
||||||
TLSData *context.TLSData
|
TLSData *context.TLSData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
command.RegisterDefaultStoreEndpoints(
|
||||||
|
store.EndpointTypeGetter(KubernetesEndpoint, func() interface{} { return &EndpointMeta{} }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// WithTLSData loads TLS materials for the endpoint
|
// WithTLSData loads TLS materials for the endpoint
|
||||||
func (c *EndpointMeta) WithTLSData(s store.Reader, 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)
|
||||||
|
@ -61,6 +74,25 @@ func (c *Endpoint) KubernetesConfig() clientcmd.ClientConfig {
|
||||||
return clientcmd.NewDefaultClientConfig(*cfg, &clientcmd.ConfigOverrides{})
|
return clientcmd.NewDefaultClientConfig(*cfg, &clientcmd.ConfigOverrides{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResolveDefault returns endpoint metadata for the default Kubernetes
|
||||||
|
// endpoint, which is derived from the env-based kubeconfig.
|
||||||
|
func (c *EndpointMeta) ResolveDefault() (interface{}, *store.EndpointTLSData) {
|
||||||
|
kubeconfig := os.Getenv("KUBECONFIG")
|
||||||
|
if kubeconfig == "" {
|
||||||
|
kubeconfig = filepath.Join(homedir.Get(), ".kube/config")
|
||||||
|
}
|
||||||
|
kubeEP, err := FromKubeConfig(kubeconfig, "", "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var tls *store.EndpointTLSData
|
||||||
|
if kubeEP.TLSData != nil {
|
||||||
|
tls = kubeEP.TLSData.ToStoreTLSData()
|
||||||
|
}
|
||||||
|
return kubeEP.EndpointMeta, tls
|
||||||
|
}
|
||||||
|
|
||||||
// EndpointFromContext extracts kubernetes endpoint info from current context
|
// EndpointFromContext extracts kubernetes endpoint info from current context
|
||||||
func EndpointFromContext(metadata store.Metadata) *EndpointMeta {
|
func EndpointFromContext(metadata store.Metadata) *EndpointMeta {
|
||||||
ep, ok := metadata.Endpoints[KubernetesEndpoint]
|
ep, ok := metadata.Endpoints[KubernetesEndpoint]
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
|
cliflags "github.com/docker/cli/cli/flags"
|
||||||
|
"gotest.tools/assert"
|
||||||
|
"gotest.tools/env"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDefaultContextInitializer(t *testing.T) {
|
||||||
|
cli, err := command.NewDockerCli()
|
||||||
|
assert.NilError(t, err)
|
||||||
|
defer env.Patch(t, "KUBECONFIG", "./testdata/test-kubeconfig")()
|
||||||
|
configFile := &configfile.ConfigFile{
|
||||||
|
StackOrchestrator: "all",
|
||||||
|
}
|
||||||
|
ctx, err := command.ResolveDefaultContext(&cliflags.CommonOptions{}, configFile, command.DefaultContextStoreConfig(), cli.Err())
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "default", ctx.Meta.Name)
|
||||||
|
assert.Equal(t, command.OrchestratorAll, ctx.Meta.Metadata.(command.DockerContext).StackOrchestrator)
|
||||||
|
assert.DeepEqual(t, "zoinx", ctx.Meta.Endpoints[KubernetesEndpoint].(EndpointMeta).DefaultNamespace)
|
||||||
|
}
|
Loading…
Reference in New Issue