Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 exit after showing help #468

Merged
merged 7 commits into from
Mar 4, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
313 changes: 161 additions & 152 deletions cmd/analyzer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,41 +40,9 @@ var (
analysisMode string
noDependencyRules bool
contextLines int

rootCmd = &cobra.Command{
Use: "analyze",
Short: "Tool for working with analyzer-lsp",
Run: func(c *cobra.Command, args []string) {},
}
)

func init() {
rootCmd.Flags().StringVar(&settingsFile, "provider-settings", "provider_settings.json", "path to the provider settings")
rootCmd.Flags().StringArrayVar(&rulesFile, "rules", []string{"rule-example.yaml"}, "filename or directory containing rule files")
rootCmd.Flags().StringVar(&outputViolations, "output-file", "output.yaml", "filepath to to store rule violations")
rootCmd.Flags().BoolVar(&errorOnViolations, "error-on-violation", false, "exit with 3 if any violation are found will also print violations to console")
rootCmd.Flags().StringVar(&labelSelector, "label-selector", "", "an expression to select rules based on labels")
rootCmd.Flags().StringVar(&depLabelSelector, "dep-label-selector", "", "an expression to select dependencies based on labels. This will filter out the violations from these dependencies as well these dependencies when matching dependency conditions")
rootCmd.Flags().StringVar(&incidentSelector, "incident-selector", "", "an expression to select incidents based on custom variables. ex: (!package=io.konveyor.demo.config-utils)")
rootCmd.Flags().IntVar(&logLevel, "verbose", 9, "level for logging output")
rootCmd.Flags().BoolVar(&enableJaeger, "enable-jaeger", false, "enable tracer exports to jaeger endpoint")
rootCmd.Flags().StringVar(&jaegerEndpoint, "jaeger-endpoint", "http://localhost:14268/api/traces", "jaeger endpoint to collect tracing data")
rootCmd.Flags().IntVar(&limitIncidents, "limit-incidents", 1500, "Set this to the limit incidents that a given rule can give, zero means no limit")
rootCmd.Flags().IntVar(&limitCodeSnips, "limit-code-snips", 20, "limit the number code snippets that are retrieved for a file while evaluating a rule, 0 means no limit")
rootCmd.Flags().StringVar(&analysisMode, "analysis-mode", "", "select one of full or source-only to tell the providers what to analyize. This can be given on a per provider setting, but this flag will override")
rootCmd.Flags().BoolVar(&noDependencyRules, "no-dependency-rules", false, "Disable dependency analysis rules")
rootCmd.Flags().IntVar(&contextLines, "context-lines", 10, "When violation occurs, A part of source code is added to the output, So this flag configures the number of source code lines to be printed to the output.")
}

