Files
docker-cli/cli/command/system/version.go
Ian Campbell 20c19830a9 Move versioning variables to a separate package.
This helps to avoid circular includes, by separating the pure data out from the
actual functionality in the cli subpackage, allowing other code which is
imported to access the data.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-01-29 11:26:40 +00:00

286 lines
8.2 KiB
Go

package system
import (
"context"
"fmt"
"runtime"
"sort"
"text/tabwriter"
"text/template"
"time"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
kubecontext "github.com/docker/cli/cli/context/kubernetes"
"github.com/docker/cli/cli/version"
"github.com/docker/cli/kubernetes"
"github.com/docker/cli/templates"
"github.com/docker/docker/api/types"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
kubernetesClient "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
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}}
Experimental: {{.Experimental}}
{{- 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"}}
{{- else }}
Version: {{$component.Version}}
{{- $detailsOrder := getDetailsOrder $component}}
{{- range $key := $detailsOrder}}
{{$key}}: {{index $component.Details $key}}
{{- end}}
{{- end}}
{{- end}}
{{- end}}{{- end}}`
type versionOptions struct {
format string
kubeConfig string
}
// versionInfo contains version information of both the Client, and Server
type versionInfo struct {
Client clientVersion
Server *types.Version
}
type clientVersion struct {
Platform struct{ Name string } `json:",omitempty"`
Version string
APIVersion string `json:"ApiVersion"`
DefaultAPIVersion string `json:"DefaultAPIVersion,omitempty"`
GitCommit string
GoVersion string
Os string
Arch string
BuildTime string `json:",omitempty"`
Experimental bool
}
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
}
// NewVersionCommand creates a new cobra.Command for `docker version`
func NewVersionCommand(dockerCli command.Cli) *cobra.Command {
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()
flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template")
flags.StringVar(&opts.kubeConfig, "kubeconfig", "", "Kubernetes config file")
flags.SetAnnotation("kubeconfig", "kubernetes", nil)
return cmd
}
func reformatDate(buildTime string) string {
t, errTime := time.Parse(time.RFC3339Nano, buildTime)
if errTime == nil {
return t.Format(time.ANSIC)
}
return buildTime
}
func runVersion(dockerCli command.Cli, opts *versionOptions) error {
var err error
tmpl, err := newVersionTemplate(opts.format)
if err != nil {
return cli.StatusError{StatusCode: 64, Status: err.Error()}
}
orchestrator, err := dockerCli.StackOrchestrator("")
if err != nil {
return cli.StatusError{StatusCode: 64, Status: err.Error()}
}
vd := versionInfo{
Client: clientVersion{
Platform: struct{ Name string }{version.PlatformName},
Version: version.Version,
APIVersion: dockerCli.Client().ClientVersion(),
DefaultAPIVersion: dockerCli.DefaultVersion(),
GoVersion: runtime.Version(),
GitCommit: version.GitCommit,
BuildTime: reformatDate(version.BuildTime),
Os: runtime.GOOS,
Arch: runtime.GOARCH,
Experimental: dockerCli.ClientInfo().HasExperimental,
},
}
sv, err := dockerCli.Client().ServerVersion(context.Background())
if err == nil {
vd.Server = &sv
var kubeVersion *kubernetesVersion
if orchestrator.HasKubernetes() {
kubeVersion = getKubernetesVersion(dockerCli, opts.kubeConfig)
}
foundEngine := false
foundKubernetes := false
for _, component := range sv.Components {
switch component.Name {
case "Engine":
foundEngine = true
buildTime, ok := component.Details["BuildTime"]
if ok {
component.Details["BuildTime"] = reformatDate(buildTime)
}
case "Kubernetes":
foundKubernetes = true
if _, ok := component.Details["StackAPI"]; !ok && kubeVersion != nil {
component.Details["StackAPI"] = kubeVersion.StackAPI
}
}
}
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),
},
})
}
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,
},
})
}
}
if err2 := prettyPrintVersion(dockerCli, vd, tmpl); err2 != nil && err == nil {
err = err2
}
return err
}
func prettyPrintVersion(dockerCli command.Cli, vd versionInfo, tmpl *template.Template) error {
t := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 1, ' ', 0)
err := tmpl.Execute(t, vd)
t.Write([]byte("\n"))
t.Flush()
return err
}
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")
}
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
}
func getKubernetesVersion(dockerCli command.Cli, kubeConfig string) *kubernetesVersion {
version := kubernetesVersion{
Kubernetes: "Unknown",
StackAPI: "Unknown",
}
var (
clientConfig clientcmd.ClientConfig
err error
)
if dockerCli.CurrentContext() == "" {
clientConfig = kubernetes.NewKubernetesConfig(kubeConfig)
} else {
clientConfig, err = kubecontext.ConfigFromContext(dockerCli.CurrentContext(), dockerCli.ContextStore())
}
if err != nil {
logrus.Debugf("failed to get Kubernetes configuration: %s", err)
return &version
}
config, err := clientConfig.ClientConfig()
if err != nil {
logrus.Debugf("failed to get Kubernetes client config: %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, dockerCli.ClientInfo().HasExperimental)
version.Kubernetes = getKubernetesServerVersion(kubeClient)
return &version
}
func getStackVersion(client *kubernetesClient.Clientset, experimental bool) string {
apiVersion, err := kubernetes.GetStackAPIVersion(client, experimental)
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()
}