Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Windows Compatibility-Add PowerShell Script as an Alternative to Grep on Linux #690

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
80 changes: 67 additions & 13 deletions provider/internal/builtin/service_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"sync"
Expand Down Expand Up @@ -83,14 +84,12 @@ func (p *builtinServiceClient) Evaluate(ctx context.Context, cap string, conditi
if c.Pattern == "" {
return response, fmt.Errorf("could not parse provided regex pattern as string: %v", conditionInfo)
}

var outputBytes []byte
grep := exec.Command("grep", "-o", "-n", "-R", "-P", c.Pattern, p.config.Location)
outputBytes, err := grep.Output()
//Runs on Windows using PowerShell.exe and Unix based systems using grep
outputBytes, err := runOSSpecificGrepCommand(c.Pattern, p.config.Location)
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok && exitError.ExitCode() == 1 {
return response, nil
}
return response, fmt.Errorf("could not run grep with provided pattern %+v", err)
return response, err
}

matches := []string{}
Expand All @@ -100,13 +99,10 @@ func (p *builtinServiceClient) Evaluate(ctx context.Context, cap string, conditi
}

for _, match := range matches {
//TODO(fabianvf): This will not work if there is a `:` in the filename, do we care?
pieces := strings.SplitN(match, ":", 3)
if len(pieces) != 3 {
//TODO(fabianvf): Just log or return?
//(shawn-hurley): I think the return is good personally
return response, fmt.Errorf(
"malformed response from grep, cannot parse grep output '%s' with pattern {filepath}:{lineNumber}:{matchingText}", match)
var pieces []string
pieces, err := parseGrepOutputForFileContent(match)
if err != nil {
return response, fmt.Errorf("could not parse grep output '%s' for the Pattern '%v': %v ", match, c.Pattern, err)
}

containsFile, err := provider.FilterFilePattern(c.FilePattern, pieces[0])
Expand All @@ -130,6 +126,7 @@ func (p *builtinServiceClient) Evaluate(ctx context.Context, cap string, conditi
if err != nil {
return response, fmt.Errorf("cannot convert line number string to integer")
}

response.Incidents = append(response.Incidents, provider.IncidentContext{
FileURI: uri.File(absPath),
LineNumber: &lineNumber,
Expand Down Expand Up @@ -497,3 +494,60 @@ func (b *builtinServiceClient) isFileIncluded(absolutePath string) bool {
b.log.V(7).Info("excluding file from search", "file", absolutePath)
return false
}

func parseGrepOutputForFileContent(match string) ([]string, error) {
// This will parse the output of the PowerShell/grep in the form
// "Filepath:Linenumber:Matchingtext" to return string array of path, line number and matching text
// works with handling both windows and unix based file paths eg: "C:\path\to\file" and "/path/to/file"
re, err := regexp.Compile(`^(.*?):(\d+):(.*)$`)
if err != nil {
return nil, fmt.Errorf("failed to compile regular expression: %v", err)
}
submatches := re.FindStringSubmatch(match)
if len(submatches) != 4 {
return nil, fmt.Errorf(
"malformed response from file search, cannot parse result '%s' with pattern %#q", match, re)
}
return submatches[1:], nil
}

func runOSSpecificGrepCommand(pattern string, location string) ([]byte, error) {
var outputBytes []byte
var err error
var utilName string

if runtime.GOOS == "windows" {
utilName = "powershell.exe"
// Windows does not have grep, so we use PowerShell.exe's Select-String instead
// This is a workaround until we can find a better solution
psScript := `
$pattern = $env:PATTERN
$location = $env:FILEPATH
Get-ChildItem -Path $location -Recurse -File | ForEach-Object {
$file = $_
# Search for the pattern in the file
Select-String -Path $file.FullName -Pattern $pattern -AllMatches | ForEach-Object {
foreach ($match in $_.Matches) {
"{0}:{1}:{2}" -f $file.FullName, $_.LineNumber, $match.Value
}
}
}`
findstr := exec.Command(utilName, "-Command", psScript)
findstr.Env = append(os.Environ(), "PATTERN="+pattern, "FILEPATH="+location)
outputBytes, err = findstr.Output()

} else {
utilName = "grep"
// Linux and MacOS use grep
grep := exec.Command(utilName, "-o", "-n", "-R", "-P", pattern, location)
outputBytes, err = grep.Output()
}
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok && exitError.ExitCode() == 1 {
return nil, nil
}
return nil, fmt.Errorf("could not run '%s' with provided pattern %+v", utilName, err)
}

return outputBytes, nil
}
Loading