-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement sum test cases and a mocked Executor model
- Loading branch information
Showing
12 changed files
with
4,268 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
Supermicro Update Manager (for UEFI BIOS) 2.14.0 (2024/02/15) (ARM64) | ||
Copyright(C) 2013-2024 Super Micro Computer, Inc. All rights reserved. | ||
.................[0m | ||
[0m | ||
|
||
[1m[1m[1mNote:[0m[0m [0mNo BIOS setting has been changed. | ||
|
||
[0m[1m[1m[1mStatus:[0m[0m [0mThe BIOS configuration is updated for 10.145.129.168 | ||
|
||
[0m[1m[1m[1mNote:[0m[0m [0mYou have to reboot or power up the system for the changes to take effect. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Supermicro Update Manager (for UEFI BIOS) 2.14.0 (2024/02/15) (ARM64) | ||
Copyright(C) 2013-2024 Super Micro Computer, Inc. All rights reserved. | ||
..................[0m | ||
[0m[1m[1m[1mStatus:[0m[0m [0mThe BIOS configuration is updated for 10.145.129.168 | ||
|
||
[0m[1m[1m[1mNote:[0m[0m [0mYou have to reboot or power up the system for the changes to take effect. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
Supermicro Update Manager (for UEFI BIOS) 2.14.0 (2024/02/15) (ARM64) | ||
Copyright(C) 2013-2024 Super Micro Computer, Inc. All rights reserved. | ||
.................[0m | ||
[0m[1m[1m[1mStatus:[0m[0m [0mThe managed system 10.145.129.168 is rebooting. | ||
|
||
.............................[0mDone | ||
.... | ||
.................[0m | ||
[0m | ||
|
||
[1m[1m[1mNote:[0m[0m [0mNo BIOS setting has been changed. | ||
|
||
[0m[1m[1m[1mStatus:[0m[0m [0mThe BIOS configuration is updated for 10.145.129.168 | ||
|
||
[0m[0m[0;33m[0;33m[0;33mWARNING[0m[0m: [0mWithout option --post_complete, please manually confirm the managed system is POST complete before executing next action. | ||
|
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
Supermicro Update Manager (for UEFI BIOS) 2.14.0 (2024/02/15) (ARM64) | ||
Copyright(C) 2013-2024 Super Micro Computer, Inc. All rights reserved. | ||
.................[0m | ||
[0m[1m[1m[1mStatus:[0m[0m [0mThe managed system 10.145.129.168 is rebooting. | ||
|
||
.............................[0mDone | ||
.... | ||
.................[0m | ||
[0m | ||
|
||
[1m[1m[1mNote:[0m[0m [0mNo BIOS setting has been changed. | ||
|
||
[0m[1m[1m[1mStatus:[0m[0m [0mThe BIOS configuration is updated for 10.145.129.168 | ||
|
||
[0m[0m[0;33m[0;33m[0;33mWARNING[0m[0m: [0mWithout option --post_complete, please manually confirm the managed system is POST complete before executing next action. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package executor | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
) | ||
|
||
var ( | ||
ErrNoCommandOutput = errors.New("command returned no output") | ||
ErrVersionStrExpectedSemver = errors.New("expected version string to follow semver format") | ||
ErrFakeExecutorInvalidArgs = errors.New("invalid number of args passed to fake executor") | ||
ErrRepositoryBaseURL = errors.New("repository base URL undefined, ensure UpdateOptions.BaseURL OR UPDATE_BASE_URL env var is set") | ||
ErrNoUpdatesApplicable = errors.New("no updates applicable") | ||
ErrDmiDecodeRun = errors.New("error running dmidecode") | ||
ErrComponentListExpected = errors.New("expected a list of components to apply updates") | ||
ErrDeviceInventory = errors.New("failed to collect device inventory") | ||
ErrUnsupportedDiskVendor = errors.New("unsupported disk vendor") | ||
ErrNoUpdateHandlerForComponent = errors.New("component slug has no update handler declared") | ||
ErrBinNotExecutable = errors.New("bin has no executable bit set") | ||
ErrBinLstat = errors.New("failed to run lstat on bin") | ||
ErrBinLookupPath = errors.New("failed to lookup bin path") | ||
) | ||
|
||
// ExecError is returned when the command exits with an error or a non zero exit status | ||
type ExecError struct { | ||
Cmd string | ||
Stderr string | ||
Stdout string | ||
ExitCode int | ||
} | ||
|
||
// Error implements the error interface | ||
func (u *ExecError) Error() string { | ||
return fmt.Sprintf("cmd %s exited with error: %s\n\t exitCode: %d\n\t stdout: %s", u.Cmd, u.Stderr, u.ExitCode, u.Stdout) | ||
} | ||
|
||
func newExecError(cmd string, r *Result) *ExecError { | ||
return &ExecError{ | ||
Cmd: cmd, | ||
Stderr: string(r.Stderr), | ||
Stdout: string(r.Stdout), | ||
ExitCode: r.ExitCode, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
package executor | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"io" | ||
"os" | ||
"os/exec" | ||
"strings" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
// Executor interface lets us implement dummy executors for tests | ||
type Executor interface { | ||
ExecWithContext(context.Context) (*Result, error) | ||
SetArgs([]string) | ||
SetEnv([]string) | ||
SetQuiet() | ||
SetVerbose() | ||
GetCmd() string | ||
DisableBinCheck() | ||
SetStdin(io.Reader) | ||
CmdPath() string | ||
CheckExecutable() error | ||
// for tests | ||
SetStdout([]byte) | ||
SetStderr([]byte) | ||
SetExitCode(int) | ||
} | ||
|
||
func NewExecutor(cmd string) Executor { | ||
return &Execute{Cmd: cmd, CheckBin: true} | ||
} | ||
|
||
// An execute instace | ||
type Execute struct { | ||
Cmd string | ||
Args []string | ||
Env []string | ||
Stdin io.Reader | ||
CheckBin bool | ||
Quiet bool | ||
} | ||
|
||
// The result of a command execution | ||
type Result struct { | ||
Stdout []byte | ||
Stderr []byte | ||
ExitCode int | ||
} | ||
|
||
// GetCmd returns the command with args as a string | ||
func (e *Execute) GetCmd() string { | ||
cmd := []string{e.Cmd} | ||
cmd = append(cmd, e.Args...) | ||
|
||
return strings.Join(cmd, " ") | ||
} | ||
|
||
// CmdPath returns the absolute path to the executable | ||
// this means the caller should not have disabled CheckBin. | ||
func (e *Execute) CmdPath() string { | ||
return e.Cmd | ||
} | ||
|
||
// SetArgs sets the command args | ||
func (e *Execute) SetArgs(a []string) { | ||
e.Args = a | ||
} | ||
|
||
// SetEnv sets the env variables | ||
func (e *Execute) SetEnv(env []string) { | ||
e.Env = env | ||
} | ||
|
||
// SetQuiet lowers the verbosity | ||
func (e *Execute) SetQuiet() { | ||
e.Quiet = true | ||
} | ||
|
||
// SetVerbose does whats it says | ||
func (e *Execute) SetVerbose() { | ||
e.Quiet = false | ||
} | ||
|
||
// SetStdin sets the reader to the command stdin | ||
func (e *Execute) SetStdin(r io.Reader) { | ||
e.Stdin = r | ||
} | ||
|
||
// DisableBinCheck disables validating the binary exists and is executable | ||
func (e *Execute) DisableBinCheck() { | ||
e.CheckBin = false | ||
} | ||
|
||
// SetStdout doesn't do much, is around for tests | ||
func (e *Execute) SetStdout(_ []byte) { | ||
} | ||
|
||
// SetStderr doesn't do much, is around for tests | ||
func (e *Execute) SetStderr(_ []byte) { | ||
} | ||
|
||
// SetExitCode doesn't do much, is around for tests | ||
func (e *Execute) SetExitCode(_ int) { | ||
} | ||
|
||
// ExecWithContext executes the command and returns the Result object | ||
func (e *Execute) ExecWithContext(ctx context.Context) (result *Result, err error) { | ||
if e.CheckBin { | ||
err = e.CheckExecutable() | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
cmd := exec.CommandContext(ctx, e.Cmd, e.Args...) | ||
cmd.Env = append(cmd.Env, e.Env...) | ||
cmd.Stdin = e.Stdin | ||
|
||
var stdoutBuf, stderrBuf bytes.Buffer | ||
if !e.Quiet { | ||
cmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf) | ||
cmd.Stderr = io.MultiWriter(os.Stderr, &stderrBuf) | ||
} else { | ||
cmd.Stderr = &stderrBuf | ||
cmd.Stdout = &stdoutBuf | ||
} | ||
|
||
if err := cmd.Run(); err != nil { | ||
result = &Result{stdoutBuf.Bytes(), stderrBuf.Bytes(), cmd.ProcessState.ExitCode()} | ||
return result, newExecError(e.GetCmd(), result) | ||
} | ||
|
||
result = &Result{stdoutBuf.Bytes(), stderrBuf.Bytes(), cmd.ProcessState.ExitCode()} | ||
|
||
return result, nil | ||
} | ||
|
||
// CheckExecutable determines if the set Cmd value exists as a file and is an executable. | ||
func (e *Execute) CheckExecutable() error { | ||
var path string | ||
|
||
if strings.Contains(e.Cmd, "/") { | ||
path = e.Cmd | ||
} else { | ||
var err error | ||
path, err = exec.LookPath(e.Cmd) | ||
if err != nil { | ||
return errors.Wrap(ErrBinLookupPath, err.Error()) | ||
} | ||
|
||
e.Cmd = path | ||
} | ||
|
||
fileInfo, err := os.Lstat(path) | ||
if err != nil { | ||
return errors.Wrap(ErrBinLstat, err.Error()) | ||
} | ||
|
||
// bit mask 0111 indicates atleast one of owner, group, others has an executable bit set | ||
if fileInfo.Mode()&0o111 == 0 { | ||
return ErrBinNotExecutable | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package executor | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"fmt" | ||
"io/fs" | ||
"os" | ||
"testing" | ||
|
||
"github.com/pkg/errors" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func Test_Stdin(t *testing.T) { | ||
e := new(Execute) | ||
e.Cmd = "grep" | ||
e.Args = []string{"hello"} | ||
e.Stdin = bytes.NewReader([]byte("hello")) | ||
e.SetQuiet() | ||
|
||
result, err := e.ExecWithContext(context.Background()) | ||
if err != nil { | ||
fmt.Println(err.Error()) | ||
} | ||
|
||
assert.Equal(t, []byte("hello\n"), result.Stdout) | ||
} | ||
|
||
type checkBinTester struct { | ||
createFile bool | ||
filePath string | ||
expectedErr error | ||
fileMode uint | ||
testName string | ||
} | ||
|
||
func initCheckBinTests() []checkBinTester { | ||
return []checkBinTester{ | ||
{ | ||
false, | ||
"f", | ||
ErrBinLookupPath, | ||
0, | ||
"bin path lookup err test", | ||
}, | ||
{ | ||
false, | ||
"/tmp/f", | ||
ErrBinLstat, | ||
0, | ||
"bin exists err test", | ||
}, | ||
{ | ||
true, | ||
"/tmp/f", | ||
ErrBinNotExecutable, | ||
0o666, | ||
"bin exists with no executable bit test", | ||
}, | ||
{ | ||
true, | ||
"/tmp/j", | ||
nil, | ||
0o667, | ||
"bin with executable bit returns no error", | ||
}, | ||
{ | ||
true, | ||
"/tmp/k", | ||
nil, | ||
0o700, | ||
"bin with owner executable bit returns no error", | ||
}, | ||
{ | ||
true, | ||
"/tmp/l", | ||
nil, | ||
0o070, | ||
"bin with group executable bit returns no error", | ||
}, | ||
{ | ||
true, | ||
"/tmp/m", | ||
nil, | ||
0o007, | ||
"bin with other executable bit returns no error", | ||
}, | ||
} | ||
} | ||
|
||
func Test_CheckExecutable(t *testing.T) { | ||
tests := initCheckBinTests() | ||
for _, c := range tests { | ||
if c.createFile { | ||
f, err := os.Create(c.filePath) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
// nolint:gocritic // test code | ||
defer os.Remove(c.filePath) | ||
|
||
if c.fileMode != 0 { | ||
err = f.Chmod(fs.FileMode(c.fileMode)) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
} | ||
} | ||
|
||
e := new(Execute) | ||
e.Cmd = c.filePath | ||
err := e.CheckExecutable() | ||
assert.Equal(t, c.expectedErr, errors.Cause(err), c.testName) | ||
} | ||
} |
Oops, something went wrong.