Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
303 changes: 103 additions & 200 deletions cli/command/system/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,174 +5,23 @@ import (
"fmt"
"io"
"runtime"
"sort"
"strconv"
"strings"
"text/template"
"time"

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/formatter/tabwriter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/cli/cli/version"
"github.com/docker/cli/templates"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/spf13/cobra"
"github.com/tonistiigi/go-rosetta"
)

const defaultVersionTemplate = `{{with .Client -}}
Client:{{if ne .Platform nil}}{{if ne .Platform.Name ""}} {{.Platform.Name}}{{end}}{{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}}
Context: {{.Context}}
{{- end}}

{{- if ne .Server nil}}{{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"}}
{{- else }}
Version: {{$component.Version}}
{{- $detailsOrder := getDetailsOrder $component}}
{{- range $key := $detailsOrder}}
{{$key}}: {{index $component.Details $key}}
{{- end}}
{{- end}}
{{- end}}
{{- end}}{{- end}}`

type versionOptions struct {
format string
}

// versionInfo contains version information of both the Client, and Server
type versionInfo struct {
Client clientVersion
Server *serverVersion
}

type platformInfo struct {
Name string `json:"Name,omitempty"`
}

type clientVersion struct {
Platform *platformInfo `json:"Platform,omitempty"`
Version string `json:"Version,omitempty"`
APIVersion string `json:"ApiVersion,omitempty"`
DefaultAPIVersion string `json:"DefaultAPIVersion,omitempty"`
GitCommit string `json:"GitCommit,omitempty"`
GoVersion string `json:"GoVersion,omitempty"`
Os string `json:"Os,omitempty"`
Arch string `json:"Arch,omitempty"`
BuildTime string `json:"BuildTime,omitempty"`
Context string `json:"Context"`
}

// serverVersion contains information about the Docker server host.
// it's the client-side presentation of [client.ServerVersionResult].
type serverVersion struct {
Platform client.PlatformInfo `json:",omitempty"` // Platform is the platform (product name) the server is running on.
Version string `json:"Version"` // Version is the version of the daemon.
APIVersion string `json:"ApiVersion"` // APIVersion is the highest API version supported by the server.
MinAPIVersion string `json:"MinAPIVersion,omitempty"` // MinAPIVersion is the minimum API version the server supports.
Os string `json:"Os"` // Os is the operating system the server runs on.
Arch string `json:"Arch"` // Arch is the hardware architecture the server runs on.
Components []system.ComponentVersion `json:"Components,omitempty"` // Components contains version information for the components making up the server.

// The following fields are deprecated, they relate to the Engine component and are kept for backwards compatibility

GitCommit string `json:"GitCommit,omitempty"`
GoVersion string `json:"GoVersion,omitempty"`
KernelVersion string `json:"KernelVersion,omitempty"`
Experimental bool `json:"Experimental,omitempty"`
BuildTime string `json:"BuildTime,omitempty"`
}

// newClientVersion constructs a new clientVersion. If a dockerCLI is
// passed as argument, additional information is included (API version),
// which may invoke an API connection. Pass nil to omit the additional
// information.
func newClientVersion(contextName string, dockerCli command.Cli) clientVersion {
v := clientVersion{
Version: version.Version,
DefaultAPIVersion: client.MaxAPIVersion,
GoVersion: runtime.Version(),
GitCommit: version.GitCommit,
BuildTime: reformatDate(version.BuildTime),
Os: runtime.GOOS,
Arch: arch(),
Context: contextName,
}
if version.PlatformName != "" {
v.Platform = &platformInfo{Name: version.PlatformName}
}
if dockerCli != nil {
v.APIVersion = dockerCli.CurrentVersion()
}
return v
}

func newServerVersion(sv client.ServerVersionResult) *serverVersion {
out := &serverVersion{
Platform: sv.Platform,
Version: sv.Version,
APIVersion: sv.APIVersion,
MinAPIVersion: sv.MinAPIVersion,
Os: sv.Os,
Arch: sv.Arch,
Experimental: sv.Experimental, //nolint:staticcheck // ignore deprecated field.
Components: make([]system.ComponentVersion, 0, len(sv.Components)),
}
foundEngine := false
for _, component := range sv.Components {
if component.Name == "Engine" {
foundEngine = true
buildTime, ok := component.Details["BuildTime"]
if ok {
component.Details["BuildTime"] = reformatDate(buildTime)
}
out.GitCommit = component.Details["GitCommit"]
out.GoVersion = component.Details["GoVersion"]
out.KernelVersion = component.Details["KernelVersion"]
out.Experimental = func() bool { b, _ := strconv.ParseBool(component.Details["Experimental"]); return b }()
out.BuildTime = buildTime
}
out.Components = append(out.Components, component)
}

if !foundEngine {
out.Components = append(out.Components, system.ComponentVersion{
Name: "Engine",
Version: sv.Version,
Details: map[string]string{
"ApiVersion": sv.APIVersion,
"MinAPIVersion": sv.MinAPIVersion,
"Os": sv.Os,
"Arch": sv.Arch,
},
})
}
return out
}

