Skip to content

Commit f225ba3

Browse files
committed
[PLUTO-1396] Handle tool errors gracefully
1 parent 9a3ae32 commit f225ba3

File tree

3 files changed

+173
-35
lines changed

3 files changed

+173
-35
lines changed

cmd/analyze.go

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -188,42 +188,33 @@ func getToolName(toolName string, version string) string {
188188
return toolName
189189
}
190190

191-
func runEslintAnalysis(workDirectory string, pathsToCheck []string, autoFix bool, outputFile string, outputFormat string) {
191+
func runEslintAnalysis(workDirectory string, pathsToCheck []string, autoFix bool, outputFile string, outputFormat string) error {
192192
eslint := config.Config.Tools()["eslint"]
193193
eslintInstallationDirectory := eslint.InstallDir
194194
nodeRuntime := config.Config.Runtimes()["node"]
195195
nodeBinary := nodeRuntime.Binaries["node"]
196196

197-
tools.RunEslint(workDirectory, eslintInstallationDirectory, nodeBinary, pathsToCheck, autoFix, outputFile, outputFormat)
197+
return tools.RunEslint(workDirectory, eslintInstallationDirectory, nodeBinary, pathsToCheck, autoFix, outputFile, outputFormat)
198198
}
199199

200-
func runTrivyAnalysis(workDirectory string, pathsToCheck []string, outputFile string, outputFormat string) {
200+
func runTrivyAnalysis(workDirectory string, pathsToCheck []string, outputFile string, outputFormat string) error {
201201
trivy := config.Config.Tools()["trivy"]
202202
trivyBinary := trivy.Binaries["trivy"]
203203

204-
err := tools.RunTrivy(workDirectory, trivyBinary, pathsToCheck, outputFile, outputFormat)
205-
if err != nil {
206-
log.Fatalf("Error running Trivy: %v", err)
207-
}
204+
return tools.RunTrivy(workDirectory, trivyBinary, pathsToCheck, outputFile, outputFormat)
208205
}
209206

210-
func runPmdAnalysis(workDirectory string, pathsToCheck []string, outputFile string, outputFormat string) {
207+
func runPmdAnalysis(workDirectory string, pathsToCheck []string, outputFile string, outputFormat string) error {
211208
pmd := config.Config.Tools()["pmd"]
212209
pmdBinary := pmd.Binaries["pmd"]
213210

214-
err := tools.RunPmd(workDirectory, pmdBinary, pathsToCheck, outputFile, outputFormat, config.Config)
215-
if err != nil {
216-
log.Fatalf("Error running PMD: %v", err)
217-
}
211+
return tools.RunPmd(workDirectory, pmdBinary, pathsToCheck, outputFile, outputFormat, config.Config)
218212
}
219213

220-
func runPylintAnalysis(workDirectory string, pathsToCheck []string, outputFile string, outputFormat string) {
214+
func runPylintAnalysis(workDirectory string, pathsToCheck []string, outputFile string, outputFormat string) error {
221215
pylint := config.Config.Tools()["pylint"]
222216

223-
err := tools.RunPylint(workDirectory, pylint, pathsToCheck, outputFile, outputFormat)
224-
if err != nil {
225-
log.Fatalf("Error running Pylint: %v", err)
226-
}
217+
return tools.RunPylint(workDirectory, pylint, pathsToCheck, outputFile, outputFormat)
227218
}
228219

229220
var analyzeCmd = &cobra.Command{
@@ -262,13 +253,22 @@ var analyzeCmd = &cobra.Command{
262253
defer os.RemoveAll(tmpDir)
263254

264255
var sarifOutputs []string
256+
failedTools := make(map[string]error)
265257
for toolName := range toolsToRun {
266258
log.Printf("Running %s...\n", toolName)
267259
tmpFile := filepath.Join(tmpDir, fmt.Sprintf("%s.sarif", toolName))
268-
runTool(workDirectory, toolName, args, tmpFile)
260+
if err := runTool(workDirectory, toolName, args, tmpFile); err != nil {
261+
log.Printf("Warning: Tool %s failed: %v\n", toolName, err)
262+
failedTools[toolName] = err
263+
continue
264+
}
269265
sarifOutputs = append(sarifOutputs, tmpFile)
270266
}
271267

268+
if len(sarifOutputs) == 0 && len(failedTools) > 0 {
269+
log.Fatal("All tools failed to run. No analysis results available.")
270+
}
271+
272272
// create output file tmp file
273273
tmpOutputFile := filepath.Join(tmpDir, "merged.sarif")
274274

@@ -277,6 +277,22 @@ var analyzeCmd = &cobra.Command{
277277
log.Fatalf("Failed to merge SARIF outputs: %v", err)
278278
}
279279

280+
// Add error runs to the merged SARIF
281+
if len(failedTools) > 0 {
282+
mergedSarif, err := utils.ReadSarifFile(tmpOutputFile)
283+
if err != nil {
284+
log.Fatalf("Failed to read merged SARIF: %v", err)
285+
}
286+
287+
for toolName, err := range failedTools {
288+
utils.AddErrorRun(&mergedSarif, toolName, err.Error())
289+
}
290+
291+
if err := utils.WriteSarifFile(mergedSarif, tmpOutputFile); err != nil {
292+
log.Fatalf("Failed to write updated SARIF: %v", err)
293+
}
294+
}
295+
280296
if outputFile != "" {
281297
// copy tmpOutputFile to outputFile
282298
content, err := os.ReadFile(tmpOutputFile)
@@ -302,17 +318,17 @@ var analyzeCmd = &cobra.Command{
302318
},
303319
}
304320

305-
func runTool(workDirectory string, toolName string, args []string, outputFile string) {
321+
func runTool(workDirectory string, toolName string, args []string, outputFile string) error {
306322
switch toolName {
307323
case "eslint":
308-
runEslintAnalysis(workDirectory, args, autoFix, outputFile, outputFormat)
324+
return runEslintAnalysis(workDirectory, args, autoFix, outputFile, outputFormat)
309325
case "trivy":
310-
runTrivyAnalysis(workDirectory, args, outputFile, outputFormat)
326+
return runTrivyAnalysis(workDirectory, args, outputFile, outputFormat)
311327
case "pmd":
312-
runPmdAnalysis(workDirectory, args, outputFile, outputFormat)
328+
return runPmdAnalysis(workDirectory, args, outputFile, outputFormat)
313329
case "pylint":
314-
runPylintAnalysis(workDirectory, args, outputFile, outputFormat)
330+
return runPylintAnalysis(workDirectory, args, outputFile, outputFormat)
315331
default:
316-
log.Printf("Warning: Unsupported tool: %s\n", toolName)
332+
return fmt.Errorf("unsupported tool: %s", toolName)
317333
}
318334
}

tools/eslintRunner.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package tools
22

33
import (
44
"codacy/cli-v2/config"
5+
"fmt"
56
"os"
67
"os/exec"
78
"path/filepath"
@@ -10,7 +11,7 @@ import (
1011
// * Run from the root of the repo we want to analyse
1112
// * NODE_PATH="<the installed eslint path>/node_modules"
1213
// * The local installed ESLint should have the @microsoft/eslint-formatter-sarif installed
13-
func RunEslint(repositoryToAnalyseDirectory string, eslintInstallationDirectory string, nodeBinary string, pathsToCheck []string, autoFix bool, outputFile string, outputFormat string) {
14+
func RunEslint(repositoryToAnalyseDirectory string, eslintInstallationDirectory string, nodeBinary string, pathsToCheck []string, autoFix bool, outputFile string, outputFormat string) error {
1415
eslintInstallationNodeModules := filepath.Join(eslintInstallationDirectory, "node_modules")
1516
eslintJsPath := filepath.Join(eslintInstallationNodeModules, ".bin", "eslint")
1617

@@ -50,10 +51,14 @@ func RunEslint(repositoryToAnalyseDirectory string, eslintInstallationDirectory
5051
nodePathEnv := "NODE_PATH=" + eslintInstallationNodeModules
5152
cmd.Env = append(cmd.Env, nodePathEnv)
5253

53-
// DEBUG
54-
// fmt.Println(cmd.Env)
55-
// fmt.Println(cmd)
56-
57-
// TODO eslint returns 1 when it finds errors, so we're not propagating it
58-
cmd.Run()
54+
// Run the command and handle errors
55+
err := cmd.Run()
56+
if err != nil {
57+
// ESLint returns 1 when it finds errors, which is not a failure
58+
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
59+
return nil
60+
}
61+
return fmt.Errorf("failed to run ESLint: %w", err)
62+
}
63+
return nil
5964
}

utils/sarif.go

Lines changed: 121 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ type SarifReport struct {
2727
}
2828

2929
type Run struct {
30-
Tool Tool `json:"tool"`
31-
Results []Result `json:"results"`
30+
Tool Tool `json:"tool"`
31+
Results []Result `json:"results"`
32+
Invocations []Invocation `json:"invocations,omitempty"`
3233
}
3334

3435
type Tool struct {
@@ -80,18 +81,89 @@ type ArtifactLocation struct {
8081
type Region struct {
8182
StartLine int `json:"startLine"`
8283
StartColumn int `json:"startColumn"`
84+
EndLine int `json:"endLine"`
85+
EndColumn int `json:"endColumn"`
86+
}
87+
88+
type Invocation struct {
89+
ExecutionSuccessful bool `json:"executionSuccessful"`
90+
ExitCode int `json:"exitCode"`
91+
ExitSignalName string `json:"exitSignalName"`
92+
ExitSignalNumber int `json:"exitSignalNumber"`
93+
Stderr Artifact `json:"stderr"`
94+
}
95+
96+
type Artifact struct {
97+
Text string `json:"text"`
8398
}
8499

85100
type MessageText struct {
86101
Text string `json:"text"`
87102
}
88103

104+
// ReadSarifFile reads a SARIF file and returns its contents
105+
func ReadSarifFile(file string) (SarifReport, error) {
106+
data, err := os.ReadFile(file)
107+
if err != nil {
108+
return SarifReport{}, fmt.Errorf("failed to read SARIF file: %w", err)
109+
}
110+
111+
var sarif SarifReport
112+
if err := json.Unmarshal(data, &sarif); err != nil {
113+
return SarifReport{}, fmt.Errorf("failed to parse SARIF file: %w", err)
114+
}
115+
116+
return sarif, nil
117+
}
118+
119+
// WriteSarifFile writes a SARIF report to a file
120+
func WriteSarifFile(sarif SarifReport, outputFile string) error {
121+
out, err := os.Create(outputFile)
122+
if err != nil {
123+
return fmt.Errorf("failed to create output file: %w", err)
124+
}
125+
defer out.Close()
126+
127+
encoder := json.NewEncoder(out)
128+
encoder.SetIndent("", " ")
129+
if err := encoder.Encode(sarif); err != nil {
130+
return fmt.Errorf("failed to write SARIF: %w", err)
131+
}
132+
133+
return nil
134+
}
135+
136+
// AddErrorRun adds an error run to an existing SARIF report
137+
func AddErrorRun(sarif *SarifReport, toolName string, errorMessage string) {
138+
errorRun := Run{
139+
Tool: Tool{
140+
Driver: Driver{
141+
Name: toolName,
142+
Version: "1.0.0",
143+
},
144+
},
145+
Invocations: []Invocation{
146+
{
147+
ExecutionSuccessful: false,
148+
ExitCode: 1,
149+
ExitSignalName: "error",
150+
ExitSignalNumber: 1,
151+
Stderr: Artifact{
152+
Text: errorMessage,
153+
},
154+
},
155+
},
156+
Results: []Result{},
157+
}
158+
sarif.Runs = append(sarif.Runs, errorRun)
159+
}
160+
89161
// ConvertPylintToSarif converts Pylint JSON output to SARIF format
90162
func ConvertPylintToSarif(pylintOutput []byte) []byte {
91163
var issues []PylintIssue
92164
if err := json.Unmarshal(pylintOutput, &issues); err != nil {
93-
// If parsing fails, return empty SARIF report
94-
return createEmptySarifReport()
165+
// If parsing fails, return empty SARIF report with error
166+
return createEmptySarifReportWithError(err.Error())
95167
}
96168

97169
// Create SARIF report
@@ -108,6 +180,17 @@ func ConvertPylintToSarif(pylintOutput []byte) []byte {
108180
},
109181
},
110182
Results: make([]Result, 0, len(issues)),
183+
Invocations: []Invocation{
184+
{
185+
ExecutionSuccessful: true, // Pylint ran successfully if we got here
186+
ExitCode: 0,
187+
ExitSignalName: "",
188+
ExitSignalNumber: 0,
189+
Stderr: Artifact{
190+
Text: "",
191+
},
192+
},
193+
},
111194
},
112195
},
113196
}
@@ -161,6 +244,29 @@ func getSarifLevel(pylintType string) string {
161244

162245
// createEmptySarifReport creates an empty SARIF report in case of errors
163246
func createEmptySarifReport() []byte {
247+
emptyReport := SarifReport{
248+
Version: "2.1.0",
249+
Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
250+
Runs: []Run{
251+
{
252+
Tool: Tool{
253+
Driver: Driver{
254+
Name: "Pylint",
255+
Version: "3.3.6",
256+
InformationURI: "https://pylint.org",
257+
},
258+
},
259+
Results: []Result{},
260+
Invocations: []Invocation{},
261+
},
262+
},
263+
}
264+
sarifData, _ := json.MarshalIndent(emptyReport, "", " ")
265+
return sarifData
266+
}
267+
268+
// createEmptySarifReportWithError creates an empty SARIF report with error information
269+
func createEmptySarifReportWithError(errorMessage string) []byte {
164270
emptyReport := SarifReport{
165271
Version: "2.1.0",
166272
Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
@@ -174,6 +280,17 @@ func createEmptySarifReport() []byte {
174280
},
175281
},
176282
Results: []Result{},
283+
Invocations: []Invocation{
284+
{
285+
ExecutionSuccessful: false,
286+
ExitCode: 1,
287+
ExitSignalName: "error",
288+
ExitSignalNumber: 1,
289+
Stderr: Artifact{
290+
Text: errorMessage,
291+
},
292+
},
293+
},
177294
},
178295
},
179296
}

0 commit comments

Comments
 (0)