diff --git a/cmd/init.go b/cmd/init.go index c76c8bb7..91957442 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -2,6 +2,7 @@ package cmd import ( "codacy/cli-v2/config" + "codacy/cli-v2/domain" "codacy/cli-v2/tools" "codacy/cli-v2/utils" "encoding/json" @@ -19,10 +20,20 @@ import ( const CodacyApiBase = "https://app.codacy.com" -var codacyRepositoryToken string +type InitFlags struct { + apiToken string + provider string + organization string + repository string +} + +var initFlags InitFlags func init() { - initCmd.Flags().StringVar(&codacyRepositoryToken, "repository-token", "", "optional codacy repository token, if defined configurations will be fetched from codacy") + initCmd.Flags().StringVar(&initFlags.apiToken, "api-token", "", "optional codacy api token, if defined configurations will be fetched from codacy") + initCmd.Flags().StringVar(&initFlags.provider, "provider", "", "optional provider (gh/bb/gl), if defined configurations will be fetched from codacy") + initCmd.Flags().StringVar(&initFlags.organization, "organization", "", "optional remote organization name, if defined configurations will be fetched from codacy") + initCmd.Flags().StringVar(&initFlags.repository, "repository", "", "optional remote repository name, if defined configurations will be fetched from codacy") rootCmd.AddCommand(initCmd) } @@ -31,10 +42,18 @@ var initCmd = &cobra.Command{ Short: "Bootstraps project configuration", Long: "Bootstraps project configuration, creates codacy configuration file", Run: func(cmd *cobra.Command, args []string) { + // Create local codacy directory first + if err := config.Config.CreateLocalCodacyDir(); err != nil { + log.Fatalf("Failed to create local codacy directory: %v", err) + } - config.Config.CreateLocalCodacyDir() + // Create tools-configs directory + toolsConfigDir := config.Config.ToolsConfigDirectory() + if err := os.MkdirAll(toolsConfigDir, 0777); err != nil { + log.Fatalf("Failed to create tools-configs directory: %v", err) + } - cliLocalMode := len(codacyRepositoryToken) == 0 + cliLocalMode := len(initFlags.apiToken) == 0 if cliLocalMode { fmt.Println() @@ -45,15 +64,7 @@ var initCmd = &cobra.Command{ log.Fatal(err) } } else { - apiTools, err := tools.GetTools() - if err != nil { - log.Fatal(err) - } - err = createConfigurationFiles(apiTools, cliLocalMode) - if err != nil { - log.Fatal(err) - } - err = buildRepositoryConfigurationFiles(codacyRepositoryToken) + err := buildRepositoryConfigurationFiles(initFlags.apiToken) if err != nil { log.Fatal(err) } @@ -91,25 +102,27 @@ cli-config.yaml func createConfigurationFiles(tools []tools.Tool, cliLocalMode bool) error { configFile, err := os.Create(config.Config.ProjectConfigFile()) - defer configFile.Close() if err != nil { - log.Fatal(err) + return fmt.Errorf("failed to create project config file: %w", err) } + defer configFile.Close() - _, err = configFile.WriteString(configFileTemplate(tools)) + configContent := configFileTemplate(tools) + _, err = configFile.WriteString(configContent) if err != nil { - log.Fatal(err) + return fmt.Errorf("failed to write project config file: %w", err) } cliConfigFile, err := os.Create(config.Config.CliConfigFile()) - defer cliConfigFile.Close() if err != nil { - log.Fatal(err) + return fmt.Errorf("failed to create CLI config file: %w", err) } + defer cliConfigFile.Close() - _, err = cliConfigFile.WriteString(cliConfigFileTemplate(cliLocalMode)) + cliConfigContent := cliConfigFileTemplate(cliLocalMode) + _, err = cliConfigFile.WriteString(cliConfigContent) if err != nil { - log.Fatal(err) + return fmt.Errorf("failed to write CLI config file: %w", err) } return nil @@ -124,16 +137,14 @@ func configFileTemplate(tools []tools.Tool) string { pmdVersion := "6.55.0" for _, tool := range tools { - if tool.Uuid == "f8b29663-2cb2-498d-b923-a10c6a8c05cd" { + switch tool.Uuid { + case ESLint: eslintVersion = tool.Version - } - if tool.Uuid == "2fd7fbe0-33f9-4ab3-ab73-e9b62404e2cb" { + case Trivy: trivyVersion = tool.Version - } - if tool.Uuid == "31677b6d-4ae0-4f56-8041-606a8d7a8e61" { + case PyLint: pylintVersion = tool.Version - } - if tool.Uuid == "9ed24812-b6ee-4a58-9004-0ed183c45b8f" { + case PMD: pmdVersion = tool.Version } } @@ -162,7 +173,6 @@ func cliConfigFileTemplate(cliLocalMode bool) string { } func buildRepositoryConfigurationFiles(token string) error { - fmt.Println("Building repository configuration files ...") fmt.Println("Fetching repository configuration from codacy ...") toolsConfigDir := config.Config.ToolsConfigDirectory() @@ -172,266 +182,158 @@ func buildRepositoryConfigurationFiles(token string) error { return fmt.Errorf("failed to create tools-configs directory: %w", err) } - // API call to fetch settings - url := CodacyApiBase + "/2.0/project/analysis/configuration" - client := &http.Client{ Timeout: 10 * time.Second, } - // Create a new GET request - req, err := http.NewRequest("GET", url, nil) - if err != nil { - fmt.Println("Error:", err) - return err - } - - // Set the headers - req.Header.Set("project-token", token) - - // Send the request - resp, err := client.Do(req) - if err != nil { - fmt.Println("Error:", err) - return err - } - defer resp.Body.Close() - - if resp.StatusCode >= 400 { - return errors.New("failed to get repository's configuration from Codacy API") - } - - // Read the response body - body, err := io.ReadAll(resp.Body) - if err != nil { - fmt.Println("Error:", err) - return err - } - - var objmap map[string]json.RawMessage - err = json.Unmarshal(body, &objmap) + apiTools, err := tools.GetRepositoryTools(CodacyApiBase, token, initFlags.provider, initFlags.organization, initFlags.repository) if err != nil { - fmt.Println("Error unmarshaling response:", err) return err } - var apiToolConfigurations []CodacyToolConfiguration - err = json.Unmarshal(objmap["toolConfiguration"], &apiToolConfigurations) + err = createConfigurationFiles(apiTools, true) if err != nil { - fmt.Println("Error unmarshaling tool configurations:", err) - return err + log.Fatal(err) } - // ESLint configuration - eslintApiConfiguration := extractESLintConfiguration(apiToolConfigurations) - if eslintApiConfiguration != nil { - eslintDomainConfiguration := convertAPIToolConfigurationToDomain(*eslintApiConfiguration) - eslintConfigurationString := tools.CreateEslintConfig(eslintDomainConfiguration) + for _, tool := range apiTools { + url := fmt.Sprintf("%s/api/v3/analysis/organizations/%s/%s/repositories/%s/tools/%s/patterns?enabled=true", + CodacyApiBase, + initFlags.provider, + initFlags.organization, + initFlags.repository, + tool.Uuid) - eslintConfigFile, err := os.Create(filepath.Join(toolsConfigDir, "eslint.config.mjs")) + // Create a new GET request + req, err := http.NewRequest("GET", url, nil) if err != nil { - return fmt.Errorf("failed to create eslint config file: %v", err) + fmt.Println("Error:", err) + return err } - defer eslintConfigFile.Close() - _, err = eslintConfigFile.WriteString(eslintConfigurationString) - if err != nil { - return fmt.Errorf("failed to write eslint config: %v", err) - } - fmt.Println("ESLint configuration created based on Codacy settings") - } else { - err = createDefaultEslintConfigFile(toolsConfigDir) - if err != nil { - return fmt.Errorf("failed to create default ESLint config: %v", err) - } - fmt.Println("Default ESLint configuration created") - } + // Set the headers + req.Header.Set("api-token", token) - // Trivy configuration - trivyApiConfiguration := extractTrivyConfiguration(apiToolConfigurations) - if trivyApiConfiguration != nil { - 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(toolsConfigDir) + // Send the request + resp, err := client.Do(req) if err != nil { - return fmt.Errorf("failed to create default Trivy config: %v", err) + fmt.Println("Error:", err) + return err } - fmt.Println("Default Trivy configuration created") - } + defer resp.Body.Close() - // PMD configuration - pmdApiConfiguration := extractPMDConfiguration(apiToolConfigurations) - if pmdApiConfiguration != nil { - err = createPMDConfigFile(*pmdApiConfiguration, toolsConfigDir) - if err != nil { - return fmt.Errorf("failed to create PMD config: %v", err) + if resp.StatusCode >= 400 { + return errors.New("failed to get repository's configuration from Codacy API") } - fmt.Println("PMD configuration created based on Codacy settings") - } else { - err = createDefaultPMDConfigFile(toolsConfigDir) + + // Read the response body + body, err := io.ReadAll(resp.Body) if err != nil { - return fmt.Errorf("failed to create default PMD config: %v", err) + fmt.Println("Error:", err) + return err } - fmt.Println("Default PMD configuration created") - } - return nil -} + var objmap map[string]json.RawMessage + err = json.Unmarshal(body, &objmap) -func convertAPIToolConfigurationToDomain(config CodacyToolConfiguration) tools.ToolConfiguration { - var patterns []tools.PatternConfiguration + if err != nil { + fmt.Println("Error unmarshaling response:", err) + return err + } - for _, pattern := range config.Patterns { - var parameters []tools.PatternParameterConfiguration + var apiToolConfigurations []domain.PatternConfiguration + err = json.Unmarshal(objmap["data"], &apiToolConfigurations) - for _, parameter := range pattern.Parameters { - parameters = append(parameters, tools.PatternParameterConfiguration{ - Name: parameter.Name, - Value: parameter.Value, - }) + if err != nil { + fmt.Println("Error unmarshaling tool configurations:", err) + return err } - patterns = append( - patterns, - tools.PatternConfiguration{ - PatternId: pattern.InternalId, - ParameterConfigurations: parameters, - }, - ) + createToolFileConfigurations(tool, apiToolConfigurations) } - return tools.ToolConfiguration{ - PatternsConfiguration: patterns, - } + return nil } -func extractESLintConfiguration(toolConfigurations []CodacyToolConfiguration) *CodacyToolConfiguration { +// map tool uuid to tool name +func createToolFileConfigurations(tool tools.Tool, patternConfiguration []domain.PatternConfiguration) error { + toolsConfigDir := config.Config.ToolsConfigDirectory() + switch tool.Uuid { + case ESLint: + if len(patternConfiguration) > 0 { + eslintConfigurationString := tools.CreateEslintConfig(patternConfiguration) - //ESLInt internal codacy uuid, to filter ot not ESLint tools - //"f8b29663-2cb2-498d-b923-a10c6a8c05cd" + eslintConfigFile, err := os.Create(filepath.Join(toolsConfigDir, "eslint.config.mjs")) + if err != nil { + return fmt.Errorf("failed to create eslint config file: %v", err) + } + defer eslintConfigFile.Close() - for _, toolConfiguration := range toolConfigurations { - if toolConfiguration.Uuid == "f8b29663-2cb2-498d-b923-a10c6a8c05cd" { - return &toolConfiguration + _, err = eslintConfigFile.WriteString(eslintConfigurationString) + if err != nil { + return fmt.Errorf("failed to write eslint config: %v", err) + } + fmt.Println("ESLint configuration created based on Codacy settings") + } else { + err := createDefaultEslintConfigFile(toolsConfigDir) + if err != nil { + return fmt.Errorf("failed to create default ESLint config: %v", err) + } + fmt.Println("Default ESLint configuration created") } - } - - return nil -} - -// extractTrivyConfiguration extracts Trivy configuration from the Codacy API response -func extractTrivyConfiguration(toolConfigurations []CodacyToolConfiguration) *CodacyToolConfiguration { - // Trivy internal codacy uuid - const TrivyUUID = "2fd7fbe0-33f9-4ab3-ab73-e9b62404e2cb" - - for _, toolConfiguration := range toolConfigurations { - if toolConfiguration.Uuid == TrivyUUID { - return &toolConfiguration + case Trivy: + if len(patternConfiguration) > 0 { + err := createTrivyConfigFile(patternConfiguration, toolsConfigDir) + if err != nil { + return fmt.Errorf("failed to create Trivy config: %v", err) + } + } else { + err := createDefaultTrivyConfigFile(toolsConfigDir) + if err != nil { + return fmt.Errorf("failed to create default Trivy config: %v", err) + } } - } - - return nil -} - -// Add PMD-specific functions -func extractPMDConfiguration(toolConfigurations []CodacyToolConfiguration) *CodacyToolConfiguration { - const PMDUUID = "9ed24812-b6ee-4a58-9004-0ed183c45b8f" - for _, toolConfiguration := range toolConfigurations { - if toolConfiguration.Uuid == PMDUUID { - return &toolConfiguration + fmt.Println("Trivy configuration created based on Codacy settings") + case PMD: + if len(patternConfiguration) > 0 { + err := createPMDConfigFile(patternConfiguration, toolsConfigDir) + if err != nil { + return fmt.Errorf("failed to create PMD config: %v", err) + } + } else { + err := createDefaultPMDConfigFile(toolsConfigDir) + if err != nil { + return fmt.Errorf("failed to create default PMD config: %v", err) + } } + fmt.Println("PMD configuration created based on Codacy settings") } return nil } -func createPMDConfigFile(config CodacyToolConfiguration, toolsConfigDir string) error { - pmdDomainConfiguration := convertAPIToolConfigurationToDomain(config) - pmdConfigurationString := tools.CreatePmdConfig(pmdDomainConfiguration) +func createPMDConfigFile(config []domain.PatternConfiguration, toolsConfigDir string) error { + pmdConfigurationString := tools.CreatePmdConfig(config) return os.WriteFile(filepath.Join(toolsConfigDir, "pmd-ruleset.xml"), []byte(pmdConfigurationString), utils.DefaultRW) } func createDefaultPMDConfigFile(toolsConfigDir string) error { - emptyConfig := tools.ToolConfiguration{} - content := tools.CreatePmdConfig(emptyConfig) + content := tools.CreatePmdConfig([]domain.PatternConfiguration{}) return os.WriteFile(filepath.Join(toolsConfigDir, "pmd-ruleset.xml"), []byte(content), utils.DefaultRW) } -type CodacyToolConfiguration struct { - Uuid string `json:"uuid"` - IsEnabled bool `json:"isEnabled"` - Patterns []PatternConfiguration `json:"patterns"` -} - -type PatternConfiguration struct { - InternalId string `json:"internalId"` - Parameters []ParameterConfiguration `json:"parameters"` -} - -type ParameterConfiguration struct { - Name string `json:"name"` - Value string `json:"value"` -} - // createTrivyConfigFile creates a trivy.yaml configuration file based on the API configuration -func createTrivyConfigFile(config CodacyToolConfiguration, toolsConfigDir string) error { - // Convert CodacyToolConfiguration to tools.ToolConfiguration - trivyDomainConfiguration := convertAPIToolConfigurationForTrivy(config) +func createTrivyConfigFile(config []domain.PatternConfiguration, toolsConfigDir string) error { - // Use the shared CreateTrivyConfig function to generate the config content - trivyConfigurationString := tools.CreateTrivyConfig(trivyDomainConfiguration) + trivyConfigurationString := tools.CreateTrivyConfig(config) // Write to file return os.WriteFile(filepath.Join(toolsConfigDir, "trivy.yaml"), []byte(trivyConfigurationString), utils.DefaultRW) } -// convertAPIToolConfigurationForTrivy converts API tool configuration to domain model for Trivy -func convertAPIToolConfigurationForTrivy(config CodacyToolConfiguration) tools.ToolConfiguration { - var patterns []tools.PatternConfiguration - - // Only process if tool is enabled - if config.IsEnabled { - for _, pattern := range config.Patterns { - var parameters []tools.PatternParameterConfiguration - - // By default patterns are enabled - patternEnabled := true - - // Check if there's an explicit enabled parameter - for _, param := range pattern.Parameters { - if param.Name == "enabled" && param.Value == "false" { - patternEnabled = false - } - } - - // Add enabled parameter - parameters = append(parameters, tools.PatternParameterConfiguration{ - Name: "enabled", - Value: fmt.Sprintf("%t", patternEnabled), - }) - - patterns = append( - patterns, - tools.PatternConfiguration{ - PatternId: pattern.InternalId, - ParameterConfigurations: parameters, - }, - ) - } - } - - return tools.ToolConfiguration{ - PatternsConfiguration: patterns, - } -} - // createDefaultTrivyConfigFile creates a default trivy.yaml configuration file func createDefaultTrivyConfigFile(toolsConfigDir string) error { // Use empty tool configuration to get default settings - emptyConfig := tools.ToolConfiguration{} + emptyConfig := []domain.PatternConfiguration{} content := tools.CreateTrivyConfig(emptyConfig) // Write to file @@ -441,9 +343,16 @@ func createDefaultTrivyConfigFile(toolsConfigDir string) error { // createDefaultEslintConfigFile creates a default eslint.config.mjs configuration file func createDefaultEslintConfigFile(toolsConfigDir string) error { // Use empty tool configuration to get default settings - emptyConfig := tools.ToolConfiguration{} + emptyConfig := []domain.PatternConfiguration{} content := tools.CreateEslintConfig(emptyConfig) // Write to file return os.WriteFile(filepath.Join(toolsConfigDir, "eslint.config.mjs"), []byte(content), utils.DefaultRW) } + +const ( + ESLint string = "f8b29663-2cb2-498d-b923-a10c6a8c05cd" + Trivy string = "2fd7fbe0-33f9-4ab3-ab73-e9b62404e2cb" + PMD string = "9ed24812-b6ee-4a58-9004-0ed183c45b8f" + PyLint string = "31677b6d-4ae0-4f56-8041-606a8d7a8e61" +) diff --git a/cmd/root.go b/cmd/root.go index 5ff7a1e2..c2a9d284 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -48,6 +48,7 @@ func showWelcomeMessage() { fmt.Println() cyan.Println("Initialize your project with:") fmt.Println(" codacy-cli init --repository-token YOUR_TOKEN") + fmt.Println(" codacy-cli init --codacy-api-token YOUR_TOKEN") fmt.Println() fmt.Println("Or run without a token to use local configuration:") fmt.Println(" codacy-cli init") diff --git a/domain/patternConfiguration.go b/domain/patternConfiguration.go new file mode 100644 index 00000000..efec1ef1 --- /dev/null +++ b/domain/patternConfiguration.go @@ -0,0 +1,15 @@ +package domain + +type ParameterConfiguration struct { + Name string `json:"name"` + Value string `json:"value"` +} + +type PatternDefinition struct { + Id string `json:"id"` +} + +type PatternConfiguration struct { + PatternDefinition PatternDefinition `json:"patternDefinition"` + Parameters []ParameterConfiguration +} diff --git a/plugins/tool-utils.go b/plugins/tool-utils.go index b2731d62..c96c8b87 100644 --- a/plugins/tool-utils.go +++ b/plugins/tool-utils.go @@ -294,3 +294,34 @@ func getDownloadURL(urlTemplate string, fileName string, version string, mappedA return buf.String() } + +// GetSupportedTools returns a map of supported tool names based on the tools folder +func GetSupportedTools() (map[string]struct{}, error) { + supportedTools := make(map[string]struct{}) + + // Read all directories in the tools folder + entries, err := toolsFS.ReadDir("tools") + if err != nil { + return nil, fmt.Errorf("failed to read tools directory: %w", err) + } + + // For each directory, check if it has a plugin.yaml file + for _, entry := range entries { + if !entry.IsDir() { + continue + } + + toolName := entry.Name() + pluginPath := filepath.Join("tools", toolName, "plugin.yaml") + + // Check if plugin.yaml exists + _, err := toolsFS.ReadFile(pluginPath) + if err != nil { + continue // Skip if no plugin.yaml + } + + supportedTools[toolName] = struct{}{} + } + + return supportedTools, nil +} diff --git a/plugins/tool-utils_test.go b/plugins/tool-utils_test.go index a2e90caf..1b931b22 100644 --- a/plugins/tool-utils_test.go +++ b/plugins/tool-utils_test.go @@ -152,3 +152,45 @@ func TestProcessToolsWithDownload(t *testing.T) { } assert.Contains(t, trivyInfo.DownloadURL, expectedArch) } + +func TestGetSupportedTools(t *testing.T) { + tests := []struct { + name string + expectedTools []string + expectedError bool + }{ + { + name: "should return supported tools", + expectedTools: []string{ + "eslint", + "pmd", + "pylint", + "trivy", + }, + expectedError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + supportedTools, err := GetSupportedTools() + + if tt.expectedError { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.NotNil(t, supportedTools) + + // Check that all expected tools are supported + for _, expectedTool := range tt.expectedTools { + _, exists := supportedTools[expectedTool] + assert.True(t, exists, "tool %s should be supported", expectedTool) + } + + // Check that we have exactly the expected number of tools + assert.Equal(t, len(tt.expectedTools), len(supportedTools), "number of supported tools should match") + }) + } +} diff --git a/tools/eslintConfigCreator.go b/tools/eslintConfigCreator.go index 2bddf973..6b3f472b 100644 --- a/tools/eslintConfigCreator.go +++ b/tools/eslintConfigCreator.go @@ -1,6 +1,7 @@ package tools import ( + "codacy/cli-v2/domain" "encoding/json" "fmt" "strings" @@ -18,14 +19,14 @@ func quoteWhenIsNotJson(value string) string { } } -func CreateEslintConfig(configuration ToolConfiguration) string { +func CreateEslintConfig(configuration []domain.PatternConfiguration) string { result := `export default [ { rules: { ` - for _, patternConfiguration := range configuration.PatternsConfiguration { - rule := strings.TrimPrefix(patternConfiguration.PatternId, "ESLint8_") + for _, patternConfiguration := range configuration { + rule := strings.TrimPrefix(patternConfiguration.PatternDefinition.Id, "ESLint8_") const tempstring = "TEMPORARYSTRING" rule = strings.ReplaceAll(rule, "__", tempstring) @@ -34,7 +35,7 @@ func CreateEslintConfig(configuration ToolConfiguration) string { parametersString := "" - for _, parameter := range patternConfiguration.ParameterConfigurations { + for _, parameter := range patternConfiguration.Parameters { if parameter.Name == "unnamedParam" { parametersString += quoteWhenIsNotJson(parameter.Value) } @@ -42,7 +43,7 @@ func CreateEslintConfig(configuration ToolConfiguration) string { // build named parameters json object namedParametersString := "" - for _, parameter := range patternConfiguration.ParameterConfigurations { + for _, parameter := range patternConfiguration.Parameters { if parameter.Name != "unnamedParam" { if len(namedParametersString) == 0 { diff --git a/tools/eslintConfigCreator_test.go b/tools/eslintConfigCreator_test.go index 809d50ce..d1739b8c 100644 --- a/tools/eslintConfigCreator_test.go +++ b/tools/eslintConfigCreator_test.go @@ -1,19 +1,20 @@ package tools import ( + "codacy/cli-v2/domain" "testing" "github.com/stretchr/testify/assert" ) -func testConfig(t *testing.T, configuration ToolConfiguration, expected string) { +func testConfig(t *testing.T, configuration []domain.PatternConfiguration, expected string) { actual := CreateEslintConfig(configuration) assert.Equal(t, expected, actual) } func TestCreateEslintConfigEmptyConfig(t *testing.T) { testConfig(t, - ToolConfiguration{}, + []domain.PatternConfiguration{}, `export default [ { rules: { @@ -24,10 +25,10 @@ func TestCreateEslintConfigEmptyConfig(t *testing.T) { func TestCreateEslintConfigConfig1(t *testing.T) { testConfig(t, - ToolConfiguration{ - PatternsConfiguration: []PatternConfiguration{ - { - PatternId: "ESLint8_semi", + []domain.PatternConfiguration{ + { + PatternDefinition: domain.PatternDefinition{ + Id: "ESLint8_semi", }, }, }, @@ -42,15 +43,15 @@ func TestCreateEslintConfigConfig1(t *testing.T) { func TestCreateEslintConfigUnnamedParam(t *testing.T) { testConfig(t, - ToolConfiguration{ - PatternsConfiguration: []PatternConfiguration{ - { - PatternId: "ESLint8_semi", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "unnamedParam", - Value: "never", - }, + []domain.PatternConfiguration{ + { + PatternDefinition: domain.PatternDefinition{ + Id: "ESLint8_semi", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "unnamedParam", + Value: "never", }, }, }, @@ -66,15 +67,15 @@ func TestCreateEslintConfigUnnamedParam(t *testing.T) { func TestCreateEslintConfigNamedParam(t *testing.T) { testConfig(t, - ToolConfiguration{ - PatternsConfiguration: []PatternConfiguration{ - { - PatternId: "consistent-return", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "treatUndefinedAsUnspecified", - Value: "false", - }, + []domain.PatternConfiguration{ + { + PatternDefinition: domain.PatternDefinition{ + Id: "consistent-return", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "treatUndefinedAsUnspecified", + Value: "false", }, }, }, @@ -90,19 +91,19 @@ func TestCreateEslintConfigNamedParam(t *testing.T) { func TestCreateEslintConfigUnnamedAndNamedParam(t *testing.T) { testConfig(t, - ToolConfiguration{ - PatternsConfiguration: []PatternConfiguration{ - { - PatternId: "consistent-return", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "treatUndefinedAsUnspecified", - Value: "false", - }, - { - Name: "unnamedParam", - Value: "foo", - }, + []domain.PatternConfiguration{ + { + PatternDefinition: domain.PatternDefinition{ + Id: "consistent-return", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "treatUndefinedAsUnspecified", + Value: "false", + }, + { + Name: "unnamedParam", + Value: "foo", }, }, }, @@ -118,10 +119,10 @@ func TestCreateEslintConfigUnnamedAndNamedParam(t *testing.T) { func TestCreateEslintConfigSupportPlugins(t *testing.T) { testConfig(t, - ToolConfiguration{ - PatternsConfiguration: []PatternConfiguration{ - { - PatternId: "plugin/consistent-return", + []domain.PatternConfiguration{ + { + PatternDefinition: domain.PatternDefinition{ + Id: "plugin/consistent-return", }, }, }, diff --git a/tools/getTools.go b/tools/getTools.go index fc269c8e..5b376fe0 100644 --- a/tools/getTools.go +++ b/tools/getTools.go @@ -1,48 +1,128 @@ package tools import ( + "codacy/cli-v2/plugins" "encoding/json" "errors" "fmt" "io" "net/http" + "strings" "time" ) -func GetTools() ([]Tool, error) { +func enrichToolsWithVersion(tools []Tool) ([]Tool, error) { client := &http.Client{ Timeout: 10 * time.Second, } // Create a new GET request req, err := http.NewRequest("GET", "https://api.codacy.com/api/v3/tools", nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + // Send the request + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to get tools from Codacy API: status code %d", resp.StatusCode) + } + + // Read the response body + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + var response ToolsResponse + if err := json.Unmarshal(body, &response); err != nil { + return nil, fmt.Errorf("failed to unmarshal response: %w", err) + } + + // Create a map of tool UUIDs to versions + versionMap := make(map[string]string) + for _, tool := range response.Data { + versionMap[tool.Uuid] = tool.Version + } + + // Enrich the input tools with versions + for i, tool := range tools { + if version, exists := versionMap[tool.Uuid]; exists { + tools[i].Version = version + } + } + + return tools, nil +} + +func GetRepositoryTools(codacyBase string, apiToken string, provider string, organization string, repository string) ([]Tool, error) { + client := &http.Client{ + Timeout: 10 * time.Second, + } + + url := fmt.Sprintf("%s/api/v3/analysis/organizations/%s/%s/repositories/%s/tools", + codacyBase, + provider, + organization, + repository) + + // Create a new GET request + req, err := http.NewRequest("GET", url, nil) if err != nil { fmt.Println("Error:", err) return nil, err } + // Set the headers + req.Header.Set("api-token", apiToken) + // Send the request resp, err := client.Do(req) if err != nil { fmt.Println("Error:", err) return nil, err } + defer resp.Body.Close() + if resp.StatusCode != 200 { - return nil, errors.New("failed to get tools from Codacy API") + return nil, errors.New("failed to get repository tools from Codacy API") } // Read the response body body, err := io.ReadAll(resp.Body) - defer resp.Body.Close() if err != nil { fmt.Println("Error:", err) return nil, err } var response ToolsResponse - _ = json.Unmarshal(body, &response) + err = json.Unmarshal(body, &response) + if err != nil { + fmt.Println("Error unmarshaling response:", err) + return nil, err + } + + supportedTools, err := plugins.GetSupportedTools() + if err != nil { + return nil, err + } + + // Filter enabled tools + var enabledTools []Tool + for _, tool := range response.Data { + if tool.Settings.Enabled { + if _, exists := supportedTools[strings.ToLower(tool.Name)]; exists { + enabledTools = append(enabledTools, tool) + } + } + } - return response.Data, nil + return enrichToolsWithVersion(enabledTools) } type ToolsResponse struct { @@ -50,7 +130,11 @@ type ToolsResponse struct { } type Tool struct { - Uuid string `json:"uuid"` - Name string `json:"name"` - Version string `json:"version"` + Uuid string `json:"uuid"` + Name string `json:"name"` + Version string `json:"version"` + Settings struct { + Enabled bool `json:"isEnabled"` + UsesConfigFile bool `json:"hasConfigurationFile"` + } `json:"settings"` } diff --git a/tools/getTools_test.go b/tools/getTools_test.go deleted file mode 100644 index a1ba85cc..00000000 --- a/tools/getTools_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package tools - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGetTools(t *testing.T) { - obtained, err := GetTools() - - assert.Nil(t, err) - - assert.Contains(t, obtained, Tool{ - Uuid: "f8b29663-2cb2-498d-b923-a10c6a8c05cd", - Name: "ESLint", - Version: "8.57.0", - }) -} diff --git a/tools/pmdConfigCreator.go b/tools/pmdConfigCreator.go index a32dc8d6..c349a89b 100644 --- a/tools/pmdConfigCreator.go +++ b/tools/pmdConfigCreator.go @@ -1,6 +1,7 @@ package tools import ( + "codacy/cli-v2/domain" _ "embed" "encoding/xml" "fmt" @@ -171,20 +172,20 @@ func ConvertToPMDRuleset(rules []Rule) (string, error) { } // CreatePmdConfig creates a PMD configuration from the provided tool configuration -func CreatePmdConfig(configuration ToolConfiguration) string { +func CreatePmdConfig(configuration []domain.PatternConfiguration) string { // If no patterns provided, return the default ruleset - if len(configuration.PatternsConfiguration) == 0 { + if len(configuration) == 0 { return defaultPMDRuleset } // Convert ToolConfiguration to our Rule format var rules []Rule - for _, pattern := range configuration.PatternsConfiguration { + for _, pattern := range configuration { // Check if pattern is enabled patternEnabled := true var parameters []Parameter - for _, param := range pattern.ParameterConfigurations { + for _, param := range pattern.Parameters { if param.Name == "enabled" && param.Value == "false" { patternEnabled = false break @@ -198,7 +199,7 @@ func CreatePmdConfig(configuration ToolConfiguration) string { } // Apply prefix to pattern ID if needed - patternID := pattern.PatternId + patternID := pattern.PatternDefinition.Id if !strings.HasPrefix(patternID, "PMD_") && !strings.Contains(patternID, "/") { patternID = prefixPatternID(patternID) } diff --git a/tools/pmdConfigCreator_test.go b/tools/pmdConfigCreator_test.go index b53175b0..12004c89 100644 --- a/tools/pmdConfigCreator_test.go +++ b/tools/pmdConfigCreator_test.go @@ -7,6 +7,8 @@ import ( "strings" "testing" + "codacy/cli-v2/domain" + "github.com/stretchr/testify/assert" ) @@ -37,37 +39,41 @@ type PMDProperty struct { func TestCreatePmdConfig(t *testing.T) { // Setup test configuration with patterns - config := ToolConfiguration{ - PatternsConfiguration: []PatternConfiguration{ - { - PatternId: "java/codestyle/AtLeastOneConstructor", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "enabled", - Value: "true", - }, + config := []domain.PatternConfiguration{ + { + PatternDefinition: domain.PatternDefinition{ + Id: "java/codestyle/AtLeastOneConstructor", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "enabled", + Value: "true", }, }, - { - PatternId: "java/design/UnusedPrivateField", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "enabled", - Value: "true", - }, + }, + { + PatternDefinition: domain.PatternDefinition{ + Id: "java/design/UnusedPrivateField", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "enabled", + Value: "true", }, }, - { - PatternId: "java/design/LoosePackageCoupling", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "enabled", - Value: "true", - }, - { - Name: "packages", - Value: "java.util,java.io", - }, + }, + { + PatternDefinition: domain.PatternDefinition{ + Id: "java/design/LoosePackageCoupling", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "enabled", + Value: "true", + }, + { + Name: "packages", + Value: "java.util,java.io", }, }, }, @@ -105,15 +111,15 @@ func TestCreatePmdConfig(t *testing.T) { } func TestCreatePmdConfigWithDisabledRules(t *testing.T) { - config := ToolConfiguration{ - PatternsConfiguration: []PatternConfiguration{ - { - PatternId: "java/codestyle/AtLeastOneConstructor", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "enabled", - Value: "false", - }, + config := []domain.PatternConfiguration{ + { + PatternDefinition: domain.PatternDefinition{ + Id: "java/codestyle/AtLeastOneConstructor", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "enabled", + Value: "false", }, }, }, @@ -163,9 +169,7 @@ func TestCreatePmdConfigEmpty(t *testing.T) { } defer os.Chdir(cwd) - config := ToolConfiguration{ - PatternsConfiguration: []PatternConfiguration{}, - } + config := []domain.PatternConfiguration{} obtainedConfig := CreatePmdConfig(config) diff --git a/tools/trivyConfigCreator.go b/tools/trivyConfigCreator.go index 4d50bb80..36be28ba 100644 --- a/tools/trivyConfigCreator.go +++ b/tools/trivyConfigCreator.go @@ -1,12 +1,13 @@ package tools import ( + "codacy/cli-v2/domain" "fmt" "strings" ) // CreateTrivyConfig generates a Trivy configuration based on the tool configuration -func CreateTrivyConfig(config ToolConfiguration) string { +func CreateTrivyConfig(config []domain.PatternConfiguration) string { // Default settings - include all severities and scanners includeLow := true includeMedium := true @@ -15,28 +16,28 @@ func CreateTrivyConfig(config ToolConfiguration) string { includeSecret := true // Process patterns from Codacy API - for _, pattern := range config.PatternsConfiguration { + for _, pattern := range config { // Check if pattern is enabled patternEnabled := true - for _, param := range pattern.ParameterConfigurations { + for _, param := range pattern.Parameters { if param.Name == "enabled" && param.Value == "false" { patternEnabled = false } } // Map patterns to configurations - if pattern.PatternId == "Trivy_vulnerability_minor" { + if pattern.PatternDefinition.Id == "Trivy_vulnerability_minor" { includeLow = patternEnabled } - if pattern.PatternId == "Trivy_vulnerability_medium" { + if pattern.PatternDefinition.Id == "Trivy_vulnerability_medium" { includeMedium = patternEnabled } - if pattern.PatternId == "Trivy_vulnerability" { + if pattern.PatternDefinition.Id == "Trivy_vulnerability" { // This covers HIGH and CRITICAL includeHigh = patternEnabled includeCritical = patternEnabled } - if pattern.PatternId == "Trivy_secret" { + if pattern.PatternDefinition.Id == "Trivy_secret" { includeSecret = patternEnabled } } diff --git a/tools/trivyConfigCreator_test.go b/tools/trivyConfigCreator_test.go index f238056e..3c8d78d4 100644 --- a/tools/trivyConfigCreator_test.go +++ b/tools/trivyConfigCreator_test.go @@ -1,19 +1,20 @@ package tools import ( + "codacy/cli-v2/domain" "testing" "github.com/stretchr/testify/assert" ) -func testTrivyConfig(t *testing.T, configuration ToolConfiguration, expected string) { +func testTrivyConfig(t *testing.T, configuration []domain.PatternConfiguration, expected string) { actual := CreateTrivyConfig(configuration) assert.Equal(t, expected, actual) } func TestCreateTrivyConfigEmptyConfig(t *testing.T) { testTrivyConfig(t, - ToolConfiguration{}, + []domain.PatternConfiguration{}, `severity: - LOW - MEDIUM @@ -29,42 +30,48 @@ scan: func TestCreateTrivyConfigAllEnabled(t *testing.T) { testTrivyConfig(t, - ToolConfiguration{ - PatternsConfiguration: []PatternConfiguration{ - { - PatternId: "Trivy_vulnerability_minor", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "enabled", - Value: "true", - }, + []domain.PatternConfiguration{ + { + PatternDefinition: domain.PatternDefinition{ + Id: "Trivy_vulnerability_minor", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "enabled", + Value: "true", }, }, - { - PatternId: "Trivy_vulnerability_medium", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "enabled", - Value: "true", - }, + }, + { + PatternDefinition: domain.PatternDefinition{ + Id: "Trivy_vulnerability_medium", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "enabled", + Value: "true", }, }, - { - PatternId: "Trivy_vulnerability", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "enabled", - Value: "true", - }, + }, + { + PatternDefinition: domain.PatternDefinition{ + Id: "Trivy_vulnerability", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "enabled", + Value: "true", }, }, - { - PatternId: "Trivy_secret", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "enabled", - Value: "true", - }, + }, + { + PatternDefinition: domain.PatternDefinition{ + Id: "Trivy_secret", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "enabled", + Value: "true", }, }, }, @@ -84,15 +91,15 @@ scan: func TestCreateTrivyConfigNoLow(t *testing.T) { testTrivyConfig(t, - ToolConfiguration{ - PatternsConfiguration: []PatternConfiguration{ - { - PatternId: "Trivy_vulnerability_minor", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "enabled", - Value: "false", - }, + []domain.PatternConfiguration{ + { + PatternDefinition: domain.PatternDefinition{ + Id: "Trivy_vulnerability_minor", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "enabled", + Value: "false", }, }, }, @@ -111,33 +118,37 @@ scan: func TestCreateTrivyConfigOnlyHigh(t *testing.T) { testTrivyConfig(t, - ToolConfiguration{ - PatternsConfiguration: []PatternConfiguration{ - { - PatternId: "Trivy_vulnerability_minor", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "enabled", - Value: "false", - }, + []domain.PatternConfiguration{ + { + PatternDefinition: domain.PatternDefinition{ + Id: "Trivy_vulnerability_minor", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "enabled", + Value: "false", }, }, - { - PatternId: "Trivy_vulnerability_medium", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "enabled", - Value: "false", - }, + }, + { + PatternDefinition: domain.PatternDefinition{ + Id: "Trivy_vulnerability_medium", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "enabled", + Value: "false", }, }, - { - PatternId: "Trivy_secret", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "enabled", - Value: "false", - }, + }, + { + PatternDefinition: domain.PatternDefinition{ + Id: "Trivy_secret", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "enabled", + Value: "false", }, }, }, @@ -154,33 +165,37 @@ scan: func TestCreateTrivyConfigNoVulnerabilities(t *testing.T) { testTrivyConfig(t, - ToolConfiguration{ - PatternsConfiguration: []PatternConfiguration{ - { - PatternId: "Trivy_vulnerability_minor", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "enabled", - Value: "false", - }, + []domain.PatternConfiguration{ + { + PatternDefinition: domain.PatternDefinition{ + Id: "Trivy_vulnerability_minor", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "enabled", + Value: "false", }, }, - { - PatternId: "Trivy_vulnerability_medium", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "enabled", - Value: "false", - }, + }, + { + PatternDefinition: domain.PatternDefinition{ + Id: "Trivy_vulnerability_medium", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "enabled", + Value: "false", }, }, - { - PatternId: "Trivy_vulnerability", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "enabled", - Value: "false", - }, + }, + { + PatternDefinition: domain.PatternDefinition{ + Id: "Trivy_vulnerability", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "enabled", + Value: "false", }, }, }, @@ -196,33 +211,37 @@ scan: func TestCreateTrivyConfigOnlySecretsLow(t *testing.T) { testTrivyConfig(t, - ToolConfiguration{ - PatternsConfiguration: []PatternConfiguration{ - { - PatternId: "Trivy_vulnerability_minor", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "enabled", - Value: "true", - }, + []domain.PatternConfiguration{ + { + PatternDefinition: domain.PatternDefinition{ + Id: "Trivy_vulnerability_minor", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "enabled", + Value: "true", }, }, - { - PatternId: "Trivy_vulnerability_medium", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "enabled", - Value: "false", - }, + }, + { + PatternDefinition: domain.PatternDefinition{ + Id: "Trivy_vulnerability_medium", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "enabled", + Value: "false", }, }, - { - PatternId: "Trivy_vulnerability", - ParameterConfigurations: []PatternParameterConfiguration{ - { - Name: "enabled", - Value: "false", - }, + }, + { + PatternDefinition: domain.PatternDefinition{ + Id: "Trivy_vulnerability", + }, + Parameters: []domain.ParameterConfiguration{ + { + Name: "enabled", + Value: "false", }, }, },