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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ A simple package to execute shell commands on linux, darwin and windows.

## Installation

`$ go get -u github.com/commander-cli/cmd@v1.5.0`
```bash
$ go get -u github.com/commander-cli/cmd@v1.5.0
```

## Usage

Expand Down
39 changes: 18 additions & 21 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,25 +47,24 @@ type Command struct {
//
// Example:
//
// env := map[string]string{"ENV": "VALUE"}
//
// env := map[string]string{"ENV": "VALUE"}
type EnvVars map[string]string

// NewCommand creates a new command
// You can add option with variadic option argument
// Default timeout is set to 30 minutes
//
// Example:
// c := cmd.NewCommand("echo hello", function (c *Command) {
// c.WorkingDir = "/tmp"
// })
// c.Execute()
//
// or you can use existing options functions
// c := cmd.NewCommand("echo hello", function (c *Command) {
// c.WorkingDir = "/tmp"
// })
// c.Execute()
//
// c := cmd.NewCommand("echo hello", cmd.WithStandardStreams)
// c.Execute()
// or you can use existing options functions
//
// c := cmd.NewCommand("echo hello", cmd.WithStandardStreams)
// c.Execute()
func NewCommand(cmd string, options ...func(*Command)) *Command {
c := &Command{
Command: cmd,
Expand All @@ -90,12 +89,11 @@ func NewCommand(cmd string, options ...func(*Command)) *Command {
//
// Example:
//
// c := cmd.NewCommand(
// "echo hello",
// cmd.WithCustomBaseCommand(exec.Command("/bin/bash", "-c")),
// )
// c.Execute()
//
// c := cmd.NewCommand(
// "echo hello",
// cmd.WithCustomBaseCommand(exec.Command("/bin/bash", "-c")),
// )
// c.Execute()
func WithCustomBaseCommand(baseCommand *exec.Cmd) func(c *Command) {
return func(c *Command) {
baseCommand.Args = append(baseCommand.Args, c.Command)
Expand All @@ -108,9 +106,8 @@ func WithCustomBaseCommand(baseCommand *exec.Cmd) func(c *Command) {
//
// Example:
//
// c := cmd.NewCommand("echo hello", cmd.WithStandardStreams)
// c.Execute()
//
// c := cmd.NewCommand("echo hello", cmd.WithStandardStreams)
// c.Execute()
func WithStandardStreams(c *Command) {
c.StdoutWriter = io.MultiWriter(os.Stdout, &c.stdout, &c.combined)
c.StderrWriter = io.MultiWriter(os.Stderr, &c.stderr, &c.combined)
Expand All @@ -135,8 +132,8 @@ func WithCustomStderr(writers ...io.Writer) func(c *Command) {
// WithTimeout sets the timeout of the command
//
// Example:
// cmd.NewCommand("sleep 10;", cmd.WithTimeout(500))
//
// cmd.NewCommand("sleep 10;", cmd.WithTimeout(500))
func WithTimeout(t time.Duration) func(c *Command) {
return func(c *Command) {
c.Timeout = t
Expand Down Expand Up @@ -201,13 +198,13 @@ func (c *Command) Combined() string {
return c.combined.String()
}

//ExitCode returns the exit code of the command
// ExitCode returns the exit code of the command
func (c *Command) ExitCode() int {
c.isExecuted("ExitCode")
return c.exitCode
}

//Executed returns if the command was already executed
// Executed returns if the command was already executed
func (c *Command) Executed() bool {
return c.executed
}
Expand Down
17 changes: 17 additions & 0 deletions command_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,26 @@ package cmd

import (
"os/exec"
"syscall"
)

func createBaseCommand(c *Command) *exec.Cmd {
cmd := exec.Command("/bin/sh", "-c", c.Command)
return cmd
}

// WithUser allows the command to be run as a different
// user.
//
// Example:
//
// cred := syscall.Credential{Uid: 1000, Gid: 1000}
// c := NewCommand("echo hello", cred)
// c.Execute()
func WithUser(credential syscall.Credential) func(c *Command) {
return func(c *Command) {
c.baseCommand.SysProcAttr = &syscall.SysProcAttr{
Credential: &credential,
}
}
}
12 changes: 12 additions & 0 deletions command_darwin_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"syscall"
"testing"
"time"

Expand Down Expand Up @@ -32,3 +33,14 @@ func TestCommand_WithValidTimeout(t *testing.T) {

assert.Nil(t, err)
}

// I really don't see the point of mocking this
// as the stdlib does so already. So testing here
// seems redundant. This simple check if we're compliant
Comment thread
dylanhitt marked this conversation as resolved.
// with an api changes
Comment thread
dylanhitt marked this conversation as resolved.
func TestCommand_WithUser(t *testing.T) {
cmd := NewCommand("echo hello", WithUser(syscall.Credential{Uid: 1111}))
err := cmd.Execute()
assert.Equal(t, uint32(1111), cmd.baseCommand.SysProcAttr.Credential.Uid)
assert.Error(t, err)
}
17 changes: 17 additions & 0 deletions command_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,26 @@ package cmd

import (
"os/exec"
"syscall"
)

func createBaseCommand(c *Command) *exec.Cmd {
cmd := exec.Command("/bin/sh", "-c", c.Command)
return cmd
}

// WithUser allows the command to be run as a different
// user.
//
// Example:
//
// cred := syscall.Credential{Uid: 1000, Gid: 1000}
// c := NewCommand("echo hello", cred)
// c.Execute()
func WithUser(credential syscall.Credential) func(c *Command) {
return func(c *Command) {
c.baseCommand.SysProcAttr = &syscall.SysProcAttr{
Credential: &credential,
}
}
}
8 changes: 8 additions & 0 deletions command_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"os/exec"
"strings"
"syscall"
"testing"
"time"

Expand Down Expand Up @@ -171,3 +172,10 @@ func TestCommand_WithCustomBaseCommand(t *testing.T) {
assert.NotEqual(t, "/bin/sh\n", cmd.Stdout())
assert.Equal(t, "/bin/bash\n", cmd.Stdout())
}

func TestCommand_WithUser(t *testing.T) {
cred := syscall.Credential{}
cmd := NewCommand("echo hello", WithUser(cred))
err := cmd.Execute()
assert.Error(t, err)
}
17 changes: 17 additions & 0 deletions command_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,26 @@ package cmd

import (
"os/exec"
"syscall"
)

func createBaseCommand(c *Command) *exec.Cmd {
cmd := exec.Command(`C:\windows\system32\cmd.exe`, "/C", c.Command)
return cmd
}

// WithUser allows the command to be run as a different
// user.
//
// Example:
//
// token := syscall.Token(handle)
// c := NewCommand("echo hello", token)
// c.Execute()
func WithUser(token syscall.Token) func(c *Command) {
return func(c *Command) {
c.baseCommand.SysProcAttr = &syscall.SysProcAttr{
Token: token,
}
}
}
13 changes: 12 additions & 1 deletion command_windows_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package cmd

import (
"github.com/stretchr/testify/assert"
"strings"
"syscall"
"testing"
"time"
"unsafe"

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

func TestCommand_ExecuteStderr(t *testing.T) {
Expand Down Expand Up @@ -34,3 +37,11 @@ func TestCommand_WithValidTimeout(t *testing.T) {

assert.Nil(t, err)
}

func TestCommand_WithUser(t *testing.T) {
onehundred := 100
token := syscall.Token(uintptr(unsafe.Pointer(&onehundred)))
cmd := NewCommand("echo hello", WithUser(token))
err := cmd.Execute()
assert.Error(t, err)
}