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  }