Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .codacy/codacy.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
runtimes:
- [email protected]
- [email protected]
tools:
- [email protected]
- [email protected]
- [email protected]
13 changes: 12 additions & 1 deletion cmd/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ type Pattern struct {
}

func init() {
analyzeCmd.Flags().StringVarP(&outputFile, "output", "o", "", "output file for the results")
analyzeCmd.Flags().StringVarP(&outputFile, "output", "o", "", "Output file for analysis results")
analyzeCmd.Flags().StringVarP(&toolToAnalyze, "tool", "t", "", "Which tool to run analysis with")
analyzeCmd.Flags().StringVar(&outputFormat, "format", "", "Output format (use 'sarif' for SARIF format)")
analyzeCmd.Flags().BoolVar(&autoFix, "fix", false, "Apply auto fix to your issues when available")
Expand Down Expand Up @@ -203,6 +203,15 @@ func runTrivyAnalysis(workDirectory string, pathsToCheck []string, outputFile st
}
}

func runPylintAnalysis(workDirectory string, pathsToCheck []string, outputFile string, outputFormat string) {
pylint := config.Config.Tools()["pylint"]

err := tools.RunPylint(workDirectory, pylint, pathsToCheck, outputFile, outputFormat)
if err != nil {
log.Fatalf("Error running Pylint: %v", err)
}
}

