Skip to content

Commit 783b122

Browse files
fetch codacy default values for all tools, refactor to use a new codacy-client package where api requests should be handled
1 parent 068ea50 commit 783b122

File tree

5 files changed

+222
-29
lines changed

5 files changed

+222
-29
lines changed

cmd/init.go

Lines changed: 76 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmd
22

33
import (
4+
codacyclient "codacy/cli-v2/codacy-client"
45
"codacy/cli-v2/config"
56
"codacy/cli-v2/domain"
67
"codacy/cli-v2/tools"
@@ -23,20 +24,13 @@ import (
2324

2425
const CodacyApiBase = "https://app.codacy.com"
2526

26-
type InitFlags struct {
27-
apiToken string
28-
provider string
29-
organization string
30-
repository string
31-
}
32-
33-
var initFlags InitFlags
27+
var initFlags domain.InitFlags
3428

3529
func init() {
36-
initCmd.Flags().StringVar(&initFlags.apiToken, "api-token", "", "optional codacy api token, if defined configurations will be fetched from codacy")
37-
initCmd.Flags().StringVar(&initFlags.provider, "provider", "", "provider (gh/bb/gl) to fetch configurations from codacy, required when api-token is provided")
38-
initCmd.Flags().StringVar(&initFlags.organization, "organization", "", "remote organization name to fetch configurations from codacy, required when api-token is provided")
39-
initCmd.Flags().StringVar(&initFlags.repository, "repository", "", "remote repository name to fetch configurations from codacy, required when api-token is provided")
30+
initCmd.Flags().StringVar(&initFlags.ApiToken, "api-token", "", "optional codacy api token, if defined configurations will be fetched from codacy")
31+
initCmd.Flags().StringVar(&initFlags.Provider, "provider", "", "provider (gh/bb/gl) to fetch configurations from codacy, required when api-token is provided")
32+
initCmd.Flags().StringVar(&initFlags.Organization, "organization", "", "remote organization name to fetch configurations from codacy, required when api-token is provided")
33+
initCmd.Flags().StringVar(&initFlags.Repository, "repository", "", "remote repository name to fetch configurations from codacy, required when api-token is provided")
4034
rootCmd.AddCommand(initCmd)
4135
}
4236

@@ -56,7 +50,7 @@ var initCmd = &cobra.Command{
5650
log.Fatalf("Failed to create tools-configs directory: %v", err)
5751
}
5852

59-
cliLocalMode := len(initFlags.apiToken) == 0
53+
cliLocalMode := len(initFlags.ApiToken) == 0
6054

6155
if cliLocalMode {
6256
fmt.Println()
@@ -71,7 +65,7 @@ var initCmd = &cobra.Command{
7165
log.Fatal(err)
7266
}
7367
} else {
74-
err := buildRepositoryConfigurationFiles(initFlags.apiToken)
68+
err := buildRepositoryConfigurationFiles(initFlags.ApiToken)
7569
if err != nil {
7670
log.Fatal(err)
7771
}
@@ -263,7 +257,7 @@ func buildRepositoryConfigurationFiles(token string) error {
263257
Timeout: 10 * time.Second,
264258
}
265259

266-
apiTools, err := tools.GetRepositoryTools(CodacyApiBase, token, initFlags.provider, initFlags.organization, initFlags.repository)
260+
apiTools, err := tools.GetRepositoryTools(CodacyApiBase, token, initFlags.Provider, initFlags.Organization, initFlags.Repository)
267261
if err != nil {
268262
return err
269263
}
@@ -278,7 +272,7 @@ func buildRepositoryConfigurationFiles(token string) error {
278272
}
279273

280274
// Generate languages configuration based on API tools response
281-
if err := tools.CreateLanguagesConfigFile(apiTools, toolsConfigDir, uuidToName, token, initFlags.provider, initFlags.organization, initFlags.repository); err != nil {
275+
if err := tools.CreateLanguagesConfigFile(apiTools, toolsConfigDir, uuidToName, token, initFlags.Provider, initFlags.Organization, initFlags.Repository); err != nil {
282276
return fmt.Errorf("failed to create languages configuration file: %w", err)
283277
}
284278

@@ -296,9 +290,9 @@ func buildRepositoryConfigurationFiles(token string) error {
296290

297291
url := fmt.Sprintf("%s/api/v3/analysis/organizations/%s/%s/repositories/%s/tools/%s/patterns?enabled=true&limit=1000",
298292
CodacyApiBase,
299-
initFlags.provider,
300-
initFlags.organization,
301-
initFlags.repository,
293+
initFlags.Provider,
294+
initFlags.Organization,
295+
initFlags.Repository,
302296
tool.Uuid)
303297

304298
// Create a new GET request
@@ -485,6 +479,7 @@ func createDefaultTrivyConfigFile(toolsConfigDir string) error {
485479
// createDefaultEslintConfigFile creates a default eslint.config.mjs configuration file
486480
func createDefaultEslintConfigFile(toolsConfigDir string) error {
487481
// Use empty tool configuration to get default settings
482+
//
488483
emptyConfig := []domain.PatternConfiguration{}
489484
content := tools.CreateEslintConfig(emptyConfig)
490485

@@ -566,16 +561,59 @@ func createLizardConfigFile(toolsConfigDir string, patternConfiguration []domain
566561

567562
// buildDefaultConfigurationFiles creates default configuration files for all tools
568563
func buildDefaultConfigurationFiles(toolsConfigDir string) error {
569-
// Create default Lizard configuration
570-
if err := createLizardConfigFile(toolsConfigDir, []domain.PatternConfiguration{}); err != nil {
571-
return fmt.Errorf("failed to create default Lizard configuration: %w", err)
572-
}
573564

574-
// Add other default tool configurations here as needed
575-
// For example:
576-
// if err := createDefaultEslintConfigFile(toolsConfigDir); err != nil {
577-
// return fmt.Errorf("failed to create default ESLint configuration: %w", err)
578-
// }
565+
for _, tool := range AvailableTools {
566+
patternsConfig, err := codacyclient.GetDefaultToolPatternsConfig(initFlags, tool)
567+
if err != nil {
568+
return fmt.Errorf("failed to get default tool patterns config: %w", err)
569+
}
570+
switch tool {
571+
case ESLint:
572+
eslintConfigurationString := tools.CreateEslintConfig(patternsConfig)
573+
574+
eslintConfigFile, err := os.Create(filepath.Join(toolsConfigDir, "eslint.config.mjs"))
575+
if err != nil {
576+
return fmt.Errorf("failed to create eslint config file: %v", err)
577+
}
578+
defer eslintConfigFile.Close()
579+
580+
_, err = eslintConfigFile.WriteString(eslintConfigurationString)
581+
if err != nil {
582+
return fmt.Errorf("failed to write eslint config: %v", err)
583+
}
584+
fmt.Println("ESLint configuration created. Ignoring plugin rules. ESLint plugins are not supported yet.")
585+
case Trivy:
586+
if err := createTrivyConfigFile(patternsConfig, toolsConfigDir); err != nil {
587+
return fmt.Errorf("failed to create default Trivy configuration: %w", err)
588+
}
589+
fmt.Println("Trivy configuration created")
590+
case PMD:
591+
if err := createPMDConfigFile(patternsConfig, toolsConfigDir); err != nil {
592+
return fmt.Errorf("failed to create default PMD configuration: %w", err)
593+
}
594+
fmt.Println("PMD configuration created")
595+
case PyLint:
596+
if err := createPylintConfigFile(patternsConfig, toolsConfigDir); err != nil {
597+
return fmt.Errorf("failed to create default Pylint configuration: %w", err)
598+
}
599+
fmt.Println("Pylint configuration created")
600+
case DartAnalyzer:
601+
if err := createDartAnalyzerConfigFile(patternsConfig, toolsConfigDir); err != nil {
602+
return fmt.Errorf("failed to create default Dart Analyzer configuration: %w", err)
603+
}
604+
fmt.Println("Dart Analyzer configuration created")
605+
case Semgrep:
606+
if err := createSemgrepConfigFile(patternsConfig, toolsConfigDir); err != nil {
607+
return fmt.Errorf("failed to create default Semgrep configuration: %w", err)
608+
}
609+
fmt.Println("Semgrep configuration created")
610+
case Lizard:
611+
if err := createLizardConfigFile(toolsConfigDir, patternsConfig); err != nil {
612+
return fmt.Errorf("failed to create default Lizard configuration: %w", err)
613+
}
614+
fmt.Println("Lizard configuration created")
615+
}
616+
}
579617

580618
return nil
581619
}
@@ -589,3 +627,13 @@ const (
589627
Semgrep string = "6792c561-236d-41b7-ba5e-9d6bee0d548b"
590628
Lizard string = "76348462-84b3-409a-90d3-955e90abfb87"
591629
)
630+
631+
var AvailableTools = []string{
632+
ESLint,
633+
Trivy,
634+
PMD,
635+
PyLint,
636+
DartAnalyzer,
637+
Semgrep,
638+
Lizard,
639+
}

codacy-client/client.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package codacyclient
2+
3+
import (
4+
"codacy/cli-v2/domain"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"net/http"
10+
"time"
11+
)
12+
13+
const timeout = 10 * time.Second
14+
const CodacyApiBase = "https://app.codacy.com"
15+
16+
func getRequest(url string, initFlags domain.InitFlags) ([]byte, error) {
17+
client := &http.Client{
18+
Timeout: timeout,
19+
}
20+
21+
req, err := http.NewRequest("GET", url, nil)
22+
if err != nil {
23+
return nil, err
24+
}
25+
26+
req.Header.Set("api-token", initFlags.ApiToken)
27+
28+
resp, err := client.Do(req)
29+
30+
if err != nil {
31+
fmt.Printf("Error sending request: %v\n", err)
32+
return nil, err
33+
}
34+
35+
defer resp.Body.Close()
36+
37+
if resp.StatusCode != 200 {
38+
fmt.Println("Error:", url)
39+
return nil, errors.New("Failed in request to " + url)
40+
}
41+
42+
// Read the response body
43+
body, err := io.ReadAll(resp.Body)
44+
if err != nil {
45+
return nil, fmt.Errorf("failed to read response body: %w", err)
46+
}
47+
48+
return body, nil
49+
}
50+
51+
// handlePaginationGeneric fetches all paginated results of type T using a provided fetchPage function.
52+
// - baseURL: the base URL for the paginated API
53+
// - initialCursor: the initial cursor (empty string for first page)
54+
// - fetchPage: a function that fetches a page and returns ([]T, nextCursor, error)
55+
//
56+
// Returns a slice of all results of type T and any error encountered.
57+
func handlePaginationGeneric[T any](
58+
baseURL string,
59+
initialCursor string,
60+
fetchPage func(url string) ([]T, string, error),
61+
) ([]T, error) {
62+
var allResults []T
63+
cursor := initialCursor
64+
firstRequest := true
65+
66+
for {
67+
pageURL := baseURL
68+
if !firstRequest && cursor != "" {
69+
if pageURL[len(pageURL)-1] == '?' || pageURL[len(pageURL)-1] == '&' {
70+
pageURL += fmt.Sprintf("cursor=%s", cursor)
71+
} else {
72+
pageURL += fmt.Sprintf("&cursor=%s", cursor)
73+
}
74+
}
75+
firstRequest = false
76+
77+
results, nextCursor, err := fetchPage(pageURL)
78+
if err != nil {
79+
return nil, err
80+
}
81+
allResults = append(allResults, results...)
82+
83+
if nextCursor == "" {
84+
break
85+
}
86+
cursor = nextCursor
87+
}
88+
89+
return allResults, nil
90+
}
91+
92+
func GetDefaultToolPatternsConfig(initFlags domain.InitFlags, toolUUID string) ([]domain.PatternConfiguration, error) {
93+
baseURL := fmt.Sprintf("%s/api/v3/tools/%s/patterns?enabled=true", CodacyApiBase, toolUUID)
94+
95+
fetchPage := func(url string) ([]domain.PatternConfiguration, string, error) {
96+
response, err := getRequest(url, initFlags)
97+
if err != nil {
98+
return nil, "", fmt.Errorf("failed to get patterns page: %w", err)
99+
}
100+
101+
var objmap map[string]json.RawMessage
102+
if err := json.Unmarshal(response, &objmap); err != nil {
103+
return nil, "", fmt.Errorf("failed to unmarshal response: %w", err)
104+
}
105+
106+
var patterns []domain.PatternDefinition
107+
if err := json.Unmarshal(objmap["data"], &patterns); err != nil {
108+
return nil, "", fmt.Errorf("failed to unmarshal patterns: %w", err)
109+
}
110+
111+
patternConfigurations := make([]domain.PatternConfiguration, len(patterns))
112+
for i, pattern := range patterns {
113+
patternConfigurations[i] = domain.PatternConfiguration{
114+
PatternDefinition: pattern,
115+
Parameters: pattern.Parameters,
116+
Enabled: pattern.Enabled,
117+
}
118+
}
119+
120+
var pagination domain.Pagination
121+
if objmap["pagination"] != nil {
122+
if err := json.Unmarshal(objmap["pagination"], &pagination); err != nil {
123+
return nil, "", fmt.Errorf("failed to unmarshal pagination: %w", err)
124+
}
125+
}
126+
127+
return patternConfigurations, pagination.Cursor, nil
128+
}
129+
130+
return handlePaginationGeneric(baseURL, "", fetchPage)
131+
}

domain/initFlags.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package domain
2+
3+
type InitFlags struct {
4+
ApiToken string
5+
Provider string
6+
Organization string
7+
Repository string
8+
}

domain/pagination.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package domain
2+
3+
type Pagination struct {
4+
Cursor string `json:"cursor"`
5+
Limit int `json:"limit"`
6+
Total int `json:"total"`
7+
}

domain/patternConfiguration.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,4 @@ type PatternConfiguration struct {
2424
PatternDefinition PatternDefinition `json:"patternDefinition"`
2525
Parameters []ParameterConfiguration
2626
Enabled bool `json:"enabled"`
27-
IsCustom bool `json:"isCustom"`
2827
}

0 commit comments

Comments
 (0)