func main() {
if err := rootCmd.Execute(); err != nil {
println(err.Error())
} else if rootCmd.Flags().Changed("help") {
return
}

// This will globally prevent the yaml library from auto-wrapping lines at 80 characters
yaml.FutureLineWrap()
func AnalysisCmd() *cobra.Command {

logrusLog := logrus.New()
logrusLog.SetOutput(os.Stdout)
Expand All @@ -87,142 +55,183 @@ func main() {
logrusErrLog.SetOutput(os.Stderr)
errLog := logrusr.New(logrusErrLog)

err := validateFlags()
if err != nil {
errLog.Error(err, "failed to validate flags")
os.Exit(1)
}
rootCmd := &cobra.Command{
Use: "analyze",
Short: "Tool for working with analyzer-lsp",
PreRunE: func(c *cobra.Command, args []string) error {
err := validateFlags()
if err != nil {
errLog.Error(err, "failed to validate flags")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can we return err here? and return nil after the if block? just making sure if we make changes in future, it doesn't go unnoticed that err could be non-nil after the if block

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!


ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
return err
}

selectors := []engine.RuleSelector{}
if labelSelector != "" {
selector, err := labels.NewLabelSelector[*engine.RuleMeta](labelSelector, nil)
if err != nil {
errLog.Error(err, "failed to create label selector from expression", "selector", labelSelector)
os.Exit(1)
}
selectors = append(selectors, selector)
}
return nil
},
Run: func(c *cobra.Command, args []string) {

var dependencyLabelSelector *labels.LabelSelector[*konveyor.Dep]
if depLabelSelector != "" {
dependencyLabelSelector, err = labels.NewLabelSelector[*konveyor.Dep](depLabelSelector, nil)
if err != nil {
errLog.Error(err, "failed to create label selector from expression", "selector", labelSelector)
os.Exit(1)
}
}
// This will globally prevent the yaml library from auto-wrapping lines at 80 characters
yaml.FutureLineWrap()

tracerOptions := tracing.Options{
EnableJaeger: enableJaeger,
JaegerEndpoint: jaegerEndpoint,
}
tp, err := tracing.InitTracerProvider(log, tracerOptions)
if err != nil {
errLog.Error(err, "failed to initialize tracing")
os.Exit(1)
}
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()

defer tracing.Shutdown(ctx, log, tp)

ctx, span := tracing.StartNewSpan(ctx, "main")
defer span.End()
selectors := []engine.RuleSelector{}
if labelSelector != "" {
selector, err := labels.NewLabelSelector[*engine.RuleMeta](labelSelector, nil)
if err != nil {
errLog.Error(err, "failed to create label selector from expression", "selector", labelSelector)
os.Exit(1)
}
selectors = append(selectors, selector)
}

// Get the configs
configs, err := provider.GetConfig(settingsFile)
if err != nil {
errLog.Error(err, "unable to get configuration")
os.Exit(1)
}
var dependencyLabelSelector *labels.LabelSelector[*konveyor.Dep]
var err error
if depLabelSelector != "" {
dependencyLabelSelector, err = labels.NewLabelSelector[*konveyor.Dep](depLabelSelector, nil)
if err != nil {
errLog.Error(err, "failed to create label selector from expression", "selector", labelSelector)
os.Exit(1)
}
}

//start up the rule eng
eng := engine.CreateRuleEngine(ctx,
10,
log,
engine.WithIncidentLimit(limitIncidents),
engine.WithCodeSnipLimit(limitCodeSnips),
engine.WithContextLines(contextLines),
engine.WithIncidentSelector(incidentSelector),
)

providers := map[string]provider.InternalProviderClient{}

for _, config := range configs {
config.ContextLines = contextLines
// IF analsyis mode is set from the CLI, then we will override this for each init config
if analysisMode != "" {
inits := []provider.InitConfig{}
for _, i := range config.InitConfig {
i.AnalysisMode = provider.AnalysisMode(analysisMode)
inits = append(inits, i)
tracerOptions := tracing.Options{
EnableJaeger: enableJaeger,
JaegerEndpoint: jaegerEndpoint,
}
config.InitConfig = inits
}
prov, err := lib.GetProviderClient(config, log)
if err != nil {
errLog.Error(err, "unable to create provider client")
os.Exit(1)
}
providers[config.Name] = prov
if s, ok := prov.(provider.Startable); ok {
if err := s.Start(ctx); err != nil {
errLog.Error(err, "unable to create provider client")
tp, err := tracing.InitTracerProvider(log, tracerOptions)
if err != nil {
errLog.Error(err, "failed to initialize tracing")
os.Exit(1)
}
}
}

parser := parser.RuleParser{
ProviderNameToClient: providers,
Log: log.WithName("parser"),
NoDependencyRules: noDependencyRules,
DepLabelSelector: dependencyLabelSelector,
}
ruleSets := []engine.RuleSet{}
needProviders := map[string]provider.InternalProviderClient{}
for _, f := range rulesFile {
internRuleSet, internNeedProviders, err := parser.LoadRules(f)
if err != nil {
log.WithValues("fileName", f).Error(err, "unable to parse all the rules for ruleset")
}
ruleSets = append(ruleSets, internRuleSet...)
for k, v := range internNeedProviders {
needProviders[k] = v
}
}
// Now that we have all the providers, we need to start them.
for name, provider := range needProviders {
err := provider.ProviderInit(ctx)
if err != nil {
errLog.Error(err, "unable to init the providers", "provider", name)
os.Exit(1)
}
}
defer tracing.Shutdown(ctx, log, tp)

rulesets := eng.RunRules(ctx, ruleSets, selectors...)
eng.Stop()
ctx, span := tracing.StartNewSpan(ctx, "main")
defer span.End()

for _, provider := range needProviders {
provider.Stop()
}
// Get the configs
configs, err := provider.GetConfig(settingsFile)
if err != nil {
errLog.Error(err, "unable to get configuration")
os.Exit(1)
}

//start up the rule eng
eng := engine.CreateRuleEngine(ctx,
10,
log,
engine.WithIncidentLimit(limitIncidents),
engine.WithCodeSnipLimit(limitCodeSnips),
engine.WithContextLines(contextLines),
engine.WithIncidentSelector(incidentSelector),
)

providers := map[string]provider.InternalProviderClient{}

for _, config := range configs {
config.ContextLines = contextLines
// IF analsyis mode is set from the CLI, then we will override this for each init config
if analysisMode != "" {
inits := []provider.InitConfig{}
for _, i := range config.InitConfig {
i.AnalysisMode = provider.AnalysisMode(analysisMode)
inits = append(inits, i)
}
config.InitConfig = inits
}
prov, err := lib.GetProviderClient(config, log)
if err != nil {
errLog.Error(err, "unable to create provider client")
os.Exit(1)
}
providers[config.Name] = prov
if s, ok := prov.(provider.Startable); ok {
if err := s.Start(ctx); err != nil {
errLog.Error(err, "unable to create provider client")
os.Exit(1)
}
}
}

parser := parser.RuleParser{
ProviderNameToClient: providers,
Log: log.WithName("parser"),
NoDependencyRules: noDependencyRules,
DepLabelSelector: dependencyLabelSelector,
}
ruleSets := []engine.RuleSet{}
needProviders := map[string]provider.InternalProviderClient{}
for _, f := range rulesFile {
internRuleSet, internNeedProviders, err := parser.LoadRules(f)
if err != nil {
log.WithValues("fileName", f).Error(err, "unable to parse all the rules for ruleset")
}
ruleSets = append(ruleSets, internRuleSet...)
for k, v := range internNeedProviders {
needProviders[k] = v
}
}
// Now that we have all the providers, we need to start them.
for name, provider := range needProviders {
err := provider.ProviderInit(ctx)
if err != nil {
errLog.Error(err, "unable to init the providers", "provider", name)
os.Exit(1)
}
}

rulesets := eng.RunRules(ctx, ruleSets, selectors...)
eng.Stop()

for _, provider := range needProviders {
provider.Stop()
}

sort.SliceStable(rulesets, func(i, j int) bool {
return rulesets[i].Name < rulesets[j].Name
})
sort.SliceStable(rulesets, func(i, j int) bool {
return rulesets[i].Name < rulesets[j].Name
})

// Write results out to CLI
b, _ := yaml.Marshal(rulesets)
if errorOnViolations && len(rulesets) != 0 {
fmt.Printf("%s", string(b))
os.Exit(EXIT_ON_ERROR_CODE)
// Write results out to CLI
b, _ := yaml.Marshal(rulesets)
if errorOnViolations && len(rulesets) != 0 {
fmt.Printf("%s", string(b))
os.Exit(EXIT_ON_ERROR_CODE)
}

err = os.WriteFile(outputViolations, b, 0644)
if err != nil {
errLog.Error(err, "error writing output file", "file", outputViolations)
os.Exit(1) // Treat the error as a fatal error
}
},
}

err = os.WriteFile(outputViolations, b, 0644)
if err != nil {
errLog.Error(err, "error writing output file", "file", outputViolations)
os.Exit(1) // Treat the error as a fatal error
rootCmd.Flags().StringVar(&settingsFile, "provider-settings", "provider_settings.json", "path to the provider settings")
rootCmd.Flags().StringArrayVar(&rulesFile, "rules", []string{"rule-example.yaml"}, "filename or directory containing rule files")
rootCmd.Flags().StringVar(&outputViolations, "output-file", "output.yaml", "filepath to to store rule violations")
rootCmd.Flags().BoolVar(&errorOnViolations, "error-on-violation", false, "exit with 3 if any violation are found will also print violations to console")
rootCmd.Flags().StringVar(&labelSelector, "label-selector", "", "an expression to select rules based on labels")
rootCmd.Flags().StringVar(&depLabelSelector, "dep-label-selector", "", "an expression to select dependencies based on labels. This will filter out the violations from these dependencies as well these dependencies when matching dependency conditions")
rootCmd.Flags().StringVar(&incidentSelector, "incident-selector", "", "an expression to select incidents based on custom variables. ex: (!package=io.konveyor.demo.config-utils)")
rootCmd.Flags().IntVar(&logLevel, "verbose", 9, "level for logging output")
rootCmd.Flags().BoolVar(&enableJaeger, "enable-jaeger", false, "enable tracer exports to jaeger endpoint")
rootCmd.Flags().StringVar(&jaegerEndpoint, "jaeger-endpoint", "http://localhost:14268/api/traces", "jaeger endpoint to collect tracing data")
rootCmd.Flags().IntVar(&limitIncidents, "limit-incidents", 1500, "Set this to the limit incidents that a given rule can give, zero means no limit")
rootCmd.Flags().IntVar(&limitCodeSnips, "limit-code-snips", 20, "limit the number code snippets that are retrieved for a file while evaluating a rule, 0 means no limit")
rootCmd.Flags().StringVar(&analysisMode, "analysis-mode", "", "select one of full or source-only to tell the providers what to analyize. This can be given on a per provider setting, but this flag will override")
rootCmd.Flags().BoolVar(&noDependencyRules, "no-dependency-rules", false, "Disable dependency analysis rules")
rootCmd.Flags().IntVar(&contextLines, "context-lines", 10, "When violation occurs, A part of source code is added to the output, So this flag configures the number of source code lines to be printed to the output.")

return rootCmd
}

func main() {
if err := AnalysisCmd().Execute(); err != nil {
os.Exit(1)
} else if AnalysisCmd().Flags().Changed("help") {
return
}
}

Expand Down
Loading