Skip to content

Commit b751631

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 237e427 commit b751631

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
}
@@ -265,7 +259,7 @@ func buildRepositoryConfigurationFiles(token string) error {
265259
Timeout: 10 * time.Second,
266260
}
267261

268-
apiTools, err := tools.GetRepositoryTools(CodacyApiBase, token, initFlags.provider, initFlags.organization, initFlags.repository)
262+
apiTools, err := tools.GetRepositoryTools(CodacyApiBase, token, initFlags.Provider, initFlags.Organization, initFlags.Repository)
269263
if err != nil {
270264
return err
271265
}
@@ -282,7 +276,7 @@ func buildRepositoryConfigurationFiles(token string) error {
282276
}
283277

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

@@ -300,9 +294,9 @@ func buildRepositoryConfigurationFiles(token string) error {
300294

301295
url := fmt.Sprintf("%s/api/v3/analysis/organizations/%s/%s/repositories/%s/tools/%s/patterns?enabled=true&limit=1000",
302296
CodacyApiBase,
303-
initFlags.provider,
304-
initFlags.organization,
305-
initFlags.repository,
297+
initFlags.Provider,
298+
initFlags.Organization,
299+
initFlags.Repository,
306300
tool.Uuid)
307301

308302
// Create a new GET request
@@ -493,6 +487,7 @@ func createDefaultTrivyConfigFile(toolsConfigDir string) error {
493487
// createDefaultEslintConfigFile creates a default eslint.config.mjs configuration file
494488
func createDefaultEslintConfigFile(toolsConfigDir string) error {
495489
// Use empty tool configuration to get default settings
490+
//
496491
emptyConfig := []domain.PatternConfiguration{}
497492
content := tools.CreateEslintConfig(emptyConfig)
498493

@@ -573,16 +568,59 @@ func createLizardConfigFile(toolsConfigDir string, patternConfiguration []domain
573568

574569
// buildDefaultConfigurationFiles creates default configuration files for all tools
575570
func buildDefaultConfigurationFiles(toolsConfigDir string) error {
576-
// Create default Lizard configuration
577-
if err := createLizardConfigFile(toolsConfigDir, []domain.PatternConfiguration{}); err != nil {
578-
return fmt.Errorf("failed to create default Lizard configuration: %w", err)
579-
}
580571

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

587625
return nil
588626
}
@@ -596,3 +634,13 @@ const (
596634
Semgrep string = "6792c561-236d-41b7-ba5e-9d6bee0d548b"
597635
Lizard string = "76348462-84b3-409a-90d3-955e90abfb87"
598636
)
637+
638+
var AvailableTools = []string{
639+
ESLint,
640+
Trivy,
641+
PMD,
642+
PyLint,
643+
DartAnalyzer,
644+
Semgrep,
645+
Lizard,
646+
}

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)