github.com/verrazzano/verrazzano@v1.7.1/tools/vz/cmd/analyze/analyze.go (about)

     1  // Copyright (c) 2022, 2024, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package analyze
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  
    11  	"github.com/spf13/cobra"
    12  	cmdhelpers "github.com/verrazzano/verrazzano/tools/vz/cmd/helpers"
    13  	"github.com/verrazzano/verrazzano/tools/vz/pkg/analysis"
    14  	vzbugreport "github.com/verrazzano/verrazzano/tools/vz/pkg/bugreport"
    15  	"github.com/verrazzano/verrazzano/tools/vz/pkg/constants"
    16  	"github.com/verrazzano/verrazzano/tools/vz/pkg/helpers"
    17  )
    18  
    19  const (
    20  	CommandName = "analyze"
    21  	helpShort   = "Analyze cluster"
    22  	helpLong    = `Analyze cluster for identifying issues and providing advice`
    23  	helpExample = `
    24  # Run analysis tool on captured directory
    25  vz analyze --capture-dir <path>
    26  
    27  # Run analysis tool on the live cluster
    28  vz analyze
    29  `
    30  )
    31  
    32  type directoryAndTarValidationStruct struct {
    33  	directory  string
    34  	tarFile    string
    35  	reportFile string
    36  	isVerbose  bool
    37  }
    38  
    39  func NewCmdAnalyze(vzHelper helpers.VZHelper) *cobra.Command {
    40  	cmd := cmdhelpers.NewCommand(vzHelper, CommandName, helpShort, helpLong)
    41  	cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
    42  		return validateReportFormat(cmd)
    43  	}
    44  	cmd.RunE = func(cmd *cobra.Command, args []string) error {
    45  		return RunCmdAnalyze(cmd, vzHelper, true)
    46  	}
    47  
    48  	cmd.Example = helpExample
    49  	cmd.PersistentFlags().String(constants.DirectoryFlagName, constants.DirectoryFlagValue, constants.DirectoryFlagUsage)
    50  	cmd.PersistentFlags().String(constants.ReportFileFlagName, constants.ReportFileFlagValue, constants.ReportFileFlagUsage)
    51  	cmd.PersistentFlags().String(constants.TarFileFlagName, constants.TarFileFlagValue, constants.TarFileFlagUsage)
    52  	cmd.PersistentFlags().String(constants.ReportFormatFlagName, constants.SummaryReport, constants.ReportFormatFlagUsage)
    53  	cmd.PersistentFlags().BoolP(constants.VerboseFlag, constants.VerboseFlagShorthand, constants.VerboseFlagDefault, constants.VerboseFlagUsage)
    54  
    55  	// Verifies that the CLI args are not set at the creation of a command
    56  	vzHelper.VerifyCLIArgsNil(cmd)
    57  
    58  	return cmd
    59  }
    60  
    61  // analyzeLiveCluster Analyzes live cluster by capturing the snapshot, when capture-dir is not set
    62  func analyzeLiveCluster(cmd *cobra.Command, vzHelper helpers.VZHelper, directory string) error {
    63  	// Get the kubernetes clientset, which will validate that the kubeconfig and context are valid.
    64  	kubeClient, err := vzHelper.GetKubeClient(cmd)
    65  	if err != nil {
    66  		return err
    67  	}
    68  
    69  	// Get the dynamic client to retrieve OAM resources
    70  	dynamicClient, err := vzHelper.GetDynamicClient(cmd)
    71  	if err != nil {
    72  		return err
    73  	}
    74  
    75  	// Get the controller runtime client
    76  	client, err := vzHelper.GetClient(cmd)
    77  	if err != nil {
    78  		return err
    79  	}
    80  
    81  	// Create a directory for the analyze command
    82  	reportDirectory := filepath.Join(directory, constants.BugReportRoot)
    83  	err = os.MkdirAll(reportDirectory, os.ModePerm)
    84  	if err != nil {
    85  		return fmt.Errorf("an error occurred while creating the directory %s: %s", reportDirectory, err.Error())
    86  	}
    87  
    88  	// Get the list of namespaces with label verrazzano-managed=true, where the applications are deployed
    89  	moreNS := helpers.GetVZManagedNamespaces(kubeClient)
    90  
    91  	// Instruct the helper to display the message for analyzing the live cluster
    92  	helpers.SetIsLiveCluster()
    93  
    94  	// Capture cluster snapshot
    95  	podLogs := helpers.PodLogs{
    96  		IsPodLog: true,
    97  		Duration: int64(0),
    98  	}
    99  	clusterSnapshotCtx := helpers.ClusterSnapshotCtx{BugReportDir: reportDirectory, MoreNS: moreNS, PrintReportToConsole: true}
   100  	return vzbugreport.CaptureClusterSnapshot(kubeClient, dynamicClient, client, vzHelper, podLogs, clusterSnapshotCtx)
   101  }
   102  
   103  func RunCmdAnalyze(cmd *cobra.Command, vzHelper helpers.VZHelper, printReportToConsole bool) error {
   104  	validatedStruct, err := parseFlags(cmd, vzHelper, constants.DirectoryFlagName, constants.TarFileFlagName, constants.ReportFileFlagName, constants.VerboseFlag)
   105  	if err != nil {
   106  		return err
   107  	}
   108  	reportFormat := getReportFormat(cmd)
   109  
   110  	// set the flag to control the display the resources captured
   111  	helpers.SetVerboseOutput(validatedStruct.isVerbose)
   112  	if validatedStruct.directory == "" {
   113  		// Create a temporary directory to place the generated files, which will also be the input for analyze command
   114  		validatedStruct.directory, err = os.MkdirTemp("", constants.BugReportDir)
   115  		defer os.RemoveAll(validatedStruct.directory)
   116  		if err != nil {
   117  			return fmt.Errorf("an error occurred while creating the directory to place cluster resources: %s", err.Error())
   118  		}
   119  
   120  		if validatedStruct.tarFile == "" {
   121  			if err := analyzeLiveCluster(cmd, vzHelper, validatedStruct.directory); err != nil {
   122  				return err
   123  			}
   124  		} else {
   125  			//This is the case where only the tar string is specified
   126  			file, err := os.Open(validatedStruct.tarFile)
   127  			defer file.Close()
   128  			if err != nil {
   129  				return fmt.Errorf("an error occurred when trying to open %s: %s", validatedStruct.tarFile, err.Error())
   130  			}
   131  			err = helpers.UntarArchive(validatedStruct.directory, file)
   132  			if err != nil {
   133  				return fmt.Errorf("an error occurred while trying to untar %s: %s", validatedStruct.tarFile, err.Error())
   134  			}
   135  		}
   136  	}
   137  	return analysis.AnalysisMain(vzHelper, validatedStruct.directory, validatedStruct.reportFile, reportFormat, printReportToConsole)
   138  }
   139  
   140  // This function validates the directory and tar file flags along with checking that the directory flag and the tar file are not both specified
   141  func parseFlags(cmd *cobra.Command, vzHelper helpers.VZHelper, directoryFlagValue string, tarFlagValue string, reportFileFlagValue string, verboseFlagValue string) (*directoryAndTarValidationStruct, error) {
   142  	directory, err := cmd.PersistentFlags().GetString(directoryFlagValue)
   143  	if err != nil {
   144  		return nil, fmt.Errorf(constants.FlagErrorMessage, constants.DirectoryFlagName, err.Error())
   145  	}
   146  	tarFileString, err := cmd.PersistentFlags().GetString(tarFlagValue)
   147  	if err != nil {
   148  		return nil, fmt.Errorf(constants.FlagErrorMessage, constants.TarFileFlagName, err.Error())
   149  	}
   150  	if directory != "" && tarFileString != "" {
   151  		return nil, fmt.Errorf("a directory and a tar file cannot be both specified")
   152  	}
   153  	isVerbose, err := cmd.PersistentFlags().GetBool(verboseFlagValue)
   154  	if err != nil {
   155  		return nil, fmt.Errorf(constants.FlagErrorMessage, constants.VerboseFlag, err.Error())
   156  	}
   157  	if err := setVzK8sVersion(tarFileString, directory, vzHelper, cmd); err == nil {
   158  		fmt.Fprintf(vzHelper.GetOutputStream(), helpers.GetVersionOut())
   159  	}
   160  	reportFileName, err := cmd.PersistentFlags().GetString(reportFileFlagValue)
   161  	if err != nil {
   162  		fmt.Fprintf(vzHelper.GetOutputStream(), "error getting the report file name: %s", err.Error())
   163  	}
   164  	return &directoryAndTarValidationStruct{directory: directory, tarFile: tarFileString, reportFile: reportFileName, isVerbose: isVerbose}, nil
   165  }
   166  
   167  // setVzK8sVersion sets vz and k8s version
   168  func setVzK8sVersion(tarFileString string, directory string, vzHelper helpers.VZHelper, cmd *cobra.Command) error {
   169  	if directory == "" && tarFileString == "" {
   170  		// Get the controller runtime client
   171  		client, err := vzHelper.GetClient(cmd)
   172  		if err != nil {
   173  			return err
   174  		}
   175  		// set vz version
   176  		if err := helpers.SetVzVer(&client); err != nil {
   177  			return err
   178  		}
   179  		// set cluster k8s version
   180  		if err := helpers.SetK8sVer(); err != nil {
   181  			return err
   182  		}
   183  		// print k8s and vz version on console stdout
   184  		return nil
   185  	}
   186  	return fmt.Errorf("cannot set vz and k8s version")
   187  }
   188  
   189  // validateReportFormat validates the value specified for flag report-format
   190  func validateReportFormat(cmd *cobra.Command) error {
   191  	reportFormatValue := getReportFormat(cmd)
   192  	switch reportFormatValue {
   193  	case constants.SummaryReport, constants.DetailedReport:
   194  		return nil
   195  	default:
   196  		return fmt.Errorf("%q is not valid for flag report-format, only %q and %q are valid", reportFormatValue, constants.SummaryReport, constants.DetailedReport)
   197  	}
   198  }
   199  
   200  // getReportFormat returns the value set for flag report-format
   201  func getReportFormat(cmd *cobra.Command) string {
   202  	reportFormat := cmd.PersistentFlags().Lookup(constants.ReportFormatFlagName)
   203  	if reportFormat == nil {
   204  		return constants.SummaryReport
   205  	}
   206  	return reportFormat.Value.String()
   207  }