2016-09-08 13:11:39 -04:00
|
|
|
package system
|
|
|
|
|
|
|
|
import (
|
2018-05-03 21:02:44 -04:00
|
|
|
"context"
|
2017-12-01 08:47:20 -05:00
|
|
|
"fmt"
|
2016-09-08 13:11:39 -04:00
|
|
|
"runtime"
|
2017-12-06 15:35:43 -05:00
|
|
|
"sort"
|
2018-03-26 10:09:52 -04:00
|
|
|
"text/tabwriter"
|
2017-12-06 15:35:43 -05:00
|
|
|
"text/template"
|
2016-09-08 13:11:39 -04:00
|
|
|
"time"
|
|
|
|
|
2017-04-17 18:07:56 -04:00
|
|
|
"github.com/docker/cli/cli"
|
|
|
|
"github.com/docker/cli/cli/command"
|
2018-02-22 08:55:42 -05:00
|
|
|
"github.com/docker/cli/kubernetes"
|
2017-08-08 11:26:24 -04:00
|
|
|
"github.com/docker/cli/templates"
|
2016-09-08 13:11:39 -04:00
|
|
|
"github.com/docker/docker/api/types"
|
2018-05-31 04:10:56 -04:00
|
|
|
"github.com/pkg/errors"
|
2018-02-22 08:55:42 -05:00
|
|
|
"github.com/sirupsen/logrus"
|
2016-09-08 13:11:39 -04:00
|
|
|
"github.com/spf13/cobra"
|
2018-02-22 08:55:42 -05:00
|
|
|
kubernetesClient "k8s.io/client-go/kubernetes"
|
2016-09-08 13:11:39 -04:00
|
|
|
)
|
|
|
|
|
2017-12-01 08:47:20 -05:00
|
|
|
var versionTemplate = `{{with .Client -}}
|
|
|
|
Client:{{if ne .Platform.Name ""}} {{.Platform.Name}}{{end}}
|
|
|
|
Version: {{.Version}}
|
|
|
|
API version: {{.APIVersion}}{{if ne .APIVersion .DefaultAPIVersion}} (downgraded from {{.DefaultAPIVersion}}){{end}}
|
|
|
|
Go version: {{.GoVersion}}
|
|
|
|
Git commit: {{.GitCommit}}
|
|
|
|
Built: {{.BuildTime}}
|
|
|
|
OS/Arch: {{.Os}}/{{.Arch}}
|
2017-12-20 09:04:41 -05:00
|
|
|
Experimental: {{.Experimental}}
|
2017-12-01 08:47:20 -05:00
|
|
|
{{- end}}
|
|
|
|
|
|
|
|
{{- if .ServerOK}}{{with .Server}}
|
|
|
|
|
|
|
|
Server:{{if ne .Platform.Name ""}} {{.Platform.Name}}{{end}}
|
|
|
|
{{- range $component := .Components}}
|
|
|
|
{{$component.Name}}:
|
|
|
|
{{- if eq $component.Name "Engine" }}
|
|
|
|
Version: {{.Version}}
|
|
|
|
API version: {{index .Details "ApiVersion"}} (minimum version {{index .Details "MinAPIVersion"}})
|
|
|
|
Go version: {{index .Details "GoVersion"}}
|
|
|
|
Git commit: {{index .Details "GitCommit"}}
|
|
|
|
Built: {{index .Details "BuildTime"}}
|
|
|
|
OS/Arch: {{index .Details "Os"}}/{{index .Details "Arch"}}
|
|
|
|
Experimental: {{index .Details "Experimental"}}
|
2017-12-06 15:35:43 -05:00
|
|
|
{{- else }}
|
2017-12-01 08:47:20 -05:00
|
|
|
Version: {{$component.Version}}
|
2017-12-06 15:35:43 -05:00
|
|
|
{{- $detailsOrder := getDetailsOrder $component}}
|
|
|
|
{{- range $key := $detailsOrder}}
|
2018-05-18 08:46:23 -04:00
|
|
|
{{$key}}: {{index $component.Details $key}}
|
2017-12-01 08:47:20 -05:00
|
|
|
{{- end}}
|
|
|
|
{{- end}}
|
|
|
|
{{- end}}
|
2018-03-20 11:42:57 -04:00
|
|
|
{{- end}}{{- end}}`
|
2016-09-08 13:11:39 -04:00
|
|
|
|
|
|
|
type versionOptions struct {
|
2018-02-22 08:55:42 -05:00
|
|
|
format string
|
|
|
|
kubeConfig string
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2017-02-14 19:21:40 -05:00
|
|
|
// versionInfo contains version information of both the Client, and Server
|
|
|
|
type versionInfo struct {
|
2018-03-20 11:42:57 -04:00
|
|
|
Client clientVersion
|
|
|
|
Server *types.Version
|
2017-02-14 19:21:40 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
type clientVersion struct {
|
2017-12-01 08:47:20 -05:00
|
|
|
Platform struct{ Name string } `json:",omitempty"`
|
|
|
|
|
2017-02-14 19:21:40 -05:00
|
|
|
Version string
|
|
|
|
APIVersion string `json:"ApiVersion"`
|
|
|
|
DefaultAPIVersion string `json:"DefaultAPIVersion,omitempty"`
|
|
|
|
GitCommit string
|
|
|
|
GoVersion string
|
|
|
|
Os string
|
|
|
|
Arch string
|
|
|
|
BuildTime string `json:",omitempty"`
|
2017-12-20 09:04:41 -05:00
|
|
|
Experimental bool
|
2017-02-14 19:21:40 -05:00
|
|
|
}
|
|
|
|
|
2018-02-22 08:55:42 -05:00
|
|
|
type kubernetesVersion struct {
|
|
|
|
Kubernetes string
|
|
|
|
StackAPI string
|
|
|
|
}
|
|
|
|
|
2017-02-14 19:21:40 -05:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2016-09-08 13:11:39 -04:00
|
|
|
// NewVersionCommand creates a new cobra.Command for `docker version`
|
2017-12-05 09:41:03 -05:00
|
|
|
func NewVersionCommand(dockerCli command.Cli) *cobra.Command {
|
2016-09-08 13:11:39 -04:00
|
|
|
var opts versionOptions
|
|
|
|
|
|
|
|
cmd := &cobra.Command{
|
|
|
|
Use: "version [OPTIONS]",
|
|
|
|
Short: "Show the Docker version information",
|
|
|
|
Args: cli.NoArgs,
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
return runVersion(dockerCli, &opts)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
flags := cmd.Flags()
|
2016-10-18 06:50:11 -04:00
|
|
|
flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template")
|
2018-06-22 02:33:55 -04:00
|
|
|
flags.StringVar(&opts.kubeConfig, "kubeconfig", "", "Kubernetes config file")
|
2018-02-22 08:55:42 -05:00
|
|
|
flags.SetAnnotation("kubeconfig", "kubernetes", nil)
|
2016-09-08 13:11:39 -04:00
|
|
|
|
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
2017-12-01 08:47:20 -05:00
|
|
|
func reformatDate(buildTime string) string {
|
|
|
|
t, errTime := time.Parse(time.RFC3339Nano, buildTime)
|
|
|
|
if errTime == nil {
|
|
|
|
return t.Format(time.ANSIC)
|
|
|
|
}
|
|
|
|
return buildTime
|
|
|
|
}
|
|
|
|
|
2017-12-05 09:41:03 -05:00
|
|
|
func runVersion(dockerCli command.Cli, opts *versionOptions) error {
|
2017-12-06 15:35:43 -05:00
|
|
|
var err error
|
2018-05-31 04:10:56 -04:00
|
|
|
tmpl, err := newVersionTemplate(opts.format)
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
2018-05-31 04:10:56 -04:00
|
|
|
return cli.StatusError{StatusCode: 64, Status: err.Error()}
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2018-06-22 10:26:18 -04:00
|
|
|
orchestrator, err := command.GetStackOrchestrator("", dockerCli.ConfigFile().StackOrchestrator, dockerCli.Err())
|
2018-06-20 08:48:50 -04:00
|
|
|
if err != nil {
|
|
|
|
return cli.StatusError{StatusCode: 64, Status: err.Error()}
|
|
|
|
}
|
|
|
|
|
2017-02-14 19:21:40 -05:00
|
|
|
vd := versionInfo{
|
|
|
|
Client: clientVersion{
|
2017-12-05 09:41:03 -05:00
|
|
|
Platform: struct{ Name string }{cli.PlatformName},
|
2017-05-09 12:38:23 -04:00
|
|
|
Version: cli.Version,
|
2017-02-14 19:21:40 -05:00
|
|
|
APIVersion: dockerCli.Client().ClientVersion(),
|
|
|
|
DefaultAPIVersion: dockerCli.DefaultVersion(),
|
|
|
|
GoVersion: runtime.Version(),
|
2017-05-09 12:38:23 -04:00
|
|
|
GitCommit: cli.GitCommit,
|
2017-12-05 09:41:03 -05:00
|
|
|
BuildTime: reformatDate(cli.BuildTime),
|
2017-02-14 19:21:40 -05:00
|
|
|
Os: runtime.GOOS,
|
|
|
|
Arch: runtime.GOARCH,
|
2017-12-20 09:04:41 -05:00
|
|
|
Experimental: dockerCli.ClientInfo().HasExperimental,
|
2016-09-08 13:11:39 -04:00
|
|
|
},
|
|
|
|
}
|
2017-12-01 08:47:20 -05:00
|
|
|
|
2017-12-05 09:41:03 -05:00
|
|
|
sv, err := dockerCli.Client().ServerVersion(context.Background())
|
2017-12-01 08:47:20 -05:00
|
|
|
if err == nil {
|
|
|
|
vd.Server = &sv
|
2018-06-20 08:48:50 -04:00
|
|
|
var kubeVersion *kubernetesVersion
|
|
|
|
if orchestrator.HasKubernetes() {
|
|
|
|
kubeVersion = getKubernetesVersion(opts.kubeConfig)
|
|
|
|
}
|
2017-12-01 08:47:20 -05:00
|
|
|
foundEngine := false
|
2018-03-20 11:42:57 -04:00
|
|
|
foundKubernetes := false
|
2017-12-01 08:47:20 -05:00
|
|
|
for _, component := range sv.Components {
|
2018-03-20 11:42:57 -04:00
|
|
|
switch component.Name {
|
|
|
|
case "Engine":
|
2017-12-01 08:47:20 -05:00
|
|
|
foundEngine = true
|
|
|
|
buildTime, ok := component.Details["BuildTime"]
|
|
|
|
if ok {
|
|
|
|
component.Details["BuildTime"] = reformatDate(buildTime)
|
|
|
|
}
|
2018-03-20 11:42:57 -04:00
|
|
|
case "Kubernetes":
|
|
|
|
foundKubernetes = true
|
|
|
|
if _, ok := component.Details["StackAPI"]; !ok && kubeVersion != nil {
|
|
|
|
component.Details["StackAPI"] = kubeVersion.StackAPI
|
|
|
|
}
|
2017-12-01 08:47:20 -05:00
|
|
|
}
|
|
|
|
}
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2017-12-01 08:47:20 -05:00
|
|
|
if !foundEngine {
|
|
|
|
vd.Server.Components = append(vd.Server.Components, types.ComponentVersion{
|
|
|
|
Name: "Engine",
|
|
|
|
Version: sv.Version,
|
|
|
|
Details: map[string]string{
|
|
|
|
"ApiVersion": sv.APIVersion,
|
|
|
|
"MinAPIVersion": sv.MinAPIVersion,
|
|
|
|
"GitCommit": sv.GitCommit,
|
|
|
|
"GoVersion": sv.GoVersion,
|
|
|
|
"Os": sv.Os,
|
|
|
|
"Arch": sv.Arch,
|
|
|
|
"BuildTime": reformatDate(vd.Server.BuildTime),
|
|
|
|
"Experimental": fmt.Sprintf("%t", sv.Experimental),
|
|
|
|
},
|
|
|
|
})
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2018-03-20 11:42:57 -04:00
|
|
|
if !foundKubernetes && kubeVersion != nil {
|
|
|
|
vd.Server.Components = append(vd.Server.Components, types.ComponentVersion{
|
|
|
|
Name: "Kubernetes",
|
|
|
|
Version: kubeVersion.Kubernetes,
|
|
|
|
Details: map[string]string{
|
|
|
|
"StackAPI": kubeVersion.StackAPI,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2018-05-31 04:10:56 -04:00
|
|
|
if err2 := prettyPrintVersion(dockerCli, vd, tmpl); err2 != nil && err == nil {
|
2016-09-08 13:11:39 -04:00
|
|
|
err = err2
|
|
|
|
}
|
2018-05-31 04:10:56 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func prettyPrintVersion(dockerCli command.Cli, vd versionInfo, tmpl *template.Template) error {
|
|
|
|
t := tabwriter.NewWriter(dockerCli.Out(), 15, 1, 1, ' ', 0)
|
|
|
|
err := tmpl.Execute(t, vd)
|
2018-03-26 10:09:52 -04:00
|
|
|
t.Write([]byte("\n"))
|
|
|
|
t.Flush()
|
2016-09-08 13:11:39 -04:00
|
|
|
return err
|
|
|
|
}
|
2017-12-06 15:35:43 -05:00
|
|
|
|
2018-05-31 04:10:56 -04:00
|
|
|
func newVersionTemplate(templateFormat string) (*template.Template, error) {
|
|
|
|
if templateFormat == "" {
|
|
|
|
templateFormat = versionTemplate
|
|
|
|
}
|
|
|
|
tmpl := templates.New("version").Funcs(template.FuncMap{"getDetailsOrder": getDetailsOrder})
|
|
|
|
tmpl, err := tmpl.Parse(templateFormat)
|
|
|
|
|
|
|
|
return tmpl, errors.Wrap(err, "Template parsing error")
|
|
|
|
}
|
|
|
|
|
2017-12-06 15:35:43 -05:00
|
|
|
func getDetailsOrder(v types.ComponentVersion) []string {
|
|
|
|
out := make([]string, 0, len(v.Details))
|
|
|
|
for k := range v.Details {
|
|
|
|
out = append(out, k)
|
|
|
|
}
|
|
|
|
sort.Strings(out)
|
|
|
|
return out
|
|
|
|
}
|
2018-02-22 08:55:42 -05:00
|
|
|
|
2018-06-20 08:48:50 -04:00
|
|
|
func getKubernetesVersion(kubeConfig string) *kubernetesVersion {
|
2018-02-22 08:55:42 -05:00
|
|
|
version := kubernetesVersion{
|
|
|
|
Kubernetes: "Unknown",
|
|
|
|
StackAPI: "Unknown",
|
|
|
|
}
|
2018-03-30 12:04:38 -04:00
|
|
|
clientConfig := kubernetes.NewKubernetesConfig(kubeConfig)
|
|
|
|
config, err := clientConfig.ClientConfig()
|
2018-02-22 08:55:42 -05:00
|
|
|
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()
|
|
|
|
}
|