// newVersionCommand creates a new cobra.Command for `docker version`
func newVersionCommand(dockerCLI command.Cli) *cobra.Command {
// NewVersionCommand crea il comando Cobra per 'docker version'
func NewVersionCommand(dockerCLI command.Cli) *cobra.Command {
var opts versionOptions

cmd := &cobra.Command{
Expand All @@ -182,31 +31,12 @@ func newVersionCommand(dockerCLI command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
return runVersion(cmd.Context(), dockerCLI, &opts)
},
Annotations: map[string]string{
"category-top": "10",
},
ValidArgsFunction: cobra.NoFileCompletions,
DisableFlagsInUseLine: true,
}

cmd.Flags().StringVarP(&opts.format, "format", "f", "", flagsHelper.InspectFormatHelp)
return cmd
}

func reformatDate(buildTime string) string {
t, errTime := time.Parse(time.RFC3339Nano, buildTime)
if errTime == nil {
return t.Format(time.ANSIC)
}
return buildTime
}
flags := cmd.Flags()
flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template")

func arch() string {
out := runtime.GOARCH
if rosetta.Enabled() {
out += " (rosetta)"
}
return out
return cmd
}

func runVersion(ctx context.Context, dockerCLI command.Cli, opts *versionOptions) error {
Expand All @@ -219,43 +49,116 @@ func runVersion(ctx context.Context, dockerCLI command.Cli, opts *versionOptions
vd := versionInfo{
Client: newClientVersion(dockerCLI.CurrentContext(), dockerCLI),
}

// Chiamata API al Daemon Docker di background
sv, err := dockerCLI.Client().ServerVersion(ctx, client.ServerVersionOptions{})
if err == nil {
vd.Server = newServerVersion(sv)

// -------------------------------------------------------------------------
// NOSTRA MODIFICA: Log esplicito in caso di Downgrade dell'API Session
// -------------------------------------------------------------------------
if vd.Client.APIVersion != sv.APIVersion {
_, _ = fmt.Fprintf(dockerCLI.Err(), "Note: API version session negotiated to %s (Client supports %s)\n", sv.APIVersion, vd.Client.APIVersion)
}
// -------------------------------------------------------------------------
}

if err2 := prettyPrintVersion(dockerCLI.Out(), vd, tmpl); err2 != nil && err == nil {
err = err2
}
return err
}

func prettyPrintVersion(out io.Writer, vd versionInfo, tmpl *template.Template) error {
t := tabwriter.NewWriter(out, 20, 1, 1, ' ', 0)
err := tmpl.Execute(t, vd)
_, _ = t.Write([]byte("\n"))
_ = t.Flush()
return err
type platformInfo struct {
Name string `json:",omitempty"`
}

type clientVersion struct {
Platform *platformInfo `json:",omitempty"`
Version string
APIVersion string
DefaultAPIVersion string
GitCommit string
GoVersion string
Os string
Arch string
BuildTime string
Context string
}

type serverVersion struct {
types.Version
}

func newVersionTemplate(templateFormat string) (*template.Template, error) {
switch templateFormat {
case "":
templateFormat = defaultVersionTemplate
case formatter.JSONFormatKey:
templateFormat = formatter.JSONFormat
type versionInfo struct {
Client clientVersion
Server *serverVersion `json:",omitempty"`
}

func newClientVersion(curContext string, dockerCLI command.Cli) clientVersion {
return clientVersion{
Version: dockerCLI.VersionInfo().Version,
APIVersion: dockerCLI.Client().ClientVersion(),
DefaultAPIVersion: dockerCLI.DefaultVersion(),
GitCommit: dockerCLI.VersionInfo().GitCommit,
GoVersion: runtime.Version(),
Os: runtime.GOOS,
Arch: runtime.GOARCH,
BuildTime: dockerCLI.VersionInfo().BuildTime,
Context: curContext,
}
tmpl, err := templates.New("version").Funcs(template.FuncMap{"getDetailsOrder": getDetailsOrder}).Parse(templateFormat)
if err != nil {
return nil, fmt.Errorf("template parsing error: %w", err)
}

func newServerVersion(sv types.Version) *serverVersion {
return &serverVersion{Version: sv}
}

func prettyPrintVersion(w io.Writer, vd versionInfo, tmpl *template.Template) error {
return tmpl.Execute(w, vd)
}

func newVersionTemplate(tmplStr string) (*template.Template, error) {
if tmplStr != "" {
return template.New("version").Parse(tmplStr + "\n")
}
return tmpl, nil
return template.New("version").Parse(defaultVersionTemplate)
}

func getDetailsOrder(v system.ComponentVersion) []string {
out := make([]string, 0, len(v.Details))
for k := range v.Details {
out = append(out, k)
func formatTime(t string) string {
if t == "" {
return ""
}
sort.Strings(out)
return out
parsed, err := time.Parse(time.RFC3339, t)
if err != nil {
return t
}
return parsed.Format(time.ANSIC)
}

const defaultVersionTemplate = `Client:
Version: {{.Client.Version}}
API version: {{.Client.APIVersion}}{{if ne .Client.APIVersion .Client.DefaultAPIVersion}} (downgraded from {{.Client.DefaultAPIVersion}}){{end}}
Go version: {{.Client.GoVersion}}
Git commit: {{.Client.GitCommit}}
Built: {{formatTime .Client.BuildTime}}
OS/Arch: {{.Client.Os}}/{{.Client.Arch}}
Context: {{.Client.Context}}
{{if .Server}}
Server:
Engine:
Version: {{.Server.Version}}
API version: {{.Server.APIVersion}} (minimum version {{.Server.MinAPIVersion}})
Go version: {{.Server.GoVersion}}
Git commit: {{.Server.GitCommit}}
Built: {{formatTime .Server.BuildTime}}
OS/Arch: {{.Server.Os}}/{{.Server.Arch}}
Experimental: {{.Server.Experimental}}
{{if .Server.Components}}{{range .Server.Components}} {{.Name}}:
Version: {{.Version}}
GitCommit: {{index .Details "GitCommit"}}
{{end}}{{end}}{{end}}`

func init() {
var _ func(string) string = formatTime
}