package kubernetes import ( "fmt" "net" "net/url" "os" "github.com/docker/cli/cli/command" kubecontext "github.com/docker/cli/cli/context/kubernetes" kubernetes "github.com/docker/compose-on-kubernetes/api" cliv1beta1 "github.com/docker/compose-on-kubernetes/api/client/clientset/typed/compose/v1beta1" "github.com/pkg/errors" flag "github.com/spf13/pflag" kubeclient "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" ) // KubeCli holds kubernetes specifics (client, namespace) with the command.Cli type KubeCli struct { command.Cli kubeConfig *restclient.Config kubeNamespace string clientSet *kubeclient.Clientset } // Options contains resolved parameters to initialize kubernetes clients type Options struct { Namespace string Config string Orchestrator command.Orchestrator } // NewOptions returns an Options initialized with command line flags func NewOptions(flags *flag.FlagSet, orchestrator command.Orchestrator) Options { opts := Options{ Orchestrator: orchestrator, } if namespace, err := flags.GetString("namespace"); err == nil { opts.Namespace = namespace } if kubeConfig, err := flags.GetString("kubeconfig"); err == nil { opts.Config = kubeConfig } return opts } // 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) flags.SetAnnotation("namespace", "deprecated", nil) } // WrapCli wraps command.Cli with kubernetes specifics func WrapCli(dockerCli command.Cli, opts Options) (*KubeCli, error) { cli := &KubeCli{ Cli: dockerCli, } 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 } cli.kubeNamespace = opts.Namespace if opts.Namespace == "" { configNamespace, _, err := clientConfig.Namespace() switch { case os.IsNotExist(err), os.IsPermission(err): return nil, errors.Wrap(err, "unable to load configuration file") case err != nil: return nil, err } cli.kubeNamespace = configNamespace } config, err := clientConfig.ClientConfig() if err != nil { return nil, err } cli.kubeConfig = config clientSet, err := kubeclient.NewForConfig(config) if err != nil { return nil, err } cli.clientSet = clientSet if opts.Orchestrator.HasAll() { if err := cli.checkHostsMatch(); err != nil { return nil, err } } return cli, nil } func (c *KubeCli) composeClient() (*Factory, error) { return NewFactory(c.kubeNamespace, c.kubeConfig, c.clientSet) } 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 } func (c *KubeCli) stacksv1beta1() (cliv1beta1.StackInterface, error) { raw, err := newStackV1Beta1(c.kubeConfig, c.kubeNamespace) if err != nil { return nil, err } return raw.stacks, nil }