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
}