Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/command/stack/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
opts.Namespace = args[0]
if dockerCli.ClientInfo().HasKubernetes() {
kli, err := kubernetes.WrapCli(dockerCli, cmd)
kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags()))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope that in the future when we add kube to more commands we can have this accept an Options struct instead of cmd.Flags().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I'll refactor that in a future PR.

if err != nil {
return err
}
Expand Down
32 changes: 0 additions & 32 deletions cli/command/stack/kubernetes/check.go

This file was deleted.

71 changes: 46 additions & 25 deletions cli/command/stack/kubernetes/cli.go
Original file line number Diff line number Diff line change
@@ -1,59 +1,75 @@
package kubernetes

import (
"fmt"
"os"
"path/filepath"

"github.com/docker/cli/cli/command"
"github.com/docker/cli/kubernetes"
composev1beta1 "github.com/docker/cli/kubernetes/client/clientset_generated/clientset/typed/compose/v1beta1"
"github.com/docker/docker/pkg/homedir"
"github.com/spf13/cobra"
"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
}

// NewOptions returns an Options initialized with command line flags
func NewOptions(flags *flag.FlagSet) Options {
var opts Options
if namespace, err := flags.GetString("namespace"); err == nil {
opts.Namespace = namespace
}
if kubeConfig, err := flags.GetString("kubeconfig"); err == nil {
opts.Config = kubeConfig
}
return opts
}

// WrapCli wraps command.Cli with kubernetes specifics
func WrapCli(dockerCli command.Cli, cmd *cobra.Command) (*KubeCli, error) {
func WrapCli(dockerCli command.Cli, opts Options) (*KubeCli, error) {
var err error
cli := &KubeCli{
Cli: dockerCli,
kubeNamespace: "default",
}
if cmd.Flags().Changed("namespace") {
cli.kubeNamespace, err = cmd.Flags().GetString("namespace")
if err != nil {
return nil, err
}
}
kubeConfig := ""
if cmd.Flags().Changed("kubeconfig") {
kubeConfig, err = cmd.Flags().GetString("kubeconfig")
if err != nil {
return nil, err
}
if opts.Namespace != "" {
cli.kubeNamespace = opts.Namespace
}
kubeConfig := opts.Config
if kubeConfig == "" {
if config := os.Getenv("KUBECONFIG"); config != "" {
kubeConfig = config
} else {
kubeConfig = filepath.Join(homedir.Get(), ".kube/config")
}
}

config, err := clientcmd.BuildConfigFromFlags("", kubeConfig)
config, err := kubernetes.NewKubernetesConfig(kubeConfig)
if err != nil {
return nil, fmt.Errorf("Failed to load kubernetes configuration file '%s'", kubeConfig)
return nil, err
}
cli.kubeConfig = config

clientSet, err := kubeclient.NewForConfig(config)
if err != nil {
return nil, err
}
cli.clientSet = clientSet

return cli, nil
}

Expand All @@ -62,15 +78,20 @@ func (c *KubeCli) composeClient() (*Factory, error) {
}

func (c *KubeCli) stacks() (composev1beta1.StackInterface, error) {
err := APIPresent(c.kubeConfig)
if err != nil {
return nil, err
}
version, err := kubernetes.GetStackAPIVersion(c.clientSet)

clientSet, err := composev1beta1.NewForConfig(c.kubeConfig)
if err != nil {
return nil, err
}

return clientSet.Stacks(c.kubeNamespace), nil
switch version {
case kubernetes.StackAPIV1Beta1:
clientSet, err := composev1beta1.NewForConfig(c.kubeConfig)
if err != nil {
return nil, err
}
return clientSet.Stacks(c.kubeNamespace), nil
default:
return nil, errors.Errorf("no supported Stack API version")
}
}
2 changes: 1 addition & 1 deletion cli/command/stack/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
if dockerCli.ClientInfo().HasKubernetes() {
kli, err := kubernetes.WrapCli(dockerCli, cmd)
kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags()))
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cli/command/stack/ps.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func newPsCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
opts.Namespace = args[0]
if dockerCli.ClientInfo().HasKubernetes() {
kli, err := kubernetes.WrapCli(dockerCli, cmd)
kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags()))
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cli/command/stack/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
opts.Namespaces = args
if dockerCli.ClientInfo().HasKubernetes() {
kli, err := kubernetes.WrapCli(dockerCli, cmd)
kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags()))
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cli/command/stack/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func newServicesCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
opts.Namespace = args[0]
if dockerCli.ClientInfo().HasKubernetes() {
kli, err := kubernetes.WrapCli(dockerCli, cmd)
kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags()))
if err != nil {
return err
}
Expand Down
72 changes: 68 additions & 4 deletions cli/command/system/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ import (

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/kubernetes"
"github.com/docker/cli/templates"
"github.com/docker/docker/api/types"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/net/context"
kubernetesClient "k8s.io/client-go/kubernetes"
)

