1 package cmd
2
3 import (
4 "context"
5 "fmt"
6 "io"
7 "os"
8 "time"
9
10 "github.com/linkerd/linkerd2/pkg/healthcheck"
11 "github.com/linkerd/linkerd2/pkg/k8s"
12 "github.com/linkerd/linkerd2/pkg/version"
13 "github.com/spf13/cobra"
14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15 )
16
17 const defaultVersionString = "unavailable"
18
19 type versionOptions struct {
20 shortVersion bool
21 onlyClientVersion bool
22 proxy bool
23 namespace string
24 }
25
26 func newVersionOptions() *versionOptions {
27 return &versionOptions{
28 shortVersion: false,
29 onlyClientVersion: false,
30 proxy: false,
31 namespace: "",
32 }
33 }
34
35 func newCmdVersion() *cobra.Command {
36 options := newVersionOptions()
37
38 cmd := &cobra.Command{
39 Use: "version",
40 Short: "Print the client and server version information",
41 Args: cobra.NoArgs,
42 RunE: func(cmd *cobra.Command, args []string) error {
43 var k8sAPI *k8s.KubernetesAPI
44 var err error
45 if !options.onlyClientVersion {
46 k8sAPI, err = k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
47 if err != nil {
48 return err
49 }
50 }
51
52 configureAndRunVersion(k8sAPI, options, os.Stdout)
53 return nil
54 },
55 }
56
57 cmd.PersistentFlags().BoolVar(&options.shortVersion, "short", options.shortVersion, "Print the version number(s) only, with no additional output")
58 cmd.PersistentFlags().BoolVar(&options.onlyClientVersion, "client", options.onlyClientVersion, "Print the client version only")
59 cmd.PersistentFlags().BoolVar(&options.proxy, "proxy", options.proxy, "Print data-plane versions")
60 cmd.PersistentFlags().StringVarP(&options.namespace, "namespace", "n", options.namespace, "Namespace to use for --proxy versions (default: all namespaces)")
61
62 return cmd
63 }
64
65 func configureAndRunVersion(
66 k8sAPI *k8s.KubernetesAPI,
67 options *versionOptions,
68 stdout io.Writer,
69 ) {
70 clientVersion := version.Version
71 if options.shortVersion {
72 fmt.Fprintln(stdout, clientVersion)
73 } else {
74 fmt.Fprintf(stdout, "Client version: %s\n", clientVersion)
75 }
76
77 if !options.onlyClientVersion {
78 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
79 defer cancel()
80 serverVersion, err := healthcheck.GetServerVersion(ctx, controlPlaneNamespace, k8sAPI)
81 if err != nil {
82 serverVersion = defaultVersionString
83 }
84
85 if options.shortVersion {
86 fmt.Fprintln(stdout, serverVersion)
87 } else {
88 fmt.Fprintf(stdout, "Server version: %s\n", serverVersion)
89 }
90
91 if options.proxy {
92 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
93 defer cancel()
94 selector := fmt.Sprintf("%s=%s", k8s.ControllerNSLabel, controlPlaneNamespace)
95 podList, err := k8sAPI.CoreV1().Pods(options.namespace).List(ctx, metav1.ListOptions{LabelSelector: selector})
96 if err != nil {
97 fmt.Fprintln(stdout, "Proxy versions: unavailable")
98 } else {
99 counts := make(map[string]int)
100 for _, pod := range podList.Items {
101 counts[k8s.GetProxyVersion(pod)]++
102 }
103 if len(counts) == 0 {
104 fmt.Fprintln(stdout, "Proxy versions: unavailable")
105 } else {
106 fmt.Fprintln(stdout, "Proxy versions:")
107 for version, count := range counts {
108 fmt.Fprintf(stdout, "\t%s (%d pods)\n", version, count)
109 }
110 }
111 }
112 }
113 }
114 }
115
View as plain text