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  }