Skip to content

Commit

Permalink
use AnsiblePlaybookErrorEnrich to provide extra details on ansible-pl…
Browse files Browse the repository at this point in the history
…aybook errors
  • Loading branch information
apenella committed Apr 19, 2024
1 parent e3c840f commit 430d96b
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 3 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ _**Important:** The master branch may contain unreleased or pre-released feature
- [Execute package](#execute-package)
- [Executor interface](#executor-interface)
- [Commander interface](#commander-interface)
- [ErrorEnricher interface](#errorenricher-interface)
- [Executabler interface](#executabler-interface)
- [DefaultExecute struct](#defaultexecute-struct)
- [Defining a Custom Executor](#defining-a-custom-executor)
Expand Down Expand Up @@ -71,6 +72,7 @@ _**Important:** The master branch may contain unreleased or pre-released feature
- [AnsibleInventoryOptions struct](#ansibleinventoryoptions-struct)
- [Playbook package](#playbook-package)
- [AnsiblePlaybookCmd struct](#ansibleplaybookcmd-struct)
- [AnsiblePlaybookErrorEnrich struct](#ansibleplaybookerrorenrich-struct)
- [AnsiblePlaybookExecute struct](#ansibleplaybookexecute-struct)
- [AnsiblePlaybookOptions struct](#ansibleplaybookoptions-struct)
- [Vault package](#vault-package)
Expand Down Expand Up @@ -320,6 +322,17 @@ type Commander interface {
}
```
#### ErrorEnricher interface
The `ErrorEnricher` interface defines components responsible for enriching the error message. The [AnsiblePlaybookErrorEnrich](#ansibleplaybookerrorenrich-struct) struct implements this interface.
The [DefaultExecute](#defaultexecute-struct) struct uses this interface to enrich the error message. Below is the definition of the `ErrorEnricher` interface:
```go
type ErrorEnricher interface {
Enrich(err error) error
}
```
#### Executabler interface
The `Executabler` interface defines a component required by [DefaultExecute](#defaultexecute-struct) to execute commands. Through the `Executabler` interface, you can customize the execution of commands according to your requirements.
Expand Down Expand Up @@ -865,6 +878,10 @@ if err != nil {
}
```
#### AnsiblePlaybookErrorEnrich struct
The `AnsiblePlaybookErrorEnrich` struct, that implements the [ErrorEnricher](#errorenricher-interface) interface, is responsible for enriching the error message when executing an _ansible-playbook_ command. Based on the exit code of the command execution, the `AnsiblePlaybookErrorEnrich` struct appends additional information to the error message. This additional information includes the exit code, the command that was executed, and the error message.
#### AnsiblePlaybookExecute struct
The `AnsiblePlaybookExecute` struct serves as a streamlined [executor](#executor) for running `ansible-playbook` command. It encapsulates the setup process for both the [command generator](#command-generator) and _executor_. This _executor_ is particularly useful when no additional configuration or customization is required.
Expand Down
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Version 2.0.0 of *go-ansible* introduces several disruptive changes. Read the up
- `ansibleplaybook-ssh` example to show how to execute an Ansible playbook using SSH as the connection method.
- `AnsiblePlaybookExecute` _executor_ has been introduced. That _executor_ allows you to create an executor to run `ansible-playbook` commands using the default settings of `DefaultExecute`. This _executor_ is located in the `github.com/apenella/go-ansible/v2/pkg/execute/playbook` package.
- `Commander` interface has been introduced in the `github.com/apenella/go-ansible/v2/pkg/execute` package. This interface defines the criteria for a struct to be compliant in generating execution commands.
- `ErrorEnricher` interface has been introduced in the `github.com/apenella/go-ansible/v2/pkg/execute` package. This interface defines the criteria for a struct to be compliant in enriching the error message of the command execution.
- `Executabler` interface has been introduced in the `github.com/apenella/go-ansible/v2/pkg/execute` package. This interface defines the criteria for a struct to be compliant in executing external commands.
- `ExecutorEnvVarSetter` interface in `github.com/apenella/go-ansible/v2/pkg/execute/configuration` defines the criteria for a struct to be compliant in setting Ansible configuration.
- `ExecutorQuietStdoutCallbackSetter` interface has been introduced in the `github.com/apenella/go-ansible/v2/pkg/execute/stdoutcallback` package. This interface defines the criteria for a struct to be compliant in setting an executor that accepts the stdout callback configuration and that enables the `Quiet` method for Ansible executions.
Expand Down
35 changes: 35 additions & 0 deletions docs/upgrade_guide_to_2.x.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Proceed through the following sections to understand the changes in version _2.x
- [Changes on the interfaces](#changes-on-the-interfaces)
- [Added _Cmder_ interface](#added-cmder-interface)
- [Added _Commander_ interface](#added-commander-interface)
- [Added _ErrorEnricher_ interface](#added-errorenricher-interface)
- [Added _Executabler_ interface](#added-executabler-interface)
- [Added _ExecutorEnvVarSetter_ interface](#added-executorenvvarsetter-interface)
- [Added _ExecutorQuietStdoutCallbackSetter_ interface](#added-executorquietstdoutcallbacksetter-interface)
Expand All @@ -30,9 +31,11 @@ Proceed through the following sections to understand the changes in version _2.x
- [Replacing the _options_ argument](#replacing-the-options-argument)
- [Changes on the _DefaultExecute_ struct](#changes-on-the-defaultexecute-struct)
- [Adding _Cmd_ attribute to generate commands](#adding-cmd-attribute-to-generate-commands)
- [Adding _ErrorEnrich_ attribute to enrich error messages](#adding-errorenrich-attribute-to-enrich-error-messages)
- [Adding _Exec_ attribute for running external commands](#adding-exec-attribute-for-running-external-commands)
- [Adding _Output_ attribute for printing execution results](#adding-output-attribute-for-printing-execution-results)
- [Removing the _ShowDuration_ attribute](#removing-the-showduration-attribute)
- [Removing the error enrichment for ansible-playbook commands](#removing-the-error-enrichment-for-ansible-playbook-commands)
- [Changing the _Transformer_ location](#changing-the-transformer-location)
- [Changes on the _AnsiblePlaybookCmd_ struct](#changes-on-the-ansibleplaybookcmd-struct)
- [Renaming the _Options_ attribute](#renaming-the-options-attribute)
Expand Down Expand Up @@ -88,6 +91,17 @@ type Commander interface {
}
```

### Added _ErrorEnricher_ interface

The `ErrorEnricher` interface defined in _github.com/apenella/go-ansible/v2/pkg/execute_ is used to enrich the error message. The `DefaultExecute` struct uses that enable you to append additional information to the error message when an error occurs.

```go
// ErrorEnricher interface to enrich and customize errors
type ErrorEnricher interface {
Enrich(err error) error
}
```

### Added _Executabler_ interface

The `Executabler` interface defined in _github.com/apenella/go-ansible/v2/pkg/execute_ is used to run external commands. It is required by `DefaultExecute` struct, but you can also use it to implement your custom executor.
Expand Down Expand Up @@ -277,6 +291,21 @@ exec := execute.NewDefaultExecute(

In the above example, `playbookCmd` is of type `Commander`. The `Cmd` value is set to `playbookCmd` using the `WithCmd` function when instantiating a new `DefaultExecute`. The `DefaultExecute` will then use `playbookCmd` to generate the command for execution.

### Adding _ErrorEnrich_ attribute to enrich error messages

The `ErrorEnrich` attribute provides the component responsible for enriching error messages. The `DefaultExecute` struct uses the `ErrorEnricher` interface to append additional information to the error message when an error occurs.

You can set that attribute when you instantiate the `DefaultExecute` struct. The following code snippet demonstrates how to instantiate a `DefaultExecute` struct with a custom `ErrorEnricher`:

```go
exec := execute.NewDefaultExecute(
execute.WithCmd(cmd),
execute.WithErrorEnrich(playbook.NewAnsiblePlaybookErrorEnrich()),
)
```

That is related to the [Removing the error enrichment for ansible-playbook commands](#removing-the-error-enrichment-for-ansible-playbook-commands).

### Adding _Exec_ attribute for running external commands

The `DefaultExecute` now includes the `Exec` attribute of type `Executabler`. The `Exec` component is responsible for executing external commands. If you do not define the `Exec` attribute, it defaults to using the `OsExec` struct, which wraps the `os/exec` package.
Expand Down Expand Up @@ -362,6 +391,12 @@ if err != nil {
fmt.Println("Duration: ", exec.Duration().String())
```

### Removing the error enrichment for ansible-playbook commands

The `DefaultExecute` struct used to enrich the error message based on the exit code. Those enrichments are no longer available by default. The main reason is because those enrichments were based on the _ansible-playbook_ command exit code. However, the `DefaultExecute` is provided by the attribute `ErrorEnrich` to allow you to enrich the error messages.

That is related to [Adding _ErrorEnrich_ attribute to enrich error messages](#adding-errorenrich-attribute-to-enrich-error-messages).

### Changing the _Transformer_ location

If you configure transformers to modify the output of the execution's results, note that the _transformer_ package in the _go-ansible_ library has been relocated. It was moved from _github.com/apenella/go-ansible/pkg/stdoutcallback/results_ to _github.com/apenella/go-ansible/v2/pkg/execute/result/transformer_. Therefore, ensure that your code is adapted to this updated location.
Expand Down
7 changes: 4 additions & 3 deletions examples/ansibleplaybook-ssh/ansibleplaybook-ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ func main() {

exec := execute.NewDefaultExecute(
execute.WithCmd(cmd),
execute.WithErrorEnrich(playbook.NewAnsiblePlaybookErrorEnrich()),
)

fmt.Println("Executing command: ", cmd.String())

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

Sensitive data returned by an access to VaultPasswordFile
flows to a logging call.

err := exec.Execute(context.TODO())
if err != nil {
fmt.Println(err)
os.Exit(1)
}

fmt.Println(cmd.String())

}
1 change: 1 addition & 0 deletions pkg/execute/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Commander interface {
String() string
}

// ErrorEnricher interface to enrich and customize errors
type ErrorEnricher interface {
Enrich(err error) error
}
78 changes: 78 additions & 0 deletions pkg/playbook/ansiblePlaybookErrorEnrich.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package playbook

import (
"fmt"
"os/exec"
"syscall"

"github.com/pkg/errors"
)

const (
// TODO: error management
// AnsiblePlaybookErrorCodeGeneralError is the error code for a general error
AnsiblePlaybookErrorCodeGeneralError = 1
// AnsiblePlaybookErrorCodeOneOrMoreHostFailed is the error code for a one or more host failed
AnsiblePlaybookErrorCodeOneOrMoreHostFailed = 2
// AnsiblePlaybookErrorCodeOneOrMoreHostUnreachable is the error code for a one or more host unreachable
AnsiblePlaybookErrorCodeOneOrMoreHostUnreachable = 3
// AnsiblePlaybookErrorCodeParserError is the error code for a parser error
AnsiblePlaybookErrorCodeParserError = 4
// AnsiblePlaybookErrorCodeBadOrIncompleteOptions is the error code for a bad or incomplete options
AnsiblePlaybookErrorCodeBadOrIncompleteOptions = 5
// AnsiblePlaybookErrorCodeUserInterruptedExecution is the error code for a user interrupted execution
AnsiblePlaybookErrorCodeUserInterruptedExecution = 99
// AnsiblePlaybookErrorCodeUnexpectedError is the error code for a unexpected error
AnsiblePlaybookErrorCodeUnexpectedError = 250

// AnsiblePlaybookErrorMessageGeneralError is the error message for a general error
AnsiblePlaybookErrorMessageGeneralError = "ansible-playbook error: general error"
// AnsiblePlaybookErrorMessageOneOrMoreHostFailed is the error message for a one or more host failed
AnsiblePlaybookErrorMessageOneOrMoreHostFailed = "ansible-playbook error: one or more host failed"
// AnsiblePlaybookErrorMessageOneOrMoreHostUnreachable is the error message for a one or more host unreachable
AnsiblePlaybookErrorMessageOneOrMoreHostUnreachable = "ansible-playbook error: one or more host unreachable"
// AnsiblePlaybookErrorMessageParserError is the error message for a parser error
AnsiblePlaybookErrorMessageParserError = "ansible-playbook error: parser error"
// AnsiblePlaybookErrorMessageBadOrIncompleteOptions is the error message for a bad or incomplete options
AnsiblePlaybookErrorMessageBadOrIncompleteOptions = "ansible-playbook error: bad or incomplete options"
// AnsiblePlaybookErrorMessageUserInterruptedExecution is the error message for a user interrupted execution
AnsiblePlaybookErrorMessageUserInterruptedExecution = "ansible-playbook error: user interrupted execution"
// AnsiblePlaybookErrorMessageUnexpectedError is the error message for a unexpected error
AnsiblePlaybookErrorMessageUnexpectedError = "ansible-playbook error: unexpected error"
)

type AnsiblePlaybookErrorEnrich struct{}

// NewAnsiblePlaybookErrorEnrich creates a new AnsiblePlaybookErrorEnrich instance
func NewAnsiblePlaybookErrorEnrich() *AnsiblePlaybookErrorEnrich {
return &AnsiblePlaybookErrorEnrich{}
}

func (e *AnsiblePlaybookErrorEnrich) Enrich(err error) error {

errorMessage := ""

exitError, exists := err.(*exec.ExitError)

if exists {
ws := exitError.Sys().(syscall.WaitStatus)
switch ws.ExitStatus() {
case AnsiblePlaybookErrorCodeGeneralError:
errorMessage = fmt.Sprintf("%s\n\n%s", AnsiblePlaybookErrorMessageGeneralError, errorMessage)
case AnsiblePlaybookErrorCodeOneOrMoreHostFailed:
errorMessage = fmt.Sprintf("%s\n\n%s", AnsiblePlaybookErrorMessageOneOrMoreHostFailed, errorMessage)
case AnsiblePlaybookErrorCodeOneOrMoreHostUnreachable:
errorMessage = fmt.Sprintf("%s\n\n%s", AnsiblePlaybookErrorMessageOneOrMoreHostUnreachable, errorMessage)
case AnsiblePlaybookErrorCodeParserError:
errorMessage = fmt.Sprintf("%s\n\n%s", AnsiblePlaybookErrorMessageParserError, errorMessage)
case AnsiblePlaybookErrorCodeBadOrIncompleteOptions:
errorMessage = fmt.Sprintf("%s\n\n%s", AnsiblePlaybookErrorMessageBadOrIncompleteOptions, errorMessage)
case AnsiblePlaybookErrorCodeUserInterruptedExecution:
errorMessage = fmt.Sprintf("%s\n\n%s", AnsiblePlaybookErrorMessageUserInterruptedExecution, errorMessage)
case AnsiblePlaybookErrorCodeUnexpectedError:
errorMessage = fmt.Sprintf("%s\n\n%s", AnsiblePlaybookErrorMessageUnexpectedError, errorMessage)
}
}

return errors.Wrap(err, errorMessage)
}
1 change: 1 addition & 0 deletions pkg/playbook/ansiblePlaybookErrorEnrich_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package playbook
1 change: 1 addition & 0 deletions pkg/playbook/ansiblePlaybookExecute.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func (e *AnsiblePlaybookExecute) Execute(ctx context.Context) error {

exec := execute.NewDefaultExecute(
execute.WithCmd(e.cmd),
execute.WithErrorEnrich(NewAnsiblePlaybookErrorEnrich()),
)

err := exec.Execute(ctx)
Expand Down

0 comments on commit 430d96b

Please sign in to comment.