2016-09-08 13:11:39 -04:00
package system
import (
2018-05-03 21:02:44 -04:00
"context"
2016-09-08 13:11:39 -04:00
"runtime"
2017-12-06 15:35:43 -05:00
"sort"
2020-10-02 08:19:34 -04:00
"strconv"
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-12-17 05:27:07 -05:00
kubecontext "github.com/docker/cli/cli/context/kubernetes"
2019-01-08 10:03:51 -05:00
"github.com/docker/cli/cli/version"
2019-01-16 06:56:37 -05:00
"github.com/docker/cli/kubernetes"
2017-08-08 11:26:24 -04:00
"github.com/docker/cli/templates"
2019-05-14 10:12:06 -04:00
kubeapi "github.com/docker/compose-on-kubernetes/api"
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"
2020-07-27 00:31:00 -04:00
"github.com/tonistiigi/go-rosetta"
2018-02-22 08:55:42 -05:00
kubernetesClient "k8s.io/client-go/kubernetes"
2018-12-17 05:27:07 -05:00
"k8s.io/client-go/tools/clientcmd"
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 } }
2020-05-07 06:57:20 -04:00
Context : { { . Context } }
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" `
2020-05-07 06:57:20 -04:00
Context string
2020-10-02 08:19:34 -04:00
Experimental bool ` json:",omitempty" ` // Deprecated: experimental CLI features always enabled. This field is kept for backward-compatibility, and is always "true"
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 )
2021-06-29 08:48:14 -04:00
flags . SetAnnotation ( "kubeconfig" , "deprecated" , 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
}
2020-07-27 00:31:00 -04:00
func arch ( ) string {
arch := runtime . GOARCH
if rosetta . Enabled ( ) {
arch += " (rosetta)"
}
return arch
}
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-12-17 05:27:07 -05:00
orchestrator , err := dockerCli . StackOrchestrator ( "" )
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 {
2019-01-08 10:03:51 -05:00
Platform : struct { Name string } { version . PlatformName } ,
Version : version . Version ,
2017-02-14 19:21:40 -05:00
APIVersion : dockerCli . Client ( ) . ClientVersion ( ) ,
DefaultAPIVersion : dockerCli . DefaultVersion ( ) ,
GoVersion : runtime . Version ( ) ,
2019-01-08 10:03:51 -05:00
GitCommit : version . GitCommit ,
BuildTime : reformatDate ( version . BuildTime ) ,
2017-02-14 19:21:40 -05:00
Os : runtime . GOOS ,
2020-07-27 00:31:00 -04:00
Arch : arch ( ) ,
2020-10-02 08:19:34 -04:00
Experimental : true ,
2020-05-07 06:57:20 -04:00
Context : dockerCli . CurrentContext ( ) ,
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 ( ) {
2018-12-17 05:27:07 -05:00
kubeVersion = getKubernetesVersion ( dockerCli , opts . kubeConfig )
2018-06-20 08:48:50 -04:00
}
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 ) ,
2020-10-02 08:19:34 -04:00
"Experimental" : strconv . FormatBool ( sv . Experimental ) ,
2017-12-01 08:47:20 -05:00
} ,
} )
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 {
2018-07-11 19:34:34 -04:00
t := tabwriter . NewWriter ( dockerCli . Out ( ) , 20 , 1 , 1 , ' ' , 0 )
2018-05-31 04:10:56 -04:00
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-12-17 05:27:07 -05:00
func getKubernetesVersion ( dockerCli command . Cli , kubeConfig string ) * kubernetesVersion {
2018-02-22 08:55:42 -05:00
version := kubernetesVersion {
Kubernetes : "Unknown" ,
StackAPI : "Unknown" ,
}
2018-12-17 05:27:07 -05:00
var (
clientConfig clientcmd . ClientConfig
err error
)
2018-11-09 09:10:41 -05:00
if dockerCli . CurrentContext ( ) == "" {
2019-05-14 10:12:06 -04:00
clientConfig = kubeapi . NewKubernetesConfig ( kubeConfig )
2018-12-17 05:27:07 -05:00
} else {
clientConfig , err = kubecontext . ConfigFromContext ( dockerCli . CurrentContext ( ) , dockerCli . ContextStore ( ) )
}
2018-02-22 08:55:42 -05:00
if err != nil {
logrus . Debugf ( "failed to get Kubernetes configuration: %s" , err )
return & version
}
2018-12-17 05:27:07 -05:00
config , err := clientConfig . ClientConfig ( )
if err != nil {
logrus . Debugf ( "failed to get Kubernetes client config: %s" , err )
return & version
}
2018-02-22 08:55:42 -05:00
kubeClient , err := kubernetesClient . NewForConfig ( config )
if err != nil {
logrus . Debugf ( "failed to get Kubernetes client: %s" , err )
return & version
}
2020-10-02 08:19:34 -04:00
version . StackAPI = getStackVersion ( kubeClient )
2018-02-22 08:55:42 -05:00
version . Kubernetes = getKubernetesServerVersion ( kubeClient )
return & version
}
2020-10-02 08:19:34 -04:00
func getStackVersion ( client * kubernetesClient . Clientset ) string {
apiVersion , err := kubernetes . GetStackAPIVersion ( client )
2018-02-22 08:55:42 -05:00
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 ( )
}