func Analyze()

in istioctl/cmd/analyze.go [86:324]


func Analyze() *cobra.Command {
	analysisCmd := &cobra.Command{
		Use:   "analyze <file>...",
		Short: "Analyze Istio configuration and print validation messages",
		Example: `  # Analyze the current live cluster
  istioctl analyze

  # Analyze the current live cluster, simulating the effect of applying additional yaml files
  istioctl analyze a.yaml b.yaml my-app-config/

  # Analyze the current live cluster, simulating the effect of applying a directory of config recursively
  istioctl analyze --recursive my-istio-config/

  # Analyze yaml files without connecting to a live cluster
  istioctl analyze --use-kube=false a.yaml b.yaml my-app-config/

  # Analyze the current live cluster and suppress PodMissingProxy for pod mypod in namespace 'testing'.
  istioctl analyze -S "IST0103=Pod mypod.testing"

  # Analyze the current live cluster and suppress PodMissingProxy for all pods in namespace 'testing',
  # and suppress MisplacedAnnotation on deployment foobar in namespace default.
  istioctl analyze -S "IST0103=Pod *.testing" -S "IST0107=Deployment foobar.default"

  # List available analyzers
  istioctl analyze -L`,
		RunE: func(cmd *cobra.Command, args []string) error {
			msgOutputFormat = strings.ToLower(msgOutputFormat)
			_, ok := formatting.MsgOutputFormats[msgOutputFormat]
			if !ok {
				return CommandParseError{
					fmt.Errorf("%s not a valid option for format. See istioctl analyze --help", msgOutputFormat),
				}
			}

			if listAnalyzers {
				fmt.Print(AnalyzersAsString(analyzers.All()))
				return nil
			}

			readers, err := gatherFiles(cmd, args)
			if err != nil {
				return err
			}
			cancel := make(chan struct{})

			// We use the "namespace" arg that's provided as part of root istioctl as a flag for specifying what namespace to use
			// for file resources that don't have one specified.
			selectedNamespace = handlers.HandleNamespace(namespace, defaultNamespace)

			// check whether selected namespace exists.
			if namespace != "" && useKube {
				client, err := kube.NewExtendedClient(kube.BuildClientCmd(kubeconfig, configContext), "")
				if err != nil {
					return err
				}
				_, err = client.CoreV1().Namespaces().Get(context.TODO(), namespace, v1.GetOptions{})
				if errors.IsNotFound(err) {
					fmt.Fprintf(cmd.ErrOrStderr(), "namespace %q not found\n", namespace)
					return nil
				}
			}

			// If we've explicitly asked for all namespaces, blank the selectedNamespace var out
			if allNamespaces {
				selectedNamespace = ""
			}

			sa := local.NewIstiodAnalyzer(analyzers.AllCombined(),
				resource.Namespace(selectedNamespace),
				resource.Namespace(istioNamespace), nil, true)

			// Check for suppressions and add them to our SourceAnalyzer
			suppressions := make([]local.AnalysisSuppression, 0, len(suppress))
			for _, s := range suppress {
				parts := strings.Split(s, "=")
				if len(parts) != 2 {
					return fmt.Errorf("%s is not a valid suppression value. See istioctl analyze --help", s)
				}
				// Check to see if the supplied code is valid. If not, emit a
				// warning but continue.
				codeIsValid := false
				for _, at := range msg.All() {
					if at.Code() == parts[0] {
						codeIsValid = true
						break
					}
				}

				if !codeIsValid {
					fmt.Fprintf(cmd.ErrOrStderr(), "Warning: Supplied message code '%s' is an unknown message code and will not have any effect.\n", parts[0])
				}
				suppressions = append(suppressions, local.AnalysisSuppression{
					Code:         parts[0],
					ResourceName: parts[1],
				})
			}
			sa.SetSuppressions(suppressions)

			// If we're using kube, use that as a base source.
			if useKube {
				// Set up the kube client
				restConfig, err := kube.DefaultRestConfig(kubeconfig, configContext)
				if err != nil {
					return err
				}
				k, err := kube.NewClient(kube.NewClientConfigForRestConfig(restConfig))
				if err != nil {
					return err
				}
				sa.AddRunningKubeSource(k)
			}

			// If we explicitly specify mesh config, use it.
			// This takes precedence over default mesh config or mesh config from a running Kube instance.
			if meshCfgFile != "" {
				_ = sa.AddFileKubeMeshConfig(meshCfgFile)
			}

			// If we're not using kube (files only), add defaults for some resources we expect to be provided by Istio
			if !useKube {
				err := sa.AddDefaultResources()
				if err != nil {
					return err
				}
			}

			// If files are provided, treat them (collectively) as a source.
			parseErrors := 0
			if len(readers) > 0 {
				if err = sa.AddReaderKubeSource(readers); err != nil {
					fmt.Fprintf(cmd.ErrOrStderr(), "Error(s) adding files: %v", err)
					parseErrors++
				}
			}

			// Do the analysis
			result, err := sa.Analyze(cancel)
			if err != nil {
				return err
			}

			// Maybe output details about which analyzers ran
			if verbose {
				fmt.Fprintf(cmd.ErrOrStderr(), "Analyzed resources in %s\n", analyzeTargetAsString())

				if len(result.SkippedAnalyzers) > 0 {
					fmt.Fprintln(cmd.ErrOrStderr(), "Skipped analyzers:")
					for _, a := range result.SkippedAnalyzers {
						fmt.Fprintln(cmd.ErrOrStderr(), "\t", a)
					}
				}
				if len(result.ExecutedAnalyzers) > 0 {
					fmt.Fprintln(cmd.ErrOrStderr(), "Executed analyzers:")
					for _, a := range result.ExecutedAnalyzers {
						fmt.Fprintln(cmd.ErrOrStderr(), "\t", a)
					}
				}
				fmt.Fprintln(cmd.ErrOrStderr())
			}

			// Get messages for output
			outputMessages := result.Messages.SetDocRef("istioctl-analyze").FilterOutLowerThan(outputThreshold.Level)

			// Print all the messages to stdout in the specified format
			output, err := formatting.Print(outputMessages, msgOutputFormat, colorize)
			if err != nil {
				return err
			}
			fmt.Fprintln(cmd.OutOrStdout(), output)

			// An extra message on success
			if len(outputMessages) == 0 {
				if parseErrors == 0 {
					if len(readers) > 0 {
						var files []string
						for _, r := range readers {
							files = append(files, r.Name)
						}
						fmt.Fprintf(cmd.ErrOrStderr(), "\u2714 No validation issues found when analyzing %s.\n", strings.Join(files, "\n"))
					} else {
						fmt.Fprintf(cmd.ErrOrStderr(), "\u2714 No validation issues found when analyzing %s.\n", analyzeTargetAsString())
					}
				} else {
					fileOrFiles := "files"
					if parseErrors == 1 {
						fileOrFiles = "file"
					}
					fmt.Fprintf(cmd.ErrOrStderr(),
						"No validation issues found when analyzing %s (but %d %s could not be parsed).\n",
						analyzeTargetAsString(),
						parseErrors,
						fileOrFiles,
					)
				}
			}

			// Return code is based on the unfiltered validation message list/parse errors
			// We're intentionally keeping failure threshold and output threshold decoupled for now
			var returnError error
			if msgOutputFormat == formatting.LogFormat {
				returnError = errorIfMessagesExceedThreshold(result.Messages)
				if returnError == nil && parseErrors > 0 && !ignoreUnknown {
					returnError = FileParseError{}
				}
			}
			return returnError
		},
	}

	analysisCmd.PersistentFlags().BoolVarP(&listAnalyzers, "list-analyzers", "L", false,
		"List the analyzers available to run. Suppresses normal execution.")
	analysisCmd.PersistentFlags().BoolVarP(&useKube, "use-kube", "k", true,
		"Use live Kubernetes cluster for analysis. Set --use-kube=false to analyze files only.")
	analysisCmd.PersistentFlags().BoolVar(&colorize, "color", formatting.IstioctlColorDefault(analysisCmd.OutOrStdout()),
		"Default true.  Disable with '=false' or set $TERM to dumb")
	analysisCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false,
		"Enable verbose output")
	analysisCmd.PersistentFlags().Var(&failureThreshold, "failure-threshold",
		fmt.Sprintf("The severity level of analysis at which to set a non-zero exit code. Valid values: %v", diag.GetAllLevelStrings()))
	analysisCmd.PersistentFlags().Var(&outputThreshold, "output-threshold",
		fmt.Sprintf("The severity level of analysis at which to display messages. Valid values: %v", diag.GetAllLevelStrings()))
	analysisCmd.PersistentFlags().StringVarP(&msgOutputFormat, "output", "o", formatting.LogFormat,
		fmt.Sprintf("Output format: one of %v", formatting.MsgOutputFormatKeys))
	analysisCmd.PersistentFlags().StringVar(&meshCfgFile, "meshConfigFile", "",
		"Overrides the mesh config values to use for analysis.")
	analysisCmd.PersistentFlags().BoolVarP(&allNamespaces, "all-namespaces", "A", false,
		"Analyze all namespaces")
	analysisCmd.PersistentFlags().StringArrayVarP(&suppress, "suppress", "S", []string{},
		"Suppress reporting a message code on a specific resource. Values are supplied in the form "+
			`<code>=<resource> (e.g. '--suppress "IST0102=DestinationRule primary-dr.default"'). Can be repeated. `+
			`You can include the wildcard character '*' to support a partial match (e.g. '--suppress "IST0102=DestinationRule *.default" ).`)
	analysisCmd.PersistentFlags().DurationVar(&analysisTimeout, "timeout", 30*time.Second,
		"The duration to wait before failing")
	analysisCmd.PersistentFlags().BoolVarP(&recursive, "recursive", "R", false,
		"Process directory arguments recursively. Useful when you want to analyze related manifests organized within the same directory.")
	analysisCmd.PersistentFlags().BoolVar(&ignoreUnknown, "ignore-unknown", false,
		"Don't complain about un-parseable input documents, for cases where analyze should run only on k8s compliant inputs.")
	return analysisCmd
}