Skip to content

Commit

Permalink
add Quiet method to DefaultExecutes that forces to remove verbosity t…
Browse files Browse the repository at this point in the history
…o command to be executed
  • Loading branch information
apenella committed Apr 10, 2024
1 parent f61260f commit 9a384e5
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 12 deletions.
3 changes: 3 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Version 2.0.0 of *go-ansible* introduces several disruptive changes. Read the up
### Fixed

- Quote properly the attributes `SCPExtraArgs`, `SFTPExtraArgs`, `SSHCommonArgs`, `SSHExtraArgs` in `AnsibleAdhocOptions` and `AnsiblePlaybookOptions` structs when generating the command to be executed. #140
- When using the JSON Stdout Callback method combined with enabled verbosity in the command, it causes an error during JSON parsing. To resolve this issue, the `DefaultExecute` struct includes the `Quiet` method, which removes verbosity from the executed command. #110

### Added

Expand All @@ -48,8 +49,10 @@ Version 2.0.0 of *go-ansible* introduces several disruptive changes. Read the up
- `github.com/apenella/go-ansible/v2/pkg/galaxy/collection/install` package has been introduced. This package allows you to install Ansible collections from the Ansible Galaxy. Along with this package, the example `workflowexecute-ansibleplaybook-with-galaxy-install-collection` has been added to demonstrate how to install an Ansible collection and execute an Ansible playbook in a sequence.
- `github.com/apenella/go-ansible/v2/pkg/galaxy/role/install` package has been introduced. This package allows you to install Ansible roles from the Ansible Galaxy. Along with this package, the example `workflowexecute-ansibleplaybook-with-galaxy-install-role` has been added to demonstrate how to install an Ansible role and execute an Ansible playbook in a sequence.
- `NewAnsibleAdhocCmd`, `NewAnsibleInventoryCmd` and `NewAnsiblePlaybookCmd` functions have been introduced. These functions are responsible for creating the `AnsibleAdhocCmd`, `AnsibleInventoryCmd` and `AnsiblePlaybookCmd` structs, respectively.
- `Path` attribute has been added to the `AnsiblePlaybookJSONResultsPlayTaskHostsItem` struct.
- `ResultsOutputer` interface has been introduced in the `github.com/apenella/go-ansible/v2/pkg/execute/result` package. This interface defines the criteria for a struct to be compliant in printing execution results.
- A utility to generate the code for the configuration package has been introduced. This utility is located in the `utils/cmd/configGenerator.go`.
- The `Quiet` method has been added to the `DefaultExecute` struct. This method forces to remove verbosity from the executed command.

### Changed

Expand Down
50 changes: 43 additions & 7 deletions pkg/execute/defaultExecute.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ type DefaultExecute struct {
Exec Executabler
// Cmd is the command generator
Cmd Commander
// quiet is a flag to set the executor in quiet mode
quiet bool
}

// NewDefaultExecute return a new DefaultExecute instance with all options
Expand Down Expand Up @@ -126,13 +128,40 @@ func (e *DefaultExecute) AddEnvVarSafe(key, value string) error {
return nil
}

func (e *DefaultExecute) Quiet() {
e.quiet = true
}

// quietCommand returns the command without the verbose flags -v, -vv, -vvv and -vvvv
func (e *DefaultExecute) quietCommand() ([]string, error) {

errContext := "(execute::DefaultExecute:quietCommand)"

command, err := e.Cmd.Command()
if err != nil {
return nil, errors.New(errContext, "Error creating command", err)
}

quietCommand := make([]string, 0)
for _, cmd := range command {
if cmd == "-v" || cmd == "-vv" || cmd == "-vvv" || cmd == "-vvvv" || cmd == "--verbose" {
continue
}
quietCommand = append(quietCommand, cmd)
}

return quietCommand, nil
}

