Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions plugins/tools/semgrep/embedded/semgrep.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Package embedded contains embedded files used by the tools package
package embedded

import "embed"

//go:embed rules.yaml
var rulesFS embed.FS

// GetSemgrepRules returns the embedded Semgrep rules
func GetSemgrepRules() []byte {
data, err := rulesFS.ReadFile("rules.yaml")
if err != nil {
panic(err) // This should never happen as the file is embedded
}
return data
}
60 changes: 14 additions & 46 deletions tools/semgrepConfigCreator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package tools

import (
"codacy/cli-v2/domain"
"codacy/cli-v2/plugins/tools/semgrep/embedded"
"fmt"
"os"
"path/filepath"
"strings"

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

// FilterRulesFromFile extracts enabled rules from a rules.yaml file based on configuration
func FilterRulesFromFile(rulesFilePath string, config []domain.PatternConfiguration) ([]byte, error) {
// Read the rules.yaml file
data, err := os.ReadFile(rulesFilePath)
if err != nil {
return nil, fmt.Errorf("failed to read rules file: %w", err)
}

// Parse the YAML file
func FilterRulesFromFile(rulesData []byte, config []domain.PatternConfiguration) ([]byte, error) {
// Parse the YAML data
var allRules semgrepRulesFile
if err := yaml.Unmarshal(data, &allRules); err != nil {
if err := yaml.Unmarshal(rulesData, &allRules); err != nil {
return nil, fmt.Errorf("failed to parse rules file: %w", err)
}

// If no configuration provided, return all rules
if len(config) == 0 {
return rulesData, nil
}

// Create a map of enabled pattern IDs for faster lookup
enabledPatterns := make(map[string]bool)
for _, pattern := range config {
Expand Down Expand Up @@ -67,45 +66,14 @@ func FilterRulesFromFile(rulesFilePath string, config []domain.PatternConfigurat
return yaml.Marshal(filteredRules)
}

// GetSemgrepConfig gets the Semgrep configuration based on the pattern configuration
// GetSemgrepConfig gets the Semgrep configuration based on the pattern configuration.
// If no configuration is provided, returns all default rules.
func GetSemgrepConfig(config []domain.PatternConfiguration) ([]byte, error) {
// Get the executable's directory
execPath, err := getExecutablePath()
if err != nil {
return nil, fmt.Errorf("failed to get executable path: %w", err)
}
execDir := filepath.Dir(execPath)

// Get the default rules file location relative to the executable
rulesFile := filepath.Join(execDir, "plugins", "tools", "semgrep", "rules.yaml")

// Check if it exists and config is not empty
if _, err := os.Stat(rulesFile); err == nil && len(config) > 0 {
// Try to filter rules from the file
return FilterRulesFromFile(rulesFile, config)
}

// If rules.yaml doesn't exist or config is empty, return an error
return nil, fmt.Errorf("rules.yaml not found or empty configuration")
return FilterRulesFromFile(embedded.GetSemgrepRules(), config)
}

// GetDefaultSemgrepConfig gets the default Semgrep configuration
func GetDefaultSemgrepConfig() ([]byte, error) {
// Get the executable's directory
execPath, err := getExecutablePath()
if err != nil {
return nil, fmt.Errorf("failed to get executable path: %w", err)
}
execDir := filepath.Dir(execPath)

// Get the default rules file location relative to the executable
rulesFile := filepath.Join(execDir, "plugins", "tools", "semgrep", "rules.yaml")

// If the file exists, return its contents
if _, err := os.Stat(rulesFile); err == nil {
return os.ReadFile(rulesFile)
}

// Return an error if rules.yaml doesn't exist
return nil, fmt.Errorf("rules.yaml not found")
// Return the embedded rules
return embedded.GetSemgrepRules(), nil
}
135 changes: 20 additions & 115 deletions tools/semgrepConfigCreator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,135 +2,67 @@ package tools

import (
"codacy/cli-v2/domain"
"os"
"path/filepath"
"codacy/cli-v2/plugins/tools/semgrep/embedded"
"testing"

"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)

// Sample rules YAML content for testing
const sampleRulesYAML = `rules:
- id: rule1
pattern: |
$X
message: "Test rule 1"
languages: [go]
severity: INFO
- id: rule2
pattern: |
$Y
message: "Test rule 2"
languages: [javascript]
severity: WARNING
- id: rule3
pattern-either:
- pattern: "foo()"
- pattern: "bar()"
message: "Test rule 3"
languages: [python]
severity: ERROR
`

// TestFilterRulesFromFile tests the FilterRulesFromFile function
func TestFilterRulesFromFile(t *testing.T) {
// Create a temporary rules file
tempDir := t.TempDir()
rulesFile := filepath.Join(tempDir, "rules.yaml")
err := os.WriteFile(rulesFile, []byte(sampleRulesYAML), 0644)
assert.NoError(t, err)
// Get the actual rules file content
rulesData := embedded.GetSemgrepRules()

// Test case 1: Filter with enabled rules
config := []domain.PatternConfiguration{
{
Enabled: true,
PatternDefinition: domain.PatternDefinition{
Id: "Semgrep_rule1",
Enabled: true,
},
},
{
Enabled: true,
PatternDefinition: domain.PatternDefinition{
Id: "Semgrep_rule3",
Id: "Semgrep_ai.csharp.detect-openai.detect-openai",
Enabled: true,
},
},
}

result, err := FilterRulesFromFile(rulesFile, config)
result, err := FilterRulesFromFile(rulesData, config)
assert.NoError(t, err)

// Parse the result and verify the rules
// Parse the result and verify we got filtered rules
var parsedRules semgrepRulesFile
err = yaml.Unmarshal(result, &parsedRules)
assert.NoError(t, err)
assert.Equal(t, 2, len(parsedRules.Rules))

// Check that it contains rule1 and rule3 but not rule2
ruleIDs := map[string]bool{}
for _, rule := range parsedRules.Rules {
id, _ := rule["id"].(string)
ruleIDs[id] = true
}
assert.True(t, ruleIDs["rule1"])
assert.False(t, ruleIDs["rule2"])
assert.True(t, ruleIDs["rule3"])
assert.Equal(t, 1, len(parsedRules.Rules))

// Test case 2: No enabled rules should return an error
noEnabledConfig := []domain.PatternConfiguration{
{
Enabled: false,
PatternDefinition: domain.PatternDefinition{
Id: "Semgrep_rule1",
Id: "Semgrep_nonexistent",
Enabled: false,
},
},
}

_, err = FilterRulesFromFile(rulesFile, noEnabledConfig)
_, err = FilterRulesFromFile(rulesData, noEnabledConfig)
assert.Error(t, err)
assert.Contains(t, err.Error(), "no matching rules found")

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

// TestGetSemgrepConfig tests the GetSemgrepConfig function
func TestGetSemgrepConfig(t *testing.T) {
// Create a temporary rules file
tempDir := t.TempDir()
testRulesFile := filepath.Join(tempDir, "rules.yaml")
err := os.WriteFile(testRulesFile, []byte(sampleRulesYAML), 0644)
assert.NoError(t, err)

// Create a mock executable path that points to our temp directory
originalGetExecutablePath := getExecutablePath
getExecutablePath = func() (string, error) {
return filepath.Join(tempDir, "test-executable"), nil
}
defer func() {
getExecutablePath = originalGetExecutablePath
}()

// Create the plugins directory structure
pluginsDir := filepath.Join(tempDir, "plugins", "tools", "semgrep")
err = os.MkdirAll(pluginsDir, 0755)
assert.NoError(t, err)

// Copy our test file to the plugins directory
err = os.WriteFile(filepath.Join(pluginsDir, "rules.yaml"), []byte(sampleRulesYAML), 0644)
assert.NoError(t, err)

// Test with valid configuration
config := []domain.PatternConfiguration{
{
Enabled: true,
PatternDefinition: domain.PatternDefinition{
Id: "Semgrep_rule1",
Id: "Semgrep_ai.csharp.detect-openai.detect-openai",
Enabled: true,
},
},
Expand All @@ -144,49 +76,22 @@ func TestGetSemgrepConfig(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, 1, len(parsedRules.Rules))

// Test with empty configuration
_, err = GetSemgrepConfig([]domain.PatternConfiguration{})
assert.Error(t, err)
// Test with empty configuration (should return all rules)
result, err = GetSemgrepConfig([]domain.PatternConfiguration{})
assert.NoError(t, err)
err = yaml.Unmarshal(result, &parsedRules)
assert.NoError(t, err)
assert.True(t, len(parsedRules.Rules) > 0)
}

// TestGetDefaultSemgrepConfig tests the GetDefaultSemgrepConfig function
func TestGetDefaultSemgrepConfig(t *testing.T) {
// Create a temporary rules file
tempDir := t.TempDir()
testRulesFile := filepath.Join(tempDir, "rules.yaml")
err := os.WriteFile(testRulesFile, []byte(sampleRulesYAML), 0644)
assert.NoError(t, err)

// Create a mock executable path that points to our temp directory
originalGetExecutablePath := getExecutablePath
getExecutablePath = func() (string, error) {
return filepath.Join(tempDir, "test-executable"), nil
}
defer func() {
getExecutablePath = originalGetExecutablePath
}()

// Create the plugins directory structure
pluginsDir := filepath.Join(tempDir, "plugins", "tools", "semgrep")
err = os.MkdirAll(pluginsDir, 0755)
assert.NoError(t, err)

// Copy our test file to the plugins directory
err = os.WriteFile(filepath.Join(pluginsDir, "rules.yaml"), []byte(sampleRulesYAML), 0644)
assert.NoError(t, err)

// Test getting default config
result, err := GetDefaultSemgrepConfig()
assert.NoError(t, err)

var parsedRules semgrepRulesFile
err = yaml.Unmarshal(result, &parsedRules)
assert.NoError(t, err)
assert.Equal(t, 3, len(parsedRules.Rules))

// Test when rules.yaml doesn't exist
os.Remove(filepath.Join(pluginsDir, "rules.yaml"))
_, err = GetDefaultSemgrepConfig()
assert.Error(t, err)
assert.Contains(t, err.Error(), "rules.yaml not found")
assert.True(t, len(parsedRules.Rules) > 0)
}
Loading