2017-12-04 06:30:39 -05:00
|
|
|
package kubernetes
|
|
|
|
|
|
|
|
import (
|
2018-05-02 12:52:31 -04:00
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"net/url"
|
2018-07-16 14:41:22 -04:00
|
|
|
"os"
|
2018-05-02 12:52:31 -04:00
|
|
|
|
2017-12-04 06:30:39 -05:00
|
|
|
"github.com/docker/cli/cli/command"
|
2018-12-17 05:27:07 -05:00
|
|
|
kubecontext "github.com/docker/cli/cli/context/kubernetes"
|
2018-12-20 02:45:35 -05:00
|
|
|
kubernetes "github.com/docker/compose-on-kubernetes/api"
|
|
|
|
cliv1beta1 "github.com/docker/compose-on-kubernetes/api/client/clientset/typed/compose/v1beta1"
|
2018-07-16 14:41:22 -04:00
|
|
|
"github.com/pkg/errors"
|
2018-02-22 08:55:42 -05:00
|
|
|
flag "github.com/spf13/pflag"
|
|
|
|
kubeclient "k8s.io/client-go/kubernetes"
|
2017-12-04 06:30:39 -05:00
|
|
|
restclient "k8s.io/client-go/rest"
|
2018-12-17 05:27:07 -05:00
|
|
|
"k8s.io/client-go/tools/clientcmd"
|
2017-12-04 06:30:39 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
// KubeCli holds kubernetes specifics (client, namespace) with the command.Cli
|
|
|
|
type KubeCli struct {
|
|
|
|
command.Cli
|
|
|
|
kubeConfig *restclient.Config
|
|
|
|
kubeNamespace string
|
2018-02-22 08:55:42 -05:00
|
|
|
clientSet *kubeclient.Clientset
|
|
|
|
}
|
|
|
|
|
|
|
|
// Options contains resolved parameters to initialize kubernetes clients
|
|
|
|
type Options struct {
|
2018-06-20 08:48:50 -04:00
|
|
|
Namespace string
|
|
|
|
Config string
|
|
|
|
Orchestrator command.Orchestrator
|
2018-02-22 08:55:42 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewOptions returns an Options initialized with command line flags
|
2018-06-20 08:48:50 -04:00
|
|
|
func NewOptions(flags *flag.FlagSet, orchestrator command.Orchestrator) Options {
|
|
|
|
opts := Options{
|
|
|
|
Orchestrator: orchestrator,
|
|
|
|
}
|
2018-02-22 08:55:42 -05:00
|
|
|
if namespace, err := flags.GetString("namespace"); err == nil {
|
|
|
|
opts.Namespace = namespace
|
|
|
|
}
|
|
|
|
if kubeConfig, err := flags.GetString("kubeconfig"); err == nil {
|
|
|
|
opts.Config = kubeConfig
|
|
|
|
}
|
|
|
|
return opts
|
2017-12-04 06:30:39 -05:00
|
|
|
}
|
|
|
|
|
2018-04-26 05:13:14 -04:00
|
|
|
// AddNamespaceFlag adds the namespace flag to the given flag set
|
|
|
|
func AddNamespaceFlag(flags *flag.FlagSet) {
|
|
|
|
flags.String("namespace", "", "Kubernetes namespace to use")
|
|
|
|
flags.SetAnnotation("namespace", "kubernetes", nil)
|
2021-06-29 08:48:14 -04:00
|
|
|
flags.SetAnnotation("namespace", "deprecated", nil)
|
2018-04-26 05:13:14 -04:00
|
|
|
}
|
|
|
|
|
2017-12-05 03:35:52 -05:00
|
|
|
// WrapCli wraps command.Cli with kubernetes specifics
|
2018-02-22 08:55:42 -05:00
|
|
|
func WrapCli(dockerCli command.Cli, opts Options) (*KubeCli, error) {
|
2017-12-04 06:30:39 -05:00
|
|
|
cli := &KubeCli{
|
2018-03-30 12:04:38 -04:00
|
|
|
Cli: dockerCli,
|
2017-12-04 06:30:39 -05:00
|
|
|
}
|
2018-12-17 05:27:07 -05:00
|
|
|
var (
|
|
|
|
clientConfig clientcmd.ClientConfig
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
if dockerCli.CurrentContext() == "" {
|
|
|
|
clientConfig = kubernetes.NewKubernetesConfig(opts.Config)
|
|
|
|
} else {
|
|
|
|
clientConfig, err = kubecontext.ConfigFromContext(dockerCli.CurrentContext(), dockerCli.ContextStore())
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-03-30 12:04:38 -04:00
|
|
|
|
|
|
|
cli.kubeNamespace = opts.Namespace
|
2018-05-09 01:57:58 -04:00
|
|
|
if opts.Namespace == "" {
|
2018-03-30 12:04:38 -04:00
|
|
|
configNamespace, _, err := clientConfig.Namespace()
|
2018-07-16 14:41:22 -04:00
|
|
|
switch {
|
|
|
|
case os.IsNotExist(err), os.IsPermission(err):
|
|
|
|
return nil, errors.Wrap(err, "unable to load configuration file")
|
|
|
|
case err != nil:
|
2018-03-30 12:04:38 -04:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
cli.kubeNamespace = configNamespace
|
|
|
|
}
|
|
|
|
|
|
|
|
config, err := clientConfig.ClientConfig()
|
2017-12-04 06:30:39 -05:00
|
|
|
if err != nil {
|
2018-02-22 08:55:42 -05:00
|
|
|
return nil, err
|
2017-12-04 06:30:39 -05:00
|
|
|
}
|
|
|
|
cli.kubeConfig = config
|
|
|
|
|
2018-02-22 08:55:42 -05:00
|
|
|
clientSet, err := kubeclient.NewForConfig(config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
cli.clientSet = clientSet
|
|
|
|
|
2018-06-20 08:48:50 -04:00
|
|
|
if opts.Orchestrator.HasAll() {
|
2018-05-02 12:52:31 -04:00
|
|
|
if err := cli.checkHostsMatch(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2017-12-04 06:30:39 -05:00
|
|
|
return cli, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *KubeCli) composeClient() (*Factory, error) {
|
2020-10-02 08:19:34 -04:00
|
|
|
return NewFactory(c.kubeNamespace, c.kubeConfig, c.clientSet)
|
2017-12-04 06:30:39 -05:00
|
|
|
}
|
2018-05-02 12:52:31 -04:00
|
|
|
|
|
|
|
func (c *KubeCli) checkHostsMatch() error {
|
|
|
|
daemonEndpoint, err := url.Parse(c.Client().DaemonHost())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
kubeEndpoint, err := url.Parse(c.kubeConfig.Host)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if daemonEndpoint.Hostname() == kubeEndpoint.Hostname() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// The daemon can be local in Docker for Desktop, e.g. "npipe", "unix", ...
|
|
|
|
if daemonEndpoint.Scheme != "tcp" {
|
|
|
|
ips, err := net.LookupIP(kubeEndpoint.Hostname())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, ip := range ips {
|
|
|
|
if ip.IsLoopback() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fmt.Fprintf(c.Err(), "WARNING: Swarm and Kubernetes hosts do not match (docker host=%s, kubernetes host=%s).\n"+
|
|
|
|
" Update $DOCKER_HOST (or pass -H), or use 'kubectl config use-context' to match.\n", daemonEndpoint.Hostname(), kubeEndpoint.Hostname())
|
|
|
|
return nil
|
|
|
|
}
|
2018-05-14 09:44:55 -04:00
|
|
|
|
|
|
|
func (c *KubeCli) stacksv1beta1() (cliv1beta1.StackInterface, error) {
|
|
|
|
raw, err := newStackV1Beta1(c.kubeConfig, c.kubeNamespace)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return raw.stacks, nil
|
|
|
|
}
|