Skip to content

Commit 0f51dbf

Browse files
feat: Add trivy along with Add support for download-based tools PLUTO-1360 PLUTO-1361 (#47)
* feat: Add trivy, along with support for download-based tools and enhance installation logic * feature: Add trivy as a runner * feature: Add support for trivy configuration --------- Co-authored-by: Joao Machado <[email protected]>
1 parent 03133b3 commit 0f51dbf

File tree

16 files changed

+2756
-77
lines changed

16 files changed

+2756
-77
lines changed

.codacy/codacy.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ runtimes:
22
33
tools:
44
5+

cmd/analyze.go

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,25 @@ func getToolName(toolName string, version string) string {
184184
return toolName
185185
}
186186

187+
func runEslintAnalysis(workDirectory string, pathsToCheck []string, autoFix bool, outputFile string, outputFormat string) {
188+
eslint := config.Config.Tools()["eslint"]
189+
eslintInstallationDirectory := eslint.InstallDir
190+
nodeRuntime := config.Config.Runtimes()["node"]
191+
nodeBinary := nodeRuntime.Binaries["node"]
192+
193+
tools.RunEslint(workDirectory, eslintInstallationDirectory, nodeBinary, pathsToCheck, autoFix, outputFile, outputFormat)
194+
}
195+
196+
func runTrivyAnalysis(workDirectory string, pathsToCheck []string, outputFile string, outputFormat string) {
197+
trivy := config.Config.Tools()["trivy"]
198+
trivyBinary := trivy.Binaries["trivy"]
199+
200+
err := tools.RunTrivy(workDirectory, trivyBinary, pathsToCheck, outputFile, outputFormat)
201+
if err != nil {
202+
log.Fatalf("Error running Trivy: %v", err)
203+
}
204+
}
205+
187206
var analyzeCmd = &cobra.Command{
188207
Use: "analyze",
189208
Short: "Runs all linters.",
@@ -194,30 +213,23 @@ var analyzeCmd = &cobra.Command{
194213
log.Fatal(err)
195214
}
196215

197-
// TODO add more tools here
198-
switch toolToAnalyze {
199-
case "eslint":
200-
// nothing
201-
case "":
202-
log.Fatal("You need to specify a tool to run analysis with, e.g., '--tool eslint'", toolToAnalyze)
203-
default:
204-
log.Fatal("Trying to run unsupported tool: ", toolToAnalyze)
205-
}
206-
207-
eslint := config.Config.Tools()["eslint"]
208-
eslintInstallationDirectory := eslint.InstallDir
209-
nodeRuntime := config.Config.Runtimes()["node"]
210-
nodeBinary := nodeRuntime.Binaries["node"]
211-
212216
log.Printf("Running %s...\n", toolToAnalyze)
213217
if outputFormat == "sarif" {
214218
log.Println("Output will be in SARIF format")
215219
}
216-
217220
if outputFile != "" {
218221
log.Println("Output will be available at", outputFile)
219222
}
220223

221-
tools.RunEslint(workDirectory, eslintInstallationDirectory, nodeBinary, args, autoFix, outputFile, outputFormat)
224+
switch toolToAnalyze {
225+
case "eslint":
226+
runEslintAnalysis(workDirectory, args, autoFix, outputFile, outputFormat)
227+
case "trivy":
228+
runTrivyAnalysis(workDirectory, args, outputFile, outputFormat)
229+
case "":
230+
log.Fatal("You need to specify a tool to run analysis with, e.g., '--tool eslint'")
231+
default:
232+
log.Fatal("Trying to run unsupported tool: ", toolToAnalyze)
233+
}
222234
},
223-
}
235+
}

