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 }