Skip to content

Commit 9d78132

Browse files
committed
Initial commit
0 parents  commit 9d78132

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+17283
-0
lines changed

.githooks/pre-commit

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env bash
2+
3+
echo "Run gofmt..."
4+
FMT_FILES=`gofmt -l . | grep -v "vendor/"`
5+
if [[ $FMT_FILES != "" ]]; then
6+
echo "Some files aren't formatted, please run 'go fmt ./...' to format your source code before committing"
7+
echo "${FMT_FILES}"
8+
exit 1
9+
fi
10+
11+
echo "Run go vet..."
12+
vetcount=`go vet ./... 2>&1 | wc -l`
13+
if [ $vetcount -gt 0 ]; then
14+
echo "Some files aren't passing vet heuristics, please run 'go vet ./...' to see the errors it flags and correct your source code before committing"
15+
exit 1
16+
fi

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.idea

Makefile

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
exe = cmd/operator/*
2+
cmd = operator
3+
TRAVIS_TAG ?= "0.0.0"
4+
5+
.PHONY: deps lint test integration integration-windows git-hooks init
6+
7+
init: git-hooks
8+
9+
git-hooks:
10+
$(info INFO: Starting build $@)
11+
ln -sf ../../.githooks/pre-commit .git/hooks/pre-commit
12+
13+
deps:
14+
$(info INFO: Starting build $@)
15+
go mod vendor
16+
17+
build:
18+
$(info INFO: Starting build $@)
19+
go build $(exe)
20+
21+
lint:
22+
$(info INFO: Starting build $@)
23+
golint pkg/ cmd/
24+
25+
test:
26+
$(info INFO: Starting build $@)
27+
go test ./...
28+
29+
test-coverage:
30+
$(info INFO: Starting build $@)
31+
go test -coverprofile c.out ./...

README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# cmd package
2+
3+
A simple package to execute shell commands on windows, darwin and windows.
4+
5+
## Usage
6+
7+
```go
8+
cmd := NewCommand("echo hello")
9+
10+
err := cmd.Execute()
11+
if err != nil {
12+
panic(err.Error())
13+
}
14+
15+
fmt.Println(cmd.Stdout())
16+
fmt.Println(cmd.Stderr())
17+
```
18+
19+
### Stream output to stderr and stdout
20+
21+
```go
22+
cmd := NewCommand("echo hello", WithStandardStreams)
23+
cmd.Execute()
24+
```
25+
26+
### Set custom options
27+
28+
```go
29+
func SetTimeout(c *Command) {
30+
c.Timeout = 1 * time.Hour
31+
}
32+
33+
func SetWorkingDir(c *Command) {
34+
c.WorkingDir = "/tmp/test"
35+
}
36+
37+
cmd := NewCommand("pwd", SetTimeout, SetWorkingDir)
38+
cmd.Execute()
39+
```
40+
41+
## Development
42+
43+
### Running tests
44+
45+
```
46+
make test
47+
```
48+
49+
### ToDo
50+
51+
- Reports
52+
- Coverage reports
53+
- Go report
54+
- Codeclimate
55+
- Travis-Pipeline
56+
- Documentation
57+
- os.Stdout and os.Stderr output access after execution

command.go

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package cmd
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io"
7+
"os"
8+
"os/exec"
9+
"regexp"
10+
"strings"
11+
"syscall"
12+
"time"
13+
)
14+
15+
//Command represents a single command which can be executed
16+
type Command struct {
17+
Command string
18+
Env []string
19+
Dir string
20+
Timeout time.Duration
21+
StderrWriter io.Writer
22+
StdoutWriter io.Writer
23+
WorkingDir string
24+
executed bool
25+
stderr bytes.Buffer
26+
stdout bytes.Buffer
27+
exitCode int
28+
}
29+
30+
//NewCommand creates a new command
31+
func NewCommand(cmd string, options ...func(*Command)) *Command {
32+
c := &Command{
33+
Command: cmd,
34+
Timeout: 1 * time.Minute,
35+
executed: false,
36+
Env: []string{},
37+
}
38+
39+
c.StdoutWriter = &c.stdout
40+
c.StderrWriter = &c.stderr
41+
42+
for _, o := range options {
43+
o(c)
44+
}
45+
46+
return c
47+
}
48+
49+
// WithStandardStreams creates a command which steams its output to the stderr and stdout streams of the operating system
50+
func WithStandardStreams(c *Command) {
51+
c.StdoutWriter = os.Stdout
52+
c.StderrWriter = os.Stderr
53+
}
54+
55+
// AddEnv adds an environment variable to the command
56+
// If a variable gets passed like ${VAR_NAME} the env variable will be read out by the current shell
57+
func (c *Command) AddEnv(key string, value string) {
58+
vars := parseEnvVariableFromShell(value)
59+
for _, v := range vars {
60+
value = strings.Replace(value, v, os.Getenv(removeEnvVarSyntax(v)), -1)
61+
}
62+
63+
c.Env = append(c.Env, fmt.Sprintf("%s=%s", key, value))
64+
}
65+
66+
// Removes the ${...} characters
67+
func removeEnvVarSyntax(v string) string {
68+
return v[2:(len(v) - 1)]
69+
}
70+
71+
//Read all environment variables from the given value
72+
//with the syntax ${VAR_NAME}
73+
func parseEnvVariableFromShell(val string) []string {
74+
reg := regexp.MustCompile(`\$\{.*?\}`)
75+
matches := reg.FindAllString(val, -1)
76+
return matches
77+
}
78+
79+
//SetTimeoutMS sets the timeout in milliseconds
80+
func (c *Command) SetTimeoutMS(ms int) {
81+
if ms == 0 {
82+
c.Timeout = 1 * time.Minute
83+
return
84+
}
85+
c.Timeout = time.Duration(ms) * time.Millisecond
86+
}
87+
88+
// SetTimeout sets the timeout given a time unit
89+
// Example: SetTimeout("100s") sets the timeout to 100 seconds
90+
func (c *Command) SetTimeout(timeout string) error {
91+
d, err := time.ParseDuration(timeout)
92+
if err != nil {
93+
return err
94+
}
95+
96+
c.Timeout = d
97+
return nil
98+
}
99+
100+
//Stdout returns the output to stdout
101+
func (c *Command) Stdout() string {
102+
c.isExecuted("Stdout")
103+
return c.stdout.String()
104+
}
105+
106+
//Stderr returns the output to stderr
107+
func (c *Command) Stderr() string {
108+
c.isExecuted("Stderr")
109+
return c.stderr.String()
110+
}
111+
112+
//ExitCode returns the exit code of the command
113+
func (c *Command) ExitCode() int {
114+
c.isExecuted("ExitCode")
115+
return c.exitCode
116+
}
117+
118+
//Executed returns if the command was already executed
119+
func (c *Command) Executed() bool {
120+
return c.executed
121+
}
122+
123+
func (c *Command) isExecuted(property string) {
124+
if !c.executed {
125+
panic("Can not read " + property + " if command was not executed.")
126+
}
127+
}
128+
129+
// Execute executes the command and writes the results into it's own instance
130+
// The results can be received with the Stdout(), Stderr() and ExitCode() methods
131+
func (c *Command) Execute() error {
132+
cmd := createBaseCommand(c)
133+
cmd.Env = c.Env
134+
cmd.Dir = c.Dir
135+
cmd.Stdout = c.StdoutWriter
136+
cmd.Stderr = c.StderrWriter
137+
cmd.Dir = c.WorkingDir
138+
139+
err := cmd.Start()
140+
if err != nil {
141+
return err
142+
}
143+
144+
done := make(chan error)
145+
go func() {
146+
done <- cmd.Wait()
147+
}()
148+
149+
select {
150+
case err := <-done:
151+
if err != nil {
152+
c.getExitCode(err)
153+
break
154+
}
155+
c.exitCode = 0
156+
case <-time.After(c.Timeout):
157+
if err := cmd.Process.Kill(); err != nil {
158+
return fmt.Errorf("Timeout occurred and can not kill process with pid %v", cmd.Process.Pid)
159+
}
160+
return fmt.Errorf("Command timed out after %v", c.Timeout)
161+
}
162+
163+
//Remove leading and trailing whitespaces
164+
c.executed = true
165+
166+
return nil
167+
}
168+
169+
func (c *Command) getExitCode(err error) {
170+
if exitErr, ok := err.(*exec.ExitError); ok {
171+
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
172+
c.exitCode = status.ExitStatus()
173+
}
174+
}
175+
}

command_darwin.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package cmd
2+
3+
import (
4+
"os/exec"
5+
"strings"
6+
)
7+
8+
func createBaseCommand(c *Command) *exec.Cmd {
9+
cmd := exec.Command("/bin/sh", "-Command", c.Command)
10+
return cmd
11+
}
12+
13+
func (c *Command) removeLineBreaks(text string) string {
14+
return strings.Trim(text, "\n")
15+
}

command_darwin_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package cmd
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"testing"
6+
)
7+
8+
func TestCommand_ExecuteStderr(t *testing.T) {
9+
cmd := NewCommand(">&2 echo hello")
10+
11+
err := cmd.Execute()
12+
13+
assert.Nil(t, err)
14+
assert.Equal(t, "hello", cmd.Stderr())
15+
}
16+
17+
func TestCommand_WithTimeout(t *testing.T) {
18+
cmd := NewCommand("sleep 0.5;")
19+
cmd.SetTimeoutMS(5)
20+
21+
err := cmd.Execute()
22+
23+
assert.NotNil(t, err)
24+
assert.Equal(t, "Command timed out after 5ms", err.Error())
25+
}
26+
27+
func TestCommand_WithValidTimeout(t *testing.T) {
28+
cmd := NewCommand("sleep 0.01;")
29+
cmd.SetTimeoutMS(500)
30+
31+
err := cmd.Execute()
32+
33+
assert.Nil(t, err)
34+
}

command_linux.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package cmd
2+
3+
import (
4+
"os/exec"
5+
"strings"
6+
)
7+
8+
func createBaseCommand(c *Command) *exec.Cmd {
9+
cmd := exec.Command("/bin/sh", "-c", c.Command)
10+
return cmd
11+
}
12+
13+
func (c *Command) removeLineBreaks(text string) string {
14+
return strings.Trim(text, "\n")
15+
}

0 commit comments

Comments
 (0)