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 }