github.com/xeptore/docker-cli@v20.10.14+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 flags.SetAnnotation("kubeconfig", "deprecated", nil) 118 119 return cmd 120 } 121 122 func reformatDate(buildTime string) string { 123 t, errTime := time.Parse(time.RFC3339Nano, buildTime) 124 if errTime == nil { 125 return t.Format(time.ANSIC) 126 } 127 return buildTime 128 } 129 130 func arch() string { 131 arch := runtime.GOARCH 132 if rosetta.Enabled() { 133 arch += " (rosetta)" 134 } 135 return arch 136 } 137 138 func runVersion(dockerCli command.Cli, opts *versionOptions) error { 139 var err error 140 tmpl, err := newVersionTemplate(opts.format) 141 if err != nil { 142 return cli.StatusError{StatusCode: 64, Status: err.Error()} 143 } 144 145 orchestrator, err := dockerCli.StackOrchestrator("") 146 if err != nil { 147 return cli.StatusError{StatusCode: 64, Status: err.Error()} 148 } 149 150 vd := versionInfo{ 151 Client: clientVersion{ 152 Platform: struct{ Name string }{version.PlatformName}, 153 Version: version.Version, 154 APIVersion: dockerCli.Client().ClientVersion(), 155 DefaultAPIVersion: dockerCli.DefaultVersion(), 156 GoVersion: runtime.Version(), 157 GitCommit: version.GitCommit, 158 BuildTime: reformatDate(version.BuildTime), 159 Os: runtime.GOOS, 160 Arch: arch(), 161 Experimental: true, 162 Context: dockerCli.CurrentContext(), 163 }, 164 } 165 166 sv, err := dockerCli.Client().ServerVersion(context.Background()) 167 if err == nil { 168 vd.Server = &sv 169 var kubeVersion *kubernetesVersion 170 if orchestrator.HasKubernetes() { 171 kubeVersion = getKubernetesVersion(dockerCli, opts.kubeConfig) 172 } 173 foundEngine := false 174 foundKubernetes := false 175 for _, component := range sv.Components { 176 switch component.Name { 177 case "Engine": 178 foundEngine = true 179 buildTime, ok := component.Details["BuildTime"] 180 if ok { 181 component.Details["BuildTime"] = reformatDate(buildTime) 182 } 183 case "Kubernetes": 184 foundKubernetes = true 185 if _, ok := component.Details["StackAPI"]; !ok && kubeVersion != nil { 186 component.Details["StackAPI"] = kubeVersion.StackAPI 187 } 188 } 189 } 190 191 if !foundEngine { 192 vd.Server.Components = append(vd.Server.Components, types.ComponentVersion{ 193 Name: "Engine", 194 Version: sv.Version, 195 Details: map[string]string{ 196 "ApiVersion": sv.APIVersion, 197 "MinAPIVersion": sv.MinAPIVersion, 198 "GitCommit": sv.GitCommit, 199 "GoVersion": sv.GoVersion, 200 "Os": sv.Os, 201 "Arch": sv.Arch, 202 "BuildTime": reformatDate(vd.Server.BuildTime), 203 "Experimental": strconv.FormatBool(sv.Experimental), 204 }, 205 }) 206 } 207 if !foundKubernetes && kubeVersion != nil { 208 vd.Server.Components = append(vd.Server.Components, types.ComponentVersion{ 209 Name: "Kubernetes", 210 Version: kubeVersion.Kubernetes, 211 Details: map[string]string{ 212 "StackAPI": kubeVersion.StackAPI, 213 }, 214 }) 215 } 216 } 217 if err2 := prettyPrintVersion(dockerCli, vd, tmpl); err2 != nil && err == nil { 218 err = err2 219 } 220 return err 221 } 222 223 func prettyPrintVersion(dockerCli command.Cli, vd versionInfo, tmpl *template.Template) error { 224 t := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 1, ' ', 0) 225 err := tmpl.Execute(t, vd) 226 t.Write([]byte("\n")) 227 t.Flush() 228 return err 229 } 230 231 func newVersionTemplate(templateFormat string) (*template.Template, error) { 232 if templateFormat == "" { 233 templateFormat = versionTemplate 234 } 235 tmpl := templates.New("version").Funcs(template.FuncMap{"getDetailsOrder": getDetailsOrder}) 236 tmpl, err := tmpl.Parse(templateFormat) 237 238 return tmpl, errors.Wrap(err, "Template parsing error") 239 } 240 241 func getDetailsOrder(v types.ComponentVersion) []string { 242 out := make([]string, 0, len(v.Details)) 243 for k := range v.Details { 244 out = append(out, k) 245 } 246 sort.Strings(out) 247 return out 248 } 249 250 func getKubernetesVersion(dockerCli command.Cli, kubeConfig string) *kubernetesVersion { 251 version := kubernetesVersion{ 252 Kubernetes: "Unknown", 253 StackAPI: "Unknown", 254 } 255 var ( 256 clientConfig clientcmd.ClientConfig 257 err error 258 ) 259 if dockerCli.CurrentContext() == "" { 260 clientConfig = kubeapi.NewKubernetesConfig(kubeConfig) 261 } else { 262 clientConfig, err = kubecontext.ConfigFromContext(dockerCli.CurrentContext(), dockerCli.ContextStore()) 263 } 264 if err != nil { 265 logrus.Debugf("failed to get Kubernetes configuration: %s", err) 266 return &version 267 } 268 config, err := clientConfig.ClientConfig() 269 if err != nil { 270 logrus.Debugf("failed to get Kubernetes client config: %s", err) 271 return &version 272 } 273 kubeClient, err := kubernetesClient.NewForConfig(config) 274 if err != nil { 275 logrus.Debugf("failed to get Kubernetes client: %s", err) 276 return &version 277 } 278 version.StackAPI = getStackVersion(kubeClient) 279 version.Kubernetes = getKubernetesServerVersion(kubeClient) 280 return &version 281 } 282 283 func getStackVersion(client *kubernetesClient.Clientset) string { 284 apiVersion, err := kubernetes.GetStackAPIVersion(client) 285 if err != nil { 286 logrus.Debugf("failed to get Stack API version: %s", err) 287 return "Unknown" 288 } 289 return string(apiVersion) 290 } 291 292 func getKubernetesServerVersion(client *kubernetesClient.Clientset) string { 293 kubeVersion, err := client.DiscoveryClient.ServerVersion() 294 if err != nil { 295 logrus.Debugf("failed to get Kubernetes server version: %s", err) 296 return "Unknown" 297 } 298 return kubeVersion.String() 299 }