github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/report/report.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package report
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/spf13/cobra"
    29  	corev1 "k8s.io/api/core/v1"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    32  	"k8s.io/apimachinery/pkg/runtime/schema"
    33  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    34  	"k8s.io/cli-runtime/pkg/genericclioptions"
    35  	"k8s.io/cli-runtime/pkg/genericiooptions"
    36  	"k8s.io/cli-runtime/pkg/printers"
    37  	"k8s.io/klog/v2"
    38  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    39  	"k8s.io/kubectl/pkg/util"
    40  	"k8s.io/kubectl/pkg/util/i18n"
    41  	"k8s.io/kubectl/pkg/util/templates"
    42  	"k8s.io/utils/strings/slices"
    43  
    44  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    45  	clischeme "github.com/1aal/kubeblocks/pkg/cli/scheme"
    46  	"github.com/1aal/kubeblocks/pkg/cli/spinner"
    47  	"github.com/1aal/kubeblocks/pkg/cli/types"
    48  	cliutil "github.com/1aal/kubeblocks/pkg/cli/util"
    49  	"github.com/1aal/kubeblocks/pkg/constant"
    50  )
    51  
    52  const (
    53  	versionFile     = "version.txt"
    54  	manifestsFolder = "manifests"
    55  	eventsFolder    = "events"
    56  	logsFolder      = "logs"
    57  
    58  	kubeBlocksReport = "kubeblocks"
    59  	clusterReport    = "cluster"
    60  )
    61  
    62  var (
    63  	reportClusterExamples = templates.Examples(`
    64  	# report KubeBlocks status
    65  	kbcli report cluster mycluster
    66  
    67  	# report KubeBlocks cluster information to file
    68  	kbcli report cluster mycluster -f filename
    69  
    70  	# report KubeBlocks cluster information with logs
    71  	kbcli report cluster mycluster --with-logs
    72  
    73  	# report KubeBlocks cluster information with logs and mask sensitive info
    74  	kbcli report cluster mycluster --with-logs --mask
    75  
    76  	# report KubeBlocks cluster information with logs since 1 hour ago
    77  	kbcli report cluster mycluster --with-logs --since 1h
    78  
    79  	# report KubeBlocks cluster information with logs since given time
    80  	kbcli report cluster mycluster --with-logs --since-time 2023-05-23T00:00:00Z
    81  
    82  	# report KubeBlocks cluster information with logs for all containers
    83  	kbcli report cluster mycluster --with-logs --all-containers
    84  	`)
    85  
    86  	reportKBExamples = templates.Examples(`
    87  	# report KubeBlocks status
    88  	kbcli report kubeblocks
    89  
    90  	# report KubeBlocks information to file
    91  	kbcli report kubeblocks -f filename
    92  
    93  	# report KubeBlocks information with logs
    94  	kbcli report kubeblocks --with-logs
    95  
    96  	# report KubeBlocks information with logs and mask sensitive info
    97  	kbcli report kubeblocks --with-logs --mask
    98  	`)
    99  )
   100  
   101  var _ reportInterface = &reportKubeblocksOptions{}
   102  var _ reportInterface = &reportClusterOptions{}
   103  
   104  // NewReportCmd creates command for reports.
   105  func NewReportCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   106  	cmd := &cobra.Command{
   107  		Use:   "report [kubeblocks | cluster]",
   108  		Short: "report kubeblocks or cluster info.",
   109  	}
   110  	cmd.AddCommand(
   111  		newKubeblocksReportCmd(f, streams),
   112  		newClusterReportCmd(f, streams),
   113  	)
   114  	return cmd
   115  }
   116  
   117  type reportInterface interface {
   118  	handleEvents(ctx context.Context) error
   119  	handleLogs(ctx context.Context) error
   120  	handleManifests(ctx context.Context) error
   121  }
   122  type reportOptions struct {
   123  	genericiooptions.IOStreams
   124  	// file name to output
   125  	file string
   126  	// namespace of resource
   127  	namespace string
   128  	// include withLogs or not
   129  	withLogs bool
   130  	// followings flags are for logs
   131  	// all containers or main container only
   132  	allContainers bool
   133  	// since time
   134  	sinceTime string
   135  	// since second
   136  	sinceDuration time.Duration
   137  	// log options
   138  	logOptions *corev1.PodLogOptions
   139  	// various clients
   140  	genericClientSet *genericClientSet
   141  	// enableMask sensitive info or not
   142  	mask bool
   143  	// resource printer, default to YAML printer without managed fields
   144  	resourcePrinter printers.ResourcePrinterFunc
   145  	// JSONYamlPrintFlags is used to print JSON or YAML
   146  	JSONYamlPrintFlags *genericclioptions.JSONYamlPrintFlags
   147  	// outpout format	, default to YAML
   148  	outputFormat string
   149  	// reportWritter is used to write report to file
   150  	reportWritter reportWritter
   151  }
   152  
   153  type reportKubeblocksOptions struct {
   154  	reportOptions
   155  	kubeBlocksSelector metav1.ListOptions
   156  }
   157  
   158  type reportClusterOptions struct {
   159  	reportOptions
   160  	clusterName     string
   161  	clusterSelector metav1.ListOptions
   162  	cluster         *appsv1alpha1.Cluster
   163  }
   164  
   165  func newReportOptions(f genericiooptions.IOStreams) reportOptions {
   166  	return reportOptions{
   167  		IOStreams:          f,
   168  		JSONYamlPrintFlags: genericclioptions.NewJSONYamlPrintFlags(),
   169  	}
   170  }
   171  
   172  func (o *reportOptions) complete(f cmdutil.Factory) error {
   173  	var err error
   174  
   175  	if o.genericClientSet, err = NewGenericClientSet(f); err != nil {
   176  		return err
   177  	}
   178  
   179  	// complete log options
   180  	if o.logOptions, err = o.toLogOptions(); err != nil {
   181  		return err
   182  	}
   183  
   184  	// complete printer
   185  	if o.resourcePrinter, err = o.parsePrinter(); err != nil {
   186  		return err
   187  	}
   188  
   189  	o.reportWritter = &reportZipWritter{}
   190  	return nil
   191  }
   192  
   193  func (o *reportOptions) validate() error {
   194  	// make sure sinceTime and sinceSeconds are not both set
   195  	if len(o.sinceTime) > 0 && o.sinceDuration != 0 {
   196  		return fmt.Errorf("only one of --since-time / --since may be used")
   197  	}
   198  	if (!o.withLogs) && (len(o.sinceTime) > 0 || o.sinceDuration != 0 || o.allContainers) {
   199  		return fmt.Errorf("--since-time / --since / --all-contaiiners can only be used when --with-logs is set")
   200  	}
   201  	o.outputFormat = strings.ToLower(o.outputFormat)
   202  	if slices.Index(o.JSONYamlPrintFlags.AllowedFormats(), o.outputFormat) == -1 {
   203  		return fmt.Errorf("output format %s is not supported", o.outputFormat)
   204  	}
   205  	return nil
   206  }
   207  
   208  func (o *reportOptions) addFlags(cmd *cobra.Command) {
   209  	cmd.Flags().StringVarP(&o.file, "file", "f", "", "zip file for output")
   210  	cmd.Flags().BoolVar(&o.mask, "mask", true, "mask sensitive info for secrets and configmaps")
   211  	cmd.Flags().BoolVar(&o.withLogs, "with-logs", false, "include pod logs")
   212  	cmd.Flags().BoolVar(&o.allContainers, "all-containers", o.allContainers, "Get all containers' logs in the pod(s). Byt default, only the main container (the first container) will have logs recorded.")
   213  	cmd.Flags().StringVar(&o.sinceTime, "since-time", o.sinceTime, i18n.T("Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used."))
   214  	cmd.Flags().DurationVar(&o.sinceDuration, "since", o.sinceDuration, "Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs. Only one of since-time / since may be used.")
   215  
   216  	cmd.Flags().StringVarP(&o.outputFormat, "output", "o", "json", fmt.Sprintf("Output format. One of: %s.", strings.Join(o.JSONYamlPrintFlags.AllowedFormats(), "|")))
   217  	cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc("output", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
   218  		return o.JSONYamlPrintFlags.AllowedFormats(), cobra.ShellCompDirectiveNoFileComp
   219  	}))
   220  }
   221  
   222  func (o *reportOptions) toLogOptions() (*corev1.PodLogOptions, error) {
   223  	logOptions := &corev1.PodLogOptions{}
   224  
   225  	if len(o.sinceTime) > 0 {
   226  		t, err := util.ParseRFC3339(o.sinceTime, metav1.Now)
   227  		if err != nil {
   228  			return nil, err
   229  		}
   230  		logOptions.SinceTime = &t
   231  	}
   232  
   233  	if o.sinceDuration != 0 {
   234  		sec := int64(o.sinceDuration.Round(time.Second).Seconds())
   235  		logOptions.SinceSeconds = &sec
   236  	}
   237  
   238  	return logOptions, nil
   239  }
   240  
   241  func (o *reportOptions) parsePrinter() (printers.ResourcePrinterFunc, error) {
   242  	var err error
   243  	// by default, use YAML printer without managed fields
   244  	printer, err := o.JSONYamlPrintFlags.ToPrinter(o.outputFormat)
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  	// wrap printer with typesetter
   249  	typeSetterPrinter := printers.NewTypeSetter(clischeme.Scheme)
   250  	if printer, err = typeSetterPrinter.WrapToPrinter(printer, nil); err != nil {
   251  		return nil, err
   252  	}
   253  	// if mask is enabled, wrap printer with mask printer
   254  	if o.mask {
   255  		printer = &MaskPrinter{Delegate: printer}
   256  	}
   257  	return printer.PrintObj, nil
   258  }
   259  
   260  func newKubeblocksReportCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   261  	o := &reportKubeblocksOptions{reportOptions: newReportOptions(streams)}
   262  	cmd := &cobra.Command{
   263  		Use:     "kubeblocks [-f file] [--with-logs] [--mask]",
   264  		Aliases: []string{"kb"},
   265  		Short:   "Report KubeBlocks information, including deployments, events, logs, etc.",
   266  		Args:    cobra.NoArgs,
   267  		Example: reportKBExamples,
   268  		Run: func(cmd *cobra.Command, args []string) {
   269  			cliutil.CheckErr(o.validate())
   270  			cliutil.CheckErr(o.complete(f))
   271  			cliutil.CheckErr(o.run(f, streams))
   272  		},
   273  	}
   274  	o.addFlags(cmd)
   275  	return cmd
   276  }
   277  
   278  func (o *reportKubeblocksOptions) complete(f cmdutil.Factory) error {
   279  	if err := o.reportOptions.complete(f); err != nil {
   280  		return err
   281  	}
   282  	o.namespace, _ = cliutil.GetKubeBlocksNamespace(o.genericClientSet.client)
   283  	// complete file name
   284  	o.file = formatReportName(o.file, kubeBlocksReport)
   285  	if exists, _ := cliutil.FileExists(o.file); exists {
   286  		return fmt.Errorf("file already exist will not overwrite")
   287  	}
   288  	// complete kb selector
   289  	o.kubeBlocksSelector = metav1.ListOptions{LabelSelector: buildKubeBlocksSelector()}
   290  	return nil
   291  }
   292  
   293  func (o *reportKubeblocksOptions) run(f cmdutil.Factory, streams genericiooptions.IOStreams) error {
   294  	ctx, cancel := context.WithCancel(context.Background())
   295  	defer cancel()
   296  
   297  	if err := o.reportWritter.Init(o.file, o.resourcePrinter); err != nil {
   298  		return err
   299  	}
   300  	defer func() {
   301  		if err := o.reportWritter.Close(); err != nil {
   302  			klog.Errorf("close zip file error: %v", err)
   303  		}
   304  	}()
   305  
   306  	fmt.Fprintf(o.Out, "reporting KubeBlocks information to %s\n", o.file)
   307  
   308  	if err := o.reportWritter.WriteKubeBlocksVersion(versionFile, o.genericClientSet.client); err != nil {
   309  		return err
   310  	}
   311  	if err := o.handleManifests(ctx); err != nil {
   312  		return err
   313  	}
   314  	if err := o.handleEvents(ctx); err != nil {
   315  		return err
   316  	}
   317  	if err := o.handleLogs(ctx); err != nil {
   318  		return err
   319  	}
   320  
   321  	return nil
   322  }
   323  
   324  func (o *reportKubeblocksOptions) handleManifests(ctx context.Context) error {
   325  	var (
   326  		scopedgvrs = []schema.GroupVersionResource{
   327  			types.DeployGVR(),
   328  			types.StatefulSetGVR(),
   329  			types.ConfigmapGVR(),
   330  			types.SecretGVR(),
   331  			types.ServiceGVR(),
   332  			types.RoleGVR(),
   333  			types.RoleBindingGVR(),
   334  		}
   335  
   336  		globalGvrs = []schema.GroupVersionResource{
   337  			types.AddonGVR(),
   338  			types.ClusterDefGVR(),
   339  			types.ClusterRoleGVR(),
   340  			types.ClusterRoleBindingGVR(),
   341  		}
   342  	)
   343  	// write manifest
   344  	s := spinner.New(o.Out, spinnerMsg("processing manifests"))
   345  	defer s.Fail()
   346  	// get namespaced resources
   347  	allErrors := make([]error, 0)
   348  	resourceLists := make([]*unstructured.UnstructuredList, 0)
   349  	resourceLists = append(resourceLists, cliutil.ListResourceByGVR(ctx, o.genericClientSet.dynamic, o.namespace, scopedgvrs, []metav1.ListOptions{o.kubeBlocksSelector}, &allErrors)...)
   350  	// get global resources
   351  	resourceLists = append(resourceLists, cliutil.ListResourceByGVR(ctx, o.genericClientSet.dynamic, metav1.NamespaceAll, globalGvrs, []metav1.ListOptions{o.kubeBlocksSelector}, &allErrors)...)
   352  	// get all storage class
   353  	resourceLists = append(resourceLists, cliutil.ListResourceByGVR(ctx, o.genericClientSet.dynamic, metav1.NamespaceAll, []schema.GroupVersionResource{types.StorageClassGVR()}, []metav1.ListOptions{{}}, &allErrors)...)
   354  	if err := o.reportWritter.WriteObjects(manifestsFolder, resourceLists, o.outputFormat); err != nil {
   355  		return err
   356  	}
   357  	s.Success()
   358  	return utilerrors.NewAggregate(allErrors)
   359  }
   360  
   361  func (o *reportKubeblocksOptions) handleEvents(ctx context.Context) error {
   362  	// write events
   363  	s := spinner.New(o.Out, spinnerMsg("processing events"))
   364  	defer s.Fail()
   365  
   366  	// get all events under kubeblocks namespace
   367  	if events, err := o.genericClientSet.client.CoreV1().Events(o.namespace).List(ctx, metav1.ListOptions{}); err != nil {
   368  		return err
   369  	} else {
   370  		eventMap := map[string][]corev1.Event{o.namespace + "-kubeblocks": events.Items}
   371  		if err := o.reportWritter.WriteEvents(eventsFolder, eventMap, o.outputFormat); err != nil {
   372  			return err
   373  		}
   374  	}
   375  	s.Success()
   376  	return nil
   377  }
   378  
   379  func (o *reportKubeblocksOptions) handleLogs(ctx context.Context) error {
   380  	if !o.withLogs {
   381  		return nil
   382  	}
   383  	s := spinner.New(o.Out, spinnerMsg("process pod logs"))
   384  	defer s.Fail()
   385  	// write logs
   386  	podList, err := o.genericClientSet.client.CoreV1().Pods(o.namespace).List(ctx, o.kubeBlocksSelector)
   387  	if err != nil {
   388  		return err
   389  	}
   390  
   391  	if err := o.reportWritter.WriteLogs(logsFolder, ctx, o.genericClientSet.client, podList, *o.logOptions, o.allContainers); err != nil {
   392  		return err
   393  	}
   394  	s.Success()
   395  	return nil
   396  }
   397  
   398  func newClusterReportCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   399  	o := &reportClusterOptions{reportOptions: newReportOptions(streams)}
   400  
   401  	cmd := &cobra.Command{
   402  		Use:               "cluster NAME [-f file] [-with-logs] [-mask]",
   403  		Short:             "Report Cluster information",
   404  		Example:           reportClusterExamples,
   405  		ValidArgsFunction: cliutil.ResourceNameCompletionFunc(f, types.ClusterGVR()),
   406  		Run: func(cmd *cobra.Command, args []string) {
   407  			cliutil.CheckErr(o.validate(args))
   408  			cliutil.CheckErr(o.complete(f))
   409  			cliutil.CheckErr(o.run(f, streams))
   410  		},
   411  	}
   412  	o.addFlags(cmd)
   413  	return cmd
   414  }
   415  
   416  func (o *reportClusterOptions) validate(args []string) error {
   417  	if err := o.reportOptions.validate(); err != nil {
   418  		return err
   419  	}
   420  	if len(args) != 1 {
   421  		return fmt.Errorf("only ONE cluster name is allowed")
   422  	}
   423  	o.clusterName = args[0]
   424  	return nil
   425  }
   426  
   427  func (o *reportClusterOptions) complete(f cmdutil.Factory) error {
   428  	var err error
   429  	if err := o.reportOptions.complete(f); err != nil {
   430  		return err
   431  	}
   432  	// update namespace
   433  	o.namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
   434  	if err != nil {
   435  		return err
   436  	}
   437  	// complete file name
   438  
   439  	o.file = formatReportName(o.file, fmt.Sprintf("%s-%s", clusterReport, o.clusterName))
   440  
   441  	if exists, _ := cliutil.FileExists(o.file); exists {
   442  		return fmt.Errorf("file already exist will not overwrite")
   443  	}
   444  
   445  	o.clusterSelector = metav1.ListOptions{LabelSelector: buildClusterResourceSelector(o.clusterName)}
   446  	return nil
   447  }
   448  
   449  func (o *reportClusterOptions) run(f cmdutil.Factory, streams genericiooptions.IOStreams) error {
   450  	ctx, cancel := context.WithCancel(context.Background())
   451  	defer cancel()
   452  	var err error
   453  	// make cluster exists before processing
   454  	if _, err = o.genericClientSet.kbClientSet.AppsV1alpha1().Clusters(o.namespace).Get(ctx, o.clusterName, metav1.GetOptions{}); err != nil {
   455  		return err
   456  	}
   457  
   458  	if err := o.reportWritter.Init(o.file, o.resourcePrinter); err != nil {
   459  		return err
   460  	}
   461  	defer func() {
   462  		if err := o.reportWritter.Close(); err != nil {
   463  			klog.Errorf("close zip file error: %v", err)
   464  		}
   465  	}()
   466  
   467  	fmt.Fprintf(o.Out, "reporting cluster information to %s\n", o.file)
   468  
   469  	if err := o.reportWritter.WriteKubeBlocksVersion(versionFile, o.genericClientSet.client); err != nil {
   470  		return err
   471  	}
   472  	if err := o.handleManifests(ctx); err != nil {
   473  		return err
   474  	}
   475  	if err := o.handleEvents(ctx); err != nil {
   476  		return err
   477  	}
   478  	if err := o.handleLogs(ctx); err != nil {
   479  		return err
   480  	}
   481  	return nil
   482  }
   483  
   484  func (o *reportClusterOptions) handleManifests(ctx context.Context) error {
   485  	var (
   486  		scopedgvrs = []schema.GroupVersionResource{
   487  			types.DeployGVR(),
   488  			types.StatefulSetGVR(),
   489  			types.ConfigmapGVR(),
   490  			types.SecretGVR(),
   491  			types.ServiceGVR(),
   492  			types.RoleGVR(),
   493  			types.RoleBindingGVR(),
   494  			types.BackupGVR(),
   495  			types.BackupPolicyGVR(),
   496  			types.BackupScheduleGVR(),
   497  			types.ActionSetGVR(),
   498  			types.RestoreGVR(),
   499  			types.PVCGVR(),
   500  		}
   501  		globalGvrs = []schema.GroupVersionResource{
   502  			types.PVGVR(),
   503  		}
   504  	)
   505  
   506  	var err error
   507  	if o.cluster, err = o.genericClientSet.kbClientSet.AppsV1alpha1().Clusters(o.namespace).Get(ctx, o.clusterName, metav1.GetOptions{}); err != nil {
   508  		return err
   509  	}
   510  
   511  	// write manifest
   512  	s := spinner.New(o.Out, spinnerMsg("processing manifests"))
   513  	defer s.Fail()
   514  
   515  	allErrors := make([]error, 0)
   516  	// get namespaced resources
   517  	resourceLists := make([]*unstructured.UnstructuredList, 0)
   518  	// write manifest
   519  	resourceLists = append(resourceLists, cliutil.ListResourceByGVR(ctx, o.genericClientSet.dynamic, o.namespace, scopedgvrs, []metav1.ListOptions{o.clusterSelector}, &allErrors)...)
   520  	resourceLists = append(resourceLists, cliutil.ListResourceByGVR(ctx, o.genericClientSet.dynamic, metav1.NamespaceAll, globalGvrs, []metav1.ListOptions{o.clusterSelector}, &allErrors)...)
   521  	if err := o.reportWritter.WriteObjects("manifests", resourceLists, o.outputFormat); err != nil {
   522  		return err
   523  	}
   524  
   525  	if err := o.reportWritter.WriteSingleObject(manifestsFolder, types.KindCluster, o.cluster.Name, o.cluster, o.outputFormat); err != nil {
   526  		return err
   527  	}
   528  
   529  	// get cluster definition
   530  	clusterDefName := o.cluster.Spec.ClusterDefRef
   531  	if clusterDef, err := o.genericClientSet.kbClientSet.AppsV1alpha1().ClusterDefinitions().Get(ctx, clusterDefName, metav1.GetOptions{}); err != nil {
   532  		return err
   533  	} else if err = o.reportWritter.WriteSingleObject(manifestsFolder, types.KindClusterDef, clusterDef.Name, clusterDef, o.outputFormat); err != nil {
   534  		return err
   535  	}
   536  
   537  	// get cluster version
   538  	clusterVersionName := o.cluster.Spec.ClusterVersionRef
   539  	if clusterVersion, err := o.genericClientSet.kbClientSet.AppsV1alpha1().ClusterVersions().Get(ctx, clusterVersionName, metav1.GetOptions{}); err != nil {
   540  		return err
   541  	} else if err = o.reportWritter.WriteSingleObject(manifestsFolder, types.KindClusterVersion, clusterVersion.Name, clusterVersion, o.outputFormat); err != nil {
   542  		return err
   543  	}
   544  
   545  	s.Success()
   546  	return nil
   547  }
   548  
   549  func (o *reportClusterOptions) handleEvents(ctx context.Context) error {
   550  	s := spinner.New(o.Out, spinnerMsg("processing events"))
   551  	defer s.Fail()
   552  
   553  	events := make(map[string][]corev1.Event, 0)
   554  	// get all events of cluster
   555  	clusterEvents, err := o.genericClientSet.client.CoreV1().Events(o.namespace).List(ctx, metav1.ListOptions{FieldSelector: fmt.Sprintf("involvedObject.name=%s", o.clusterName)})
   556  	if err != nil {
   557  		return err
   558  	}
   559  	events["cluster-"+o.clusterName] = clusterEvents.Items
   560  
   561  	// get all events for pods
   562  	podList, err := o.genericClientSet.client.CoreV1().Pods(o.namespace).List(ctx, o.clusterSelector)
   563  	if err != nil {
   564  		return err
   565  	}
   566  
   567  	for _, pod := range podList.Items {
   568  		if podEvents, err := o.genericClientSet.client.CoreV1().Events(o.namespace).List(ctx, metav1.ListOptions{FieldSelector: fmt.Sprintf("involvedObject.name=%s", pod.Name)}); err != nil {
   569  			return err
   570  		} else {
   571  			events["pod-"+pod.Name] = podEvents.Items
   572  		}
   573  	}
   574  
   575  	if err = o.reportWritter.WriteEvents(eventsFolder, events, o.outputFormat); err != nil {
   576  		return err
   577  	}
   578  	s.Success()
   579  	return nil
   580  }
   581  
   582  func (o *reportClusterOptions) handleLogs(ctx context.Context) error {
   583  	if !o.withLogs {
   584  		return nil
   585  	}
   586  
   587  	s := spinner.New(o.Out, spinnerMsg("process pod logs"))
   588  	defer s.Fail()
   589  
   590  	// get all events for pods
   591  	if podList, err := o.genericClientSet.client.CoreV1().Pods(o.namespace).List(ctx, o.clusterSelector); err != nil {
   592  		return err
   593  	} else if err := o.reportWritter.WriteLogs(logsFolder, ctx, o.genericClientSet.client, podList, *o.logOptions, o.allContainers); err != nil {
   594  		return err
   595  	}
   596  
   597  	s.Success()
   598  	return nil
   599  }
   600  
   601  func spinnerMsg(format string, a ...any) spinner.Option {
   602  	return spinner.WithMessage(fmt.Sprintf("%-50s", fmt.Sprintf(format, a...)))
   603  }
   604  
   605  func formatReportName(fileName string, kind string) string {
   606  	if len(fileName) > 0 {
   607  		return fileName
   608  	}
   609  	return fmt.Sprintf("report-%s-%s.zip", kind, time.Now().Local().Format("2006-01-02-15-04-05"))
   610  }
   611  
   612  func buildClusterResourceSelector(clusterName string) string {
   613  	// app.kubernetes.io/instance: <clusterName>
   614  	// app.kubernetes.io/managed-by: kubeblocks
   615  	return fmt.Sprintf("%s=%s, %s=%s", constant.AppInstanceLabelKey, clusterName, constant.AppManagedByLabelKey, constant.AppName)
   616  }
   617  
   618  func buildKubeBlocksSelector() string {
   619  	// app.kubernetes.io/name: kubeblocks
   620  	// app.kubernetes.io/instance: kubeblocks
   621  	return fmt.Sprintf("%s=%s,%s=%s",
   622  		constant.AppInstanceLabelKey, types.KubeBlocksReleaseName,
   623  		constant.AppNameLabelKey, types.KubeBlocksChartName)
   624  }