From 144a657af9a40ab379a9319ddbe7725f74681701 Mon Sep 17 00:00:00 2001 From: Emily McMullan Date: Fri, 14 Jun 2024 12:49:05 -0400 Subject: [PATCH] Use provider config options with default configs (#240) * merge provider config options with default config options Signed-off-by: Emily McMullan * update reading from configs Signed-off-by: Emily McMullan --------- Signed-off-by: Emily McMullan --- README.md | 1 + cmd/analyze.go | 176 ++++++++++++++++++++++++++++++++++++--------- cmd/settings.go | 1 - docs/usage.md | 18 +++++ golang.json.sample | 12 ++++ java.json.sample | 14 ++++ 6 files changed, 187 insertions(+), 35 deletions(-) create mode 100644 golang.json.sample create mode 100644 java.json.sample diff --git a/README.md b/README.md index e7898e68..7e499aa6 100644 --- a/README.md +++ b/README.md @@ -233,6 +233,7 @@ See different ways to run the test command in the [test runner doc](./docs/testr ## References - [Example usage scenarios](./docs/examples.md) +- [Using provider options](./docs/usage.md) - [Test runner for YAML rules](./docs/testrunner.md) ## Code of Conduct diff --git a/cmd/analyze.go b/cmd/analyze.go index 178fa508..48ee2118 100644 --- a/cmd/analyze.go +++ b/cmd/analyze.go @@ -56,6 +56,15 @@ const ( nodeJSProvider = "javascript" ) +// provider config options +const ( + mavenSettingsFile = "mavenSettingsFile" + lspServerPath = "lspServerPath" + lspServerName = "lspServerName" + workspaceFolders = "workspaceFolders" + dependencyProviderPath = "dependencyProviderPath" +) + // kantra analyze flags type analyzeCommand struct { listSources bool @@ -74,9 +83,6 @@ type analyzeCommand struct { rules []string jaegerEndpoint string enableDefaultRulesets bool - httpProxy string - httpsProxy string - noProxy string contextLines int incidentSelector string depFolders []string @@ -225,9 +231,6 @@ func NewAnalyzeCmd(log logr.Logger) *cobra.Command { analyzeCommand.Flags().BoolVar(&analyzeCmd.overwrite, "overwrite", false, "overwrite output directory") analyzeCommand.Flags().StringVar(&analyzeCmd.jaegerEndpoint, "jaeger-endpoint", "", "jaeger endpoint to collect traces") analyzeCommand.Flags().BoolVar(&analyzeCmd.enableDefaultRulesets, "enable-default-rulesets", true, "run default rulesets with analysis") - analyzeCommand.Flags().StringVar(&analyzeCmd.httpProxy, "http-proxy", loadEnvInsensitive("http_proxy"), "HTTP proxy string URL") - analyzeCommand.Flags().StringVar(&analyzeCmd.httpsProxy, "https-proxy", loadEnvInsensitive("https_proxy"), "HTTPS proxy string URL") - analyzeCommand.Flags().StringVar(&analyzeCmd.noProxy, "no-proxy", loadEnvInsensitive("no_proxy"), "proxy excluded URLs (relevant only with proxy)") analyzeCommand.Flags().IntVar(&analyzeCmd.contextLines, "context-lines", 100, "number of lines of source code to include in the output for each incident") analyzeCommand.Flags().StringVar(&analyzeCmd.incidentSelector, "incident-selector", "", "an expression to select incidents based on custom variables. ex: (!package=io.konveyor.demo.config-utils)") analyzeCommand.Flags().StringArrayVarP(&analyzeCmd.depFolders, "dependency-folders", "d", []string{}, "directory for dependencies") @@ -570,8 +573,6 @@ func (a *analyzeCommand) getConfigVolumes(providers []string, ports map[string]i default: return nil, fmt.Errorf("unable to find config for provider %v", providers[0]) } - - // TODO (pgaikwad): binaries don't work with alizer right now, we need to revisit this if !foundJava && a.isFileInput { foundJava = true } @@ -600,6 +601,7 @@ func (a *analyzeCommand) getConfigVolumes(providers []string, ports map[string]i }, }, } + if a.mavenSettingsFile != "" { err := copyFileContents(a.mavenSettingsFile, filepath.Join(tempDir, "settings.xml")) if err != nil { @@ -608,9 +610,6 @@ func (a *analyzeCommand) getConfigVolumes(providers []string, ports map[string]i } javaConfig.InitConfig[0].ProviderSpecificConfig["mavenSettingsFile"] = fmt.Sprintf("%s/%s", ConfigMountPath, "settings.xml") } - if Settings.JvmMaxMem != "" { - javaConfig.InitConfig[0].ProviderSpecificConfig["jvmMaxMem"] = Settings.JvmMaxMem - } goConfig := provider.Config{ Name: goProvider, @@ -679,7 +678,6 @@ func (a *analyzeCommand) getConfigVolumes(providers []string, ports map[string]i }, }, } - switch { case foundJava: provConfig = append(provConfig, javaConfig) @@ -691,30 +689,18 @@ func (a *analyzeCommand) getConfigVolumes(providers []string, ports map[string]i provConfig = append(provConfig, nodeJSConfig) } - // Set proxy to providers - if a.httpProxy != "" || a.httpsProxy != "" { - proxy := provider.Proxy{ - HTTPProxy: a.httpProxy, - HTTPSProxy: a.httpsProxy, - NoProxy: a.noProxy, - } - for i := range provConfig { - provConfig[i].Proxy = &proxy - } - } - - jsonData, err := json.MarshalIndent(&provConfig, "", " ") + err = a.getProviderOptions(tempDir, provConfig, providers[0]) if err != nil { - a.log.V(1).Error(err, "failed to marshal provider config") - return nil, err - } - err = os.WriteFile(filepath.Join(tempDir, "settings.json"), jsonData, os.ModePerm) - if err != nil { - a.log.V(1).Error(err, - "failed to write provider config", "dir", tempDir, "file", "settings.json") - return nil, err + if errors.Is(err, os.ErrNotExist) { + a.log.V(5).Info("provider options config not found, using default options") + err := a.writeProvConfig(tempDir, provConfig) + if err != nil { + return nil, err + } + } else { + return nil, err + } } - settingsVols := map[string]string{ tempDir: ConfigMountPath, } @@ -1490,6 +1476,128 @@ func (a *analyzeCommand) ConvertXML(ctx context.Context) (string, error) { return tempOutputDir, nil } +func (a *analyzeCommand) writeProvConfig(tempDir string, config []provider.Config) error { + jsonData, err := json.MarshalIndent(&config, "", " ") + if err != nil { + a.log.V(1).Error(err, "failed to marshal provider config") + return err + } + err = os.WriteFile(filepath.Join(tempDir, "settings.json"), jsonData, os.ModePerm) + if err != nil { + a.log.V(1).Error(err, + "failed to write provider config", "dir", tempDir, "file", "settings.json") + return err + } + return nil +} + +func (a *analyzeCommand) getProviderOptions(tempDir string, provConfig []provider.Config, prov string) error { + var confDir string + var set bool + ops := runtime.GOOS + if ops == "linux" { + confDir, set = os.LookupEnv("XDG_CONFIG_HOME") + } + if ops != "linux" || confDir == "" || !set { + // on Unix, including macOS, this returns the $HOME environment variable. On Windows, it returns %USERPROFILE% + var err error + confDir, err = os.UserHomeDir() + if err != nil { + return err + } + } + // get provider options from provider settings file + data, err := os.ReadFile(filepath.Join(confDir, ".kantra", fmt.Sprintf("%v.json", prov))) + if err != nil { + return err + } + optionsConfig := &[]provider.Config{} + err = yaml.Unmarshal(data, optionsConfig) + if err != nil { + a.log.V(1).Error(err, "failed to unmarshal provider options file") + return err + } + mergedConfig, err := a.mergeProviderConfig(provConfig, *optionsConfig, tempDir) + if err != nil { + return err + } + err = a.writeProvConfig(tempDir, mergedConfig) + if err != nil { + return err + } + return nil +} + +func (a *analyzeCommand) mergeProviderConfig(defaultConf, optionsConf []provider.Config, tempDir string) ([]provider.Config, error) { + merged := []provider.Config{} + seen := map[string]*provider.Config{} + + // find options used for supported providers + for idx, conf := range defaultConf { + seen[conf.Name] = &defaultConf[idx] + } + + for _, conf := range optionsConf { + if _, ok := seen[conf.Name]; ok { + // set provider config options + if conf.ContextLines != 0 { + seen[conf.Name].ContextLines = conf.ContextLines + } + if conf.Proxy != nil { + seen[conf.Name].Proxy = conf.Proxy + } + } + // set init config options + for i, init := range conf.InitConfig { + if len(init.AnalysisMode) != 0 { + seen[conf.Name].InitConfig[i].AnalysisMode = init.AnalysisMode + } + if len(init.ProviderSpecificConfig) != 0 { + provSpecificConf, err := a.mergeProviderSpecificConfig(init.ProviderSpecificConfig, seen[conf.Name].InitConfig[i].ProviderSpecificConfig, tempDir) + if err != nil { + return nil, err + } + seen[conf.Name].InitConfig[i].ProviderSpecificConfig = provSpecificConf + } + } + } + for _, v := range seen { + merged = append(merged, *v) + } + return merged, nil +} + +func (a *analyzeCommand) mergeProviderSpecificConfig(optionsConf, seenConf map[string]interface{}, tempDir string) (map[string]interface{}, error) { + for k, v := range optionsConf { + switch { + case optionsConf[k] == "": + continue + // special case for maven settings file to mount correctly + case k == mavenSettingsFile: + // validate maven settings file + if _, err := os.Stat(v.(string)); err != nil { + return nil, fmt.Errorf("%w failed to stat maven settings file at path %s", err, v) + } + if absPath, err := filepath.Abs(v.(string)); err == nil { + seenConf[k] = absPath + } + // copy file to mount path + err := copyFileContents(v.(string), filepath.Join(tempDir, "settings.xml")) + if err != nil { + a.log.V(1).Error(err, "failed copying maven settings file", "path", v) + return nil, err + } + seenConf[k] = fmt.Sprintf("%s/%s", ConfigMountPath, "settings.xml") + continue + // we don't want users to override these options here + // use --overrideProviderSettings to do so + case k != lspServerPath && k != lspServerName && k != workspaceFolders && k != dependencyProviderPath: + seenConf[k] = v + } + } + return seenConf, nil +} + func (a *analyzeCommand) CleanAnalysisResources(ctx context.Context) error { if !a.cleanup { return nil diff --git a/cmd/settings.go b/cmd/settings.go index 55508de4..f563f4c9 100644 --- a/cmd/settings.go +++ b/cmd/settings.go @@ -26,7 +26,6 @@ type Config struct { RootCommandName string `env:"CMD_NAME" default:"kantra"` PodmanBinary string `env:"PODMAN_BIN" default:"/usr/bin/podman"` RunnerImage string `env:"RUNNER_IMG" default:"quay.io/konveyor/kantra"` - JvmMaxMem string `env:"JVM_MAX_MEM" default:""` RunLocal bool `env:"RUN_LOCAL"` JavaProviderImage string `env:"JAVA_PROVIDER_IMG" default:"quay.io/konveyor/java-external-provider:latest"` GenericProviderImage string `env:"GENERIC_PROVIDER_IMG" default:"quay.io/konveyor/generic-external-provider:latest"` diff --git a/docs/usage.md b/docs/usage.md index 93cf0877..b2603f4f 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -13,3 +13,21 @@ label, the given rule will not match. - You must add the target label to the custom rule and specify the `--target` in order to run this rule. + + +## Provider Options + +The supported providers have several options to utilize. Examples of the available +options can be found [here](../java.json.sample) and [here](../golang.json.sample). To read about each of these options, +see the analyzer provider [documentation](https://github.com/konveyor/analyzer-lsp/blob/main/docs/providers.md). + +Kantra will look for these options at: +- Linux: `$XDG_CONFIG_HOME/.kantra/.json` and then `$HOME/.kantra/.json` +- MacOS: `$HOME/.kantra/.json` +- Windows: `%USERPROFILE%/.kantra/.json` + +Current supported providers are: +- java +- golang +- python +- nodejs diff --git a/golang.json.sample b/golang.json.sample new file mode 100644 index 00000000..274e84b5 --- /dev/null +++ b/golang.json.sample @@ -0,0 +1,12 @@ +[ + { + "name": "go", + "initConfig": [{ + "analysisMode": "full", + "providerSpecificConfig": { + "lspServerArgs": [], + "lspServerInitializationOptions": "" + } + }] + } +] diff --git a/java.json.sample b/java.json.sample new file mode 100644 index 00000000..2c0dface --- /dev/null +++ b/java.json.sample @@ -0,0 +1,14 @@ +[ + { + "name": "java", + "initConfig": [{ + "providerSpecificConfig": { + "bundles": "", + "depOpenSourceLabelsFile": "", + "jvmMaxMem": "", + "mavenSettingsFile": "" + }, + "analysisMode": "source-only" + }] + } +]