istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/proxyconfig/proxyconfig.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 proxyconfig 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "io" 22 "os" 23 "regexp" 24 "strings" 25 26 "github.com/hashicorp/go-multierror" 27 "github.com/spf13/cobra" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "sigs.k8s.io/yaml" 30 31 "istio.io/istio/istioctl/pkg/cli" 32 "istio.io/istio/istioctl/pkg/completion" 33 "istio.io/istio/istioctl/pkg/kubeinject" 34 istioctlutil "istio.io/istio/istioctl/pkg/util" 35 sdscompare "istio.io/istio/istioctl/pkg/writer/compare/sds" 36 "istio.io/istio/istioctl/pkg/writer/envoy/clusters" 37 "istio.io/istio/istioctl/pkg/writer/envoy/configdump" 38 "istio.io/istio/operator/pkg/util" 39 "istio.io/istio/pilot/pkg/model" 40 "istio.io/istio/pkg/config/host" 41 "istio.io/istio/pkg/kube" 42 "istio.io/istio/pkg/log" 43 ) 44 45 const ( 46 jsonOutput = "json" 47 yamlOutput = "yaml" 48 summaryOutput = "short" 49 prometheusOutput = "prom" 50 prometheusMergedOutput = "prom-merged" 51 52 defaultProxyAdminPort = 15000 53 ) 54 55 var ( 56 fqdn, direction, subset string 57 port int 58 verboseProxyConfig bool 59 waypointProxyConfig bool 60 61 address, listenerType, statsType string 62 63 routeName string 64 65 clusterName, status string 66 67 // output format (json, yaml or short) 68 outputFormat string 69 70 proxyAdminPort int 71 72 configDumpFile string 73 74 labelSelector = "" 75 loggerName string 76 ) 77 78 // Level is an enumeration of all supported log levels. 79 type Level int 80 81 const ( 82 defaultLoggerName = "level" 83 defaultEnvoyOutputLevel = WarningLevel 84 ) 85 86 const ( 87 // OffLevel disables logging 88 OffLevel Level = iota 89 // CriticalLevel enables critical level logging 90 CriticalLevel 91 // ErrorLevel enables error level logging 92 ErrorLevel 93 // WarningLevel enables warning level logging 94 WarningLevel 95 // InfoLevel enables info level logging 96 InfoLevel 97 // DebugLevel enables debug level logging 98 DebugLevel 99 // TraceLevel enables trace level logging 100 TraceLevel 101 ) 102 103 var levelToString = map[Level]string{ 104 TraceLevel: "trace", 105 DebugLevel: "debug", 106 InfoLevel: "info", 107 WarningLevel: "warning", 108 ErrorLevel: "error", 109 CriticalLevel: "critical", 110 OffLevel: "off", 111 } 112 113 var stringToLevel = map[string]Level{ 114 "trace": TraceLevel, 115 "debug": DebugLevel, 116 "info": InfoLevel, 117 "warning": WarningLevel, 118 "warn": WarningLevel, 119 "error": ErrorLevel, 120 "critical": CriticalLevel, 121 "off": OffLevel, 122 } 123 124 var ( 125 loggerLevelString = "" 126 reset = false 127 ) 128 129 func extractConfigDump(kubeClient kube.CLIClient, podName, podNamespace string, eds bool) ([]byte, error) { 130 path := "config_dump" 131 if eds { 132 path += "?include_eds=true" 133 } 134 debug, err := kubeClient.EnvoyDoWithPort(context.TODO(), podName, podNamespace, "GET", path, proxyAdminPort) 135 if err != nil { 136 return nil, fmt.Errorf("failed to execute command on %s.%s sidecar: %v", podName, podNamespace, err) 137 } 138 return debug, err 139 } 140 141 func setupPodConfigdumpWriter(kubeClient kube.CLIClient, podName, podNamespace string, includeEds bool, out io.Writer) (*configdump.ConfigWriter, error) { 142 debug, err := extractConfigDump(kubeClient, podName, podNamespace, includeEds) 143 if err != nil { 144 return nil, err 145 } 146 return setupConfigdumpEnvoyConfigWriter(debug, out) 147 } 148 149 func readFile(filename string) ([]byte, error) { 150 file := os.Stdin 151 if filename != "-" { 152 var err error 153 file, err = os.Open(filename) 154 if err != nil { 155 return nil, err 156 } 157 } 158 defer func() { 159 if err := file.Close(); err != nil { 160 log.Errorf("failed to close %s: %s", filename, err) 161 } 162 }() 163 return io.ReadAll(file) 164 } 165 166 func setupFileConfigdumpWriter(filename string, out io.Writer) (*configdump.ConfigWriter, error) { 167 data, err := readFile(filename) 168 if err != nil { 169 return nil, err 170 } 171 return setupConfigdumpEnvoyConfigWriter(data, out) 172 } 173 174 func setupConfigdumpEnvoyConfigWriter(debug []byte, out io.Writer) (*configdump.ConfigWriter, error) { 175 cw := &configdump.ConfigWriter{Stdout: out} 176 err := cw.Prime(debug) 177 if err != nil { 178 return nil, err 179 } 180 return cw, nil 181 } 182 183 func setupEnvoyClusterStatsConfig(kubeClient kube.CLIClient, podName, podNamespace string, outputFormat string) (string, error) { 184 path := "clusters" 185 if outputFormat == jsonOutput || outputFormat == yamlOutput { 186 // for yaml output we will convert the json to yaml when printed 187 path += "?format=json" 188 } 189 result, err := kubeClient.EnvoyDoWithPort(context.TODO(), podName, podNamespace, "GET", path, proxyAdminPort) 190 if err != nil { 191 return "", fmt.Errorf("failed to execute command on Envoy: %v", err) 192 } 193 return string(result), nil 194 } 195 196 func setupEnvoyServerStatsConfig(kubeClient kube.CLIClient, podName, podNamespace string, outputFormat string) (string, error) { 197 path := "stats" 198 if outputFormat == jsonOutput || outputFormat == yamlOutput { 199 // for yaml output we will convert the json to yaml when printed 200 path += "?format=json" 201 } else if outputFormat == prometheusOutput { 202 path += "/prometheus" 203 } else if outputFormat == prometheusMergedOutput { 204 pod, err := kubeClient.Kube().CoreV1().Pods(podNamespace).Get(context.Background(), podName, metav1.GetOptions{}) 205 if err != nil { 206 return "", fmt.Errorf("failed to retrieve Pod %s/%s: %v", podNamespace, podName, err) 207 } 208 209 promPath, promPort, err := util.PrometheusPathAndPort(pod) 210 if err != nil { 211 return "", fmt.Errorf("failed to retrieve prometheus path and port from Pod %s/%s: %v", podNamespace, podName, err) 212 } 213 path = promPath 214 port = promPort 215 } 216 217 result, err := kubeClient.EnvoyDoWithPort(context.Background(), podName, podNamespace, "GET", path, proxyAdminPort) 218 if err != nil { 219 return "", fmt.Errorf("failed to execute command on Envoy: %v", err) 220 } 221 return string(result), nil 222 } 223 224 func setupEnvoyLogConfig(kubeClient kube.CLIClient, param, podName, podNamespace string) (string, error) { 225 path := "logging" 226 if param != "" { 227 path = path + "?" + param 228 } 229 result, err := kubeClient.EnvoyDoWithPort(context.TODO(), podName, podNamespace, "POST", path, proxyAdminPort) 230 if err != nil { 231 return "", fmt.Errorf("failed to execute command on Envoy: %v", err) 232 } 233 return string(result), nil 234 } 235 236 func getLogLevelFromConfigMap(ctx cli.Context) (string, error) { 237 valuesConfig, err := kubeinject.GetValuesFromConfigMap(ctx, "") 238 if err != nil { 239 return "", err 240 } 241 var values struct { 242 SidecarInjectorWebhook struct { 243 Global struct { 244 Proxy struct { 245 LogLevel string `json:"logLevel"` 246 } `json:"proxy"` 247 } `json:"global"` 248 } `json:"sidecarInjectorWebhook"` 249 } 250 if err := yaml.Unmarshal([]byte(valuesConfig), &values); err != nil { 251 return "", fmt.Errorf("failed to parse values config: %v [%v]", err, valuesConfig) 252 } 253 return values.SidecarInjectorWebhook.Global.Proxy.LogLevel, nil 254 } 255 256 func setupPodClustersWriter(kubeClient kube.CLIClient, podName, podNamespace string, out io.Writer) (*clusters.ConfigWriter, error) { 257 path := "clusters?format=json" 258 debug, err := kubeClient.EnvoyDoWithPort(context.TODO(), podName, podNamespace, "GET", path, proxyAdminPort) 259 if err != nil { 260 return nil, fmt.Errorf("failed to execute command on Envoy: %v", err) 261 } 262 return setupClustersEnvoyConfigWriter(debug, out) 263 } 264 265 func setupFileClustersWriter(filename string, out io.Writer) (*clusters.ConfigWriter, error) { 266 data, err := readFile(filename) 267 if err != nil { 268 return nil, err 269 } 270 return setupClustersEnvoyConfigWriter(data, out) 271 } 272 273 // TODO(fisherxu): migrate this to config dump when implemented in Envoy 274 // Issue to track -> https://github.com/envoyproxy/envoy/issues/3362 275 func setupClustersEnvoyConfigWriter(debug []byte, out io.Writer) (*clusters.ConfigWriter, error) { 276 cw := &clusters.ConfigWriter{Stdout: out} 277 err := cw.Prime(debug) 278 if err != nil { 279 return nil, err 280 } 281 return cw, nil 282 } 283 284 func clusterConfigCmd(ctx cli.Context) *cobra.Command { 285 var podName, podNamespace string 286 287 clusterConfigCmd := &cobra.Command{ 288 Use: "cluster [<type>/]<name>[.<namespace>]", 289 Short: "Retrieves cluster configuration for the Envoy in the specified pod", 290 Long: `Retrieve information about cluster configuration for the Envoy instance in the specified pod.`, 291 Example: ` # Retrieve summary about cluster configuration for a given pod from Envoy. 292 istioctl proxy-config clusters <pod-name[.namespace]> 293 294 # Retrieve cluster summary for clusters with port 9080. 295 istioctl proxy-config clusters <pod-name[.namespace]> --port 9080 296 297 # Retrieve full cluster dump for clusters that are inbound with a FQDN of details.default.svc.cluster.local. 298 istioctl proxy-config clusters <pod-name[.namespace]> --fqdn details.default.svc.cluster.local --direction inbound -o json 299 300 # Retrieve cluster summary without using Kubernetes API 301 ssh <user@hostname> 'curl localhost:15000/config_dump' > envoy-config.json 302 istioctl proxy-config clusters --file envoy-config.json 303 `, 304 Aliases: []string{"clusters", "c"}, 305 Args: func(cmd *cobra.Command, args []string) error { 306 if (len(args) == 1) != (configDumpFile == "") { 307 cmd.Println(cmd.UsageString()) 308 return fmt.Errorf("cluster requires pod name or --file parameter") 309 } 310 return nil 311 }, 312 RunE: func(c *cobra.Command, args []string) error { 313 kubeClient, err := ctx.CLIClient() 314 if err != nil { 315 return err 316 } 317 var configWriter *configdump.ConfigWriter 318 if len(args) == 1 { 319 if podName, podNamespace, err = getPodName(ctx, args[0]); err != nil { 320 return err 321 } 322 configWriter, err = setupPodConfigdumpWriter(kubeClient, podName, podNamespace, false, c.OutOrStdout()) 323 } else { 324 configWriter, err = setupFileConfigdumpWriter(configDumpFile, c.OutOrStdout()) 325 } 326 if err != nil { 327 return err 328 } 329 filter := configdump.ClusterFilter{ 330 FQDN: host.Name(fqdn), 331 Port: port, 332 Subset: subset, 333 Direction: model.TrafficDirection(direction), 334 } 335 switch outputFormat { 336 case summaryOutput: 337 return configWriter.PrintClusterSummary(filter) 338 case jsonOutput, yamlOutput: 339 return configWriter.PrintClusterDump(filter, outputFormat) 340 default: 341 return fmt.Errorf("output format %q not supported", outputFormat) 342 } 343 }, 344 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 345 } 346 347 clusterConfigCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", summaryOutput, "Output format: one of json|yaml|short") 348 clusterConfigCmd.PersistentFlags().StringVar(&fqdn, "fqdn", "", "Filter clusters by substring of Service FQDN field") 349 clusterConfigCmd.PersistentFlags().StringVar(&direction, "direction", "", "Filter clusters by Direction field") 350 clusterConfigCmd.PersistentFlags().StringVar(&subset, "subset", "", "Filter clusters by substring of Subset field") 351 clusterConfigCmd.PersistentFlags().IntVar(&port, "port", 0, "Filter clusters by Port field") 352 clusterConfigCmd.PersistentFlags().StringVarP(&configDumpFile, "file", "f", "", 353 "Envoy config dump JSON file") 354 355 return clusterConfigCmd 356 } 357 358 func allConfigCmd(ctx cli.Context) *cobra.Command { 359 allConfigCmd := &cobra.Command{ 360 Use: "all [<type>/]<name>[.<namespace>]", 361 Short: "Retrieves all configuration for the Envoy in the specified pod", 362 Long: `Retrieve information about all configuration for the Envoy instance in the specified pod.`, 363 Example: ` # Retrieve summary about all configuration for a given pod from Envoy. 364 istioctl proxy-config all <pod-name[.namespace]> 365 366 # Retrieve full cluster dump as JSON 367 istioctl proxy-config all <pod-name[.namespace]> -o json 368 369 # Retrieve full cluster dump with short syntax 370 istioctl pc a <pod-name[.namespace]> 371 372 # Retrieve cluster summary without using Kubernetes API 373 ssh <user@hostname> 'curl localhost:15000/config_dump' > envoy-config.json 374 istioctl proxy-config all --file envoy-config.json 375 `, 376 Aliases: []string{"a"}, 377 Args: func(cmd *cobra.Command, args []string) error { 378 if (len(args) == 1) != (configDumpFile == "") { 379 cmd.Println(cmd.UsageString()) 380 return fmt.Errorf("all requires pod name or --file parameter") 381 } 382 return nil 383 }, 384 RunE: func(c *cobra.Command, args []string) error { 385 kubeClient, err := ctx.CLIClient() 386 if err != nil { 387 return err 388 } 389 switch outputFormat { 390 case jsonOutput, yamlOutput: 391 var dump []byte 392 var err error 393 if len(args) == 1 { 394 podName, podNamespace, err := getPodName(ctx, args[0]) 395 if err != nil { 396 return err 397 } 398 dump, err = extractConfigDump(kubeClient, podName, podNamespace, true) 399 if err != nil { 400 return err 401 } 402 } else { 403 dump, err = readFile(configDumpFile) 404 if err != nil { 405 return err 406 } 407 } 408 if outputFormat == yamlOutput { 409 if dump, err = yaml.JSONToYAML(dump); err != nil { 410 return err 411 } 412 } 413 fmt.Fprintln(c.OutOrStdout(), string(dump)) 414 415 case summaryOutput: 416 var configWriter *configdump.ConfigWriter 417 if len(args) == 1 { 418 podName, podNamespace, err := getPodName(ctx, args[0]) 419 if err != nil { 420 return err 421 } 422 423 configWriter, err = setupPodConfigdumpWriter(kubeClient, podName, podNamespace, true, c.OutOrStdout()) 424 if err != nil { 425 return err 426 } 427 } else { 428 var err error 429 configWriter, err = setupFileConfigdumpWriter(configDumpFile, c.OutOrStdout()) 430 if err != nil { 431 return err 432 } 433 } 434 configdump.SetPrintConfigTypeInSummary(true) 435 sdscompare.SetPrintConfigTypeInSummary(true) 436 return configWriter.PrintFullSummary( 437 configdump.ClusterFilter{ 438 FQDN: host.Name(fqdn), 439 Port: port, 440 Subset: subset, 441 Direction: model.TrafficDirection(direction), 442 }, 443 configdump.ListenerFilter{ 444 Address: address, 445 Port: uint32(port), 446 Type: listenerType, 447 Verbose: verboseProxyConfig, 448 }, 449 configdump.RouteFilter{ 450 Name: routeName, 451 Verbose: verboseProxyConfig, 452 }, 453 configdump.EndpointFilter{ 454 Address: address, 455 Port: uint32(port), 456 Cluster: clusterName, 457 Status: status, 458 }, 459 ) 460 default: 461 return fmt.Errorf("output format %q not supported", outputFormat) 462 } 463 return nil 464 }, 465 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 466 } 467 468 allConfigCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", summaryOutput, "Output format: one of json|yaml|short") 469 allConfigCmd.PersistentFlags().StringVarP(&configDumpFile, "file", "f", "", 470 "Envoy config dump file") 471 allConfigCmd.PersistentFlags().BoolVar(&verboseProxyConfig, "verbose", true, "Output more information") 472 473 // cluster 474 allConfigCmd.PersistentFlags().StringVar(&fqdn, "fqdn", "", "Filter clusters by substring of Service FQDN field") 475 allConfigCmd.PersistentFlags().StringVar(&direction, "direction", "", "Filter clusters by Direction field") 476 allConfigCmd.PersistentFlags().StringVar(&subset, "subset", "", "Filter clusters by substring of Subset field") 477 478 // applies to cluster and route 479 allConfigCmd.PersistentFlags().IntVar(&port, "port", 0, "Filter clusters and listeners by Port field") 480 481 // Listener 482 allConfigCmd.PersistentFlags().StringVar(&address, "address", "", "Filter listeners by address field") 483 allConfigCmd.PersistentFlags().StringVar(&listenerType, "type", "", "Filter listeners by type field") 484 485 // route 486 allConfigCmd.PersistentFlags().StringVar(&routeName, "name", "", "Filter listeners by route name field") 487 488 return allConfigCmd 489 } 490 491 func listenerConfigCmd(ctx cli.Context) *cobra.Command { 492 var podName, podNamespace string 493 494 listenerConfigCmd := &cobra.Command{ 495 Use: "listener [<type>/]<name>[.<namespace>]", 496 Short: "Retrieves listener configuration for the Envoy in the specified pod", 497 Long: `Retrieve information about listener configuration for the Envoy instance in the specified pod.`, 498 Example: ` # Retrieve summary about listener configuration for a given pod from Envoy. 499 istioctl proxy-config listeners <pod-name[.namespace]> 500 501 # Retrieve listener summary for listeners with port 9080. 502 istioctl proxy-config listeners <pod-name[.namespace]> --port 9080 503 504 # Retrieve full listener dump for HTTP listeners with a wildcard address (0.0.0.0). 505 istioctl proxy-config listeners <pod-name[.namespace]> --type HTTP --address 0.0.0.0 -o json 506 507 # Retrieve listener summary without using Kubernetes API 508 ssh <user@hostname> 'curl localhost:15000/config_dump' > envoy-config.json 509 istioctl proxy-config listeners --file envoy-config.json 510 `, 511 Aliases: []string{"listeners", "l"}, 512 Args: func(cmd *cobra.Command, args []string) error { 513 if (len(args) == 1) != (configDumpFile == "") { 514 cmd.Println(cmd.UsageString()) 515 return fmt.Errorf("listener requires pod name or --file parameter") 516 } 517 return nil 518 }, 519 RunE: func(c *cobra.Command, args []string) error { 520 kubeClient, err := ctx.CLIClient() 521 if err != nil { 522 return err 523 } 524 var configWriter *configdump.ConfigWriter 525 if len(args) == 1 { 526 if podName, podNamespace, err = getPodName(ctx, args[0]); err != nil { 527 return err 528 } 529 configWriter, err = setupPodConfigdumpWriter(kubeClient, podName, podNamespace, false, c.OutOrStdout()) 530 } else { 531 configWriter, err = setupFileConfigdumpWriter(configDumpFile, c.OutOrStdout()) 532 } 533 if err != nil { 534 return err 535 } 536 filter := configdump.ListenerFilter{ 537 Address: address, 538 Port: uint32(port), 539 Type: listenerType, 540 Verbose: verboseProxyConfig, 541 } 542 543 if waypointProxyConfig { 544 return configWriter.PrintRemoteListenerSummary() 545 } 546 switch outputFormat { 547 case summaryOutput: 548 return configWriter.PrintListenerSummary(filter) 549 case jsonOutput, yamlOutput: 550 return configWriter.PrintListenerDump(filter, outputFormat) 551 default: 552 return fmt.Errorf("output format %q not supported", outputFormat) 553 } 554 }, 555 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 556 } 557 558 listenerConfigCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", summaryOutput, "Output format: one of json|yaml|short") 559 listenerConfigCmd.PersistentFlags().StringVar(&address, "address", "", "Filter listeners by address field") 560 listenerConfigCmd.PersistentFlags().StringVar(&listenerType, "type", "", "Filter listeners by type field") 561 listenerConfigCmd.PersistentFlags().IntVar(&port, "port", 0, "Filter listeners by Port field") 562 listenerConfigCmd.PersistentFlags().BoolVar(&verboseProxyConfig, "verbose", true, "Output more information") 563 listenerConfigCmd.PersistentFlags().BoolVar(&waypointProxyConfig, "waypoint", false, "Output waypoint information") 564 // Until stabilized 565 _ = listenerConfigCmd.PersistentFlags().MarkHidden("waypoint") 566 listenerConfigCmd.PersistentFlags().StringVarP(&configDumpFile, "file", "f", "", 567 "Envoy config dump JSON file") 568 569 return listenerConfigCmd 570 } 571 572 func StatsConfigCmd(ctx cli.Context) *cobra.Command { 573 var podName, podNamespace string 574 575 statsConfigCmd := &cobra.Command{ 576 Use: "envoy-stats [<type>/]<name>[.<namespace>]", 577 Short: "Retrieves Envoy metrics in the specified pod", 578 Long: `Retrieve Envoy emitted metrics for the specified pod.`, 579 Example: ` # Retrieve Envoy emitted metrics for the specified pod. 580 istioctl experimental envoy-stats <pod-name[.namespace]> 581 582 # Retrieve Envoy server metrics in prometheus format 583 istioctl experimental envoy-stats <pod-name[.namespace]> --output prom 584 585 # Retrieve Envoy server metrics in prometheus format with custom proxy admin port 586 istioctl experimental envoy-stats <pod-name[.namespace]> --output prom --proxy-admin-port 15000 587 588 # Retrieve Envoy server metrics in prometheus format with merged application metrics 589 istioctl experimental envoy-stats <pod-name[.namespace]> --output prom-merged 590 591 # Retrieve Envoy cluster metrics 592 istioctl experimental envoy-stats <pod-name[.namespace]> --type clusters 593 `, 594 Aliases: []string{"es"}, 595 Args: func(cmd *cobra.Command, args []string) error { 596 if len(args) != 1 && (labelSelector == "") { 597 cmd.Println(cmd.UsageString()) 598 return fmt.Errorf("stats requires pod name or label selector") 599 } 600 return nil 601 }, 602 RunE: func(c *cobra.Command, args []string) error { 603 var stats string 604 kubeClient, err := ctx.CLIClient() 605 if err != nil { 606 return err 607 } 608 if podName, podNamespace, err = getPodName(ctx, args[0]); err != nil { 609 return err 610 } 611 if statsType == "" || statsType == "server" { 612 stats, err = setupEnvoyServerStatsConfig(kubeClient, podName, podNamespace, outputFormat) 613 if err != nil { 614 return err 615 } 616 } else if statsType == "cluster" || statsType == "clusters" { 617 stats, err = setupEnvoyClusterStatsConfig(kubeClient, podName, podNamespace, outputFormat) 618 if err != nil { 619 return err 620 } 621 } else { 622 return fmt.Errorf("unknown stats type %s", statsType) 623 } 624 625 switch outputFormat { 626 // convert the json output to yaml 627 case yamlOutput: 628 var out []byte 629 if out, err = yaml.JSONToYAML([]byte(stats)); err != nil { 630 return err 631 } 632 _, _ = fmt.Fprint(c.OutOrStdout(), string(out)) 633 default: 634 _, _ = fmt.Fprint(c.OutOrStdout(), stats) 635 } 636 637 return nil 638 }, 639 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 640 } 641 statsConfigCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", summaryOutput, "Output format: one of json|yaml|short|prom|prom-merged") 642 statsConfigCmd.PersistentFlags().StringVarP(&statsType, "type", "t", "server", "Where to grab the stats: one of server|clusters") 643 statsConfigCmd.PersistentFlags().IntVar(&proxyAdminPort, "proxy-admin-port", defaultProxyAdminPort, "Envoy proxy admin port") 644 645 return statsConfigCmd 646 } 647 648 func logCmd(ctx cli.Context) *cobra.Command { 649 var podNamespace string 650 var podNames []string 651 652 logCmd := &cobra.Command{ 653 Use: "log [<type>/]<name>[.<namespace>]", 654 Short: "Retrieves logging levels of the Envoy in the specified pod", 655 Long: "Retrieve information about logging levels of the Envoy instance in the specified pod, and update optionally", 656 Example: ` # Retrieve information about logging levels for a given pod from Envoy. 657 istioctl proxy-config log <pod-name[.namespace]> 658 659 # Update levels of the all loggers 660 istioctl proxy-config log <pod-name[.namespace]> --level none 661 662 # Update levels of the specified loggers. 663 istioctl proxy-config log <pod-name[.namespace]> --level http:debug,redis:debug 664 665 # Reset levels of all the loggers to default value (warning). 666 istioctl proxy-config log <pod-name[.namespace]> -r 667 `, 668 Aliases: []string{"o"}, 669 Args: func(cmd *cobra.Command, args []string) error { 670 if labelSelector == "" && len(args) < 1 { 671 cmd.Println(cmd.UsageString()) 672 return fmt.Errorf("log requires pod name or --selector") 673 } 674 if reset && loggerLevelString != "" { 675 cmd.Println(cmd.UsageString()) 676 return fmt.Errorf("--level cannot be combined with --reset") 677 } 678 if outputFormat != "" && outputFormat != summaryOutput { 679 return fmt.Errorf("--output is not applicable for this command") 680 } 681 return nil 682 }, 683 RunE: func(c *cobra.Command, args []string) error { 684 kubeClient, err := ctx.CLIClient() 685 if err != nil { 686 return err 687 } 688 if labelSelector != "" { 689 if podNames, podNamespace, err = getPodNameBySelector(ctx, kubeClient, labelSelector); err != nil { 690 return err 691 } 692 } else { 693 if podNames, podNamespace, err = getPodNames(ctx, args[0], ctx.Namespace()); err != nil { 694 return err 695 } 696 } 697 for _, pod := range podNames { 698 loggerName, err = setupEnvoyLogConfig(kubeClient, "", pod, podNamespace) 699 if err != nil { 700 return err 701 } 702 } 703 704 destLoggerLevels := map[string]Level{} 705 if reset { 706 // reset logging level to `defaultOutputLevel`, and ignore the `level` option 707 levelString, _ := getLogLevelFromConfigMap(ctx) 708 level, ok := stringToLevel[levelString] 709 if ok { 710 destLoggerLevels[defaultLoggerName] = level 711 } else { 712 log.Warnf("unable to get logLevel from ConfigMap istio-sidecar-injector, using default value %q for envoy proxies", 713 levelToString[defaultEnvoyOutputLevel]) 714 destLoggerLevels[defaultLoggerName] = defaultEnvoyOutputLevel 715 } 716 } else if loggerLevelString != "" { 717 levels := strings.Split(loggerLevelString, ",") 718 for _, ol := range levels { 719 if !strings.Contains(ol, ":") && !strings.Contains(ol, "=") { 720 level, ok := stringToLevel[ol] 721 if ok { 722 destLoggerLevels = map[string]Level{ 723 defaultLoggerName: level, 724 } 725 } else { 726 return fmt.Errorf("unrecognized logging level: %v", ol) 727 } 728 } else { 729 logParts := strings.Split(ol, "::") // account for any specified namespace 730 loggerAndLevelOnly := logParts[len(logParts)-1] 731 loggerLevel := regexp.MustCompile(`[:=]`).Split(loggerAndLevelOnly, 2) 732 if !strings.Contains(loggerName, loggerLevel[0]) && loggerLevel[0] != defaultLoggerName { 733 return fmt.Errorf("unrecognized logger name: %v", loggerLevel[0]) 734 } 735 level, ok := stringToLevel[loggerLevel[1]] 736 if !ok { 737 return fmt.Errorf("unrecognized logging level: %v", loggerLevel[1]) 738 } 739 destLoggerLevels[loggerLevel[0]] = level 740 } 741 } 742 } 743 744 var resp string 745 var errs *multierror.Error 746 for _, podName := range podNames { 747 if len(destLoggerLevels) == 0 { 748 resp, err = setupEnvoyLogConfig(kubeClient, "", podName, podNamespace) 749 } else { 750 if ll, ok := destLoggerLevels[defaultLoggerName]; ok { 751 // update levels of all loggers first 752 resp, err = setupEnvoyLogConfig(kubeClient, defaultLoggerName+"="+levelToString[ll], podName, podNamespace) 753 } 754 for lg, ll := range destLoggerLevels { 755 if lg == defaultLoggerName { 756 continue 757 } 758 resp, err = setupEnvoyLogConfig(kubeClient, lg+"="+levelToString[ll], podName, podNamespace) 759 } 760 } 761 if err != nil { 762 errs = multierror.Append(errs, fmt.Errorf("error configuring log level for %v.%v: %v", podName, podNamespace, err)) 763 } else { 764 _, _ = fmt.Fprintf(c.OutOrStdout(), "%v.%v:\n%v", podName, podNamespace, resp) 765 } 766 } 767 if err := multierror.Flatten(errs.ErrorOrNil()); err != nil { 768 return err 769 } 770 return nil 771 }, 772 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 773 } 774 775 levelListString := fmt.Sprintf("[%s, %s, %s, %s, %s, %s, %s]", 776 levelToString[TraceLevel], 777 levelToString[DebugLevel], 778 levelToString[InfoLevel], 779 levelToString[WarningLevel], 780 levelToString[ErrorLevel], 781 levelToString[CriticalLevel], 782 levelToString[OffLevel]) 783 784 logCmd.PersistentFlags().BoolVarP(&reset, "reset", "r", reset, "Reset levels to default value (warning).") 785 logCmd.PersistentFlags().StringVarP(&labelSelector, "selector", "l", "", "Label selector") 786 logCmd.PersistentFlags().StringVar(&loggerLevelString, "level", loggerLevelString, 787 fmt.Sprintf("Comma-separated minimum per-logger level of messages to output, in the form of"+ 788 " [<logger>:]<level>,[<logger>:]<level>,... or <level> to change all active loggers, "+ 789 "where logger components can be listed by running \"istioctl proxy-config log <pod-name[.namespace]>\""+ 790 "or referred from https://github.com/envoyproxy/envoy/blob/main/source/common/common/logger.h, and level can be one of %s", levelListString)) 791 return logCmd 792 } 793 794 func routeConfigCmd(ctx cli.Context) *cobra.Command { 795 var podName, podNamespace string 796 797 routeConfigCmd := &cobra.Command{ 798 Use: "route [<type>/]<name>[.<namespace>]", 799 Short: "Retrieves route configuration for the Envoy in the specified pod", 800 Long: `Retrieve information about route configuration for the Envoy instance in the specified pod.`, 801 Example: ` # Retrieve summary about route configuration for a given pod from Envoy. 802 istioctl proxy-config routes <pod-name[.namespace]> 803 804 # Retrieve route summary for route 9080. 805 istioctl proxy-config route <pod-name[.namespace]> --name 9080 806 807 # Retrieve full route dump for route 9080 808 istioctl proxy-config route <pod-name[.namespace]> --name 9080 -o json 809 810 # Retrieve route summary without using Kubernetes API 811 ssh <user@hostname> 'curl localhost:15000/config_dump' > envoy-config.json 812 istioctl proxy-config routes --file envoy-config.json 813 `, 814 Aliases: []string{"routes", "r"}, 815 Args: func(cmd *cobra.Command, args []string) error { 816 if (len(args) == 1) != (configDumpFile == "") { 817 cmd.Println(cmd.UsageString()) 818 return fmt.Errorf("route requires pod name or --file parameter") 819 } 820 return nil 821 }, 822 RunE: func(c *cobra.Command, args []string) error { 823 var configWriter *configdump.ConfigWriter 824 kubeClient, err := ctx.CLIClient() 825 if err != nil { 826 return err 827 } 828 if len(args) == 1 { 829 if podName, podNamespace, err = getPodName(ctx, args[0]); err != nil { 830 return err 831 } 832 configWriter, err = setupPodConfigdumpWriter(kubeClient, podName, podNamespace, false, c.OutOrStdout()) 833 } else { 834 configWriter, err = setupFileConfigdumpWriter(configDumpFile, c.OutOrStdout()) 835 } 836 if err != nil { 837 return err 838 } 839 filter := configdump.RouteFilter{ 840 Name: routeName, 841 Verbose: verboseProxyConfig, 842 } 843 switch outputFormat { 844 case summaryOutput: 845 return configWriter.PrintRouteSummary(filter) 846 case jsonOutput, yamlOutput: 847 return configWriter.PrintRouteDump(filter, outputFormat) 848 default: 849 return fmt.Errorf("output format %q not supported", outputFormat) 850 } 851 }, 852 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 853 } 854 855 routeConfigCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", summaryOutput, "Output format: one of json|yaml|short") 856 routeConfigCmd.PersistentFlags().StringVar(&routeName, "name", "", "Filter listeners by route name field") 857 routeConfigCmd.PersistentFlags().BoolVar(&verboseProxyConfig, "verbose", true, "Output more information") 858 routeConfigCmd.PersistentFlags().StringVarP(&configDumpFile, "file", "f", "", 859 "Envoy config dump JSON file") 860 861 return routeConfigCmd 862 } 863 864 func endpointConfigCmd(ctx cli.Context) *cobra.Command { 865 var podName, podNamespace string 866 867 endpointConfigCmd := &cobra.Command{ 868 Use: "endpoint [<type>/]<name>[.<namespace>]", 869 Short: "Retrieves endpoint configuration for the Envoy in the specified pod", 870 Long: `Retrieve information about endpoint configuration for the Envoy instance in the specified pod.`, 871 Example: ` # Retrieve full endpoint configuration for a given pod from Envoy. 872 istioctl proxy-config endpoint <pod-name[.namespace]> 873 874 # Retrieve endpoint summary for endpoint with port 9080. 875 istioctl proxy-config endpoint <pod-name[.namespace]> --port 9080 876 877 # Retrieve full endpoint with a address (172.17.0.2). 878 istioctl proxy-config endpoint <pod-name[.namespace]> --address 172.17.0.2 -o json 879 880 # Retrieve full endpoint with a cluster name (outbound|9411||zipkin.istio-system.svc.cluster.local). 881 istioctl proxy-config endpoint <pod-name[.namespace]> --cluster "outbound|9411||zipkin.istio-system.svc.cluster.local" -o json 882 # Retrieve full endpoint with the status (healthy). 883 istioctl proxy-config endpoint <pod-name[.namespace]> --status healthy -ojson 884 885 # Retrieve endpoint summary without using Kubernetes API 886 ssh <user@hostname> 'curl localhost:15000/clusters?format=json' > envoy-clusters.json 887 istioctl proxy-config endpoints --file envoy-clusters.json 888 `, 889 Aliases: []string{"endpoints", "ep"}, 890 Args: func(cmd *cobra.Command, args []string) error { 891 if (len(args) == 1) != (configDumpFile == "") { 892 cmd.Println(cmd.UsageString()) 893 return fmt.Errorf("endpoints requires pod name or --file parameter") 894 } 895 return nil 896 }, 897 RunE: func(c *cobra.Command, args []string) error { 898 var configWriter *clusters.ConfigWriter 899 kubeClient, err := ctx.CLIClient() 900 if err != nil { 901 return err 902 } 903 if len(args) == 1 { 904 if podName, podNamespace, err = getPodName(ctx, args[0]); err != nil { 905 return err 906 } 907 configWriter, err = setupPodClustersWriter(kubeClient, podName, podNamespace, c.OutOrStdout()) 908 } else { 909 configWriter, err = setupFileClustersWriter(configDumpFile, c.OutOrStdout()) 910 } 911 if err != nil { 912 return err 913 } 914 915 filter := clusters.EndpointFilter{ 916 Address: address, 917 Port: uint32(port), 918 Cluster: clusterName, 919 Status: status, 920 } 921 922 switch outputFormat { 923 case summaryOutput: 924 return configWriter.PrintEndpointsSummary(filter) 925 case jsonOutput, yamlOutput: 926 return configWriter.PrintEndpoints(filter, outputFormat) 927 default: 928 return fmt.Errorf("output format %q not supported", outputFormat) 929 } 930 }, 931 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 932 } 933 934 endpointConfigCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", summaryOutput, "Output format: one of json|yaml|short") 935 endpointConfigCmd.PersistentFlags().StringVar(&address, "address", "", "Filter endpoints by address field") 936 endpointConfigCmd.PersistentFlags().IntVar(&port, "port", 0, "Filter endpoints by Port field") 937 endpointConfigCmd.PersistentFlags().StringVar(&clusterName, "cluster", "", "Filter endpoints by cluster name field") 938 endpointConfigCmd.PersistentFlags().StringVar(&status, "status", "", "Filter endpoints by status field") 939 endpointConfigCmd.PersistentFlags().StringVarP(&configDumpFile, "file", "f", "", 940 "Envoy config dump JSON file") 941 942 return endpointConfigCmd 943 } 944 945 // edsConfigCmd is a command to dump EDS output. This differs from "endpoints" which pulls from /clusters. 946 // Notably, this shows metadata and locality, while clusters shows outlier health status 947 func edsConfigCmd(ctx cli.Context) *cobra.Command { 948 var podName, podNamespace string 949 950 endpointConfigCmd := &cobra.Command{ 951 Use: "eds [<type>/]<name>[.<namespace>]", 952 // Currently, we have an "endpoints" and "eds" command. While for simple use cases these are nearly identical, they give 953 // pretty different outputs for the full JSON output. This makes it a useful command for developers, but may be overwhelming 954 // for basic usage. For now, hide to avoid confusion. 955 Hidden: true, 956 Short: "Retrieves endpoint configuration for the Envoy in the specified pod", 957 Long: `Retrieve information about endpoint configuration for the Envoy instance in the specified pod.`, 958 Example: ` # Retrieve full endpoint configuration for a given pod from Envoy. 959 istioctl proxy-config eds <pod-name[.namespace]> 960 961 # Retrieve endpoint summary for endpoint with port 9080. 962 istioctl proxy-config eds <pod-name[.namespace]> --port 9080 963 964 # Retrieve full endpoint with a address (172.17.0.2). 965 istioctl proxy-config eds <pod-name[.namespace]> --address 172.17.0.2 -o json 966 967 # Retrieve full endpoint with a cluster name (outbound|9411||zipkin.istio-system.svc.cluster.local). 968 istioctl proxy-config eds <pod-name[.namespace]> --cluster "outbound|9411||zipkin.istio-system.svc.cluster.local" -o json 969 # Retrieve full endpoint with the status (healthy). 970 istioctl proxy-config eds <pod-name[.namespace]> --status healthy -ojson 971 972 # Retrieve endpoint summary without using Kubernetes API 973 ssh <user@hostname> 'curl localhost:15000/config_dump?include_eds=true' > envoy-config.json 974 istioctl proxy-config eds --file envoy-config.json 975 `, 976 Args: func(cmd *cobra.Command, args []string) error { 977 if (len(args) == 1) != (configDumpFile == "") { 978 cmd.Println(cmd.UsageString()) 979 return fmt.Errorf("eds requires pod name or --file parameter") 980 } 981 return nil 982 }, 983 RunE: func(c *cobra.Command, args []string) error { 984 var configWriter *configdump.ConfigWriter 985 kubeClient, err := ctx.CLIClient() 986 if err != nil { 987 return err 988 } 989 if len(args) == 1 { 990 if podName, podNamespace, err = getPodName(ctx, args[0]); err != nil { 991 return err 992 } 993 configWriter, err = setupPodConfigdumpWriter(kubeClient, podName, podNamespace, true, c.OutOrStdout()) 994 } else { 995 configWriter, err = setupFileConfigdumpWriter(configDumpFile, c.OutOrStdout()) 996 } 997 if err != nil { 998 return err 999 } 1000 1001 filter := configdump.EndpointFilter{ 1002 Address: address, 1003 Port: uint32(port), 1004 Cluster: clusterName, 1005 Status: status, 1006 } 1007 1008 switch outputFormat { 1009 case summaryOutput: 1010 return configWriter.PrintEndpointsSummary(filter) 1011 case jsonOutput, yamlOutput: 1012 return configWriter.PrintEndpoints(filter, outputFormat) 1013 default: 1014 return fmt.Errorf("output format %q not supported", outputFormat) 1015 } 1016 }, 1017 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 1018 } 1019 1020 endpointConfigCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", summaryOutput, "Output format: one of json|yaml|short") 1021 endpointConfigCmd.PersistentFlags().StringVar(&address, "address", "", "Filter endpoints by address field") 1022 endpointConfigCmd.PersistentFlags().IntVar(&port, "port", 0, "Filter endpoints by Port field") 1023 endpointConfigCmd.PersistentFlags().StringVar(&clusterName, "cluster", "", "Filter endpoints by cluster name field") 1024 endpointConfigCmd.PersistentFlags().StringVar(&status, "status", "", "Filter endpoints by status field") 1025 endpointConfigCmd.PersistentFlags().StringVarP(&configDumpFile, "file", "f", "", 1026 "Envoy config dump JSON file") 1027 1028 return endpointConfigCmd 1029 } 1030 1031 func bootstrapConfigCmd(ctx cli.Context) *cobra.Command { 1032 var podName, podNamespace string 1033 1034 // Shadow outputVariable since this command uses a different default value 1035 var outputFormat string 1036 1037 bootstrapConfigCmd := &cobra.Command{ 1038 Use: "bootstrap [<type>/]<name>[.<namespace>]", 1039 Short: "Retrieves bootstrap configuration for the Envoy in the specified pod", 1040 Long: `Retrieve information about bootstrap configuration for the Envoy instance in the specified pod.`, 1041 Example: ` # Retrieve full bootstrap configuration for a given pod from Envoy. 1042 istioctl proxy-config bootstrap <pod-name[.namespace]> 1043 1044 # Retrieve full bootstrap without using Kubernetes API 1045 ssh <user@hostname> 'curl localhost:15000/config_dump' > envoy-config.json 1046 istioctl proxy-config bootstrap --file envoy-config.json 1047 1048 # Show a human-readable Istio and Envoy version summary 1049 istioctl proxy-config bootstrap <pod-name[.namespace]> -o short 1050 `, 1051 Aliases: []string{"b"}, 1052 Args: func(cmd *cobra.Command, args []string) error { 1053 if (len(args) == 1) != (configDumpFile == "") { 1054 cmd.Println(cmd.UsageString()) 1055 return fmt.Errorf("bootstrap requires pod name or --file parameter") 1056 } 1057 return nil 1058 }, 1059 RunE: func(c *cobra.Command, args []string) error { 1060 var configWriter *configdump.ConfigWriter 1061 kubeClient, err := ctx.CLIClient() 1062 if err != nil { 1063 return err 1064 } 1065 if len(args) == 1 { 1066 if podName, podNamespace, err = getPodName(ctx, args[0]); err != nil { 1067 return err 1068 } 1069 configWriter, err = setupPodConfigdumpWriter(kubeClient, podName, podNamespace, false, c.OutOrStdout()) 1070 } else { 1071 configWriter, err = setupFileConfigdumpWriter(configDumpFile, c.OutOrStdout()) 1072 } 1073 if err != nil { 1074 return err 1075 } 1076 1077 switch outputFormat { 1078 case summaryOutput: 1079 return configWriter.PrintBootstrapSummary() 1080 case jsonOutput, yamlOutput: 1081 return configWriter.PrintBootstrapDump(outputFormat) 1082 default: 1083 return fmt.Errorf("output format %q not supported", outputFormat) 1084 } 1085 }, 1086 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 1087 } 1088 1089 bootstrapConfigCmd.Flags().StringVarP(&outputFormat, "output", "o", jsonOutput, "Output format: one of json|yaml|short") 1090 bootstrapConfigCmd.PersistentFlags().StringVarP(&configDumpFile, "file", "f", "", 1091 "Envoy config dump JSON file") 1092 1093 return bootstrapConfigCmd 1094 } 1095 1096 func secretConfigCmd(ctx cli.Context) *cobra.Command { 1097 var podName, podNamespace string 1098 1099 secretConfigCmd := &cobra.Command{ 1100 Use: "secret [<type>/]<name>[.<namespace>]", 1101 Short: "Retrieves secret configuration for the Envoy in the specified pod", 1102 Long: `Retrieve information about secret configuration for the Envoy instance in the specified pod.`, 1103 Example: ` # Retrieve full secret configuration for a given pod from Envoy. 1104 istioctl proxy-config secret <pod-name[.namespace]> 1105 1106 # Retrieve full bootstrap without using Kubernetes API 1107 ssh <user@hostname> 'curl localhost:15000/config_dump' > envoy-config.json 1108 istioctl proxy-config secret --file envoy-config.json`, 1109 Aliases: []string{"secrets", "s"}, 1110 Args: func(cmd *cobra.Command, args []string) error { 1111 if (len(args) == 1) != (configDumpFile == "") { 1112 cmd.Println(cmd.UsageString()) 1113 return fmt.Errorf("secret requires pod name or --file parameter") 1114 } 1115 return nil 1116 }, 1117 RunE: func(c *cobra.Command, args []string) error { 1118 var cw *configdump.ConfigWriter 1119 kubeClient, err := ctx.CLIClient() 1120 if err != nil { 1121 return err 1122 } 1123 if len(args) == 1 { 1124 if podName, podNamespace, err = getPodName(ctx, args[0]); err != nil { 1125 return err 1126 } 1127 cw, err = setupPodConfigdumpWriter(kubeClient, podName, podNamespace, false, c.OutOrStdout()) 1128 } else { 1129 cw, err = setupFileConfigdumpWriter(configDumpFile, c.OutOrStdout()) 1130 if err != nil { 1131 log.Warnf("couldn't parse envoy secrets dump: %v", err) 1132 } 1133 } 1134 if err != nil { 1135 return err 1136 } 1137 switch outputFormat { 1138 case summaryOutput: 1139 return cw.PrintSecretSummary() 1140 case jsonOutput, yamlOutput: 1141 return cw.PrintSecretDump(outputFormat) 1142 default: 1143 return fmt.Errorf("output format %q not supported", outputFormat) 1144 } 1145 }, 1146 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 1147 } 1148 1149 secretConfigCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", summaryOutput, "Output format: one of json|yaml|short") 1150 secretConfigCmd.PersistentFlags().StringVarP(&configDumpFile, "file", "f", "", 1151 "Envoy config dump JSON file") 1152 return secretConfigCmd 1153 } 1154 1155 func rootCACompareConfigCmd(ctx cli.Context) *cobra.Command { 1156 var podName1, podName2, podNamespace1, podNamespace2 string 1157 1158 rootCACompareConfigCmd := &cobra.Command{ 1159 Use: "rootca-compare [pod/]<name-1>[.<namespace-1>] [pod/]<name-2>[.<namespace-2>]", 1160 Short: "Compare ROOTCA values for the two given pods", 1161 Long: `Compare ROOTCA values for given 2 pods to check the connectivity between them.`, 1162 Example: ` # Compare ROOTCA values for given 2 pods to check the connectivity between them. 1163 istioctl proxy-config rootca-compare <pod-name-1[.namespace]> <pod-name-2[.namespace]>`, 1164 Aliases: []string{"rc"}, 1165 Args: func(cmd *cobra.Command, args []string) error { 1166 if len(args) != 2 { 1167 cmd.Println(cmd.UsageString()) 1168 return fmt.Errorf("rootca-compare requires 2 pods as an argument") 1169 } 1170 return nil 1171 }, 1172 RunE: func(c *cobra.Command, args []string) error { 1173 kubeClient, err := ctx.CLIClient() 1174 if err != nil { 1175 return err 1176 } 1177 1178 var rootCA1, rootCA2 string 1179 if len(args) == 2 { 1180 if podName1, podNamespace1, err = getPodName(ctx, args[0]); err != nil { 1181 return err 1182 } 1183 rootCA1, err = extractRootCA(kubeClient, podName1, podNamespace1, c.OutOrStdout()) 1184 if err != nil { 1185 return err 1186 } 1187 1188 if podName2, podNamespace2, err = getPodName(ctx, args[1]); err != nil { 1189 return err 1190 } 1191 rootCA2, err = extractRootCA(kubeClient, podName2, podNamespace2, c.OutOrStdout()) 1192 if err != nil { 1193 return err 1194 } 1195 } else { 1196 c.Println(c.UsageString()) 1197 return fmt.Errorf("rootca-compare requires 2 pods as an argument") 1198 } 1199 1200 var returnErr error 1201 if rootCA1 == rootCA2 { 1202 report := fmt.Sprintf("Both [%s.%s] and [%s.%s] have the identical ROOTCA, theoretically the connectivity between them is available", 1203 podName1, podNamespace1, podName2, podNamespace2) 1204 c.Println(report) 1205 returnErr = nil 1206 } else { 1207 report := fmt.Sprintf("Both [%s.%s] and [%s.%s] have the non identical ROOTCA, theoretically the connectivity between them is unavailable", 1208 podName1, podNamespace1, podName2, podNamespace2) 1209 returnErr = fmt.Errorf(report) 1210 } 1211 return returnErr 1212 }, 1213 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 1214 } 1215 1216 rootCACompareConfigCmd.Long += "\n\n" + istioctlutil.ExperimentalMsg 1217 return rootCACompareConfigCmd 1218 } 1219 1220 func extractRootCA(client kube.CLIClient, podName, podNamespace string, out io.Writer) (string, error) { 1221 configWriter, err := setupPodConfigdumpWriter(client, podName, podNamespace, false, out) 1222 if err != nil { 1223 return "", err 1224 } 1225 return configWriter.PrintPodRootCAFromDynamicSecretDump() 1226 } 1227 1228 func ProxyConfig(ctx cli.Context) *cobra.Command { 1229 configCmd := &cobra.Command{ 1230 Use: "proxy-config", 1231 Short: "Retrieve information about proxy configuration from Envoy [kube only]", 1232 Long: `A group of commands used to retrieve information about proxy configuration from the Envoy config dump`, 1233 Example: ` # Retrieve information about proxy configuration from an Envoy instance. 1234 istioctl proxy-config <clusters|listeners|routes|endpoints|bootstrap|log|secret> <pod-name[.namespace]>`, 1235 Aliases: []string{"pc"}, 1236 } 1237 1238 configCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", summaryOutput, "Output format: one of json|yaml|short") 1239 configCmd.PersistentFlags().IntVar(&proxyAdminPort, "proxy-admin-port", defaultProxyAdminPort, "Envoy proxy admin port") 1240 1241 configCmd.AddCommand(clusterConfigCmd(ctx)) 1242 configCmd.AddCommand(allConfigCmd(ctx)) 1243 configCmd.AddCommand(listenerConfigCmd(ctx)) 1244 configCmd.AddCommand(logCmd(ctx)) 1245 configCmd.AddCommand(routeConfigCmd(ctx)) 1246 configCmd.AddCommand(bootstrapConfigCmd(ctx)) 1247 configCmd.AddCommand(endpointConfigCmd(ctx)) 1248 configCmd.AddCommand(edsConfigCmd(ctx)) 1249 configCmd.AddCommand(secretConfigCmd(ctx)) 1250 configCmd.AddCommand(rootCACompareConfigCmd(ctx)) 1251 configCmd.AddCommand(ecdsConfigCmd(ctx)) 1252 1253 return configCmd 1254 } 1255 1256 func getPodNames(ctx cli.Context, podflag, ns string) ([]string, string, error) { 1257 podNames, ns, err := ctx.InferPodsFromTypedResource(podflag, ns) 1258 if err != nil { 1259 log.Errorf("pods lookup failed") 1260 return []string{}, "", err 1261 } 1262 return podNames, ns, nil 1263 } 1264 1265 func getPodName(ctx cli.Context, podflag string) (string, string, error) { 1266 return getPodNameWithNamespace(ctx, podflag, ctx.Namespace()) 1267 } 1268 1269 func getPodNameWithNamespace(ctx cli.Context, podflag, ns string) (string, string, error) { 1270 var podName, podNamespace string 1271 podName, podNamespace, err := ctx.InferPodInfoFromTypedResource(podflag, ns) 1272 if err != nil { 1273 return "", "", err 1274 } 1275 return podName, podNamespace, nil 1276 } 1277 1278 func getPodNameBySelector(ctx cli.Context, kubeClient kube.CLIClient, labelSelector string) ([]string, string, error) { 1279 var ( 1280 podNames []string 1281 ns string 1282 ) 1283 pl, err := kubeClient.PodsForSelector(context.TODO(), ctx.NamespaceOrDefault(ctx.Namespace()), labelSelector) 1284 if err != nil { 1285 return nil, "", fmt.Errorf("not able to locate pod with selector %s: %v", labelSelector, err) 1286 } 1287 if len(pl.Items) < 1 { 1288 return nil, "", errors.New("no pods found") 1289 } 1290 for _, pod := range pl.Items { 1291 podNames = append(podNames, pod.Name) 1292 } 1293 ns = pl.Items[0].Namespace 1294 return podNames, ns, nil 1295 } 1296 1297 func ecdsConfigCmd(ctx cli.Context) *cobra.Command { 1298 var podName, podNamespace string 1299 1300 ecdsConfigCmd := &cobra.Command{ 1301 Use: "ecds [<type>/]<name>[.<namespace>]", 1302 Aliases: []string{"ec"}, 1303 Short: "Retrieves typed extension configuration for the Envoy in the specified pod", 1304 Long: `Retrieve information about typed extension configuration for the Envoy instance in the specified pod.`, 1305 Example: ` # Retrieve full typed extension configuration for a given pod from Envoy. 1306 istioctl proxy-config ecds <pod-name[.namespace]> 1307 1308 # Retrieve endpoint summary without using Kubernetes API 1309 ssh <user@hostname> 'curl localhost:15000/config_dump' > envoy-config.json 1310 istioctl proxy-config ecds --file envoy-config.json 1311 `, 1312 Args: func(cmd *cobra.Command, args []string) error { 1313 if (len(args) == 1) != (configDumpFile == "") { 1314 cmd.Println(cmd.UsageString()) 1315 return fmt.Errorf("ecds requires pod name or --file parameter") 1316 } 1317 return nil 1318 }, 1319 RunE: func(c *cobra.Command, args []string) error { 1320 var configWriter *configdump.ConfigWriter 1321 kubeClient, err := ctx.CLIClient() 1322 if err != nil { 1323 return err 1324 } 1325 if len(args) == 1 { 1326 if podName, podNamespace, err = getPodName(ctx, args[0]); err != nil { 1327 return err 1328 } 1329 configWriter, err = setupPodConfigdumpWriter(kubeClient, podName, podNamespace, true, c.OutOrStdout()) 1330 } else { 1331 configWriter, err = setupFileConfigdumpWriter(configDumpFile, c.OutOrStdout()) 1332 } 1333 if err != nil { 1334 return err 1335 } 1336 1337 switch outputFormat { 1338 case summaryOutput: 1339 return configWriter.PrintEcdsSummary() 1340 case jsonOutput, yamlOutput: 1341 return configWriter.PrintEcds(outputFormat) 1342 default: 1343 return fmt.Errorf("output format %q not supported", outputFormat) 1344 } 1345 }, 1346 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 1347 } 1348 1349 ecdsConfigCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", summaryOutput, "Output format: one of json|yaml|short") 1350 ecdsConfigCmd.PersistentFlags().StringVarP(&configDumpFile, "file", "f", "", "Envoy config dump JSON file") 1351 1352 return ecdsConfigCmd 1353 }