istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/writer/envoy/configdump/cluster.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 configdump
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"sort"
    21  	"strings"
    22  	"text/tabwriter"
    23  
    24  	cluster "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
    25  	"sigs.k8s.io/yaml"
    26  
    27  	"istio.io/istio/istioctl/pkg/util/proto"
    28  	"istio.io/istio/pilot/pkg/model"
    29  	v3 "istio.io/istio/pilot/pkg/xds/v3"
    30  	"istio.io/istio/pkg/config/host"
    31  )
    32  
    33  // ClusterFilter is used to pass filter information into cluster based config writer print functions
    34  type ClusterFilter struct {
    35  	FQDN      host.Name
    36  	Port      int
    37  	Subset    string
    38  	Direction model.TrafficDirection
    39  }
    40  
    41  // Verify returns true if the passed cluster matches the filter fields
    42  func (c *ClusterFilter) Verify(cluster *cluster.Cluster) bool {
    43  	name := cluster.Name
    44  	if c.FQDN == "" && c.Port == 0 && c.Subset == "" && c.Direction == "" {
    45  		return true
    46  	}
    47  	if c.FQDN != "" && !strings.Contains(name, string(c.FQDN)) {
    48  		return false
    49  	}
    50  	if c.Direction != "" && !strings.Contains(name, string(c.Direction)) {
    51  		return false
    52  	}
    53  	if c.Subset != "" && !strings.Contains(name, c.Subset) {
    54  		return false
    55  	}
    56  	if c.Port != 0 {
    57  		p := fmt.Sprintf("|%v|", c.Port)
    58  		if !strings.Contains(name, p) {
    59  			return false
    60  		}
    61  	}
    62  	return true
    63  }
    64  
    65  // PrintClusterSummary prints a summary of the relevant clusters in the config dump to the ConfigWriter stdout
    66  func (c *ConfigWriter) PrintClusterSummary(filter ClusterFilter) error {
    67  	w, clusters, err := c.setupClusterConfigWriter()
    68  	if err != nil {
    69  		return err
    70  	}
    71  	if includeConfigType {
    72  		_, _ = fmt.Fprintln(w, "NAME\tSERVICE FQDN\tPORT\tSUBSET\tDIRECTION\tTYPE\tDESTINATION RULE")
    73  	} else {
    74  		_, _ = fmt.Fprintln(w, "SERVICE FQDN\tPORT\tSUBSET\tDIRECTION\tTYPE\tDESTINATION RULE")
    75  	}
    76  	for _, c := range clusters {
    77  		if filter.Verify(c) {
    78  			if len(strings.Split(c.Name, "|")) > 3 {
    79  				direction, subset, fqdn, port := model.ParseSubsetKey(c.Name)
    80  				if subset == "" {
    81  					subset = "-"
    82  				}
    83  				if includeConfigType {
    84  					c.Name = fmt.Sprintf("cluster/%s", c.Name)
    85  					_, _ = fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%s\t%s\n", c.Name, fqdn, port, subset, direction, c.GetType(),
    86  						describeManagement(c.GetMetadata()))
    87  				} else {
    88  					_, _ = fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%s\t%s\n", fqdn, port, subset, direction, c.GetType(),
    89  						describeManagement(c.GetMetadata()))
    90  				}
    91  			} else {
    92  				if includeConfigType && len(c.Name) > 0 {
    93  					c.Name = fmt.Sprintf("cluster/%s", c.Name)
    94  					_, _ = fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%s\t%s\n", c.Name, c.Name, "-", "-", "-", c.GetType(),
    95  						describeManagement(c.GetMetadata()))
    96  				} else {
    97  					_, _ = fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%s\t%s\n", c.Name, "-", "-", "-", c.GetType(),
    98  						describeManagement(c.GetMetadata()))
    99  				}
   100  			}
   101  		}
   102  	}
   103  	return w.Flush()
   104  }
   105  
   106  // PrintClusterDump prints the relevant clusters in the config dump to the ConfigWriter stdout
   107  func (c *ConfigWriter) PrintClusterDump(filter ClusterFilter, outputFormat string) error {
   108  	_, clusters, err := c.setupClusterConfigWriter()
   109  	if err != nil {
   110  		return err
   111  	}
   112  	filteredClusters := make(proto.MessageSlice, 0, len(clusters))
   113  	for _, cluster := range clusters {
   114  		if filter.Verify(cluster) {
   115  			filteredClusters = append(filteredClusters, cluster)
   116  		}
   117  	}
   118  	out, err := json.MarshalIndent(filteredClusters, "", "    ")
   119  	if err != nil {
   120  		return err
   121  	}
   122  	if outputFormat == "yaml" {
   123  		if out, err = yaml.JSONToYAML(out); err != nil {
   124  			return err
   125  		}
   126  	}
   127  	_, _ = fmt.Fprintln(c.Stdout, string(out))
   128  	return nil
   129  }
   130  
   131  func (c *ConfigWriter) setupClusterConfigWriter() (*tabwriter.Writer, []*cluster.Cluster, error) {
   132  	clusters, err := c.retrieveSortedClusterSlice()
   133  	if err != nil {
   134  		return nil, nil, err
   135  	}
   136  	w := new(tabwriter.Writer).Init(c.Stdout, 0, 8, 5, ' ', 0)
   137  	return w, clusters, nil
   138  }
   139  
   140  func (c *ConfigWriter) retrieveSortedClusterSlice() ([]*cluster.Cluster, error) {
   141  	if c.configDump == nil {
   142  		return nil, fmt.Errorf("config writer has not been primed")
   143  	}
   144  	clusterDump, err := c.configDump.GetClusterConfigDump()
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  	clusters := make([]*cluster.Cluster, 0)
   149  	for _, c := range clusterDump.DynamicActiveClusters {
   150  		if c.Cluster != nil {
   151  			clusterTyped := &cluster.Cluster{}
   152  			// Support v2 or v3 in config dump. See ads.go:RequestedTypes for more info.
   153  			c.Cluster.TypeUrl = v3.ClusterType
   154  			err = c.Cluster.UnmarshalTo(clusterTyped)
   155  			if err != nil {
   156  				return nil, err
   157  			}
   158  			clusters = append(clusters, clusterTyped)
   159  		}
   160  	}
   161  	for _, c := range clusterDump.StaticClusters {
   162  		if c.Cluster != nil {
   163  			clusterTyped := &cluster.Cluster{}
   164  			// Support v2 or v3 in config dump. See ads.go:RequestedTypes for more info.
   165  			c.Cluster.TypeUrl = v3.ClusterType
   166  			err = c.Cluster.UnmarshalTo(clusterTyped)
   167  			if err != nil {
   168  				return nil, err
   169  			}
   170  			clusters = append(clusters, clusterTyped)
   171  		}
   172  	}
   173  	if len(clusters) == 0 {
   174  		return nil, fmt.Errorf("no clusters found")
   175  	}
   176  	sort.Slice(clusters, func(i, j int) bool {
   177  		iDirection, iSubset, iName, iPort := safelyParseSubsetKey(clusters[i].Name)
   178  		jDirection, jSubset, jName, jPort := safelyParseSubsetKey(clusters[j].Name)
   179  		if iName == jName {
   180  			if iSubset == jSubset {
   181  				if iPort == jPort {
   182  					return iDirection < jDirection
   183  				}
   184  				return iPort < jPort
   185  			}
   186  			return iSubset < jSubset
   187  		}
   188  		return iName < jName
   189  	})
   190  	return clusters, nil
   191  }
   192  
   193  func safelyParseSubsetKey(key string) (model.TrafficDirection, string, host.Name, int) {
   194  	if len(strings.Split(key, "|")) > 3 {
   195  		return model.ParseSubsetKey(key)
   196  	}
   197  	name := host.Name(key)
   198  	return "", "", name, 0
   199  }