Skip to content

Commit

Permalink
Merge pull request #898 from silvin-lubecki/add-stack-to-version-command
Browse files Browse the repository at this point in the history
Print Stack API and Kubernetes versions in version command
  • Loading branch information
Vincent Demeester authored Feb 27, 2018
2 parents 750b038 + 854aad8 commit 2851c00
Show file tree
Hide file tree
Showing 11 changed files with 242 additions and 66 deletions.
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()))
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

0 comments on commit 2851c00

Please sign in to comment.