@@ -18,6 +18,7 @@ import (
1818 "time"
1919
2020 "github.com/spf13/cobra"
21+ "gopkg.in/yaml.v3"
2122)
2223
2324const CodacyApiBase = "https://app.codacy.com"
@@ -141,10 +142,11 @@ func configFileTemplate(tools []tools.Tool) string {
141142
142143 // Default versions
143144 defaultVersions := map [string ]string {
144- ESLint : "9.3.0" ,
145- Trivy : "0.59.1" ,
146- PyLint : "3.3.6" ,
147- PMD : "6.55.0" ,
145+ ESLint : "9.3.0" ,
146+ Trivy : "0.59.1" ,
147+ PyLint : "3.3.6" ,
148+ PMD : "6.55.0" ,
149+ Semgrep : "1.78.0" ,
148150 }
149151
150152 // Build map of enabled tools with their versions
@@ -188,10 +190,11 @@ func configFileTemplate(tools []tools.Tool) string {
188190 if len (tools ) > 0 {
189191 // Add only the tools that are in the API response (enabled tools)
190192 uuidToName := map [string ]string {
191- ESLint : "eslint" ,
192- Trivy : "trivy" ,
193- PyLint : "pylint" ,
194- PMD : "pmd" ,
193+ ESLint : "eslint" ,
194+ Trivy : "trivy" ,
195+ PyLint : "pylint" ,
196+ PMD : "pmd" ,
197+ Semgrep : "semgrep" ,
195198 }
196199
197200 for uuid , name := range uuidToName {
@@ -205,6 +208,7 @@ func configFileTemplate(tools []tools.Tool) string {
205208 sb .WriteString (fmt .Sprintf (" - trivy@%s\n " , defaultVersions [Trivy ]))
206209 sb .WriteString (fmt .Sprintf (" - pylint@%s\n " , defaultVersions [PyLint ]))
207210 sb .WriteString (fmt .Sprintf (" - pmd@%s\n " , defaultVersions [PMD ]))
211+ sb .WriteString (fmt .Sprintf (" - semgrep@%s\n " , defaultVersions [Semgrep ]))
208212 }
209213
210214 return sb .String ()
@@ -257,7 +261,8 @@ func buildRepositoryConfigurationFiles(token string) error {
257261
258262 // Only generate config files for tools not using their own config file
259263 for _ , tool := range configuredToolsWithUI {
260- url := fmt .Sprintf ("%s/api/v3/analysis/organizations/%s/%s/repositories/%s/tools/%s/patterns?enabled=true" ,
264+
265+ url := fmt .Sprintf ("%s/api/v3/analysis/organizations/%s/%s/repositories/%s/tools/%s/patterns?enabled=true&limit=1000" ,
261266 CodacyApiBase ,
262267 initFlags .provider ,
263268 initFlags .organization ,
@@ -380,6 +385,19 @@ func createToolFileConfigurations(tool tools.Tool, patternConfiguration []domain
380385 }
381386 }
382387 fmt .Println ("Pylint configuration created based on Codacy settings" )
388+ case Semgrep :
389+ if len (patternConfiguration ) > 0 {
390+ err := createSemgrepConfigFile (patternConfiguration , toolsConfigDir )
391+ if err != nil {
392+ return fmt .Errorf ("failed to create Semgrep config: %v" , err )
393+ }
394+ } else {
395+ err := createDefaultSemgrepConfigFile (toolsConfigDir )
396+ if err != nil {
397+ return fmt .Errorf ("failed to create default Semgrep config: %v" , err )
398+ }
399+ }
400+ fmt .Println ("Semgrep configuration created based on Codacy settings" )
383401 }
384402 return nil
385403}
@@ -434,6 +452,128 @@ func createDefaultEslintConfigFile(toolsConfigDir string) error {
434452 return os .WriteFile (filepath .Join (toolsConfigDir , "eslint.config.mjs" ), []byte (content ), utils .DefaultFilePerms )
435453}
436454
455+ // SemgrepRulesFile represents the structure of the rules.yaml file
456+ type SemgrepRulesFile struct {
457+ Rules []map [string ]interface {} `yaml:"rules"`
458+ }
459+
460+ // createSemgrepConfigFile creates a semgrep.yaml configuration file based on the API configuration
461+ func createSemgrepConfigFile (config []domain.PatternConfiguration , toolsConfigDir string ) error {
462+ // When specific patterns are configured, filter rules from rules.yaml
463+ if len (config ) > 0 {
464+ // First try to read the rules.yaml file
465+ rulesFile := filepath .Join ("plugins" , "tools" , "semgrep" , "rules.yaml" )
466+ if _ , err := os .Stat (rulesFile ); err == nil {
467+ // Read and parse the rules.yaml file
468+ data , err := os .ReadFile (rulesFile )
469+ if err != nil {
470+ fmt .Printf ("Warning: Failed to read rules.yaml: %v\n " , err )
471+ // Fall back to the old method
472+ semgrepConfigurationString := tools .CreateSemgrepConfig (config )
473+ return os .WriteFile (filepath .Join (toolsConfigDir , "semgrep.yaml" ), []byte (semgrepConfigurationString ), utils .DefaultFilePerms )
474+ }
475+
476+ // Parse the YAML file just enough to get the rules array
477+ var allRules SemgrepRulesFile
478+ if err := yaml .Unmarshal (data , & allRules ); err != nil {
479+ fmt .Printf ("Warning: Failed to parse rules.yaml: %v\n " , err )
480+ // Fall back to the old method
481+ semgrepConfigurationString := tools .CreateSemgrepConfig (config )
482+ return os .WriteFile (filepath .Join (toolsConfigDir , "semgrep.yaml" ), []byte (semgrepConfigurationString ), utils .DefaultFilePerms )
483+ }
484+
485+ // Create a map of enabled pattern IDs for faster lookup
486+ enabledPatterns := make (map [string ]bool )
487+ for _ , pattern := range config {
488+ if pattern .Enabled && pattern .PatternDefinition .Enabled {
489+ // Extract rule ID from pattern ID
490+ parts := strings .SplitN (pattern .PatternDefinition .Id , "_" , 2 )
491+ if len (parts ) == 2 {
492+ ruleID := parts [1 ]
493+ enabledPatterns [ruleID ] = true
494+ }
495+ }
496+ }
497+
498+ // Filter the rules based on enabled patterns
499+ var filteredRules SemgrepRulesFile
500+ filteredRules .Rules = []map [string ]interface {}{}
501+
502+ for _ , rule := range allRules .Rules {
503+ // Get the rule ID
504+ if ruleID , ok := rule ["id" ].(string ); ok && enabledPatterns [ruleID ] {
505+ // If this rule is enabled, include it
506+ filteredRules .Rules = append (filteredRules .Rules , rule )
507+ }
508+ }
509+
510+ // If no rules match, use the old method
511+ if len (filteredRules .Rules ) == 0 {
512+ fmt .Println ("Warning: No matching rules found in rules.yaml" )
513+ semgrepConfigurationString := tools .CreateSemgrepConfig (config )
514+ return os .WriteFile (filepath .Join (toolsConfigDir , "semgrep.yaml" ), []byte (semgrepConfigurationString ), utils .DefaultFilePerms )
515+ }
516+
517+ // Marshal the filtered rules back to YAML
518+ filteredData , err := yaml .Marshal (filteredRules )
519+ if err != nil {
520+ fmt .Printf ("Warning: Failed to marshal filtered rules: %v\n " , err )
521+ // Fall back to the old method
522+ semgrepConfigurationString := tools .CreateSemgrepConfig (config )
523+ return os .WriteFile (filepath .Join (toolsConfigDir , "semgrep.yaml" ), []byte (semgrepConfigurationString ), utils .DefaultFilePerms )
524+ }
525+
526+ // Write the filtered rules to semgrep.yaml
527+ return os .WriteFile (filepath .Join (toolsConfigDir , "semgrep.yaml" ), filteredData , utils .DefaultFilePerms )
528+ }
529+
530+ // If rules.yaml doesn't exist, fall back to the old method
531+ semgrepConfigurationString := tools .CreateSemgrepConfig (config )
532+ return os .WriteFile (filepath .Join (toolsConfigDir , "semgrep.yaml" ), []byte (semgrepConfigurationString ), utils .DefaultFilePerms )
533+ }
534+
535+ // For default case with no specific patterns, use the entire rules.yaml
536+ rulesFile := filepath .Join ("plugins" , "tools" , "semgrep" , "rules.yaml" )
537+ if _ , err := os .Stat (rulesFile ); err == nil {
538+ data , err := os .ReadFile (rulesFile )
539+ if err != nil {
540+ fmt .Printf ("Warning: Failed to read rules.yaml: %v\n " , err )
541+ // Fall back to the old method for default config
542+ emptyConfig := []domain.PatternConfiguration {}
543+ content := tools .CreateSemgrepConfig (emptyConfig )
544+ return os .WriteFile (filepath .Join (toolsConfigDir , "semgrep.yaml" ), []byte (content ), utils .DefaultFilePerms )
545+ }
546+ return os .WriteFile (filepath .Join (toolsConfigDir , "semgrep.yaml" ), data , utils .DefaultFilePerms )
547+ }
548+
549+ // Fall back to default config
550+ emptyConfig := []domain.PatternConfiguration {}
551+ content := tools .CreateSemgrepConfig (emptyConfig )
552+ return os .WriteFile (filepath .Join (toolsConfigDir , "semgrep.yaml" ), []byte (content ), utils .DefaultFilePerms )
553+ }
554+
555+ // createDefaultSemgrepConfigFile creates a default semgrep.yaml configuration file
556+ func createDefaultSemgrepConfigFile (toolsConfigDir string ) error {
557+ // Use rules.yaml as the default
558+ rulesFile := filepath .Join ("plugins" , "tools" , "semgrep" , "rules.yaml" )
559+ if _ , err := os .Stat (rulesFile ); err == nil {
560+ data , err := os .ReadFile (rulesFile )
561+ if err != nil {
562+ fmt .Printf ("Warning: Failed to read rules.yaml: %v\n " , err )
563+ // Fall back to the old method
564+ emptyConfig := []domain.PatternConfiguration {}
565+ content := tools .CreateSemgrepConfig (emptyConfig )
566+ return os .WriteFile (filepath .Join (toolsConfigDir , "semgrep.yaml" ), []byte (content ), utils .DefaultFilePerms )
567+ }
568+ return os .WriteFile (filepath .Join (toolsConfigDir , "semgrep.yaml" ), data , utils .DefaultFilePerms )
569+ }
570+
571+ // Fall back to the old method if rules.yaml doesn't exist
572+ emptyConfig := []domain.PatternConfiguration {}
573+ content := tools .CreateSemgrepConfig (emptyConfig )
574+ return os .WriteFile (filepath .Join (toolsConfigDir , "semgrep.yaml" ), []byte (content ), utils .DefaultFilePerms )
575+ }
576+
437577// cleanConfigDirectory removes all previous configuration files in the tools-configs directory
438578func cleanConfigDirectory (toolsConfigDir string ) error {
439579 // Check if directory exists
@@ -462,8 +602,9 @@ func cleanConfigDirectory(toolsConfigDir string) error {
462602}
463603
464604const (
465- ESLint string = "f8b29663-2cb2-498d-b923-a10c6a8c05cd"
466- Trivy string = "2fd7fbe0-33f9-4ab3-ab73-e9b62404e2cb"
467- PMD string = "9ed24812-b6ee-4a58-9004-0ed183c45b8f"
468- PyLint string = "31677b6d-4ae0-4f56-8041-606a8d7a8e61"
605+ ESLint string = "f8b29663-2cb2-498d-b923-a10c6a8c05cd"
606+ Trivy string = "2fd7fbe0-33f9-4ab3-ab73-e9b62404e2cb"
607+ PMD string = "9ed24812-b6ee-4a58-9004-0ed183c45b8f"
608+ PyLint string = "31677b6d-4ae0-4f56-8041-606a8d7a8e61"
609+ Semgrep string = "6792c561-236d-41b7-ba5e-9d6bee0d548b"
469610)
0 commit comments