// Execute takes a command and args and runs it, streaming output to stdout
func (e *DefaultExecute) Execute(ctx context.Context) error {

var err error
var cmdStderr, cmdStdout io.ReadCloser
var wg sync.WaitGroup

errContext := "(execute::DefaultExecute::Execute)"

defer e.checkCompatibility()

execErrChan := make(chan error)
Expand All @@ -151,12 +180,19 @@ func (e *DefaultExecute) Execute(ctx context.Context) error {
}

if e.Cmd == nil {
return errors.New("(DefaultExecute::Execute)", "Command is not defined")
return errors.New(errContext, "Command is not defined")
}

command, err := e.Cmd.Command()
if err != nil {
return errors.New("(DefaultExecute::Execute)", "Error creating command", err)
return errors.New(errContext, "Error creating command", err)
}

if e.quiet {
command, err = e.quietCommand()
if err != nil {
return errors.New(errContext, "Error creating quiet command", err)
}
}

cmd := e.Exec.CommandContext(ctx, command[0], command[1:]...)
Expand All @@ -182,13 +218,13 @@ func (e *DefaultExecute) Execute(ctx context.Context) error {
cmdStdout, err = cmd.StdoutPipe()
defer cmdStdout.Close()
if err != nil {
return errors.New("(DefaultExecute::Execute)", "Error creating stdout pipe", err)
return errors.New(errContext, "Error creating stdout pipe", err)
}

cmdStderr, err = cmd.StderrPipe()
defer cmdStderr.Close()
if err != nil {
return errors.New("(DefaultExecute::Execute)", "Error creating stderr pipe", err)
return errors.New(errContext, "Error creating stderr pipe", err)
}

if e.Output == nil {
Expand All @@ -200,7 +236,7 @@ func (e *DefaultExecute) Execute(ctx context.Context) error {

err = cmd.Start()
if err != nil {
return errors.New("(DefaultExecute::Execute)", "Error starting command", err)
return errors.New(errContext, "Error starting command", err)
}

// Waig for stdout and stderr
Expand Down Expand Up @@ -228,7 +264,7 @@ func (e *DefaultExecute) Execute(ctx context.Context) error {
wg.Wait()

if err := <-execErrChan; err != nil {
return errors.New("(DefaultExecute::Execute)", "Error managing results output", err)
return errors.New(errContext, "Error managing results output", err)
}

err = cmd.Wait()
Expand Down Expand Up @@ -267,7 +303,7 @@ func (e *DefaultExecute) Execute(ctx context.Context) error {
errorMessage = fmt.Sprintf("%s\n\n%s", AnsiblePlaybookErrorMessageUnexpectedError, errorMessage)
}
}
return errors.New("(DefaultExecute::Execute)", fmt.Sprintf("Error during command execution: %s", errorMessage))
return errors.New(errContext, fmt.Sprintf("Error during command execution: %s", errorMessage))
}
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/execute/defaultExecuteOptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func WithEnvVars(vars map[string]string) ExecuteOptions {
}
}

// WithOutput add
// WithOutput sets the output mechanism to DefaultExecutor
func WithOutput(output result.ResultsOutputer) ExecuteOptions {
return func(e *DefaultExecute) {
e.WithOutput(output)
Expand Down
97 changes: 96 additions & 1 deletion pkg/execute/defaultExecute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ func TestExecute(t *testing.T) {
var stdout, stderr bytes.Buffer
var cmdRead bytes.Buffer

errContext := "(execute::DefaultExecute::Execute)"

tests := []struct {
desc string
err error
Expand All @@ -53,7 +55,7 @@ func TestExecute(t *testing.T) {
}{
{
desc: "Testing error executing a command when Command is not defiend",
err: errors.New("(DefaultExecute::Execute)", "Command is not defined"),
err: errors.New(errContext, "Command is not defined"),
execute: NewDefaultExecute(
WithWrite(io.Writer(&stdout)),
WithWriteError(io.Writer(&stderr)),
Expand Down Expand Up @@ -92,6 +94,39 @@ func TestExecute(t *testing.T) {
e.AssertExpectations(t)
},
},
{
desc: "Testing execute a command in a quiet mode",
err: &errors.Error{},
exec: exec.NewMockCmd(),
execute: &DefaultExecute{
Exec: exec.NewMockExec(),
Cmd: mocks.NewMockAnsibleCmd([]string{"ansible-playbook", "--connection", "local", "../../test/test_site.yml"}, nil),
Write: io.Writer(&stdout),
WriterError: io.Writer(&stderr),
quiet: true,
},

prepareAssertFunc: func(e *exec.MockExec, cmd *exec.MockCmd) {
if e == nil {
t.Fatal("prepareAssertFunc requires a *exec.MockExec")
}

if cmd == nil {
t.Fatal("prepareAssertFunc requires a *exec.MockCmd")
}

cmd.On("StdoutPipe").Return(io.NopCloser(io.Reader(&cmdRead)), nil)
cmd.On("StderrPipe").Return(io.NopCloser(io.Reader(&cmdRead)), nil)
cmd.On("Start").Return(nil)
cmd.On("Wait").Return(nil)

e.On("CommandContext", context.TODO(), "ansible-playbook", []string{"--connection", "local", "../../test/test_site.yml"}).Return(cmd)
},
assertFunc: func(e *exec.MockExec, cmd *exec.MockCmd) {
cmd.AssertExpectations(t)
e.AssertExpectations(t)
},
},
}

for _, test := range tests {
Expand Down Expand Up @@ -192,6 +227,66 @@ func TestExecute(t *testing.T) {
// }
// }

// TestQuiet tests the function WithQuiet
func TestQuiet(t *testing.T) {
execute := &DefaultExecute{}
execute.Quiet()

assert.Equal(t, execute.quiet, true)
}

func TestQuietCommand(t *testing.T) {
tests := []struct {
desc string
execute *DefaultExecute
expected []string
err error
}{
{
desc: "Testing execute a command with verbose flags",
err: &errors.Error{},
execute: &DefaultExecute{
Exec: exec.NewMockExec(),
Cmd: mocks.NewMockAnsibleCmd(
[]string{
"ansible-playbook",
"--connection",
"local",
"site.yml",
"-v",
"-vv",
"-vvv",
"-vvvv",
"--verbose",
},
nil),
// The test executes the quietCommand method so it is not necessary to set the quiet flag
// quiet: true,
},

expected: []string{
"ansible-playbook",
"--connection",
"local",
"site.yml",
},
},
}

for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
t.Log(test.desc)

command, err := test.execute.quietCommand()
if err != nil {
assert.Equal(t, test.err, err)
} else {
assert.Equal(t, test.expected, command)
}
})
}
}

func TestEnviron(t *testing.T) {
tests := []struct {
desc string
Expand Down
5 changes: 5 additions & 0 deletions pkg/execute/mockExecute.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ func NewMockExecute() *MockExecute {
return &MockExecute{}
}

// Quiet is a mock
func (e *MockExecute) Quiet() {
e.Called()
}

// Execute is a mock
func (e *MockExecute) Execute(ctx context.Context) error {
args := e.Called(ctx)
Expand Down
6 changes: 6 additions & 0 deletions pkg/execute/stdoutcallback/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ type ExecutorStdoutCallbackSetter interface {
AddEnvVar(key, value string)
WithOutput(output result.ResultsOutputer)
}

// ExecutorQuietStdoutCallbackSetter extends the ExecutorStdoutCallbackSetter interface by adding a method to force the non-verbose mode in the Stdout Callback configuration
type ExecutorQuietStdoutCallbackSetter interface {
ExecutorStdoutCallbackSetter
Quiet()
}
7 changes: 4 additions & 3 deletions pkg/execute/stdoutcallback/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ const (
)

type JSONStdoutCallbackExecute struct {
executor ExecutorStdoutCallbackSetter
executor ExecutorQuietStdoutCallbackSetter
}

func NewJSONStdoutCallbackExecute(executor ExecutorStdoutCallbackSetter) *JSONStdoutCallbackExecute {
func NewJSONStdoutCallbackExecute(executor ExecutorQuietStdoutCallbackSetter) *JSONStdoutCallbackExecute {
return &JSONStdoutCallbackExecute{executor: executor}
}

func (e *JSONStdoutCallbackExecute) WithExecutor(exec ExecutorStdoutCallbackSetter) *JSONStdoutCallbackExecute {
func (e *JSONStdoutCallbackExecute) WithExecutor(exec ExecutorQuietStdoutCallbackSetter) *JSONStdoutCallbackExecute {
e.executor = exec
return e
}
Expand All @@ -33,6 +33,7 @@ func (e *JSONStdoutCallbackExecute) Execute(ctx context.Context) error {
return fmt.Errorf("JSONStdoutCallbackExecute executor requires an executor")
}

e.executor.Quiet()
e.executor.WithOutput(jsonresults.NewJSONStdoutCallbackResults())

return configuration.NewAnsibleWithConfigurationSettingsExecute(e.executor,
Expand Down
2 changes: 2 additions & 0 deletions pkg/execute/stdoutcallback/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func TestJSONStdoutCallbackExecute(t *testing.T) {
t.Run("Testing JSON stdout callback execution", func(t *testing.T) {
exec := execute.NewMockExecute()

exec.On("Quiet")
exec.On("WithOutput", mock.Anything).Return(exec)
exec.On("AddEnvVar", configuration.AnsibleStdoutCallback, JSONStdoutCallback)
exec.On("Execute", mock.Anything).Return(nil)
Expand All @@ -30,6 +31,7 @@ func TestJSONStdoutCallbackExecute(t *testing.T) {
t.Run("Testing error on JSON stdout callback when execute function returns an error", func(t *testing.T) {
exec := execute.NewMockExecute()

exec.On("Quiet")
exec.On("WithOutput", mock.Anything).Return(exec)
exec.On("AddEnvVar", configuration.AnsibleStdoutCallback, JSONStdoutCallback)
exec.On("Execute", mock.Anything).Return(errors.New("some error"))
Expand Down

0 comments on commit 9a384e5

Please sign in to comment.