Skip to content

Commit c7a56ea

Browse files
fix: semgrep local init (#129)
This pull request fixes the handling of Semgrep rules by embedding the rules.yaml file directly into the binary, simplifying file access and improving test reliability. The most important changes include introducing an embedded file system for rules.yaml, updating the configuration logic to use the embedded file, and refactoring the tests accordingly.
1 parent a84eb7c commit c7a56ea

File tree

4 files changed

+50
-161
lines changed

4 files changed

+50
-161
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Package embedded contains embedded files used by the tools package
2+
package embedded
3+
4+
import "embed"
5+
6+
//go:embed rules.yaml
7+
var rulesFS embed.FS
8+
9+
// GetSemgrepRules returns the embedded Semgrep rules
10+
func GetSemgrepRules() []byte {
11+
data, err := rulesFS.ReadFile("rules.yaml")
12+
if err != nil {
13+
panic(err) // This should never happen as the file is embedded
14+
}
15+
return data
16+
}

tools/semgrepConfigCreator.go

Lines changed: 14 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ package tools
22

33
import (
44
"codacy/cli-v2/domain"
5+
"codacy/cli-v2/plugins/tools/semgrep/embedded"
56
"fmt"
67
"os"
7-
"path/filepath"
88
"strings"
99

1010
"gopkg.in/yaml.v3"
@@ -20,19 +20,18 @@ type semgrepRulesFile struct {
2020
var getExecutablePath = os.Executable
2121

2222
// FilterRulesFromFile extracts enabled rules from a rules.yaml file based on configuration
23-
func FilterRulesFromFile(rulesFilePath string, config []domain.PatternConfiguration) ([]byte, error) {
24-
// Read the rules.yaml file
25-
data, err := os.ReadFile(rulesFilePath)
26-
if err != nil {
27-
return nil, fmt.Errorf("failed to read rules file: %w", err)
28-
}
29-
30-
// Parse the YAML file
23+
func FilterRulesFromFile(rulesData []byte, config []domain.PatternConfiguration) ([]byte, error) {
24+
// Parse the YAML data
3125
var allRules semgrepRulesFile
32-
if err := yaml.Unmarshal(data, &allRules); err != nil {
26+
if err := yaml.Unmarshal(rulesData, &allRules); err != nil {
3327
return nil, fmt.Errorf("failed to parse rules file: %w", err)
3428
}
3529

30+
// If no configuration provided, return all rules
31+
if len(config) == 0 {
32+
return rulesData, nil
33+
}
34+
3635
// Create a map of enabled pattern IDs for faster lookup
3736
enabledPatterns := make(map[string]bool)
3837
for _, pattern := range config {
@@ -67,45 +66,14 @@ func FilterRulesFromFile(rulesFilePath string, config []domain.PatternConfigurat
6766
return yaml.Marshal(filteredRules)
6867
}
6968

70-
// GetSemgrepConfig gets the Semgrep configuration based on the pattern configuration
69+
// GetSemgrepConfig gets the Semgrep configuration based on the pattern configuration.
70+
// If no configuration is provided, returns all default rules.
7171
func GetSemgrepConfig(config []domain.PatternConfiguration) ([]byte, error) {
72-
// Get the executable's directory
73-
execPath, err := getExecutablePath()
74-
if err != nil {
75-
return nil, fmt.Errorf("failed to get executable path: %w", err)
76-
}
77-
execDir := filepath.Dir(execPath)
78-
79-
// Get the default rules file location relative to the executable
80-
rulesFile := filepath.Join(execDir, "plugins", "tools", "semgrep", "rules.yaml")
81-
82-
// Check if it exists and config is not empty
83-
if _, err := os.Stat(rulesFile); err == nil && len(config) > 0 {
84-
// Try to filter rules from the file
85-
return FilterRulesFromFile(rulesFile, config)
86-
}
87-
88-
// If rules.yaml doesn't exist or config is empty, return an error
89-
return nil, fmt.Errorf("rules.yaml not found or empty configuration")
72+
return FilterRulesFromFile(embedded.GetSemgrepRules(), config)
9073
}
9174

9275
// GetDefaultSemgrepConfig gets the default Semgrep configuration
9376
func GetDefaultSemgrepConfig() ([]byte, error) {
94-
// Get the executable's directory
95-
execPath, err := getExecutablePath()
96-
if err != nil {
97-
return nil, fmt.Errorf("failed to get executable path: %w", err)
98-
}
99-
execDir := filepath.Dir(execPath)
100-
101-
// Get the default rules file location relative to the executable
102-
rulesFile := filepath.Join(execDir, "plugins", "tools", "semgrep", "rules.yaml")
103-
104-
// If the file exists, return its contents
105-
if _, err := os.Stat(rulesFile); err == nil {
106-
return os.ReadFile(rulesFile)
107-
}
108-
109-
// Return an error if rules.yaml doesn't exist
110-
return nil, fmt.Errorf("rules.yaml not found")
77+
// Return the embedded rules
78+
return embedded.GetSemgrepRules(), nil
11179
}

tools/semgrepConfigCreator_test.go

Lines changed: 20 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -2,135 +2,67 @@ package tools
22

33
import (
44
"codacy/cli-v2/domain"
5-
"os"
6-
"path/filepath"
5+
"codacy/cli-v2/plugins/tools/semgrep/embedded"
76
"testing"
87

98
"github.com/stretchr/testify/assert"
109
"gopkg.in/yaml.v3"
1110
)
1211

13-
// Sample rules YAML content for testing
14-
const sampleRulesYAML = `rules:
15-
- id: rule1
16-
pattern: |
17-
$X
18-
message: "Test rule 1"
19-
languages: [go]
20-
severity: INFO
21-
- id: rule2
22-
pattern: |
23-
$Y
24-
message: "Test rule 2"
25-
languages: [javascript]
26-
severity: WARNING
27-
- id: rule3
28-
pattern-either:
29-
- pattern: "foo()"
30-
- pattern: "bar()"
31-
message: "Test rule 3"
32-
languages: [python]
33-
severity: ERROR
34-
`
35-
3612
// TestFilterRulesFromFile tests the FilterRulesFromFile function
3713
func TestFilterRulesFromFile(t *testing.T) {
38-
// Create a temporary rules file
39-
tempDir := t.TempDir()
40-
rulesFile := filepath.Join(tempDir, "rules.yaml")
41-
err := os.WriteFile(rulesFile, []byte(sampleRulesYAML), 0644)
42-
assert.NoError(t, err)
14+
// Get the actual rules file content
15+
rulesData := embedded.GetSemgrepRules()
4316

4417
// Test case 1: Filter with enabled rules
4518
config := []domain.PatternConfiguration{
4619
{
4720
Enabled: true,
4821
PatternDefinition: domain.PatternDefinition{
49-
Id: "Semgrep_rule1",
50-
Enabled: true,
51-
},
52-
},
53-
{
54-
Enabled: true,
55-
PatternDefinition: domain.PatternDefinition{
56-
Id: "Semgrep_rule3",
22+
Id: "Semgrep_ai.csharp.detect-openai.detect-openai",
5723
Enabled: true,
5824
},
5925
},
6026
}
6127

62-
result, err := FilterRulesFromFile(rulesFile, config)
28+
result, err := FilterRulesFromFile(rulesData, config)
6329
assert.NoError(t, err)
6430

65-
// Parse the result and verify the rules
31+
// Parse the result and verify we got filtered rules
6632
var parsedRules semgrepRulesFile
6733
err = yaml.Unmarshal(result, &parsedRules)
6834
assert.NoError(t, err)
69-
assert.Equal(t, 2, len(parsedRules.Rules))
70-
71-
// Check that it contains rule1 and rule3 but not rule2
72-
ruleIDs := map[string]bool{}
73-
for _, rule := range parsedRules.Rules {
74-
id, _ := rule["id"].(string)
75-
ruleIDs[id] = true
76-
}
77-
assert.True(t, ruleIDs["rule1"])
78-
assert.False(t, ruleIDs["rule2"])
79-
assert.True(t, ruleIDs["rule3"])
35+
assert.Equal(t, 1, len(parsedRules.Rules))
8036

8137
// Test case 2: No enabled rules should return an error
8238
noEnabledConfig := []domain.PatternConfiguration{
8339
{
8440
Enabled: false,
8541
PatternDefinition: domain.PatternDefinition{
86-
Id: "Semgrep_rule1",
42+
Id: "Semgrep_nonexistent",
8743
Enabled: false,
8844
},
8945
},
9046
}
9147

92-
_, err = FilterRulesFromFile(rulesFile, noEnabledConfig)
48+
_, err = FilterRulesFromFile(rulesData, noEnabledConfig)
9349
assert.Error(t, err)
9450
assert.Contains(t, err.Error(), "no matching rules found")
9551

96-
// Test case 3: Non-existent rules file should return an error
97-
_, err = FilterRulesFromFile(filepath.Join(tempDir, "nonexistent.yaml"), config)
52+
// Test case 3: Invalid YAML should return an error
53+
_, err = FilterRulesFromFile([]byte("invalid yaml"), config)
9854
assert.Error(t, err)
99-
assert.Contains(t, err.Error(), "failed to read rules file")
55+
assert.Contains(t, err.Error(), "failed to parse rules file")
10056
}
10157

10258
// TestGetSemgrepConfig tests the GetSemgrepConfig function
10359
func TestGetSemgrepConfig(t *testing.T) {
104-
// Create a temporary rules file
105-
tempDir := t.TempDir()
106-
testRulesFile := filepath.Join(tempDir, "rules.yaml")
107-
err := os.WriteFile(testRulesFile, []byte(sampleRulesYAML), 0644)
108-
assert.NoError(t, err)
109-
110-
// Create a mock executable path that points to our temp directory
111-
originalGetExecutablePath := getExecutablePath
112-
getExecutablePath = func() (string, error) {
113-
return filepath.Join(tempDir, "test-executable"), nil
114-
}
115-
defer func() {
116-
getExecutablePath = originalGetExecutablePath
117-
}()
118-
119-
// Create the plugins directory structure
120-
pluginsDir := filepath.Join(tempDir, "plugins", "tools", "semgrep")
121-
err = os.MkdirAll(pluginsDir, 0755)
122-
assert.NoError(t, err)
123-
124-
// Copy our test file to the plugins directory
125-
err = os.WriteFile(filepath.Join(pluginsDir, "rules.yaml"), []byte(sampleRulesYAML), 0644)
126-
assert.NoError(t, err)
127-
12860
// Test with valid configuration
12961
config := []domain.PatternConfiguration{
13062
{
13163
Enabled: true,
13264
PatternDefinition: domain.PatternDefinition{
133-
Id: "Semgrep_rule1",
65+
Id: "Semgrep_ai.csharp.detect-openai.detect-openai",
13466
Enabled: true,
13567
},
13668
},
@@ -144,49 +76,22 @@ func TestGetSemgrepConfig(t *testing.T) {
14476
assert.NoError(t, err)
14577
assert.Equal(t, 1, len(parsedRules.Rules))
14678

147-
// Test with empty configuration
148-
_, err = GetSemgrepConfig([]domain.PatternConfiguration{})
149-
assert.Error(t, err)
79+
// Test with empty configuration (should return all rules)
80+
result, err = GetSemgrepConfig([]domain.PatternConfiguration{})
81+
assert.NoError(t, err)
82+
err = yaml.Unmarshal(result, &parsedRules)
83+
assert.NoError(t, err)
84+
assert.True(t, len(parsedRules.Rules) > 0)
15085
}
15186

15287
// TestGetDefaultSemgrepConfig tests the GetDefaultSemgrepConfig function
15388
func TestGetDefaultSemgrepConfig(t *testing.T) {
154-
// Create a temporary rules file
155-
tempDir := t.TempDir()
156-
testRulesFile := filepath.Join(tempDir, "rules.yaml")
157-
err := os.WriteFile(testRulesFile, []byte(sampleRulesYAML), 0644)
158-
assert.NoError(t, err)
159-
160-
// Create a mock executable path that points to our temp directory
161-
originalGetExecutablePath := getExecutablePath
162-
getExecutablePath = func() (string, error) {
163-
return filepath.Join(tempDir, "test-executable"), nil
164-
}
165-
defer func() {
166-
getExecutablePath = originalGetExecutablePath
167-
}()
168-
169-
// Create the plugins directory structure
170-
pluginsDir := filepath.Join(tempDir, "plugins", "tools", "semgrep")
171-
err = os.MkdirAll(pluginsDir, 0755)
172-
assert.NoError(t, err)
173-
174-
// Copy our test file to the plugins directory
175-
err = os.WriteFile(filepath.Join(pluginsDir, "rules.yaml"), []byte(sampleRulesYAML), 0644)
176-
assert.NoError(t, err)
177-
17889
// Test getting default config
17990
result, err := GetDefaultSemgrepConfig()
18091
assert.NoError(t, err)
18192

18293
var parsedRules semgrepRulesFile
18394
err = yaml.Unmarshal(result, &parsedRules)
18495
assert.NoError(t, err)
185-
assert.Equal(t, 3, len(parsedRules.Rules))
186-
187-
// Test when rules.yaml doesn't exist
188-
os.Remove(filepath.Join(pluginsDir, "rules.yaml"))
189-
_, err = GetDefaultSemgrepConfig()
190-
assert.Error(t, err)
191-
assert.Contains(t, err.Error(), "rules.yaml not found")
96+
assert.True(t, len(parsedRules.Rules) > 0)
19297
}

0 commit comments

Comments
 (0)