Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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,4 +1,6 @@
runtimes:
- node@22.2.0
- python@3.9.21
tools:
- eslint@9.3.0
- pylint@2.13.9
16 changes: 16 additions & 0 deletions cmd/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,24 @@ var analyzeCmd = &cobra.Command{
switch toolToAnalyze {
case "eslint":
// nothing
case "pylint":
pylint := config.Config.Tools()["pylint"]
if pylint == nil {
log.Fatal("Pylint is not installed. Please install it using 'codacy-cli install'.")
}
pylintInstallationDirectory := pylint.Info()["installDir"]
pythonRuntime := config.Config.Runtimes()["python"]
pythonBinary := pythonRuntime.Info()["python"]

log.Printf("Running %s...\n", toolToAnalyze)
if outputFile != "" {
log.Println("Output will be available at", outputFile)
}
tools.RunPylint(workDirectory, pylintInstallationDirectory, pythonBinary, args, autoFix, outputFile)

case "":
log.Fatal("You need to specify a tool to run analysis with, e.g., '--tool eslint'", toolToAnalyze)

default:
log.Fatal("Trying to run unsupported tool: ", toolToAnalyze)
}
Expand Down
8 changes: 7 additions & 1 deletion cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,24 @@ func configFileTemplate(tools []tools.Tool) string {

// Default version
eslintVersion := "9.3.0"
pylintVersion := "2.13.9"

for _, tool := range tools {
if tool.Uuid == "f8b29663-2cb2-498d-b923-a10c6a8c05cd" {
eslintVersion = tool.Version
}
if tool.Uuid == "34225275-f79e-4b85-8126-c7512c987c0d" {
pylintVersion = tool.Version
}
}

return fmt.Sprintf(`runtimes:
- node@22.2.0
- python@3.9.21
tools:
- eslint@%s
`, eslintVersion)
- pylint@%s
`, eslintVersion, pylintVersion)
}

func buildRepositoryConfigurationFiles(token string) error {
Expand Down
11 changes: 11 additions & 0 deletions cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ func fetchRuntimes(config *cfg.ConfigType) {
if err != nil {
log.Fatal(err)
}
case "python":
err := cfg.InstallPython(r)
if err != nil {
log.Fatal(err)
}
default:
log.Fatal("Unknown runtime:", r.Name())
}
Expand All @@ -52,6 +57,12 @@ func fetchTools(config *cfg.ConfigType) {
fmt.Println(err.Error())
log.Fatal(err)
}
case "pylint":
pythonRuntime := config.Runtimes()["python"]
err := cfg.InstallPylint(pythonRuntime, tool)
if err != nil {
log.Fatal(err)
}
default:
log.Fatal("Unknown tool:", tool.Name())
}
Expand Down
Binary file added codacy-cli
Binary file not shown.
Binary file added codacy-cli-local
Binary file not shown.
58 changes: 58 additions & 0 deletions config/pylint-utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package config

import (
"fmt"
"log"
"os/exec"
"path"
)

func getInfoPylint() map[string]string {
pythonRuntime := Config.Runtimes()["python"]

pythonFolder := fmt.Sprintf("%s@%s", pythonRuntime.Name(), pythonRuntime.Version())
pythonInstallDir := path.Join(Config.RuntimesDirectory(), pythonFolder, "python")
pylintPath := path.Join(pythonInstallDir, "bin", "pylint")

return map[string]string{
"installDir": pythonInstallDir,
"pylint": pylintPath,
}

}

// installing in the python runtime because

Check notice on line 24 in config/pylint-utils.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

config/pylint-utils.go#L24

comment on exported function InstallPylint should be of the form "InstallPylint ..."
// f you install Pylint in a different tools folder, it will not work properly because of the following reasons:
// Python Virtual Environment Isolation:
// When you install Pylint in the tools folder (separately from Python), you are essentially mixing environments.
// The python binary located in /Users/yasmin/.cache/codacy/runtimes/python@3.10.16/python/bin/python3 will not have
// access to packages installed elsewhere unless properly referenced.
// PYTHONPATH Limitation:
// You tried passing the tools directory via PYTHONPATH.
// While PYTHONPATH allows modules to be found, it doesn't register packages installed by pip properly.
// Pylint Binary (pylint):
// The pylint binary expects the pylint package to be installed within the same Python environment that is running it.
// If you run:
// /Users/.cache/codacy/runtimes/python@3.10.16/python/bin/python3 -m pylint
// It will look for the pylint module installed under its site-packages directory within:
// /Users/.cache/codacy/runtimes/python@3.10.16/python/lib/python3.10/site-packages
func InstallPylint(pythonRuntime *Runtime, pylint *Runtime) error {
log.Println("Installing Pylint")

pythonInfo := getInfoPython(pythonRuntime)

pythonBinary := pythonInfo["python"]

// to install pylint using oython binary
Comment thread
zhamborova marked this conversation as resolved.
Outdated
cmd := exec.Command(pythonBinary, "-m", "pip", "install",

Check failure on line 47 in config/pylint-utils.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

config/pylint-utils.go#L47

Detected non-static command inside Command.

Check failure on line 47 in config/pylint-utils.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

config/pylint-utils.go#L47

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.
fmt.Sprintf("pylint==%s", pylint.Version()),
)

output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("error installing Pylint: %v\nOutput: %s", err, string(output))
}

log.Println("Pylint installed successfully")
return nil
}
114 changes: 114 additions & 0 deletions config/python-utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package config

import (
"codacy/cli-v2/utils"
"fmt"
"log"
"os"
"path"
"runtime"
)

func getInfoPython(r *Runtime) map[string]string {
pythonFolder := fmt.Sprintf("%s@%s", r.Name(), r.Version())
installDir := path.Join(Config.RuntimesDirectory(), pythonFolder)

var pythonBinary, pipBinary string

//todo check windows dire,
//had to add python subdir to path since tar extracts it there
if runtime.GOOS == "windows" {
pythonBinary = path.Join(installDir, "Scripts", "python.exe")
pipBinary = path.Join(installDir, "Scripts", "pip.exe")
} else {
pythonBinary = path.Join(installDir, "python", "bin", "python3")
pipBinary = path.Join(installDir, "python", "bin", "pip")
}

return map[string]string{
"installDir": installDir,
"python": pythonBinary,
"pip": pipBinary,
}
}

func getDownloadURL(pythonRuntime *Runtime) string {

Check warning on line 35 in config/python-utils.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

config/python-utils.go#L35

Method getDownloadURL has a cyclomatic complexity of 8 (limit is 7)

version := pythonRuntime.Version()
goos := runtime.GOOS
goarch := runtime.GOARCH

var pyArch string
switch goarch {
case "386":
pyArch = "x86"
case "amd64":
pyArch = "x86_64"
case "arm":
pyArch = "armv7l"
case "arm64":
pyArch = "aarch64"
default:
pyArch = goarch
}

var pyOS string
switch goos {
case "darwin":
pyOS = "apple-darwin"
case "linux":
pyOS = "unknown-linux-gnu"
case "windows":
pyOS = "pc-windows-msvc"
default:
pyOS = goos
}

releaseVersion := "20250317"
baseURL := "https://github.com/astral-sh/python-build-standalone/releases/download/"

filename := fmt.Sprintf("cpython-%s+%s-%s-%s-install_only.tar.gz", version, releaseVersion, pyArch, pyOS)

return fmt.Sprintf("%s%s/%s", baseURL, releaseVersion, filename)

}

func InstallPython(r *Runtime) error {

Check notice on line 76 in config/python-utils.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

config/python-utils.go#L76

exported function InstallPython should have comment or be unexported

pythonFolder := fmt.Sprintf("%s@%s", r.Name(), r.Version())
installDir := path.Join(Config.RuntimesDirectory(), pythonFolder)
log.Println("Fetching python...")
downloadPythonURL := getDownloadURL(r)
pythonTar, err := utils.DownloadFile(downloadPythonURL, Config.RuntimesDirectory())
if err != nil {
return err
}

// Make sure the installDir exists
err = os.MkdirAll(installDir, 0777)

Check warning on line 88 in config/python-utils.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

config/python-utils.go#L88

Detected file permissions that are set to more than `0600` (user/owner can read and write). Setting file permissions to higher than `0600` is most likely unnecessary and violates the principle of least privilege.

Check warning on line 88 in config/python-utils.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

config/python-utils.go#L88

The application was found setting directory permissions to overly permissive values.
if err != nil {
return fmt.Errorf("failed to create install directory: %v", err)
}

// Open the downloaded file
t, err := os.Open(pythonTar)
defer t.Close()
if err != nil {
return err
}

// Extract the archive to the desired directory without creating links yet
err = utils.ExtractTarGz(t, installDir)
if err != nil {
return fmt.Errorf("failed to extract archive: %v", err)
}

//remove tar after extraction
err = os.Remove(pythonTar)
if err != nil {
return fmt.Errorf("failed to delete downloaded archive: %v", err)
}

log.Println("Python successfully installed at:", installDir)
return nil
}
4 changes: 4 additions & 0 deletions config/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ func (r *Runtime) populateInfo() {
r.info = genInfoNode(r)
case "eslint":
r.info = genInfoEslint(r)
case "python":
r.info = getInfoPython(r)
case "pylint":
r.info = getInfoPylint()
}
}

Expand Down
84 changes: 84 additions & 0 deletions tools/pylintRunner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package tools

import (
"bytes"
"codacy/cli-v2/utils"
"log"
"os"
"os/exec"
"path/filepath"
)

func RunPylint(repositoryToAnalyseDirectory string, pylintInstallationDirectory string, pythonBinary string, pathsToCheck []string, autoFix bool, outputFile string) {

Check warning 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 6 parameters (limit is 5)

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

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

parameter 'pylintInstallationDirectory' seems to be unused, consider removing or renaming it as _

// Prepare the command to run pylint as a module with JSON output
var args []string
args = append(args, "-m", "pylint", "--output-format=json")

// Create a temporary file for JSON output if an output file is specified
tempFile := ""
if outputFile != "" {
tempFile = filepath.Join(os.TempDir(), "pylint_output.json")
args = append(args, "--output", tempFile)
}

// Add files/directories to check
if len(pathsToCheck) > 0 {
args = append(args, pathsToCheck...)
} else {
args = append(args, repositoryToAnalyseDirectory)
}

cmd := exec.Command(pythonBinary, args...)

Check failure on line 32 in tools/pylintRunner.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tools/pylintRunner.go#L32

Detected non-static command inside Command.

Check failure on line 32 in tools/pylintRunner.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tools/pylintRunner.go#L32

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 = repositoryToAnalyseDirectory

// Set stderr and stdout to be displayed
cmd.Stderr = os.Stderr

// For terminal output capture mode
var stdout bytes.Buffer
if outputFile == "" {
// Terminal output mode - capture JSON and print SARIF directly
cmd.Stdout = &stdout
} else {
// File output mode - show output in terminal
cmd.Stdout = os.Stdout
}

// Run pylint
log.Printf("Running pylint command: %v", cmd.Args)
// Pylint returns non-zero exit codes when it finds issues, so we're not checking the error
cmd.Run()

if outputFile != "" {
// Read the JSON output from the temporary file
outputData, err := os.ReadFile(tempFile)
if err != nil {
log.Printf("Failed to read pylint output from %s: %v", tempFile, err)
return
}

// Delete temporary file
defer os.Remove(tempFile)

// Convert JSON to SARIF using the utility function
sarifData := utils.ConvertPylintToSarif(outputData)

// Write SARIF to the output file
err = os.WriteFile(outputFile, sarifData, 0644)

Check warning on line 68 in tools/pylintRunner.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tools/pylintRunner.go#L68

The application was found setting file permissions to overly permissive values.
if err != nil {
log.Printf("Failed to write SARIF output to %s: %v", outputFile, err)
}

log.Printf("SARIF output saved to: %s\n", outputFile)
} else {
// Get the JSON output from the buffer
jsonOutput := stdout.Bytes()

// Convert JSON to SARIF
sarifOutput := utils.ConvertPylintToSarif(jsonOutput)

// Print the SARIF output to stdout
os.Stdout.Write(sarifOutput)
}
}
Loading