The CLI disabled experimental features by default, requiring users
to set a configuration option to enable them.
Disabling experimental features was a request from Enterprise users
that did not want experimental features to be accessible.
We are changing this policy, and now enable experimental features
by default. Experimental features may still change and/or removed,
and will be highlighted in the documentation and "usage" output.
For example, the `docker manifest inspect --help` output now shows:
EXPERIMENTAL:
docker manifest inspect is an experimental feature.
Experimental features provide early access to product functionality. These features
may change between releases without warning or can be removed entirely from a future
release. Learn more about experimental features: https://docs.docker.com/go/experimental/
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
299 lines
8.5 KiB
Go
299 lines
8.5 KiB
Go
package system
|
|
|
|
import (
|
|
"context"
|
|
"runtime"
|
|
"sort"
|
|
"strconv"
|
|
"text/tabwriter"
|
|
"text/template"
|
|
"time"
|
|
|
|
"github.com/docker/cli/cli"
|
|
"github.com/docker/cli/cli/command"
|
|
kubecontext "github.com/docker/cli/cli/context/kubernetes"
|
|
"github.com/docker/cli/cli/version"
|
|
"github.com/docker/cli/kubernetes"
|
|
"github.com/docker/cli/templates"
|
|
kubeapi "github.com/docker/compose-on-kubernetes/api"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/spf13/cobra"
|
|
"github.com/tonistiigi/go-rosetta"
|
|
kubernetesClient "k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/tools/clientcmd"
|
|
)
|
|
|
|
var versionTemplate = `{{with .Client -}}
|
|
Client:{{if ne .Platform.Name ""}} {{.Platform.Name}}{{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}}
|
|
Experimental: {{.Experimental}}
|
|
{{- end}}
|
|
|
|
{{- if .ServerOK}}{{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
|
|
kubeConfig string
|
|
}
|
|
|
|
// versionInfo contains version information of both the Client, and Server
|
|
type versionInfo struct {
|
|
Client clientVersion
|
|
Server *types.Version
|
|
}
|
|
|
|
type clientVersion struct {
|
|
Platform struct{ Name string } `json:",omitempty"`
|
|
|
|
Version string
|
|
APIVersion string `json:"ApiVersion"`
|
|
DefaultAPIVersion string `json:"DefaultAPIVersion,omitempty"`
|
|
GitCommit string
|
|
GoVersion string
|
|
Os string
|
|
Arch string
|
|
BuildTime string `json:",omitempty"`
|
|
Context string
|
|
Experimental bool `json:",omitempty"` // Deprecated: experimental CLI features always enabled. This field is kept for backward-compatibility, and is always "true"
|
|
}
|
|
|
|
type kubernetesVersion struct {
|
|
Kubernetes string
|
|
StackAPI string
|
|
}
|
|
|
|
// ServerOK returns true when the client could connect to the docker server
|
|
// and parse the information received. It returns false otherwise.
|
|
func (v versionInfo) ServerOK() bool {
|
|
return v.Server != nil
|
|
}
|
|
|
|
// NewVersionCommand creates a new cobra.Command for `docker version`
|
|
func NewVersionCommand(dockerCli command.Cli) *cobra.Command {
|
|
var opts versionOptions
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "version [OPTIONS]",
|
|
Short: "Show the Docker version information",
|
|
Args: cli.NoArgs,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return runVersion(dockerCli, &opts)
|
|
},
|
|
}
|
|
|
|
flags := cmd.Flags()
|
|
flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template")
|
|
flags.StringVar(&opts.kubeConfig, "kubeconfig", "", "Kubernetes config file")
|
|
flags.SetAnnotation("kubeconfig", "kubernetes", nil)
|
|
|
|
return cmd
|
|
}
|
|
|
|
func reformatDate(buildTime string) string {
|
|
t, errTime := time.Parse(time.RFC3339Nano, buildTime)
|
|
if errTime == nil {
|
|
return t.Format(time.ANSIC)
|
|
}
|
|
return buildTime
|
|
}
|
|
|
|
func arch() string {
|
|
arch := runtime.GOARCH
|
|
if rosetta.Enabled() {
|
|
arch += " (rosetta)"
|
|
}
|
|
return arch
|
|
}
|
|
|
|
func runVersion(dockerCli command.Cli, opts *versionOptions) error {
|
|
var err error
|
|
tmpl, err := newVersionTemplate(opts.format)
|
|
if err != nil {
|
|
return cli.StatusError{StatusCode: 64, Status: err.Error()}
|
|
}
|
|
|
|
orchestrator, err := dockerCli.StackOrchestrator("")
|
|
if err != nil {
|
|
return cli.StatusError{StatusCode: 64, Status: err.Error()}
|
|
}
|
|
|
|
vd := versionInfo{
|
|
Client: clientVersion{
|
|
Platform: struct{ Name string }{version.PlatformName},
|
|
Version: version.Version,
|
|
APIVersion: dockerCli.Client().ClientVersion(),
|
|
DefaultAPIVersion: dockerCli.DefaultVersion(),
|
|
GoVersion: runtime.Version(),
|
|
GitCommit: version.GitCommit,
|
|
BuildTime: reformatDate(version.BuildTime),
|
|
Os: runtime.GOOS,
|
|
Arch: arch(),
|
|
Experimental: true,
|
|
Context: dockerCli.CurrentContext(),
|
|
},
|
|
}
|
|
|
|
sv, err := dockerCli.Client().ServerVersion(context.Background())
|
|
if err == nil {
|
|
vd.Server = &sv
|
|
var kubeVersion *kubernetesVersion
|
|
if orchestrator.HasKubernetes() {
|
|
kubeVersion = getKubernetesVersion(dockerCli, opts.kubeConfig)
|
|
}
|
|
foundEngine := false
|
|
foundKubernetes := false
|
|
for _, component := range sv.Components {
|
|
switch component.Name {
|
|
case "Engine":
|
|
foundEngine = true
|
|
buildTime, ok := component.Details["BuildTime"]
|
|
if ok {
|
|
component.Details["BuildTime"] = reformatDate(buildTime)
|
|
}
|
|
case "Kubernetes":
|
|
foundKubernetes = true
|
|
if _, ok := component.Details["StackAPI"]; !ok && kubeVersion != nil {
|
|
component.Details["StackAPI"] = kubeVersion.StackAPI
|
|
}
|
|
}
|
|
}
|
|
|
|
if !foundEngine {
|
|
vd.Server.Components = append(vd.Server.Components, types.ComponentVersion{
|
|
Name: "Engine",
|
|
Version: sv.Version,
|
|
Details: map[string]string{
|
|
"ApiVersion": sv.APIVersion,
|
|
"MinAPIVersion": sv.MinAPIVersion,
|
|
"GitCommit": sv.GitCommit,
|
|
"GoVersion": sv.GoVersion,
|
|
"Os": sv.Os,
|
|
"Arch": sv.Arch,
|
|
"BuildTime": reformatDate(vd.Server.BuildTime),
|
|
"Experimental": strconv.FormatBool(sv.Experimental),
|
|
},
|
|
})
|
|
}
|
|
if !foundKubernetes && kubeVersion != nil {
|
|
vd.Server.Components = append(vd.Server.Components, types.ComponentVersion{
|
|
Name: "Kubernetes",
|
|
Version: kubeVersion.Kubernetes,
|
|
Details: map[string]string{
|
|
"StackAPI": kubeVersion.StackAPI,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
if err2 := prettyPrintVersion(dockerCli, vd, tmpl); err2 != nil && err == nil {
|
|
err = err2
|
|
}
|
|
return err
|
|
}
|
|
|
|
func prettyPrintVersion(dockerCli command.Cli, vd versionInfo, tmpl *template.Template) error {
|
|
t := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 1, ' ', 0)
|
|
err := tmpl.Execute(t, vd)
|
|
t.Write([]byte("\n"))
|
|
t.Flush()
|
|
return err
|
|
}
|
|
|
|
func newVersionTemplate(templateFormat string) (*template.Template, error) {
|
|
if templateFormat == "" {
|
|
templateFormat = versionTemplate
|
|
}
|
|
tmpl := templates.New("version").Funcs(template.FuncMap{"getDetailsOrder": getDetailsOrder})
|
|
tmpl, err := tmpl.Parse(templateFormat)
|
|
|
|
return tmpl, errors.Wrap(err, "Template parsing error")
|
|
}
|
|
|
|
func getDetailsOrder(v types.ComponentVersion) []string {
|
|
out := make([]string, 0, len(v.Details))
|
|
for k := range v.Details {
|
|
out = append(out, k)
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
func getKubernetesVersion(dockerCli command.Cli, kubeConfig string) *kubernetesVersion {
|
|
version := kubernetesVersion{
|
|
Kubernetes: "Unknown",
|
|
StackAPI: "Unknown",
|
|
}
|
|
var (
|
|
clientConfig clientcmd.ClientConfig
|
|
err error
|
|
)
|
|
if dockerCli.CurrentContext() == "" {
|
|
clientConfig = kubeapi.NewKubernetesConfig(kubeConfig)
|
|
} else {
|
|
clientConfig, err = kubecontext.ConfigFromContext(dockerCli.CurrentContext(), dockerCli.ContextStore())
|
|
}
|
|
if err != nil {
|
|
logrus.Debugf("failed to get Kubernetes configuration: %s", err)
|
|
return &version
|
|
}
|
|
config, err := clientConfig.ClientConfig()
|
|
if err != nil {
|
|
logrus.Debugf("failed to get Kubernetes client config: %s", err)
|
|
return &version
|
|
}
|
|
kubeClient, err := kubernetesClient.NewForConfig(config)
|
|
if err != nil {
|
|
logrus.Debugf("failed to get Kubernetes client: %s", err)
|
|
return &version
|
|
}
|
|
version.StackAPI = getStackVersion(kubeClient)
|
|
version.Kubernetes = getKubernetesServerVersion(kubeClient)
|
|
return &version
|
|
}
|
|
|
|
func getStackVersion(client *kubernetesClient.Clientset) string {
|
|
apiVersion, err := kubernetes.GetStackAPIVersion(client)
|
|
if err != nil {
|
|
logrus.Debugf("failed to get Stack API version: %s", err)
|
|
return "Unknown"
|
|
}
|
|
return string(apiVersion)
|
|
}
|
|
|
|
func getKubernetesServerVersion(client *kubernetesClient.Clientset) string {
|
|
kubeVersion, err := client.DiscoveryClient.ServerVersion()
|
|
if err != nil {
|
|
logrus.Debugf("failed to get Kubernetes server version: %s", err)
|
|
return "Unknown"
|
|
}
|
|
return kubeVersion.String()
|
|
}
|