From 19926146065506f3629ea405d37cbc60f8ebcdc2 Mon Sep 17 00:00:00 2001 From: dylanhitt Date: Mon, 14 Nov 2022 21:22:29 -0500 Subject: [PATCH 1/5] Add the ability to change user on *nix systems --- command.go | 39 ++++++++++++++++++--------------------- command_darwin.go | 9 +++++++++ command_darwin_test.go | 11 +++++++++++ command_linux.go | 9 +++++++++ command_linux_test.go | 8 ++++++++ 5 files changed, 55 insertions(+), 21 deletions(-) diff --git a/command.go b/command.go index 2896716..e544878 100644 --- a/command.go +++ b/command.go @@ -47,8 +47,7 @@ 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 @@ -56,16 +55,16 @@ type EnvVars map[string]string // 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, @@ -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) @@ -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) @@ -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 @@ -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 } diff --git a/command_darwin.go b/command_darwin.go index eb10896..f039f7b 100644 --- a/command_darwin.go +++ b/command_darwin.go @@ -2,9 +2,18 @@ package cmd import ( "os/exec" + "syscall" ) func createBaseCommand(c *Command) *exec.Cmd { cmd := exec.Command("/bin/sh", "-c", c.Command) return cmd } + +func WithUser(credential syscall.Credential) func(c *Command) { + return func(c *Command) { + c.baseCommand.SysProcAttr = &syscall.SysProcAttr{ + Credential: &credential, + } + } +} diff --git a/command_darwin_test.go b/command_darwin_test.go index e107fc9..ceb3aec 100644 --- a/command_darwin_test.go +++ b/command_darwin_test.go @@ -1,6 +1,7 @@ package cmd import ( + "syscall" "testing" "time" @@ -32,3 +33,13 @@ 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 +// with an api changes +func TestCommand_WithUser(t *testing.T) { + cmd := NewCommand("echo hello", WithUser(syscall.Credential{})) + err := cmd.Execute() + assert.Error(t, err) +} diff --git a/command_linux.go b/command_linux.go index eb10896..f039f7b 100644 --- a/command_linux.go +++ b/command_linux.go @@ -2,9 +2,18 @@ package cmd import ( "os/exec" + "syscall" ) func createBaseCommand(c *Command) *exec.Cmd { cmd := exec.Command("/bin/sh", "-c", c.Command) return cmd } + +func WithUser(credential syscall.Credential) func(c *Command) { + return func(c *Command) { + c.baseCommand.SysProcAttr = &syscall.SysProcAttr{ + Credential: &credential, + } + } +} diff --git a/command_linux_test.go b/command_linux_test.go index 0649bd6..d9d63a7 100644 --- a/command_linux_test.go +++ b/command_linux_test.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "strings" + "syscall" "testing" "time" @@ -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) +} From fec2ff158f49524a0a5818e513715aeb93af56f1 Mon Sep 17 00:00:00 2001 From: dylanhitt Date: Mon, 14 Nov 2022 21:42:35 -0500 Subject: [PATCH 2/5] Add WithUser for windows --- command_windows.go | 9 +++++++++ command_windows_test.go | 13 ++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/command_windows.go b/command_windows.go index 45d5cec..fce4a27 100644 --- a/command_windows.go +++ b/command_windows.go @@ -2,9 +2,18 @@ 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 } + +func WithUser(token syscall.Token) func(c *Command) { + return func(c *Command) { + c.baseCommand.SysProcAttr = &syscall.SysProcAttr{ + Token: token, + } + } +} diff --git a/command_windows_test.go b/command_windows_test.go index db9c383..5e18908 100644 --- a/command_windows_test.go +++ b/command_windows_test.go @@ -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) { @@ -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) +} From 49c8d4e83b3073ed2337764897f142e915878e90 Mon Sep 17 00:00:00 2001 From: Dylan Hitt Date: Tue, 22 Nov 2022 10:23:47 -0500 Subject: [PATCH 3/5] Fix typos found in code review --- command_darwin_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/command_darwin_test.go b/command_darwin_test.go index ceb3aec..057b75a 100644 --- a/command_darwin_test.go +++ b/command_darwin_test.go @@ -36,8 +36,8 @@ func TestCommand_WithValidTimeout(t *testing.T) { // 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 -// with an api changes +// seems redundant. This simply checks if we're compliant +// with api changes func TestCommand_WithUser(t *testing.T) { cmd := NewCommand("echo hello", WithUser(syscall.Credential{})) err := cmd.Execute() From 13abf4ee2fabaad0348da25e33c8763648b223c1 Mon Sep 17 00:00:00 2001 From: dylanhitt Date: Thu, 24 Nov 2022 13:01:22 -0500 Subject: [PATCH 4/5] Add WithUser docs --- command_darwin.go | 8 ++++++++ command_linux.go | 8 ++++++++ command_windows.go | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/command_darwin.go b/command_darwin.go index f039f7b..35e6355 100644 --- a/command_darwin.go +++ b/command_darwin.go @@ -10,6 +10,14 @@ func createBaseCommand(c *Command) *exec.Cmd { 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{ diff --git a/command_linux.go b/command_linux.go index f039f7b..35e6355 100644 --- a/command_linux.go +++ b/command_linux.go @@ -10,6 +10,14 @@ func createBaseCommand(c *Command) *exec.Cmd { 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{ diff --git a/command_windows.go b/command_windows.go index fce4a27..45b8c6f 100644 --- a/command_windows.go +++ b/command_windows.go @@ -10,6 +10,14 @@ func createBaseCommand(c *Command) *exec.Cmd { 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{ From 2ee13137e954b476b178a75cd5c43119165f5189 Mon Sep 17 00:00:00 2001 From: dylanhitt Date: Mon, 14 Nov 2022 21:22:29 -0500 Subject: [PATCH 5/5] Add the ability to change user on *nix systems --- README.md | 4 +++- command_darwin_test.go | 7 ++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 30585d7..13bcbb8 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/command_darwin_test.go b/command_darwin_test.go index 057b75a..7a575ba 100644 --- a/command_darwin_test.go +++ b/command_darwin_test.go @@ -36,10 +36,11 @@ func TestCommand_WithValidTimeout(t *testing.T) { // I really don't see the point of mocking this // as the stdlib does so already. So testing here -// seems redundant. This simply checks if we're compliant -// with api changes +// seems redundant. This simple check if we're compliant +// with an api changes func TestCommand_WithUser(t *testing.T) { - cmd := NewCommand("echo hello", WithUser(syscall.Credential{})) + 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) }