blob: f73a03374bd37a1b8225a40e7f36de4b4eb1c173 [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"errors"
"fmt"
"io"
"os"
"regexp"
"strings"
)
import (
"github.com/spf13/cobra"
"istio.io/pkg/log"
"sigs.k8s.io/yaml"
)
import (
"github.com/apache/dubbo-go-pixiu/istioctl/pkg/util/handlers"
"github.com/apache/dubbo-go-pixiu/istioctl/pkg/writer/envoy/clusters"
"github.com/apache/dubbo-go-pixiu/istioctl/pkg/writer/envoy/configdump"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/model"
"github.com/apache/dubbo-go-pixiu/pkg/config/host"
)
const (
jsonOutput = "json"
yamlOutput = "yaml"
summaryOutput = "short"
prometheusOutput = "prom"
prometheusMergedOutput = "prom-merged"
)
var (
fqdn, direction, subset string
port int
verboseProxyConfig bool
address, listenerType, statsType string
routeName string
clusterName, status string
// output format (yaml or short)
outputFormat string
)
// Level is an enumeration of all supported log levels.
type Level int
const (
defaultLoggerName = "level"
defaultOutputLevel = WarningLevel
)
const (
// OffLevel disables logging
OffLevel Level = iota
// CriticalLevel enables critical level logging
CriticalLevel
// ErrorLevel enables error level logging
ErrorLevel
// WarningLevel enables warning level logging
WarningLevel
// InfoLevel enables info level logging
InfoLevel
// DebugLevel enables debug level logging
DebugLevel
// TraceLevel enables trace level logging
TraceLevel
)
// existing sorted active loggers
var activeLoggers = []string{
"admin",
"aws",
"assert",
"backtrace",
"client",
"config",
"connection",
"conn_handler", // Added through https://github.com/envoyproxy/envoy/pull/8263
"dubbo",
"file",
"filter",
"forward_proxy",
"grpc",
"hc",
"health_checker",
"http",
"http2",
"hystrix",
"init",
"io",
"jwt",
"kafka",
"lua",
"main",
"misc",
"mongo",
"quic",
"pool",
"rbac",
"redis",
"router",
"runtime",
"stats",
"secret",
"tap",
"testing",
"thrift",
"tracing",
"upstream",
"udp",
"wasm",
}
var levelToString = map[Level]string{
TraceLevel: "trace",
DebugLevel: "debug",
InfoLevel: "info",
WarningLevel: "warning",
ErrorLevel: "error",
CriticalLevel: "critical",
OffLevel: "off",
}
var stringToLevel = map[string]Level{
"trace": TraceLevel,
"debug": DebugLevel,
"info": InfoLevel,
"warning": WarningLevel,
"error": ErrorLevel,
"critical": CriticalLevel,
"off": OffLevel,
}
var (
loggerLevelString = ""
reset = false
)
func extractConfigDump(podName, podNamespace string) ([]byte, error) {
kubeClient, err := kubeClient(kubeconfig, configContext)
if err != nil {
return nil, fmt.Errorf("failed to create k8s client: %v", err)
}
path := "config_dump"
debug, err := kubeClient.EnvoyDo(context.TODO(), podName, podNamespace, "GET", path)
if err != nil {
return nil, fmt.Errorf("failed to execute command on %s.%s sidecar: %v", podName, podNamespace, err)
}
return debug, err
}
func setupPodConfigdumpWriter(podName, podNamespace string, out io.Writer) (*configdump.ConfigWriter, error) {
debug, err := extractConfigDump(podName, podNamespace)
if err != nil {
return nil, err
}
return setupConfigdumpEnvoyConfigWriter(debug, out)
}
func readFile(filename string) ([]byte, error) {
file := os.Stdin
if filename != "-" {
var err error
file, err = os.Open(filename)
if err != nil {
return nil, err
}
}
defer func() {
if err := file.Close(); err != nil {
log.Errorf("failed to close %s: %s", filename, err)
}
}()
return io.ReadAll(file)
}
func setupFileConfigdumpWriter(filename string, out io.Writer) (*configdump.ConfigWriter, error) {
data, err := readFile(filename)
if err != nil {
return nil, err
}
return setupConfigdumpEnvoyConfigWriter(data, out)
}
func setupConfigdumpEnvoyConfigWriter(debug []byte, out io.Writer) (*configdump.ConfigWriter, error) {
cw := &configdump.ConfigWriter{Stdout: out}
err := cw.Prime(debug)
if err != nil {
return nil, err
}
return cw, nil
}
func setupEnvoyClusterStatsConfig(podName, podNamespace string, outputFormat string) (string, error) {
kubeClient, err := kubeClient(kubeconfig, configContext)
if err != nil {
return "", fmt.Errorf("failed to create Kubernetes client: %v", err)
}
path := "clusters"
if outputFormat == jsonOutput || outputFormat == yamlOutput {
// for yaml output we will convert the json to yaml when printed
path += "?format=json"
}
result, err := kubeClient.EnvoyDo(context.TODO(), podName, podNamespace, "GET", path)
if err != nil {
return "", fmt.Errorf("failed to execute command on Envoy: %v", err)
}
return string(result), nil
}
func setupEnvoyServerStatsConfig(podName, podNamespace string, outputFormat string) (string, error) {
kubeClient, err := kubeClient(kubeconfig, configContext)
if err != nil {
return "", fmt.Errorf("failed to create Kubernetes client: %v", err)
}
path := "stats"
port := 15000
if outputFormat == jsonOutput || outputFormat == yamlOutput {
// for yaml output we will convert the json to yaml when printed
path += "?format=json"
} else if outputFormat == prometheusOutput {
path += "/prometheus"
} else if outputFormat == prometheusMergedOutput {
path += "/prometheus"
port = 15020
}
result, err := kubeClient.EnvoyDoWithPort(context.Background(), podName, podNamespace, "GET", path, port)
if err != nil {
return "", fmt.Errorf("failed to execute command on Envoy: %v", err)
}
return string(result), nil
}
func setupEnvoyLogConfig(param, podName, podNamespace string) (string, error) {
kubeClient, err := kubeClient(kubeconfig, configContext)
if err != nil {
return "", fmt.Errorf("failed to create Kubernetes client: %v", err)
}
path := "logging"
if param != "" {
path = path + "?" + param
}
result, err := kubeClient.EnvoyDo(context.TODO(), podName, podNamespace, "POST", path)
if err != nil {
return "", fmt.Errorf("failed to execute command on Envoy: %v", err)
}
return string(result), nil
}
func getLogLevelFromConfigMap() (string, error) {
valuesConfig, err := getValuesFromConfigMap(kubeconfig, "")
if err != nil {
return "", err
}
var values struct {
SidecarInjectorWebhook struct {
Global struct {
Proxy struct {
LogLevel string `json:"logLevel"`
} `json:"proxy"`
} `json:"global"`
} `json:"sidecarInjectorWebhook"`
}
if err := yaml.Unmarshal([]byte(valuesConfig), &values); err != nil {
return "", fmt.Errorf("failed to parse values config: %v [%v]", err, valuesConfig)
}
return values.SidecarInjectorWebhook.Global.Proxy.LogLevel, nil
}
func setupPodClustersWriter(podName, podNamespace string, out io.Writer) (*clusters.ConfigWriter, error) {
kubeClient, err := kubeClient(kubeconfig, configContext)
if err != nil {
return nil, fmt.Errorf("failed to create k8s client: %v", err)
}
path := "clusters?format=json"
debug, err := kubeClient.EnvoyDo(context.TODO(), podName, podNamespace, "GET", path)
if err != nil {
return nil, fmt.Errorf("failed to execute command on Envoy: %v", err)
}
return setupClustersEnvoyConfigWriter(debug, out)
}
func setupFileClustersWriter(filename string, out io.Writer) (*clusters.ConfigWriter, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer func() {
if err := file.Close(); err != nil {
log.Errorf("failed to close %s: %s", filename, err)
}
}()
data, err := io.ReadAll(file)
if err != nil {
return nil, err
}
return setupClustersEnvoyConfigWriter(data, out)
}
// TODO(fisherxu): migrate this to config dump when implemented in Envoy
// Issue to track -> https://github.com/envoyproxy/envoy/issues/3362
func setupClustersEnvoyConfigWriter(debug []byte, out io.Writer) (*clusters.ConfigWriter, error) {
cw := &clusters.ConfigWriter{Stdout: out}
err := cw.Prime(debug)
if err != nil {
return nil, err
}
return cw, nil
}
func clusterConfigCmd() *cobra.Command {
var podName, podNamespace string
clusterConfigCmd := &cobra.Command{
Use: "cluster [<type>/]<name>[.<namespace>]",
Short: "Retrieves cluster configuration for the Envoy in the specified pod",
Long: `Retrieve information about cluster configuration for the Envoy instance in the specified pod.`,
Example: ` # Retrieve summary about cluster configuration for a given pod from Envoy.
istioctl proxy-config clusters <pod-name[.namespace]>
# Retrieve cluster summary for clusters with port 9080.
istioctl proxy-config clusters <pod-name[.namespace]> --port 9080
# Retrieve full cluster dump for clusters that are inbound with a FQDN of details.default.svc.cluster.local.
istioctl proxy-config clusters <pod-name[.namespace]> --fqdn details.default.svc.cluster.local --direction inbound -o json
# Retrieve cluster summary without using Kubernetes API
ssh <user@hostname> 'curl localhost:15000/config_dump' > envoy-config.json
istioctl proxy-config clusters --file envoy-config.json
`,
Aliases: []string{"clusters", "c"},
Args: func(cmd *cobra.Command, args []string) error {
if (len(args) == 1) != (configDumpFile == "") {
cmd.Println(cmd.UsageString())
return fmt.Errorf("cluster requires pod name or --file parameter")
}
return nil
},
RunE: func(c *cobra.Command, args []string) error {
var configWriter *configdump.ConfigWriter
var err error
if len(args) == 1 {
if podName, podNamespace, err = getPodName(args[0]); err != nil {
return err
}
configWriter, err = setupPodConfigdumpWriter(podName, podNamespace, c.OutOrStdout())
} else {
configWriter, err = setupFileConfigdumpWriter(configDumpFile, c.OutOrStdout())
}
if err != nil {
return err
}
filter := configdump.ClusterFilter{
FQDN: host.Name(fqdn),
Port: port,
Subset: subset,
Direction: model.TrafficDirection(direction),
}
switch outputFormat {
case summaryOutput:
return configWriter.PrintClusterSummary(filter)
case jsonOutput, yamlOutput:
return configWriter.PrintClusterDump(filter, outputFormat)
default:
return fmt.Errorf("output format %q not supported", outputFormat)
}
},
ValidArgsFunction: validPodsNameArgs,
}
clusterConfigCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", summaryOutput, "Output format: one of json|yaml|short")
clusterConfigCmd.PersistentFlags().StringVar(&fqdn, "fqdn", "", "Filter clusters by substring of Service FQDN field")
clusterConfigCmd.PersistentFlags().StringVar(&direction, "direction", "", "Filter clusters by Direction field")
clusterConfigCmd.PersistentFlags().StringVar(&subset, "subset", "", "Filter clusters by substring of Subset field")
clusterConfigCmd.PersistentFlags().IntVar(&port, "port", 0, "Filter clusters by Port field")
clusterConfigCmd.PersistentFlags().StringVarP(&configDumpFile, "file", "f", "",
"Envoy config dump JSON file")
return clusterConfigCmd
}
func allConfigCmd() *cobra.Command {
allConfigCmd := &cobra.Command{
Use: "all [<type>/]<name>[.<namespace>]",
Short: "Retrieves all configuration for the Envoy in the specified pod",
Long: `Retrieve information about all configuration for the Envoy instance in the specified pod.`,
Example: ` # Retrieve summary about all configuration for a given pod from Envoy.
istioctl proxy-config all <pod-name[.namespace]>
# Retrieve full cluster dump as JSON
istioctl proxy-config all <pod-name[.namespace]> -o json
# Retrieve full cluster dump with short syntax
istioctl pc a <pod-name[.namespace]>
# Retrieve cluster summary without using Kubernetes API
ssh <user@hostname> 'curl localhost:15000/config_dump' > envoy-config.json
istioctl proxy-config all --file envoy-config.json
`,
Aliases: []string{"a"},
Args: func(cmd *cobra.Command, args []string) error {
if (len(args) == 1) != (configDumpFile == "") {
cmd.Println(cmd.UsageString())
return fmt.Errorf("all requires pod name or --file parameter")
}
return nil
},
RunE: func(c *cobra.Command, args []string) error {
switch outputFormat {
case jsonOutput, yamlOutput:
var dump []byte
var err error
if len(args) == 1 {
podName, podNamespace, err := getPodName(args[0])
if err != nil {
return err
}
dump, err = extractConfigDump(podName, podNamespace)
if err != nil {
return err
}
} else {
dump, err = readFile(configDumpFile)
if err != nil {
return err
}
}
if outputFormat == yamlOutput {
if dump, err = yaml.JSONToYAML(dump); err != nil {
return err
}
}
fmt.Fprintln(c.OutOrStdout(), string(dump))
case summaryOutput:
var configWriter *configdump.ConfigWriter
if len(args) == 1 {
podName, podNamespace, err := getPodName(args[0])
if err != nil {
return err
}
configWriter, err = setupPodConfigdumpWriter(podName, podNamespace, c.OutOrStdout())
if err != nil {
return err
}
} else {
var err error
configWriter, err = setupFileConfigdumpWriter(configDumpFile, c.OutOrStdout())
if err != nil {
return err
}
}
return configWriter.PrintFullSummary(
configdump.ClusterFilter{
FQDN: host.Name(fqdn),
Port: port,
Subset: subset,
Direction: model.TrafficDirection(direction),
},
configdump.ListenerFilter{
Address: address,
Port: uint32(port),
Type: listenerType,
Verbose: verboseProxyConfig,
},
configdump.RouteFilter{
Name: routeName,
Verbose: verboseProxyConfig,
},
)
default:
return fmt.Errorf("output format %q not supported", outputFormat)
}
return nil
},
ValidArgsFunction: validPodsNameArgs,
}
allConfigCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", summaryOutput, "Output format: one of json|yaml|short")
allConfigCmd.PersistentFlags().StringVarP(&configDumpFile, "file", "f", "",
"Envoy config dump file")
allConfigCmd.PersistentFlags().BoolVar(&verboseProxyConfig, "verbose", true, "Output more information")
// cluster
allConfigCmd.PersistentFlags().StringVar(&fqdn, "fqdn", "", "Filter clusters by substring of Service FQDN field")
allConfigCmd.PersistentFlags().StringVar(&direction, "direction", "", "Filter clusters by Direction field")
allConfigCmd.PersistentFlags().StringVar(&subset, "subset", "", "Filter clusters by substring of Subset field")
// applies to cluster and route
allConfigCmd.PersistentFlags().IntVar(&port, "port", 0, "Filter clusters and listeners by Port field")
// Listener
allConfigCmd.PersistentFlags().StringVar(&address, "address", "", "Filter listeners by address field")
allConfigCmd.PersistentFlags().StringVar(&listenerType, "type", "", "Filter listeners by type field")
// route
allConfigCmd.PersistentFlags().StringVar(&routeName, "name", "", "Filter listeners by route name field")
return allConfigCmd
}
func listenerConfigCmd() *cobra.Command {
var podName, podNamespace string
listenerConfigCmd := &cobra.Command{
Use: "listener [<type>/]<name>[.<namespace>]",
Short: "Retrieves listener configuration for the Envoy in the specified pod",
Long: `Retrieve information about listener configuration for the Envoy instance in the specified pod.`,
Example: ` # Retrieve summary about listener configuration for a given pod from Envoy.
istioctl proxy-config listeners <pod-name[.namespace]>
# Retrieve listener summary for listeners with port 9080.
istioctl proxy-config listeners <pod-name[.namespace]> --port 9080
# Retrieve full listener dump for HTTP listeners with a wildcard address (0.0.0.0).
istioctl proxy-config listeners <pod-name[.namespace]> --type HTTP --address 0.0.0.0 -o json
# Retrieve listener summary without using Kubernetes API
ssh <user@hostname> 'curl localhost:15000/config_dump' > envoy-config.json
istioctl proxy-config listeners --file envoy-config.json
`,
Aliases: []string{"listeners", "l"},
Args: func(cmd *cobra.Command, args []string) error {
if (len(args) == 1) != (configDumpFile == "") {
cmd.Println(cmd.UsageString())
return fmt.Errorf("listener requires pod name or --file parameter")
}
return nil
},
RunE: func(c *cobra.Command, args []string) error {
var configWriter *configdump.ConfigWriter
var err error
if len(args) == 1 {
if podName, podNamespace, err = getPodName(args[0]); err != nil {
return err
}
configWriter, err = setupPodConfigdumpWriter(podName, podNamespace, c.OutOrStdout())
} else {
configWriter, err = setupFileConfigdumpWriter(configDumpFile, c.OutOrStdout())
}
if err != nil {
return err
}
filter := configdump.ListenerFilter{
Address: address,
Port: uint32(port),
Type: listenerType,
Verbose: verboseProxyConfig,
}
switch outputFormat {
case summaryOutput:
return configWriter.PrintListenerSummary(filter)
case jsonOutput, yamlOutput:
return configWriter.PrintListenerDump(filter, outputFormat)
default:
return fmt.Errorf("output format %q not supported", outputFormat)
}
},
ValidArgsFunction: validPodsNameArgs,
}
listenerConfigCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", summaryOutput, "Output format: one of json|yaml|short")
listenerConfigCmd.PersistentFlags().StringVar(&address, "address", "", "Filter listeners by address field")
listenerConfigCmd.PersistentFlags().StringVar(&listenerType, "type", "", "Filter listeners by type field")
listenerConfigCmd.PersistentFlags().IntVar(&port, "port", 0, "Filter listeners by Port field")
listenerConfigCmd.PersistentFlags().BoolVar(&verboseProxyConfig, "verbose", true, "Output more information")
listenerConfigCmd.PersistentFlags().StringVarP(&configDumpFile, "file", "f", "",
"Envoy config dump JSON file")
return listenerConfigCmd
}
func statsConfigCmd() *cobra.Command {
var podName, podNamespace string
statsConfigCmd := &cobra.Command{
Use: "envoy-stats [<type>/]<name>[.<namespace>]",
Short: "Retrieves Envoy metrics in the specified pod",
Long: `Retrieve Envoy emitted metrics for the specified pod.`,
Example: ` # Retrieve Envoy emitted metrics for the specified pod.
istioctl experimental envoy-stats <pod-name[.namespace]>
# Retrieve Envoy server metrics in prometheus format
istioctl experimental envoy-stats <pod-name[.namespace]> --output prom
# Retrieve Envoy server metrics in prometheus format with merged application metrics
istioctl experimental envoy-stats <pod-name[.namespace]> --output prom-merged
# Retrieve Envoy cluster metrics
istioctl experimental envoy-stats <pod-name[.namespace]> --type clusters
`,
Aliases: []string{"es"},
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 && (labelSelector == "") {
cmd.Println(cmd.UsageString())
return fmt.Errorf("stats requires pod name or label selector")
}
return nil
},
RunE: func(c *cobra.Command, args []string) error {
var stats string
var err error
if podName, podNamespace, err = getPodName(args[0]); err != nil {
return err
}
if statsType == "" || statsType == "server" {
stats, err = setupEnvoyServerStatsConfig(podName, podNamespace, outputFormat)
if err != nil {
return err
}
} else if statsType == "cluster" || statsType == "clusters" {
stats, err = setupEnvoyClusterStatsConfig(podName, podNamespace, outputFormat)
if err != nil {
return err
}
} else {
return fmt.Errorf("unknown stats type %s", statsType)
}
switch outputFormat {
// convert the json output to yaml
case yamlOutput:
var out []byte
if out, err = yaml.JSONToYAML([]byte(stats)); err != nil {
return err
}
_, _ = fmt.Fprint(c.OutOrStdout(), string(out))
default:
_, _ = fmt.Fprint(c.OutOrStdout(), stats)
}
return nil
},
ValidArgsFunction: validPodsNameArgs,
}
statsConfigCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", summaryOutput, "Output format: one of json|yaml|prom")
statsConfigCmd.PersistentFlags().StringVarP(&statsType, "type", "t", "server", "Where to grab the stats: one of server|clusters")
return statsConfigCmd
}
func logCmd() *cobra.Command {
var podName, podNamespace string
var podNames []string
logCmd := &cobra.Command{
Use: "log [<type>/]<name>[.<namespace>]",
Short: "(experimental) Retrieves logging levels of the Envoy in the specified pod",
Long: "(experimental) Retrieve information about logging levels of the Envoy instance in the specified pod, and update optionally",
Example: ` # Retrieve information about logging levels for a given pod from Envoy.
istioctl proxy-config log <pod-name[.namespace]>
# Update levels of the all loggers
istioctl proxy-config log <pod-name[.namespace]> --level none
# Update levels of the specified loggers.
istioctl proxy-config log <pod-name[.namespace]> --level http:debug,redis:debug
# Reset levels of all the loggers to default value (warning).
istioctl proxy-config log <pod-name[.namespace]> -r
`,
Aliases: []string{"o"},
Args: func(cmd *cobra.Command, args []string) error {
if labelSelector == "" && len(args) < 1 {
cmd.Println(cmd.UsageString())
return fmt.Errorf("log requires pod name or --selector")
}
if reset && loggerLevelString != "" {
cmd.Println(cmd.UsageString())
return fmt.Errorf("--level cannot be combined with --reset")
}
return nil
},
RunE: func(c *cobra.Command, args []string) error {
var err error
var loggerNames []string
if labelSelector != "" {
if podNames, podNamespace, err = getPodNameBySelector(labelSelector); err != nil {
return err
}
for _, pod := range podNames {
name, err = setupEnvoyLogConfig("", pod, podNamespace)
loggerNames = append(loggerNames, name)
}
if err != nil {
return err
}
if len(podNames) > 0 {
podName = podNames[0]
}
} else {
if podName, podNamespace, err = getPodName(args[0]); err != nil {
return err
}
name, err := setupEnvoyLogConfig("", podName, podNamespace)
loggerNames = append(loggerNames, name)
if err != nil {
return err
}
}
destLoggerLevels := map[string]Level{}
if reset {
// reset logging level to `defaultOutputLevel`, and ignore the `level` option
levelString, _ := getLogLevelFromConfigMap()
level, ok := stringToLevel[levelString]
if ok {
destLoggerLevels[defaultLoggerName] = level
} else {
log.Warnf("unable to get logLevel from ConfigMap istio-sidecar-injector, using default value: %v",
levelToString[defaultOutputLevel])
destLoggerLevels[defaultLoggerName] = defaultOutputLevel
}
} else if loggerLevelString != "" {
levels := strings.Split(loggerLevelString, ",")
for _, ol := range levels {
if !strings.Contains(ol, ":") && !strings.Contains(ol, "=") {
level, ok := stringToLevel[ol]
if ok {
destLoggerLevels = map[string]Level{
defaultLoggerName: level,
}
} else {
return fmt.Errorf("unrecognized logging level: %v", ol)
}
} else {
loggerLevel := regexp.MustCompile(`[:=]`).Split(ol, 2)
for _, logName := range loggerNames {
if !strings.Contains(logName, loggerLevel[0]) {
return fmt.Errorf("unrecognized logger name: %v", loggerLevel[0])
}
}
level, ok := stringToLevel[loggerLevel[1]]
if !ok {
return fmt.Errorf("unrecognized logging level: %v", loggerLevel[1])
}
destLoggerLevels[loggerLevel[0]] = level
}
}
}
var resp string
if len(destLoggerLevels) == 0 {
resp, err = setupEnvoyLogConfig("", podName, podNamespace)
} else {
if ll, ok := destLoggerLevels[defaultLoggerName]; ok {
// update levels of all loggers first
resp, err = setupEnvoyLogConfig(defaultLoggerName+"="+levelToString[ll], podName, podNamespace)
delete(destLoggerLevels, defaultLoggerName)
}
for lg, ll := range destLoggerLevels {
resp, err = setupEnvoyLogConfig(lg+"="+levelToString[ll], podName, podNamespace)
}
}
if err != nil {
return err
}
_, _ = fmt.Fprint(c.OutOrStdout(), resp)
return nil
},
ValidArgsFunction: validPodsNameArgs,
}
levelListString := fmt.Sprintf("[%s, %s, %s, %s, %s, %s, %s]",
levelToString[TraceLevel],
levelToString[DebugLevel],
levelToString[InfoLevel],
levelToString[WarningLevel],
levelToString[ErrorLevel],
levelToString[CriticalLevel],
levelToString[OffLevel])
s := strings.Join(activeLoggers, ", ")
logCmd.PersistentFlags().BoolVarP(&reset, "reset", "r", reset, "Reset levels to default value (warning).")
logCmd.PersistentFlags().StringVarP(&labelSelector, "selector", "l", "", "Label selector")
logCmd.PersistentFlags().StringVar(&loggerLevelString, "level", loggerLevelString,
fmt.Sprintf("Comma-separated minimum per-logger level of messages to output, in the form of"+
" [<logger>:]<level>,[<logger>:]<level>,... where logger can be one of %s and level can be one of %s",
s, levelListString))
return logCmd
}
func routeConfigCmd() *cobra.Command {
var podName, podNamespace string
routeConfigCmd := &cobra.Command{
Use: "route [<type>/]<name>[.<namespace>]",
Short: "Retrieves route configuration for the Envoy in the specified pod",
Long: `Retrieve information about route configuration for the Envoy instance in the specified pod.`,
Example: ` # Retrieve summary about route configuration for a given pod from Envoy.
istioctl proxy-config routes <pod-name[.namespace]>
# Retrieve route summary for route 9080.
istioctl proxy-config route <pod-name[.namespace]> --name 9080
# Retrieve full route dump for route 9080
istioctl proxy-config route <pod-name[.namespace]> --name 9080 -o json
# Retrieve route summary without using Kubernetes API
ssh <user@hostname> 'curl localhost:15000/config_dump' > envoy-config.json
istioctl proxy-config routes --file envoy-config.json
`,
Aliases: []string{"routes", "r"},
Args: func(cmd *cobra.Command, args []string) error {
if (len(args) == 1) != (configDumpFile == "") {
cmd.Println(cmd.UsageString())
return fmt.Errorf("route requires pod name or --file parameter")
}
return nil
},
RunE: func(c *cobra.Command, args []string) error {
var configWriter *configdump.ConfigWriter
var err error
if len(args) == 1 {
if podName, podNamespace, err = getPodName(args[0]); err != nil {
return err
}
configWriter, err = setupPodConfigdumpWriter(podName, podNamespace, c.OutOrStdout())
} else {
configWriter, err = setupFileConfigdumpWriter(configDumpFile, c.OutOrStdout())
}
if err != nil {
return err
}
filter := configdump.RouteFilter{
Name: routeName,
Verbose: verboseProxyConfig,
}
switch outputFormat {
case summaryOutput:
return configWriter.PrintRouteSummary(filter)
case jsonOutput, yamlOutput:
return configWriter.PrintRouteDump(filter, outputFormat)
default:
return fmt.Errorf("output format %q not supported", outputFormat)
}
},
ValidArgsFunction: validPodsNameArgs,
}
routeConfigCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", summaryOutput, "Output format: one of json|yaml|short")
routeConfigCmd.PersistentFlags().StringVar(&routeName, "name", "", "Filter listeners by route name field")
routeConfigCmd.PersistentFlags().BoolVar(&verboseProxyConfig, "verbose", true, "Output more information")
routeConfigCmd.PersistentFlags().StringVarP(&configDumpFile, "file", "f", "",
"Envoy config dump JSON file")
return routeConfigCmd
}
func endpointConfigCmd() *cobra.Command {
var podName, podNamespace string
endpointConfigCmd := &cobra.Command{
Use: "endpoint [<type>/]<name>[.<namespace>]",
Short: "Retrieves endpoint configuration for the Envoy in the specified pod",
Long: `Retrieve information about endpoint configuration for the Envoy instance in the specified pod.`,
Example: ` # Retrieve full endpoint configuration for a given pod from Envoy.
istioctl proxy-config endpoint <pod-name[.namespace]>
# Retrieve endpoint summary for endpoint with port 9080.
istioctl proxy-config endpoint <pod-name[.namespace]> --port 9080
# Retrieve full endpoint with a address (172.17.0.2).
istioctl proxy-config endpoint <pod-name[.namespace]> --address 172.17.0.2 -o json
# Retrieve full endpoint with a cluster name (outbound|9411||zipkin.dubbo-system.svc.cluster.local).
istioctl proxy-config endpoint <pod-name[.namespace]> --cluster "outbound|9411||zipkin.dubbo-system.svc.cluster.local" -o json
# Retrieve full endpoint with the status (healthy).
istioctl proxy-config endpoint <pod-name[.namespace]> --status healthy -ojson
# Retrieve endpoint summary without using Kubernetes API
ssh <user@hostname> 'curl localhost:15000/clusters?format=json' > envoy-clusters.json
istioctl proxy-config endpoints --file envoy-clusters.json
`,
Aliases: []string{"endpoints", "ep"},
Args: func(cmd *cobra.Command, args []string) error {
if (len(args) == 1) != (configDumpFile == "") {
cmd.Println(cmd.UsageString())
return fmt.Errorf("endpoints requires pod name or --file parameter")
}
return nil
},
RunE: func(c *cobra.Command, args []string) error {
var configWriter *clusters.ConfigWriter
var err error
if len(args) == 1 {
if podName, podNamespace, err = getPodName(args[0]); err != nil {
return err
}
configWriter, err = setupPodClustersWriter(podName, podNamespace, c.OutOrStdout())
} else {
configWriter, err = setupFileClustersWriter(configDumpFile, c.OutOrStdout())
}
if err != nil {
return err
}
filter := clusters.EndpointFilter{
Address: address,
Port: uint32(port),
Cluster: clusterName,
Status: status,
}
switch outputFormat {
case summaryOutput:
return configWriter.PrintEndpointsSummary(filter)
case jsonOutput, yamlOutput:
return configWriter.PrintEndpoints(filter, outputFormat)
default:
return fmt.Errorf("output format %q not supported", outputFormat)
}
},
ValidArgsFunction: validPodsNameArgs,
}
endpointConfigCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", summaryOutput, "Output format: one of json|yaml|short")
endpointConfigCmd.PersistentFlags().StringVar(&address, "address", "", "Filter endpoints by address field")
endpointConfigCmd.PersistentFlags().IntVar(&port, "port", 0, "Filter endpoints by Port field")
endpointConfigCmd.PersistentFlags().StringVar(&clusterName, "cluster", "", "Filter endpoints by cluster name field")
endpointConfigCmd.PersistentFlags().StringVar(&status, "status", "", "Filter endpoints by status field")
endpointConfigCmd.PersistentFlags().StringVarP(&configDumpFile, "file", "f", "",
"Envoy config dump JSON file")
return endpointConfigCmd
}
func bootstrapConfigCmd() *cobra.Command {
var podName, podNamespace string
// Shadow outputVariable since this command uses a different default value
var outputFormat string
bootstrapConfigCmd := &cobra.Command{
Use: "bootstrap [<type>/]<name>[.<namespace>]",
Short: "Retrieves bootstrap configuration for the Envoy in the specified pod",
Long: `Retrieve information about bootstrap configuration for the Envoy instance in the specified pod.`,
Example: ` # Retrieve full bootstrap configuration for a given pod from Envoy.
istioctl proxy-config bootstrap <pod-name[.namespace]>
# Retrieve full bootstrap without using Kubernetes API
ssh <user@hostname> 'curl localhost:15000/config_dump' > envoy-config.json
istioctl proxy-config bootstrap --file envoy-config.json
# Show a human-readable Istio and Envoy version summary
istioctl proxy-config bootstrap -o short
`,
Aliases: []string{"b"},
Args: func(cmd *cobra.Command, args []string) error {
if (len(args) == 1) != (configDumpFile == "") {
cmd.Println(cmd.UsageString())
return fmt.Errorf("bootstrap requires pod name or --file parameter")
}
return nil
},
RunE: func(c *cobra.Command, args []string) error {
var configWriter *configdump.ConfigWriter
var err error
if len(args) == 1 {
if podName, podNamespace, err = getPodName(args[0]); err != nil {
return err
}
configWriter, err = setupPodConfigdumpWriter(podName, podNamespace, c.OutOrStdout())
} else {
configWriter, err = setupFileConfigdumpWriter(configDumpFile, c.OutOrStdout())
}
if err != nil {
return err
}
switch outputFormat {
case summaryOutput:
return configWriter.PrintVersionSummary()
case jsonOutput, yamlOutput:
return configWriter.PrintBootstrapDump(outputFormat)
default:
return fmt.Errorf("output format %q not supported", outputFormat)
}
},
ValidArgsFunction: validPodsNameArgs,
}
bootstrapConfigCmd.Flags().StringVarP(&outputFormat, "output", "o", jsonOutput, "Output format: one of json|yaml|short")
bootstrapConfigCmd.PersistentFlags().StringVarP(&configDumpFile, "file", "f", "",
"Envoy config dump JSON file")
return bootstrapConfigCmd
}
func secretConfigCmd() *cobra.Command {
var podName, podNamespace string
secretConfigCmd := &cobra.Command{
Use: "secret [<type>/]<name>[.<namespace>]",
Short: "Retrieves secret configuration for the Envoy in the specified pod",
Long: `Retrieve information about secret configuration for the Envoy instance in the specified pod.`,
Example: ` # Retrieve full secret configuration for a given pod from Envoy.
istioctl proxy-config secret <pod-name[.namespace]>
# Retrieve full bootstrap without using Kubernetes API
ssh <user@hostname> 'curl localhost:15000/config_dump' > envoy-config.json
istioctl proxy-config secret --file envoy-config.json`,
Aliases: []string{"secrets", "s"},
Args: func(cmd *cobra.Command, args []string) error {
if (len(args) == 1) != (configDumpFile == "") {
cmd.Println(cmd.UsageString())
return fmt.Errorf("secret requires pod name or --file parameter")
}
return nil
},
RunE: func(c *cobra.Command, args []string) error {
var configWriter *configdump.ConfigWriter
var err error
if len(args) == 1 {
if podName, podNamespace, err = getPodName(args[0]); err != nil {
return err
}
configWriter, err = setupPodConfigdumpWriter(podName, podNamespace, c.OutOrStdout())
} else {
configWriter, err = setupFileConfigdumpWriter(configDumpFile, c.OutOrStdout())
}
if err != nil {
return err
}
switch outputFormat {
case summaryOutput:
return configWriter.PrintSecretSummary()
case jsonOutput, yamlOutput:
return configWriter.PrintSecretDump(outputFormat)
default:
return fmt.Errorf("output format %q not supported", outputFormat)
}
},
ValidArgsFunction: validPodsNameArgs,
}
secretConfigCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", summaryOutput, "Output format: one of json|yaml|short")
secretConfigCmd.PersistentFlags().StringVarP(&configDumpFile, "file", "f", "",
"Envoy config dump JSON file")
secretConfigCmd.Long += "\n\n" + ExperimentalMsg
return secretConfigCmd
}
func rootCACompareConfigCmd() *cobra.Command {
var podName1, podName2, podNamespace1, podNamespace2 string
rootCACompareConfigCmd := &cobra.Command{
Use: "rootca-compare [pod/]<name-1>[.<namespace-1>] [pod/]<name-2>[.<namespace-2>]",
Short: "Compare ROOTCA values for the two given pods",
Long: `Compare ROOTCA values for given 2 pods to check the connectivity between them.`,
Example: ` # Compare ROOTCA values for given 2 pods to check the connectivity between them.
istioctl proxy-config rootca-compare <pod-name-1[.namespace]> <pod-name-2[.namespace]>`,
Aliases: []string{"rc"},
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 2 {
cmd.Println(cmd.UsageString())
return fmt.Errorf("rootca-compare requires 2 pods as an argument")
}
return nil
},
RunE: func(c *cobra.Command, args []string) error {
var configWriter1, configWriter2 *configdump.ConfigWriter
var err error
if len(args) == 2 {
if podName1, podNamespace1, err = getPodName(args[0]); err != nil {
return err
}
configWriter1, err = setupPodConfigdumpWriter(podName1, podNamespace1, c.OutOrStdout())
if err != nil {
return err
}
if podName2, podNamespace2, err = getPodName(args[1]); err != nil {
return err
}
configWriter2, err = setupPodConfigdumpWriter(podName2, podNamespace2, c.OutOrStdout())
if err != nil {
return err
}
} else {
c.Println(c.UsageString())
return fmt.Errorf("rootca-compare requires 2 pods as an argument")
}
rootCA1, err1 := configWriter1.PrintPodRootCAFromDynamicSecretDump()
if err1 != nil {
return fmt.Errorf("error when retrieving ROOTCA of [%s]: %v", args[0], err1)
}
rootCA2, err2 := configWriter2.PrintPodRootCAFromDynamicSecretDump()
if err2 != nil {
return fmt.Errorf("error when retrieving ROOTCA of [%s]: %v", args[1], err2)
}
var returnErr error
if rootCA1 == rootCA2 {
report := fmt.Sprintf("Both [%s.%s] and [%s.%s] have the identical ROOTCA, theoretically the connectivity between them is available",
podName1, podNamespace1, podName2, podNamespace2)
c.Println(report)
returnErr = nil
} else {
report := fmt.Sprintf("Both [%s.%s] and [%s.%s] have the non identical ROOTCA, theoretically the connectivity between them is unavailable",
podName1, podNamespace1, podName2, podNamespace2)
returnErr = fmt.Errorf(report)
}
return returnErr
},
ValidArgsFunction: validPodsNameArgs,
}
rootCACompareConfigCmd.Long += "\n\n" + ExperimentalMsg
return rootCACompareConfigCmd
}
func proxyConfig() *cobra.Command {
configCmd := &cobra.Command{
Use: "proxy-config",
Short: "Retrieve information about proxy configuration from Envoy [kube only]",
Long: `A group of commands used to retrieve information about proxy configuration from the Envoy config dump`,
Example: ` # Retrieve information about proxy configuration from an Envoy instance.
istioctl proxy-config <clusters|listeners|routes|endpoints|bootstrap|log|secret> <pod-name[.namespace]>`,
Aliases: []string{"pc"},
}
configCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", summaryOutput, "Output format: one of json|yaml|short")
configCmd.AddCommand(clusterConfigCmd())
configCmd.AddCommand(allConfigCmd())
configCmd.AddCommand(listenerConfigCmd())
configCmd.AddCommand(logCmd())
configCmd.AddCommand(routeConfigCmd())
configCmd.AddCommand(bootstrapConfigCmd())
configCmd.AddCommand(endpointConfigCmd())
configCmd.AddCommand(secretConfigCmd())
configCmd.AddCommand(rootCACompareConfigCmd())
return configCmd
}
func getPodName(podflag string) (string, string, error) {
kubeClient, err := kubeClient(kubeconfig, configContext)
if err != nil {
return "", "", fmt.Errorf("failed to create k8s client: %w", err)
}
var podName, ns string
podName, ns, err = handlers.InferPodInfoFromTypedResource(podflag,
handlers.HandleNamespace(namespace, defaultNamespace),
kubeClient.UtilFactory())
if err != nil {
return "", "", err
}
return podName, ns, nil
}
func getPodNameBySelector(labelSelector string) ([]string, string, error) {
var (
podNames []string
ns string
)
client, err := kubeClient(kubeconfig, configContext)
if err != nil {
return nil, "", fmt.Errorf("failed to create k8s client: %w", err)
}
pl, err := client.PodsForSelector(context.TODO(), handlers.HandleNamespace(namespace, defaultNamespace), labelSelector)
if err != nil {
return nil, "", fmt.Errorf("not able to locate pod with selector %s: %v", labelSelector, err)
}
if len(pl.Items) < 1 {
return nil, "", errors.New("no pods found")
}
for _, pod := range pl.Items {
podNames = append(podNames, pod.Name)
}
ns = pl.Items[0].Namespace
return podNames, ns, nil
}