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 }