github.com/cspotcode/docker-cli@v20.10.0-rc1.0.20201201121459-3faad7acc5b8+incompatible/cli/command/system/version.go (about)

     1  package system
     2  
     3  import (
     4  	"context"
     5  	"runtime"
     6  	"sort"
     7  	"strconv"
     8  	"text/tabwriter"
     9  	"text/template"
    10  	"time"
    11  
    12  	"github.com/docker/cli/cli"
    13  	"github.com/docker/cli/cli/command"
    14  	kubecontext "github.com/docker/cli/cli/context/kubernetes"
    15  	"github.com/docker/cli/cli/version"
    16  	"github.com/docker/cli/kubernetes"
    17  	"github.com/docker/cli/templates"
    18  	kubeapi "github.com/docker/compose-on-kubernetes/api"
    19  	"github.com/docker/docker/api/types"
    20  	"github.com/pkg/errors"
    21  	"github.com/sirupsen/logrus"
    22  	"github.com/spf13/cobra"
    23  	"github.com/tonistiigi/go-rosetta"
    24  	kubernetesClient "k8s.io/client-go/kubernetes"
    25  	"k8s.io/client-go/tools/clientcmd"
    26  )
    27  
    28  var versionTemplate = `{{with .Client -}}
    29  Client:{{if ne .Platform.Name ""}} {{.Platform.Name}}{{end}}
    30   Version:	{{.Version}}
    31   API version:	{{.APIVersion}}{{if ne .APIVersion .DefaultAPIVersion}} (downgraded from {{.DefaultAPIVersion}}){{end}}
    32   Go version:	{{.GoVersion}}
    33   Git commit:	{{.GitCommit}}
    34   Built:	{{.BuildTime}}
    35   OS/Arch:	{{.Os}}/{{.Arch}}
    36   Context:	{{.Context}}
    37   Experimental:	{{.Experimental}}
    38  {{- end}}
    39  
    40  {{- if .ServerOK}}{{with .Server}}
    41  
    42  Server:{{if ne .Platform.Name ""}} {{.Platform.Name}}{{end}}
    43   {{- range $component := .Components}}
    44   {{$component.Name}}:
    45    {{- if eq $component.Name "Engine" }}
    46    Version:	{{.Version}}
    47    API version:	{{index .Details "ApiVersion"}} (minimum version {{index .Details "MinAPIVersion"}})
    48    Go version:	{{index .Details "GoVersion"}}
    49    Git commit:	{{index .Details "GitCommit"}}
    50    Built:	{{index .Details "BuildTime"}}
    51    OS/Arch:	{{index .Details "Os"}}/{{index .Details "Arch"}}
    52    Experimental:	{{index .Details "Experimental"}}
    53    {{- else }}
    54    Version:	{{$component.Version}}
    55    {{- $detailsOrder := getDetailsOrder $component}}
    56    {{- range $key := $detailsOrder}}
    57    {{$key}}:	{{index $component.Details $key}}
    58     {{- end}}
    59    {{- end}}
    60   {{- end}}
    61   {{- end}}{{- end}}`
    62  
    63  type versionOptions struct {
    64  	format     string
    65  	kubeConfig string
    66  }
    67  
    68  // versionInfo contains version information of both the Client, and Server
    69  type versionInfo struct {
    70  	Client clientVersion
    71  	Server *types.Version
    72  }
    73  
    74  type clientVersion struct {
    75  	Platform struct{ Name string } `json:",omitempty"`
    76  
    77  	Version           string
    78  	APIVersion        string `json:"ApiVersion"`
    79  	DefaultAPIVersion string `json:"DefaultAPIVersion,omitempty"`
    80  	GitCommit         string
    81  	GoVersion         string
    82  	Os                string
    83  	Arch              string
    84  	BuildTime         string `json:",omitempty"`
    85  	Context           string
    86  	Experimental      bool `json:",omitempty"` // Deprecated: experimental CLI features always enabled. This field is kept for backward-compatibility, and is always "true"
    87  }
    88  
    89  type kubernetesVersion struct {
    90  	Kubernetes string
    91  	StackAPI   string
    92  }
    93  
    94  // ServerOK returns true when the client could connect to the docker server
    95  // and parse the information received. It returns false otherwise.
    96  func (v versionInfo) ServerOK() bool {
    97  	return v.Server != nil
    98  }
    99  
   100  // NewVersionCommand creates a new cobra.Command for `docker version`
   101  func NewVersionCommand(dockerCli command.Cli) *cobra.Command {
   102  	var opts versionOptions
   103  
   104  	cmd := &cobra.Command{
   105  		Use:   "version [OPTIONS]",
   106  		Short: "Show the Docker version information",
   107  		Args:  cli.NoArgs,
   108  		RunE: func(cmd *cobra.Command, args []string) error {
   109  			return runVersion(dockerCli, &opts)
   110  		},
   111  	}
   112  
   113  	flags := cmd.Flags()
   114  	flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template")
   115  	flags.StringVar(&opts.kubeConfig, "kubeconfig", "", "Kubernetes config file")
   116  	flags.SetAnnotation("kubeconfig", "kubernetes", nil)
   117  
   118  	return cmd
   119  }
   120  
   121  func reformatDate(buildTime string) string {
   122  	t, errTime := time.Parse(time.RFC3339Nano, buildTime)
   123  	if errTime == nil {
   124  		return t.Format(time.ANSIC)
   125  	}
   126  	return buildTime
   127  }
   128  
   129  func arch() string {
   130  	arch := runtime.GOARCH
   131  	if rosetta.Enabled() {
   132  		arch += " (rosetta)"
   133  	}
   134  	return arch
   135  }
   136  
   137  func runVersion(dockerCli command.Cli, opts *versionOptions) error {
   138  	var err error
   139  	tmpl, err := newVersionTemplate(opts.format)
   140  	if err != nil {
   141  		return cli.StatusError{StatusCode: 64, Status: err.Error()}
   142  	}
   143  
   144  	orchestrator, err := dockerCli.StackOrchestrator("")
   145  	if err != nil {
   146  		return cli.StatusError{StatusCode: 64, Status: err.Error()}
   147  	}
   148  
   149  	vd := versionInfo{
   150  		Client: clientVersion{
   151  			Platform:          struct{ Name string }{version.PlatformName},
   152  			Version:           version.Version,
   153  			APIVersion:        dockerCli.Client().ClientVersion(),
   154  			DefaultAPIVersion: dockerCli.DefaultVersion(),
   155  			GoVersion:         runtime.Version(),
   156  			GitCommit:         version.GitCommit,
   157  			BuildTime:         reformatDate(version.BuildTime),
   158  			Os:                runtime.GOOS,
   159  			Arch:              arch(),
   160  			Experimental:      true,
   161  			Context:           dockerCli.CurrentContext(),
   162  		},
   163  	}
   164  
   165  	sv, err := dockerCli.Client().ServerVersion(context.Background())
   166  	if err == nil {
   167  		vd.Server = &sv
   168  		var kubeVersion *kubernetesVersion
   169  		if orchestrator.HasKubernetes() {
   170  			kubeVersion = getKubernetesVersion(dockerCli, opts.kubeConfig)
   171  		}
   172  		foundEngine := false
   173  		foundKubernetes := false
   174  		for _, component := range sv.Components {
   175  			switch component.Name {
   176  			case "Engine":
   177  				foundEngine = true
   178  				buildTime, ok := component.Details["BuildTime"]
   179  				if ok {
   180  					component.Details["BuildTime"] = reformatDate(buildTime)
   181  				}
   182  			case "Kubernetes":
   183  				foundKubernetes = true
   184  				if _, ok := component.Details["StackAPI"]; !ok && kubeVersion != nil {
   185  					component.Details["StackAPI"] = kubeVersion.StackAPI
   186  				}
   187  			}
   188  		}
   189  
   190  		if !foundEngine {
   191  			vd.Server.Components = append(vd.Server.Components, types.ComponentVersion{
   192  				Name:    "Engine",
   193  				Version: sv.Version,
   194  				Details: map[string]string{
   195  					"ApiVersion":    sv.APIVersion,
   196  					"MinAPIVersion": sv.MinAPIVersion,
   197  					"GitCommit":     sv.GitCommit,
   198  					"GoVersion":     sv.GoVersion,
   199  					"Os":            sv.Os,
   200  					"Arch":          sv.Arch,
   201  					"BuildTime":     reformatDate(vd.Server.BuildTime),
   202  					"Experimental":  strconv.FormatBool(sv.Experimental),
   203  				},
   204  			})
   205  		}
   206  		if !foundKubernetes && kubeVersion != nil {
   207  			vd.Server.Components = append(vd.Server.Components, types.ComponentVersion{
   208  				Name:    "Kubernetes",
   209  				Version: kubeVersion.Kubernetes,
   210  				Details: map[string]string{
   211  					"StackAPI": kubeVersion.StackAPI,
   212  				},
   213  			})
   214  		}
   215  	}
   216  	if err2 := prettyPrintVersion(dockerCli, vd, tmpl); err2 != nil && err == nil {
   217  		err = err2
   218  	}
   219  	return err
   220  }
   221  
   222  func prettyPrintVersion(dockerCli command.Cli, vd versionInfo, tmpl *template.Template) error {
   223  	t := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 1, ' ', 0)
   224  	err := tmpl.Execute(t, vd)
   225  	t.Write([]byte("\n"))
   226  	t.Flush()
   227  	return err
   228  }
   229  
   230  func newVersionTemplate(templateFormat string) (*template.Template, error) {
   231  	if templateFormat == "" {
   232  		templateFormat = versionTemplate
   233  	}
   234  	tmpl := templates.New("version").Funcs(template.FuncMap{"getDetailsOrder": getDetailsOrder})
   235  	tmpl, err := tmpl.Parse(templateFormat)
   236  
   237  	return tmpl, errors.Wrap(err, "Template parsing error")
   238  }
   239  
   240  func getDetailsOrder(v types.ComponentVersion) []string {
   241  	out := make([]string, 0, len(v.Details))
   242  	for k := range v.Details {
   243  		out = append(out, k)
   244  	}
   245  	sort.Strings(out)
   246  	return out
   247  }
   248  
   249  func getKubernetesVersion(dockerCli command.Cli, kubeConfig string) *kubernetesVersion {
   250  	version := kubernetesVersion{
   251  		Kubernetes: "Unknown",
   252  		StackAPI:   "Unknown",
   253  	}
   254  	var (
   255  		clientConfig clientcmd.ClientConfig
   256  		err          error
   257  	)
   258  	if dockerCli.CurrentContext() == "" {
   259  		clientConfig = kubeapi.NewKubernetesConfig(kubeConfig)
   260  	} else {
   261  		clientConfig, err = kubecontext.ConfigFromContext(dockerCli.CurrentContext(), dockerCli.ContextStore())
   262  	}
   263  	if err != nil {
   264  		logrus.Debugf("failed to get Kubernetes configuration: %s", err)
   265  		return &version
   266  	}
   267  	config, err := clientConfig.ClientConfig()
   268  	if err != nil {
   269  		logrus.Debugf("failed to get Kubernetes client config: %s", err)
   270  		return &version
   271  	}
   272  	kubeClient, err := kubernetesClient.NewForConfig(config)
   273  	if err != nil {
   274  		logrus.Debugf("failed to get Kubernetes client: %s", err)
   275  		return &version
   276  	}
   277  	version.StackAPI = getStackVersion(kubeClient)
   278  	version.Kubernetes = getKubernetesServerVersion(kubeClient)
   279  	return &version
   280  }
   281  
   282  func getStackVersion(client *kubernetesClient.Clientset) string {
   283  	apiVersion, err := kubernetes.GetStackAPIVersion(client)
   284  	if err != nil {
   285  		logrus.Debugf("failed to get Stack API version: %s", err)
   286  		return "Unknown"
   287  	}
   288  	return string(apiVersion)
   289  }
   290  
   291  func getKubernetesServerVersion(client *kubernetesClient.Clientset) string {
   292  	kubeVersion, err := client.DiscoveryClient.ServerVersion()
   293  	if err != nil {
   294  		logrus.Debugf("failed to get Kubernetes server version: %s", err)
   295  		return "Unknown"
   296  	}
   297  	return kubeVersion.String()
   298  }