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

Implement process wrapper & add executor tests #27

Merged
merged 6 commits into from
Oct 23, 2022
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
1 change: 1 addition & 0 deletions .github/workflows/push_pull.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ on:

jobs:
ci:
timeout-minutes: 2
strategy:
fail-fast: false
matrix:
Expand Down
11 changes: 8 additions & 3 deletions cmd/cli/main.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package main

import (
"context"
"fmt"
"os"

app "github.com/dugajean/goke/internal"
"github.com/dugajean/goke/internal/cli"
)

func main() {
argIndex := app.PermutateArgs(os.Args)
opts := cli.GetOptions()
opts := app.GetOptions()

handleGlobalFlags(&opts)

Expand All @@ -20,13 +20,18 @@ func main() {
os.Exit(1)
}

// Wrappers
fs := app.LocalFileSystem{}
proc := app.ShellProcess{}

// Main components
p := app.NewParser(cfg, &opts, &fs)
p.Bootstrap()

l := app.NewLockfile(p.GetFilePaths(), &opts, &fs)
l.Bootstrap()

e := app.NewExecutor(&p, &l, &opts)
ctx := context.Background()
e := app.NewExecutor(&p, &l, &opts, &proc, &fs, &ctx)
e.Start(parseTaskName(argIndex))
}
4 changes: 3 additions & 1 deletion goke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ main:
- "go build -o './build/${BINARY}' ./cmd/cli"

genmocks:
files: [internal/filesystem.go]
files: [internal/filesystem.go, internal/process.go]
run:
- "mockery --name=FileSystem --recursive --output=internal/tests --outpkg=tests --filename=filesystem_mock.go"
- "mockery --name=Process --recursive --output=internal/tests --outpkg=tests --filename=process_mock.go"

greet-cats:
files: ["foo"]
run:
- 'echo "Hello Frey"'
- 'echo "Hello Bunny"'
Expand Down
21 changes: 0 additions & 21 deletions internal/cli/options.go

This file was deleted.

132 changes: 132 additions & 0 deletions internal/cli/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package cli

import (
"fmt"
"os"
"os/exec"
"regexp"
"strings"
)

var osCommandRegexp = regexp.MustCompile(`\$\((.+)\)`)
var osEnvRegexp = regexp.MustCompile(`\$\{(.+)\}`)

// Parses the interpolated system commands, ie. "Hello $(echo 'World')" and returns it.
// Returns the command wrapper in $() and without the wrapper.
func parseSystemCmd(re *regexp.Regexp, str string) (string, string) {
match := re.FindAllStringSubmatch(str, -1)

if len(match) > 0 && len(match[0]) > 0 {
return match[0][0], match[0][1]
}

return "", ""
}

// prase system commands and store results to env
func SetEnvVariables(vars map[string]string) (map[string]string, error) {
retVars := make(map[string]string)
for k, v := range vars {
_, cmd := parseSystemCmd(osCommandRegexp, v)

if cmd == "" {
retVars[k] = v
_ = os.Setenv(k, v)
continue
}

splitCmd, err := ParseCommandLine(os.ExpandEnv(cmd))
if err != nil {
return retVars, err
}

out, err := exec.Command(splitCmd[0], splitCmd[1:]...).Output()
if err != nil {
return retVars, err
}

outStr := strings.TrimSpace(string(out))
retVars[k] = outStr
_ = os.Setenv(k, outStr)
}

return retVars, nil
}

// Parses the command string into an array of [command, args, args]...
func ParseCommandLine(command string) ([]string, error) {
var args []string
state := "start"
current := ""
quote := "\""
escapeNext := true

for i := 0; i < len(command); i++ {
c := command[i]

if state == "quotes" {
if string(c) != quote {
current += string(c)
} else {
args = append(args, current)
current = ""
state = "start"
}
continue
}

if escapeNext {
current += string(c)
escapeNext = false
continue
}

if c == '\\' {
escapeNext = true
continue
}

if c == '"' || c == '\'' {
state = "quotes"
quote = string(c)
continue
}

if state == "arg" {
if c == ' ' || c == '\t' {
args = append(args, current)
current = ""
state = "start"
} else {
current += string(c)
}
continue
}

if c != ' ' && c != '\t' {
state = "arg"
current += string(c)
}
}

if state == "quotes" {
return []string{}, fmt.Errorf("unclosed quote in command: %s", command)
}

if current != "" {
args = append(args, current)
}

return args, nil
}

// Replace the placeholders with actual environment variable values in string pointer.
// Given that a string pointer must be provided, the replacement happens in place.
func ReplaceEnvironmentVariables(re *regexp.Regexp, str *string) {
resolved := *str
raw, env := parseSystemCmd(re, resolved)

if raw != "" && env != "" {
*str = strings.Replace(resolved, raw, os.Getenv(env), -1)
}
}
71 changes: 71 additions & 0 deletions internal/cli/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package cli

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
)

func TestParseSystemCmd(t *testing.T) {

cmds := []string{
"$(echo 'Hello Thor')",
"hello world",
}

want := [][]string{
{"$(echo 'Hello Thor')", "echo 'Hello Thor'"},
{"", ""},
}

for i, cmd := range cmds {
got0, got1 := parseSystemCmd(osCommandRegexp, cmd)
assert.Equal(t, want[i][0], got0, "expected "+want[i][0]+", got ", got0)
assert.Equal(t, want[i][1], got1, "expected "+want[i][1]+", got ", got1)
}

}

func TestSetEnvVariables(t *testing.T) {

values := map[string]string{
"THOR": "Lord of thunder",
"THOR_CMD": "$(echo 'Hello Thor')",
}

want := map[string]string{
"THOR": "Lord of thunder",
"THOR_CMD": "Hello Thor",
}

got, _ := SetEnvVariables(values)
assert.Equal(t, want["THOR"], os.Getenv("THOR"))
assert.Equal(t, want["THOR_CMD"], os.Getenv("THOR_CMD"))

for k := range got {
assert.Equal(t, want[k], got[k])
}
}

func TestParseCommandLine(t *testing.T) {
t.Skip()
}

func TestReplaceEnvironmentVariables(t *testing.T) {
values := map[string]string{
"THOR": "Lord of thunder",
"LOKI": "Lord of deception",
}

for k, v := range values {
t.Setenv(k, v)
}

str := "I am ${THOR}"
want := "I am Lord of thunder"

ReplaceEnvironmentVariables(osEnvRegexp, &str)

assert.Equal(t, want, str, "wrong env value is injected")
}
Loading