istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/dashboard/dashboard.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package dashboard
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"os/exec"
    24  	"os/signal"
    25  	"runtime"
    26  
    27  	"github.com/spf13/cobra"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  
    30  	"istio.io/istio/istioctl/pkg/cli"
    31  	"istio.io/istio/istioctl/pkg/clioptions"
    32  	"istio.io/istio/istioctl/pkg/util"
    33  	"istio.io/istio/pkg/kube"
    34  	"istio.io/istio/pkg/log"
    35  )
    36  
    37  var (
    38  	listenPort     = 0
    39  	controlZport   = 0
    40  	promPort       = 0
    41  	grafanaPort    = 0
    42  	kialiPort      = 0
    43  	jaegerPort     = 0
    44  	zipkinPort     = 0
    45  	skywalkingPort = 0
    46  
    47  	bindAddress = ""
    48  
    49  	// open browser or not, default is true
    50  	browser = true
    51  
    52  	// label selector
    53  	labelSelector = ""
    54  
    55  	proxyAdminPort int
    56  )
    57  
    58  const (
    59  	defaultPrometheusPort = 9090
    60  	defaultGrafanaPort    = 3000
    61  	defaultKialiPort      = 20001
    62  	defaultJaegerPort     = 16686
    63  	defaultZipkinPort     = 9411
    64  	defaultSkywalkingPort = 8080
    65  )
    66  
    67  // port-forward to Istio System Prometheus; open browser
    68  func promDashCmd(ctx cli.Context) *cobra.Command {
    69  	var opts clioptions.ControlPlaneOptions
    70  	cmd := &cobra.Command{
    71  		Use:   "prometheus",
    72  		Short: "Open Prometheus web UI",
    73  		Long:  `Open Istio's Prometheus dashboard`,
    74  		Example: `  istioctl dashboard prometheus
    75  
    76    # with short syntax
    77    istioctl dash prometheus
    78    istioctl d prometheus`,
    79  		RunE: func(cmd *cobra.Command, args []string) error {
    80  			client, err := ctx.CLIClientWithRevision(opts.Revision)
    81  			if err != nil {
    82  				return fmt.Errorf("failed to create k8s client: %v", err)
    83  			}
    84  
    85  			name, namespace, err := inferPodMeta(ctx, client, "app.kubernetes.io/name=prometheus")
    86  			if err != nil {
    87  				return err
    88  			}
    89  			return portForward(name, namespace, "Prometheus",
    90  				"http://%s", bindAddress, promPort, client, cmd.OutOrStdout(), browser)
    91  		},
    92  	}
    93  	return cmd
    94  }
    95  
    96  // port-forward to Istio System Grafana; open browser
    97  func grafanaDashCmd(ctx cli.Context) *cobra.Command {
    98  	var opts clioptions.ControlPlaneOptions
    99  	cmd := &cobra.Command{
   100  		Use:   "grafana",
   101  		Short: "Open Grafana web UI",
   102  		Long:  `Open Istio's Grafana dashboard`,
   103  		Example: `  istioctl dashboard grafana
   104  
   105    # with short syntax
   106    istioctl dash grafana
   107    istioctl d grafana`,
   108  		RunE: func(cmd *cobra.Command, args []string) error {
   109  			client, err := ctx.CLIClientWithRevision(opts.Revision)
   110  			if err != nil {
   111  				return fmt.Errorf("failed to create k8s client: %v", err)
   112  			}
   113  
   114  			name, namespace, err := inferPodMeta(ctx, client, "app.kubernetes.io/name=grafana")
   115  			if err != nil {
   116  				return err
   117  			}
   118  			return portForward(name, namespace, "Grafana",
   119  				"http://%s", bindAddress, grafanaPort, client, cmd.OutOrStdout(), browser)
   120  		},
   121  	}
   122  
   123  	return cmd
   124  }
   125  
   126  // port-forward to Istio System Kiali; open browser
   127  func kialiDashCmd(ctx cli.Context) *cobra.Command {
   128  	var opts clioptions.ControlPlaneOptions
   129  	cmd := &cobra.Command{
   130  		Use:   "kiali",
   131  		Short: "Open Kiali web UI",
   132  		Long:  `Open Istio's Kiali dashboard`,
   133  		Example: `  istioctl dashboard kiali
   134  
   135    # with short syntax
   136    istioctl dash kiali
   137    istioctl d kiali`,
   138  		RunE: func(cmd *cobra.Command, args []string) error {
   139  			client, err := ctx.CLIClientWithRevision(opts.Revision)
   140  			if err != nil {
   141  				return fmt.Errorf("failed to create k8s client: %v", err)
   142  			}
   143  
   144  			name, namespace, err := inferPodMeta(ctx, client, "app=kiali")
   145  			if err != nil {
   146  				return err
   147  			}
   148  			return portForward(name, namespace, "Kiali",
   149  				"http://%s/kiali", bindAddress, kialiPort, client, cmd.OutOrStdout(), browser)
   150  		},
   151  	}
   152  
   153  	return cmd
   154  }
   155  
   156  // port-forward to Istio System Jaeger; open browser
   157  func jaegerDashCmd(ctx cli.Context) *cobra.Command {
   158  	var opts clioptions.ControlPlaneOptions
   159  	cmd := &cobra.Command{
   160  		Use:   "jaeger",
   161  		Short: "Open Jaeger web UI",
   162  		Long:  `Open Istio's Jaeger dashboard`,
   163  		Example: `  istioctl dashboard jaeger
   164  
   165    # with short syntax
   166    istioctl dash jaeger
   167    istioctl d jaeger`,
   168  		RunE: func(cmd *cobra.Command, args []string) error {
   169  			client, err := ctx.CLIClientWithRevision(opts.Revision)
   170  			if err != nil {
   171  				return fmt.Errorf("failed to create k8s client: %v", err)
   172  			}
   173  
   174  			name, namespace, err := inferPodMeta(ctx, client, "app=jaeger")
   175  			if err != nil {
   176  				return err
   177  			}
   178  			return portForward(name, namespace, "Jaeger",
   179  				"http://%s", bindAddress, jaegerPort, client, cmd.OutOrStdout(), browser)
   180  		},
   181  	}
   182  
   183  	return cmd
   184  }
   185  
   186  // port-forward to Istio System Zipkin; open browser
   187  func zipkinDashCmd(ctx cli.Context) *cobra.Command {
   188  	var opts clioptions.ControlPlaneOptions
   189  	cmd := &cobra.Command{
   190  		Use:   "zipkin",
   191  		Short: "Open Zipkin web UI",
   192  		Long:  `Open Istio's Zipkin dashboard`,
   193  		Example: `  istioctl dashboard zipkin
   194  
   195    # with short syntax
   196    istioctl dash zipkin
   197    istioctl d zipkin`,
   198  		RunE: func(cmd *cobra.Command, args []string) error {
   199  			client, err := ctx.CLIClientWithRevision(opts.Revision)
   200  			if err != nil {
   201  				return fmt.Errorf("failed to create k8s client: %v", err)
   202  			}
   203  
   204  			name, namespace, err := inferPodMeta(ctx, client, "app=zipkin")
   205  			if err != nil {
   206  				return err
   207  			}
   208  			return portForward(name, namespace, "Zipkin",
   209  				"http://%s", bindAddress, zipkinPort, client, cmd.OutOrStdout(), browser)
   210  		},
   211  	}
   212  
   213  	return cmd
   214  }
   215  
   216  type CreateProxyDashCmdConfig struct {
   217  	CommandUsage   string
   218  	CommandShort   string
   219  	CommandLong    string
   220  	CommandExample string
   221  }
   222  
   223  func createDashCmd(ctx cli.Context, config CreateProxyDashCmdConfig) *cobra.Command {
   224  	cmd := &cobra.Command{
   225  		Use:     config.CommandUsage,
   226  		Short:   config.CommandShort,
   227  		Long:    config.CommandLong,
   228  		Example: config.CommandExample,
   229  		RunE: func(c *cobra.Command, args []string) error {
   230  			kubeClient, err := ctx.CLIClient()
   231  			if err != nil {
   232  				return fmt.Errorf("failed to create k8s client: %v", err)
   233  			}
   234  			if labelSelector == "" && len(args) < 1 {
   235  				c.Println(c.UsageString())
   236  				return fmt.Errorf("specify a pod or --selector")
   237  			}
   238  
   239  			if labelSelector != "" && len(args) > 0 {
   240  				c.Println(c.UsageString())
   241  				return fmt.Errorf("name cannot be provided when a selector is specified")
   242  			}
   243  
   244  			if err != nil {
   245  				return fmt.Errorf("failed to create k8s client: %v", err)
   246  			}
   247  
   248  			var podName, ns string
   249  			if labelSelector != "" {
   250  				pl, err := kubeClient.PodsForSelector(context.TODO(), ctx.NamespaceOrDefault(ctx.Namespace()), labelSelector)
   251  				if err != nil {
   252  					return fmt.Errorf("not able to locate pod with selector %s: %v", labelSelector, err)
   253  				}
   254  
   255  				if len(pl.Items) < 1 {
   256  					return errors.New("no pods found")
   257  				}
   258  
   259  				if len(pl.Items) > 1 {
   260  					log.Warnf("more than 1 pods fits selector: %s; will use pod: %s", labelSelector, pl.Items[0].Name)
   261  				}
   262  
   263  				// only use the first pod in the list
   264  				podName = pl.Items[0].Name
   265  				ns = pl.Items[0].Namespace
   266  			} else {
   267  				podName, ns, err = ctx.InferPodInfoFromTypedResource(args[0], ctx.NamespaceOrDefault(ctx.Namespace()))
   268  				if err != nil {
   269  					return err
   270  				}
   271  			}
   272  
   273  			return portForward(podName, ns, fmt.Sprintf("Envoy sidecar %s", podName),
   274  				"http://%s", bindAddress, proxyAdminPort, kubeClient, c.OutOrStdout(), browser)
   275  		},
   276  	}
   277  
   278  	return cmd
   279  }
   280  
   281  // port-forward to sidecar Envoy admin port; open browser
   282  func envoyDashCmd(ctx cli.Context) *cobra.Command {
   283  	return createDashCmd(ctx, CreateProxyDashCmdConfig{
   284  		CommandUsage: "envoy [<type>/]<name>[.<namespace>]",
   285  		CommandShort: "Open Envoy admin web UI",
   286  		CommandLong:  `Open the Envoy admin dashboard for a sidecar`,
   287  		CommandExample: `  # Open Envoy dashboard for the productpage-123-456.default pod
   288    istioctl dashboard envoy productpage-123-456.default
   289  
   290    # Open Envoy dashboard for one pod under a deployment
   291    istioctl dashboard envoy deployment/productpage-v1
   292  
   293    # with short syntax
   294    istioctl dash envoy productpage-123-456.default
   295    istioctl d envoy productpage-123-456.default
   296  `,
   297  	})
   298  }
   299  
   300  func proxyDashCmd(ctx cli.Context) *cobra.Command {
   301  	return createDashCmd(ctx, CreateProxyDashCmdConfig{
   302  		CommandUsage: "proxy [<type>/]<name>[.<namespace>]",
   303  		CommandShort: "Open admin web UI for a proxy",
   304  		CommandLong:  `Open the admin dashboard for a proxy, like envoy and ztunnel pods`,
   305  		CommandExample: `  # Open envoy admin dashboard for the productpage-123-456.default pod
   306    istioctl dashboard proxy productpage-123-456.default
   307  
   308    # Open envoy admin dashboard for one pod under a deployment
   309    istioctl dashboard proxy deployment/productpage-v1
   310  
   311    # Open dashboard for the ztunnel-bwh89.istio-system pod
   312    istioctl dashboard proxy ztunnel-bwh89.istio-system
   313  
   314    # Open dashboard for a waypoint pod
   315    istioctl dashboard proxy namespace-istio-waypoint-869b56b69c-7khz4
   316  
   317    # with short syntax
   318    istioctl dash proxy ztunnel-bwh89.istio-system
   319    istioctl d proxy ztunnel-bwh89.istio-system
   320  `,
   321  	})
   322  }
   323  
   324  // port-forward to sidecar ControlZ port; open browser
   325  func controlZDashCmd(ctx cli.Context) *cobra.Command {
   326  	var opts clioptions.ControlPlaneOptions
   327  	cmd := &cobra.Command{
   328  		Use:   "controlz [<type>/]<name>[.<namespace>]",
   329  		Short: "Open ControlZ web UI",
   330  		Long:  `Open the ControlZ web UI for a pod in the Istio control plane`,
   331  		Example: `  # Open ControlZ web UI for the istiod-123-456.istio-system pod
   332    istioctl dashboard controlz istiod-123-456.istio-system
   333  
   334    # Open ControlZ web UI for the istiod-56dd66799-jfdvs pod in a custom namespace
   335    istioctl dashboard controlz istiod-123-456 -n custom-ns
   336  
   337    # Open ControlZ web UI for any Istiod pod
   338    istioctl dashboard controlz deployment/istiod.istio-system
   339  
   340    # with short syntax
   341    istioctl dash controlz pilot-123-456.istio-system
   342    istioctl d controlz pilot-123-456.istio-system
   343  `,
   344  		RunE: func(c *cobra.Command, args []string) error {
   345  			if labelSelector == "" && len(args) < 1 {
   346  				c.Println(c.UsageString())
   347  				return fmt.Errorf("specify a pod or --selector")
   348  			}
   349  
   350  			if labelSelector != "" && len(args) > 0 {
   351  				c.Println(c.UsageString())
   352  				return fmt.Errorf("name cannot be provided when a selector is specified")
   353  			}
   354  
   355  			client, err := ctx.CLIClientWithRevision(opts.Revision)
   356  			if err != nil {
   357  				return fmt.Errorf("failed to create k8s client: %v", err)
   358  			}
   359  
   360  			var podName, ns string
   361  			if labelSelector != "" {
   362  				pl, err := client.PodsForSelector(context.TODO(), ctx.NamespaceOrDefault(ctx.IstioNamespace()), labelSelector)
   363  				if err != nil {
   364  					return fmt.Errorf("not able to locate pod with selector %s: %v", labelSelector, err)
   365  				}
   366  
   367  				if len(pl.Items) < 1 {
   368  					return errors.New("no pods found")
   369  				}
   370  
   371  				if len(pl.Items) > 1 {
   372  					log.Warnf("more than 1 pods fits selector: %s; will use pod: %s", labelSelector, pl.Items[0].Name)
   373  				}
   374  
   375  				// only use the first pod in the list
   376  				podName = pl.Items[0].Name
   377  				ns = pl.Items[0].Namespace
   378  			} else {
   379  				podName, ns, err = ctx.InferPodInfoFromTypedResource(args[0], ctx.IstioNamespace())
   380  				if err != nil {
   381  					return err
   382  				}
   383  			}
   384  
   385  			return portForward(podName, ns, fmt.Sprintf("ControlZ %s", podName),
   386  				"http://%s", bindAddress, controlZport, client, c.OutOrStdout(), browser)
   387  		},
   388  	}
   389  
   390  	return cmd
   391  }
   392  
   393  // istioDebugDashCmd port-forwards to istio monitoring port; open browser to the debug page
   394  func istioDebugDashCmd(ctx cli.Context) *cobra.Command {
   395  	var opts clioptions.ControlPlaneOptions
   396  	cmd := &cobra.Command{
   397  		Use:   "istiod-debug [<type>/]<name>[.<namespace>]",
   398  		Short: "Open Istio debug web UI",
   399  		Long:  `Open the debug web UI for a Istio control plane pod`,
   400  		Example: `  # Open Istio debug web UI for the istiod-123-456.istio-system pod
   401    istioctl dashboard istiod-debug istiod-123-456.istio-system
   402  
   403    # Open Istio debug web UI for the istiod-56dd66799-jfdvs pod in a custom namespace
   404    istioctl dashboard istiod-debug istiod-123-456 -n custom-ns
   405  
   406    # Open Istio debug web UI for any Istiod pod
   407    istioctl dashboard istiod-debug deployment/istiod.istio-system
   408  
   409    # with short syntax
   410    istioctl dash istiod-debug pilot-123-456.istio-system
   411    istioctl d istiod-debug pilot-123-456.istio-system
   412  `,
   413  		RunE: func(c *cobra.Command, args []string) error {
   414  			if labelSelector == "" && len(args) < 1 {
   415  				c.Println(c.UsageString())
   416  				return fmt.Errorf("specify a pod or --selector")
   417  			}
   418  
   419  			if labelSelector != "" && len(args) > 0 {
   420  				c.Println(c.UsageString())
   421  				return fmt.Errorf("name cannot be provided when a selector is specified")
   422  			}
   423  
   424  			client, err := ctx.CLIClientWithRevision(opts.Revision)
   425  			if err != nil {
   426  				return fmt.Errorf("failed to create k8s client: %v", err)
   427  			}
   428  
   429  			var podName, ns string
   430  			if labelSelector != "" {
   431  				pl, err := client.PodsForSelector(context.TODO(), ctx.NamespaceOrDefault(ctx.IstioNamespace()), labelSelector)
   432  				if err != nil {
   433  					return fmt.Errorf("not able to locate pod with selector %s: %v", labelSelector, err)
   434  				}
   435  
   436  				if len(pl.Items) < 1 {
   437  					return errors.New("no pods found")
   438  				}
   439  
   440  				if len(pl.Items) > 1 {
   441  					log.Warnf("more than 1 pods fits selector: %s; will use pod: %s", labelSelector, pl.Items[0].Name)
   442  				}
   443  
   444  				// only use the first pod in the list
   445  				podName = pl.Items[0].Name
   446  				ns = pl.Items[0].Namespace
   447  			} else {
   448  				podName, ns, err = ctx.InferPodInfoFromTypedResource(args[0], ctx.IstioNamespace())
   449  				if err != nil {
   450  					return err
   451  				}
   452  			}
   453  			port := inferMonitoringPort(client, podName, ns)
   454  			return portForward(podName, ns, fmt.Sprintf("Istio debug %s", podName),
   455  				"http://%s/debug", bindAddress, port, client, c.OutOrStdout(), browser)
   456  		},
   457  	}
   458  	return cmd
   459  }
   460  
   461  func inferMonitoringPort(client kube.Client, name, ns string) int {
   462  	port := 15014
   463  	pod, err := client.Kube().CoreV1().Pods(ns).Get(context.Background(), name, metav1.GetOptions{})
   464  	if err != nil {
   465  		return port
   466  	}
   467  	return kube.FindIstiodMonitoringPort(pod)
   468  }
   469  
   470  // port-forward to SkyWalking UI on istio-system
   471  func skywalkingDashCmd(ctx cli.Context) *cobra.Command {
   472  	var opts clioptions.ControlPlaneOptions
   473  	cmd := &cobra.Command{
   474  		Use:   "skywalking",
   475  		Short: "Open SkyWalking UI",
   476  		Long:  "Open the Istio dashboard in the SkyWalking UI",
   477  		Example: `  istioctl dashboard skywalking
   478  
   479    # with short syntax
   480    istioctl dash skywalking
   481    istioctl d skywalking`,
   482  		RunE: func(cmd *cobra.Command, args []string) error {
   483  			client, err := ctx.CLIClientWithRevision(opts.Revision)
   484  			if err != nil {
   485  				return fmt.Errorf("failed to create k8s client: %v", err)
   486  			}
   487  
   488  			name, namespace, err := inferPodMeta(ctx, client, "app=skywalking-ui")
   489  			if err != nil {
   490  				return err
   491  			}
   492  			return portForward(name, namespace, "SkyWalking",
   493  				"http://%s", bindAddress, skywalkingPort, client, cmd.OutOrStdout(), browser)
   494  		},
   495  	}
   496  
   497  	return cmd
   498  }
   499  
   500  // portForward first tries to forward localhost:remotePort to podName:remotePort, falls back to dynamic local port
   501  func portForward(podName, namespace, flavor, urlFormat, localAddress string, remotePort int,
   502  	client kube.CLIClient, writer io.Writer, browser bool,
   503  ) error {
   504  	// port preference:
   505  	// - If --listenPort is specified, use it
   506  	// - without --listenPort, prefer the remotePort but fall back to a random port
   507  	var portPrefs []int
   508  	if listenPort != 0 {
   509  		portPrefs = []int{listenPort}
   510  	} else {
   511  		portPrefs = []int{remotePort, 0}
   512  	}
   513  
   514  	var err error
   515  	for _, localPort := range portPrefs {
   516  		var fw kube.PortForwarder
   517  		fw, err = client.NewPortForwarder(podName, namespace, localAddress, localPort, remotePort)
   518  		if err != nil {
   519  			return fmt.Errorf("could not build port forwarder for %s: %v", flavor, err)
   520  		}
   521  
   522  		if err = fw.Start(); err != nil {
   523  			fw.Close()
   524  			// Try the next port
   525  			continue
   526  		}
   527  
   528  		// Close the port forwarder when the command is terminated.
   529  		ClosePortForwarderOnInterrupt(fw)
   530  
   531  		log.Debugf(fmt.Sprintf("port-forward to %s pod ready", flavor))
   532  		openBrowser(fmt.Sprintf(urlFormat, fw.Address()), writer, browser)
   533  
   534  		// Wait for stop
   535  		fw.WaitForStop()
   536  
   537  		return nil
   538  	}
   539  
   540  	return fmt.Errorf("failure running port forward process: %v", err)
   541  }
   542  
   543  func ClosePortForwarderOnInterrupt(fw kube.PortForwarder) {
   544  	go func() {
   545  		signals := make(chan os.Signal, 1)
   546  		signal.Notify(signals, os.Interrupt)
   547  		defer signal.Stop(signals)
   548  		<-signals
   549  		fw.Close()
   550  	}()
   551  }
   552  
   553  func openBrowser(url string, writer io.Writer, browser bool) {
   554  	var err error
   555  
   556  	fmt.Fprintf(writer, "%s\n", url)
   557  
   558  	if !browser {
   559  		fmt.Fprint(writer, "skipping opening a browser")
   560  		return
   561  	}
   562  
   563  	switch runtime.GOOS {
   564  	case "linux":
   565  		err = exec.Command("xdg-open", url).Start()
   566  	case "windows":
   567  		err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
   568  	case "darwin":
   569  		err = exec.Command("open", url).Start()
   570  	default:
   571  		fmt.Fprintf(writer, "Unsupported platform %q; open %s in your browser.\n", runtime.GOOS, url)
   572  	}
   573  
   574  	if err != nil {
   575  		fmt.Fprintf(writer, "Failed to open browser; open %s in your browser.\n", url)
   576  	}
   577  }
   578  
   579  func Dashboard(cliContext cli.Context) *cobra.Command {
   580  	dashboardCmd := &cobra.Command{
   581  		Use:     "dashboard",
   582  		Aliases: []string{"dash", "d"},
   583  		Short:   "Access to Istio web UIs",
   584  		Args: func(cmd *cobra.Command, args []string) error {
   585  			if len(args) != 0 {
   586  				return fmt.Errorf("unknown dashboard %q", args[0])
   587  			}
   588  			return nil
   589  		},
   590  		RunE: func(cmd *cobra.Command, args []string) error {
   591  			cmd.HelpFunc()(cmd, args)
   592  			return nil
   593  		},
   594  	}
   595  
   596  	dashboardCmd.PersistentFlags().IntVarP(&listenPort, "port", "p", 0, "Local port to listen to")
   597  	dashboardCmd.PersistentFlags().StringVar(&bindAddress, "address", "localhost",
   598  		"Address to listen on. Only accepts IP address or localhost as a value. "+
   599  			"When localhost is supplied, istioctl will try to bind on both 127.0.0.1 and ::1 "+
   600  			"and will fail if neither of these address are available to bind.")
   601  	dashboardCmd.PersistentFlags().BoolVar(&browser, "browser", true,
   602  		"When --browser is supplied as false, istioctl dashboard will not open the browser. "+
   603  			"Default is true which means istioctl dashboard will always open a browser to view the dashboard.")
   604  
   605  	kiali := kialiDashCmd(cliContext)
   606  	kiali.PersistentFlags().IntVar(&kialiPort, "ui-port", defaultKialiPort, "The component dashboard UI port.")
   607  	dashboardCmd.AddCommand(kiali)
   608  
   609  	prom := promDashCmd(cliContext)
   610  	prom.PersistentFlags().IntVar(&promPort, "ui-port", defaultPrometheusPort, "The component dashboard UI port.")
   611  	dashboardCmd.AddCommand(prom)
   612  
   613  	graf := grafanaDashCmd(cliContext)
   614  	graf.PersistentFlags().IntVar(&grafanaPort, "ui-port", defaultGrafanaPort, "The component dashboard UI port.")
   615  	dashboardCmd.AddCommand(graf)
   616  
   617  	jaeger := jaegerDashCmd(cliContext)
   618  	jaeger.PersistentFlags().IntVar(&jaegerPort, "ui-port", defaultJaegerPort, "The component dashboard UI port.")
   619  	dashboardCmd.AddCommand(jaeger)
   620  
   621  	zipkin := zipkinDashCmd(cliContext)
   622  	zipkin.PersistentFlags().IntVar(&zipkinPort, "ui-port", defaultZipkinPort, "The component dashboard UI port.")
   623  	dashboardCmd.AddCommand(zipkin)
   624  
   625  	skywalking := skywalkingDashCmd(cliContext)
   626  	skywalking.PersistentFlags().IntVar(&skywalkingPort, "ui-port", defaultSkywalkingPort, "The component dashboard UI port.")
   627  	dashboardCmd.AddCommand(skywalking)
   628  
   629  	envoy := envoyDashCmd(cliContext)
   630  	envoy.Long += fmt.Sprintf("\n\n%s\n", "Note: envoy command is deprecated and can be replaced with proxy command, "+
   631  		"e.g. `istioctl dashboard proxy --help`")
   632  	envoy.PersistentFlags().StringVarP(&labelSelector, "selector", "l", "", "Label selector")
   633  	envoy.PersistentFlags().IntVar(&proxyAdminPort, "ui-port", util.DefaultProxyAdminPort, "The component dashboard UI port.")
   634  	dashboardCmd.AddCommand(envoy)
   635  
   636  	proxy := proxyDashCmd(cliContext)
   637  	proxy.PersistentFlags().StringVarP(&labelSelector, "selector", "l", "", "Label selector")
   638  	proxy.PersistentFlags().IntVar(&proxyAdminPort, "ui-port", util.DefaultProxyAdminPort, "The component dashboard UI port.")
   639  	dashboardCmd.AddCommand(proxy)
   640  
   641  	controlz := controlZDashCmd(cliContext)
   642  	controlz.PersistentFlags().IntVar(&controlZport, "ctrlz_port", 9876, "ControlZ port")
   643  	controlz.PersistentFlags().StringVarP(&labelSelector, "selector", "l", "", "Label selector")
   644  	dashboardCmd.AddCommand(controlz)
   645  
   646  	istioDebug := istioDebugDashCmd(cliContext)
   647  	istioDebug.PersistentFlags().StringVarP(&labelSelector, "selector", "l", "", "Label selector")
   648  	dashboardCmd.AddCommand(istioDebug)
   649  
   650  	return dashboardCmd
   651  }
   652  
   653  func inferPodMeta(ctx cli.Context, client kube.CLIClient, labelSelector string) (name, namespace string, err error) {
   654  	for _, ns := range []string{ctx.IstioNamespace(), ctx.NamespaceOrDefault(ctx.Namespace())} {
   655  		pl, err := client.PodsForSelector(context.TODO(), ns, labelSelector)
   656  		if err != nil {
   657  			continue
   658  		}
   659  		if len(pl.Items) > 0 {
   660  			return pl.Items[0].Name, pl.Items[0].Namespace, nil
   661  		}
   662  	}
   663  	return "", "", fmt.Errorf("no pods found with selector %s", labelSelector)
   664  }