cmd/init.go

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,20 +72,25 @@ func createConfigurationFile(tools []tools.Tool) error {
7272

7373
func configFileTemplate(tools []tools.Tool) string {
7474

75-
// Default version
75+
// Default versions
7676
eslintVersion := "9.3.0"
77+
trivyVersion := "0.59.1" // Latest stable version
7778

7879
for _, tool := range tools {
7980
if tool.Uuid == "f8b29663-2cb2-498d-b923-a10c6a8c05cd" {
8081
eslintVersion = tool.Version
8182
}
83+
if tool.Uuid == "2fd7fbe0-33f9-4ab3-ab73-e9b62404e2cb" {
84+
trivyVersion = tool.Version
85+
}
8286
}
8387

8488
return fmt.Sprintf(`runtimes:
8589
8690
tools:
8791
- eslint@%s
88-
`, eslintVersion)
92+
- trivy@%s
93+
`, eslintVersion, trivyVersion)
8994
}
9095

9196
func buildRepositoryConfigurationFiles(token string) error {
@@ -150,7 +155,24 @@ func buildRepositoryConfigurationFiles(token string) error {
150155
_, err = eslintConfigFile.WriteString(eslintConfigurationString)
151156
if err != nil {
152157
log.Fatal(err)
158+
}
153159

160+
// Create Trivy configuration after processing ESLint
161+
trivyApiConfiguration := extractTrivyConfiguration(apiToolConfigurations)
162+
if trivyApiConfiguration != nil {
163+
// Create trivy.yaml file based on API configuration
164+
err = createTrivyConfigFile(*trivyApiConfiguration)
165+
if err != nil {
166+
log.Fatal(err)
167+
}
168+
fmt.Println("Trivy configuration created based on Codacy settings")
169+
} else {
170+
// Create default trivy.yaml if no configuration from API
171+
err = createDefaultTrivyConfigFile()
172+
if err != nil {
173+
log.Fatal(err)
174+
}
175+
fmt.Println("Default Trivy configuration created")
154176
}
155177

156178
return nil
@@ -197,6 +219,20 @@ func extractESLintConfiguration(toolConfigurations []CodacyToolConfiguration) *C
197219
return nil
198220
}
199221

222+
// extractTrivyConfiguration extracts Trivy configuration from the Codacy API response
223+
func extractTrivyConfiguration(toolConfigurations []CodacyToolConfiguration) *CodacyToolConfiguration {
224+
// Trivy internal codacy uuid
225+
const TrivyUUID = "2fd7fbe0-33f9-4ab3-ab73-e9b62404e2cb"
226+
227+
for _, toolConfiguration := range toolConfigurations {
228+
if toolConfiguration.Uuid == TrivyUUID {
229+
return &toolConfiguration
230+
}
231+
}
232+
233+
return nil
234+
}
235+
200236
type CodacyToolConfiguration struct {
201237
Uuid string `json:"uuid"`
202238
IsEnabled bool `json:"isEnabled"`
@@ -212,3 +248,65 @@ type ParameterConfiguration struct {
212248
name string `json:"name"`
213249
value string `json:"value"`
214250
}
251+
252+
// createTrivyConfigFile creates a trivy.yaml configuration file based on the API configuration
253+
func createTrivyConfigFile(config CodacyToolConfiguration) error {
254+
// Convert CodacyToolConfiguration to tools.ToolConfiguration
255+
trivyDomainConfiguration := convertAPIToolConfigurationForTrivy(config)
256+
257+
// Use the shared CreateTrivyConfig function to generate the config content
258+
trivyConfigurationString := tools.CreateTrivyConfig(trivyDomainConfiguration)
259+
260+
// Write to file
261+
return os.WriteFile("trivy.yaml", []byte(trivyConfigurationString), 0644)
262+
}
263+
264+
// convertAPIToolConfigurationForTrivy converts API tool configuration to domain model for Trivy
265+
func convertAPIToolConfigurationForTrivy(config CodacyToolConfiguration) tools.ToolConfiguration {
266+
var patterns []tools.PatternConfiguration
267+
268+
// Only process if tool is enabled
269+
if config.IsEnabled {
270+
for _, pattern := range config.Patterns {
271+
var parameters []tools.PatternParameterConfiguration
272+
273+
// By default patterns are enabled
274+
patternEnabled := true
275+
276+
// Check if there's an explicit enabled parameter
277+
for _, param := range pattern.Parameters {
278+
if param.name == "enabled" && param.value == "false" {
279+
patternEnabled = false
280+
}
281+
}
282+
283+
// Add enabled parameter
284+
parameters = append(parameters, tools.PatternParameterConfiguration{
285+
Name: "enabled",
286+
Value: fmt.Sprintf("%t", patternEnabled),
287+
})
288+
289+
patterns = append(
290+
patterns,
291+
tools.PatternConfiguration{
292+
PatternId: pattern.InternalId,
293+
ParamenterConfigurations: parameters,
294+
},
295+
)
296+
}
297+
}
298+
299+
return tools.ToolConfiguration{
300+
PatternsConfiguration: patterns,
301+
}
302+
}
303+
304+
// createDefaultTrivyConfigFile creates a default trivy.yaml configuration file
305+
func createDefaultTrivyConfigFile() error {
306+
// Use empty tool configuration to get default settings
307+
emptyConfig := tools.ToolConfiguration{}
308+
content := tools.CreateTrivyConfig(emptyConfig)
309+
310+
// Write to file
311+
return os.WriteFile("trivy.yaml", []byte(content), 0644)
312+
}

config/tools-installer.go

Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package config
33
import (
44
"bytes"
55
"codacy/cli-v2/plugins"
6+
"codacy/cli-v2/utils"
67
"fmt"
78
"log"
89
"os"
910
"os/exec"
11+
"path/filepath"
1012
"strings"
1113
"text/template"
1214
)
@@ -30,18 +32,26 @@ func InstallTool(name string, toolInfo *plugins.ToolInfo) error {
3032
return nil
3133
}
3234

33-
// Get the runtime for this tool
34-
runtimeInfo, ok := Config.Runtimes()[toolInfo.Runtime]
35-
if !ok {
36-
return fmt.Errorf("required runtime %s not found for tool %s", toolInfo.Runtime, name)
37-
}
38-
3935
// Make sure the installation directory exists
4036
err := os.MkdirAll(toolInfo.InstallDir, 0755)
4137
if err != nil {
4238
return fmt.Errorf("failed to create installation directory: %w", err)
4339
}
4440

41+
// Check if this is a download-based tool (like trivy) or a runtime-based tool (like eslint)
42+
if toolInfo.DownloadURL != "" {
43+
// This is a download-based tool
44+
return installDownloadBasedTool(toolInfo)
45+
}
46+
47+
// This is a runtime-based tool, proceed with regular installation
48+
49+
// Get the runtime for this tool
50+
runtimeInfo, ok := Config.Runtimes()[toolInfo.Runtime]
51+
if !ok {
52+
return fmt.Errorf("required runtime %s not found for tool %s", toolInfo.Runtime, name)
53+
}
54+
4555
// Prepare template data
4656
templateData := map[string]string{
4757
"InstallDir": toolInfo.InstallDir,
@@ -80,7 +90,7 @@ func InstallTool(name string, toolInfo *plugins.ToolInfo) error {
8090

8191
// Execute the installation command using the package manager
8292
cmd := exec.Command(packageManagerBinary, strings.Split(installCmd, " ")...)
83-
93+
8494
log.Printf("Installing %s v%s...\n", toolInfo.Name, toolInfo.Version)
8595
output, err := cmd.CombinedOutput()
8696
if err != nil {
@@ -91,6 +101,64 @@ func InstallTool(name string, toolInfo *plugins.ToolInfo) error {
91101
return nil
92102
}
93103

104+
// installDownloadBasedTool installs a tool by downloading and extracting it
105+
func installDownloadBasedTool(toolInfo *plugins.ToolInfo) error {
106+
// Create a file name for the downloaded archive
107+
fileName := filepath.Base(toolInfo.DownloadURL)
108+
downloadPath := filepath.Join(Config.ToolsDirectory(), fileName)
109+
110+
// Check if the file already exists
111+
_, err := os.Stat(downloadPath)
112+
if os.IsNotExist(err) {
113+
// Download the file
114+
log.Printf("Downloading %s v%s...\n", toolInfo.Name, toolInfo.Version)
115+
downloadPath, err = utils.DownloadFile(toolInfo.DownloadURL, Config.ToolsDirectory())
116+
if err != nil {
117+
return fmt.Errorf("failed to download tool: %w", err)
118+
}
119+
} else if err != nil {
120+
return fmt.Errorf("error checking for existing download: %w", err)
121+
} else {
122+
log.Printf("Using existing download for %s v%s\n", toolInfo.Name, toolInfo.Version)
123+
}
124+
125+
// Open the downloaded file
126+
file, err := os.Open(downloadPath)
127+
if err != nil {
128+
return fmt.Errorf("failed to open downloaded file: %w", err)
129+
}
130+
defer file.Close()
131+
132+
// Create the installation directory
133+
err = os.MkdirAll(toolInfo.InstallDir, 0755)
134+
if err != nil {
135+
return fmt.Errorf("failed to create installation directory: %w", err)
136+
}
137+
138+
// Extract directly to the installation directory
139+
log.Printf("Extracting %s v%s...\n", toolInfo.Name, toolInfo.Version)
140+
if strings.HasSuffix(fileName, ".zip") {
141+
err = utils.ExtractZip(file.Name(), toolInfo.InstallDir)
142+
} else {
143+
err = utils.ExtractTarGz(file, toolInfo.InstallDir)
144+
}
145+
146+
if err != nil {
147+
return fmt.Errorf("failed to extract tool: %w", err)
148+
}
149+
150+
// Make sure all binaries are executable
151+
for _, binaryPath := range toolInfo.Binaries {
152+
err = os.Chmod(filepath.Join(toolInfo.InstallDir, filepath.Base(binaryPath)), 0755)
153+
if err != nil && !os.IsNotExist(err) {
154+
return fmt.Errorf("failed to make binary executable: %w", err)
155+
}
156+
}
157+
158+
log.Printf("Successfully installed %s v%s\n", toolInfo.Name, toolInfo.Version)
159+
return nil
160+
}
161+
94162
// isToolInstalled checks if a tool is already installed by checking for the binary
95163
func isToolInstalled(toolInfo *plugins.ToolInfo) bool {
96164
// If there are no binaries, check the install directory
@@ -116,12 +184,12 @@ func executeToolTemplate(tmplStr string, data map[string]string) (string, error)
116184
if err != nil {
117185
return "", err
118186
}
119-
187+
120188
var buf bytes.Buffer
121189
err = tmpl.Execute(&buf, data)
122190
if err != nil {
123191
return "", err
124192
}
125-
193+
126194
return buf.String(), nil
127195
}

0 commit comments

Comments
 (0)