istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tools/bug-report/pkg/content/content.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 content 16 17 import ( 18 "fmt" 19 "strings" 20 "time" 21 22 "github.com/hashicorp/go-multierror" 23 24 "istio.io/istio/istioctl/pkg/util/formatting" 25 "istio.io/istio/pkg/config/analysis/analyzers" 26 "istio.io/istio/pkg/config/analysis/diag" 27 "istio.io/istio/pkg/config/analysis/local" 28 "istio.io/istio/pkg/config/resource" 29 "istio.io/istio/pkg/kube" 30 "istio.io/istio/pkg/log" 31 "istio.io/istio/pkg/util/istiomultierror" 32 "istio.io/istio/tools/bug-report/pkg/common" 33 "istio.io/istio/tools/bug-report/pkg/kubectlcmd" 34 ) 35 36 const ( 37 coredumpDir = "/var/lib/istio" 38 ) 39 40 // Params contains parameters for running a kubectl fetch command. 41 type Params struct { 42 Runner *kubectlcmd.Runner 43 DryRun bool 44 Verbose bool 45 ClusterVersion string 46 Namespace string 47 IstioNamespace string 48 Pod string 49 Container string 50 KubeConfig string 51 KubeContext string 52 } 53 54 func (p *Params) SetDryRun(dryRun bool) *Params { 55 out := *p 56 out.DryRun = dryRun 57 return &out 58 } 59 60 func (p *Params) SetVerbose(verbose bool) *Params { 61 out := *p 62 out.Verbose = verbose 63 return &out 64 } 65 66 func (p *Params) SetNamespace(namespace string) *Params { 67 out := *p 68 out.Namespace = namespace 69 return &out 70 } 71 72 func (p *Params) SetIstioNamespace(namespace string) *Params { 73 out := *p 74 out.IstioNamespace = namespace 75 return &out 76 } 77 78 func (p *Params) SetPod(pod string) *Params { 79 out := *p 80 out.Pod = pod 81 return &out 82 } 83 84 func (p *Params) SetContainer(container string) *Params { 85 out := *p 86 out.Container = container 87 return &out 88 } 89 90 // GetK8sResources returns all k8s cluster resources. 91 func GetK8sResources(p *Params) (map[string]string, error) { 92 out, err := p.Runner.RunCmd("get --all-namespaces "+ 93 "all,nodes,namespaces,jobs,ingresses,endpoints,endpointslices,customresourcedefinitions,configmaps,events,"+ 94 "mutatingwebhookconfigurations,validatingwebhookconfigurations,networkpolicies "+ 95 "-o yaml", "", p.KubeConfig, p.KubeContext, p.DryRun) 96 return map[string]string{ 97 "k8s-resources": out, 98 }, err 99 } 100 101 // GetSecrets returns all k8s secrets. If full is set, the secret contents are also returned. 102 func GetSecrets(p *Params) (map[string]string, error) { 103 cmdStr := "get secrets --all-namespaces" 104 if p.Verbose { 105 cmdStr += " -o yaml" 106 } 107 out, err := p.Runner.RunCmd(cmdStr, "", p.KubeConfig, p.KubeContext, p.DryRun) 108 return map[string]string{ 109 "secrets": out, 110 }, err 111 } 112 113 // GetCRs returns CR contents for all CRDs in the cluster. 114 func GetCRs(p *Params) (map[string]string, error) { 115 crds, err := getCRDList(p) 116 if err != nil { 117 return nil, err 118 } 119 out, err := p.Runner.RunCmd("get --all-namespaces "+strings.Join(crds, ",")+" -o yaml", "", p.KubeConfig, p.KubeContext, p.DryRun) 120 return map[string]string{ 121 "crs": out, 122 }, err 123 } 124 125 // GetClusterInfo returns the cluster info. 126 func GetClusterInfo(p *Params) (map[string]string, error) { 127 out, err := p.Runner.RunCmd("config current-context", "", p.KubeConfig, p.KubeContext, p.DryRun) 128 if err != nil { 129 return nil, err 130 } 131 ret := make(map[string]string) 132 // Add the endpoint to the context 133 ret["cluster-context"] = out + p.Runner.Client.RESTConfig().Host + "\n" 134 out, err = p.Runner.RunCmd("version", "", p.KubeConfig, p.KubeContext, p.DryRun) 135 if err != nil { 136 return nil, err 137 } 138 ret["kubectl-version"] = out 139 return ret, nil 140 } 141 142 // GetClusterContext returns the cluster context. 143 func GetClusterContext(runner *kubectlcmd.Runner, kubeConfig string) (string, error) { 144 return runner.RunCmd("config current-context", "", kubeConfig, "", false) 145 } 146 147 // GetNodeInfo returns node information. 148 func GetNodeInfo(p *Params) (map[string]string, error) { 149 out, err := p.Runner.RunCmd("get nodes -o yaml", "", p.KubeConfig, p.KubeContext, p.DryRun) 150 return map[string]string{ 151 "nodes": out, 152 }, err 153 } 154 155 // GetPodInfo returns pod details for istioNamespace. 156 func GetPodInfo(p *Params) (map[string]string, error) { 157 if p.IstioNamespace == "" { 158 return nil, fmt.Errorf("getPodInfo requires the Istio namespace") 159 } 160 out, err := p.Runner.RunCmd("get pods -o yaml", p.IstioNamespace, p.KubeConfig, p.KubeContext, p.DryRun) 161 return map[string]string{ 162 "pods": out, 163 }, err 164 } 165 166 // GetEvents returns events for all namespaces. 167 func GetEvents(p *Params) (map[string]string, error) { 168 out, err := p.Runner.RunCmd("get events --all-namespaces -o wide --sort-by=.metadata.creationTimestamp", "", p.KubeConfig, p.KubeContext, p.DryRun) 169 return map[string]string{ 170 "events": out, 171 }, err 172 } 173 174 // GetIstiodInfo returns internal Istiod debug info. 175 func GetIstiodInfo(p *Params) (map[string]string, error) { 176 if p.Namespace == "" || p.Pod == "" { 177 return nil, fmt.Errorf("getIstiodInfo requires namespace and pod") 178 } 179 errs := istiomultierror.New() 180 ret := make(map[string]string) 181 for _, url := range common.IstiodDebugURLs(p.ClusterVersion) { 182 out, err := p.Runner.Exec(p.Namespace, p.Pod, common.DiscoveryContainerName, fmt.Sprintf(`pilot-discovery request GET %s`, url), p.DryRun) 183 if err != nil { 184 errs = multierror.Append(errs, err) 185 continue 186 } 187 ret[url] = out 188 } 189 if errs.ErrorOrNil() != nil { 190 return nil, errs 191 } 192 return ret, nil 193 } 194 195 // GetProxyInfo returns internal proxy debug info. 196 func GetProxyInfo(p *Params) (map[string]string, error) { 197 errs := istiomultierror.New() 198 if p.Namespace == "" || p.Pod == "" { 199 return nil, fmt.Errorf("getProxyInfo requires namespace and pod") 200 } 201 ret := make(map[string]string) 202 for _, url := range common.ProxyDebugURLs(p.ClusterVersion) { 203 out, err := p.Runner.EnvoyGet(p.Namespace, p.Pod, url, p.DryRun) 204 if err != nil { 205 errs = multierror.Append(errs, err) 206 continue 207 } 208 ret[url] = out 209 } 210 if errs.ErrorOrNil() != nil { 211 return nil, errs 212 } 213 return ret, nil 214 } 215 216 func GetZtunnelInfo(p *Params) (map[string]string, error) { 217 if p.Namespace == "" || p.Pod == "" { 218 return nil, fmt.Errorf("getZtunnelInfo requires namespace and pod") 219 } 220 errs := istiomultierror.New() 221 ret := make(map[string]string) 222 for _, url := range common.ZtunnelDebugURLs(p.ClusterVersion) { 223 out, err := p.Runner.EnvoyGet(p.Namespace, p.Pod, url, p.DryRun) 224 if err != nil { 225 errs = multierror.Append(errs, err) 226 continue 227 } 228 ret[url] = out 229 } 230 if errs.ErrorOrNil() != nil { 231 return nil, errs 232 } 233 return ret, nil 234 } 235 236 // GetNetstat returns netstat for the given container. 237 func GetNetstat(p *Params) (map[string]string, error) { 238 if p.Namespace == "" || p.Pod == "" { 239 return nil, fmt.Errorf("getNetstat requires namespace and pod") 240 } 241 242 out, err := p.Runner.Exec(p.Namespace, p.Pod, common.ProxyContainerName, "netstat -natpw", p.DryRun) 243 if err != nil { 244 return nil, err 245 } 246 return map[string]string{ 247 "netstat": out, 248 }, err 249 } 250 251 // GetAnalyze returns the output of istioctl analyze. 252 func GetAnalyze(p *Params, timeout time.Duration) (map[string]string, error) { 253 out := make(map[string]string) 254 sa := local.NewSourceAnalyzer(analyzers.AllCombined(), resource.Namespace(p.Namespace), resource.Namespace(p.IstioNamespace), nil) 255 256 k, err := kube.NewClient(kube.NewClientConfigForRestConfig(p.Runner.Client.RESTConfig()), "") 257 if err != nil { 258 return nil, err 259 } 260 k = kube.EnableCrdWatcher(k) 261 sa.AddRunningKubeSource(k) 262 263 cancel := make(chan struct{}) 264 result, err := sa.Analyze(cancel) 265 if err != nil { 266 return nil, err 267 } 268 269 if len(result.SkippedAnalyzers) > 0 { 270 log.Infof("Skipped analyzers:") 271 for _, a := range result.SkippedAnalyzers { 272 log.Infof("\t: %s", a) 273 } 274 } 275 if len(result.ExecutedAnalyzers) > 0 { 276 log.Infof("Executed analyzers:") 277 for _, a := range result.ExecutedAnalyzers { 278 log.Infof("\t: %s", a) 279 } 280 } 281 282 // Get messages for output 283 outputMessages := result.Messages.SetDocRef("istioctl-analyze").FilterOutLowerThan(diag.Info) 284 285 // Print all the messages to stdout in the specified format 286 output, err := formatting.Print(outputMessages, formatting.LogFormat, false) 287 if err != nil { 288 return nil, err 289 } 290 if p.Namespace == common.NamespaceAll { 291 out[common.StrNamespaceAll] = output 292 } else { 293 out[p.Namespace] = output 294 } 295 return out, nil 296 } 297 298 // GetCoredumps returns coredumps for the given namespace/pod/container. 299 func GetCoredumps(p *Params) (map[string]string, error) { 300 if p.Namespace == "" || p.Pod == "" { 301 return nil, fmt.Errorf("getCoredumps requires namespace and pod") 302 } 303 cds, err := getCoredumpList(p) 304 if err != nil { 305 return nil, err 306 } 307 308 ret := make(map[string]string) 309 log.Infof("%s/%s/%s has %d coredumps", p.Namespace, p.Pod, p.Container, len(cds)) 310 for idx, cd := range cds { 311 outStr, err := p.Runner.Cat(p.Namespace, p.Pod, p.Container, cd, p.DryRun) 312 if err != nil { 313 log.Warn(err) 314 continue 315 } 316 ret[fmt.Sprint(idx)+".core"] = outStr 317 } 318 return ret, nil 319 } 320 321 func getCoredumpList(p *Params) ([]string, error) { 322 out, err := p.Runner.Exec(p.Namespace, p.Pod, p.Container, fmt.Sprintf("find %s -name core.*", coredumpDir), p.DryRun) 323 if err != nil { 324 return nil, err 325 } 326 var cds []string 327 for _, cd := range strings.Split(out, "\n") { 328 if strings.TrimSpace(cd) != "" { 329 cds = append(cds, cd) 330 } 331 } 332 return cds, nil 333 } 334 335 func getCRDList(p *Params) ([]string, error) { 336 crdStr, err := p.Runner.RunCmd("get customresourcedefinitions --no-headers", "", p.KubeConfig, p.KubeContext, p.DryRun) 337 if err != nil { 338 return nil, err 339 } 340 var out []string 341 for _, crd := range strings.Split(crdStr, "\n") { 342 if strings.TrimSpace(crd) == "" { 343 continue 344 } 345 out = append(out, strings.Split(crd, " ")[0]) 346 } 347 return out, nil 348 }