diff --git a/.codacy/cli-config.yaml b/.codacy/cli-config.yaml deleted file mode 100644 index 6ae4b29d..00000000 --- a/.codacy/cli-config.yaml +++ /dev/null @@ -1 +0,0 @@ -mode: local \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6b64633d..6422c1b9 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ go.work.sum .idea/ .vscode/ -cli-v2 +# Codacy CLI +cli-v2 \ No newline at end of file diff --git a/cmd/analyze.go b/cmd/analyze.go index 56108e0e..8821d501 100644 --- a/cmd/analyze.go +++ b/cmd/analyze.go @@ -20,7 +20,6 @@ var outputFormat string var sarifPath string var commitUuid string var projectToken string -var pmdRulesetFile string type Sarif struct { Runs []struct { @@ -96,7 +95,6 @@ func init() { 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") - analyzeCmd.Flags().StringVar(&pmdRulesetFile, "rulesets", "", "Path to PMD ruleset file") rootCmd.AddCommand(analyzeCmd) } @@ -209,7 +207,7 @@ func runPmdAnalysis(workDirectory string, pathsToCheck []string, outputFile stri pmd := config.Config.Tools()["pmd"] pmdBinary := pmd.Binaries["pmd"] - err := tools.RunPmd(workDirectory, pmdBinary, pathsToCheck, outputFile, outputFormat, pmdRulesetFile) + err := tools.RunPmd(workDirectory, pmdBinary, pathsToCheck, outputFile, outputFormat, "") if err != nil { log.Fatalf("Error running PMD: %v", err) } diff --git a/cmd/init.go b/cmd/init.go index ca09413a..c76c8bb7 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -3,6 +3,7 @@ package cmd import ( "codacy/cli-v2/config" "codacy/cli-v2/tools" + "codacy/cli-v2/utils" "encoding/json" "errors" "fmt" @@ -10,6 +11,7 @@ import ( "log" "net/http" "os" + "path/filepath" "time" "github.com/spf13/cobra" @@ -55,6 +57,7 @@ var initCmd = &cobra.Command{ if err != nil { log.Fatal(err) } + createGitIgnoreFile() } fmt.Println() fmt.Println("✅ Successfully initialized Codacy configuration!") @@ -66,6 +69,26 @@ var initCmd = &cobra.Command{ }, } +func createGitIgnoreFile() error { + gitIgnorePath := filepath.Join(config.Config.LocalCodacyDirectory(), ".gitignore") + gitIgnoreFile, err := os.Create(gitIgnorePath) + if err != nil { + return fmt.Errorf("failed to create .gitignore file: %w", err) + } + defer gitIgnoreFile.Close() + + content := `# Codacy CLI +tools-configs/ +.gitignore +cli-config.yaml +` + if _, err := gitIgnoreFile.WriteString(content); err != nil { + return fmt.Errorf("failed to write to .gitignore file: %w", err) + } + + return nil +} + func createConfigurationFiles(tools []tools.Tool, cliLocalMode bool) error { configFile, err := os.Create(config.Config.ProjectConfigFile()) defer configFile.Close() @@ -142,6 +165,13 @@ func buildRepositoryConfigurationFiles(token string) error { fmt.Println("Building repository configuration files ...") fmt.Println("Fetching repository configuration from codacy ...") + toolsConfigDir := config.Config.ToolsConfigDirectory() + + // Create tools-configs directory if it doesn't exist + if err := os.MkdirAll(toolsConfigDir, utils.DefaultDirPerms); err != nil { + return fmt.Errorf("failed to create tools-configs directory: %w", err) + } + // API call to fetch settings url := CodacyApiBase + "/2.0/project/analysis/configuration" @@ -198,7 +228,7 @@ func buildRepositoryConfigurationFiles(token string) error { eslintDomainConfiguration := convertAPIToolConfigurationToDomain(*eslintApiConfiguration) eslintConfigurationString := tools.CreateEslintConfig(eslintDomainConfiguration) - eslintConfigFile, err := os.Create("eslint.config.mjs") + eslintConfigFile, err := os.Create(filepath.Join(toolsConfigDir, "eslint.config.mjs")) if err != nil { return fmt.Errorf("failed to create eslint config file: %v", err) } @@ -210,7 +240,7 @@ func buildRepositoryConfigurationFiles(token string) error { } fmt.Println("ESLint configuration created based on Codacy settings") } else { - err = createDefaultEslintConfigFile() + err = createDefaultEslintConfigFile(toolsConfigDir) if err != nil { return fmt.Errorf("failed to create default ESLint config: %v", err) } @@ -220,13 +250,13 @@ func buildRepositoryConfigurationFiles(token string) error { // Trivy configuration trivyApiConfiguration := extractTrivyConfiguration(apiToolConfigurations) if trivyApiConfiguration != nil { - err = createTrivyConfigFile(*trivyApiConfiguration) + err = createTrivyConfigFile(*trivyApiConfiguration, toolsConfigDir) if err != nil { return fmt.Errorf("failed to create Trivy config: %v", err) } fmt.Println("Trivy configuration created based on Codacy settings") } else { - err = createDefaultTrivyConfigFile() + err = createDefaultTrivyConfigFile(toolsConfigDir) if err != nil { return fmt.Errorf("failed to create default Trivy config: %v", err) } @@ -236,13 +266,13 @@ func buildRepositoryConfigurationFiles(token string) error { // PMD configuration pmdApiConfiguration := extractPMDConfiguration(apiToolConfigurations) if pmdApiConfiguration != nil { - err = createPMDConfigFile(*pmdApiConfiguration) + err = createPMDConfigFile(*pmdApiConfiguration, toolsConfigDir) if err != nil { return fmt.Errorf("failed to create PMD config: %v", err) } fmt.Println("PMD configuration created based on Codacy settings") } else { - err = createDefaultPMDConfigFile() + err = createDefaultPMDConfigFile(toolsConfigDir) if err != nil { return fmt.Errorf("failed to create default PMD config: %v", err) } @@ -318,16 +348,16 @@ func extractPMDConfiguration(toolConfigurations []CodacyToolConfiguration) *Coda return nil } -func createPMDConfigFile(config CodacyToolConfiguration) error { +func createPMDConfigFile(config CodacyToolConfiguration, toolsConfigDir string) error { pmdDomainConfiguration := convertAPIToolConfigurationToDomain(config) pmdConfigurationString := tools.CreatePmdConfig(pmdDomainConfiguration) - return os.WriteFile("pmd-ruleset.xml", []byte(pmdConfigurationString), 0644) + return os.WriteFile(filepath.Join(toolsConfigDir, "pmd-ruleset.xml"), []byte(pmdConfigurationString), utils.DefaultRW) } -func createDefaultPMDConfigFile() error { +func createDefaultPMDConfigFile(toolsConfigDir string) error { emptyConfig := tools.ToolConfiguration{} content := tools.CreatePmdConfig(emptyConfig) - return os.WriteFile("pmd-ruleset.xml", []byte(content), 0644) + return os.WriteFile(filepath.Join(toolsConfigDir, "pmd-ruleset.xml"), []byte(content), utils.DefaultRW) } type CodacyToolConfiguration struct { @@ -347,7 +377,7 @@ type ParameterConfiguration struct { } // createTrivyConfigFile creates a trivy.yaml configuration file based on the API configuration -func createTrivyConfigFile(config CodacyToolConfiguration) error { +func createTrivyConfigFile(config CodacyToolConfiguration, toolsConfigDir string) error { // Convert CodacyToolConfiguration to tools.ToolConfiguration trivyDomainConfiguration := convertAPIToolConfigurationForTrivy(config) @@ -355,7 +385,7 @@ func createTrivyConfigFile(config CodacyToolConfiguration) error { trivyConfigurationString := tools.CreateTrivyConfig(trivyDomainConfiguration) // Write to file - return os.WriteFile("trivy.yaml", []byte(trivyConfigurationString), 0644) + return os.WriteFile(filepath.Join(toolsConfigDir, "trivy.yaml"), []byte(trivyConfigurationString), utils.DefaultRW) } // convertAPIToolConfigurationForTrivy converts API tool configuration to domain model for Trivy @@ -399,21 +429,21 @@ func convertAPIToolConfigurationForTrivy(config CodacyToolConfiguration) tools.T } // createDefaultTrivyConfigFile creates a default trivy.yaml configuration file -func createDefaultTrivyConfigFile() error { +func createDefaultTrivyConfigFile(toolsConfigDir string) error { // Use empty tool configuration to get default settings emptyConfig := tools.ToolConfiguration{} content := tools.CreateTrivyConfig(emptyConfig) // Write to file - return os.WriteFile("trivy.yaml", []byte(content), 0644) + return os.WriteFile(filepath.Join(toolsConfigDir, "trivy.yaml"), []byte(content), utils.DefaultRW) } // createDefaultEslintConfigFile creates a default eslint.config.mjs configuration file -func createDefaultEslintConfigFile() error { +func createDefaultEslintConfigFile(toolsConfigDir string) error { // Use empty tool configuration to get default settings emptyConfig := tools.ToolConfiguration{} content := tools.CreateEslintConfig(emptyConfig) // Write to file - return os.WriteFile("eslint.config.mjs", []byte(content), 0644) + return os.WriteFile(filepath.Join(toolsConfigDir, "eslint.config.mjs"), []byte(content), utils.DefaultRW) } diff --git a/config/config.go b/config/config.go index 8bff582d..d293c33b 100644 --- a/config/config.go +++ b/config/config.go @@ -15,6 +15,7 @@ type ConfigType struct { runtimesDirectory string toolsDirectory string localCodacyDirectory string + toolsConfigDirectory string projectConfigFile string cliConfigFile string @@ -42,6 +43,10 @@ func (c *ConfigType) LocalCodacyDirectory() string { return c.localCodacyDirectory } +func (c *ConfigType) ToolsConfigsDirectory() string { + return c.toolsConfigDirectory +} + func (c *ConfigType) ProjectConfigFile() string { return c.projectConfigFile } @@ -88,11 +93,16 @@ func (c *ConfigType) AddTools(configs []plugins.ToolConfig) error { return nil } +func (c *ConfigType) ToolsConfigDirectory() string { + return c.toolsConfigDirectory +} + func (c *ConfigType) setupCodacyPaths() { c.globalCacheDirectory = filepath.Join(c.homePath, ".cache", "codacy") c.runtimesDirectory = filepath.Join(c.globalCacheDirectory, "runtimes") c.toolsDirectory = filepath.Join(c.globalCacheDirectory, "tools") c.localCodacyDirectory = ".codacy" + c.toolsConfigDirectory = filepath.Join(c.localCodacyDirectory, "tools-configs") c.projectConfigFile = filepath.Join(c.localCodacyDirectory, "codacy.yaml") c.cliConfigFile = filepath.Join(c.localCodacyDirectory, "cli-config.yaml") diff --git a/tools/eslintConfigCreator.go b/tools/eslintConfigCreator.go index f52bf776..2bddf973 100644 --- a/tools/eslintConfigCreator.go +++ b/tools/eslintConfigCreator.go @@ -26,7 +26,6 @@ func CreateEslintConfig(configuration ToolConfiguration) string { for _, patternConfiguration := range configuration.PatternsConfiguration { rule := strings.TrimPrefix(patternConfiguration.PatternId, "ESLint8_") - fmt.Println("Rule:", rule) const tempstring = "TEMPORARYSTRING" rule = strings.ReplaceAll(rule, "__", tempstring) diff --git a/tools/eslintRunner.go b/tools/eslintRunner.go index 798b87f9..3ae58b0b 100644 --- a/tools/eslintRunner.go +++ b/tools/eslintRunner.go @@ -1,6 +1,7 @@ package tools import ( + "codacy/cli-v2/config" "os" "os/exec" "path/filepath" @@ -14,6 +15,15 @@ func RunEslint(repositoryToAnalyseDirectory string, eslintInstallationDirectory eslintJsPath := filepath.Join(eslintInstallationNodeModules, ".bin", "eslint") cmd := exec.Command(nodeBinary, eslintJsPath) + + // For Eslint compatibility with version 8. + // https://eslint.org/docs/v8.x/use/configure/configuration-files-new + cmd.Env = append(cmd.Env, "ESLINT_USE_FLAT_CONFIG=true") + + // Add config file from tools-configs directory + configFile := filepath.Join(config.Config.ToolsConfigDirectory(), "eslint.config.mjs") + cmd.Args = append(cmd.Args, "-c", configFile) + if autoFix { cmd.Args = append(cmd.Args, "--fix") } @@ -39,9 +49,9 @@ func RunEslint(repositoryToAnalyseDirectory string, eslintInstallationDirectory nodePathEnv := "NODE_PATH=" + eslintInstallationNodeModules cmd.Env = append(cmd.Env, nodePathEnv) - //DEBUG - //fmt.Println(cmd.Env) - //fmt.Println(cmd) + // DEBUG + // fmt.Println(cmd.Env) + // fmt.Println(cmd) // TODO eslint returns 1 when it finds errors, so we're not propagating it cmd.Run() diff --git a/tools/pmdRunner.go b/tools/pmdRunner.go index 996d30b9..173094d0 100644 --- a/tools/pmdRunner.go +++ b/tools/pmdRunner.go @@ -1,41 +1,55 @@ package tools import ( + "codacy/cli-v2/config" "os" "os/exec" + "path/filepath" "strings" ) // RunPmd executes PMD static code analyzer with the specified options +// +// Parameters: +// - repositoryToAnalyseDirectory: The root directory of the repository to analyze +// - pmdBinary: Path to the PMD executable +// - pathsToCheck: List of specific paths to analyze, if empty analyzes whole repository +// - outputFile: Path where analysis results should be written +// - outputFormat: Format for the output (e.g. "sarif") +// - rulesetFile: Path to custom ruleset XML file, if empty uses default ruleset +// +// Returns: +// - error: nil if analysis succeeds or violations found, error otherwise func RunPmd(repositoryToAnalyseDirectory string, pmdBinary string, pathsToCheck []string, outputFile string, outputFormat string, rulesetFile string) error { - cmdArgs := []string{"pmd"} + cmd := exec.Command(pmdBinary, "pmd") + + // Add config file from tools-configs directory if not specified + if rulesetFile == "" { + configFile := filepath.Join(config.Config.ToolsConfigDirectory(), "pmd-ruleset.xml") + cmd.Args = append(cmd.Args, "-R", configFile) + } else { + cmd.Args = append(cmd.Args, "-R", rulesetFile) + } // Add source directories (comma-separated list for PMD) if len(pathsToCheck) > 0 { dirArg := strings.Join(pathsToCheck, ",") - cmdArgs = append(cmdArgs, "-d", dirArg) + cmd.Args = append(cmd.Args, "-d", dirArg) } else { // Fall back to whole repo if no specific paths given - cmdArgs = append(cmdArgs, "-d", repositoryToAnalyseDirectory) - } - - // Add ruleset - if rulesetFile != "" { - cmdArgs = append(cmdArgs, "-R", rulesetFile) + cmd.Args = append(cmd.Args, "-d", repositoryToAnalyseDirectory) } // Format if outputFormat != "" { - cmdArgs = append(cmdArgs, "-f", outputFormat) + cmd.Args = append(cmd.Args, "-f", outputFormat) } // Output file if outputFile != "" { - cmdArgs = append(cmdArgs, "-r", outputFile) + cmd.Args = append(cmd.Args, "-r", outputFile) } - cmd := exec.Command(pmdBinary, cmdArgs...) - cmd.Dir = repositoryToAnalyseDirectory cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout diff --git a/tools/trivyRunner.go b/tools/trivyRunner.go index 784a65bb..b3636087 100644 --- a/tools/trivyRunner.go +++ b/tools/trivyRunner.go @@ -1,14 +1,20 @@ package tools import ( + "codacy/cli-v2/config" "os" "os/exec" + "path/filepath" ) // RunTrivy executes Trivy vulnerability scanner with the specified options func RunTrivy(repositoryToAnalyseDirectory string, trivyBinary string, pathsToCheck []string, outputFile string, outputFormat string) error { cmd := exec.Command(trivyBinary, "fs") + // Add config file from tools-configs directory + configFile := filepath.Join(config.Config.ToolsConfigDirectory(), "trivy.yaml") + cmd.Args = append(cmd.Args, "--config", configFile) + // Add format options if outputFile != "" { cmd.Args = append(cmd.Args, "--output", outputFile) diff --git a/utils/files.go b/utils/files.go new file mode 100644 index 00000000..a44b9965 --- /dev/null +++ b/utils/files.go @@ -0,0 +1,26 @@ +package utils + +const ( + + // FilePermission represents the default file permission (rw-r--r--) + // This permission gives: + // - read/write (rw-) permissions to the owner + // - read-only (r--) permissions to the group + // - read-only (r--) permissions to others + DefaultRW = 0644 + + // DefaultDirPerms represents the default directory permission (rwxr-xr-x) + // This permission gives: + // - read/write/execute (rwx) permissions to the owner + // - read/execute (r-x) permissions to the group + // - read/execute (r-x) permissions to others + // + // Execute permission on directories is required to: + // - List directory contents (ls) + // - Access files within the directory (cd) + // - Create/delete files in the directory + // Without execute permission, users cannot traverse into or use the directory, + // even if they have read/write permissions on files inside it + + DefaultDirPerms = 0755 // For directories +)