var analyzeCmd = &cobra.Command{
Use: "analyze",
Short: "Runs all linters.",
Expand All @@ -226,6 +235,8 @@ var analyzeCmd = &cobra.Command{
runEslintAnalysis(workDirectory, args, autoFix, outputFile, outputFormat)
case "trivy":
runTrivyAnalysis(workDirectory, args, outputFile, outputFormat)
case "pylint":
runPylintAnalysis(workDirectory, args, outputFile, outputFormat)
case "":
log.Fatal("You need to specify a tool to run analysis with, e.g., '--tool eslint'")
default:
Expand Down
7 changes: 6 additions & 1 deletion cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ func configFileTemplate(tools []tools.Tool) string {
// Default versions
eslintVersion := "9.3.0"
trivyVersion := "0.59.1" // Latest stable version
pylintVersion := "3.3.6"

for _, tool := range tools {
if tool.Uuid == "f8b29663-2cb2-498d-b923-a10c6a8c05cd" {
Expand All @@ -83,6 +84,9 @@ func configFileTemplate(tools []tools.Tool) string {
if tool.Uuid == "2fd7fbe0-33f9-4ab3-ab73-e9b62404e2cb" {
trivyVersion = tool.Version
}
if tool.Uuid == "31677b6d-4ae0-4f56-8041-606a8d7a8e61" {
pylintVersion = tool.Version
}
}

return fmt.Sprintf(`runtimes:
Expand All @@ -91,7 +95,8 @@ func configFileTemplate(tools []tools.Tool) string {
tools:
- eslint@%s
- trivy@%s
`, eslintVersion, trivyVersion)
- pylint@%s
`, eslintVersion, trivyVersion, pylintVersion)
}

func buildRepositoryConfigurationFiles(token string) error {
Expand Down
42 changes: 41 additions & 1 deletion config/tools-installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,16 @@
return installDownloadBasedTool(toolInfo)
}

// This is a runtime-based tool, proceed with regular installation
// Handle Python tools differently
if toolInfo.Runtime == "python" {
return installPythonTool(name, toolInfo)
}

// Handle other runtime-based tools
return installRuntimeTool(name, toolInfo)
}

func installRuntimeTool(name string, toolInfo *plugins.ToolInfo) error {

Check warning on line 56 in config/tools-installer.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

config/tools-installer.go#L56

Method installRuntimeTool has a cyclomatic complexity of 9 (limit is 7)
// Get the runtime for this tool
runtimeInfo, ok := Config.Runtimes()[toolInfo.Runtime]
if !ok {
Expand Down Expand Up @@ -159,6 +167,38 @@
return nil
}

func installPythonTool(name string, toolInfo *plugins.ToolInfo) error {
log.Printf("Installing %s v%s...\n", toolInfo.Name, toolInfo.Version)

runtimeInfo, ok := Config.Runtimes()[toolInfo.Runtime]
if !ok {
return fmt.Errorf("required runtime %s not found for tool %s", toolInfo.Runtime, name)
}

pythonBinary, ok := runtimeInfo.Binaries["python3"]
if !ok {
return fmt.Errorf("python3 binary not found in runtime")
}

// Create venv
cmd := exec.Command(pythonBinary, "-m", "venv", filepath.Join(toolInfo.InstallDir, "venv"))

Check failure on line 184 in config/tools-installer.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

config/tools-installer.go#L184

Detected non-static command inside Command.

Check failure on line 184 in config/tools-installer.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

config/tools-installer.go#L184

OS command injection is a critical vulnerability that can lead to a full system compromise as it may allow an adversary to pass in arbitrary commands or arguments to be executed.
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to create venv: %s\nError: %w", string(output), err)
}

// Install the tool using pip from venv
pipPath := filepath.Join(toolInfo.InstallDir, "venv", "bin", "pip")
cmd = exec.Command(pipPath, "install", fmt.Sprintf("%s==%s", toolInfo.Name, toolInfo.Version))

Check failure on line 192 in config/tools-installer.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

config/tools-installer.go#L192

Detected non-static command inside Command.

Check failure on line 192 in config/tools-installer.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

config/tools-installer.go#L192

OS command injection is a critical vulnerability that can lead to a full system compromise as it may allow an adversary to pass in arbitrary commands or arguments to be executed.
output, err = cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to install tool: %s\nError: %w", string(output), err)
}

log.Printf("Successfully installed %s v%s\n", toolInfo.Name, toolInfo.Version)
return nil
}

// isToolInstalled checks if a tool is already installed by checking for the binary
func isToolInstalled(toolInfo *plugins.ToolInfo) bool {
// If there are no binaries, check the install directory
Expand Down
5 changes: 5 additions & 0 deletions plugins/runtime-utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,11 @@

// GetInstallationDirectoryPath returns the installation directory path for the runtime
func (p *runtimePlugin) getInstallationDirectoryPath(runtimesDir string, version string) string {
// For Python, we want to use a simpler directory structure
if p.Config.Name == "python" {
return path.Join(runtimesDir, "python")

Check warning on line 259 in plugins/runtime-utils.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

plugins/runtime-utils.go#L259

`path.Join(...)` always joins using a forward slash.
Copy link

Copilot AI Apr 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For cross-platform compatibility, consider using filepath.Join instead of path.Join when constructing filesystem paths.

Suggested change
return path.Join(runtimesDir, "python")
return filepath.Join(runtimesDir, "python")

Copilot uses AI. Check for mistakes.
}
// For other runtimes, keep using the filename-based directory
fileName := p.getFileName(version)
return path.Join(runtimesDir, fileName)
}
Expand Down
4 changes: 2 additions & 2 deletions plugins/runtimes/python/plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ download:
"windows": "pc-windows-msvc"
binaries:
- name: python3
path: "python/bin/python3"
path: "bin/python3"
- name: pip
path: "python/bin/pip"
path: "bin/pip"
16 changes: 16 additions & 0 deletions plugins/tools/pylint/plugin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: pylint
description: Python linter
runtime: python
runtime_binaries:
package_manager: python3
execution: python3
binaries:
- name: python
path: "venv/bin/python3"
formatters:
- name: json
flag: "--output-format=json"
output_options:
file_flag: "--output"
analysis_options:
default_path: "."
80 changes: 80 additions & 0 deletions tools/pylintRunner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package tools

import (
"codacy/cli-v2/plugins"
"codacy/cli-v2/utils"
"fmt"
"os"
"os/exec"
"path/filepath"
)

func RunPylint(workDirectory string, toolInfo *plugins.ToolInfo, files []string, outputFile string, outputFormat string) error {

Check failure on line 12 in tools/pylintRunner.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tools/pylintRunner.go#L12

Method RunPylint has a cyclomatic complexity of 11 (limit is 10)

Check notice on line 12 in tools/pylintRunner.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tools/pylintRunner.go#L12

exported function RunPylint should have comment or be unexported
// Get Python binary from venv
pythonPath := filepath.Join(toolInfo.InstallDir, "venv", "bin", "python3")

// Construct base command with -m pylint to run pylint module
args := []string{"-m", "pylint"}

// Always use JSON output format since we'll convert to SARIF if needed
args = append(args, "--output-format=json")

// Create a temporary file for JSON output if we need to convert to SARIF
var tempFile string
if outputFormat == "sarif" {
tmp, err := os.CreateTemp("", "pylint-*.json")
if err != nil {
return fmt.Errorf("failed to create temporary file: %w", err)
}
tempFile = tmp.Name()
tmp.Close()
defer os.Remove(tempFile)
args = append(args, fmt.Sprintf("--output=%s", tempFile))
} else if outputFile != "" {
args = append(args, fmt.Sprintf("--output=%s", outputFile))
}

// Add files to analyze - if no files specified, analyze current directory
if len(files) > 0 {
args = append(args, files...)
} else {
args = append(args, ".")
}

// Create and run command
cmd := exec.Command(pythonPath, args...)

Check failure on line 45 in tools/pylintRunner.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tools/pylintRunner.go#L45

Detected non-static command inside Command.

Check failure on line 45 in tools/pylintRunner.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tools/pylintRunner.go#L45

OS command injection is a critical vulnerability that can lead to a full system compromise as it may allow an adversary to pass in arbitrary commands or arguments to be executed.
cmd.Dir = workDirectory
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

// Run the command
err := cmd.Run()
if err != nil {
// Pylint returns non-zero exit code when it finds issues
// We should not treat this as an error
if _, ok := err.(*exec.ExitError); !ok {
return fmt.Errorf("failed to run pylint: %w", err)
}
}

// If SARIF output is requested, convert JSON to SARIF
if outputFormat == "sarif" {
jsonOutput, err := os.ReadFile(tempFile)
if err != nil {
return fmt.Errorf("failed to read pylint output: %w", err)
}

sarifOutput := utils.ConvertPylintToSarif(jsonOutput)

if outputFile != "" {
err = os.WriteFile(outputFile, sarifOutput, 0644)

Check warning on line 70 in tools/pylintRunner.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tools/pylintRunner.go#L70

The application was found setting file permissions to overly permissive values.
if err != nil {
return fmt.Errorf("failed to write SARIF output: %w", err)
}
} else {
fmt.Println(string(sarifOutput))
}
}

return nil
}
Loading