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  }