Skip to content

Commit 99c7a92

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

File tree

4 files changed

+189
-35
lines changed

4 files changed

+189
-35
lines changed

cmd/analyze.go

Lines changed: 100 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"net/http"
1212
"os"
1313
"path/filepath"
14+
"strings"
1415

1516
"codacy/cli-v2/utils"
1617

@@ -188,42 +189,33 @@ func getToolName(toolName string, version string) string {
188189
return toolName
189190
}
190191

191-
func runEslintAnalysis(workDirectory string, pathsToCheck []string, autoFix bool, outputFile string, outputFormat string) {
192+
func runEslintAnalysis(workDirectory string, pathsToCheck []string, autoFix bool, outputFile string, outputFormat string) error {
192193
eslint := config.Config.Tools()["eslint"]
193194
eslintInstallationDirectory := eslint.InstallDir
194195
nodeRuntime := config.Config.Runtimes()["node"]
195196
nodeBinary := nodeRuntime.Binaries["node"]
196197

197-
tools.RunEslint(workDirectory, eslintInstallationDirectory, nodeBinary, pathsToCheck, autoFix, outputFile, outputFormat)
198+
return tools.RunEslint(workDirectory, eslintInstallationDirectory, nodeBinary, pathsToCheck, autoFix, outputFile, outputFormat)
198199
}
199200

200-
func runTrivyAnalysis(workDirectory string, pathsToCheck []string, outputFile string, outputFormat string) {
201+
func runTrivyAnalysis(workDirectory string, pathsToCheck []string, outputFile string, outputFormat string) error {
201202
trivy := config.Config.Tools()["trivy"]
202203
trivyBinary := trivy.Binaries["trivy"]
203204

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

210-
func runPmdAnalysis(workDirectory string, pathsToCheck []string, outputFile string, outputFormat string) {
208+
func runPmdAnalysis(workDirectory string, pathsToCheck []string, outputFile string, outputFormat string) error {
211209
pmd := config.Config.Tools()["pmd"]
212210
pmdBinary := pmd.Binaries["pmd"]
213211

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

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

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

229221
var analyzeCmd = &cobra.Command{
@@ -262,13 +254,22 @@ var analyzeCmd = &cobra.Command{
262254
defer os.RemoveAll(tmpDir)
263255

264256
var sarifOutputs []string
257+
failedTools := make(map[string]error)
265258
for toolName := range toolsToRun {
266259
log.Printf("Running %s...\n", toolName)
267260
tmpFile := filepath.Join(tmpDir, fmt.Sprintf("%s.sarif", toolName))
268-
runTool(workDirectory, toolName, args, tmpFile)
261+
if err := runTool(workDirectory, toolName, args, tmpFile); err != nil {
262+
log.Printf("Warning: Tool %s failed: %v\n", toolName, err)
263+
failedTools[toolName] = err
264+
continue
265+
}
269266
sarifOutputs = append(sarifOutputs, tmpFile)
270267
}
271268

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

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

281+
// Add error runs to the merged SARIF
282+
if len(failedTools) > 0 {
283+
mergedSarif, err := utils.ReadSarifFile(tmpOutputFile)
284+
if err != nil {
285+
log.Fatalf("Failed to read merged SARIF: %v", err)
286+
}
287+
288+
for toolName, err := range failedTools {
289+
utils.AddErrorRun(&mergedSarif, toolName, err.Error())
290+
}
291+
292+
if err := utils.WriteSarifFile(mergedSarif, tmpOutputFile); err != nil {
293+
log.Fatalf("Failed to write updated SARIF: %v", err)
294+
}
295+
}
296+
280297
if outputFile != "" {
281298
// copy tmpOutputFile to outputFile
282299
content, err := os.ReadFile(tmpOutputFile)
@@ -293,26 +310,83 @@ var analyzeCmd = &cobra.Command{
293310
fmt.Println(string(content))
294311
}
295312
} else {
296-
// Run tools without merging outputs
313+
// Create temporary directory for individual tool outputs
314+
tmpDir, err := os.MkdirTemp("", "codacy-analysis-*")
315+
if err != nil {
316+
log.Fatalf("Failed to create temporary directory: %v", err)
317+
}
318+
defer os.RemoveAll(tmpDir)
319+
320+
var toolOutputs []string
321+
failedTools := make(map[string]error)
297322
for toolName := range toolsToRun {
298323
log.Printf("Running %s...\n", toolName)
299-
runTool(workDirectory, toolName, args, outputFile)
324+
tmpFile := filepath.Join(tmpDir, fmt.Sprintf("%s.json", toolName))
325+
if err := runTool(workDirectory, toolName, args, tmpFile); err != nil {
326+
log.Printf("Warning: Tool %s failed: %v\n", toolName, err)
327+
failedTools[toolName] = err
328+
continue
329+
}
330+
toolOutputs = append(toolOutputs, tmpFile)
331+
}
332+
333+
// Create merged output
334+
mergedOutput := make(map[string]interface{})
335+
mergedOutput["tools"] = make(map[string]interface{})
336+
mergedOutput["errors"] = make(map[string]string)
337+
338+
// Add error information
339+
for toolName, err := range failedTools {
340+
mergedOutput["errors"].(map[string]string)[toolName] = err.Error()
341+
}
342+
343+
// Add tool outputs
344+
for _, toolOutput := range toolOutputs {
345+
output, err := os.ReadFile(toolOutput)
346+
if err != nil {
347+
log.Printf("Warning: Failed to read output from %s: %v\n", filepath.Base(toolOutput), err)
348+
continue
349+
}
350+
351+
toolName := strings.TrimSuffix(filepath.Base(toolOutput), ".json")
352+
var toolOutput interface{}
353+
if err := json.Unmarshal(output, &toolOutput); err != nil {
354+
log.Printf("Warning: Failed to parse output from %s as JSON, storing raw output: %v", toolName, err)
355+
toolOutput = map[string]interface{}{
356+
"raw": string(output),
357+
}
358+
}
359+
mergedOutput["tools"].(map[string]interface{})[toolName] = toolOutput
360+
}
361+
362+
// Write merged output
363+
mergedJSON, err := json.MarshalIndent(mergedOutput, "", " ")
364+
if err != nil {
365+
log.Fatalf("Failed to marshal merged output: %v", err)
366+
}
367+
368+
if outputFile != "" {
369+
if err := os.WriteFile(outputFile, mergedJSON, utils.DefaultFilePerms); err != nil {
370+
log.Fatalf("Failed to write merged output: %v", err)
371+
}
372+
} else {
373+
fmt.Println(string(mergedJSON))
300374
}
301375
}
302376
},
303377
}
304378

305-
func runTool(workDirectory string, toolName string, args []string, outputFile string) {
379+
func runTool(workDirectory string, toolName string, args []string, outputFile string) error {
306380
switch toolName {
307381
case "eslint":
308-
runEslintAnalysis(workDirectory, args, autoFix, outputFile, outputFormat)
382+
return runEslintAnalysis(workDirectory, args, autoFix, outputFile, outputFormat)
309383
case "trivy":
310-
runTrivyAnalysis(workDirectory, args, outputFile, outputFormat)
384+
return runTrivyAnalysis(workDirectory, args, outputFile, outputFormat)
311385
case "pmd":
312-
runPmdAnalysis(workDirectory, args, outputFile, outputFormat)
386+
return runPmdAnalysis(workDirectory, args, outputFile, outputFormat)
313387
case "pylint":
314-
runPylintAnalysis(workDirectory, args, outputFile, outputFormat)
388+
return runPylintAnalysis(workDirectory, args, outputFile, outputFormat)
315389
default:
316-
log.Printf("Warning: Unsupported tool: %s\n", toolName)
390+
return fmt.Errorf("unsupported tool: %s", toolName)
317391
}
318392
}

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
}

tools/pmdRunner.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ func RunPmd(repositoryToAnalyseDirectory string, pmdBinary string, pathsToCheck
3939
// Format
4040
if outputFormat != "" {
4141
cmd.Args = append(cmd.Args, "-f", outputFormat)
42+
} else {
43+
// Default to JSON format if no format specified
44+
cmd.Args = append(cmd.Args, "-f", "json")
4245
}
4346

4447
// Output file

utils/sarif.go

Lines changed: 74 additions & 2 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,12 +81,83 @@ 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

0 commit comments

Comments
 (0)