var versionTemplate = `{{with .Client -}}
Expand Down Expand Up @@ -48,16 +51,23 @@ Server:{{if ne .Platform.Name ""}} {{.Platform.Name}}{{end}}
{{- end}}
{{- end}}
{{- end}}
{{- end}}{{- end}}
{{- if .KubernetesOK}}{{with .Kubernetes}}
Kubernetes:
Version: {{.Kubernetes}}
Stack API: {{.StackAPI}}
{{- end}}{{end}}`

type versionOptions struct {
format string
format string
kubeConfig string
}

// versionInfo contains version information of both the Client, and Server
type versionInfo struct {
Client clientVersion
Server *types.Version
Client clientVersion
Server *types.Version
Kubernetes *kubernetesVersion
}

type clientVersion struct {
Expand All @@ -75,12 +85,21 @@ type clientVersion struct {
Orchestrator string `json:",omitempty"`
}

type kubernetesVersion struct {
Kubernetes string
StackAPI string
}

// ServerOK returns true when the client could connect to the docker server
// and parse the information received. It returns false otherwise.
func (v versionInfo) ServerOK() bool {
return v.Server != nil
}

func (v versionInfo) KubernetesOK() bool {
return v.Kubernetes != nil
}

// NewVersionCommand creates a new cobra.Command for `docker version`
func NewVersionCommand(dockerCli command.Cli) *cobra.Command {
var opts versionOptions
Expand All @@ -95,8 +114,10 @@ func NewVersionCommand(dockerCli command.Cli) *cobra.Command {
}

flags := cmd.Flags()

flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template")
flags.StringVarP(&opts.kubeConfig, "kubeconfig", "k", "", "Kubernetes config file")
flags.SetAnnotation("kubeconfig", "kubernetes", nil)
flags.SetAnnotation("kubeconfig", "experimentalCLI", nil)

return cmd
}
Expand Down Expand Up @@ -139,6 +160,7 @@ func runVersion(dockerCli command.Cli, opts *versionOptions) error {
Experimental: dockerCli.ClientInfo().HasExperimental,
Orchestrator: string(dockerCli.ClientInfo().Orchestrator),
},
Kubernetes: getKubernetesVersion(dockerCli, opts.kubeConfig),
}

sv, err := dockerCli.Client().ServerVersion(context.Background())
Expand Down Expand Up @@ -189,3 +211,45 @@ func getDetailsOrder(v types.ComponentVersion) []string {
sort.Strings(out)
return out
}

func getKubernetesVersion(dockerCli command.Cli, kubeConfig string) *kubernetesVersion {
if !dockerCli.ClientInfo().HasKubernetes() {
return nil
}

version := kubernetesVersion{
Kubernetes: "Unknown",
StackAPI: "Unknown",
}
config, err := kubernetes.NewKubernetesConfig(kubeConfig)
if err != nil {
logrus.Debugf("failed to get Kubernetes configuration: %s", err)
return &version
}
kubeClient, err := kubernetesClient.NewForConfig(config)
if err != nil {
logrus.Debugf("failed to get Kubernetes client: %s", err)
return &version
}
version.StackAPI = getStackVersion(kubeClient)
version.Kubernetes = getKubernetesServerVersion(kubeClient)
return &version
}

func getStackVersion(client *kubernetesClient.Clientset) string {
apiVersion, err := kubernetes.GetStackAPIVersion(client)
if err != nil {
logrus.Debugf("failed to get Stack API version: %s", err)
return "Unknown"
}
return string(apiVersion)
}

func getKubernetesServerVersion(client *kubernetesClient.Clientset) string {
kubeVersion, err := client.DiscoveryClient.ServerVersion()
if err != nil {
logrus.Debugf("failed to get Kubernetes server version: %s", err)
return "Unknown"
}
return kubeVersion.String()
}
50 changes: 50 additions & 0 deletions kubernetes/check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package kubernetes

import (
apiv1beta1 "github.com/docker/cli/kubernetes/compose/v1beta1"
"github.com/pkg/errors"
apimachinerymetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
)

// StackVersion represents the detected Compose Component on Kubernetes side.
type StackVersion string

const (
// StackAPIV1Beta1 is returned if it's the most recent version available.
StackAPIV1Beta1 = StackVersion("v1beta1")
)

// GetStackAPIVersion returns the most recent stack API installed.
func GetStackAPIVersion(clientSet *kubernetes.Clientset) (StackVersion, error) {
groups, err := clientSet.Discovery().ServerGroups()
if err != nil {
return "", err
}

return getAPIVersion(groups)
}

func getAPIVersion(groups *metav1.APIGroupList) (StackVersion, error) {
switch {
case findVersion(apiv1beta1.SchemeGroupVersion, groups.Groups):
return StackAPIV1Beta1, nil
default:
return "", errors.Errorf("failed to find a Stack API version")
}
}

func findVersion(stackAPI schema.GroupVersion, groups []apimachinerymetav1.APIGroup) bool {
for _, group := range groups {
if group.Name == stackAPI.Group {
for _, version := range group.Versions {
if version.Version == stackAPI.Version {
return true
}
}
}
}
return false
}
Loading