internal/platform/run.go (286 lines of code) (raw):

/* * Copyright 2021-2024 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package platform import ( "errors" "fmt" "os" "path" "path/filepath" "strings" "github.com/JetBrains/qodana-cli/internal/platform/strutil" "github.com/JetBrains/qodana-cli/internal/cloud" platformcmd "github.com/JetBrains/qodana-cli/internal/platform/cmd" "github.com/JetBrains/qodana-cli/internal/platform/commoncontext" "github.com/JetBrains/qodana-cli/internal/platform/effectiveconfig" "github.com/JetBrains/qodana-cli/internal/platform/msg" "github.com/JetBrains/qodana-cli/internal/platform/qdenv" "github.com/JetBrains/qodana-cli/internal/platform/qdyaml" "github.com/JetBrains/qodana-cli/internal/platform/thirdpartyscan" "github.com/JetBrains/qodana-cli/internal/platform/tokenloader" "github.com/JetBrains/qodana-cli/internal/platform/utils" "github.com/JetBrains/qodana-cli/internal/tooling" log "github.com/sirupsen/logrus" ) func RunThirdPartyLinterAnalysis( cliOptions platformcmd.CliOptions, linter ThirdPartyLinter, linterInfo thirdpartyscan.LinterInfo, ) (int, error) { qdenv.InitializeQodanaGlobalEnv(cliOptions) var err error commonCtx := commoncontext.Compute3rdParty( linterInfo.LinterName, linterInfo.IsEap, cliOptions.CacheDir, cliOptions.ResultsDir, cliOptions.ReportDir, qdenv.GetQodanaGlobalEnv(qdenv.QodanaToken), cliOptions.ClearCache, cliOptions.ProjectDir, cliOptions.RepositoryRoot, ) commonCtx, err = correctInitArgsForThirdParty(commonCtx) if err != nil { msg.ErrorMessage(err.Error()) return 1, err } resultDir := commonCtx.ResultsDir defer changeResultDirPermissionsInContainer(resultDir) thirdPartyCloudData := checkLinterLicense(commonCtx) printLinterLicense(thirdPartyCloudData.LicensePlan, linterInfo) printQodanaLogo(commonCtx.LogDir(), commonCtx.CacheDir, linterInfo) mountInfo := extractUtils(linter, commonCtx.CacheDir) localQodanaYamlFullPath := qdyaml.GetLocalNotEffectiveQodanaYamlFullPath( commonCtx.ProjectDir, cliOptions.ConfigName, ) effectiveDir, cleanup, err := utils.CreateTempDir("qodana-effective-config") if err != nil { return 1, fmt.Errorf("failed to create qodana effective configuration dir %v", err) } defer cleanup() qodanaConfigEffectiveFiles, err := effectiveconfig.CreateEffectiveConfigFiles( localQodanaYamlFullPath, cliOptions.GlobalConfigurationsDir, cliOptions.GlobalConfigurationId, mountInfo.JavaPath, effectiveDir, commonCtx.LogDir(), ) if err != nil { log.Fatalf("Failed to load Qodana configuration %s", err) } qodanaYamlConfig := thirdpartyscan.QodanaYamlConfig{} if qodanaConfigEffectiveFiles.EffectiveQodanaYamlPath != "" { yaml := qdyaml.LoadQodanaYamlByFullPath(qodanaConfigEffectiveFiles.EffectiveQodanaYamlPath) qodanaYamlConfig = thirdpartyscan.YamlConfig(yaml) } context := thirdpartyscan.ComputeContext( cliOptions, commonCtx, linterInfo, mountInfo, thirdPartyCloudData, qodanaYamlConfig, ) LogContext(&context) events := make([]tooling.FuserEvent, 0) eventsCh := createFuserEventChannel(&events) projectIdHash := thirdPartyCloudData.ProjectIdHash defer func() { logProjectClose(eventsCh, linterInfo, projectIdHash) sendFuserEvents(eventsCh, &events, context, GetDeviceIdSalt()[0]) }() logOs(eventsCh, linterInfo, projectIdHash) logProjectOpen(eventsCh, linterInfo, projectIdHash) if err = linter.RunAnalysis(context); err != nil { msg.ErrorMessage(err.Error()) return 1, err } log.Debugf("Java executable path: %s", mountInfo.JavaPath) thresholds := getFailureThresholds(context) var analysisResult int if analysisResult, err = computeBaselinePrintResults(context, thresholds); err != nil { msg.ErrorMessage(err.Error()) return 1, err } if qodanaConfigEffectiveFiles.EffectiveQodanaYamlPath != "" { err = copyQodanaYamlToLogDir(qodanaConfigEffectiveFiles.EffectiveQodanaYamlPath, context.LogDir()) if err != nil { msg.ErrorMessage(err.Error()) return 1, err } } if err = convertReportToCloudFormat(context); err != nil { msg.ErrorMessage(err.Error()) return 1, err } sendReportToQodanaServer(context) newReportUrl := cloud.GetReportUrl(context.ResultsDir()) ProcessSarif( filepath.Join(context.ResultsDir(), commoncontext.QodanaSarifName), context.AnalysisId(), newReportUrl, false, context.GenerateCodeClimateReport(), context.SendBitBucketInsights(), ) err = writeShortSarifReport(context) if err != nil { log.Warnf("Problems writing short SARIF report: %v", err) } return analysisResult, nil } func correctInitArgsForThirdParty(commonCtx commoncontext.Context) (commoncontext.Context, error) { empty := commoncontext.Context{} var err error if commonCtx.ResultsDir, err = filepath.Abs(commonCtx.ResultsDir); err != nil { return empty, fmt.Errorf("failed to get absolute path to results directory: %w", err) } if commonCtx.ReportDir, err = filepath.Abs(commonCtx.ReportDir); err != nil { return empty, fmt.Errorf("failed to get absolute path to report directory: %w", err) } tmpResultsDir := GetTmpResultsDir(commonCtx.ResultsDir) if _, err := os.Stat(tmpResultsDir); err == nil { if err := os.RemoveAll(tmpResultsDir); err != nil { return empty, fmt.Errorf("failed to remove folder with temporary data: %w", err) } } directories := []string{ commonCtx.ResultsDir, commonCtx.LogDir(), commonCtx.CacheDir, ReportResultsPath(commonCtx.ReportDir), tmpResultsDir, } for _, dir := range directories { if _, err := os.Stat(dir); os.IsNotExist(err) { if err := os.MkdirAll(dir, 0755); err != nil { return empty, fmt.Errorf("failed to create directory %s: %w", dir, err) } } } return commonCtx, nil } func checkLinterLicense(loader tokenloader.CloudTokenLoader) thirdpartyscan.ThirdPartyStartupCloudData { licensePlan := cloud.CommunityLicensePlan token := tokenloader.LoadCloudUploadToken(loader, false, false, true) projectIdHash := "" cloud.SetupLicenseToken(token) if cloud.Token.Token != "" { licenseData := cloud.GetCloudApiEndpoints().GetLicenseData(cloud.Token.Token) tokenloader.ValidateTokenPrintProject(cloud.Token.Token) licensePlan = licenseData.LicensePlan projectIdHash = licenseData.ProjectIdHash } return thirdpartyscan.ThirdPartyStartupCloudData{ LicensePlan: licensePlan, QodanaToken: token, ProjectIdHash: projectIdHash, } } func printLinterLicense(licensePlan string, linterInfo thirdpartyscan.LinterInfo) { licenseString := licensePlan if cloud.Token.Token == "" && linterInfo.IsEap { licenseString = "EAP license" } msg.SuccessMessage("Qodana license plan: %s", licenseString) } func sendReportToQodanaServer(c thirdpartyscan.Context) { if cloud.Token.IsAllowedToSendReports() { fmt.Println("Publishing report ...") publisher := Publisher{ ResultsDir: c.ResultsDir(), LogDir: c.LogDir(), AnalysisId: c.AnalysisId(), } SendReport( publisher, cloud.Token.Token, strutil.QuoteForWindows(c.MountInfo().JavaPath), ) } else { fmt.Println("Skipping report publishing") } } func copyQodanaYamlToLogDir(qodanaYamlFullPath string, logDir string) error { if _, err := os.Stat(qodanaYamlFullPath); errors.Is(err, os.ErrNotExist) { return nil } if err := utils.CopyFile(qodanaYamlFullPath, path.Join(logDir, "qodana.yaml")); err != nil { log.Errorf("Error while copying qodana.yaml: %s", err) return err } return nil } func printQodanaLogo(logDir string, cacheDir string, linterInfo thirdpartyscan.LinterInfo) { fmt.Println("\nLog directory: " + logDir) fmt.Println("Cache directory: " + cacheDir) fmt.Print(qodanaLogo(linterInfo.LinterPresentableName, linterInfo.LinterVersion, linterInfo.IsEap)) } // qodanaLogo prepares the info message for the tool func qodanaLogo(toolDesc string, version string, eap bool) string { eapString := "" if eap { eapString = "EAP" } return fmt.Sprintf( ` _ _ /\ \ /\ \ %s %s %s / \ \ / \ \ Documentation / /\ \ \ / /\ \ \ https://jb.gg/qodana-docs / / /\ \ \ / / /\ \ \ Contact us at / / / \ \_\ / / / \ \_\ qodana-support@jetbrains.com / / / _ / / / / / / / / / Or via our issue tracker / / / /\ \/ / / / / / / / https://jb.gg/qodana-issue / / /__\ \ \/ / / /___/ / / Or share your feedback at our forum / / /____\ \ \ / / /____\/ / https://jb.gg/qodana-forum \/________\_\/ \/_________/ `, toolDesc, version, eapString, ) } func changeResultDirPermissionsInContainer(resultDir string) { if !qdenv.IsContainer() { return } err := ChangeResultsPermissionsRecursively(resultDir) if err != nil { msg.ErrorMessage("Unable to change permissions in %s: %s", resultDir, err) } } func convertReportToCloudFormat(context thirdpartyscan.Context) error { log.Debugf("Generating report to %s...", context.ReportDir()) args := converterArgs(context, context.MountInfo()) stdout, _, res, err := utils.LaunchAndLog(context.LogDir(), "converter", args...) if res != 0 { return fmt.Errorf("converter exited with non-zero status code: %d", res) } if err != nil { return fmt.Errorf("error while running converter: %s", err) } if strings.Contains(stdout, "java.lang") { return fmt.Errorf("exception occured while generating report: %s", stdout) } return nil } func converterArgs(options thirdpartyscan.Context, mountInfo thirdpartyscan.MountInfo) []string { return []string{ strutil.QuoteForWindows(mountInfo.JavaPath), "-jar", strutil.QuoteForWindows(mountInfo.Converter), "-s", strutil.QuoteForWindows(options.ProjectDir()), "-d", strutil.QuoteForWindows(options.ResultsDir()), "-o", strutil.QuoteForWindows(options.ReportDir()), "-n", "result-allProblems.json", "-f", } } // TODO: think about removing short sarif generation from ultimate func writeShortSarifReport(context thirdpartyscan.Context) error { outputPath := GetShortSarifPath(context.ResultsDir()) sarifPath := GetSarifPath(context.ResultsDir()) log.Debugf("Creating short SARIF report at %s from %s...", outputPath, sarifPath) return MakeShortSarif(sarifPath, outputPath) }