2018-11-09 09:10:41 -05:00
|
|
|
package context
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/docker/cli/cli/command"
|
|
|
|
"github.com/docker/cli/cli/context"
|
|
|
|
"github.com/docker/cli/cli/context/docker"
|
|
|
|
"github.com/docker/cli/cli/context/kubernetes"
|
|
|
|
"github.com/docker/cli/cli/context/store"
|
|
|
|
"github.com/docker/docker/client"
|
|
|
|
"github.com/docker/docker/pkg/homedir"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2019-03-22 10:20:40 -04:00
|
|
|
keyFrom = "from"
|
2018-11-09 09:10:41 -05:00
|
|
|
keyHost = "host"
|
|
|
|
keyCA = "ca"
|
|
|
|
keyCert = "cert"
|
|
|
|
keyKey = "key"
|
|
|
|
keySkipTLSVerify = "skip-tls-verify"
|
|
|
|
keyKubeconfig = "config-file"
|
|
|
|
keyKubecontext = "context-override"
|
|
|
|
keyKubenamespace = "namespace-override"
|
|
|
|
)
|
|
|
|
|
|
|
|
type configKeyDescription struct {
|
|
|
|
name string
|
|
|
|
description string
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
allowedDockerConfigKeys = map[string]struct{}{
|
2019-03-22 10:20:40 -04:00
|
|
|
keyFrom: {},
|
2018-11-09 09:10:41 -05:00
|
|
|
keyHost: {},
|
|
|
|
keyCA: {},
|
|
|
|
keyCert: {},
|
|
|
|
keyKey: {},
|
|
|
|
keySkipTLSVerify: {},
|
|
|
|
}
|
|
|
|
allowedKubernetesConfigKeys = map[string]struct{}{
|
2019-03-22 10:20:40 -04:00
|
|
|
keyFrom: {},
|
2018-11-09 09:10:41 -05:00
|
|
|
keyKubeconfig: {},
|
|
|
|
keyKubecontext: {},
|
|
|
|
keyKubenamespace: {},
|
|
|
|
}
|
|
|
|
dockerConfigKeysDescriptions = []configKeyDescription{
|
|
|
|
{
|
2019-03-22 10:20:40 -04:00
|
|
|
name: keyFrom,
|
|
|
|
description: "Copy named context's Docker endpoint configuration",
|
2018-11-09 09:10:41 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: keyHost,
|
|
|
|
description: "Docker endpoint on which to connect",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: keyCA,
|
|
|
|
description: "Trust certs signed only by this CA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: keyCert,
|
|
|
|
description: "Path to TLS certificate file",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: keyKey,
|
|
|
|
description: "Path to TLS key file",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: keySkipTLSVerify,
|
|
|
|
description: "Skip TLS certificate validation",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
kubernetesConfigKeysDescriptions = []configKeyDescription{
|
|
|
|
{
|
2019-03-22 10:20:40 -04:00
|
|
|
name: keyFrom,
|
|
|
|
description: "Copy named context's Kubernetes endpoint configuration",
|
2018-11-09 09:10:41 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: keyKubeconfig,
|
|
|
|
description: "Path to a Kubernetes config file",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: keyKubecontext,
|
|
|
|
description: "Overrides the context set in the kubernetes config file",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: keyKubenamespace,
|
|
|
|
description: "Overrides the namespace set in the kubernetes config file",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
func parseBool(config map[string]string, name string) (bool, error) {
|
|
|
|
strVal, ok := config[name]
|
|
|
|
if !ok {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
res, err := strconv.ParseBool(strVal)
|
|
|
|
return res, errors.Wrap(err, name)
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateConfig(config map[string]string, allowedKeys map[string]struct{}) error {
|
|
|
|
var errs []string
|
|
|
|
for k := range config {
|
|
|
|
if _, ok := allowedKeys[k]; !ok {
|
|
|
|
errs = append(errs, fmt.Sprintf("%s: unrecognized config key", k))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(errs) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return errors.New(strings.Join(errs, "\n"))
|
|
|
|
}
|
|
|
|
|
|
|
|
func getDockerEndpoint(dockerCli command.Cli, config map[string]string) (docker.Endpoint, error) {
|
|
|
|
if err := validateConfig(config, allowedDockerConfigKeys); err != nil {
|
|
|
|
return docker.Endpoint{}, err
|
|
|
|
}
|
2019-03-22 10:20:40 -04:00
|
|
|
if contextName, ok := config[keyFrom]; ok {
|
2019-04-18 09:12:30 -04:00
|
|
|
metadata, err := dockerCli.ContextStore().GetMetadata(contextName)
|
2019-03-22 10:20:40 -04:00
|
|
|
if err != nil {
|
|
|
|
return docker.Endpoint{}, err
|
|
|
|
}
|
|
|
|
if ep, ok := metadata.Endpoints[docker.DockerEndpoint].(docker.EndpointMeta); ok {
|
|
|
|
return docker.Endpoint{EndpointMeta: ep}, nil
|
|
|
|
}
|
|
|
|
return docker.Endpoint{}, errors.Errorf("unable to get endpoint from context %q", contextName)
|
2018-11-09 09:10:41 -05:00
|
|
|
}
|
|
|
|
tlsData, err := context.TLSDataFromFiles(config[keyCA], config[keyCert], config[keyKey])
|
|
|
|
if err != nil {
|
|
|
|
return docker.Endpoint{}, err
|
|
|
|
}
|
|
|
|
skipTLSVerify, err := parseBool(config, keySkipTLSVerify)
|
|
|
|
if err != nil {
|
|
|
|
return docker.Endpoint{}, err
|
|
|
|
}
|
|
|
|
ep := docker.Endpoint{
|
|
|
|
EndpointMeta: docker.EndpointMeta{
|
|
|
|
Host: config[keyHost],
|
|
|
|
SkipTLSVerify: skipTLSVerify,
|
|
|
|
},
|
|
|
|
TLSData: tlsData,
|
|
|
|
}
|
|
|
|
// try to resolve a docker client, validating the configuration
|
|
|
|
opts, err := ep.ClientOpts()
|
|
|
|
if err != nil {
|
|
|
|
return docker.Endpoint{}, errors.Wrap(err, "invalid docker endpoint options")
|
|
|
|
}
|
|
|
|
if _, err := client.NewClientWithOpts(opts...); err != nil {
|
|
|
|
return docker.Endpoint{}, errors.Wrap(err, "unable to apply docker endpoint options")
|
|
|
|
}
|
|
|
|
return ep, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getDockerEndpointMetadataAndTLS(dockerCli command.Cli, config map[string]string) (docker.EndpointMeta, *store.EndpointTLSData, error) {
|
|
|
|
ep, err := getDockerEndpoint(dockerCli, config)
|
|
|
|
if err != nil {
|
|
|
|
return docker.EndpointMeta{}, nil, err
|
|
|
|
}
|
|
|
|
return ep.EndpointMeta, ep.TLSData.ToStoreTLSData(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getKubernetesEndpoint(dockerCli command.Cli, config map[string]string) (*kubernetes.Endpoint, error) {
|
|
|
|
if err := validateConfig(config, allowedKubernetesConfigKeys); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(config) == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
2019-03-22 10:20:40 -04:00
|
|
|
if contextName, ok := config[keyFrom]; ok {
|
2019-04-18 09:12:30 -04:00
|
|
|
ctxMeta, err := dockerCli.ContextStore().GetMetadata(contextName)
|
2019-03-22 10:20:40 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
endpointMeta := kubernetes.EndpointFromContext(ctxMeta)
|
|
|
|
if endpointMeta != nil {
|
|
|
|
res, err := endpointMeta.WithTLSData(dockerCli.ContextStore(), dockerCli.CurrentContext())
|
2018-11-09 09:10:41 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-03-22 10:20:40 -04:00
|
|
|
return &res, nil
|
2018-11-09 09:10:41 -05:00
|
|
|
}
|
2019-03-22 10:20:40 -04:00
|
|
|
|
2018-11-09 09:10:41 -05:00
|
|
|
// fallback to env-based kubeconfig
|
|
|
|
kubeconfig := os.Getenv("KUBECONFIG")
|
|
|
|
if kubeconfig == "" {
|
|
|
|
kubeconfig = filepath.Join(homedir.Get(), ".kube/config")
|
|
|
|
}
|
|
|
|
ep, err := kubernetes.FromKubeConfig(kubeconfig, "", "")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &ep, nil
|
|
|
|
}
|
|
|
|
if config[keyKubeconfig] != "" {
|
|
|
|
ep, err := kubernetes.FromKubeConfig(config[keyKubeconfig], config[keyKubecontext], config[keyKubenamespace])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &ep, nil
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getKubernetesEndpointMetadataAndTLS(dockerCli command.Cli, config map[string]string) (*kubernetes.EndpointMeta, *store.EndpointTLSData, error) {
|
|
|
|
ep, err := getKubernetesEndpoint(dockerCli, config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
if ep == nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
return &ep.EndpointMeta, ep.TLSData.ToStoreTLSData(), nil
|
|
|
|
}
|