github.com/khulnasoft/cli@v0.0.0-20240402070845-01bcad7beefa/cli/command/system/version.go (about) 1 package system 2 3 import ( 4 "context" 5 "runtime" 6 "sort" 7 "strconv" 8 "text/template" 9 "time" 10 11 "github.com/docker/docker/api/types" 12 "github.com/khulnasoft/cli/cli" 13 "github.com/khulnasoft/cli/cli/command" 14 "github.com/khulnasoft/cli/cli/command/completion" 15 "github.com/khulnasoft/cli/cli/command/formatter" 16 "github.com/khulnasoft/cli/cli/command/formatter/tabwriter" 17 flagsHelper "github.com/khulnasoft/cli/cli/flags" 18 "github.com/khulnasoft/cli/cli/version" 19 "github.com/khulnasoft/cli/templates" 20 "github.com/pkg/errors" 21 "github.com/spf13/cobra" 22 "github.com/tonistiigi/go-rosetta" 23 ) 24 25 const defaultVersionTemplate = `{{with .Client -}} 26 Client:{{if ne .Platform nil}}{{if ne .Platform.Name ""}} {{.Platform.Name}}{{end}}{{end}} 27 Version: {{.Version}} 28 API version: {{.APIVersion}}{{if ne .APIVersion .DefaultAPIVersion}} (downgraded from {{.DefaultAPIVersion}}){{end}} 29 Go version: {{.GoVersion}} 30 Git commit: {{.GitCommit}} 31 Built: {{.BuildTime}} 32 OS/Arch: {{.Os}}/{{.Arch}} 33 Context: {{.Context}} 34 {{- end}} 35 36 {{- if ne .Server nil}}{{with .Server}} 37 38 Server:{{if ne .Platform.Name ""}} {{.Platform.Name}}{{end}} 39 {{- range $component := .Components}} 40 {{$component.Name}}: 41 {{- if eq $component.Name "Engine" }} 42 Version: {{.Version}} 43 API version: {{index .Details "ApiVersion"}} (minimum version {{index .Details "MinAPIVersion"}}) 44 Go version: {{index .Details "GoVersion"}} 45 Git commit: {{index .Details "GitCommit"}} 46 Built: {{index .Details "BuildTime"}} 47 OS/Arch: {{index .Details "Os"}}/{{index .Details "Arch"}} 48 Experimental: {{index .Details "Experimental"}} 49 {{- else }} 50 Version: {{$component.Version}} 51 {{- $detailsOrder := getDetailsOrder $component}} 52 {{- range $key := $detailsOrder}} 53 {{$key}}: {{index $component.Details $key}} 54 {{- end}} 55 {{- end}} 56 {{- end}} 57 {{- end}}{{- end}}` 58 59 type versionOptions struct { 60 format string 61 } 62 63 // versionInfo contains version information of both the Client, and Server 64 type versionInfo struct { 65 Client clientVersion 66 Server *types.Version 67 } 68 69 type platformInfo struct { 70 Name string `json:"Name,omitempty"` 71 } 72 73 type clientVersion struct { 74 Platform *platformInfo `json:"Platform,omitempty"` 75 Version string `json:"Version,omitempty"` 76 APIVersion string `json:"ApiVersion,omitempty"` 77 DefaultAPIVersion string `json:"DefaultAPIVersion,omitempty"` 78 GitCommit string `json:"GitCommit,omitempty"` 79 GoVersion string `json:"GoVersion,omitempty"` 80 Os string `json:"Os,omitempty"` 81 Arch string `json:"Arch,omitempty"` 82 BuildTime string `json:"BuildTime,omitempty"` 83 Context string `json:"Context"` 84 } 85 86 // newClientVersion constructs a new clientVersion. If a dockerCLI is 87 // passed as argument, additional information is included (API version), 88 // which may invoke an API connection. Pass nil to omit the additional 89 // information. 90 func newClientVersion(contextName string, dockerCli command.Cli) clientVersion { 91 v := clientVersion{ 92 Version: version.Version, 93 GoVersion: runtime.Version(), 94 GitCommit: version.GitCommit, 95 BuildTime: reformatDate(version.BuildTime), 96 Os: runtime.GOOS, 97 Arch: arch(), 98 Context: contextName, 99 } 100 if version.PlatformName != "" { 101 v.Platform = &platformInfo{Name: version.PlatformName} 102 } 103 if dockerCli != nil { 104 v.APIVersion = dockerCli.CurrentVersion() 105 v.DefaultAPIVersion = dockerCli.DefaultVersion() 106 } 107 return v 108 } 109 110 // NewVersionCommand creates a new cobra.Command for `docker version` 111 func NewVersionCommand(dockerCli command.Cli) *cobra.Command { 112 var opts versionOptions 113 114 cmd := &cobra.Command{ 115 Use: "version [OPTIONS]", 116 Short: "Show the Docker version information", 117 Args: cli.NoArgs, 118 RunE: func(cmd *cobra.Command, args []string) error { 119 return runVersion(cmd.Context(), dockerCli, &opts) 120 }, 121 Annotations: map[string]string{ 122 "category-top": "10", 123 }, 124 ValidArgsFunction: completion.NoComplete, 125 } 126 127 cmd.Flags().StringVarP(&opts.format, "format", "f", "", flagsHelper.InspectFormatHelp) 128 return cmd 129 } 130 131 func reformatDate(buildTime string) string { 132 t, errTime := time.Parse(time.RFC3339Nano, buildTime) 133 if errTime == nil { 134 return t.Format(time.ANSIC) 135 } 136 return buildTime 137 } 138 139 func arch() string { 140 arch := runtime.GOARCH 141 if rosetta.Enabled() { 142 arch += " (rosetta)" 143 } 144 return arch 145 } 146 147 func runVersion(ctx context.Context, dockerCli command.Cli, opts *versionOptions) error { 148 var err error 149 tmpl, err := newVersionTemplate(opts.format) 150 if err != nil { 151 return cli.StatusError{StatusCode: 64, Status: err.Error()} 152 } 153 154 // TODO print error if kubernetes is used? 155 156 vd := versionInfo{ 157 Client: newClientVersion(dockerCli.CurrentContext(), dockerCli), 158 } 159 sv, err := dockerCli.Client().ServerVersion(ctx) 160 if err == nil { 161 vd.Server = &sv 162 foundEngine := false 163 for _, component := range sv.Components { 164 if component.Name == "Engine" { 165 foundEngine = true 166 buildTime, ok := component.Details["BuildTime"] 167 if ok { 168 component.Details["BuildTime"] = reformatDate(buildTime) 169 } 170 } 171 } 172 173 if !foundEngine { 174 vd.Server.Components = append(vd.Server.Components, types.ComponentVersion{ 175 Name: "Engine", 176 Version: sv.Version, 177 Details: map[string]string{ 178 "ApiVersion": sv.APIVersion, 179 "MinAPIVersion": sv.MinAPIVersion, 180 "GitCommit": sv.GitCommit, 181 "GoVersion": sv.GoVersion, 182 "Os": sv.Os, 183 "Arch": sv.Arch, 184 "BuildTime": reformatDate(vd.Server.BuildTime), 185 "Experimental": strconv.FormatBool(sv.Experimental), 186 }, 187 }) 188 } 189 } 190 if err2 := prettyPrintVersion(dockerCli, vd, tmpl); err2 != nil && err == nil { 191 err = err2 192 } 193 return err 194 } 195 196 func prettyPrintVersion(dockerCli command.Cli, vd versionInfo, tmpl *template.Template) error { 197 t := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 1, ' ', 0) 198 err := tmpl.Execute(t, vd) 199 t.Write([]byte("\n")) 200 t.Flush() 201 return err 202 } 203 204 func newVersionTemplate(templateFormat string) (*template.Template, error) { 205 switch templateFormat { 206 case "": 207 templateFormat = defaultVersionTemplate 208 case formatter.JSONFormatKey: 209 templateFormat = formatter.JSONFormat 210 } 211 tmpl := templates.New("version").Funcs(template.FuncMap{"getDetailsOrder": getDetailsOrder}) 212 tmpl, err := tmpl.Parse(templateFormat) 213 214 return tmpl, errors.Wrap(err, "template parsing error") 215 } 216 217 func getDetailsOrder(v types.ComponentVersion) []string { 218 out := make([]string, 0, len(v.Details)) 219 for k := range v.Details { 220 out = append(out, k) 221 } 222 sort.Strings(out) 223 return out 224 }